forked from VoxeLibre/VoxeLibre
Compare commits
146 Commits
master
...
redstone-u
Author | SHA1 | Date |
---|---|---|
teknomunk | 53edf3393d | |
teknomunk | 31f3a5558c | |
teknomunk | e645dfd0cc | |
teknomunk | ef257f0131 | |
teknomunk | b67f2261ed | |
teknomunk | bbbc90e514 | |
teknomunk | 1da30698d7 | |
teknomunk | 74d3fff5ff | |
teknomunk | b3a04aae3b | |
teknomunk | d3072fdf20 | |
teknomunk | 781ccc04f6 | |
teknomunk | 6396b6f56c | |
teknomunk | 13888236d6 | |
teknomunk | e970a5f414 | |
teknomunk | 6107bba52f | |
teknomunk | a958fbbf71 | |
teknomunk | 27fb96afdf | |
teknomunk | 09f034de16 | |
teknomunk | 939d2c9ef0 | |
teknomunk | 3baf1a2f17 | |
teknomunk | d40b52bea5 | |
teknomunk | 3117f932a6 | |
teknomunk | e4aef86352 | |
teknomunk | aefdb963de | |
teknomunk | 6468ba7f33 | |
teknomunk | b8f0a271dd | |
teknomunk | db2f02b485 | |
teknomunk | 4ed0fe6a74 | |
teknomunk | 160863a740 | |
teknomunk | a2bb88bb2e | |
teknomunk | 120af0f434 | |
teknomunk | 10fd9bb918 | |
teknomunk | d165e0d2ed | |
teknomunk | 4cdb9fd876 | |
teknomunk | 2f0976edc6 | |
teknomunk | 9d393aa2f1 | |
teknomunk | 61a1cda7f8 | |
teknomunk | 388e63da7e | |
teknomunk | 190ce1b811 | |
teknomunk | 1054d38b4e | |
teknomunk | a980446315 | |
teknomunk | 0ad7ddf2c6 | |
teknomunk | 35bc1b6be4 | |
teknomunk | ccf5882a98 | |
teknomunk | e5fb891d99 | |
teknomunk | e9c4cdf62f | |
teknomunk | 94c1026ba3 | |
teknomunk | f3b0ee67ed | |
teknomunk | 59f64ca947 | |
teknomunk | 80a45c2c0d | |
teknomunk | a96c3fe3ac | |
teknomunk | fba49df2f0 | |
teknomunk | b0fff9f3e9 | |
teknomunk | 6d0ce3ffd1 | |
teknomunk | fee12804f8 | |
teknomunk | a149ef5f05 | |
teknomunk | db2c200136 | |
teknomunk | 99dec4217c | |
teknomunk | e5f4650114 | |
teknomunk | acb246b88e | |
teknomunk | 51036b0592 | |
teknomunk | 9781627bb2 | |
teknomunk | c56d98ab2f | |
teknomunk | 49ef40aa3c | |
teknomunk | 5c351ae258 | |
teknomunk | 581dcd7b3f | |
teknomunk | c04ed709e0 | |
teknomunk | fe3e783f6f | |
teknomunk | d1b5e77ca1 | |
teknomunk | 778e42165a | |
teknomunk | 15c13b7bb3 | |
teknomunk | a4987f63e9 | |
teknomunk | 79d068b98a | |
teknomunk | 79b3b3b26b | |
teknomunk | 475d6f3e93 | |
teknomunk | e59a89eb2a | |
teknomunk | a48c58a244 | |
teknomunk | 52904ef5e0 | |
teknomunk | 095ad465e5 | |
teknomunk | db96e86d57 | |
teknomunk | dcf833907e | |
teknomunk | 24bf99df44 | |
teknomunk | 386bde698d | |
teknomunk | 3ca5bd0dff | |
teknomunk | bacc8bdf64 | |
teknomunk | ce9e3481af | |
teknomunk | 659a387256 | |
teknomunk | b6e51a3c40 | |
teknomunk | 6d9cae1034 | |
teknomunk | e6664de6e7 | |
teknomunk | fd03fc0027 | |
teknomunk | c9883cc6cc | |
teknomunk | 5bd06f5c03 | |
teknomunk | 284c366136 | |
teknomunk | c1a7001c31 | |
teknomunk | 8cd9ee9b32 | |
teknomunk | 3391a28fa9 | |
teknomunk | ad11fc22ec | |
teknomunk | 4589206985 | |
teknomunk | 19e2dc58eb | |
teknomunk | 4b8cb85319 | |
teknomunk | 7b7dfd1da3 | |
teknomunk | 603577e7a6 | |
teknomunk | 34f394d8dc | |
teknomunk | 75f394e5ab | |
teknomunk | 478c488c85 | |
teknomunk | 4265f0b428 | |
teknomunk | 57ad709bdd | |
teknomunk | 296c301669 | |
teknomunk | 4c88eb1439 | |
teknomunk | 4db70fd729 | |
teknomunk | 6f7e1df002 | |
teknomunk | c18976c0ca | |
teknomunk | ff5e185629 | |
teknomunk | 64b930ac3e | |
teknomunk | f9e8f60a1c | |
teknomunk | ec2d08524e | |
teknomunk | 6c6bfcfcb2 | |
teknomunk | b0c075714f | |
teknomunk | 44f9a3e619 | |
teknomunk | 1618693726 | |
teknomunk | fd6f4ee80e | |
teknomunk | d479bfb711 | |
teknomunk | c2be93e0d5 | |
teknomunk | 0ef0ae6bee | |
teknomunk | 0fa067d3ee | |
teknomunk | e4eb38db9c | |
teknomunk | b4a0ae9c56 | |
teknomunk | 37d07b6201 | |
teknomunk | 52bca90ae2 | |
teknomunk | af9409c69f | |
teknomunk | 0951acd06c | |
teknomunk | f57d202a9d | |
teknomunk | b51496ad8e | |
teknomunk | adddaec69a | |
teknomunk | a0463b564a | |
teknomunk | 8c6b011c91 | |
teknomunk | f2c6f069d0 | |
teknomunk | 15000be8ec | |
teknomunk | f3fac3586f | |
teknomunk | 9be0d27dd8 | |
teknomunk | bee32418c4 | |
teknomunk | 5d14d43ec4 | |
teknomunk | 8a91f04cf0 | |
teknomunk | 2a9aaa02af | |
teknomunk | 44514e0fad |
|
@ -59,6 +59,15 @@ function mcl_util.mcl_log(message, module, bypass_default_logger)
|
|||
minetest.log(selected_module .. " " .. message)
|
||||
end
|
||||
end
|
||||
function mcl_util.make_mcl_logger(label, option)
|
||||
-- Return dummy function if debug option isn't set
|
||||
if not minetest.settings:get_bool(option,false) then return function() end, false end
|
||||
|
||||
local label_text = "["..tostring(label).."]"
|
||||
return function(message)
|
||||
mcl_util.mcl_log(message, label_text, true)
|
||||
end, true
|
||||
end
|
||||
|
||||
local player_timers = {}
|
||||
|
||||
|
@ -345,6 +354,33 @@ function mcl_util.hopper_push(pos, dst_pos)
|
|||
return ok
|
||||
end
|
||||
|
||||
function mcl_util.hopper_pull_to_inventory(hop_inv, hop_list, src_pos, pos)
|
||||
-- TODO: merge together with hopper_pull after https://git.minetest.land/MineClone2/MineClone2/pulls/4190 is merged
|
||||
-- Get node pos' for item transfer
|
||||
local src = minetest.get_node(src_pos)
|
||||
if not minetest.registered_nodes[src.name] then return end
|
||||
local src_type = minetest.get_item_group(src.name, "container")
|
||||
if src_type ~= 2 then return end
|
||||
local src_def = minetest.registered_nodes[src.name]
|
||||
|
||||
local src_list = 'main'
|
||||
local src_inv, stack_id
|
||||
|
||||
if src_def._mcl_hoppers_on_try_pull then
|
||||
src_inv, src_list, stack_id = src_def._mcl_hoppers_on_try_pull(src_pos, pos, hop_inv, hop_list)
|
||||
else
|
||||
local src_meta = minetest.get_meta(src_pos)
|
||||
src_inv = src_meta:get_inventory()
|
||||
stack_id = mcl_util.select_stack(src_inv, src_list, hop_inv, hop_list)
|
||||
end
|
||||
|
||||
if stack_id ~= nil then
|
||||
local ok = mcl_util.move_item(src_inv, src_list, stack_id, hop_inv, hop_list)
|
||||
if src_def._mcl_hoppers_on_after_pull then
|
||||
src_def._mcl_hoppers_on_after_pull(src_pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Try pulling from source inventory to hopper inventory
|
||||
---@param pos Vector
|
||||
---@param src_pos Vector
|
||||
|
@ -1103,3 +1139,124 @@ function mcl_util.is_it_christmas()
|
|||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local function table_merge(base, overlay)
|
||||
for k,v in pairs(overlay) do
|
||||
if type(base[k]) == "table" and type(v) == "table" then
|
||||
table_merge(base[k], v)
|
||||
else
|
||||
base[k] = v
|
||||
end
|
||||
end
|
||||
return base
|
||||
end
|
||||
mcl_util.table_merge = table_merge
|
||||
|
||||
function mcl_util.table_keys(t)
|
||||
local keys = {}
|
||||
for k,_ in pairs(t) do
|
||||
keys[#keys + 1] = k
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
||||
local uuid_to_aoid_cache = {}
|
||||
local function scan_active_objects()
|
||||
-- Update active object ids for all active objects
|
||||
for active_object_id,o in pairs(minetest.luaentities) do
|
||||
o._active_object_id = active_object_id
|
||||
if o._uuid then
|
||||
uuid_to_aoid_cache[o._uuid] = active_object_id
|
||||
end
|
||||
end
|
||||
end
|
||||
function mcl_util.get_active_object_id(obj)
|
||||
local le = obj:get_luaentity()
|
||||
|
||||
-- If the active object id in the lua entity is correct, return that
|
||||
if le._active_object_id and minetest.luaentities[le._active_object_id] == le then
|
||||
return le._active_object_id
|
||||
end
|
||||
|
||||
scan_active_objects()
|
||||
|
||||
return le._active_object_id
|
||||
end
|
||||
function mcl_util.get_active_object_id_from_uuid(uuid)
|
||||
return uuid_to_aoid_cache[uuid] or scan_active_objects() or uuid_to_aoid_cache[uuid]
|
||||
end
|
||||
function mcl_util.get_luaentity_from_uuid(uuid)
|
||||
return minetest.luaentities[ mcl_util.get_active_object_id_from_uuid(uuid) ]
|
||||
end
|
||||
function mcl_util.gen_uuid()
|
||||
-- Generate a random 128-bit ID that can be assumed to be unique
|
||||
-- To have a 1% chance of a collision, there would have to be 1.6x10^76 IDs generated
|
||||
-- https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
|
||||
local u = {}
|
||||
for i = 1,16 do
|
||||
u[#u + 1] = string.format("%02X",math.random(1,255))
|
||||
end
|
||||
return table.concat(u)
|
||||
end
|
||||
function mcl_util.assign_uuid(obj)
|
||||
assert(obj)
|
||||
|
||||
local le = obj:get_luaentity()
|
||||
if not le._uuid then
|
||||
le._uuid = mcl_util.gen_uuid()
|
||||
end
|
||||
|
||||
-- Update the cache with this new id
|
||||
local aoid = mcl_util.get_active_object_id(obj)
|
||||
uuid_to_aoid_cache[le._uuid] = aoid
|
||||
|
||||
return le._uuid
|
||||
end
|
||||
function mcl_util.metadata_last_act(meta, name, delay)
|
||||
local last_act = meta:get_float(name)
|
||||
local now = minetest.get_us_time() * 1e-6
|
||||
if last_act > now + 0.5 then
|
||||
-- Last action was in the future, clock went backwards, so reset
|
||||
elseif last_act >= now - delay then
|
||||
return false
|
||||
end
|
||||
|
||||
meta:set_float(name, now)
|
||||
return true
|
||||
end
|
||||
|
||||
-- Call a function safely and provide a backtrace on error
|
||||
function mcl_util.call_safe(label, func, args)
|
||||
local function caller()
|
||||
return func(unpack(args))
|
||||
end
|
||||
|
||||
local ok,ret = xpcall(caller, debug.traceback)
|
||||
if not ok then
|
||||
minetest.log("error",(label or "")..ret)
|
||||
end
|
||||
|
||||
return ok,ret
|
||||
end
|
||||
|
||||
function mcl_util.force_get_node(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
if node.name ~= "ignore" then return node end
|
||||
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local emin, emax = vm:read_from_map(pos, pos)
|
||||
local area = VoxelArea:new{
|
||||
MinEdge = emin,
|
||||
MaxEdge = emax,
|
||||
}
|
||||
local data = vm:get_data()
|
||||
local param_data = vm:get_light_data()
|
||||
local param2_data = vm:get_param2_data()
|
||||
|
||||
local vi = area:indexp(pos)
|
||||
return {
|
||||
name = minetest.get_name_from_content_id(data[vi]),
|
||||
param = param_data[vi],
|
||||
param2 = param2_data[vi]
|
||||
}
|
||||
end
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
local mod = vl_scheduler
|
||||
|
||||
function Class()
|
||||
local cls = {}
|
||||
cls.mt = { __index = cls }
|
||||
function cls:new(...)
|
||||
local inst = setmetatable({}, cls.mt)
|
||||
local construct = inst.construct
|
||||
if construct then inst:construct(...) end
|
||||
return inst
|
||||
end
|
||||
return cls
|
||||
end
|
||||
|
||||
-- Amoritized O(1) insert/delete functional First In, First Out (FIFO) queue
|
||||
local fifo = Class()
|
||||
mod.fifo = fifo
|
||||
function fifo:insert(node)
|
||||
if not node then return end
|
||||
|
||||
node.next = self.inbox
|
||||
self.inbox = node
|
||||
end
|
||||
function fifo:insert_many(nodes)
|
||||
while nodes do
|
||||
local node = nodes
|
||||
nodes = nodes.next
|
||||
|
||||
node.next = self.inbox
|
||||
self.inbox = node.next
|
||||
end
|
||||
end
|
||||
function fifo:get()
|
||||
if not fifo.outbox then
|
||||
-- reverse inbox
|
||||
local iter = self.inbox
|
||||
self.inbox = nil
|
||||
|
||||
while iter do
|
||||
local i = iter
|
||||
iter = iter.next
|
||||
|
||||
i.next = self.outbox
|
||||
self.outbox = i
|
||||
end
|
||||
end
|
||||
|
||||
local res = self.outbox
|
||||
if res then
|
||||
self.outbox = res.next
|
||||
res.next = nil
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
|
||||
vl_scheduler = {}
|
||||
local mod = vl_scheduler
|
||||
|
||||
-- Imports
|
||||
local call_safe = mcl_util.call_safe
|
||||
|
||||
dofile(modpath.."/queue.lua")
|
||||
dofile(modpath.."/fifo.lua")
|
||||
dofile(modpath.."/test.lua")
|
||||
|
||||
local run_queues = {}
|
||||
for i = 1,4 do
|
||||
run_queues[i] = mod.fifo:new()
|
||||
end
|
||||
local tasks = 0
|
||||
local time = 0
|
||||
local priority_queue = mod.queue:new()
|
||||
local functions = {}
|
||||
local function_id_from_name = {}
|
||||
|
||||
local unpack = unpack
|
||||
local minetest_get_us_time = minetest.get_us_time
|
||||
local queue_add_task = mod.queue.add_task
|
||||
local queue_get = mod.queue.get
|
||||
local queue_tick = mod.queue.tick
|
||||
local fifo_insert = mod.fifo.insert
|
||||
local fifo_get = mod.fifo.get
|
||||
|
||||
function mod.add_task(time, name, priority, args)
|
||||
if priority then
|
||||
if priority > 4 then priority = 4 end
|
||||
if priority < 1 then priority = 1 end
|
||||
end
|
||||
|
||||
local fid = function_id_from_name[name]
|
||||
if not fid then
|
||||
print("Trying to add task with unknown function "..name)
|
||||
return
|
||||
end
|
||||
local dtime = math.floor(time * 20) + 1
|
||||
local task = {
|
||||
time = dtime, -- Used by scheduler to track how long until this task is dispatched
|
||||
dtime = dtime, -- Original time amount
|
||||
fid = fid,
|
||||
priority = priority,
|
||||
args = args,
|
||||
}
|
||||
queue_add_task(priority_queue, task)
|
||||
end
|
||||
|
||||
function mod.register_function(name, func, default_time, default_priority)
|
||||
-- Validate module in name
|
||||
local modname = minetest.get_current_modname()
|
||||
if string.sub(name,1,#modname+1) ~= (modname..":") and string.sub(name,1) ~= ":" then
|
||||
error("Module "..modname.." is trying to register function '"..name.."' that doesn't start with either '"..modname..":' or ':'")
|
||||
end
|
||||
if string.sub(name,1) == ":" then
|
||||
name = string.sub(name,2,#name-1)
|
||||
end
|
||||
|
||||
local fid = #functions + 1
|
||||
functions[fid] = {
|
||||
func = func,
|
||||
name = name,
|
||||
fid = fid,
|
||||
}
|
||||
function_id_from_name[name] = fid
|
||||
print("Registering "..name.." as #"..tostring(fid))
|
||||
|
||||
-- Provide a function to easily schedule tasks
|
||||
local dtime = math.floor((default_time or 0) * 20)+1
|
||||
if not default_priority then default_priority = 3 end
|
||||
return function(...)
|
||||
local task = {
|
||||
time = dtime,
|
||||
dtime = dtime,
|
||||
fid = fid,
|
||||
priority = default_priority,
|
||||
args = ...
|
||||
}
|
||||
queue_add_task(priority_queue, task)
|
||||
end
|
||||
end
|
||||
|
||||
-- Examples of scheduler task:
|
||||
if false then
|
||||
-- Register a task that runs ever 0.25 seconds
|
||||
mod.register_function("vl_scheduler:test",function(task)
|
||||
print("game time="..tostring(minetest.get_gametime()))
|
||||
|
||||
-- Reschedule task
|
||||
task.time = 0.25
|
||||
return task
|
||||
end)()
|
||||
|
||||
-- Register a function that runs an action at a fixed time in the future
|
||||
local act = mod.register_function("vl_scheduler:act",function(task,arg1)
|
||||
print(dump(arg1))
|
||||
end, 0.15, 1)
|
||||
act("test")
|
||||
end
|
||||
|
||||
local function run_scheduler(dtime)
|
||||
if dtime > 0.11 then
|
||||
minetest.log("warning", "Timestep greater than 110ms("..tostring(math.floor(dtime*1000)).." ms), server lag detected")
|
||||
end
|
||||
|
||||
local start_time = minetest_get_us_time()
|
||||
local end_time = start_time + 50000
|
||||
time = time + dtime
|
||||
|
||||
-- Add tasks to the run queues
|
||||
local iter = queue_tick(priority_queue)
|
||||
while iter do
|
||||
local task = iter
|
||||
iter = iter.next
|
||||
|
||||
local priority = task.priority or 3
|
||||
|
||||
fifo_insert(run_queues[priority], task)
|
||||
tasks = tasks + 1
|
||||
end
|
||||
local task_time = minetest_get_us_time()
|
||||
--print("Took "..tostring(task_time-start_time).." us to update task list")
|
||||
|
||||
-- Run tasks until we run out of timeslice
|
||||
if tasks > 0 then
|
||||
local i = 1
|
||||
while i < 4 and minetest_get_us_time() < end_time do
|
||||
local task = fifo_get(run_queues[i])
|
||||
if task then
|
||||
tasks = tasks - 1
|
||||
local func = functions[task.fid]
|
||||
if func then
|
||||
--print("Running task "..dump(task)..",func="..dump(func))
|
||||
local ok,ret = call_safe(
|
||||
"Error while running task "..func.name..": ",
|
||||
func.func, {task, unpack(task.args or {})}
|
||||
)
|
||||
|
||||
-- If the task was returned, reschedule it
|
||||
if ret == task then
|
||||
task.time = math.floor(task.time * 20) + 1
|
||||
task.next = nil
|
||||
queue_add_task(priority_queue, task)
|
||||
end
|
||||
local next_task_time = minetest_get_us_time()
|
||||
print(func.name.." took "..(next_task_time-task_time).." us")
|
||||
task_time = next_task_time
|
||||
end
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
--print("Total scheduler time: "..tostring(minetest_get_us_time() - start_time).." microseconds")
|
||||
--print("priority_queue="..dump(priority_queue))
|
||||
end
|
||||
|
||||
minetest.register_globalstep(function(dtime) run_scheduler(dtime) end)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
name = vl_scheduler
|
||||
author = teknomunk
|
||||
description = Event and Process Scheduler
|
||||
depends = mcl_util
|
|
@ -0,0 +1,262 @@
|
|||
local mod = vl_scheduler
|
||||
local DEBUG = false
|
||||
|
||||
--[[
|
||||
|
||||
== Radix/Finger queue class
|
||||
|
||||
This is a queue based off the concepts behind finger trees (https://en.wikipedia.org/wiki/Finger_tree),
|
||||
the radix sort (https://en.wikipedia.org/wiki/Radix_sort) and funnel sort (https://en.wikipedia.org/wiki/Funnelsort)
|
||||
|
||||
This algorithm has O(1) deletion and O(k) insertion (k porportional to the time in the future) and uses log_4(n) tree nodes.
|
||||
|
||||
At the top level of the queue, there is a 20-element array of linked lists containing tasks that are scheduled to
|
||||
start running in the current second. Removing the next linked list of tasks from this array is an O(1) operation.
|
||||
|
||||
The queue then has a one-sided finger tree of 20-element lists that are used to replace the initial queue when the
|
||||
second rolls over.
|
||||
|
||||
]]
|
||||
|
||||
function Class()
|
||||
local cls = {}
|
||||
cls.mt = { __index = cls }
|
||||
function cls:new(...)
|
||||
local inst = setmetatable({}, cls.mt)
|
||||
local construct = inst.construct
|
||||
if construct then inst:construct(...) end
|
||||
return inst
|
||||
end
|
||||
return cls
|
||||
end
|
||||
|
||||
local inner_queue = Class()
|
||||
|
||||
-- Imperative forward declarations
|
||||
local inner_queue_construct
|
||||
local inner_queue_get
|
||||
local inner_queue_insert_task
|
||||
local inner_queue_add_tasks
|
||||
local inner_queue_init_slot_list
|
||||
|
||||
function inner_queue:construct(level)
|
||||
self.level = level
|
||||
self.slots = 4
|
||||
--self.items = {}
|
||||
self.unsorted_count = 0
|
||||
|
||||
-- Precompute slot size
|
||||
local slot_size = 20
|
||||
for i = 2,level do
|
||||
slot_size = slot_size * 4
|
||||
end
|
||||
self.slot_size = slot_size
|
||||
end
|
||||
inner_queue_construct = inner_queue.construct
|
||||
|
||||
function inner_queue:get()
|
||||
local slots = self.slots
|
||||
local slot = 5 - slots
|
||||
if not self.items then
|
||||
self.items = inner_queue_init_slot_list(self)
|
||||
end
|
||||
local ret = self.items[slot]
|
||||
self.items[slot] = nil
|
||||
|
||||
-- Take a way a slot, then refill if needed
|
||||
slots = slots - 1
|
||||
if slots == 0 then
|
||||
if self.next_level then
|
||||
local next_level_get = inner_queue_get(self.next_level)
|
||||
if next_level_get then
|
||||
self.items = next_level_get.items
|
||||
else
|
||||
self.items = inner_queue_init_slot_list(self)
|
||||
end
|
||||
else
|
||||
self.items = inner_queue_init_slot_list(self)
|
||||
end
|
||||
slots = 4
|
||||
end
|
||||
self.slots = slots
|
||||
|
||||
return ret
|
||||
end
|
||||
inner_queue_get = inner_queue.get
|
||||
|
||||
function inner_queue:insert_task(task)
|
||||
local slots = self.slots
|
||||
local slot_size = self.slot_size
|
||||
local level = self.level
|
||||
|
||||
local t = task.time
|
||||
if DEBUG then
|
||||
task.log = tostring(t).."(1)<- "..(task.log or "")
|
||||
print("<"..tostring(self.level).."> t="..tostring(t)..",task.time="..tostring(task.time)..",time="..tostring(time))
|
||||
end
|
||||
if not (t >= 1 ) then
|
||||
error("Invalid time: task="..dump(task))
|
||||
end
|
||||
|
||||
if t > slot_size * slots then
|
||||
-- Add to list for next level in the finger tree
|
||||
local count = self.unsorted_count + 1
|
||||
if count > 20 then
|
||||
if not self.next_level then
|
||||
self.next_level = inner_queue:new(self.level + 1)
|
||||
end
|
||||
|
||||
inner_queue_add_tasks( self.next_level, self.first_unsorted, slot_size * slots)
|
||||
self.first_unsorted = nil
|
||||
self.unsorted_count = 0
|
||||
count = 0
|
||||
end
|
||||
|
||||
task.next = self.first_unsorted
|
||||
self.first_unsorted = task
|
||||
self.unsorted_count = count + 1
|
||||
return
|
||||
end
|
||||
|
||||
-- Task belongs in a slot on this level
|
||||
if DEBUG then
|
||||
print("t="..tostring(t)..",slot_size="..tostring(slot_size)..",slots="..tostring(slots))
|
||||
end
|
||||
local slot = math.floor((t-1) / slot_size) + 1 + ( 4 - slots )
|
||||
t = (t - 1) % slot_size + 1
|
||||
if DEBUG then
|
||||
print("slot="..tostring(slot)..",t="..tostring(t)..",slots="..tostring(slots))
|
||||
end
|
||||
task.time = t
|
||||
if DEBUG then
|
||||
task.log = tostring(t).."(2)<- "..(task.log or "")
|
||||
end
|
||||
|
||||
-- Lazily initialize items
|
||||
if not self.items then
|
||||
self.items = inner_queue_init_slot_list(self)
|
||||
end
|
||||
|
||||
-- Get the sublist the item belongs in
|
||||
local list = self.items[slot]
|
||||
if not list then
|
||||
print("self="..dump(self))
|
||||
end
|
||||
if level == 1 then
|
||||
assert(task.time <= 20)
|
||||
task.next = list[t]
|
||||
list[t] = task
|
||||
else
|
||||
inner_queue_insert_task(list, task, 0)
|
||||
end
|
||||
end
|
||||
inner_queue_insert_task = inner_queue.insert_task
|
||||
|
||||
function inner_queue:add_tasks(tasks, time)
|
||||
if DEBUG then
|
||||
print("inner_queue<"..tostring(self.level)..">:add_tasks()")
|
||||
end
|
||||
local task = tasks
|
||||
local slots = self.slots
|
||||
local slot_size = self.slot_size
|
||||
|
||||
if DEBUG then
|
||||
print("This queue handles times 1-"..tostring(slot_size*slots))
|
||||
end
|
||||
while task do
|
||||
local curr_task = task
|
||||
task = task.next
|
||||
curr_task.next = nil
|
||||
curr_task.time = curr_task.time - time
|
||||
|
||||
inner_queue_insert_task(self, curr_task)
|
||||
end
|
||||
|
||||
if DEBUG then
|
||||
print("self="..dump(self))
|
||||
end
|
||||
end
|
||||
inner_queue_add_tasks = inner_queue.add_tasks
|
||||
|
||||
function inner_queue:init_slot_list()
|
||||
local level = self.level
|
||||
if level == 1 then
|
||||
return { {}, {}, {}, {} }
|
||||
else
|
||||
local r = {}
|
||||
for i=1,4 do
|
||||
r[i] = inner_queue:new(level - 1)
|
||||
end
|
||||
return r
|
||||
end
|
||||
end
|
||||
inner_queue_init_slot_list = inner_queue.init_slot_list
|
||||
|
||||
local queue = Class()
|
||||
mod.queue = queue
|
||||
function queue:construct()
|
||||
self.items = {}
|
||||
self.unsorted_count = 0
|
||||
self.m_tick = 1
|
||||
self.next_level = inner_queue:new(1)
|
||||
end
|
||||
function queue:add_task(task)
|
||||
-- Adjust time to align with the start of the current second
|
||||
local t = task.time
|
||||
task.original_time = t
|
||||
t = t + self.m_tick
|
||||
if DEBUG then
|
||||
print("add_task({ time="..tostring(t).." })")
|
||||
end
|
||||
|
||||
-- Handle task in current seccond
|
||||
if t <= 20 then
|
||||
task.next = self.items[t]
|
||||
self.items[t] = task
|
||||
return
|
||||
end
|
||||
|
||||
-- Update task time
|
||||
t = t - 20
|
||||
task.time = t
|
||||
|
||||
local count = self.unsorted_count
|
||||
if count > 20 then
|
||||
-- Push to next level
|
||||
self.unsorted_count = 0
|
||||
inner_queue_add_tasks( self.next_level, self.first_unsorted, 0)
|
||||
self.first_unsorted = nil
|
||||
count = 0
|
||||
end
|
||||
|
||||
-- Add to the list of tasks for later time slots
|
||||
task.next = self.first_unsorted
|
||||
task.time = task.time
|
||||
self.first_unsorted = task
|
||||
self.unsorted_count = count + 1
|
||||
end
|
||||
function queue:tick()
|
||||
-- Get the tasks for this tick
|
||||
local ret = nil
|
||||
if self.items then
|
||||
ret = self.items[self.m_tick]
|
||||
self.items[self.m_tick] = nil
|
||||
end
|
||||
self.m_tick = self.m_tick + 1
|
||||
|
||||
-- Handle second rollover
|
||||
if self.m_tick == 21 then
|
||||
-- Push items to next level
|
||||
if self.first_unsorted then
|
||||
inner_queue_add_tasks(self.next_level, self.first_unsorted, 0)
|
||||
self.first_unsorted = nil
|
||||
self.unsorted_count = 0
|
||||
end
|
||||
|
||||
self.items = inner_queue_get(self.next_level) or {}
|
||||
self.m_tick = 1
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
local mod = vl_scheduler
|
||||
function mod.test()
|
||||
local t = mod.queue.new()
|
||||
|
||||
local pr = PseudoRandom(123456789)
|
||||
|
||||
local start_time = minetest.get_us_time()
|
||||
for i=1,500 do
|
||||
t:add_task({ time = pr:next(1,3600) })
|
||||
|
||||
local stop_time = minetest.get_us_time()
|
||||
print("took "..tostring(stop_time - start_time).."us")
|
||||
start_time = stop_time
|
||||
end
|
||||
|
||||
--print(dump(t:tick()))
|
||||
print(dump(t))
|
||||
|
||||
print("starting ticks")
|
||||
|
||||
local start_time = minetest.get_us_time()
|
||||
for i=1,3600 do
|
||||
local s = t:tick()
|
||||
print("time="..tostring(i+1))
|
||||
--print(dump(s))
|
||||
|
||||
local stop_time = minetest.get_us_time()
|
||||
print("took "..tostring(stop_time - start_time).."us")
|
||||
start_time = stop_time
|
||||
end
|
||||
end
|
||||
|
||||
--mod.test()
|
||||
--error("test failed")
|
|
@ -35,7 +35,9 @@ function mcl_entity_invs.load_inv(ent,size)
|
|||
mcl_log("load_inv 3")
|
||||
inv = minetest.create_detached_inventory(ent._inv_id, inv_callbacks)
|
||||
inv:set_size("main", size)
|
||||
if ent._items then
|
||||
if ent._mcl_entity_invs_load_items then
|
||||
inv:set_list("main",ent:_mcl_entity_invs_load_items())
|
||||
elseif ent._items then
|
||||
inv:set_list("main",ent._items)
|
||||
end
|
||||
else
|
||||
|
@ -46,9 +48,14 @@ end
|
|||
|
||||
function mcl_entity_invs.save_inv(ent)
|
||||
if ent._inv then
|
||||
ent._items = {}
|
||||
local items = {}
|
||||
for i,it in ipairs(ent._inv:get_list("main")) do
|
||||
ent._items[i] = it:to_string()
|
||||
items[i] = it:to_string()
|
||||
end
|
||||
if ent._mcl_entity_invs_save_items then
|
||||
ent:_mcl_entity_invs_save_items(items)
|
||||
else
|
||||
ent._items = items
|
||||
end
|
||||
minetest.remove_detached_inventory(ent._inv_id)
|
||||
ent._inv = nil
|
||||
|
|
|
@ -832,6 +832,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
_insta_collect = self._insta_collect,
|
||||
_flowing = self._flowing,
|
||||
_removed = self._removed,
|
||||
_immortal = self._immortal,
|
||||
})
|
||||
-- sfan5 guessed that the biggest serializable item
|
||||
-- entity would have a size of 65530 bytes. This has
|
||||
|
@ -884,6 +885,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
self._insta_collect = data._insta_collect
|
||||
self._flowing = data._flowing
|
||||
self._removed = data._removed
|
||||
self._immortal = data._immortal
|
||||
end
|
||||
else
|
||||
self.itemstring = staticdata
|
||||
|
@ -976,7 +978,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
if self._collector_timer then
|
||||
self._collector_timer = self._collector_timer + dtime
|
||||
end
|
||||
if time_to_live > 0 and self.age > time_to_live then
|
||||
if time_to_live > 0 and ( self.age > time_to_live and not self._immortal ) then
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
return
|
||||
|
|
|
@ -0,0 +1,284 @@
|
|||
# Table of Contents
|
||||
1. [Useful Constants](#useful-constants)
|
||||
2. [Rail](#rail)
|
||||
1. [Constants](#constants)
|
||||
2. [Functions](#functions)
|
||||
3. [Node Definition Options](#node-definition-options)
|
||||
3. [Cart Functions](#cart-functions)
|
||||
4. [Cart Data Functions](#cart-data-functions)
|
||||
5. [Cart-Node Interactions](#cart-node-interactions)
|
||||
6. [Train Functions](#train-functions)
|
||||
|
||||
## Useful Constants
|
||||
|
||||
- `mcl_minecarts.north`
|
||||
- `mcl_minecarts.south`
|
||||
- `mcl_minecarts.east`
|
||||
- `mcl_minecarts.west`
|
||||
|
||||
Human-readable names for the cardinal directions.
|
||||
|
||||
- `mcl_minecarts.SPEED_MAX`
|
||||
|
||||
Maximum speed that minecarts will be accelerated to with powered rails, in blocks per
|
||||
second. Defined as 10 blocks/second.
|
||||
|
||||
- `mcl_minecarts.CART_BLOCKS_SIZE`
|
||||
|
||||
The size of blocks to use when searching for carts to respawn. Defined as is 64 blocks.
|
||||
|
||||
- `mcl_minecarts.FRICTION`
|
||||
|
||||
Rail friction. Defined as is 0.4 blocks/second^2.
|
||||
|
||||
- `mcl_minecarts.MAX_TRAIN_LENGTH`
|
||||
|
||||
The maximum number of carts that can be in a single train. Defined as 4 carts.
|
||||
|
||||
- `mcl_minecarts.PASSENGER_ATTACH_POSITION`
|
||||
|
||||
Where to attach passengers to the minecarts.
|
||||
|
||||
## Rail
|
||||
|
||||
### Constants
|
||||
|
||||
`mcl_minecarts.HORIZONTAL_CURVES_RULES`
|
||||
`mcl_minecarts.HORIZONTAL_STANDARD_RULES`
|
||||
|
||||
Rail connection rules. Each rule is an table with the following indexes:
|
||||
|
||||
1. `node_name_suffix` - The suffix added to a node's `_mcl_minecarts.base_name` to
|
||||
get the name of the node to use for this connection.
|
||||
2. `param2_value` - The value of the node's param2. Used to specify rotation.
|
||||
|
||||
and the following named options:
|
||||
|
||||
- `mask` - Directional connections mask
|
||||
- `score` - priority of the rule. If more than one rule matches, the one with the
|
||||
highest store is selected.
|
||||
- `can_slope` - true if the result of this rule can be converted into a slope.
|
||||
|
||||
`mcl_minecarts.RAIL_GROUPS.STANDARD`
|
||||
`mcl_minecarts.RAIL_GROUPS.CURVES`
|
||||
|
||||
These constants are used to specify a rail node's `group.rail` value.
|
||||
|
||||
### Functions
|
||||
|
||||
`mcl_minecarts.get_rail_connections(node_position, options)`
|
||||
|
||||
Calculate the rail adjacency information for rail placement. Arguments are:
|
||||
|
||||
- `node_position` - the location of the node to calculate adjacency for.
|
||||
- `options` - A table containing any of these options:
|
||||
- `legacy`- if true, don't check that a connection proceeds out in a direction
|
||||
a cart can travel. Used for converting legacy rail to newer equivalents.
|
||||
- `ignore_neightbor_connections` - if true, don't check that a cart could leave
|
||||
the neighboring node from this direction.
|
||||
|
||||
`mcl_minecarts:is_rail(position, railtype)`
|
||||
|
||||
Determines if the node at `position` is a rail. If `railtype` is provided,
|
||||
determine if the node at `position` is that type of rail.
|
||||
|
||||
`mcl_minecarts.register_rail(itemstring, node_definition)`
|
||||
|
||||
Registers a rail with a few sensible defaults and if a craft recipe was specified,
|
||||
register that as well.
|
||||
|
||||
`mcl_minecarts.register_straight_rail(base_name, tiles, node_definition)`
|
||||
|
||||
Registers a rail with only straight and sloped variants.
|
||||
|
||||
`mcl_minecarts.register_curves_rail(base_name, tiles, node_definition)`
|
||||
|
||||
Registers a rail with straight, sloped, curved, tee and cross variants.
|
||||
|
||||
`mcl_minecarts.update_rail_connections(node_position, options)`
|
||||
|
||||
Converts the rail at `node_position`, if possible, another variant (curve, etc.)
|
||||
and rotates the node as needed so that rails connect together. `options` is
|
||||
passed thru to `mcl_minecarts.get_rail_connections()`
|
||||
|
||||
`mcl_minecarts:get_rail_direction(rail_position, cart_direction)`
|
||||
|
||||
Returns the next direction a cart traveling in the direction specified in `cart_direction`
|
||||
will travel from the rail located at `rail_position`.
|
||||
|
||||
### Node Definition Options
|
||||
|
||||
`_mcl_minecarts.railtype`
|
||||
|
||||
This declares the variant type of the rail. This will be one of the following:
|
||||
|
||||
- "straight" - two connections opposite each other and no vertical change.
|
||||
- "sloped" - two connections opposite each other with one of these connections
|
||||
one block higher.
|
||||
- "corner" - two connections at 90 degrees from each other.
|
||||
- "tee" - three connections
|
||||
- "cross" - four connections allowing only straight-thru movement
|
||||
|
||||
#### Hooks
|
||||
`_mcl_minecarts.get_next_dir = function(node_position, current_direction, node)`
|
||||
|
||||
Called to get the next direction a cart will travel after passing thru this node.
|
||||
|
||||
## Cart Functions
|
||||
|
||||
`mcl_minecarts.attach_driver(cart, player)`
|
||||
|
||||
This attaches (ObjectRef) `player` to the (LuaEntity) `cart`.
|
||||
|
||||
`mcl_minecarts.detach_minecart(cart_data)`
|
||||
|
||||
This detaches a minecart from any rail it is attached to and makes it start moving
|
||||
as an entity affected by gravity. It will keep moving in the same direction and
|
||||
at the same speed it was moving at before it detaches.
|
||||
|
||||
`mcl_minecarts.get_cart_position(cart_data)`
|
||||
|
||||
Compute the location of a minecart from its cart data. This works even when the entity
|
||||
is unloaded.
|
||||
|
||||
`mcl_minecarts.kill_cart(cart_data)`
|
||||
|
||||
Kills a cart and drops it as an item, even if the cart entity is unloaded.
|
||||
|
||||
`mcl_minecarts.place_minecart(itemstack, pointed_thing, placer)`
|
||||
|
||||
Places a minecart at the location specified by `pointed_thing`
|
||||
|
||||
`mcl_minecarts.register_minecart(minecart_definition)`
|
||||
|
||||
Registers a minecart. `minecart_definition` defines the entity. All the options supported by
|
||||
normal minetest entities are supported, with a few additions:
|
||||
|
||||
- `craft` - Crafting recipe for this cart.
|
||||
- `drop` - List of items to drop when the cart is killed. (required)
|
||||
- `entity_id` - The entity id of the cart. (required)
|
||||
- `itemstring` - This is the itemstring to use for this entity. (required)
|
||||
|
||||
`mcl_minecarts.reverse_cart_direction(cart_data)`
|
||||
|
||||
Force a minecart to start moving in the opposite direction of its current direction.
|
||||
|
||||
`mcl_minecarts.snap_direction(direction_vector)`
|
||||
|
||||
Returns a valid cart movement direction that has the smallest angle between it and `direction_vector`.
|
||||
|
||||
`mcl_minecarts:update_cart_orientation(cart)`
|
||||
|
||||
Updates the rotation of a cart entity to match the cart's data.
|
||||
|
||||
## Cart Data Functions
|
||||
|
||||
`mcl_minecarts.destroy_cart_data(uuid)`
|
||||
|
||||
Destroys the data for the cart with the identitfier in `uuid`.
|
||||
|
||||
`mcl_minecarts.find_carts_by_block_map(block_map)`
|
||||
|
||||
Returns a list of cart data for carts located in the blocks specified in `block_map`. Used
|
||||
to respawn carts entering areas around players.
|
||||
|
||||
`mcl_minecarts.add_blocks_to_map(block_map, min_pos, max_pos)`
|
||||
|
||||
Add blocks that fully contain `min_pos` and `max_pos` to `block_map` for use by
|
||||
`mcl_minecarts.find_cart_by_block_map`.
|
||||
|
||||
`mcl_minecarts.get_cart_data(uuid)`
|
||||
|
||||
Loads the data for the cart with the identitfier in `uuid`.
|
||||
|
||||
`mcl_minecarts.save_cart_data(uuid)`
|
||||
|
||||
Saves the data for the cart with the identifier in `uuid`.
|
||||
|
||||
`mcl_minecart.update_cart_data(data)`
|
||||
|
||||
Replaces the cart data for the cart with the identifier in `data.uuid`, then saves
|
||||
the data.
|
||||
|
||||
## Cart-Node Interactions
|
||||
|
||||
As the cart moves thru the environment, it can interact with the surrounding blocks
|
||||
thru a number of handlers in the block definitions. All these handlers are defined
|
||||
as:
|
||||
|
||||
`function(node_position, cart_luaentity, cart_direction, cart_position)`
|
||||
|
||||
Arguments:
|
||||
- `node_position` - position of the node the cart is interacting with
|
||||
- `cart_luaentity` - The luaentity of the cart that is entering this block. Will
|
||||
be nil for minecarts moving thru unloaded blocks
|
||||
- `cart_direction` - The direction the cart is moving
|
||||
- `cart_position` - The location of the cart
|
||||
- `cart_data` - Information about the cart. This will always be defined.
|
||||
|
||||
There are several variants of this handler:
|
||||
- `_mcl_minecarts_on_enter` - The cart enters this block
|
||||
- `_mcl_minecarts_on_enter_below` - The cart enters above this block
|
||||
- `_mcl_minecarts_on_enter_above` - The cart enters below this block
|
||||
- `_mcl_minecarts_on_enter_side` - The cart enters beside this block
|
||||
|
||||
Mods can also define global handlers that are called for every node. These
|
||||
handlers are defined as:
|
||||
|
||||
`function(node_position, cart_luaentity, cart_direction, node_definition, cart_data)`
|
||||
|
||||
Arguments:
|
||||
- `node_position` - position of the node the cart is interacting with
|
||||
- `cart_luaentity` - The luaentity of the cart that is entering this block. Will
|
||||
be nil for minecarts moving thru unloaded blocks
|
||||
- `cart_direction` - The direction the cart is moving
|
||||
- `cart_position` - The location of the cart
|
||||
- `cart_data` - Information about the cart. This will always be defined.
|
||||
- `node_definition` - The definition of the node at `node_position`
|
||||
|
||||
The available hooks are:
|
||||
- `_mcl_minecarts.on_enter` - The cart enters this block
|
||||
- `_mcl_minecarts.on_enter_below` - The cart enters above this block
|
||||
- `_mcl_minecarts.on_enter_above` - The cart enters below this block
|
||||
- `_mcl_minecarts.on_enter_side` - The cart enters beside this block
|
||||
|
||||
Only a single function can be installed in each of these handlers. Before installing,
|
||||
preserve the existing handler and call it from inside your handler if not `nil`.
|
||||
|
||||
## Train Functions
|
||||
|
||||
`mcl_minecarts.break_train_at(cart_data)`
|
||||
|
||||
Splits a train apart at the specified cart.
|
||||
|
||||
`mcl_minecarts.distance_between_cars(cart1_data, cart2_data)`
|
||||
|
||||
Returns the distance between two carts even if both entities are unloaded, or nil if either
|
||||
cart is not on a rail.
|
||||
|
||||
`mcl_minecarts.is_in_same_train(cart1_data, cart2_data)`
|
||||
|
||||
Returns true if cart1 and cart2 are a part of the same train and false otherwise.
|
||||
|
||||
`mcl_minecarts.link_cart_ahead(cart_data, cart_ahead_data)`
|
||||
|
||||
Given two carts, link them together into a train, with the second cart ahead of the first.
|
||||
|
||||
`mcl_minecarts.train_cars(cart_data)`
|
||||
|
||||
Use to iterate over all carts in a train. Expected usage:
|
||||
|
||||
`for cart in mcl_minecarts.train_cars(cart) do --[[ code ]] end`
|
||||
|
||||
`mcl_minecarts.reverse_train(cart)`
|
||||
|
||||
Make all carts in a train reverse and start moving in the opposite direction.
|
||||
|
||||
`mcl_minecarts.train_length(cart_data)`
|
||||
|
||||
Compute the current length of the train containing the cart whose data is `cart_data`.
|
||||
|
||||
`mcl_minecarts.update_train(cart_data)`
|
||||
|
||||
When provided with the rear-most cart of a tain, update speeds of all carts in the train
|
||||
so that it holds together and moves as a unit.
|
|
@ -0,0 +1,120 @@
|
|||
|
||||
## Organization
|
||||
- [ init.lua](./init.lua) - module entrypoint. The other files are included from here
|
||||
and several constants are defined here
|
||||
|
||||
- [carts.lua](./carts/lua) - This file contains code related to cart entities, cart
|
||||
type registration, creation, estruction and updating. The global step function
|
||||
responsible for updating attached carts is in this file. The various carts are
|
||||
referenced from this file but actually reside in the subdirectory [carts/](./carts/).
|
||||
|
||||
- [functions.lua](./functions.lua) - This file contains various minecart and rail
|
||||
utility functions used by the rest of the code.
|
||||
|
||||
- [movement.lua](./movement.lua) - This file contains the code related to cart
|
||||
movement physics.
|
||||
|
||||
- [rails.lua](./rails.lua) - This file contains code related to rail registation,
|
||||
placement, connection rules and cart direction selection. This contains the rail
|
||||
behaviors and the LBM code for updating legacy rail nodes to the new versions
|
||||
that don't use the raillike draw type.
|
||||
|
||||
- [storage.lua](./storage.lua) - This file contains the code than manages minecart
|
||||
state data to allow processing minecarts while entities are unloaded.
|
||||
|
||||
- [train.lua](./train.lua) - This file contains code related to multi-car trains.
|
||||
|
||||
## Rail Nodes
|
||||
|
||||
Previous versions of mcl\_minecarts used one node type for each rail type (standard,
|
||||
powered, detector and activator) using the raillike draw type that minetest provides.
|
||||
This version does not use the raillike draw type and instead uses a 1/16th of a block
|
||||
high nodebox and uses an additional node definition for each variant. The variants
|
||||
present are:
|
||||
|
||||
- straight
|
||||
- sloped
|
||||
- corner
|
||||
- tee
|
||||
- cross
|
||||
|
||||
Of the rail types provided by this module, standard has all of these variants. The
|
||||
remaining types only have straight and sloped variants.
|
||||
|
||||
Unlike the old rail type, this version will only update connections when placed, and
|
||||
will only place a variant that already has connections into the space the rail is
|
||||
being placed. Here is how to create the various varients:
|
||||
|
||||
- Straight rail is placed when with zero or one adjacent rail nodes. If no rails
|
||||
are adjacent, the rail is placed in line with the direction the player is facing.
|
||||
If there is exactly one adjacent rail present, the straight rail will always rotate
|
||||
to connect to it.
|
||||
|
||||
- Sloped rail is placed when there are two rails in a straight line, with one being
|
||||
one block higher. When rail is placed adjacent to a straight rail one block lower
|
||||
and the rail is facing the block the rail is being placed on, the lower rail will
|
||||
convert into a slope.
|
||||
|
||||
- A corner rail is placed when there are exactly two adjacent rails that are not in
|
||||
a line and lead into the space the rail is being placed. The corner will be rotated
|
||||
to connect these two rails.
|
||||
|
||||
- A tee rail is placed where there are exactly three rails adjact and those existing
|
||||
rails lead into the the space the new rail is being placed.
|
||||
|
||||
- A rail cross is placed when there is rail in all four adjacent blocks and they all
|
||||
have a path into the space the new rail is being placed.
|
||||
|
||||
The tee variant will interact with redstone and mesecons to switch the curved section.
|
||||
|
||||
## On-rail Minecart Movement
|
||||
|
||||
Minecart movement is handled in two distinct regimes: on a rail and off. The
|
||||
off-rail movement is handled with minetest's builtin entity movement handling.
|
||||
The on-rail movement is handled with a custom algorithm. This section details
|
||||
the latter.
|
||||
|
||||
The data for on-rail minecart movement is stored entirely inside mod storage
|
||||
and indexed by a hex-encoded 128-bit universally-unique identifier (uuid). Minecart
|
||||
entities store this uuid and a sequence identifier. The code for handling this
|
||||
storage is in [storage.lua](./storage.lua). This was done so that minecarts can
|
||||
still move while no players are connected or when out of range of players. Inspiration
|
||||
for this was the [Advanced Trains mod](http://advtrains.de/). This is a behavior difference
|
||||
when compared to minecraft, as carts there will stop movement when out of range of
|
||||
players.
|
||||
|
||||
Processing for minecart movement is as follows:
|
||||
1. In a globalstep handler in [carts.lua](./carts.lua), determine which carts are
|
||||
moving.
|
||||
2. Call `do_movement` in [movement.lua](./movement.lua) to update
|
||||
each cart's location and handle interactions with the environment.
|
||||
1. Each movement is broken up into one or more steps that are completely
|
||||
contained inside a block. This prevents carts from ever jumping from
|
||||
one rail to another over a gap or thru solid blocks because of server
|
||||
lag. Each step is processed with `do_movement_step`
|
||||
2. Each step uses physically accurate, timestep-independent physics
|
||||
to move the cart. Calculating the acceleration to apply to a cart
|
||||
is broken out into its own function (`calculate_acceperation`).
|
||||
3. As the cart enters and leaves blocks, handlers in nearby blocks are called
|
||||
to allow the cart to efficiently interact with the environment. Handled by
|
||||
the functions `handle_cart_enter` and `handle_cart_leave`
|
||||
4. The cart checks for nearby carts and collides elastically with these. The
|
||||
calculations for these collisions are in the function `handle_cart_collision`
|
||||
5. If the cart enters a new block, determine the new direction the cart will
|
||||
move with `mcl_minecarts:get_rail_direction` in [functions.lua](./functions.lua).
|
||||
The rail nodes provide a hook `_mcl_minecarts.get_next_direction` that
|
||||
provides this information based on the previous movement direction.
|
||||
3. If an entity exists for a given cart, the entity will update its position
|
||||
while loaded in.
|
||||
|
||||
Cart movement when on a rail occurs regarless of whether an entity for that
|
||||
cart exists or is loaded into memory. As a consequence of this movement, it
|
||||
is possible for carts with unloaded entities to enter range of a player.
|
||||
To handle this, periodic checks are performed around players and carts that
|
||||
are within range but don't have a cart have a new entity spawned.
|
||||
|
||||
Every time a cart has a new entity spawned, it increases a sequence number in
|
||||
the cart data to allow removing old entities from the minetest engine. Any cart
|
||||
entity that does not have the current sequence number for a minecart gets removed
|
||||
once processing for that entity resumes.
|
||||
|
|
@ -10,6 +10,7 @@ MIT License
|
|||
Copyright (C) 2012-2016 PilzAdam
|
||||
Copyright (C) 2014-2016 SmallJoker
|
||||
Copyright (C) 2012-2016 Various Minetest developers and contributors
|
||||
Copyright (C) 2024 teknomunk
|
||||
|
||||
Authors/licenses of media files:
|
||||
-----------------------
|
||||
|
|
|
@ -0,0 +1,659 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local mcl_log,DEBUG = mcl_util.make_mcl_logger("mcl_logging_minecarts", "Minecarts")
|
||||
|
||||
-- Imports
|
||||
local CART_BLOCK_SIZE = mod.CART_BLOCK_SIZE
|
||||
local table_merge = mcl_util.table_merge
|
||||
local get_cart_data = mod.get_cart_data
|
||||
local save_cart_data = mod.save_cart_data
|
||||
local update_cart_data = mod.update_cart_data
|
||||
local destroy_cart_data = mod.destroy_cart_data
|
||||
local find_carts_by_block_map = mod.find_carts_by_block_map
|
||||
local do_movement,do_detached_movement,handle_cart_enter = dofile(modpath.."/movement.lua")
|
||||
assert(do_movement)
|
||||
assert(do_detached_movement)
|
||||
assert(handle_cart_enter)
|
||||
|
||||
-- Constants
|
||||
local max_step_distance = 0.5
|
||||
local MINECART_MAX_HP = 4
|
||||
local TWO_OVER_PI = 2 / math.pi
|
||||
|
||||
local function detach_driver(self)
|
||||
local staticdata = self._staticdata
|
||||
|
||||
if not self._driver then
|
||||
return
|
||||
end
|
||||
|
||||
-- Update player infomation
|
||||
local driver_name = self._driver
|
||||
local playerinfo = mcl_playerinfo[driver_name]
|
||||
if playerinfo then
|
||||
playerinfo.attached_to = nil
|
||||
end
|
||||
mcl_player.player_attached[driver_name] = nil
|
||||
|
||||
minetest.log("action", driver_name.." left a minecart")
|
||||
|
||||
-- Update cart informatino
|
||||
self._driver = nil
|
||||
self._start_pos = nil
|
||||
local player_meta = mcl_playerinfo.get_mod_meta(driver_name, modname)
|
||||
player_meta.attached_to = nil
|
||||
|
||||
-- Detatch the player object from the minecart
|
||||
local player = minetest.get_player_by_name(driver_name)
|
||||
if player then
|
||||
local dir = staticdata.dir or vector.new(1,0,0)
|
||||
local cart_pos = mod.get_cart_position(staticdata) or self.object:get_pos()
|
||||
local new_pos = vector.offset(cart_pos, -dir.z, 0, dir.x)
|
||||
player:set_detach()
|
||||
--print("placing player at "..tostring(new_pos).." from cart at "..tostring(cart_pos)..", old_pos="..tostring(player:get_pos()).."dir="..tostring(dir))
|
||||
|
||||
-- There needs to be a delay here or the player's position won't update
|
||||
minetest.after(0.1,function(driver_name,new_pos)
|
||||
local player = minetest.get_player_by_name(driver_name)
|
||||
player:moveto(new_pos, false)
|
||||
end, driver_name, new_pos)
|
||||
|
||||
player:set_eye_offset(vector.zero(),vector.zero())
|
||||
mcl_player.player_set_animation(player, "stand" , 30)
|
||||
--else
|
||||
--print("No player object found for "..driver_name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Table for item-to-entity mapping. Keys: itemstring, Values: Corresponding entity ID
|
||||
local entity_mapping = {}
|
||||
|
||||
local function make_staticdata( _, connected_at, dir )
|
||||
return {
|
||||
connected_at = connected_at,
|
||||
distance = 0,
|
||||
velocity = 0,
|
||||
dir = vector.new(dir),
|
||||
mass = 1,
|
||||
seq = 1,
|
||||
}
|
||||
end
|
||||
|
||||
local DEFAULT_CART_DEF = {
|
||||
initial_properties = {
|
||||
physical = true,
|
||||
collisionbox = {-10/16., -0.5, -10/16, 10/16, 0.25, 10/16},
|
||||
visual = "mesh",
|
||||
visual_size = {x=1, y=1},
|
||||
},
|
||||
|
||||
hp_max = MINECART_MAX_HP,
|
||||
|
||||
groups = {
|
||||
minecart = 1,
|
||||
},
|
||||
|
||||
_driver = nil, -- player who sits in and controls the minecart (only for minecart!)
|
||||
_passenger = nil, -- for mobs
|
||||
_start_pos = nil, -- Used to calculate distance for “On A Rail” achievement
|
||||
_last_float_check = nil, -- timestamp of last time the cart was checked to be still on a rail
|
||||
_boomtimer = nil, -- how many seconds are left before exploding
|
||||
_blinktimer = nil, -- how many seconds are left before TNT blinking
|
||||
_blink = false, -- is TNT blink texture active?
|
||||
_old_pos = nil,
|
||||
_staticdata = nil,
|
||||
}
|
||||
function DEFAULT_CART_DEF:on_activate(staticdata, dtime_s)
|
||||
-- Transfer older data
|
||||
local data = minetest.deserialize(staticdata) or {}
|
||||
if not data.uuid then
|
||||
data.uuid = mcl_util.assign_uuid(self.object)
|
||||
end
|
||||
self._seq = data.seq or 1
|
||||
|
||||
local cd = get_cart_data(data.uuid)
|
||||
if not cd then
|
||||
update_cart_data(data)
|
||||
else
|
||||
if not cd.seq then cd.seq = 1 end
|
||||
data = cd
|
||||
end
|
||||
|
||||
-- Fix up types
|
||||
data.dir = vector.new(data.dir)
|
||||
|
||||
-- Fix mass
|
||||
data.mass = data.mass or 1
|
||||
|
||||
-- Make sure all carts have an ID to isolate them
|
||||
self._uuid = data.uuid
|
||||
self._staticdata = data
|
||||
|
||||
-- Activate cart if on powered activator rail
|
||||
if self.on_activate_by_rail then
|
||||
local pos = self.object:get_pos()
|
||||
local node = minetest.get_node(vector.floor(pos))
|
||||
if node.name == "mcl_minecarts:activator_rail_on" then
|
||||
self:on_activate_by_rail()
|
||||
end
|
||||
end
|
||||
end
|
||||
function DEFAULT_CART_DEF:get_staticdata()
|
||||
save_cart_data(self._staticdata.uuid)
|
||||
return minetest.serialize({uuid = self._staticdata.uuid, seq=self._seq})
|
||||
end
|
||||
|
||||
function DEFAULT_CART_DEF:_mcl_entity_invs_load_items()
|
||||
local staticdata = self._staticdata
|
||||
return staticdata.inventory or {}
|
||||
end
|
||||
function DEFAULT_CART_DEF:_mcl_entity_invs_save_items(items)
|
||||
local staticdata = self._staticdata
|
||||
staticdata.inventory = table.copy(items)
|
||||
end
|
||||
|
||||
function DEFAULT_CART_DEF:add_node_watch(pos)
|
||||
local staticdata = self._staticdata
|
||||
local watches = staticdata.node_watches or {}
|
||||
|
||||
for i=1,#watches do
|
||||
if watches[i] == pos then return end
|
||||
end
|
||||
|
||||
watches[#watches+1] = pos
|
||||
staticdata.node_watches = watches
|
||||
end
|
||||
function DEFAULT_CART_DEF:remove_node_watch(pos)
|
||||
local staticdata = self._staticdata
|
||||
local watches = staticdata.node_watches or {}
|
||||
|
||||
local new_watches = {}
|
||||
for i=1,#watches do
|
||||
local node_pos = watches[i]
|
||||
if node_pos ~= pos then
|
||||
new_watches[#new_watches + 1] = node_pos
|
||||
end
|
||||
end
|
||||
staticdata.node_watches = new_watches
|
||||
end
|
||||
function DEFAULT_CART_DEF:get_cart_position()
|
||||
local staticdata = self._staticdata
|
||||
|
||||
if staticdata.connected_at then
|
||||
return staticdata.connected_at + staticdata.dir * staticdata.distance
|
||||
else
|
||||
return self.object:get_pos()
|
||||
end
|
||||
end
|
||||
function DEFAULT_CART_DEF:on_punch(puncher, time_from_last_punch, tool_capabilities, dir, damage)
|
||||
if puncher == self._driver then return end
|
||||
|
||||
local staticdata = self._staticdata
|
||||
local controls = staticdata.controls or {}
|
||||
|
||||
local impulse = vector.multiply(dir, damage * 20)
|
||||
|
||||
local accel = vector.dot(staticdata.dir, impulse)
|
||||
if accel < 0 and staticdata.velocity == 0 then
|
||||
mod.reverse_direction(staticdata)
|
||||
end
|
||||
|
||||
controls.impulse = impulse
|
||||
--print("uuid="..self._uuid..", controls="..dump(controls))
|
||||
staticdata.controls = controls
|
||||
end
|
||||
function DEFAULT_CART_DEF:on_step(dtime)
|
||||
local staticdata = self._staticdata
|
||||
if not staticdata then
|
||||
staticdata = make_staticdata()
|
||||
self._staticdata = staticdata
|
||||
end
|
||||
|
||||
-- Update entity position
|
||||
local pos = mod.get_cart_position(staticdata)
|
||||
if pos then self.object:move_to(pos) end
|
||||
|
||||
-- Repair cart_type
|
||||
if not staticdata.cart_type then
|
||||
staticdata.cart_type = self.name
|
||||
end
|
||||
|
||||
-- Remove superceded entities
|
||||
if self._seq ~= staticdata.seq then
|
||||
--print("removing cart #"..staticdata.uuid.." with sequence number mismatch")
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
-- Regen
|
||||
local hp = self.object:get_hp()
|
||||
local time_now = minetest.get_gametime()
|
||||
if hp < MINECART_MAX_HP and (staticdata.last_regen or 0) <= time_now - 1 then
|
||||
staticdata.last_regen = time_now
|
||||
hp = hp + 1
|
||||
self.object:set_hp(hp)
|
||||
end
|
||||
|
||||
-- Cart specific behaviors
|
||||
local hook = self._mcl_minecarts_on_step
|
||||
if hook then hook(self,dtime) end
|
||||
|
||||
if (staticdata.hopper_delay or 0) > 0 then
|
||||
staticdata.hopper_delay = staticdata.hopper_delay - dtime
|
||||
end
|
||||
|
||||
-- Controls
|
||||
local ctrl, player = nil, nil
|
||||
if self._driver then
|
||||
player = minetest.get_player_by_name(self._driver)
|
||||
if player then
|
||||
ctrl = player:get_player_control()
|
||||
-- player detach
|
||||
if ctrl.sneak then
|
||||
detach_driver(self)
|
||||
return
|
||||
end
|
||||
|
||||
-- Experimental controls
|
||||
local now_time = minetest.get_gametime()
|
||||
local controls = {}
|
||||
if ctrl.up then controls.forward = now_time end
|
||||
if ctrl.down then controls.brake = now_time end
|
||||
controls.look = math.round(player:get_look_horizontal() * TWO_OVER_PI) % 4
|
||||
staticdata.controls = controls
|
||||
end
|
||||
|
||||
-- Give achievement when player reached a distance of 1000 nodes from the start position
|
||||
if pos and vector.distance(self._start_pos, pos) >= 1000 then
|
||||
awards.unlock(self._driver, "mcl:onARail")
|
||||
end
|
||||
end
|
||||
|
||||
if not staticdata.connected_at then
|
||||
do_detached_movement(self, dtime)
|
||||
end
|
||||
|
||||
mod.update_cart_orientation(self)
|
||||
end
|
||||
function mod.kill_cart(staticdata, killer)
|
||||
local pos
|
||||
minetest.log("action", "cart #"..staticdata.uuid.." was killed")
|
||||
|
||||
-- Leave nodes
|
||||
if staticdata.attached_at then
|
||||
handle_cart_leave(self, staticdata.attached_at, staticdata.dir )
|
||||
else
|
||||
--mcl_log("TODO: handle detatched minecart death")
|
||||
end
|
||||
|
||||
-- Handle entity-related items
|
||||
local le = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
|
||||
if le then
|
||||
pos = le.object:get_pos()
|
||||
|
||||
detach_driver(le)
|
||||
|
||||
-- Detach passenger
|
||||
if le._passenger then
|
||||
local mob = le._passenger.object
|
||||
mob:set_detach()
|
||||
end
|
||||
|
||||
-- Remove the entity
|
||||
le.object:remove()
|
||||
else
|
||||
pos = mod.get_cart_position(staticdata)
|
||||
end
|
||||
|
||||
-- Drop items
|
||||
if not staticdata.dropped then
|
||||
|
||||
-- Try to drop the cart
|
||||
local entity_def = minetest.registered_entities[staticdata.cart_type]
|
||||
if entity_def then
|
||||
local drop_cart = true
|
||||
if killer and minetest.is_creative_enabled(killer:get_player_name()) then
|
||||
drop_cart = false
|
||||
end
|
||||
|
||||
if drop_cart then
|
||||
local drop = entity_def.drop
|
||||
for d=1, #drop do
|
||||
minetest.add_item(pos, drop[d])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Drop any items in the inventory
|
||||
local inventory = staticdata.inventory
|
||||
if inventory then
|
||||
for i=1,#inventory do
|
||||
minetest.add_item(pos, inventory[i])
|
||||
end
|
||||
end
|
||||
|
||||
-- Prevent item duplication
|
||||
staticdata.dropped = true
|
||||
end
|
||||
|
||||
-- Remove data
|
||||
destroy_cart_data(staticdata.uuid)
|
||||
end
|
||||
local kill_cart = mod.kill_cart
|
||||
|
||||
function DEFAULT_CART_DEF:on_death(killer)
|
||||
kill_cart(self._staticdata, killer)
|
||||
end
|
||||
|
||||
-- Create a minecart
|
||||
function mod.create_minecart(entity_id, pos, dir)
|
||||
-- Setup cart data
|
||||
local uuid = mcl_util.gen_uuid()
|
||||
local data = make_staticdata( nil, pos, dir )
|
||||
data.uuid = uuid
|
||||
data.cart_type = entity_id
|
||||
update_cart_data(data)
|
||||
save_cart_data(uuid)
|
||||
|
||||
return uuid
|
||||
end
|
||||
local create_minecart = mod.create_minecart
|
||||
|
||||
-- Place a minecart at pointed_thing
|
||||
function mod.place_minecart(itemstack, pointed_thing, placer)
|
||||
if not pointed_thing.type == "node" then
|
||||
return
|
||||
end
|
||||
|
||||
local spawn_pos = pointed_thing.above
|
||||
local cart_dir = vector.new(1,0,0)
|
||||
|
||||
local railpos, node
|
||||
if mcl_minecarts:is_rail(pointed_thing.under) then
|
||||
railpos = pointed_thing.under
|
||||
elseif mcl_minecarts:is_rail(pointed_thing.above) then
|
||||
railpos = pointed_thing.above
|
||||
end
|
||||
if railpos then
|
||||
spawn_pos = railpos
|
||||
node = minetest.get_node(railpos)
|
||||
cart_dir = mcl_minecarts:get_rail_direction(railpos, vector.new(1,0,0))
|
||||
end
|
||||
|
||||
local entity_id = entity_mapping[itemstack:get_name()]
|
||||
|
||||
local uuid = create_minecart(entity_id, railpos, cart_dir)
|
||||
|
||||
-- Create the entity with the staticdata already setup
|
||||
local sd = minetest.serialize({ uuid=uuid, seq=1 })
|
||||
local cart = minetest.add_entity(spawn_pos, entity_id, sd)
|
||||
local staticdata = get_cart_data(uuid)
|
||||
|
||||
cart:set_yaw(minetest.dir_to_yaw(cart_dir))
|
||||
|
||||
-- Call placer
|
||||
local le = cart:get_luaentity()
|
||||
if le._mcl_minecarts_on_place then
|
||||
le._mcl_minecarts_on_place(le, placer)
|
||||
end
|
||||
|
||||
if railpos then
|
||||
handle_cart_enter(staticdata, railpos)
|
||||
end
|
||||
|
||||
local pname = placer and placer:get_player_name() or ""
|
||||
if not minetest.is_creative_enabled(pname) then
|
||||
itemstack:take_item()
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local function dropper_place_minecart(dropitem, pos)
|
||||
-- Don't try to place the minecart if pos isn't a rail
|
||||
local node = minetest.get_node(pos)
|
||||
if minetest.get_item_group(node.name, "rail") == 0 then return false end
|
||||
|
||||
mod.place_minecart(dropitem, {
|
||||
above = pos,
|
||||
under = vector.offset(pos,0,-1,0)
|
||||
})
|
||||
return true
|
||||
end
|
||||
|
||||
local function register_minecart_craftitem(itemstring, def)
|
||||
local groups = { minecart = 1, transport = 1 }
|
||||
if def.creative == false then
|
||||
groups.not_in_creative_inventory = 1
|
||||
end
|
||||
local item_def = {
|
||||
stack_max = 1,
|
||||
_mcl_dropper_on_drop = dropper_place_minecart,
|
||||
on_place = function(itemstack, placer, pointed_thing)
|
||||
if not pointed_thing.type == "node" then
|
||||
return
|
||||
end
|
||||
|
||||
-- Call on_rightclick if the pointed node defines it
|
||||
local node = minetest.get_node(pointed_thing.under)
|
||||
if placer and not placer:get_player_control().sneak then
|
||||
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
|
||||
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
|
||||
end
|
||||
end
|
||||
|
||||
return mod.place_minecart(itemstack, pointed_thing, placer)
|
||||
end,
|
||||
_on_dispense = function(stack, pos, droppos, dropnode, dropdir)
|
||||
-- Place minecart as entity on rail. If there's no rail, just drop it.
|
||||
local placed
|
||||
if minetest.get_item_group(dropnode.name, "rail") ~= 0 then
|
||||
-- FIXME: This places minecarts even if the spot is already occupied
|
||||
local pointed_thing = { under = droppos, above = vector.new( droppos.x, droppos.y+1, droppos.z ) }
|
||||
placed = mod.place_minecart(stack, pointed_thing)
|
||||
end
|
||||
if placed == nil then
|
||||
-- Drop item
|
||||
minetest.add_item(droppos, stack)
|
||||
end
|
||||
end,
|
||||
groups = groups,
|
||||
}
|
||||
item_def.description = def.description
|
||||
item_def._tt_help = def.tt_help
|
||||
item_def._doc_items_longdesc = def.longdesc
|
||||
item_def._doc_items_usagehelp = def.usagehelp
|
||||
item_def.inventory_image = def.icon
|
||||
item_def.wield_image = def.icon
|
||||
minetest.register_craftitem(itemstring, item_def)
|
||||
end
|
||||
|
||||
--[[
|
||||
Register a minecart
|
||||
* itemstring: Itemstring of minecart item
|
||||
* entity_id: ID of minecart entity
|
||||
* description: Item name / description
|
||||
* longdesc: Long help text
|
||||
* usagehelp: Usage help text
|
||||
* mesh: Minecart mesh
|
||||
* textures: Minecart textures table
|
||||
* icon: Item icon
|
||||
* drop: Dropped items after destroying minecart
|
||||
* on_rightclick: Called after rightclick
|
||||
* on_activate_by_rail: Called when above activator rail
|
||||
* creative: If false, don't show in Creative Inventory
|
||||
]]
|
||||
function mod.register_minecart(def)
|
||||
-- Make sure all required parameters are present
|
||||
for _,name in pairs({"drop","itemstring","entity_id"}) do
|
||||
assert( def[name], "def."..name..", a required parameter, is missing")
|
||||
end
|
||||
|
||||
local entity_id = def.entity_id; def.entity_id = nil
|
||||
local craft = def.craft; def.craft = nil
|
||||
local itemstring = def.itemstring; def.itemstring = nil
|
||||
|
||||
-- Build cart definition
|
||||
local cart = table.copy(DEFAULT_CART_DEF)
|
||||
table_merge(cart, def)
|
||||
minetest.register_entity(entity_id, cart)
|
||||
|
||||
-- Register item to entity mapping
|
||||
entity_mapping[itemstring] = entity_id
|
||||
|
||||
register_minecart_craftitem(itemstring, def)
|
||||
if minetest.get_modpath("doc_identifier") then
|
||||
doc.sub.identifier.register_object(entity_id, "craftitems", itemstring)
|
||||
end
|
||||
|
||||
if craft then
|
||||
minetest.register_craft(craft)
|
||||
end
|
||||
end
|
||||
local register_minecart = mod.register_minecart
|
||||
|
||||
dofile(modpath.."/carts/minecart.lua")
|
||||
dofile(modpath.."/carts/with_chest.lua")
|
||||
dofile(modpath.."/carts/with_commandblock.lua")
|
||||
dofile(modpath.."/carts/with_hopper.lua")
|
||||
dofile(modpath.."/carts/with_furnace.lua")
|
||||
dofile(modpath.."/carts/with_tnt.lua")
|
||||
|
||||
if minetest.get_modpath("mcl_wip") then
|
||||
mcl_wip.register_wip_item("mcl_minecarts:chest_minecart")
|
||||
mcl_wip.register_wip_item("mcl_minecarts:furnace_minecart")
|
||||
mcl_wip.register_wip_item("mcl_minecarts:command_block_minecart")
|
||||
end
|
||||
|
||||
local function respawn_cart(cart)
|
||||
local cart_type = cart.cart_type or "mcl_minecarts:minecart"
|
||||
local pos = mod.get_cart_position(cart)
|
||||
|
||||
local players = minetest.get_connected_players()
|
||||
local distance = nil
|
||||
for _,player in pairs(players) do
|
||||
local d = vector.distance(player:get_pos(), pos)
|
||||
if not distance or d < distance then distance = d end
|
||||
end
|
||||
if not distance or distance > 90 then return end
|
||||
|
||||
mcl_log("Respawning cart #"..cart.uuid.." at "..tostring(pos)..",distance="..distance..",node="..minetest.get_node(pos).name)
|
||||
|
||||
-- Update sequence so that old cart entities get removed
|
||||
cart.seq = (cart.seq or 1) + 1
|
||||
save_cart_data(cart.uuid)
|
||||
|
||||
-- Create the new entity and refresh caches
|
||||
local sd = minetest.serialize({ uuid=cart.uuid, seq=cart.seq })
|
||||
local entity = minetest.add_entity(pos, cart_type, sd)
|
||||
local le = entity:get_luaentity()
|
||||
le._staticdata = cart
|
||||
mcl_util.assign_uuid(entity)
|
||||
|
||||
-- We intentionally don't call the normal hooks because this minecart was already there
|
||||
end
|
||||
|
||||
-- Try to respawn cart entities for carts that have moved into range of a player
|
||||
local function try_respawn_carts()
|
||||
-- Build a map of blocks near players
|
||||
local block_map = {}
|
||||
local players = minetest.get_connected_players()
|
||||
for _,player in pairs(players) do
|
||||
local pos = player:get_pos()
|
||||
mod.add_blocks_to_map(
|
||||
block_map,
|
||||
vector.offset(pos,-CART_BLOCK_SIZE,-CART_BLOCK_SIZE,-CART_BLOCK_SIZE),
|
||||
vector.offset(pos, CART_BLOCK_SIZE, CART_BLOCK_SIZE, CART_BLOCK_SIZE)
|
||||
)
|
||||
end
|
||||
|
||||
-- Find all cart data that are in these blocks
|
||||
local carts = find_carts_by_block_map(block_map)
|
||||
|
||||
-- Check to see if any of these don't have an entity
|
||||
for _,cart in pairs(carts) do
|
||||
local le = mcl_util.get_luaentity_from_uuid(cart.uuid)
|
||||
if not le then
|
||||
respawn_cart(cart)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local timer = 0
|
||||
minetest.register_globalstep(function(dtime)
|
||||
|
||||
-- Periodically respawn carts that come into range of a player
|
||||
timer = timer - dtime
|
||||
if timer <= 0 then
|
||||
local start_time = minetest.get_us_time()
|
||||
try_respawn_carts()
|
||||
local stop_time = minetest.get_us_time()
|
||||
local duration = (stop_time - start_time) / 1e6
|
||||
timer = duration / 250e-6 -- Schedule 50us per second
|
||||
if timer > 5 then timer = 5 end
|
||||
end
|
||||
|
||||
-- Handle periodically updating out-of-range carts
|
||||
-- TODO: change how often cart positions are updated based on velocity
|
||||
local start_time
|
||||
if DEBUG then start_time = minetest.get_us_time() end
|
||||
|
||||
for uuid,staticdata in mod.carts() do
|
||||
local pos = mod.get_cart_position(staticdata)
|
||||
--[[
|
||||
local le = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
|
||||
print("cart# "..uuid..
|
||||
",velocity="..tostring(staticdata.velocity)..
|
||||
",pos="..tostring(pos)..
|
||||
",le="..tostring(le)..
|
||||
",connected_at="..tostring(staticdata.connected_at)
|
||||
)]]
|
||||
|
||||
--- Non-entity code
|
||||
if staticdata.connected_at then
|
||||
do_movement(staticdata, dtime)
|
||||
end
|
||||
end
|
||||
|
||||
if DEBUG then
|
||||
local stop_time = minetest.get_us_time()
|
||||
print("Update took "..((stop_time-start_time)*1e-6).." seconds")
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
-- Try cart reattachment
|
||||
local player_name = player:get_player_name()
|
||||
local player_meta = mcl_playerinfo.get_mod_meta(player_name, modname)
|
||||
local cart_uuid = player_meta.attached_to
|
||||
if cart_uuid then
|
||||
local cartdata = get_cart_data(cart_uuid)
|
||||
|
||||
-- Can't get into a cart that was destroyed
|
||||
if not cartdata then
|
||||
return
|
||||
end
|
||||
|
||||
-- Don't reattach players if someone else got in the cart
|
||||
if cartdata.last_player ~= player_name then
|
||||
return
|
||||
end
|
||||
|
||||
minetest.after(0.2,function(player_name, cart_uuid)
|
||||
local player = minetest.get_player_by_name(player_name)
|
||||
if not player then
|
||||
return
|
||||
end
|
||||
|
||||
local cart = mcl_util.get_luaentity_from_uuid(cart_uuid)
|
||||
if not cart then
|
||||
return
|
||||
end
|
||||
|
||||
mod.attach_driver(cart, player)
|
||||
end, player_name, cart_uuid)
|
||||
end
|
||||
end)
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
local mcl_log = mcl_util.make_mcl_logger("mcl_logging_minecarts", "Minecarts")
|
||||
local mod = mcl_minecarts
|
||||
|
||||
-- Imports
|
||||
local PASSENGER_ATTACH_POSITION = mod.PASSENGER_ATTACH_POSITION
|
||||
|
||||
local function activate_normal_minecart(self)
|
||||
detach_driver(self)
|
||||
|
||||
-- Detach passenger
|
||||
if self._passenger then
|
||||
local mob = self._passenger.object
|
||||
mob:set_detach()
|
||||
end
|
||||
end
|
||||
|
||||
function mod.attach_driver(cart, player)
|
||||
local staticdata = cart._staticdata
|
||||
|
||||
-- Make sure we have a player
|
||||
if not player or not player:is_player() then return end
|
||||
|
||||
local player_name = player:get_player_name()
|
||||
if cart._driver or player:get_player_control().sneak then return end
|
||||
|
||||
-- Update cart information
|
||||
cart._driver = player_name
|
||||
cart._start_pos = cart.object:get_pos()
|
||||
|
||||
-- Keep track of player attachment
|
||||
local player_meta = mcl_playerinfo.get_mod_meta(player_name, modname)
|
||||
player_meta.attached_to = cart._uuid
|
||||
staticdata.last_player = player_name
|
||||
|
||||
-- Update player information
|
||||
local uuid = staticdata.uuid
|
||||
mcl_player.player_attached[player_name] = true
|
||||
minetest.log("action", player_name.." entered minecart #"..tostring(uuid).." at "..tostring(cart._start_pos))
|
||||
|
||||
-- Attach the player object to the minecart
|
||||
player:set_attach(cart.object, "", vector.new(1,-1.75,-2), vector.new(0,0,0))
|
||||
minetest.after(0.2, function(name)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if player then
|
||||
mcl_player.player_set_animation(player, "sit" , 30)
|
||||
player:set_eye_offset(vector.new(0,-5.5,0), vector.new(0,-4,0))
|
||||
mcl_title.set(player, "actionbar", {text=S("Sneak to dismount"), color="white", stay=60})
|
||||
end
|
||||
end, player_name)
|
||||
end
|
||||
|
||||
mod.register_minecart({
|
||||
itemstring = "mcl_minecarts:minecart",
|
||||
craft = {
|
||||
output = "mcl_minecarts:minecart",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot"},
|
||||
},
|
||||
},
|
||||
entity_id = "mcl_minecarts:minecart",
|
||||
description = S("Minecart"),
|
||||
tt_helop = S("Vehicle for fast travel on rails"),
|
||||
long_descp = S("Minecarts can be used for a quick transportion on rails.") .. "\n" ..
|
||||
S("Minecarts only ride on rails and always follow the tracks. At a T-junction with no straight way ahead, they turn left. The speed is affected by the rail type."),
|
||||
S("You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving.") .. "\n" ..
|
||||
S("To obtain the minecart, punch it while holding down the sneak key.") .. "\n" ..
|
||||
S("If it moves over a powered activator rail, you'll get ejected."),
|
||||
initial_properties = {
|
||||
mesh = "mcl_minecarts_minecart.b3d",
|
||||
textures = {"mcl_minecarts_minecart.png"},
|
||||
},
|
||||
icon = "mcl_minecarts_minecart_normal.png",
|
||||
drop = {"mcl_minecarts:minecart"},
|
||||
on_rightclick = mod.attach_driver,
|
||||
on_activate_by_rail = activate_normal_minecart,
|
||||
_mcl_minecarts_on_step = function(self, dtime)
|
||||
-- Grab mob
|
||||
if math.random(1,20) > 15 and not self._passenger then
|
||||
local mobsnear = minetest.get_objects_inside_radius(self.object:get_pos(), 1.3)
|
||||
for n=1, #mobsnear do
|
||||
local mob = mobsnear[n]
|
||||
if mob then
|
||||
local entity = mob:get_luaentity()
|
||||
if entity and entity.is_mob then
|
||||
self._passenger = entity
|
||||
mob:set_attach(self.object, "", PASSENGER_ATTACH_POSITION, vector.zero())
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif self._passenger then
|
||||
local passenger_pos = self._passenger.object:get_pos()
|
||||
if not passenger_pos then
|
||||
self._passenger = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
|
@ -0,0 +1,35 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
-- Minecart with Chest
|
||||
mcl_minecarts.register_minecart({
|
||||
itemstring = "mcl_minecarts:chest_minecart",
|
||||
craft = {
|
||||
output = "mcl_minecarts:chest_minecart",
|
||||
recipe = {
|
||||
{"mcl_chests:chest"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
},
|
||||
entity_id = "mcl_minecarts:chest_minecart",
|
||||
description = S("Minecart with Chest"),
|
||||
tt_help = nil,
|
||||
longdesc = nil,
|
||||
usagehelp = nil,
|
||||
initial_properties = {
|
||||
mesh = "mcl_minecarts_minecart_chest.b3d",
|
||||
textures = {
|
||||
"mcl_chests_normal.png",
|
||||
"mcl_minecarts_minecart.png"
|
||||
},
|
||||
},
|
||||
icon = "mcl_minecarts_minecart_chest.png",
|
||||
drop = {"mcl_minecarts:minecart", "mcl_chests:chest"},
|
||||
groups = { container = 1 },
|
||||
on_rightclick = nil,
|
||||
on_activate_by_rail = nil,
|
||||
creative = true
|
||||
})
|
||||
mcl_entity_invs.register_inv("mcl_minecarts:chest_minecart","Minecart",27,false,true)
|
|
@ -0,0 +1,64 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
function table_metadata(table)
|
||||
return {
|
||||
table = table,
|
||||
set_string = function(self, key, value)
|
||||
--print("set_string("..tostring(key)..", "..tostring(value)..")")
|
||||
self.table[key] = tostring(value)
|
||||
end,
|
||||
get_string = function(self, key)
|
||||
if self.table[key] then
|
||||
return tostring(self.table[key])
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
-- Minecart with Command Block
|
||||
mod.register_minecart({
|
||||
itemstring = "mcl_minecarts:command_block_minecart",
|
||||
entity_id = "mcl_minecarts:command_block_minecart",
|
||||
description = S("Minecart with Command Block"),
|
||||
tt_help = nil,
|
||||
loncdesc = nil,
|
||||
usagehelp = nil,
|
||||
initial_properties = {
|
||||
mesh = "mcl_minecarts_minecart_block.b3d",
|
||||
textures = {
|
||||
"jeija_commandblock_off.png^[verticalframe:2:0",
|
||||
"jeija_commandblock_off.png^[verticalframe:2:0",
|
||||
"jeija_commandblock_off.png^[verticalframe:2:0",
|
||||
"jeija_commandblock_off.png^[verticalframe:2:0",
|
||||
"jeija_commandblock_off.png^[verticalframe:2:0",
|
||||
"jeija_commandblock_off.png^[verticalframe:2:0",
|
||||
"mcl_minecarts_minecart.png",
|
||||
},
|
||||
},
|
||||
icon = "mcl_minecarts_minecart_command_block.png",
|
||||
drop = {"mcl_minecarts:minecart"},
|
||||
on_rightclick = function(self, clicker)
|
||||
self._staticdata.meta = self._staticdata.meta or {}
|
||||
local meta = table_metadata(self._staticdata.meta)
|
||||
|
||||
mesecon.commandblock.handle_rightclick(meta, clicker)
|
||||
end,
|
||||
_mcl_minecarts_on_place = function(self, placer)
|
||||
-- Create a fake metadata object that stores into the cart's staticdata
|
||||
self._staticdata.meta = self._staticdata.meta or {}
|
||||
local meta = table_metadata(self._staticdata.meta)
|
||||
|
||||
mesecon.commandblock.initialize(meta)
|
||||
mesecon.commandblock.place(meta, placer)
|
||||
end,
|
||||
on_activate_by_rail = function(self, timer)
|
||||
self._staticdata.meta = self._staticdata.meta or {}
|
||||
local meta = table_metadata(self._staticdata.meta)
|
||||
|
||||
mesecon.commandblock.action_on(meta, self.object:get_pos())
|
||||
end,
|
||||
creative = true
|
||||
})
|
|
@ -0,0 +1,96 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
-- Minecart with Furnace
|
||||
mcl_minecarts.register_minecart({
|
||||
itemstring = "mcl_minecarts:furnace_minecart",
|
||||
craft = {
|
||||
output = "mcl_minecarts:furnace_minecart",
|
||||
recipe = {
|
||||
{"mcl_furnaces:furnace"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
},
|
||||
entity_id = "mcl_minecarts:furnace_minecart",
|
||||
description = S("Minecart with Furnace"),
|
||||
tt_help = nil,
|
||||
longdesc = S("A minecart with furnace is a vehicle that travels on rails. It can propel itself with fuel."),
|
||||
usagehelp = S("Place it on rails. If you give it some coal, the furnace will start burning for a long time and the minecart will be able to move itself. Punch it to get it moving.") .. "\n" ..
|
||||
S("To obtain the minecart and furnace, punch them while holding down the sneak key."),
|
||||
|
||||
initial_properties = {
|
||||
mesh = "mcl_minecarts_minecart_block.b3d",
|
||||
textures = {
|
||||
"default_furnace_top.png",
|
||||
"default_furnace_top.png",
|
||||
"default_furnace_front.png",
|
||||
"default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
},
|
||||
},
|
||||
icon = "mcl_minecarts_minecart_furnace.png",
|
||||
drop = {"mcl_minecarts:minecart", "mcl_furnaces:furnace"},
|
||||
on_rightclick = function(self, clicker)
|
||||
local staticdata = self._staticdata
|
||||
|
||||
-- Feed furnace with coal
|
||||
if not clicker or not clicker:is_player() then
|
||||
return
|
||||
end
|
||||
local held = clicker:get_wielded_item()
|
||||
if minetest.get_item_group(held:get_name(), "coal") == 1 then
|
||||
staticdata.fueltime = (staticdata.fueltime or 0) + 180
|
||||
|
||||
-- Trucate to 27 minutes (9 uses)
|
||||
if staticdata.fueltime > 27*60 then
|
||||
staticdata.fuel_time = 27*60
|
||||
end
|
||||
|
||||
if not minetest.is_creative_enabled(clicker:get_player_name()) then
|
||||
held:take_item()
|
||||
local index = clicker:get_wield_index()
|
||||
local inv = clicker:get_inventory()
|
||||
inv:set_stack("main", index, held)
|
||||
end
|
||||
self.object:set_properties({textures =
|
||||
{
|
||||
"default_furnace_top.png",
|
||||
"default_furnace_top.png",
|
||||
"default_furnace_front_active.png",
|
||||
"default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
}})
|
||||
end
|
||||
end,
|
||||
on_activate_by_rail = nil,
|
||||
creative = true,
|
||||
_mcl_minecarts_on_step = function(self, dtime)
|
||||
local staticdata = self._staticdata
|
||||
|
||||
-- Update furnace stuff
|
||||
if (staticdata.fueltime or 0) > 0 then
|
||||
if staticdata.velocity < 0.25 then
|
||||
staticdata.velocity = 0.25
|
||||
end
|
||||
|
||||
staticdata.fueltime = (staticdata.fueltime or dtime) - dtime
|
||||
if staticdata.fueltime <= 0 then
|
||||
self.object:set_properties({textures =
|
||||
{
|
||||
"default_furnace_top.png",
|
||||
"default_furnace_top.png",
|
||||
"default_furnace_front.png",
|
||||
"default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
}})
|
||||
staticdata.fueltime = 0
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
|
@ -0,0 +1,178 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_minecarts", false)
|
||||
local function mcl_log(message)
|
||||
if LOGGING_ON then
|
||||
mcl_util.mcl_log(message, "[Minecarts]", true)
|
||||
end
|
||||
end
|
||||
|
||||
local function hopper_take_item(self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return end
|
||||
|
||||
if not self or self.name ~= "mcl_minecarts:hopper_minecart" then return end
|
||||
|
||||
if mcl_util.check_dtime_timer(self, dtime, "hoppermc_take", 0.15) then
|
||||
--minetest.log("The check timer was triggered: " .. dump(pos) .. ", name:" .. self.name)
|
||||
else
|
||||
--minetest.log("The check timer was not triggered")
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
local above_pos = vector.offset(pos, 0, 0.9, 0)
|
||||
local objs = minetest.get_objects_inside_radius(above_pos, 1.25)
|
||||
|
||||
if objs then
|
||||
mcl_log("there is an itemstring. Number of objs: ".. #objs)
|
||||
|
||||
for k, v in pairs(objs) do
|
||||
local ent = v:get_luaentity()
|
||||
|
||||
if ent and not ent._removed and ent.itemstring and ent.itemstring ~= "" then
|
||||
local taken_items = false
|
||||
|
||||
mcl_log("ent.name: " .. tostring(ent.name))
|
||||
mcl_log("ent pos: " .. tostring(ent.object:get_pos()))
|
||||
|
||||
local inv = mcl_entity_invs.load_inv(self, 5)
|
||||
if not inv then return false end
|
||||
|
||||
local current_itemstack = ItemStack(ent.itemstring)
|
||||
|
||||
mcl_log("inv. size: " .. self._inv_size)
|
||||
if inv:room_for_item("main", current_itemstack) then
|
||||
mcl_log("Room")
|
||||
inv:add_item("main", current_itemstack)
|
||||
ent.object:get_luaentity().itemstring = ""
|
||||
ent.object:remove()
|
||||
taken_items = true
|
||||
else
|
||||
mcl_log("no Room")
|
||||
end
|
||||
|
||||
if not taken_items then
|
||||
local items_remaining = current_itemstack:get_count()
|
||||
|
||||
-- This will take part of a floating item stack if no slot can hold the full amount
|
||||
for i = 1, self._inv_size, 1 do
|
||||
local stack = inv:get_stack("main", i)
|
||||
|
||||
mcl_log("i: " .. tostring(i))
|
||||
mcl_log("Items remaining: " .. items_remaining)
|
||||
mcl_log("Name: " .. tostring(stack:get_name()))
|
||||
|
||||
if current_itemstack:get_name() == stack:get_name() then
|
||||
mcl_log("We have a match. Name: " .. tostring(stack:get_name()))
|
||||
|
||||
local room_for = stack:get_stack_max() - stack:get_count()
|
||||
mcl_log("Room for: " .. tostring(room_for))
|
||||
|
||||
if room_for == 0 then
|
||||
-- Do nothing
|
||||
mcl_log("No room")
|
||||
elseif room_for < items_remaining then
|
||||
mcl_log("We have more items remaining than space")
|
||||
|
||||
items_remaining = items_remaining - room_for
|
||||
stack:set_count(stack:get_stack_max())
|
||||
inv:set_stack("main", i, stack)
|
||||
taken_items = true
|
||||
else
|
||||
local new_stack_size = stack:get_count() + items_remaining
|
||||
stack:set_count(new_stack_size)
|
||||
mcl_log("We have more than enough space. Now holds: " .. new_stack_size)
|
||||
|
||||
inv:set_stack("main", i, stack)
|
||||
items_remaining = 0
|
||||
|
||||
ent.object:get_luaentity().itemstring = ""
|
||||
ent.object:remove()
|
||||
|
||||
taken_items = true
|
||||
break
|
||||
end
|
||||
|
||||
mcl_log("Count: " .. tostring(stack:get_count()))
|
||||
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
|
||||
--mcl_log("Is it empty: " .. stack:to_string())
|
||||
end
|
||||
|
||||
if i == self._inv_size and taken_items then
|
||||
mcl_log("We are on last item and still have items left. Set final stack size: " .. items_remaining)
|
||||
current_itemstack:set_count(items_remaining)
|
||||
--mcl_log("Itemstack2: " .. current_itemstack:to_string())
|
||||
ent.itemstring = current_itemstack:to_string()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--Add in, and delete
|
||||
if taken_items then
|
||||
mcl_log("Saving")
|
||||
mcl_entity_invs.save_inv(ent)
|
||||
return taken_items
|
||||
else
|
||||
mcl_log("No need to save")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Minecart with Hopper
|
||||
mod.register_minecart({
|
||||
itemstring = "mcl_minecarts:hopper_minecart",
|
||||
craft = {
|
||||
output = "mcl_minecarts:hopper_minecart",
|
||||
recipe = {
|
||||
{"mcl_hoppers:hopper"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
},
|
||||
entity_id = "mcl_minecarts:hopper_minecart",
|
||||
description = S("Minecart with Hopper"),
|
||||
tt_help = nil,
|
||||
longdesc = nil,
|
||||
usagehelp = nil,
|
||||
initial_properties = {
|
||||
mesh = "mcl_minecarts_minecart_hopper.b3d",
|
||||
textures = {
|
||||
"mcl_hoppers_hopper_inside.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
"mcl_hoppers_hopper_outside.png",
|
||||
"mcl_hoppers_hopper_top.png",
|
||||
},
|
||||
},
|
||||
icon = "mcl_minecarts_minecart_hopper.png",
|
||||
drop = {"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
|
||||
groups = { container = 1 },
|
||||
on_rightclick = nil,
|
||||
on_activate_by_rail = nil,
|
||||
_mcl_minecarts_on_enter = function(self, pos, staticdata)
|
||||
if (staticdata.hopper_delay or 0) > 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- try to pull from containers into our inventory
|
||||
if not self then return end
|
||||
local inv = mcl_entity_invs.load_inv(self,5)
|
||||
local above_pos = vector.offset(pos,0,1,0)
|
||||
mcl_util.hopper_pull_to_inventory(inv, 'main', above_pos, pos)
|
||||
|
||||
staticdata.hopper_delay = (staticdata.hopper_delay or 0) + (1/20)
|
||||
end,
|
||||
_mcl_minecarts_on_step = function(self, dtime)
|
||||
hopper_take_item(self, dtime)
|
||||
end,
|
||||
creative = true
|
||||
})
|
||||
mcl_entity_invs.register_inv("mcl_minecarts:hopper_minecart", "Hopper Minecart", 5, false, true)
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local function detonate_tnt_minecart(self)
|
||||
local pos = self.object:get_pos()
|
||||
self.object:remove()
|
||||
mcl_explosions.explode(pos, 6, { drop_chance = 1.0 })
|
||||
end
|
||||
|
||||
local function activate_tnt_minecart(self, timer)
|
||||
if self._boomtimer then
|
||||
return
|
||||
end
|
||||
if timer then
|
||||
self._boomtimer = timer
|
||||
else
|
||||
self._boomtimer = tnt.BOOMTIMER
|
||||
end
|
||||
self.object:set_properties({textures = {
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
}})
|
||||
self._blinktimer = tnt.BLINKTIMER
|
||||
minetest.sound_play("tnt_ignite", {pos = self.object:get_pos(), gain = 1.0, max_hear_distance = 15}, true)
|
||||
end
|
||||
mod.register_minecart({
|
||||
itemstring = "mcl_minecarts:tnt_minecart",
|
||||
craft = {
|
||||
output = "mcl_minecarts:tnt_minecart",
|
||||
recipe = {
|
||||
{"mcl_tnt:tnt"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
},
|
||||
entity_id = "mcl_minecarts:tnt_minecart",
|
||||
description = S("Minecart with TNT"),
|
||||
tt_help = S("Vehicle for fast travel on rails").."\n"..S("Can be ignited by tools or powered activator rail"),
|
||||
longdesc = S("A minecart with TNT is an explosive vehicle that travels on rail."),
|
||||
usagehelp = S("Place it on rails. Punch it to move it. The TNT is ignited with a flint and steel or when the minecart is on an powered activator rail.") .. "\n" ..
|
||||
S("To obtain the minecart and TNT, punch them while holding down the sneak key. You can't do this if the TNT was ignited."),
|
||||
initial_properties = {
|
||||
mesh = "mcl_minecarts_minecart_block.b3d",
|
||||
textures = {
|
||||
"default_tnt_top.png",
|
||||
"default_tnt_bottom.png",
|
||||
"default_tnt_side.png",
|
||||
"default_tnt_side.png",
|
||||
"default_tnt_side.png",
|
||||
"default_tnt_side.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
},
|
||||
},
|
||||
icon = "mcl_minecarts_minecart_tnt.png",
|
||||
drop = {"mcl_minecarts:minecart", "mcl_tnt:tnt"},
|
||||
on_rightclick = function(self, clicker)
|
||||
-- Ingite
|
||||
if not clicker or not clicker:is_player() then
|
||||
return
|
||||
end
|
||||
if self._boomtimer then
|
||||
return
|
||||
end
|
||||
local held = clicker:get_wielded_item()
|
||||
if held:get_name() == "mcl_fire:flint_and_steel" then
|
||||
if not minetest.is_creative_enabled(clicker:get_player_name()) then
|
||||
held:add_wear(65535/65) -- 65 uses
|
||||
local index = clicker:get_wield_index()
|
||||
local inv = clicker:get_inventory()
|
||||
inv:set_stack("main", index, held)
|
||||
end
|
||||
activate_tnt_minecart(self)
|
||||
end
|
||||
end,
|
||||
on_activate_by_rail = activate_tnt_minecart,
|
||||
creative = true,
|
||||
_mcl_minecarts_on_step = function(self, dtime)
|
||||
-- Impacts reduce the speed greatly. Use this to trigger explosions
|
||||
local current_speed = vector.length(self.object:get_velocity())
|
||||
if current_speed < (self._old_speed or 0) - 6 then
|
||||
detonate_tnt_minecart(self)
|
||||
end
|
||||
self._old_speed = current_speed
|
||||
|
||||
if self._boomtimer then
|
||||
-- Explode
|
||||
self._boomtimer = self._boomtimer - dtime
|
||||
if self._boomtimer <= 0 then
|
||||
detonate_tnt_minecart(self)
|
||||
return
|
||||
else
|
||||
tnt.smoke_step(pos)
|
||||
end
|
||||
end
|
||||
|
||||
if self._blinktimer then
|
||||
self._blinktimer = self._blinktimer - dtime
|
||||
if self._blinktimer <= 0 then
|
||||
self._blink = not self._blink
|
||||
if self._blink then
|
||||
self.object:set_properties({textures =
|
||||
{
|
||||
"default_tnt_top.png",
|
||||
"default_tnt_bottom.png",
|
||||
"default_tnt_side.png",
|
||||
"default_tnt_side.png",
|
||||
"default_tnt_side.png",
|
||||
"default_tnt_side.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
}})
|
||||
else
|
||||
self.object:set_properties({textures =
|
||||
{
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
}})
|
||||
end
|
||||
self._blinktimer = tnt.BLINKTIMER
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
|
@ -1,4 +1,13 @@
|
|||
local vector = vector
|
||||
local mod = mcl_minecarts
|
||||
local table_merge = mcl_util.table_merge
|
||||
|
||||
function get_path(base, first, ...)
|
||||
if not first then return base end
|
||||
if not base then return end
|
||||
return get_path(base[first], ...)
|
||||
end
|
||||
local force_get_node = mcl_util.force_get_node
|
||||
|
||||
function mcl_minecarts:get_sign(z)
|
||||
if z == 0 then
|
||||
|
@ -10,158 +19,388 @@ end
|
|||
|
||||
function mcl_minecarts:velocity_to_dir(v)
|
||||
if math.abs(v.x) > math.abs(v.z) then
|
||||
return {x=mcl_minecarts:get_sign(v.x), y=mcl_minecarts:get_sign(v.y), z=0}
|
||||
return vector.new(
|
||||
mcl_minecarts:get_sign(v.x),
|
||||
mcl_minecarts:get_sign(v.y),
|
||||
0
|
||||
)
|
||||
else
|
||||
return {x=0, y=mcl_minecarts:get_sign(v.y), z=mcl_minecarts:get_sign(v.z)}
|
||||
return vector.new(
|
||||
0,
|
||||
mcl_minecarts:get_sign(v.y),
|
||||
mcl_minecarts:get_sign(v.z)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_minecarts:is_rail(pos, railtype)
|
||||
local node = minetest.get_node(pos).name
|
||||
if node == "ignore" then
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local emin, emax = vm:read_from_map(pos, pos)
|
||||
local area = VoxelArea:new{
|
||||
MinEdge = emin,
|
||||
MaxEdge = emax,
|
||||
}
|
||||
local data = vm:get_data()
|
||||
local vi = area:indexp(pos)
|
||||
node = minetest.get_name_from_content_id(data[vi])
|
||||
end
|
||||
if minetest.get_item_group(node, "rail") == 0 then
|
||||
local node_name = force_get_node(pos).name
|
||||
|
||||
if minetest.get_item_group(node_name, "rail") == 0 then
|
||||
return false
|
||||
end
|
||||
if not railtype then
|
||||
return true
|
||||
end
|
||||
return minetest.get_item_group(node, "connect_to_raillike") == railtype
|
||||
return minetest.get_item_group(node_name, "connect_to_raillike") == railtype
|
||||
end
|
||||
|
||||
function mcl_minecarts:check_front_up_down(pos, dir_, check_down, railtype)
|
||||
local dir = vector.new(dir_)
|
||||
-- Front
|
||||
dir.y = 0
|
||||
local cur = vector.add(pos, dir)
|
||||
if mcl_minecarts:is_rail(cur, railtype) then
|
||||
return dir
|
||||
end
|
||||
-- Up
|
||||
if check_down then
|
||||
dir.y = 1
|
||||
cur = vector.add(pos, dir)
|
||||
if mcl_minecarts:is_rail(cur, railtype) then
|
||||
return dir
|
||||
-- Directional constants
|
||||
local north = vector.new( 0, 0, 1); local N = 1 -- 4dir = 0
|
||||
local east = vector.new( 1, 0, 0); local E = 4 -- 4dir = 1
|
||||
local south = vector.new( 0, 0,-1); local S = 2 -- 4dir = 2 Note: S is overwritten below with the translator
|
||||
local west = vector.new(-1, 0, 0); local W = 8 -- 4dir = 3
|
||||
|
||||
-- Share. Consider moving this to some shared location
|
||||
mod.north = north
|
||||
mod.south = south
|
||||
mod.east = east
|
||||
mod.west = west
|
||||
|
||||
--[[
|
||||
mcl_minecarts.snap_direction(dir)
|
||||
|
||||
returns a valid cart direction that has the smallest angle difference to `dir'
|
||||
]]
|
||||
local VALID_DIRECTIONS = {
|
||||
north, vector.offset(north, 0, 1, 0), vector.offset(north, 0, -1, 0),
|
||||
south, vector.offset(south, 0, 1, 0), vector.offset(south, 0, -1, 0),
|
||||
east, vector.offset(east, 0, 1, 0), vector.offset(east, 0, -1, 0),
|
||||
west, vector.offset(west, 0, 1, 0), vector.offset(west, 0, -1, 0),
|
||||
}
|
||||
function mod.snap_direction(dir)
|
||||
dir = vector.normalize(dir)
|
||||
local best = nil
|
||||
local diff = -1
|
||||
for _,d in pairs(VALID_DIRECTIONS) do
|
||||
local dot = vector.dot(dir,d)
|
||||
if dot > diff then
|
||||
best = d
|
||||
diff = dot
|
||||
end
|
||||
end
|
||||
-- Down
|
||||
dir.y = -1
|
||||
cur = vector.add(pos, dir)
|
||||
if mcl_minecarts:is_rail(cur, railtype) then
|
||||
return dir
|
||||
end
|
||||
return nil
|
||||
return best
|
||||
end
|
||||
|
||||
function mcl_minecarts:get_rail_direction(pos_, dir, ctrl, old_switch, railtype)
|
||||
local pos = vector.round(pos_)
|
||||
local cur
|
||||
local left_check, right_check = true, true
|
||||
local CONNECTIONS = { north, south, east, west }
|
||||
local HORIZONTAL_STANDARD_RULES = {
|
||||
[N] = { "", 0, mask = N, score = 1, can_slope = true },
|
||||
[S] = { "", 0, mask = S, score = 1, can_slope = true },
|
||||
[N+S] = { "", 0, mask = N+S, score = 2, can_slope = true },
|
||||
|
||||
-- Check left and right
|
||||
local left = {x=0, y=0, z=0}
|
||||
local right = {x=0, y=0, z=0}
|
||||
if dir.z ~= 0 and dir.x == 0 then
|
||||
left.x = -dir.z
|
||||
right.x = dir.z
|
||||
elseif dir.x ~= 0 and dir.z == 0 then
|
||||
left.z = dir.x
|
||||
right.z = -dir.x
|
||||
end
|
||||
[E] = { "", 1, mask = E, score = 1, can_slope = true },
|
||||
[W] = { "", 1, mask = W, score = 1, can_slope = true },
|
||||
[E+W] = { "", 1, mask = E+W, score = 2, can_slope = true },
|
||||
}
|
||||
mod.HORIZONTAL_STANDARD_RULES = HORIZONTAL_STANDARD_RULES
|
||||
|
||||
if ctrl then
|
||||
if old_switch == 1 then
|
||||
left_check = false
|
||||
elseif old_switch == 2 then
|
||||
right_check = false
|
||||
end
|
||||
if ctrl.left and left_check then
|
||||
cur = mcl_minecarts:check_front_up_down(pos, left, false, railtype)
|
||||
if cur then
|
||||
return cur, 1
|
||||
end
|
||||
left_check = false
|
||||
end
|
||||
if ctrl.right and right_check then
|
||||
cur = mcl_minecarts:check_front_up_down(pos, right, false, railtype)
|
||||
if cur then
|
||||
return cur, 2
|
||||
end
|
||||
right_check = true
|
||||
end
|
||||
end
|
||||
local HORIZONTAL_CURVES_RULES = {
|
||||
[N+E] = { "_corner", 3, name = "ne corner", mask = N+E, score = 3 },
|
||||
[N+W] = { "_corner", 2, name = "nw corner", mask = N+W, score = 3 },
|
||||
[S+E] = { "_corner", 0, name = "se corner", mask = S+E, score = 3 },
|
||||
[S+W] = { "_corner", 1, name = "sw corner", mask = S+W, score = 3 },
|
||||
|
||||
-- Normal
|
||||
cur = mcl_minecarts:check_front_up_down(pos, dir, true, railtype)
|
||||
if cur then
|
||||
return cur
|
||||
end
|
||||
[N+E+W] = { "_tee_off", 3, mask = N+E+W, score = 4 },
|
||||
[S+E+W] = { "_tee_off", 1, mask = S+E+W, score = 4 },
|
||||
[N+S+E] = { "_tee_off", 0, mask = N+S+E, score = 4 },
|
||||
[N+S+W] = { "_tee_off", 2, mask = N+S+W, score = 4 },
|
||||
|
||||
-- Left, if not already checked
|
||||
if left_check then
|
||||
cur = mcl_minecarts:check_front_up_down(pos, left, false, railtype)
|
||||
if cur then
|
||||
return cur
|
||||
end
|
||||
end
|
||||
[N+S+E+W] = { "_cross", 0, mask = N+S+E+W, score = 5 },
|
||||
}
|
||||
table_merge(HORIZONTAL_CURVES_RULES, HORIZONTAL_STANDARD_RULES)
|
||||
mod.HORIZONTAL_CURVES_RULES = HORIZONTAL_CURVES_RULES
|
||||
|
||||
-- Right, if not already checked
|
||||
if right_check then
|
||||
cur = mcl_minecarts:check_front_up_down(pos, right, false, railtype)
|
||||
if cur then
|
||||
return cur
|
||||
end
|
||||
end
|
||||
-- Backwards
|
||||
if not old_switch then
|
||||
cur = mcl_minecarts:check_front_up_down(pos, {
|
||||
x = -dir.x,
|
||||
y = dir.y,
|
||||
z = -dir.z
|
||||
}, true, railtype)
|
||||
if cur then
|
||||
return cur
|
||||
end
|
||||
end
|
||||
return {x=0, y=0, z=0}
|
||||
end
|
||||
|
||||
local plane_adjacents = {
|
||||
vector.new(-1,0,0),
|
||||
vector.new(1,0,0),
|
||||
vector.new(0,0,-1),
|
||||
vector.new(0,0,1),
|
||||
local HORIZONTAL_RULES_BY_RAIL_GROUP = {
|
||||
[1] = HORIZONTAL_STANDARD_RULES,
|
||||
[2] = HORIZONTAL_CURVES_RULES,
|
||||
}
|
||||
|
||||
function mcl_minecarts:get_start_direction(pos)
|
||||
local dir
|
||||
local i = 0
|
||||
while (not dir and i < #plane_adjacents) do
|
||||
i = i+1
|
||||
local node = minetest.get_node_or_nil(vector.add(pos, plane_adjacents[i]))
|
||||
if node ~= nil
|
||||
and minetest.get_item_group(node.name, "rail") == 0
|
||||
and minetest.get_item_group(node.name, "solid") == 1
|
||||
and minetest.get_item_group(node.name, "opaque") == 1
|
||||
then
|
||||
dir = mcl_minecarts:check_front_up_down(pos, vector.multiply(plane_adjacents[i], -1), true)
|
||||
end
|
||||
local function check_connection_rule(pos, connections, rule)
|
||||
-- All bits in the mask must be set for the connection to be possible
|
||||
if bit.band(rule.mask,connections) ~= rule.mask then
|
||||
--print("Mask mismatch ("..tostring(rule.mask)..","..tostring(connections)..")")
|
||||
return false
|
||||
end
|
||||
return dir
|
||||
|
||||
-- If there is an allow filter, that mush also return true
|
||||
if rule.allow and rule.allow(rule, connections, pos) then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function mcl_minecarts:set_velocity(obj, dir, factor)
|
||||
obj._velocity = vector.multiply(dir, factor or 3)
|
||||
obj._old_pos = nil
|
||||
obj._punched = true
|
||||
local function make_sloped_if_straight(pos, dir)
|
||||
local node = minetest.get_node(pos)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
|
||||
local param2 = 0
|
||||
if dir == east then
|
||||
param2 = 3
|
||||
elseif dir == west then
|
||||
param2 = 1
|
||||
elseif dir == north then
|
||||
param2 = 2
|
||||
elseif dir == south then
|
||||
param2 = 0
|
||||
end
|
||||
|
||||
if get_path( nodedef, "_mcl_minecarts", "railtype" ) == "straight" then
|
||||
minetest.swap_node(pos, {name = nodedef._mcl_minecarts.base_name .. "_sloped", param2 = param2})
|
||||
end
|
||||
end
|
||||
|
||||
local function is_connection(pos, dir)
|
||||
local node = force_get_node(pos)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
|
||||
local get_next_dir = get_path(nodedef, "_mcl_minecarts", "get_next_dir")
|
||||
if not get_next_dir then return end
|
||||
|
||||
local next_dir = get_next_dir(pos, dir, node)
|
||||
next_dir.y = 0
|
||||
return vector.equals(next_dir, dir)
|
||||
end
|
||||
|
||||
local function get_rail_connections(pos, opt)
|
||||
local legacy = opt and opt.legacy
|
||||
local ignore_neighbor_connections = opt and opt.ignore_neighbor_connections
|
||||
|
||||
local connections = 0
|
||||
for i = 1,#CONNECTIONS do
|
||||
dir = CONNECTIONS[i]
|
||||
local neighbor = vector.add(pos, dir)
|
||||
local node = force_get_node(neighbor)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
|
||||
-- Only allow connections to the open ends of rails, as decribed by get_next_dir
|
||||
if mcl_minecarts:is_rail(neighbor) and ( legacy or get_path(nodedef, "_mcl_minecarts", "get_next_dir" ) ) then
|
||||
local rev_dir = vector.direction(dir,vector.zero())
|
||||
if ignore_neighbor_connections or is_connection(neighbor, rev_dir) then
|
||||
connections = bit.bor(connections, bit.lshift(1,i - 1))
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for sloped rail one block down
|
||||
local below_neighbor = vector.offset(neighbor, 0, -1, 0)
|
||||
local node = force_get_node(below_neighbor)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
if mcl_minecarts:is_rail(below_neighbor) and ( legacy or get_path(nodedef, "_mcl_minecarts", "get_next_dir" ) ) then
|
||||
local rev_dir = vector.direction(dir, vector.zero())
|
||||
if ignore_neighbor_connections or is_connection(below_neighbor, rev_dir) then
|
||||
connections = bit.bor(connections, bit.lshift(1,i - 1))
|
||||
end
|
||||
end
|
||||
end
|
||||
return connections
|
||||
end
|
||||
mod.get_rail_connections = get_rail_connections
|
||||
|
||||
local function update_rail_connections(pos, opt)
|
||||
local ignore_neighbor_connections = opt and opt.ignore_neighbor_connections
|
||||
|
||||
local node = minetest.get_node(pos)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
if not nodedef or not nodedef._mcl_minecarts then return end
|
||||
|
||||
-- Get the mappings to use
|
||||
local rules = HORIZONTAL_RULES_BY_RAIL_GROUP[nodedef.groups.rail]
|
||||
if nodedef._mcl_minecarts and nodedef._mcl_minecarts.connection_rules then -- Custom connection rules
|
||||
rules = nodedef._mcl_minecarts.connection_rules
|
||||
end
|
||||
if not rules then return end
|
||||
|
||||
-- Horizontal rules, Check for rails on each neighbor
|
||||
local connections = get_rail_connections(pos, opt)
|
||||
|
||||
-- Check for rasing rails to slopes
|
||||
for i = 1,#CONNECTIONS do
|
||||
local dir = CONNECTIONS[i]
|
||||
local neighbor = vector.add(pos, dir)
|
||||
make_sloped_if_straight( vector.offset(neighbor, 0, -1, 0), dir )
|
||||
end
|
||||
|
||||
-- Select the best allowed connection
|
||||
local rule = nil
|
||||
local score = 0
|
||||
for k,r in pairs(rules) do
|
||||
if check_connection_rule(pos, connections, r) then
|
||||
if r.score > score then
|
||||
--print("Best rule so far is "..dump(r))
|
||||
score = r.score
|
||||
rule = r
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if rule then
|
||||
-- Apply the mapping
|
||||
local new_name = nodedef._mcl_minecarts.base_name..rule[1]
|
||||
if new_name ~= node.name or node.param2 ~= rule[2] then
|
||||
--print("swapping "..node.name.." for "..new_name..","..tostring(rule[2]).." at "..tostring(pos))
|
||||
node.name = new_name
|
||||
node.param2 = rule[2]
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
|
||||
if rule.after then
|
||||
rule.after(rule, pos, connections)
|
||||
end
|
||||
end
|
||||
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
if get_path(node_def, "_mcl_minecarts", "can_slope") then
|
||||
for i=1,#CONNECTIONS do
|
||||
local dir = CONNECTIONS[i]
|
||||
local higher_rail_pos = vector.offset(pos,dir.x,1,dir.z)
|
||||
local rev_dir = vector.direction(dir,vector.zero())
|
||||
if mcl_minecarts:is_rail(higher_rail_pos) and is_connection(higher_rail_pos, rev_dir) then
|
||||
make_sloped_if_straight(pos, rev_dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
mod.update_rail_connections = update_rail_connections
|
||||
|
||||
local north = vector.new(0,0,1)
|
||||
local south = vector.new(0,0,-1)
|
||||
local east = vector.new(1,0,0)
|
||||
local west = vector.new(-1,0,0)
|
||||
|
||||
local function is_ahead_slope(pos, dir)
|
||||
local ahead = vector.add(pos,dir)
|
||||
if mcl_minecarts:is_rail(ahead) then return false end
|
||||
|
||||
local below = vector.offset(ahead,0,-1,0)
|
||||
if not mcl_minecarts:is_rail(below) then return false end
|
||||
|
||||
local node_name = force_get_node(below).name
|
||||
return minetest.get_item_group(node_name, "rail_slope") ~= 0
|
||||
end
|
||||
|
||||
local function get_rail_direction_inner(pos, dir)
|
||||
-- Handle new track types that have track-specific direction handler
|
||||
local node = minetest.get_node(pos)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
local get_next_dir = get_path(node_def,"_mcl_minecarts","get_next_dir")
|
||||
if not get_next_dir then return dir end
|
||||
|
||||
dir = node_def._mcl_minecarts.get_next_dir(pos, dir, node)
|
||||
|
||||
-- Handle reversing if there is a solid block in the next position
|
||||
local next_pos = vector.add(pos, dir)
|
||||
local next_node = minetest.get_node(next_pos)
|
||||
local node_def = minetest.registered_nodes[next_node.name]
|
||||
if node_def and node_def.groups and ( node_def.groups.solid or node_def.groups.stair ) then
|
||||
-- Reverse the direction without giving -0 members
|
||||
dir = vector.direction(next_pos, pos)
|
||||
end
|
||||
|
||||
-- Handle going downhill
|
||||
if is_ahead_slope(pos,dir) then
|
||||
dir = vector.offset(dir,0,-1,0)
|
||||
end
|
||||
|
||||
return dir
|
||||
end
|
||||
function mcl_minecarts:get_rail_direction(pos_, dir)
|
||||
local pos = vector.round(pos_)
|
||||
|
||||
-- diagonal direction handling
|
||||
if dir.x ~= 0 and dir.z ~= 0 then
|
||||
-- Check both possible diagonal movements
|
||||
local dir_a = vector.new(dir.x,0,0)
|
||||
local dir_b = vector.new(0,0,dir.z)
|
||||
local new_dir_a = mcl_minecarts:get_rail_direction(pos, dir_a)
|
||||
local new_dir_b = mcl_minecarts:get_rail_direction(pos, dir_b)
|
||||
|
||||
-- If either is the same diagonal direction, continue as you were
|
||||
if vector.equals(dir,new_dir_a) or vector.equals(dir,new_dir_b) then
|
||||
return dir
|
||||
|
||||
-- Otherwise, if either would try to move in the same direction as
|
||||
-- what tried, move that direction
|
||||
elseif vector.equals(dir_a, new_dir_a) then
|
||||
return new_dir_a
|
||||
elseif vector.equals(dir_b, new_dir_b) then
|
||||
return new_dir_b
|
||||
end
|
||||
|
||||
-- And if none of these were true, fall thru into standard behavior
|
||||
end
|
||||
|
||||
local new_dir = get_rail_direction_inner(pos, dir)
|
||||
|
||||
-- Check four 45 degree movement
|
||||
local next_rails_dir = get_rail_direction_inner(vector.add(pos, new_dir), new_dir)
|
||||
if vector.equals(next_rails_dir, dir) and not vector.equals(new_dir, next_rails_dir) then
|
||||
return vector.add(new_dir, next_rails_dir)
|
||||
else
|
||||
return new_dir
|
||||
end
|
||||
end
|
||||
function mod.update_cart_orientation(self)
|
||||
local staticdata = self._staticdata
|
||||
|
||||
-- constants
|
||||
local _2_pi = math.pi * 2
|
||||
local pi = math.pi
|
||||
local dir = staticdata.dir
|
||||
|
||||
-- Calculate an angle from the x,z direction components
|
||||
local rot_y = math.atan2( dir.x, dir.z ) + ( staticdata.rot_adjust or 0 )
|
||||
if rot_y < 0 then
|
||||
rot_y = rot_y + _2_pi
|
||||
end
|
||||
|
||||
-- Check if the rotation is a 180 flip and don't change if so
|
||||
local rot = self.object:get_rotation()
|
||||
local diff = math.abs((rot_y - ( rot.y + pi ) % _2_pi) )
|
||||
if diff < 0.001 or diff > _2_pi - 0.001 then
|
||||
-- Update rotation adjust and recalculate the rotation
|
||||
staticdata.rot_adjust = ( ( staticdata.rot_adjust or 0 ) + pi ) % _2_pi
|
||||
rot.y = math.atan2( dir.x, dir.z ) + ( staticdata.rot_adjust or 0 )
|
||||
else
|
||||
rot.y = rot_y
|
||||
end
|
||||
|
||||
-- Forward/backwards tilt (pitch)
|
||||
if dir.y < 0 then
|
||||
rot.x = -0.25 * pi
|
||||
elseif dir.y > 0 then
|
||||
rot.x = 0.25 * pi
|
||||
else
|
||||
rot.x = 0
|
||||
end
|
||||
|
||||
if ( staticdata.rot_adjust or 0 ) < 0.01 then
|
||||
rot.x = -rot.x
|
||||
end
|
||||
if dir.z ~= 0 then
|
||||
rot.x = -rot.x
|
||||
end
|
||||
|
||||
self.object:set_rotation(rot)
|
||||
end
|
||||
|
||||
function mod.get_cart_position(cart_staticdata)
|
||||
local data = cart_staticdata
|
||||
if not data then return nil end
|
||||
if not data.connected_at then return nil end
|
||||
|
||||
return vector.add(data.connected_at, vector.multiply(data.dir or vector.zero(), data.distance or 0))
|
||||
end
|
||||
|
||||
function mod.reverse_cart_direction(staticdata)
|
||||
|
||||
-- Complete moving thru this block into the next, reverse direction, and put us back at the same position we were at
|
||||
local next_dir = -staticdata.dir
|
||||
staticdata.connected_at = staticdata.connected_at + staticdata.dir
|
||||
staticdata.distance = 1 - (staticdata.distance or 0)
|
||||
|
||||
-- recalculate direction
|
||||
local next_dir,_ = mod:get_rail_direction(staticdata.connected_at, next_dir)
|
||||
staticdata.dir = next_dir
|
||||
end
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
|||
name = mcl_minecarts
|
||||
author = Krock
|
||||
description = Minecarts are vehicles to move players quickly on rails.
|
||||
depends = mcl_title, mcl_explosions, mcl_core, mcl_sounds, mcl_player, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons, mcl_entity_invs
|
||||
optional_depends = doc_identifier, mcl_wip
|
||||
depends = mcl_title, mcl_explosions, mcl_core, mcl_util, mcl_sounds, mcl_player, mcl_playerinfo, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons, mcl_entity_invs
|
||||
optional_depends = doc_identifier, mcl_wip, mcl_physics, vl_physics
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# hand-made Wavefront .OBJ file for sloped rail
|
||||
mtllib mcl_minecarts_rail.mtl
|
||||
o flat_track.001
|
||||
v -0.500000 -0.437500 -0.500000
|
||||
v -0.500000 -0.437500 0.500000
|
||||
v 0.500000 -0.437500 0.500000
|
||||
v 0.500000 -0.437500 -0.500000
|
||||
vt 1.000000 0.000000
|
||||
vt 1.000000 1.000000
|
||||
vt 0.000000 1.000000
|
||||
vt 0.000000 0.000000
|
||||
vn 0.000000 1.000000 0.000000
|
||||
usemtl None
|
||||
s off
|
||||
f 1/1/1 2/2/1 3/3/1 4/4/1
|
|
@ -0,0 +1,15 @@
|
|||
# hand-made Wavefront .OBJ file for sloped rail
|
||||
mtllib mcl_minecarts_rail.mtl
|
||||
o sloped_rail.001
|
||||
v -0.500000 -0.437500 -0.500000
|
||||
v -0.500000 0.562500 0.500000
|
||||
v 0.500000 0.562500 0.500000
|
||||
v 0.500000 -0.437500 -0.500000
|
||||
vt 1.000000 0.000000
|
||||
vt 1.000000 1.000000
|
||||
vt 0.000000 1.000000
|
||||
vt 0.000000 0.000000
|
||||
vn 0.707106 0.707106 0.000000
|
||||
usemtl None
|
||||
s off
|
||||
f 1/1/1 2/2/1 3/3/1 4/4/1
|
|
@ -0,0 +1,564 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
-- Constants
|
||||
local mcl_debug,DEBUG = mcl_util.make_mcl_logger("mcl_logging_minecart_debug", "Minecart Debug")
|
||||
--DEBUG = false
|
||||
--mcl_debug = function(msg) print(msg) end
|
||||
|
||||
-- Imports
|
||||
local env_physics
|
||||
if minetest.get_modpath("mcl_physics") then
|
||||
env_physics = mcl_physics
|
||||
elseif minetest.get_modpath("vl_physics") then
|
||||
env_physics = vl_physics
|
||||
end
|
||||
local FRICTION = mcl_minecarts.FRICTION
|
||||
local MAX_TRAIN_LENGTH = mod.MAX_TRAIN_LENGTH
|
||||
local SPEED_MAX = mod.SPEED_MAX
|
||||
local train_length = mod.train_length
|
||||
local update_train = mod.update_train
|
||||
local reverse_train = mod.reverse_train
|
||||
local link_cart_ahead = mod.link_cart_ahead
|
||||
local update_cart_orientation = mod.update_cart_orientation
|
||||
local get_cart_data = mod.get_cart_data
|
||||
local get_cart_position = mod.get_cart_position
|
||||
|
||||
|
||||
local function reverse_direction(staticdata)
|
||||
if staticdata.behind or staticdata.ahead then
|
||||
reverse_train(staticdata)
|
||||
return
|
||||
end
|
||||
|
||||
mod.reverse_cart_direction(staticdata)
|
||||
end
|
||||
mod.reverse_direction = reverse_direction
|
||||
|
||||
|
||||
--[[
|
||||
Array of hooks { {u,v,w}, name }
|
||||
Actual position is pos + u * dir + v * right + w * up
|
||||
]]
|
||||
local enter_exit_checks = {
|
||||
{ 0, 0, 0, "" },
|
||||
{ 0, 0, 1, "_above" },
|
||||
{ 0, 0,-1, "_below" },
|
||||
{ 0, 1, 0, "_side" },
|
||||
{ 0,-1, 0, "_side" },
|
||||
}
|
||||
|
||||
local function handle_cart_enter_exit(staticdata, pos, next_dir, event)
|
||||
local luaentity = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
|
||||
local dir = staticdata.dir
|
||||
local right = vector.new( dir.z, dir.y, -dir.x)
|
||||
local up = vector.new(0,1,0)
|
||||
for i=1,#enter_exit_checks do
|
||||
local check = enter_exit_checks[i]
|
||||
|
||||
local check_pos = pos + dir * check[1] + right * check[2] + up * check[3]
|
||||
local node = minetest.get_node(check_pos)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
if node_def then
|
||||
-- node-specific hook
|
||||
local hook_name = "_mcl_minecarts_"..event..check[4]
|
||||
local hook = node_def[hook_name]
|
||||
if hook then hook(check_pos, luaentity, next_dir, pos, staticdata) end
|
||||
|
||||
-- global minecart hook
|
||||
hook = mcl_minecarts[event..check[4]]
|
||||
if hook then hook(check_pos, luaentity, next_dir, pos, staticdata, node_def) end
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle cart-specific behaviors
|
||||
if luaentity then
|
||||
local hook = luaentity["_mcl_minecarts_"..event]
|
||||
if hook then hook(luaentity, pos, staticdata) end
|
||||
else
|
||||
--minetest.log("warning", "TODO: change _mcl_minecarts_"..event.." calling so it is not dependent on the existence of a luaentity")
|
||||
end
|
||||
end
|
||||
local function set_metadata_cart_status(pos, uuid, state)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local carts = minetest.deserialize(meta:get_string("_mcl_minecarts_carts")) or {}
|
||||
carts[uuid] = state
|
||||
meta:set_string("_mcl_minecarts_carts", minetest.serialize(carts))
|
||||
end
|
||||
local function handle_cart_enter(staticdata, pos, next_dir)
|
||||
--print("entering "..tostring(pos))
|
||||
set_metadata_cart_status(pos, staticdata.uuid, 1)
|
||||
handle_cart_enter_exit(staticdata, pos, next_dir, "on_enter" )
|
||||
end
|
||||
local function handle_cart_leave(staticdata, pos, next_dir)
|
||||
--print("leaving "..tostring(pos))
|
||||
set_metadata_cart_status(pos, staticdata.uuid, nil)
|
||||
handle_cart_enter_exit(staticdata, pos, next_dir, "on_leave" )
|
||||
end
|
||||
local function handle_cart_node_watches(staticdata, dtime)
|
||||
local watches = staticdata.node_watches or {}
|
||||
local new_watches = {}
|
||||
local luaentity = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
|
||||
for i=1,#watches do
|
||||
local node_pos = watches[i]
|
||||
local node = minetest.get_node(node_pos)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
if node_def then
|
||||
local hook = node_def._mcl_minecarts_node_on_step
|
||||
if hook and hook(node_pos, luaentity, dtime, staticdata) then
|
||||
new_watches[#new_watches+1] = node_pos
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
staticdata.node_watches = new_watches
|
||||
end
|
||||
|
||||
local function detach_minecart(staticdata)
|
||||
handle_cart_leave(staticdata, staticdata.connected_at, staticdata.dir)
|
||||
staticdata.connected_at = nil
|
||||
|
||||
local luaentity = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
|
||||
if luaentity then
|
||||
luaentity.object:set_velocity(staticdata.dir * staticdata.velocity)
|
||||
end
|
||||
end
|
||||
mod.detach_minecart = detach_minecart
|
||||
|
||||
local function try_detach_minecart(staticdata)
|
||||
if not staticdata or not staticdata.connected_at then return end
|
||||
if not mod:is_rail(staticdata.connected_at) then
|
||||
mcl_debug("Detaching minecart #"..tostring(staticdata.uuid))
|
||||
detach_minecart(staticdata)
|
||||
end
|
||||
end
|
||||
|
||||
local function handle_cart_collision(cart1_staticdata, prev_pos, next_dir)
|
||||
if not cart1_staticdata then return end
|
||||
|
||||
-- Look ahead one block
|
||||
local pos = vector.add(prev_pos, next_dir)
|
||||
|
||||
local meta = minetest.get_meta(pos)
|
||||
local carts = minetest.deserialize(meta:get_string("_mcl_minecarts_carts")) or {}
|
||||
local cart_uuid = nil
|
||||
local dirty = false
|
||||
for uuid,v in pairs(carts) do
|
||||
-- Clean up dead carts
|
||||
local data = get_cart_data(uuid)
|
||||
if not data or not data.connected_at then
|
||||
carts[uuid] = nil
|
||||
dirty = true
|
||||
uuid = nil
|
||||
end
|
||||
|
||||
if uuid and uuid ~= cart1_staticdata.uuid then cart_uuid = uuid end
|
||||
end
|
||||
if dirty then
|
||||
meta:set_string("_mcl_minecarts_carts",minetest.serialize(carts))
|
||||
end
|
||||
|
||||
local meta = minetest.get_meta(vector.add(pos,next_dir))
|
||||
if not cart_uuid then return end
|
||||
|
||||
-- Don't collide with the train car in front of you
|
||||
if cart1_staticdata.ahead == cart_uuid then return end
|
||||
|
||||
minetest.log("action","cart #"..cart1_staticdata.uuid.." collided with cart #"..cart_uuid.." at "..tostring(pos))
|
||||
|
||||
-- Standard Collision Handling
|
||||
local cart2_staticdata = get_cart_data(cart_uuid)
|
||||
|
||||
local u1 = cart1_staticdata.velocity
|
||||
local u2 = cart2_staticdata.velocity
|
||||
local m1 = cart1_staticdata.mass
|
||||
local m2 = cart2_staticdata.mass
|
||||
|
||||
--print("u1="..tostring(u1)..",u2="..tostring(u2))
|
||||
if u2 == 0 and u1 < 4 and train_length(cart1_staticdata) < MAX_TRAIN_LENGTH then
|
||||
link_cart_ahead(cart1_staticdata, cart2_staticdata)
|
||||
cart2_staticdata.dir = mcl_minecarts:get_rail_direction(cart2_staticdata.connected_at, cart1_staticdata.dir)
|
||||
cart2_staticdata.velocity = cart1_staticdata.velocity
|
||||
return
|
||||
end
|
||||
|
||||
-- Calculate new velocities according to https://en.wikipedia.org/wiki/Elastic_collision#One-dimensional_Newtonian
|
||||
local c1 = m1 + m2
|
||||
local d = m1 - m2
|
||||
local v1 = ( d * u1 + 2 * m2 * u2 ) / c1
|
||||
local v2 = ( 2 * m1 * u1 + d * u2 ) / c1
|
||||
|
||||
cart1_staticdata.velocity = v1
|
||||
cart2_staticdata.velocity = v2
|
||||
|
||||
-- Force the other cart to move the same direction this one was
|
||||
cart2_staticdata.dir = mcl_minecarts:get_rail_direction(cart2_staticdata.connected_at, cart1_staticdata.dir)
|
||||
end
|
||||
|
||||
local function vector_away_from_players(cart, staticdata)
|
||||
local function player_repel(obj)
|
||||
-- Only repel from players
|
||||
local player_name = obj:get_player_name()
|
||||
if not player_name or player_name == "" then return false end
|
||||
|
||||
-- Don't repel away from players in minecarts
|
||||
local player_meta = mcl_playerinfo.get_mod_meta(player_name, modname)
|
||||
if player_meta.attached_to then return false end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- Get the cart position
|
||||
local cart_pos = mod.get_cart_position(staticdata)
|
||||
if cart then cart_pos = cart.object:get_pos() end
|
||||
if not cart_pos then return nil end
|
||||
|
||||
for _,obj in pairs(minetest.get_objects_inside_radius(cart_pos, 1.1)) do
|
||||
if player_repel(obj) then
|
||||
return obj:get_pos() - cart_pos
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function direction_away_from_players(staticdata)
|
||||
local diff = vector_away_from_players(nil,staticdata)
|
||||
if not diff then return 0 end
|
||||
|
||||
local length = vector.distance(vector.zero(),diff)
|
||||
local vec = diff / length
|
||||
local force = vector.dot( vec, vector.normalize(staticdata.dir) )
|
||||
|
||||
-- Check if this would push past the end of the track and don't move it it would
|
||||
-- This prevents an oscillation that would otherwise occur
|
||||
local dir = staticdata.dir
|
||||
if force > 0 then
|
||||
dir = -dir
|
||||
end
|
||||
if mcl_minecarts:is_rail( staticdata.connected_at + dir ) then
|
||||
if force > 0.5 then
|
||||
return -length * 4
|
||||
elseif force < -0.5 then
|
||||
return length * 4
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function calculate_acceleration(staticdata)
|
||||
local acceleration = 0
|
||||
|
||||
-- Fix up movement data
|
||||
staticdata.velocity = staticdata.velocity or 0
|
||||
|
||||
-- Apply friction if moving
|
||||
if staticdata.velocity > 0 then
|
||||
acceleration = -FRICTION
|
||||
end
|
||||
|
||||
local pos = staticdata.connected_at
|
||||
local node_name = minetest.get_node(pos).name
|
||||
local node_def = minetest.registered_nodes[node_name]
|
||||
|
||||
local ctrl = staticdata.controls or {}
|
||||
local time_active = minetest.get_gametime() - 0.25
|
||||
|
||||
if (ctrl.forward or 0) > time_active then
|
||||
if staticdata.velocity == 0 then
|
||||
local look_dir = minetest.facedir_to_dir(ctrl.look or 0)
|
||||
local dot = vector.dot(staticdata.dir, look_dir)
|
||||
if dot < 0 then
|
||||
reverse_direction(staticdata)
|
||||
end
|
||||
end
|
||||
acceleration = 4
|
||||
elseif (ctrl.brake or 0) > time_active then
|
||||
acceleration = -1.5
|
||||
elseif ctrl.impulse then
|
||||
acceleration = vector.dot(staticdata.dir, ctrl.impulse)
|
||||
ctrl.impulse = nil
|
||||
elseif (staticdata.fueltime or 0) > 0 and staticdata.velocity <= 4 then
|
||||
acceleration = 0.6
|
||||
elseif staticdata.velocity >= ( node_def._max_acceleration_velocity or SPEED_MAX ) then
|
||||
-- Standard friction
|
||||
elseif node_def and node_def._rail_acceleration then
|
||||
acceleration = node_def._rail_acceleration * 4
|
||||
end
|
||||
|
||||
-- Factor in gravity after everything else
|
||||
local gravity_strength = 2.45 --friction * 5
|
||||
if staticdata.dir.y < 0 then
|
||||
acceleration = gravity_strength - FRICTION
|
||||
elseif staticdata.dir.y > 0 then
|
||||
acceleration = -gravity_strength + FRICTION
|
||||
end
|
||||
|
||||
return acceleration
|
||||
end
|
||||
|
||||
local function do_movement_step(staticdata, dtime)
|
||||
if not staticdata.connected_at then return 0 end
|
||||
|
||||
-- Calculate timestep remaiing in this block
|
||||
local x_0 = staticdata.distance or 0
|
||||
local remaining_in_block = 1 - x_0
|
||||
local a = calculate_acceleration(staticdata)
|
||||
local v_0 = staticdata.velocity
|
||||
|
||||
-- Repel minecarts
|
||||
local away = direction_away_from_players(staticdata)
|
||||
if away > 0 then
|
||||
v_0 = away
|
||||
elseif away < 0 then
|
||||
reverse_direction(staticdata)
|
||||
v_0 = -away
|
||||
end
|
||||
|
||||
if DEBUG and ( v_0 > 0 or a ~= 0 ) then
|
||||
mcl_debug(" cart "..tostring(staticdata.uuid)..
|
||||
": a="..tostring(a)..
|
||||
",v_0="..tostring(v_0)..
|
||||
",x_0="..tostring(x_0)..
|
||||
",dtime="..tostring(dtime)..
|
||||
",dir="..tostring(staticdata.dir)..
|
||||
",connected_at="..tostring(staticdata.connected_at)..
|
||||
",distance="..tostring(staticdata.distance)
|
||||
)
|
||||
end
|
||||
|
||||
-- Not moving
|
||||
if a == 0 and v_0 == 0 then return 0 end
|
||||
|
||||
-- Movement equation with acceleration: x_1 = x_0 + v_0 * t + 0.5 * a * t*t
|
||||
local timestep
|
||||
local stops_in_block = false
|
||||
local inside = v_0 * v_0 + 2 * a * remaining_in_block
|
||||
if inside < 0 then
|
||||
-- Would stop or reverse direction inside this block, calculate time to v_1 = 0
|
||||
timestep = -v_0 / a
|
||||
stops_in_block = true
|
||||
elseif a ~= 0 then
|
||||
-- Setting x_1 = x_0 + remaining_in_block, and solving for t gives:
|
||||
timestep = ( math.sqrt( v_0 * v_0 + 2 * a * remaining_in_block) - v_0 ) / a
|
||||
else
|
||||
timestep = remaining_in_block / v_0
|
||||
end
|
||||
|
||||
-- Truncate timestep to remaining time delta
|
||||
if timestep > dtime then
|
||||
timestep = dtime
|
||||
end
|
||||
|
||||
-- Truncate timestep to prevent v_1 from being larger that speed_max
|
||||
local v_max = SPEED_MAX
|
||||
if (v_0 < v_max) and ( v_0 + a * timestep > v_max) then
|
||||
timestep = ( v_max - v_0 ) / a
|
||||
end
|
||||
|
||||
-- Prevent infinite loops
|
||||
if timestep <= 0 then return 0 end
|
||||
|
||||
-- Calculate v_1 taking v_max into account
|
||||
local v_1 = v_0 + a * timestep
|
||||
if v_1 > v_max then
|
||||
v_1 = v_max
|
||||
elseif v_1 < FRICTION / 5 then
|
||||
v_1 = 0
|
||||
end
|
||||
|
||||
-- Calculate x_1
|
||||
local x_1 = x_0 + timestep * v_0 + 0.5 * a * timestep * timestep
|
||||
|
||||
-- Update position and velocity of the minecart
|
||||
staticdata.velocity = v_1
|
||||
staticdata.distance = x_1
|
||||
|
||||
if DEBUG and (1==0) and ( v_0 > 0 or a ~= 0 ) then
|
||||
mcl_debug( "- cart #"..tostring(staticdata.uuid)..
|
||||
": a="..tostring(a)..
|
||||
",v_0="..tostring(v_0)..
|
||||
",v_1="..tostring(v_1)..
|
||||
",x_0="..tostring(x_0)..
|
||||
",x_1="..tostring(x_1)..
|
||||
",timestep="..tostring(timestep)..
|
||||
",dir="..tostring(staticdata.dir)..
|
||||
",connected_at="..tostring(staticdata.connected_at)..
|
||||
",distance="..tostring(staticdata.distance)
|
||||
)
|
||||
end
|
||||
|
||||
-- Entity movement
|
||||
local pos = staticdata.connected_at
|
||||
|
||||
-- Handle movement to next block, account for loss of precision in calculations
|
||||
if x_1 >= 0.99 then
|
||||
staticdata.distance = 0
|
||||
|
||||
-- Anchor at the next node
|
||||
local old_pos = pos
|
||||
pos = pos + staticdata.dir
|
||||
staticdata.connected_at = pos
|
||||
|
||||
-- Get the next direction
|
||||
local next_dir,_ = mcl_minecarts:get_rail_direction(pos, staticdata.dir, nil, nil, staticdata.railtype)
|
||||
if DEBUG and next_dir ~= staticdata.dir then
|
||||
mcl_debug( "Changing direction from "..tostring(staticdata.dir).." to "..tostring(next_dir))
|
||||
end
|
||||
|
||||
-- Handle cart collisions
|
||||
handle_cart_collision(staticdata, pos, next_dir)
|
||||
|
||||
-- Leave the old node
|
||||
handle_cart_leave(staticdata, old_pos, next_dir )
|
||||
|
||||
-- Enter the new node
|
||||
handle_cart_enter(staticdata, pos, next_dir)
|
||||
|
||||
-- Handle end of track
|
||||
if next_dir == staticdata.dir * -1 and next_dir.y == 0 then
|
||||
if DEBUG then mcl_debug("Stopping cart at end of track at "..tostring(pos)) end
|
||||
staticdata.velocity = 0
|
||||
end
|
||||
|
||||
-- Update cart direction
|
||||
staticdata.dir = next_dir
|
||||
elseif stops_in_block and v_1 < (FRICTION/5) and a <= 0 and staticdata.dir.y > 0 then
|
||||
-- Handle direction flip due to gravity
|
||||
if DEBUG then mcl_debug("Gravity flipped direction") end
|
||||
|
||||
-- Velocity should be zero at this point
|
||||
staticdata.velocity = 0
|
||||
|
||||
reverse_direction(staticdata)
|
||||
|
||||
-- Intermediate movement
|
||||
pos = staticdata.connected_at + staticdata.dir * staticdata.distance
|
||||
else
|
||||
-- Intermediate movement
|
||||
pos = pos + staticdata.dir * staticdata.distance
|
||||
end
|
||||
|
||||
-- Debug reporting
|
||||
if DEBUG and ( v_0 > 0 or v_1 > 0 ) then
|
||||
mcl_debug( " cart #"..tostring(staticdata.uuid)..
|
||||
": a="..tostring(a)..
|
||||
",v_0="..tostring(v_0)..
|
||||
",v_1="..tostring(v_1)..
|
||||
",x_0="..tostring(x_0)..
|
||||
",x_1="..tostring(x_1)..
|
||||
",timestep="..tostring(timestep)..
|
||||
",dir="..tostring(staticdata.dir)..
|
||||
",pos="..tostring(pos)..
|
||||
",connected_at="..tostring(staticdata.connected_at)..
|
||||
",distance="..tostring(staticdata.distance)
|
||||
)
|
||||
end
|
||||
|
||||
-- Report the amount of time processed
|
||||
return dtime - timestep
|
||||
end
|
||||
|
||||
local function do_movement( staticdata, dtime )
|
||||
assert(staticdata)
|
||||
|
||||
-- Allow the carts to be delay for the rest of the world to react before moving again
|
||||
--[[
|
||||
if ( staticdata.delay or 0 ) > dtime then
|
||||
staticdata.delay = staticdata.delay - dtime
|
||||
return
|
||||
else
|
||||
staticdata.delay = 0
|
||||
end]]
|
||||
|
||||
-- Break long movements at block boundaries to make it
|
||||
-- it impossible to jump across gaps due to server lag
|
||||
-- causing large timesteps
|
||||
while dtime > 0 do
|
||||
local new_dtime = do_movement_step(staticdata, dtime)
|
||||
try_detach_minecart(staticdata)
|
||||
|
||||
update_train(staticdata)
|
||||
|
||||
-- Handle node watches here in steps to prevent server lag from changing behavior
|
||||
handle_cart_node_watches(staticdata, dtime - new_dtime)
|
||||
|
||||
dtime = new_dtime
|
||||
end
|
||||
end
|
||||
|
||||
local function do_detached_movement(self, dtime)
|
||||
local staticdata = self._staticdata
|
||||
|
||||
-- Make sure the object is still valid before trying to move it
|
||||
if not self.object or not self.object:get_pos() then return end
|
||||
|
||||
-- Apply physics
|
||||
if env_physics then
|
||||
env_physics.apply_entity_environmental_physics(self)
|
||||
else
|
||||
-- Simple physics
|
||||
local friction = self.object:get_velocity() or vector.zero()
|
||||
friction.y = 0
|
||||
|
||||
local accel = vector.new(0,-9.81,0) -- gravity
|
||||
|
||||
-- Don't apply friction in the air
|
||||
local pos_rounded = vector.round(self.object:get_pos())
|
||||
if minetest.get_node(vector.offset(pos_rounded,0,-1,0)).name ~= "air" then
|
||||
accel = vector.add(accel, vector.multiply(friction,-0.9))
|
||||
end
|
||||
|
||||
self.object:set_acceleration(accel)
|
||||
end
|
||||
|
||||
local away = vector_away_from_players(self, staticdata)
|
||||
if away then
|
||||
local v = self.object:get_velocity()
|
||||
self.object:set_velocity(v - away)
|
||||
|
||||
-- Boost the minecart vertically a bit to get over the edge of rails and things like carpets
|
||||
local boost = vector.offset(vector.multiply(vector.normalize(away), 0.1), 0, 0.07, 0) -- 1/16th + 0.0075
|
||||
local pos = self.object:get_pos()
|
||||
self.object:set_pos(vector.add(pos,boost))
|
||||
end
|
||||
|
||||
-- Try to reconnect to rail
|
||||
local pos = self.object:get_pos()
|
||||
local yaw = self.object:get_yaw()
|
||||
local yaw_dir = minetest.yaw_to_dir(yaw)
|
||||
local test_positions = {
|
||||
pos,
|
||||
vector.offset(vector.add(pos, vector.multiply(yaw_dir, 0.5)),0,-0.55,0),
|
||||
vector.offset(vector.add(pos, vector.multiply(yaw_dir,-0.5)),0,-0.55,0),
|
||||
}
|
||||
|
||||
for i=1,#test_positions do
|
||||
local test_pos = test_positions[i]
|
||||
local pos_r = vector.round(test_pos)
|
||||
local node = minetest.get_node(pos_r)
|
||||
if minetest.get_item_group(node.name, "rail") ~= 0 then
|
||||
staticdata.connected_at = pos_r
|
||||
staticdata.railtype = node.name
|
||||
|
||||
local freebody_velocity = self.object:get_velocity()
|
||||
staticdata.dir = mod:get_rail_direction(pos_r, mod.snap_direction(freebody_velocity))
|
||||
|
||||
-- Use vector projection to only keep the velocity in the new direction of movement on the rail
|
||||
-- https://en.wikipedia.org/wiki/Vector_projection
|
||||
staticdata.velocity = vector.dot(staticdata.dir,freebody_velocity)
|
||||
print("Reattached velocity="..tostring(staticdata.velocity)..", freebody_velocity="..tostring(freebody_velocity))
|
||||
|
||||
-- Clear freebody movement
|
||||
self.object:set_velocity(vector.zero())
|
||||
self.object:set_acceleration(vector.zero())
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--return do_movement, do_detatched_movement
|
||||
return do_movement,do_detached_movement,handle_cart_enter
|
||||
|
|
@ -1,53 +1,349 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
mod.RAIL_GROUPS = {
|
||||
STANDARD = 1,
|
||||
CURVES = 2,
|
||||
}
|
||||
|
||||
-- Template rail function
|
||||
local function register_rail(itemstring, tiles, def_extras, creative)
|
||||
local groups = {handy=1,pickaxey=1, attached_node=1,rail=1,connect_to_raillike=minetest.raillike_group("rail"),dig_by_water=0,destroy_by_lava_flow=0, transport=1}
|
||||
if creative == false then
|
||||
groups.not_in_creative_inventory = 1
|
||||
end
|
||||
local ndef = {
|
||||
drawtype = "raillike",
|
||||
tiles = tiles,
|
||||
is_ground_content = false,
|
||||
inventory_image = tiles[1],
|
||||
wield_image = tiles[1],
|
||||
paramtype = "light",
|
||||
walkable = false,
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
|
||||
},
|
||||
stack_max = 64,
|
||||
groups = groups,
|
||||
sounds = mcl_sounds.node_sound_metal_defaults(),
|
||||
_mcl_blast_resistance = 0.7,
|
||||
_mcl_hardness = 0.7,
|
||||
after_destruct = function(pos)
|
||||
-- Scan for minecarts in this pos and force them to execute their "floating" check.
|
||||
-- Normally, this will make them drop.
|
||||
local objs = minetest.get_objects_inside_radius(pos, 1)
|
||||
for o=1, #objs do
|
||||
local le = objs[o]:get_luaentity()
|
||||
if le then
|
||||
-- All entities in this mod are minecarts, so this works
|
||||
if string.sub(le.name, 1, 14) == "mcl_minecarts:" then
|
||||
le._last_float_check = mcl_minecarts.check_float_time
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
if def_extras then
|
||||
for k,v in pairs(def_extras) do
|
||||
ndef[k] = v
|
||||
-- Inport functions and constants from elsewhere
|
||||
local table_merge = mcl_util.table_merge
|
||||
local check_connection_rules = mod.check_connection_rules
|
||||
local update_rail_connections = mod.update_rail_connections
|
||||
local minetest_fourdir_to_dir = minetest.fourdir_to_dir
|
||||
local minetest_dir_to_fourdir = minetest.dir_to_fourdir
|
||||
local vector_offset = vector.offset
|
||||
local vector_equals = vector.equals
|
||||
local north = mod.north
|
||||
local south = mod.south
|
||||
local east = mod.east
|
||||
local west = mod.west
|
||||
|
||||
--- Rail direction Handleres
|
||||
local function rail_dir_straight(pos, dir, node)
|
||||
dir = vector.new(dir)
|
||||
dir.y = 0
|
||||
|
||||
if node.param2 == 0 or node.param2 == 2 then
|
||||
if vector_equals(dir, north) then
|
||||
return north
|
||||
else
|
||||
return south
|
||||
end
|
||||
else
|
||||
if vector_equals(dir,east) then
|
||||
return east
|
||||
else
|
||||
return west
|
||||
end
|
||||
end
|
||||
end
|
||||
local function rail_dir_sloped(pos, dir, node)
|
||||
local uphill = minetest_fourdir_to_dir(node.param2)
|
||||
local downhill = minetest_fourdir_to_dir((node.param2+2)%4)
|
||||
local up_uphill = vector_offset(uphill,0,1,0)
|
||||
|
||||
if vector_equals(dir, uphill) or vector_equals(dir, up_uphill) then
|
||||
return up_uphill
|
||||
else
|
||||
return downhill
|
||||
end
|
||||
end
|
||||
-- Fourdir to cardinal direction
|
||||
-- 0 = north
|
||||
-- 1 = east
|
||||
-- 2 = south
|
||||
-- 3 = west
|
||||
|
||||
-- This takes a table `dirs` that has one element for each cardinal direction
|
||||
-- and which specifies the direction for a cart to continue in when entering
|
||||
-- a rail node in the direction of the cardinal. This function takes node
|
||||
-- rotations into account.
|
||||
local function rail_dir_from_table(pos, dir, node, dirs)
|
||||
dir = vector.new(dir)
|
||||
dir.y = 0
|
||||
local dir_fourdir = (minetest_dir_to_fourdir(dir) - node.param2 + 4) % 4
|
||||
local new_fourdir = (dirs[dir_fourdir] + node.param2) % 4
|
||||
return minetest_fourdir_to_dir(new_fourdir)
|
||||
end
|
||||
|
||||
local CURVE_RAIL_DIRS = { [0] = 1, 1, 2, 2, }
|
||||
local function rail_dir_curve(pos, dir, node)
|
||||
return rail_dir_from_table(pos, dir, node, CURVE_RAIL_DIRS)
|
||||
end
|
||||
local function rail_dir_tee_off(pos, dir, node)
|
||||
return rail_dir_from_table(pos, dir, node, CURVE_RAIL_DIRS)
|
||||
end
|
||||
|
||||
local TEE_RAIL_ON_DIRS = { [0] = 0, 1, 1, 0 }
|
||||
local function rail_dir_tee_on(pos, dir, node)
|
||||
return rail_dir_from_table(pos, dir, node, TEE_RAIL_ON_DIRS)
|
||||
end
|
||||
|
||||
local function rail_dir_cross(pos, dir, node)
|
||||
dir = vector.new(dir)
|
||||
dir.y = 0
|
||||
|
||||
-- Always continue in the same direction. No direction changes allowed
|
||||
return dir
|
||||
end
|
||||
|
||||
-- Setup shared text
|
||||
local railuse = S(
|
||||
"Place them on the ground to build your railway, the rails will automatically connect to each other and will"..
|
||||
" turn into curves, T-junctions, crossings and slopes as needed."
|
||||
)
|
||||
mod.text = mod.text or {}
|
||||
mod.text.railuse = railuse
|
||||
local BASE_DEF = {
|
||||
drawtype = "mesh",
|
||||
mesh = "flat_track.obj",
|
||||
paramtype = "light",
|
||||
paramtype2 = "4dir",
|
||||
stack_max = 64,
|
||||
sounds = mcl_sounds.node_sound_metal_defaults(),
|
||||
is_ground_content = true,
|
||||
paramtype = "light",
|
||||
use_texture_alpha = "clip",
|
||||
collision_box = {
|
||||
type = "fixed",
|
||||
fixed = { -8/16, -8/16, -8/16, 8/16, -7/16, 8/15 }
|
||||
},
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
|
||||
},
|
||||
groups = {
|
||||
handy=1, pickaxey=1,
|
||||
attached_node=1,
|
||||
rail=1,
|
||||
connect_to_raillike=minetest.raillike_group("rail"),
|
||||
dig_by_water=0,destroy_by_lava_flow=0,
|
||||
transport=1
|
||||
},
|
||||
description = S("New Rail"), -- Temporary name to make debugging easier
|
||||
_tt_help = S("Track for minecarts"),
|
||||
_doc_items_usagehelp = railuse,
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction."),
|
||||
on_place = function(itemstack, placer, pointed_thing)
|
||||
local node_name = minetest.get_node(pointed_thing.under).name
|
||||
-- Don't allow placing rail above rail
|
||||
if minetest.get_item_group(node_name,"rail") == 0 then
|
||||
return minetest.item_place_node(itemstack, placer, pointed_thing)
|
||||
else
|
||||
return itemstack
|
||||
end
|
||||
end,
|
||||
after_place_node = function(pos, placer, itemstack, pointed_thing)
|
||||
update_rail_connections(pos)
|
||||
end,
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_straight,
|
||||
},
|
||||
_mcl_blast_resistance = 0.7,
|
||||
_mcl_hardness = 0.7,
|
||||
}
|
||||
|
||||
local SLOPED_RAIL_DEF = table.copy(BASE_DEF)
|
||||
table_merge(SLOPED_RAIL_DEF,{
|
||||
drawtype = "mesh",
|
||||
mesh = "sloped_track.obj",
|
||||
groups = {
|
||||
rail_slope = 1,
|
||||
not_in_creative_inventory = 1,
|
||||
},
|
||||
collision_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -0.5, -0.5, -0.5, 0.5, 0.0, 0.5 },
|
||||
{ -0.5, 0.0, 0.0, 0.5, 0.5, 0.5 }
|
||||
}
|
||||
},
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -0.5, -0.5, -0.5, 0.5, 0.0, 0.5 },
|
||||
{ -0.5, 0.0, 0.0, 0.5, 0.5, 0.5 }
|
||||
}
|
||||
},
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_sloped,
|
||||
},
|
||||
})
|
||||
|
||||
function mod.register_rail(itemstring, ndef)
|
||||
assert(ndef.tiles)
|
||||
|
||||
-- Extract out the craft recipe
|
||||
local craft = ndef.craft
|
||||
ndef.craft = nil
|
||||
|
||||
-- Add sensible defaults
|
||||
if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
|
||||
if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end
|
||||
|
||||
--print("registering rail "..itemstring.." with definition: "..dump(ndef))
|
||||
|
||||
-- Make registrations
|
||||
minetest.register_node(itemstring, ndef)
|
||||
if craft then minetest.register_craft(craft) end
|
||||
end
|
||||
|
||||
function mod.register_straight_rail(base_name, tiles, def)
|
||||
def = def or {}
|
||||
local base_def = table.copy(BASE_DEF)
|
||||
local sloped_def = table.copy(SLOPED_RAIL_DEF)
|
||||
local add = {
|
||||
tiles = { tiles[1] },
|
||||
drop = base_name,
|
||||
groups = {
|
||||
rail = mod.RAIL_GROUPS.STANDARD,
|
||||
},
|
||||
_mcl_minecarts = {
|
||||
base_name = base_name,
|
||||
can_slope = true,
|
||||
},
|
||||
}
|
||||
table_merge(base_def, add); table_merge(sloped_def, add)
|
||||
table_merge(base_def, def); table_merge(sloped_def, def)
|
||||
|
||||
-- Register the base node
|
||||
mod.register_rail(base_name, base_def)
|
||||
base_def.craft = nil; sloped_def.craft = nil
|
||||
table_merge(base_def,{
|
||||
_mcl_minecarts = {
|
||||
railtype = "straight",
|
||||
},
|
||||
})
|
||||
|
||||
-- Sloped variant
|
||||
mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_sloped,
|
||||
},
|
||||
mesecons = def.mesecons_sloped,
|
||||
tiles = { tiles[1] },
|
||||
_mcl_minecarts = {
|
||||
railtype = "sloped",
|
||||
},
|
||||
}))
|
||||
end
|
||||
|
||||
function mod.register_curves_rail(base_name, tiles, def)
|
||||
def = def or {}
|
||||
local base_def = table.copy(BASE_DEF)
|
||||
local sloped_def = table.copy(SLOPED_RAIL_DEF)
|
||||
local add = {
|
||||
_mcl_minecarts = { base_name = base_name },
|
||||
groups = {
|
||||
rail = mod.RAIL_GROUPS.CURVES
|
||||
},
|
||||
drop = base_name,
|
||||
}
|
||||
table_merge(base_def, add); table_merge(sloped_def, add)
|
||||
table_merge(base_def, def); table_merge(sloped_def, def)
|
||||
|
||||
-- Register the base node
|
||||
mod.register_rail(base_name, table_merge(table.copy(base_def),{
|
||||
tiles = { tiles[1] },
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_straight,
|
||||
railtype = "straight",
|
||||
can_slope = true,
|
||||
},
|
||||
}))
|
||||
|
||||
-- Update for other variants
|
||||
base_def.craft = nil
|
||||
table_merge(base_def, {
|
||||
groups = {
|
||||
not_in_creative_inventory = 1
|
||||
}
|
||||
})
|
||||
|
||||
-- Corner variants
|
||||
mod.register_rail(base_name.."_corner", table_merge(table.copy(base_def),{
|
||||
tiles = { tiles[2] },
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_curve,
|
||||
railtype = "corner",
|
||||
},
|
||||
}))
|
||||
|
||||
-- Tee variants
|
||||
mod.register_rail(base_name.."_tee_off", table_merge(table.copy(base_def),{
|
||||
tiles = { tiles[3] },
|
||||
mesecons = {
|
||||
effector = {
|
||||
action_on = function(pos, node)
|
||||
local new_node = {name = base_name.."_tee_on", param2 = node.param2}
|
||||
minetest.swap_node(pos, new_node)
|
||||
end,
|
||||
rules = mesecon.rules.alldirs,
|
||||
}
|
||||
},
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_tee_off,
|
||||
railtype = "tee",
|
||||
},
|
||||
}))
|
||||
mod.register_rail(base_name.."_tee_on", table_merge(table.copy(base_def),{
|
||||
tiles = { tiles[4] },
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_tee_on,
|
||||
railtype = "tee",
|
||||
},
|
||||
mesecons = {
|
||||
effector = {
|
||||
action_off = function(pos, node)
|
||||
local new_node = {name = base_name.."_tee_off", param2 = node.param2}
|
||||
minetest.swap_node(pos, new_node)
|
||||
end,
|
||||
rules = mesecon.rules.alldirs,
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
-- Sloped variant
|
||||
mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
|
||||
description = S("Sloped Rail"), -- Temporary name to make debugging easier
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_sloped,
|
||||
railtype = "tee",
|
||||
},
|
||||
tiles = { tiles[1] },
|
||||
}))
|
||||
|
||||
-- Cross variant
|
||||
mod.register_rail(base_name.."_cross", table_merge(table.copy(base_def),{
|
||||
tiles = { tiles[5] },
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_cross,
|
||||
railtype = "cross",
|
||||
},
|
||||
}))
|
||||
end
|
||||
|
||||
function mod.register_rail_sloped(itemstring, def)
|
||||
assert(def.tiles)
|
||||
|
||||
-- Build the node definition
|
||||
local ndef = table.copy(SLOPED_RAIL_DEF)
|
||||
table_merge(ndef, def)
|
||||
|
||||
-- Add sensible defaults
|
||||
if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
|
||||
if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end
|
||||
|
||||
--print("registering sloped rail "..itemstring.." with definition: "..dump(ndef))
|
||||
|
||||
-- Make registrations
|
||||
minetest.register_node(itemstring, ndef)
|
||||
end
|
||||
|
||||
-- Redstone rules
|
||||
local rail_rules_long =
|
||||
mod.rail_rules_long =
|
||||
{{x=-1, y= 0, z= 0, spread=true},
|
||||
{x= 1, y= 0, z= 0, spread=true},
|
||||
{x= 0, y=-1, z= 0, spread=true},
|
||||
|
@ -64,202 +360,96 @@ local rail_rules_long =
|
|||
{x= 0, y= 1, z=-1},
|
||||
{x= 0, y=-1, z=-1}}
|
||||
|
||||
local rail_rules_short = mesecon.rules.pplate
|
||||
|
||||
local railuse = S("Place them on the ground to build your railway, the rails will automatically connect to each other and will turn into curves, T-junctions, crossings and slopes as needed.")
|
||||
|
||||
-- Normal rail
|
||||
register_rail("mcl_minecarts:rail",
|
||||
{"default_rail.png", "default_rail_curved.png", "default_rail_t_junction.png", "default_rail_crossing.png"},
|
||||
{
|
||||
description = S("Rail"),
|
||||
_tt_help = S("Track for minecarts"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction."),
|
||||
_doc_items_usagehelp = railuse,
|
||||
}
|
||||
)
|
||||
|
||||
-- Powered rail (off = brake mode)
|
||||
register_rail("mcl_minecarts:golden_rail",
|
||||
{"mcl_minecarts_rail_golden.png", "mcl_minecarts_rail_golden_curved.png", "mcl_minecarts_rail_golden_t_junction.png", "mcl_minecarts_rail_golden_crossing.png"},
|
||||
{
|
||||
description = S("Powered Rail"),
|
||||
_tt_help = S("Track for minecarts").."\n"..S("Speed up when powered, slow down when not powered"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Powered rails are able to accelerate and brake minecarts."),
|
||||
_doc_items_usagehelp = railuse .. "\n" .. S("Without redstone power, the rail will brake minecarts. To make this rail accelerate minecarts, power it with redstone power."),
|
||||
_rail_acceleration = -3,
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.off,
|
||||
offstate = "mcl_minecarts:golden_rail",
|
||||
onstate = "mcl_minecarts:golden_rail_on",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
-- Powered rail (on = acceleration mode)
|
||||
register_rail("mcl_minecarts:golden_rail_on",
|
||||
{"mcl_minecarts_rail_golden_powered.png", "mcl_minecarts_rail_golden_curved_powered.png", "mcl_minecarts_rail_golden_t_junction_powered.png", "mcl_minecarts_rail_golden_crossing_powered.png"},
|
||||
{
|
||||
_doc_items_create_entry = false,
|
||||
_rail_acceleration = 4,
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.on,
|
||||
offstate = "mcl_minecarts:golden_rail",
|
||||
onstate = "mcl_minecarts:golden_rail_on",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
effector = {
|
||||
action_on = function(pos, node)
|
||||
local dir = mcl_minecarts:get_start_direction(pos)
|
||||
if not dir then return end
|
||||
local objs = minetest.get_objects_inside_radius(pos, 1)
|
||||
for _, o in pairs(objs) do
|
||||
local l = o:get_luaentity()
|
||||
local v = o:get_velocity()
|
||||
if l and string.sub(l.name, 1, 14) == "mcl_minecarts:"
|
||||
and v and vector.equals(v, vector.zero())
|
||||
then
|
||||
mcl_minecarts:set_velocity(l, dir)
|
||||
end
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
drop = "mcl_minecarts:golden_rail",
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
-- Activator rail (off)
|
||||
register_rail("mcl_minecarts:activator_rail",
|
||||
{"mcl_minecarts_rail_activator.png", "mcl_minecarts_rail_activator_curved.png", "mcl_minecarts_rail_activator_t_junction.png", "mcl_minecarts_rail_activator_crossing.png"},
|
||||
{
|
||||
description = S("Activator Rail"),
|
||||
_tt_help = S("Track for minecarts").."\n"..S("Activates minecarts when powered"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Activator rails are used to activate special minecarts."),
|
||||
_doc_items_usagehelp = railuse .. "\n" .. S("To make this rail activate minecarts, power it with redstone power and send a minecart over this piece of rail."),
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.off,
|
||||
offstate = "mcl_minecarts:activator_rail",
|
||||
onstate = "mcl_minecarts:activator_rail_on",
|
||||
rules = rail_rules_long,
|
||||
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
-- Activator rail (on)
|
||||
register_rail("mcl_minecarts:activator_rail_on",
|
||||
{"mcl_minecarts_rail_activator_powered.png", "mcl_minecarts_rail_activator_curved_powered.png", "mcl_minecarts_rail_activator_t_junction_powered.png", "mcl_minecarts_rail_activator_crossing_powered.png"},
|
||||
{
|
||||
_doc_items_create_entry = false,
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.on,
|
||||
offstate = "mcl_minecarts:activator_rail",
|
||||
onstate = "mcl_minecarts:activator_rail_on",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
effector = {
|
||||
-- Activate minecarts
|
||||
action_on = function(pos, node)
|
||||
local pos2 = { x = pos.x, y =pos.y + 1, z = pos.z }
|
||||
local objs = minetest.get_objects_inside_radius(pos2, 1)
|
||||
for _, o in pairs(objs) do
|
||||
local l = o:get_luaentity()
|
||||
if l and string.sub(l.name, 1, 14) == "mcl_minecarts:" and l.on_activate_by_rail then
|
||||
l:on_activate_by_rail()
|
||||
end
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
},
|
||||
drop = "mcl_minecarts:activator_rail",
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
-- Detector rail (off)
|
||||
register_rail("mcl_minecarts:detector_rail",
|
||||
{"mcl_minecarts_rail_detector.png", "mcl_minecarts_rail_detector_curved.png", "mcl_minecarts_rail_detector_t_junction.png", "mcl_minecarts_rail_detector_crossing.png"},
|
||||
{
|
||||
description = S("Detector Rail"),
|
||||
_tt_help = S("Track for minecarts").."\n"..S("Emits redstone power when a minecart is detected"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. A detector rail is able to detect a minecart above it and powers redstone mechanisms."),
|
||||
_doc_items_usagehelp = railuse .. "\n" .. S("To detect a minecart and provide redstone power, connect it to redstone trails or redstone mechanisms and send any minecart over the rail."),
|
||||
mesecons = {
|
||||
receptor = {
|
||||
state = mesecon.state.off,
|
||||
rules = rail_rules_short,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
-- Detector rail (on)
|
||||
register_rail("mcl_minecarts:detector_rail_on",
|
||||
{"mcl_minecarts_rail_detector_powered.png", "mcl_minecarts_rail_detector_curved_powered.png", "mcl_minecarts_rail_detector_t_junction_powered.png", "mcl_minecarts_rail_detector_crossing_powered.png"},
|
||||
{
|
||||
_doc_items_create_entry = false,
|
||||
mesecons = {
|
||||
receptor = {
|
||||
state = mesecon.state.on,
|
||||
rules = rail_rules_short,
|
||||
},
|
||||
},
|
||||
drop = "mcl_minecarts:detector_rail",
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
|
||||
-- Crafting
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:rail 16",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
}
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:golden_rail 6",
|
||||
recipe = {
|
||||
{"mcl_core:gold_ingot", "", "mcl_core:gold_ingot"},
|
||||
{"mcl_core:gold_ingot", "mcl_core:stick", "mcl_core:gold_ingot"},
|
||||
{"mcl_core:gold_ingot", "mesecons:redstone", "mcl_core:gold_ingot"},
|
||||
}
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:activator_rail 6",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mesecons_torch:mesecon_torch_on", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
}
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:detector_rail 6",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mesecons_pressureplates:pressure_plate_stone_off", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mesecons:redstone", "mcl_core:iron_ingot"},
|
||||
}
|
||||
})
|
||||
|
||||
dofile(modpath.."/rails/normal.lua")
|
||||
dofile(modpath.."/rails/activator.lua")
|
||||
dofile(modpath.."/rails/detector.lua")
|
||||
dofile(modpath.."/rails/powered.lua")
|
||||
|
||||
-- Aliases
|
||||
if minetest.get_modpath("doc") then
|
||||
doc.add_entry_alias("nodes", "mcl_minecarts:golden_rail", "nodes", "mcl_minecarts:golden_rail_on")
|
||||
end
|
||||
|
||||
local CURVY_RAILS_MAP = {
|
||||
["mcl_minecarts:rail"] = "mcl_minecarts:rail_v2",
|
||||
}
|
||||
for old,new in pairs(CURVY_RAILS_MAP) do
|
||||
nodenames = mcl_util.table_keys(STRAIGHT_RAILS_MAP),
|
||||
minetest.register_node(old, {
|
||||
drawtype = "raillike",
|
||||
inventory_image = new_def.inventory_image,
|
||||
groups = { rail = 1 },
|
||||
tiles = { new_def.tiles[1], new_def.tiles[1], new_def.tiles[1], new_def.tiles[1] },
|
||||
})
|
||||
end
|
||||
minetest.register_lbm({
|
||||
name = "mcl_minecarts:update_legacy_curvy_rails",
|
||||
nodenames = mcl_util.table_keys(CURVY_RAILS_MAP),
|
||||
action = function(pos, node)
|
||||
node.name = CURVY_RAILS_MAP[node.name]
|
||||
if node.name then
|
||||
minetest.swap_node(pos, node)
|
||||
mod.update_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
|
||||
end
|
||||
end
|
||||
})
|
||||
local STRAIGHT_RAILS_MAP ={
|
||||
["mcl_minecarts:golden_rail"] = "mcl_minecarts:golden_rail_v2",
|
||||
["mcl_minecarts:golden_rail_on"] = "mcl_minecarts:golden_rail_v2_on",
|
||||
["mcl_minecarts:activator_rail"] = "mcl_minecarts:activator_rail_v2",
|
||||
["mcl_minecarts:activator_rail_on"] = "mcl_minecarts:activator_rail_v2_on",
|
||||
["mcl_minecarts:detector_rail"] = "mcl_minecarts:detector_rail_v2",
|
||||
["mcl_minecarts:detector_rail_on"] = "mcl_minecarts:detector_rail_v2_on",
|
||||
}
|
||||
for old,new in pairs(STRAIGHT_RAILS_MAP) do
|
||||
local new_def = minetest.registered_nodes[new]
|
||||
minetest.register_node(old, {
|
||||
drawtype = "raillike",
|
||||
inventory_image = new_def.inventory_image,
|
||||
groups = { rail = 1 },
|
||||
tiles = { new_def.tiles[1], new_def.tiles[1], new_def.tiles[1], new_def.tiles[1] },
|
||||
})
|
||||
end
|
||||
local TRANSLATE_RAILS_MAP = table.copy(STRAIGHT_RAILS_MAP)
|
||||
table_merge(TRANSLATE_RAILS_MAP, CURVY_RAILS_MAP)
|
||||
|
||||
minetest.register_lbm({
|
||||
name = "mcl_minecarts:update_legacy_straight_rails",
|
||||
nodenames = mcl_util.table_keys(STRAIGHT_RAILS_MAP),
|
||||
run_at_every_load = true,
|
||||
action = function(pos, node)
|
||||
node.name = STRAIGHT_RAILS_MAP[node.name]
|
||||
if node.name then
|
||||
local connections = mod.get_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
|
||||
if not mod.HORIZONTAL_STANDARD_RULES[connections] then
|
||||
-- Drop an immortal object at this location
|
||||
local item_entity = minetest.add_item(pos, ItemStack(node.name))
|
||||
if item_entity then
|
||||
item_entity:get_luaentity()._immortal = true
|
||||
end
|
||||
|
||||
-- This is a configuration that doesn't exist in the new rail
|
||||
-- Replace with a standard rail
|
||||
node.name = "mcl_minecarts:rail_v2"
|
||||
end
|
||||
minetest.swap_node(pos, node)
|
||||
mod.update_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- Convert old rail in the player's inventory to new rail
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local inv = player:get_inventory()
|
||||
local size = inv:get_size("main")
|
||||
for i=1,size do
|
||||
local stack = inv:get_stack("main", i)
|
||||
|
||||
local new_name = TRANSLATE_RAILS_MAP[stack:get_name()]
|
||||
if new_name then
|
||||
stack:set_name(new_name)
|
||||
inv:set_stack("main", i, stack)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
-- Activator rail (off)
|
||||
mod.register_straight_rail("mcl_minecarts:activator_rail_v2", {"mcl_minecarts_rail_activator.png"},{
|
||||
description = S("Activator Rail"),
|
||||
_tt_help = S("Track for minecarts").."\n"..S("Activates minecarts when powered"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Activator rails are used to activate special minecarts."),
|
||||
_doc_items_usagehelp = mod.text.railuse .. "\n" .. S("To make this rail activate minecarts, power it with redstone power and send a minecart over this piece of rail."),
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.off,
|
||||
offstate = "mcl_minecarts:activator_rail_v2",
|
||||
onstate = "mcl_minecarts:activator_rail_v2_on",
|
||||
rules = mod.rail_rules_long,
|
||||
},
|
||||
},
|
||||
mesecons_sloped = {
|
||||
conductor = {
|
||||
state = mesecon.state.off,
|
||||
offstate = "mcl_minecarts:activator_rail_v2_sloped",
|
||||
onstate = "mcl_minecarts:activator_rail_v2_on_sloped",
|
||||
rules = mod.rail_rules_long,
|
||||
},
|
||||
},
|
||||
craft = {
|
||||
output = "mcl_minecarts:activator_rail_v2 6",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mesecons_torch:mesecon_torch_on", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
-- Activator rail (on)
|
||||
local function activator_rail_action_on(pos, node)
|
||||
local pos2 = { x = pos.x, y =pos.y + 1, z = pos.z }
|
||||
local objs = minetest.get_objects_inside_radius(pos2, 1)
|
||||
for _, o in pairs(objs) do
|
||||
local l = o:get_luaentity()
|
||||
if l and string.sub(l.name, 1, 14) == "mcl_minecarts:" and l.on_activate_by_rail then
|
||||
l:on_activate_by_rail()
|
||||
end
|
||||
end
|
||||
end
|
||||
mod.register_straight_rail("mcl_minecarts:activator_rail_v2_on", {"mcl_minecarts_rail_activator_powered.png"},{
|
||||
_doc_items_create_entry = false,
|
||||
groups = {
|
||||
not_in_creative_inventory = 1,
|
||||
},
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.on,
|
||||
offstate = "mcl_minecarts:activator_rail_v2",
|
||||
onstate = "mcl_minecarts:activator_rail_v2_on",
|
||||
rules = mod.rail_rules_long,
|
||||
},
|
||||
effector = {
|
||||
-- Activate minecarts
|
||||
action_on = activator_rail_action_on,
|
||||
},
|
||||
|
||||
},
|
||||
mesecons_sloped = {
|
||||
conductor = {
|
||||
state = mesecon.state.on,
|
||||
offstate = "mcl_minecarts:activator_rail_v2_sloped",
|
||||
onstate = "mcl_minecarts:activator_rail_v2_on_sloped",
|
||||
rules = mod.rail_rules_long,
|
||||
},
|
||||
effector = {
|
||||
-- Activate minecarts
|
||||
action_on = activator_rail_action_on,
|
||||
},
|
||||
|
||||
},
|
||||
_mcl_minecarts_on_enter = function(pos, cart)
|
||||
if cart.on_activate_by_rail then
|
||||
cart:on_activate_by_rail()
|
||||
end
|
||||
end,
|
||||
drop = "mcl_minecarts:activator_rail_v2",
|
||||
})
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local rail_rules_short = mesecon.rules.pplate
|
||||
|
||||
-- Detector rail (off)
|
||||
mod.register_straight_rail("mcl_minecarts:detector_rail_v2",{"mcl_minecarts_rail_detector.png"},{
|
||||
description = S("Detector Rail"),
|
||||
_tt_help = S("Track for minecarts").."\n"..S("Emits redstone power when a minecart is detected"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. A detector rail is able to detect a minecart above it and powers redstone mechanisms."),
|
||||
_doc_items_usagehelp = mod.text.railuse .. "\n" .. S("To detect a minecart and provide redstone power, connect it to redstone trails or redstone mechanisms and send any minecart over the rail."),
|
||||
mesecons = {
|
||||
receptor = {
|
||||
state = mesecon.state.off,
|
||||
rules = rail_rules_short,
|
||||
},
|
||||
},
|
||||
_mcl_minecarts_on_enter = function(pos, cart)
|
||||
local node = minetest.get_node(pos)
|
||||
|
||||
local newnode = {
|
||||
name = "mcl_minecarts:detector_rail_v2_on",
|
||||
param2 = node.param2
|
||||
}
|
||||
minetest.swap_node( pos, newnode )
|
||||
mesecon.receptor_on(pos)
|
||||
end,
|
||||
craft = {
|
||||
output = "mcl_minecarts:detector_rail_v2 6",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mesecons_pressureplates:pressure_plate_stone_off", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mesecons:redstone", "mcl_core:iron_ingot"},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
-- Detector rail (on)
|
||||
mod.register_straight_rail("mcl_minecarts:detector_rail_v2_on",{"mcl_minecarts_rail_detector_powered.png"},{
|
||||
groups = {
|
||||
not_in_creative_inventory = 1,
|
||||
},
|
||||
_doc_items_create_entry = false,
|
||||
mesecons = {
|
||||
receptor = {
|
||||
state = mesecon.state.on,
|
||||
rules = rail_rules_short,
|
||||
},
|
||||
},
|
||||
_mcl_minecarts_on_leave = function(pos, cart)
|
||||
local node = minetest.get_node(pos)
|
||||
|
||||
local newnode = {
|
||||
name = "mcl_minecarts:detector_rail",
|
||||
param2 = node.param2
|
||||
}
|
||||
minetest.swap_node( pos, newnode )
|
||||
mesecon.receptor_off(pos)
|
||||
end,
|
||||
drop = "mcl_minecarts:detector_rail_v2",
|
||||
})
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
-- Normal rail
|
||||
mod.register_curves_rail("mcl_minecarts:rail_v2", {
|
||||
"default_rail.png",
|
||||
"default_rail_curved.png",
|
||||
"default_rail_t_junction.png",
|
||||
"default_rail_t_junction_on.png",
|
||||
"default_rail_crossing.png"
|
||||
},{
|
||||
description = S("Rail"),
|
||||
_tt_help = S("Track for minecarts"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction."),
|
||||
_doc_items_usagehelp = mod.text.railuse,
|
||||
craft = {
|
||||
output = "mcl_minecarts:rail_v2 16",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
-- Powered rail (off = brake mode)
|
||||
mod.register_straight_rail("mcl_minecarts:golden_rail_v2",{ "mcl_minecarts_rail_golden.png" },{
|
||||
description = S("Powered Rail"),
|
||||
_tt_help = S("Track for minecarts").."\n"..S("Speed up when powered, slow down when not powered"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Powered rails are able to accelerate and brake minecarts."),
|
||||
_doc_items_usagehelp = mod.text.railuse .. "\n" .. S("Without redstone power, the rail will brake minecarts. To make this rail accelerate"..
|
||||
" minecarts, power it with redstone power."),
|
||||
_doc_items_create_entry = false,
|
||||
_rail_acceleration = -3,
|
||||
_max_acceleration_velocity = 8,
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.off,
|
||||
offstate = "mcl_minecarts:golden_rail_v2",
|
||||
onstate = "mcl_minecarts:golden_rail_v2_on",
|
||||
rules = mod.rail_rules_long,
|
||||
},
|
||||
},
|
||||
mesecons_sloped = {
|
||||
conductor = {
|
||||
state = mesecon.state.off,
|
||||
offstate = "mcl_minecarts:golden_rail_v2_sloepd",
|
||||
onstate = "mcl_minecarts:golden_rail_v2_on_sloped",
|
||||
rules = mod.rail_rules_long,
|
||||
},
|
||||
},
|
||||
drop = "mcl_minecarts:golden_rail_v2",
|
||||
craft = {
|
||||
output = "mcl_minecarts:golden_rail_v2 6",
|
||||
recipe = {
|
||||
{"mcl_core:gold_ingot", "", "mcl_core:gold_ingot"},
|
||||
{"mcl_core:gold_ingot", "mcl_core:stick", "mcl_core:gold_ingot"},
|
||||
{"mcl_core:gold_ingot", "mesecons:redstone", "mcl_core:gold_ingot"},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
-- Powered rail (on = acceleration mode)
|
||||
mod.register_straight_rail("mcl_minecarts:golden_rail_v2_on",{ "mcl_minecarts_rail_golden_powered.png" },{
|
||||
_doc_items_create_entry = false,
|
||||
_rail_acceleration = 4,
|
||||
_max_acceleration_velocity = 8,
|
||||
groups = {
|
||||
not_in_creative_inventory = 1,
|
||||
},
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.on,
|
||||
offstate = "mcl_minecarts:golden_rail_v2",
|
||||
onstate = "mcl_minecarts:golden_rail_v2_on",
|
||||
rules = mod.rail_rules_long,
|
||||
},
|
||||
},
|
||||
mesecons_sloped = {
|
||||
conductor = {
|
||||
state = mesecon.state.on,
|
||||
offstate = "mcl_minecarts:golden_rail_v2_sloped",
|
||||
onstate = "mcl_minecarts:golden_rail_v2_on_sloped",
|
||||
rules = mod.rail_rules_long,
|
||||
},
|
||||
},
|
||||
drop = "mcl_minecarts:golden_rail_v2",
|
||||
})
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
local storage = minetest.get_mod_storage()
|
||||
local mod = mcl_minecarts
|
||||
|
||||
-- Imports
|
||||
local CART_BLOCK_SIZE = mod.CART_BLOCK_SIZE
|
||||
assert(CART_BLOCK_SIZE)
|
||||
|
||||
local cart_data = {}
|
||||
local cart_data_fail_cache = {}
|
||||
local cart_ids = storage:get_keys()
|
||||
|
||||
local function get_cart_data(uuid)
|
||||
if cart_data[uuid] then return cart_data[uuid] end
|
||||
if cart_data_fail_cache[uuid] then return nil end
|
||||
|
||||
local data = minetest.deserialize(storage:get_string("cart-"..uuid))
|
||||
if not data then
|
||||
cart_data_fail_cache[uuid] = true
|
||||
return nil
|
||||
else
|
||||
-- Repair broken data
|
||||
if not data.distance then data.distance = 0 end
|
||||
if data.distance == 0/0 then data.distance = 0 end
|
||||
if data.distance == -0/0 then data.distance = 0 end
|
||||
data.dir = vector.new(data.dir)
|
||||
data.connected_at = vector.new(data.connected_at)
|
||||
end
|
||||
|
||||
cart_data[uuid] = data
|
||||
return data
|
||||
end
|
||||
mod.get_cart_data = get_cart_data
|
||||
|
||||
-- Preload all cart data into memory
|
||||
for _,id in pairs(cart_ids) do
|
||||
local uuid = string.sub(id,6)
|
||||
get_cart_data(uuid)
|
||||
end
|
||||
|
||||
local function save_cart_data(uuid)
|
||||
if not cart_data[uuid] then return end
|
||||
storage:set_string("cart-"..uuid,minetest.serialize(cart_data[uuid]))
|
||||
end
|
||||
mod.save_cart_data = save_cart_data
|
||||
|
||||
function mod.update_cart_data(data)
|
||||
local uuid = data.uuid
|
||||
cart_data[uuid] = data
|
||||
cart_data_fail_cache[uuid] = nil
|
||||
save_cart_data(uuid)
|
||||
end
|
||||
function mod.destroy_cart_data(uuid)
|
||||
storage:set_string("cart-"..uuid,"")
|
||||
cart_data[uuid] = nil
|
||||
cart_data_fail_cache[uuid] = true
|
||||
end
|
||||
|
||||
function mod.carts()
|
||||
return pairs(cart_data)
|
||||
end
|
||||
|
||||
function mod.find_carts_by_block_map(block_map)
|
||||
local cart_list = {}
|
||||
for _,data in pairs(cart_data) do
|
||||
if data and data.connected_at then
|
||||
local pos = mod.get_cart_position(data)
|
||||
local block = vector.floor(vector.divide(pos,CART_BLOCK_SIZE))
|
||||
if block_map[vector.to_string(block)] then
|
||||
cart_list[#cart_list + 1] = data
|
||||
end
|
||||
end
|
||||
end
|
||||
return cart_list
|
||||
end
|
||||
|
||||
function mod.add_blocks_to_map(block_map, min_pos, max_pos)
|
||||
local min = vector.floor(vector.divide(min_pos, CART_BLOCK_SIZE))
|
||||
local max = vector.floor(vector.divide(max_pos, CART_BLOCK_SIZE)) + vector.new(1,1,1)
|
||||
for z = min.z,max.z do
|
||||
for y = min.y,max.y do
|
||||
for x = min.x,max.x do
|
||||
block_map[ vector.to_string(vector.new(x,y,z)) ] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_on_shutdown(function()
|
||||
for uuid,_ in pairs(cart_data) do
|
||||
save_cart_data(uuid)
|
||||
end
|
||||
end)
|
|
@ -0,0 +1,155 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
|
||||
-- Imports
|
||||
local get_cart_data = mod.get_cart_data
|
||||
local save_cart_data = mod.save_cart_data
|
||||
local MAX_TRAIN_LENGTH = mod.MAX_TRAIN_LENGTH
|
||||
|
||||
-- Follow .behind to the back end of a train
|
||||
local function find_back(start)
|
||||
assert(start)
|
||||
|
||||
while start.behind do
|
||||
local nxt = get_cart_data(start.behind)
|
||||
if not nxt then return start end
|
||||
start = nxt
|
||||
end
|
||||
return start
|
||||
end
|
||||
|
||||
-- Iterate across all the cars in a train
|
||||
local function train_cars(staticdata)
|
||||
assert(staticdata)
|
||||
|
||||
local back = find_back(staticdata)
|
||||
local limit = MAX_TRAIN_LENGTH
|
||||
return function()
|
||||
if not back or limit <= 0 then return end
|
||||
limit = limit - 1
|
||||
|
||||
local ret = back
|
||||
if back.ahead then
|
||||
back = get_cart_data(back.ahead)
|
||||
else
|
||||
back = nil
|
||||
end
|
||||
return ret
|
||||
end
|
||||
end
|
||||
function mod.train_length(cart)
|
||||
local count = 0
|
||||
for cart in train_cars(cart) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
function mod.is_in_same_train(anchor, other)
|
||||
for cart in train_cars(anchor) do
|
||||
if cart.uuid == other.uuid then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function mod.distance_between_cars(car1, car2)
|
||||
if not car1.connected_at then return nil end
|
||||
if not car2.connected_at then return nil end
|
||||
|
||||
if not car1.dir then car1.dir = vector.zero() end
|
||||
if not car2.dir then car2.dir = vector.zero() end
|
||||
|
||||
local pos1 = vector.add(car1.connected_at, vector.multiply(car1.dir, car1.distance))
|
||||
local pos2 = vector.add(car2.connected_at, vector.multiply(car2.dir, car2.distance))
|
||||
|
||||
return vector.distance(pos1, pos2)
|
||||
end
|
||||
local distance_between_cars = mod.distance_between_cars
|
||||
|
||||
local function break_train_at(cart)
|
||||
if cart.ahead then
|
||||
local ahead = get_cart_data(cart.ahead)
|
||||
if ahead then
|
||||
ahead.behind = nil
|
||||
cart.ahead = nil
|
||||
save_cart_data(ahead.uuid)
|
||||
end
|
||||
end
|
||||
if cart.behind then
|
||||
local behind = get_cart_data(cart.behind)
|
||||
if behind then
|
||||
behind.ahead = nil
|
||||
cart.behind = nil
|
||||
save_cart_data(behind.uuid)
|
||||
end
|
||||
end
|
||||
save_cart_data(cart.uuid)
|
||||
end
|
||||
mod.break_train_at = break_train_at
|
||||
|
||||
function mod.update_train(staticdata)
|
||||
--local staticdata = cart._staticdata
|
||||
|
||||
-- Only update from the back
|
||||
if staticdata.behind or not staticdata.ahead then return end
|
||||
--print("\nUpdating train")
|
||||
|
||||
-- Do no special processing if the cart is not part of a train
|
||||
if not staticdata.ahead and not staticdata.behind then return end
|
||||
|
||||
-- Calculate the maximum velocity of all train cars
|
||||
local velocity = 0
|
||||
local count = 0
|
||||
for cart in train_cars(staticdata) do
|
||||
velocity = velocity + (cart.velocity or 0)
|
||||
count = count + 1
|
||||
end
|
||||
velocity = velocity / count
|
||||
--print("Using velocity "..tostring(velocity))
|
||||
|
||||
-- Set the entire train to the average velocity
|
||||
local behind = nil
|
||||
for c in train_cars(staticdata) do
|
||||
local e = 0
|
||||
local separation
|
||||
local cart_velocity = velocity
|
||||
if not c.connected_at then
|
||||
break_train_at(c)
|
||||
elseif behind then
|
||||
separation = distance_between_cars(behind, c)
|
||||
local e = 0
|
||||
if not separation then
|
||||
break_train_at(c)
|
||||
elseif separation > 1.6 then
|
||||
cart_velocity = velocity * 0.9
|
||||
elseif separation > 2.5 then
|
||||
break_train_at(c)
|
||||
elseif separation < 1.15 then
|
||||
cart_velocity = velocity * 1.1
|
||||
end
|
||||
end
|
||||
--[[
|
||||
print(tostring(c.behind).."->"..c.uuid.."->"..tostring(c.ahead).."("..tostring(separation)..") setting cart #"..
|
||||
c.uuid.." velocity from "..tostring(c.velocity).." to "..tostring(cart_velocity))
|
||||
]]
|
||||
c.velocity = cart_velocity
|
||||
|
||||
behind = c
|
||||
end
|
||||
end
|
||||
|
||||
function mod.link_cart_ahead(staticdata, ca_staticdata)
|
||||
minetest.log("action","Linking cart #"..staticdata.uuid.." to cart #"..ca_staticdata.uuid)
|
||||
|
||||
staticdata.ahead = ca_staticdata.uuid
|
||||
ca_staticdata.behind = staticdata.uuid
|
||||
end
|
||||
|
||||
function mod.reverse_train(cart)
|
||||
for c in train_cars(cart) do
|
||||
mod.reverse_cart_direction(c)
|
||||
c.behind,c.ahead = c.ahead,c.behind
|
||||
end
|
||||
end
|
||||
|
|
@ -386,7 +386,7 @@ local tab_icon = {
|
|||
blocks = "mcl_core:brick_block",
|
||||
deco = "mcl_flowers:peony",
|
||||
redstone = "mesecons:redstone",
|
||||
rail = "mcl_minecarts:golden_rail",
|
||||
rail = "mcl_minecarts:golden_rail_v2",
|
||||
misc = "mcl_buckets:bucket_lava",
|
||||
nix = "mcl_compass:compass",
|
||||
food = "mcl_core:apple",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
mcl_comparators = {}
|
||||
local mod = mcl_comparators
|
||||
|
||||
-- Functions that get the input/output rules of the comparator
|
||||
|
||||
|
@ -10,7 +12,6 @@ local function comparator_get_output_rules(node)
|
|||
return rules
|
||||
end
|
||||
|
||||
|
||||
local function comparator_get_input_rules(node)
|
||||
local rules = {
|
||||
-- we rely on this order in update_self below
|
||||
|
@ -24,117 +25,105 @@ local function comparator_get_input_rules(node)
|
|||
return rules
|
||||
end
|
||||
|
||||
|
||||
-- Functions that are called after the delay time
|
||||
|
||||
local function comparator_turnon(params)
|
||||
local rules = comparator_get_output_rules(params.node)
|
||||
mesecon.receptor_on(params.pos, rules)
|
||||
end
|
||||
|
||||
|
||||
local function comparator_turnoff(params)
|
||||
local rules = comparator_get_output_rules(params.node)
|
||||
mesecon.receptor_off(params.pos, rules)
|
||||
end
|
||||
|
||||
|
||||
-- Functions that set the correct node type an schedule a turnon/off
|
||||
|
||||
local function comparator_activate(pos, node)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
local onstate = def.comparator_onstate
|
||||
if onstate then
|
||||
minetest.swap_node(pos, { name = onstate, param2 = node.param2 })
|
||||
end
|
||||
minetest.after(0.1, comparator_turnon , {pos = pos, node = node})
|
||||
end
|
||||
|
||||
|
||||
local function comparator_deactivate(pos, node)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
local offstate = def.comparator_offstate
|
||||
if offstate then
|
||||
minetest.swap_node(pos, { name = offstate, param2 = node.param2 })
|
||||
end
|
||||
minetest.after(0.1, comparator_turnoff, {pos = pos, node = node})
|
||||
end
|
||||
|
||||
|
||||
-- weather pos has an inventory that contains at least one item
|
||||
local function container_inventory_nonempty(pos)
|
||||
local invnode = minetest.get_node(pos)
|
||||
local invnodedef = minetest.registered_nodes[invnode.name]
|
||||
-- Ignore stale nodes
|
||||
if not invnodedef then return false end
|
||||
|
||||
-- Only accept containers. When a container is dug, it's inventory
|
||||
-- seems to stay. and we don't want to accept the inventory of an air
|
||||
-- block
|
||||
if not invnodedef.groups.container then return false end
|
||||
|
||||
local inv = minetest.get_inventory({type="node", pos=pos})
|
||||
if not inv then return false end
|
||||
|
||||
for listname, _ in pairs(inv:get_lists()) do
|
||||
if not inv:is_empty(listname) then return true end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- weather pos has an constant signal output for the comparator
|
||||
local function static_signal_output(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local g = minetest.get_item_group(node.name, "comparator_signal")
|
||||
return g > 0
|
||||
end
|
||||
|
||||
-- whether the comparator should be on according to its inputs
|
||||
local function comparator_desired_on(pos, node)
|
||||
local my_input_rules = comparator_get_input_rules(node);
|
||||
local back_rule = my_input_rules[1]
|
||||
local state
|
||||
if back_rule then
|
||||
local back_pos = vector.add(pos, back_rule)
|
||||
state = mesecon.is_power_on(back_pos) or container_inventory_nonempty(back_pos) or static_signal_output(back_pos)
|
||||
end
|
||||
|
||||
-- if back input if off, we don't need to check side inputs
|
||||
if not state then return false end
|
||||
|
||||
-- without power levels, side inputs have no influence on output in compare
|
||||
-- mode
|
||||
local mode = minetest.registered_nodes[node.name].comparator_mode
|
||||
if mode == "comp" then return state end
|
||||
|
||||
-- subtract mode, subtract max(side_inputs) from back input
|
||||
local side_state = false
|
||||
for ri = 2,3 do
|
||||
if my_input_rules[ri] then
|
||||
side_state = mesecon.is_power_on(vector.add(pos, my_input_rules[ri]))
|
||||
end
|
||||
if side_state then break end
|
||||
end
|
||||
-- state is known to be true
|
||||
return not side_state
|
||||
end
|
||||
|
||||
|
||||
local POSSIBLE_COMPARATOR_POSITIONS = {
|
||||
vector.new( 1,0, 0),
|
||||
vector.new(-1,0, 0),
|
||||
vector.new( 0,0, 1),
|
||||
vector.new( 0,0,-1),
|
||||
}
|
||||
-- update comparator state, if needed
|
||||
local function update_self(pos, node)
|
||||
print("Updating comparator at "..vector.to_string(pos))
|
||||
node = node or minetest.get_node(pos)
|
||||
local old_state = mesecon.is_receptor_on(node.name)
|
||||
local new_state = comparator_desired_on(pos, node)
|
||||
if new_state ~= old_state then
|
||||
if new_state then
|
||||
comparator_activate(pos, node)
|
||||
else
|
||||
comparator_deactivate(pos, node)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
|
||||
-- Find the node we are pointing at
|
||||
local input_rules = comparator_get_input_rules(node);
|
||||
local back_rule = input_rules[1]
|
||||
local back_pos = vector.add(pos, back_rule)
|
||||
local back_node = minetest.get_node(back_pos)
|
||||
local back_nodedef = minetest.registered_nodes[back_node.name]
|
||||
|
||||
-- Get the comparator mode
|
||||
local mode = nodedef.comparator_mode
|
||||
|
||||
-- Get a comparator reading from the block at the back of the comparator
|
||||
local power_level = 0
|
||||
if back_nodedef and back_nodedef._mcl_comparators_get_reading then
|
||||
power_level = back_nodedef._mcl_comparators_get_reading(back_pos)
|
||||
else
|
||||
power_level = vl_redstone.get_power(back_pos)
|
||||
end
|
||||
|
||||
-- Get the maximum side power level
|
||||
local side_power_level = 0
|
||||
for i=2,3 do
|
||||
local pl = vl_redstone.get_power(vector.add(pos,input_rules[i]))
|
||||
if pl > side_power_level then
|
||||
side_power_level = pl
|
||||
end
|
||||
end
|
||||
|
||||
-- Apply subtraction or comparison
|
||||
if mode == "sub" then
|
||||
power_level = power_level - side_power_level
|
||||
if power_level < 0 then power_level = 0 end
|
||||
elseif mode == "comp" then
|
||||
if side_power_level > power_level then
|
||||
power_level = 0
|
||||
end
|
||||
end
|
||||
|
||||
-- Update output power level
|
||||
vl_redstone.set_power(pos, power_level)
|
||||
|
||||
-- Update node
|
||||
if power_level ~= 0 then
|
||||
node.name = nodedef.comparator_onstate
|
||||
minetest.swap_node(pos, node)
|
||||
else
|
||||
node.name = nodedef.comparator_offstate
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
end
|
||||
|
||||
local node_readings = {}
|
||||
function mod.trigger_update(pos, node)
|
||||
local node = node or minetest.get_node(pos)
|
||||
local nodedef = minetest.registered_nodes[node.name] or {}
|
||||
|
||||
-- If this position can't provide a comparator reading, we should
|
||||
-- never trigger a comparator update
|
||||
local get_reading = nodedef._mcl_comparators_get_reading
|
||||
if not get_reading then return end
|
||||
|
||||
-- Only update if the reading has changed
|
||||
local pos_hash = minetest.hash_node_position(pos)
|
||||
local old_reading = node_readings[pos_hash]
|
||||
local new_reading = get_reading(pos)
|
||||
if old_reading == new_reading then return end
|
||||
node_readings[pos_hash] = new_reading
|
||||
|
||||
-- try to find a comparator with the back rule leading to pos
|
||||
for i = 1,#POSSIBLE_COMPARATOR_POSITIONS do
|
||||
local candidate = vector.add(pos, POSSIBLE_COMPARATOR_POSITIONS[i])
|
||||
local node = minetest.get_node(candidate)
|
||||
if minetest.get_item_group(node.name,"mcl_comparator") ~= 0 then
|
||||
update_self(candidate, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mod.read_inventory(inv, inv_name)
|
||||
local stacks = inv:get_list(inv_name)
|
||||
if not stacks then return 0 end
|
||||
|
||||
local count = 0
|
||||
for i=1,#stacks do
|
||||
count = count + ( stacks[i]:get_count() / stacks[i]:get_stack_max() )
|
||||
end
|
||||
return math.floor(count * 16 / #stacks)
|
||||
end
|
||||
|
||||
-- compute tile depending on state and mode
|
||||
local function get_tiles(state, mode)
|
||||
|
@ -215,6 +204,7 @@ local groups = {
|
|||
destroy_by_lava_flow = 1,
|
||||
dig_by_piston = 1,
|
||||
attached_node = 1,
|
||||
mcl_comparator = 1,
|
||||
}
|
||||
|
||||
local on_rotate
|
||||
|
@ -309,7 +299,7 @@ for _, mode in pairs{"comp", "sub"} do
|
|||
end
|
||||
|
||||
minetest.register_node(nodename, nodedef)
|
||||
mcl_wip.register_wip_item(nodename)
|
||||
--mcl_wip.register_wip_item(nodename)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -327,6 +317,7 @@ minetest.register_craft({
|
|||
}
|
||||
})
|
||||
|
||||
--[[
|
||||
-- Register active block handlers
|
||||
minetest.register_abm({
|
||||
label = "Comparator signal input check (comparator is off)",
|
||||
|
@ -352,7 +343,7 @@ minetest.register_abm({
|
|||
chance = 1,
|
||||
action = update_self,
|
||||
})
|
||||
|
||||
]]
|
||||
|
||||
-- Add entry aliases for the Help
|
||||
if minetest.get_modpath("doc") then
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
name = mcl_comparators
|
||||
depends = mcl_wip, mesecons, mcl_sounds
|
||||
depends = mesecons, mcl_sounds, vl_redstone
|
||||
optional_depends = doc, screwdriver
|
||||
|
|
|
@ -129,9 +129,9 @@ local dropperdef = {
|
|||
-- If they are containers - double down as hopper
|
||||
mcl_util.hopper_push(pos, droppos)
|
||||
end
|
||||
if dropnodedef.walkable then
|
||||
return
|
||||
end
|
||||
if dropnodedef.walkable then return end
|
||||
|
||||
-- Build a list of items in the dropper
|
||||
local stacks = {}
|
||||
for i = 1, inv:get_size("main") do
|
||||
local stack = inv:get_stack("main", i)
|
||||
|
@ -139,12 +139,32 @@ local dropperdef = {
|
|||
table.insert(stacks, { stack = stack, stackpos = i })
|
||||
end
|
||||
end
|
||||
|
||||
-- Pick an item to drop
|
||||
local dropitem = nil
|
||||
local stack = nil
|
||||
local r = nil
|
||||
if #stacks >= 1 then
|
||||
local r = math.random(1, #stacks)
|
||||
local stack = stacks[r].stack
|
||||
local dropitem = ItemStack(stack)
|
||||
r = math.random(1, #stacks)
|
||||
stack = stacks[r].stack
|
||||
dropitem = ItemStack(stack)
|
||||
dropitem:set_count(1)
|
||||
local stack_id = stacks[r].stackpos
|
||||
end
|
||||
if not dropitem then return end
|
||||
|
||||
-- Flag for if the item was dropped. If true the item will be removed from
|
||||
-- the inventory after dropping
|
||||
local item_dropped = false
|
||||
|
||||
-- Check if the drop item has a custom handler
|
||||
local itemdef = minetest.registered_craftitems[dropitem:get_name()]
|
||||
if itemdef._mcl_dropper_on_drop then
|
||||
item_dropped = itemdef._mcl_dropper_on_drop(dropitem, droppos)
|
||||
end
|
||||
|
||||
-- If a custom handler wasn't successful then drop the item as an entity
|
||||
if not item_dropped then
|
||||
-- Drop as entity
|
||||
local pos_variation = 100
|
||||
droppos = vector.offset(droppos,
|
||||
math.random(-pos_variation, pos_variation) / 1000,
|
||||
|
@ -156,6 +176,12 @@ local dropperdef = {
|
|||
local speed = 3
|
||||
item_entity:set_velocity(vector.multiply(drop_vel, speed))
|
||||
stack:take_item()
|
||||
item_dropped = trie
|
||||
end
|
||||
|
||||
-- Remove dropped items from inventory
|
||||
if item_dropped then
|
||||
local stack_id = stacks[r].stackpos
|
||||
inv:set_stack("main", stack_id, stack)
|
||||
end
|
||||
end,
|
||||
|
|
|
@ -58,9 +58,11 @@ local function get_highest_priority(actions)
|
|||
return highesti
|
||||
end
|
||||
|
||||
--[[
|
||||
local m_time = 0
|
||||
local resumetime = mesecon.setting("resumetime", 4)
|
||||
minetest.register_globalstep(function (dtime)
|
||||
|
||||
m_time = m_time + dtime
|
||||
-- don't even try if server has not been running for XY seconds; resumetime = time to wait
|
||||
-- after starting the server before processing the ActionQueue, don't set this too low
|
||||
|
@ -87,6 +89,7 @@ minetest.register_globalstep(function (dtime)
|
|||
table.remove(actions_now, hp)
|
||||
end
|
||||
end)
|
||||
]]
|
||||
|
||||
function mesecon.queue:execute(action)
|
||||
if not action.pos then return end
|
||||
|
|
|
@ -87,7 +87,9 @@ mesecon.queue:add_function("receptor_on", function (pos, rules)
|
|||
end)
|
||||
|
||||
function mesecon.receptor_on(pos, rules)
|
||||
mesecon.queue:add_action(pos, "receptor_on", {rules}, nil, rules)
|
||||
print("receptor_on(pos="..vector.to_string(pos)..",rules="..dump(rules))
|
||||
print(debug.traceback())
|
||||
--mesecon.queue:add_action(pos, "receptor_on", {rules}, nil, rules)
|
||||
end
|
||||
|
||||
mesecon.queue:add_function("receptor_off", function (pos, rules)
|
||||
|
@ -114,7 +116,9 @@ mesecon.queue:add_function("receptor_off", function (pos, rules)
|
|||
end)
|
||||
|
||||
function mesecon.receptor_off(pos, rules)
|
||||
mesecon.queue:add_action(pos, "receptor_off", {rules}, nil, rules)
|
||||
print("receptor_off(pos="..vector.to_string(pos)..",rules="..dump(rules))
|
||||
print(debug.traceback())
|
||||
--mesecon.queue:add_action(pos, "receptor_off", {rules}, nil, rules)
|
||||
end
|
||||
|
||||
--Services like turnoff receptor on dignode and so on
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
function mesecon.on_placenode(pos, node)
|
||||
mesecon.execute_autoconnect_hooks_now(pos, node)
|
||||
--[[
|
||||
|
||||
-- Receptors: Send on signal when active
|
||||
if mesecon.is_receptor_on(node.name) then
|
||||
|
@ -68,6 +69,7 @@ function mesecon.on_placenode(pos, node)
|
|||
end
|
||||
end
|
||||
end
|
||||
]]
|
||||
end
|
||||
|
||||
function mesecon.on_dignode(pos, node)
|
||||
|
@ -103,7 +105,7 @@ function mesecon.on_blastnode(pos, node)
|
|||
end
|
||||
|
||||
minetest.register_on_placenode(mesecon.on_placenode)
|
||||
minetest.register_on_dignode(mesecon.on_dignode)
|
||||
--minetest.register_on_dignode(mesecon.on_dignode)
|
||||
|
||||
-- Overheating service for fast circuits
|
||||
local OVERHEAT_MAX = mesecon.setting("overheat_max", 8)
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
mesecon = mesecon or {}
|
||||
local mod = {}
|
||||
mesecon.commandblock = mod
|
||||
|
||||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
local F = minetest.formspec_escape
|
||||
local color_red = mcl_colors.RED
|
||||
|
||||
mod.initialize = function(meta)
|
||||
meta:set_string("commands", "")
|
||||
meta:set_string("commander", "")
|
||||
end
|
||||
|
||||
mod.place = function(meta, placer)
|
||||
if not placer then return end
|
||||
|
||||
meta:set_string("commander", placer:get_player_name())
|
||||
end
|
||||
|
||||
mod.resolve_commands = function(commands, meta, pos)
|
||||
local players = minetest.get_connected_players()
|
||||
local commander = meta:get_string("commander")
|
||||
|
||||
-- A non-printable character used while replacing “@@”.
|
||||
local SUBSTITUTE_CHARACTER = "\26" -- ASCII SUB
|
||||
|
||||
-- No players online: remove all commands containing
|
||||
-- problematic placeholders.
|
||||
if #players == 0 then
|
||||
commands = commands:gsub("[^\r\n]+", function (line)
|
||||
line = line:gsub("@@", SUBSTITUTE_CHARACTER)
|
||||
if line:find("@n") then return "" end
|
||||
if line:find("@p") then return "" end
|
||||
if line:find("@f") then return "" end
|
||||
if line:find("@r") then return "" end
|
||||
line = line:gsub("@c", commander)
|
||||
line = line:gsub(SUBSTITUTE_CHARACTER, "@")
|
||||
return line
|
||||
end)
|
||||
return commands
|
||||
end
|
||||
|
||||
local nearest, farthest = nil, nil
|
||||
local min_distance, max_distance = math.huge, -1
|
||||
for index, player in pairs(players) do
|
||||
local distance = vector.distance(pos, player:get_pos())
|
||||
if distance < min_distance then
|
||||
min_distance = distance
|
||||
nearest = player:get_player_name()
|
||||
end
|
||||
if distance > max_distance then
|
||||
max_distance = distance
|
||||
farthest = player:get_player_name()
|
||||
end
|
||||
end
|
||||
local random = players[math.random(#players)]:get_player_name()
|
||||
commands = commands:gsub("@@", SUBSTITUTE_CHARACTER)
|
||||
commands = commands:gsub("@p", nearest)
|
||||
commands = commands:gsub("@n", nearest)
|
||||
commands = commands:gsub("@f", farthest)
|
||||
commands = commands:gsub("@r", random)
|
||||
commands = commands:gsub("@c", commander)
|
||||
commands = commands:gsub(SUBSTITUTE_CHARACTER, "@")
|
||||
return commands
|
||||
end
|
||||
local resolve_commands = mod.resolve_commands
|
||||
|
||||
mod.check_commands = function(commands, player_name)
|
||||
for _, command in pairs(commands:split("\n")) do
|
||||
local pos = command:find(" ")
|
||||
local cmd = command
|
||||
if pos then
|
||||
cmd = command:sub(1, pos - 1)
|
||||
end
|
||||
local cmddef = minetest.chatcommands[cmd]
|
||||
if not cmddef then
|
||||
-- Invalid chat command
|
||||
local msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands.", cmd)
|
||||
if string.sub(cmd, 1, 1) == "/" then
|
||||
msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands. Hint: Try to remove the leading slash.", cmd)
|
||||
end
|
||||
return false, minetest.colorize(color_red, msg)
|
||||
end
|
||||
if player_name then
|
||||
local player_privs = minetest.get_player_privs(player_name)
|
||||
|
||||
for cmd_priv, _ in pairs(cmddef.privs) do
|
||||
if player_privs[cmd_priv] ~= true then
|
||||
local msg = S("Error: You have insufficient privileges to use the command “@1” (missing privilege: @2)! The command block has not been changed.", cmd, cmd_priv)
|
||||
return false, minetest.colorize(color_red, msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
local check_commands = mod.check_commands
|
||||
|
||||
mod.action_on = function(meta, pos)
|
||||
local commander = meta:get_string("commander")
|
||||
local commands = resolve_commands(meta:get_string("commands"), meta, pos)
|
||||
for _, command in pairs(commands:split("\n")) do
|
||||
local cpos = command:find(" ")
|
||||
local cmd, param = command, ""
|
||||
if cpos then
|
||||
cmd = command:sub(1, cpos - 1)
|
||||
param = command:sub(cpos + 1)
|
||||
end
|
||||
local cmddef = minetest.chatcommands[cmd]
|
||||
if not cmddef then
|
||||
-- Invalid chat command
|
||||
return
|
||||
end
|
||||
-- Execute command in the name of commander
|
||||
cmddef.func(commander, param)
|
||||
end
|
||||
end
|
||||
|
||||
local formspec_metas = {}
|
||||
|
||||
mod.handle_rightclick = function(meta, player)
|
||||
local can_edit = true
|
||||
-- Only allow write access in Creative Mode
|
||||
if not minetest.is_creative_enabled(player:get_player_name()) then
|
||||
can_edit = false
|
||||
end
|
||||
local pname = player:get_player_name()
|
||||
if minetest.is_protected(pos, pname) then
|
||||
can_edit = false
|
||||
end
|
||||
local privs = minetest.get_player_privs(pname)
|
||||
if not privs.maphack then
|
||||
can_edit = false
|
||||
end
|
||||
|
||||
local commands = meta:get_string("commands")
|
||||
if not commands then
|
||||
commands = ""
|
||||
end
|
||||
local commander = meta:get_string("commander")
|
||||
local commanderstr
|
||||
if commander == "" or commander == nil then
|
||||
commanderstr = S("Error: No commander! Block must be replaced.")
|
||||
else
|
||||
commanderstr = S("Commander: @1", commander)
|
||||
end
|
||||
local textarea_name, submit, textarea
|
||||
-- If editing is not allowed, only allow read-only access.
|
||||
-- Player can still view the contents of the command block.
|
||||
if can_edit then
|
||||
textarea_name = "commands"
|
||||
submit = "button_exit[3.3,4.4;2,1;submit;"..F(S("Submit")).."]"
|
||||
else
|
||||
textarea_name = ""
|
||||
submit = ""
|
||||
end
|
||||
if not can_edit and commands == "" then
|
||||
textarea = "label[0.5,0.5;"..F(S("No commands.")).."]"
|
||||
else
|
||||
textarea = "textarea[0.5,0.5;8.5,4;"..textarea_name..";"..F(S("Commands:"))..";"..F(commands).."]"
|
||||
end
|
||||
local formspec = "size[9,5;]" ..
|
||||
textarea ..
|
||||
submit ..
|
||||
"image_button[8,4.4;1,1;doc_button_icon_lores.png;doc;]" ..
|
||||
"tooltip[doc;"..F(S("Help")).."]" ..
|
||||
"label[0,4;"..F(commanderstr).."]"
|
||||
|
||||
-- Store the metadata object for later use
|
||||
local fs_id = #formspec_metas + 1
|
||||
formspec_metas[fs_id] = meta
|
||||
print("using fs_id="..tostring(fs_id)..",meta="..tostring(meta)..",formspec_metas[fs_id]="..tostring(formspec_metas[fs_id]))
|
||||
|
||||
minetest.show_formspec(pname, "commandblock_"..tostring(fs_id), formspec)
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if string.sub(formname, 1, 13) == "commandblock_" then
|
||||
-- Show documentation
|
||||
if fields.doc and minetest.get_modpath("doc") then
|
||||
doc.show_entry(player:get_player_name(), "nodes", "mesecons_commandblock:commandblock_off", true)
|
||||
return
|
||||
end
|
||||
|
||||
-- Validate form fields
|
||||
if (not fields.submit and not fields.key_enter) or (not fields.commands) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Check privileges
|
||||
local privs = minetest.get_player_privs(player:get_player_name())
|
||||
if not privs.maphack then
|
||||
minetest.chat_send_player(player:get_player_name(), S("Access denied. You need the “maphack” privilege to edit command blocks."))
|
||||
return
|
||||
end
|
||||
|
||||
-- Check game mode
|
||||
if not minetest.is_creative_enabled(player:get_player_name()) then
|
||||
minetest.chat_send_player(player:get_player_name(),
|
||||
S("Editing the command block has failed! You can only change the command block in Creative Mode!")
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
-- Retrieve the metadata object this formspec data belongs to
|
||||
local index, _, fs_id = string.find(formname, "commandblock_(-?%d+)")
|
||||
fs_id = tonumber(fs_id)
|
||||
if not index or not fs_id or not formspec_metas[fs_id] then
|
||||
print("index="..tostring(index)..", fs_id="..tostring(fs_id).."formspec_metas[fs_id]="..tostring(formspec_metas[fs_id]))
|
||||
minetest.chat_send_player(player:get_player_name(), S("Editing the command block has failed! The command block is gone."))
|
||||
return
|
||||
end
|
||||
local meta = formspec_metas[fs_id]
|
||||
formspec_metas[fs_id] = nil
|
||||
|
||||
-- Verify the command
|
||||
local check, error_message = check_commands(fields.commands, player:get_player_name())
|
||||
if check == false then
|
||||
-- Command block rejected
|
||||
minetest.chat_send_player(player:get_player_name(), error_message)
|
||||
return
|
||||
end
|
||||
|
||||
-- Update the command in the metadata
|
||||
meta:set_string("commands", fields.commands)
|
||||
end
|
||||
end)
|
|
@ -1,104 +1,27 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
local F = minetest.formspec_escape
|
||||
|
||||
local tonumber = tonumber
|
||||
|
||||
local color_red = mcl_colors.RED
|
||||
-- Initialize API
|
||||
dofile(modpath.."/api.lua")
|
||||
local api = mesecon.commandblock
|
||||
|
||||
local command_blocks_activated = minetest.settings:get_bool("mcl_enable_commandblocks", true)
|
||||
local msg_not_activated = S("Command blocks are not enabled on this server")
|
||||
|
||||
local function construct(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
|
||||
meta:set_string("commands", "")
|
||||
meta:set_string("commander", "")
|
||||
api.initialize(meta)
|
||||
end
|
||||
|
||||
local function after_place(pos, placer)
|
||||
if placer then
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("commander", placer:get_player_name())
|
||||
end
|
||||
local meta = minetest.get_meta(pos)
|
||||
api.place(meta, placer)
|
||||
end
|
||||
|
||||
local function resolve_commands(commands, pos)
|
||||
local players = minetest.get_connected_players()
|
||||
|
||||
local meta = minetest.get_meta(pos)
|
||||
local commander = meta:get_string("commander")
|
||||
|
||||
-- A non-printable character used while replacing “@@”.
|
||||
local SUBSTITUTE_CHARACTER = "\26" -- ASCII SUB
|
||||
|
||||
-- No players online: remove all commands containing
|
||||
-- problematic placeholders.
|
||||
if #players == 0 then
|
||||
commands = commands:gsub("[^\r\n]+", function (line)
|
||||
line = line:gsub("@@", SUBSTITUTE_CHARACTER)
|
||||
if line:find("@n") then return "" end
|
||||
if line:find("@p") then return "" end
|
||||
if line:find("@f") then return "" end
|
||||
if line:find("@r") then return "" end
|
||||
line = line:gsub("@c", commander)
|
||||
line = line:gsub(SUBSTITUTE_CHARACTER, "@")
|
||||
return line
|
||||
end)
|
||||
return commands
|
||||
end
|
||||
|
||||
local nearest, farthest = nil, nil
|
||||
local min_distance, max_distance = math.huge, -1
|
||||
for index, player in pairs(players) do
|
||||
local distance = vector.distance(pos, player:get_pos())
|
||||
if distance < min_distance then
|
||||
min_distance = distance
|
||||
nearest = player:get_player_name()
|
||||
end
|
||||
if distance > max_distance then
|
||||
max_distance = distance
|
||||
farthest = player:get_player_name()
|
||||
end
|
||||
end
|
||||
local random = players[math.random(#players)]:get_player_name()
|
||||
commands = commands:gsub("@@", SUBSTITUTE_CHARACTER)
|
||||
commands = commands:gsub("@p", nearest)
|
||||
commands = commands:gsub("@n", nearest)
|
||||
commands = commands:gsub("@f", farthest)
|
||||
commands = commands:gsub("@r", random)
|
||||
commands = commands:gsub("@c", commander)
|
||||
commands = commands:gsub(SUBSTITUTE_CHARACTER, "@")
|
||||
return commands
|
||||
end
|
||||
|
||||
local function check_commands(commands, player_name)
|
||||
for _, command in pairs(commands:split("\n")) do
|
||||
local pos = command:find(" ")
|
||||
local cmd = command
|
||||
if pos then
|
||||
cmd = command:sub(1, pos - 1)
|
||||
end
|
||||
local cmddef = minetest.chatcommands[cmd]
|
||||
if not cmddef then
|
||||
-- Invalid chat command
|
||||
local msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands.", cmd)
|
||||
if string.sub(cmd, 1, 1) == "/" then
|
||||
msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands. Hint: Try to remove the leading slash.", cmd)
|
||||
end
|
||||
return false, minetest.colorize(color_red, msg)
|
||||
end
|
||||
if player_name then
|
||||
local player_privs = minetest.get_player_privs(player_name)
|
||||
|
||||
for cmd_priv, _ in pairs(cmddef.privs) do
|
||||
if player_privs[cmd_priv] ~= true then
|
||||
local msg = S("Error: You have insufficient privileges to use the command “@1” (missing privilege: @2)! The command block has not been changed.", cmd, cmd_priv)
|
||||
return false, minetest.colorize(color_red, msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
return api.resolve_commands(commands, meta)
|
||||
end
|
||||
|
||||
local function commandblock_action_on(pos, node)
|
||||
|
@ -107,7 +30,6 @@ local function commandblock_action_on(pos, node)
|
|||
end
|
||||
|
||||
local meta = minetest.get_meta(pos)
|
||||
local commander = meta:get_string("commander")
|
||||
|
||||
if not command_blocks_activated then
|
||||
--minetest.chat_send_player(commander, msg_not_activated)
|
||||
|
@ -115,22 +37,7 @@ local function commandblock_action_on(pos, node)
|
|||
end
|
||||
minetest.swap_node(pos, {name = "mesecons_commandblock:commandblock_on"})
|
||||
|
||||
local commands = resolve_commands(meta:get_string("commands"), pos)
|
||||
for _, command in pairs(commands:split("\n")) do
|
||||
local cpos = command:find(" ")
|
||||
local cmd, param = command, ""
|
||||
if cpos then
|
||||
cmd = command:sub(1, cpos - 1)
|
||||
param = command:sub(cpos + 1)
|
||||
end
|
||||
local cmddef = minetest.chatcommands[cmd]
|
||||
if not cmddef then
|
||||
-- Invalid chat command
|
||||
return
|
||||
end
|
||||
-- Execute command in the name of commander
|
||||
cmddef.func(commander, param)
|
||||
end
|
||||
api.action_on(meta, pos)
|
||||
end
|
||||
|
||||
local function commandblock_action_off(pos, node)
|
||||
|
@ -144,54 +51,10 @@ local function on_rightclick(pos, node, player, itemstack, pointed_thing)
|
|||
minetest.chat_send_player(player:get_player_name(), msg_not_activated)
|
||||
return
|
||||
end
|
||||
local can_edit = true
|
||||
-- Only allow write access in Creative Mode
|
||||
if not minetest.is_creative_enabled(player:get_player_name()) then
|
||||
can_edit = false
|
||||
end
|
||||
local pname = player:get_player_name()
|
||||
if minetest.is_protected(pos, pname) then
|
||||
can_edit = false
|
||||
end
|
||||
local privs = minetest.get_player_privs(pname)
|
||||
if not privs.maphack then
|
||||
can_edit = false
|
||||
end
|
||||
|
||||
local meta = minetest.get_meta(pos)
|
||||
local commands = meta:get_string("commands")
|
||||
if not commands then
|
||||
commands = ""
|
||||
end
|
||||
local commander = meta:get_string("commander")
|
||||
local commanderstr
|
||||
if commander == "" or commander == nil then
|
||||
commanderstr = S("Error: No commander! Block must be replaced.")
|
||||
else
|
||||
commanderstr = S("Commander: @1", commander)
|
||||
end
|
||||
local textarea_name, submit, textarea
|
||||
-- If editing is not allowed, only allow read-only access.
|
||||
-- Player can still view the contents of the command block.
|
||||
if can_edit then
|
||||
textarea_name = "commands"
|
||||
submit = "button_exit[3.3,4.4;2,1;submit;"..F(S("Submit")).."]"
|
||||
else
|
||||
textarea_name = ""
|
||||
submit = ""
|
||||
end
|
||||
if not can_edit and commands == "" then
|
||||
textarea = "label[0.5,0.5;"..F(S("No commands.")).."]"
|
||||
else
|
||||
textarea = "textarea[0.5,0.5;8.5,4;"..textarea_name..";"..F(S("Commands:"))..";"..F(commands).."]"
|
||||
end
|
||||
local formspec = "size[9,5;]" ..
|
||||
textarea ..
|
||||
submit ..
|
||||
"image_button[8,4.4;1,1;doc_button_icon_lores.png;doc;]" ..
|
||||
"tooltip[doc;"..F(S("Help")).."]" ..
|
||||
"label[0,4;"..F(commanderstr).."]"
|
||||
minetest.show_formspec(pname, "commandblock_"..pos.x.."_"..pos.y.."_"..pos.z, formspec)
|
||||
api.handle_rightclick(meta, player)
|
||||
|
||||
end
|
||||
|
||||
local function on_place(itemstack, placer, pointed_thing)
|
||||
|
@ -200,12 +63,10 @@ local function on_place(itemstack, placer, pointed_thing)
|
|||
end
|
||||
|
||||
-- Use pointed node's on_rightclick function first, if present
|
||||
local new_stack = mcl_util.call_on_rightclick(itemstack, placer, pointed_thing)
|
||||
if new_stack then
|
||||
return new_stack
|
||||
end
|
||||
|
||||
--local node = minetest.get_node(pointed_thing.under)
|
||||
local new_stack = mcl_util.call_on_rightclick(itemstack, placer, pointed_thing)
|
||||
if new_stack then
|
||||
return new_stack
|
||||
end
|
||||
|
||||
local privs = minetest.get_player_privs(placer:get_player_name())
|
||||
if not privs.maphack then
|
||||
|
@ -280,44 +141,6 @@ minetest.register_node("mesecons_commandblock:commandblock_on", {
|
|||
_mcl_hardness = -1,
|
||||
})
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if string.sub(formname, 1, 13) == "commandblock_" then
|
||||
if fields.doc and minetest.get_modpath("doc") then
|
||||
doc.show_entry(player:get_player_name(), "nodes", "mesecons_commandblock:commandblock_off", true)
|
||||
return
|
||||
end
|
||||
if (not fields.submit and not fields.key_enter) or (not fields.commands) then
|
||||
return
|
||||
end
|
||||
|
||||
local privs = minetest.get_player_privs(player:get_player_name())
|
||||
if not privs.maphack then
|
||||
minetest.chat_send_player(player:get_player_name(), S("Access denied. You need the “maphack” privilege to edit command blocks."))
|
||||
return
|
||||
end
|
||||
|
||||
local index, _, x, y, z = string.find(formname, "commandblock_(-?%d+)_(-?%d+)_(-?%d+)")
|
||||
if index and x and y and z then
|
||||
local pos = {x = tonumber(x), y = tonumber(y), z = tonumber(z)}
|
||||
local meta = minetest.get_meta(pos)
|
||||
if not minetest.is_creative_enabled(player:get_player_name()) then
|
||||
minetest.chat_send_player(player:get_player_name(), S("Editing the command block has failed! You can only change the command block in Creative Mode!"))
|
||||
return
|
||||
end
|
||||
local check, error_message = check_commands(fields.commands, player:get_player_name())
|
||||
if check == false then
|
||||
-- Command block rejected
|
||||
minetest.chat_send_player(player:get_player_name(), error_message)
|
||||
return
|
||||
else
|
||||
meta:set_string("commands", fields.commands)
|
||||
end
|
||||
else
|
||||
minetest.chat_send_player(player:get_player_name(), S("Editing the command block has failed! The command block is gone."))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Add entry alias for the Help
|
||||
if minetest.get_modpath("doc") then
|
||||
doc.add_entry_alias("nodes", "mesecons_commandblock:commandblock_off", "nodes", "mesecons_commandblock:commandblock_on")
|
||||
|
|
|
@ -108,10 +108,10 @@ local function check_unlock_repeater(pos, node)
|
|||
end
|
||||
if mesecon.is_powered(lpos, delayer_get_input_rules(lnode)[1]) then
|
||||
minetest.set_node(lpos, {name="mesecons_delayer:delayer_on_"..ldelay, param2=lnode.param2})
|
||||
mesecon.queue:add_action(lpos, "receptor_on", {delayer_get_output_rules(lnode)}, ldef.delayer_time, nil)
|
||||
vl_redstone.set_power(lpos, 15, ldef.delayer_time)
|
||||
else
|
||||
minetest.set_node(lpos, {name="mesecons_delayer:delayer_off_"..ldelay, param2=lnode.param2})
|
||||
mesecon.queue:add_action(lpos, "receptor_off", {delayer_get_output_rules(lnode)}, ldef.delayer_time, nil)
|
||||
vl_redstone.set_power(lpos, 0, ldef.delayer_time)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
@ -123,7 +123,7 @@ local function delayer_activate(pos, node)
|
|||
local def = minetest.registered_nodes[node.name]
|
||||
local time = def.delayer_time
|
||||
minetest.set_node(pos, {name=def.delayer_onstate, param2=node.param2})
|
||||
mesecon.queue:add_action(pos, "receptor_on", {delayer_get_output_rules(node)}, time, nil)
|
||||
vl_redstone.set_power(pos, 15, time)
|
||||
check_lock_repeater(pos, node)
|
||||
end
|
||||
|
||||
|
@ -131,7 +131,7 @@ local function delayer_deactivate(pos, node)
|
|||
local def = minetest.registered_nodes[node.name]
|
||||
local time = def.delayer_time
|
||||
minetest.set_node(pos, {name=def.delayer_offstate, param2=node.param2})
|
||||
mesecon.queue:add_action(pos, "receptor_off", {delayer_get_output_rules(node)}, time, nil)
|
||||
vl_redstone.set_power(pos, 0, time)
|
||||
check_unlock_repeater(pos, node)
|
||||
end
|
||||
|
||||
|
@ -207,7 +207,7 @@ for i = 1, 4 do
|
|||
if mesecon.is_powered(pos, delayer_get_input_rules(node)[1]) ~= false then
|
||||
local newnode = {name="mesecons_delayer:delayer_on_locked", param2 = node.param2}
|
||||
minetest.set_node(pos, newnode)
|
||||
mesecon.queue:add_action(pos, "receptor_on", {delayer_get_output_rules(newnode)}, DEFAULT_DELAY, nil)
|
||||
vl_redstone.set_power(pos, 15, DEFAULT_DELAY)
|
||||
else
|
||||
minetest.set_node(pos, {name="mesecons_delayer:delayer_off_locked", param2 = node.param2})
|
||||
end
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
name = mesecons_delayer
|
||||
depends = mesecons
|
||||
depends = mesecons, vl_redstone
|
||||
optional_depends = doc, screwdriver
|
||||
|
|
|
@ -62,51 +62,47 @@ local function torch_overheated(pos)
|
|||
timer:start(TORCH_COOLOFF)
|
||||
end
|
||||
|
||||
local function torch_action_on(pos, node)
|
||||
local overheat
|
||||
if node.name == "mesecons_torch:mesecon_torch_on" then
|
||||
overheat = mesecon.do_overheat(pos)
|
||||
if overheat then
|
||||
minetest.swap_node(pos, {name="mesecons_torch:mesecon_torch_overheated", param2=node.param2})
|
||||
else
|
||||
minetest.swap_node(pos, {name="mesecons_torch:mesecon_torch_off", param2=node.param2})
|
||||
end
|
||||
mesecon.receptor_off(pos, torch_get_output_rules(node))
|
||||
elseif node.name == "mesecons_torch:mesecon_torch_on_wall" then
|
||||
overheat = mesecon.do_overheat(pos)
|
||||
if overheat then
|
||||
minetest.swap_node(pos, {name="mesecons_torch:mesecon_torch_overheated_wall", param2=node.param2})
|
||||
else
|
||||
minetest.swap_node(pos, {name="mesecons_torch:mesecon_torch_off_wall", param2=node.param2})
|
||||
end
|
||||
mesecon.receptor_off(pos, torch_get_output_rules(node))
|
||||
end
|
||||
if overheat then
|
||||
torch_overheated(pos)
|
||||
end
|
||||
end
|
||||
local TORCH_STATE_ON = {
|
||||
["mesecons_torch:mesecon_torch_off"] = "mesecons_torch:mesecon_torch_on",
|
||||
["mesecons_torch:mesecon_torch_off_wall"] = "mesecons_torch:mesecon_torch_on_wall",
|
||||
}
|
||||
local TORCH_STATE_OFF = {
|
||||
["mesecons_torch:mesecon_torch_on"] = "mesecons_torch:mesecon_torch_off",
|
||||
["mesecons_torch:mesecon_torch_on_wall"] = "mesecons_torch:mesecon_torch_off_wall",
|
||||
}
|
||||
local TORCH_STATE_OVERHEAT = {
|
||||
["mesecons_torch:mesecon_torch_on"] = "mesecons_torch:mesecon_torch_overheated",
|
||||
["mesecons_torch:mesecon_torch_on_wall"] = "mesecons_torch:mesecon_torch_overheated_wall",
|
||||
["mesecons_torch:mesecon_torch_off"] = "mesecons_torch:mesecon_torch_overheated",
|
||||
["mesecons_torch:mesecon_torch_off_wall"] = "mesecons_torch:mesecon_torch_overheated_wall",
|
||||
}
|
||||
|
||||
local function torch_action_off(pos, node)
|
||||
local overheat
|
||||
if node.name == "mesecons_torch:mesecon_torch_off" or node.name == "mesecons_torch:mesecon_torch_overheated" then
|
||||
local function torch_action_change(pos, node, rule, strength)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
--local effector_state = ((nodedef.mesecons or {}).effector or {}).state
|
||||
--local is_on = ( effector_state == mesecon.state.on )
|
||||
local overheat = false
|
||||
local original_name = node.name
|
||||
|
||||
if strength == 0 then
|
||||
-- Turn on
|
||||
node.name = TORCH_STATE_ON[node.name] or node.name
|
||||
vl_redstone.set_power(pos, 15, 0.1)
|
||||
overheat = mesecon.do_overheat(pos)
|
||||
if overheat then
|
||||
minetest.swap_node(pos, {name="mesecons_torch:mesecon_torch_overheated", param2=node.param2})
|
||||
else
|
||||
minetest.swap_node(pos, {name="mesecons_torch:mesecon_torch_on", param2=node.param2})
|
||||
mesecon.receptor_on(pos, torch_get_output_rules(node))
|
||||
end
|
||||
elseif node.name == "mesecons_torch:mesecon_torch_off_wall" or node.name == "mesecons_torch:mesecon_torch_overheated_wall" then
|
||||
overheat = mesecon.do_overheat(pos)
|
||||
if overheat then
|
||||
minetest.swap_node(pos, {name="mesecons_torch:mesecon_torch_overheated_wall", param2=node.param2})
|
||||
else
|
||||
minetest.swap_node(pos, {name="mesecons_torch:mesecon_torch_on_wall", param2=node.param2})
|
||||
mesecon.receptor_on(pos, torch_get_output_rules(node))
|
||||
end
|
||||
else
|
||||
-- Turn off
|
||||
node.name = TORCH_STATE_OFF[node.name] or node.name
|
||||
vl_redstone.set_power(pos, 0, 0.1)
|
||||
end
|
||||
|
||||
if overheat then
|
||||
print("overheat")
|
||||
torch_overheated(pos)
|
||||
node.name = TORCH_STATE_OVERHEAT[node.name] or node.name
|
||||
end
|
||||
|
||||
if node.name ~= original_name then
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -140,9 +136,9 @@ local off_override = {
|
|||
effector = {
|
||||
state = mesecon.state.on,
|
||||
rules = torch_get_input_rules,
|
||||
action_off = torch_action_off,
|
||||
action_change = torch_action_change,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
minetest.override_item("mesecons_torch:mesecon_torch_off", off_override)
|
||||
|
@ -158,7 +154,7 @@ local overheated_override = {
|
|||
on_timer = function(pos, elapsed)
|
||||
if not mesecon.is_powered(pos) then
|
||||
local node = minetest.get_node(pos)
|
||||
torch_action_off(pos, node)
|
||||
torch_action_change(pos, node, nil, 0)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
@ -183,7 +179,7 @@ mcl_torches.register_torch(on_def)
|
|||
local on_override = {
|
||||
on_destruct = function(pos, oldnode)
|
||||
local node = minetest.get_node(pos)
|
||||
torch_action_on(pos, node)
|
||||
torch_action_change(pos, node, nil, 0)
|
||||
end,
|
||||
mesecons = {
|
||||
receptor = {
|
||||
|
@ -193,7 +189,7 @@ local on_override = {
|
|||
effector = {
|
||||
state = mesecon.state.off,
|
||||
rules = torch_get_input_rules,
|
||||
action_on = torch_action_on,
|
||||
action_change = torch_action_change,
|
||||
},
|
||||
},
|
||||
_tt_help = S("Provides redstone power when it's not powered itself"),
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
name = mesecons_torch
|
||||
depends = mesecons, mcl_torches
|
||||
depends = mesecons, mcl_torches, vl_redstone
|
||||
optional_depends = doc
|
||||
|
|
|
@ -52,7 +52,7 @@ minetest.register_node("mesecons_walllever:wall_lever_off", {
|
|||
_doc_items_usagehelp = S("Use the lever to flip it on or off."),
|
||||
on_rightclick = function(pos, node)
|
||||
minetest.swap_node(pos, {name="mesecons_walllever:wall_lever_on", param2=node.param2})
|
||||
mesecon.receptor_on(pos, lever_get_output_rules(node))
|
||||
vl_redstone.set_power(pos, 16)
|
||||
minetest.sound_play("mesecons_button_push", {pos=pos, max_hear_distance=16}, true)
|
||||
end,
|
||||
node_placement_prediction = "",
|
||||
|
@ -152,7 +152,7 @@ minetest.register_node("mesecons_walllever:wall_lever_on", {
|
|||
_doc_items_create_entry = false,
|
||||
on_rightclick = function(pos, node)
|
||||
minetest.swap_node(pos, {name="mesecons_walllever:wall_lever_off", param2=node.param2})
|
||||
mesecon.receptor_off(pos, lever_get_output_rules(node))
|
||||
vl_redstone.set_power(pos, 0)
|
||||
minetest.sound_play("mesecons_button_push", {pos=pos, max_hear_distance=16, pitch=0.9}, true)
|
||||
end,
|
||||
sounds = mcl_sounds.node_sound_stone_defaults(),
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
name = mesecons_walllever
|
||||
depends = mesecons
|
||||
depends = mesecons, vl_redstone
|
||||
optional_depends = doc
|
||||
|
|
|
@ -214,6 +214,11 @@ local function register_wires()
|
|||
local dot_off = "redstone_redstone_dust_dot.png^[colorize:#FF0000:"..ratio_off
|
||||
local dot_on = "redstone_redstone_dust_dot.png^[colorize:#FF0000:"..ratio_on
|
||||
|
||||
local crossing = "(redstone_redstone_dust_dot.png^redstone_redstone_dust_line0.png^(redstone_redstone_dust_line1.png^[transformR90))"
|
||||
local straight0 = "redstone_redstone_dust_line0.png"
|
||||
local straight1 = "redstone_redstone_dust_line0.png"
|
||||
local dot = "redstone_redstone_dust_dot.png"
|
||||
|
||||
local tiles_off, tiles_on
|
||||
|
||||
local wirehelp, tt, longdesc, usagehelp, img, desc_off, desc_on
|
||||
|
@ -221,8 +226,8 @@ local function register_wires()
|
|||
-- Non-connected redstone wire
|
||||
nodebox.fixed = {-8/16, -.5, -8/16, 8/16, -.5+1/64, 8/16}
|
||||
-- “Dot” texture
|
||||
tiles_off = { dot_off, dot_off, "blank.png", "blank.png", "blank.png", "blank.png" }
|
||||
tiles_on = { dot_on, dot_on, "blank.png", "blank.png", "blank.png", "blank.png" }
|
||||
tiles_off = { dot, dot, "blank.png", "blank.png", "blank.png", "blank.png" }
|
||||
tiles_on = { dot, dot, "blank.png", "blank.png", "blank.png", "blank.png" }
|
||||
|
||||
tt = S("Transmits redstone power, powers mechanisms")
|
||||
longdesc = S("Redstone is a versatile conductive mineral which transmits redstone power. It can be placed on the ground as a trail.").."\n"..
|
||||
|
@ -237,8 +242,8 @@ S("Read the help entries on the other redstone components to learn how redstone
|
|||
else
|
||||
-- Connected redstone wire
|
||||
table.insert(nodebox, box_center)
|
||||
tiles_off = { crossing_off, crossing_off, straight0_off, straight1_off, straight0_off, straight1_off }
|
||||
tiles_on = { crossing_on, crossing_on, straight0_on, straight1_on, straight0_on, straight1_on }
|
||||
tiles_off = { crossing, crossing, straight0, straight1, straight0, straight1 }
|
||||
tiles_on = { crossing, crossing, straight0, straight1, straight0, straight1 }
|
||||
wirehelp = false
|
||||
desc_off = S("Redstone Trail (@1)", nodeid)
|
||||
desc_on = S("Powered Redstone Trail (@1)", nodeid)
|
||||
|
@ -247,6 +252,8 @@ S("Read the help entries on the other redstone components to learn how redstone
|
|||
mesecon.register_node(":mesecons:wire_"..nodeid, {
|
||||
drawtype = "nodebox",
|
||||
paramtype = "light",
|
||||
paramtype2 = "color4dir",
|
||||
palette = "redstone-palette.png",
|
||||
use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "clip" or true,
|
||||
sunlight_propagates = true,
|
||||
selection_box = selectionbox,
|
||||
|
|
|
@ -0,0 +1,435 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
vl_redstone = {}
|
||||
local mod = vl_redstone
|
||||
|
||||
-- Imports
|
||||
local mcl_util_force_get_node = mcl_util.force_get_node
|
||||
local mcl_util_call_safe = mcl_util.call_safe
|
||||
local minetest_get_item_group = minetest.get_item_group
|
||||
local minetest_get_meta = minetest.get_meta
|
||||
local minetest_hash_node_pos = minetest.hash_node_position
|
||||
local minetest_get_position_from_hash = minetest.get_position_from_hash
|
||||
local minetest_serialize = minetest.serialize
|
||||
local minetest_deserialize = minetest.deserialize
|
||||
local minetest_swap_node = minetest.swap_node
|
||||
local vector_add = vector.add
|
||||
local vector_to_string = vector.to_string
|
||||
local vector_from_string = vector.from_string
|
||||
local math_floor = math.floor
|
||||
|
||||
-- Constants
|
||||
local REDSTONE_POWER_META = modname .. ".power"
|
||||
local REDSTONE_POWER_META_SOURCE = REDSTONE_POWER_META.."."
|
||||
|
||||
local multipower_cache = {}
|
||||
|
||||
local function hash_from_direction(dir)
|
||||
return 9 * (dir.x + 1) + 3 * (dir.y + 1) + dir.z + 1
|
||||
end
|
||||
local function direction_from_hash(dir_hash)
|
||||
local x = math_floor(dir_hash / 9) - 1
|
||||
local y = math_floor((dir_hash % 9) / 3 ) - 1
|
||||
local z = dir_hash % 3 - 1
|
||||
return vector.new(x,y,z)
|
||||
end
|
||||
local HASH_REVERSES = {}
|
||||
for i=0,27 do
|
||||
local dir = direction_from_hash(i)
|
||||
local dir_rev = vector.subtract(vector.zero(), dir)
|
||||
local dir_rev_hash = hash_from_direction(dir_rev)
|
||||
HASH_REVERSES[dir_rev_hash] = i
|
||||
--print("hash["..tostring(i).."] = "..vector_to_string(direction_from_hash(i))..", rev="..tostring(dir_rev_hash))
|
||||
end
|
||||
|
||||
local DIR_HASH_ZERO = hash_from_direction(vector.zero())
|
||||
print("DIR_HASH_ZERO="..tostring(DIR_HASH_ZERO))
|
||||
|
||||
local function get_input_rules_hash(mesecons, input_rules)
|
||||
-- Skip build if already built
|
||||
local redstone = mesecons._vl_redtone or {}
|
||||
mesecons._vl_redstone = redstone
|
||||
if redstone.input_rules_hash then return redstone.input_rules_hash end
|
||||
|
||||
-- Build the rules
|
||||
local input_rules_hash = {}
|
||||
redstone.input_rules_hash = input_rules_hash
|
||||
if input_rules then
|
||||
for i=1,#input_rules do
|
||||
input_rules_hash[hash_from_direction(input_rules[i])] = true
|
||||
end
|
||||
end
|
||||
|
||||
return input_rules_hash
|
||||
end
|
||||
|
||||
local function get_node_multipower_data(pos, no_create)
|
||||
local hash = minetest_hash_node_pos(pos)
|
||||
local node_multipower = multipower_cache[hash]
|
||||
if not node_multipower then
|
||||
local meta = minetest_get_meta(pos)
|
||||
node_multipower = minetest_deserialize(meta:get_string("vl_redstone.multipower"))
|
||||
if not no_create and ( not node_multipower or node_multipower.version ~= 1 ) then
|
||||
node_multipower = {
|
||||
version = 1,
|
||||
sources={}
|
||||
}
|
||||
end
|
||||
multipower_cache[hash] = node_multipower
|
||||
end
|
||||
return node_multipower
|
||||
end
|
||||
|
||||
local function calculate_driven_strength(pos, input_rules_hash, dir)
|
||||
local dir_hash = dir and hash_from_direction(dir)
|
||||
local node_multipower = get_node_multipower_data(pos)
|
||||
local strength = 0
|
||||
local strongest_direction_hash = nil
|
||||
local sources = node_multipower.sources
|
||||
input_rules_hash = input_rules_hash or {}
|
||||
|
||||
--print("in update_node(pos="..vector_to_string(pos)..") node_multipower("..tostring(node_multipower)..")="..dump(node_multipower))
|
||||
for pos_hash,data in pairs(sources) do
|
||||
local source_strength = data[1]
|
||||
local dirs = data[2]
|
||||
--print("data="..dump(data))
|
||||
--print("\t"..vector_to_string(pos)..".source["..vector_to_string(minetest_get_position_from_hash(pos_hash)).."] = "..tostring(strength))
|
||||
|
||||
-- Filter by specified direction
|
||||
local match = false
|
||||
if not dir_hash then
|
||||
match = true
|
||||
else
|
||||
for i=1,#dirs do
|
||||
match = match or input_rules_hash[dirs[i]]
|
||||
end
|
||||
end
|
||||
|
||||
--print("match="..tostring(match)..",source_strength="..tostring(source_strength))
|
||||
|
||||
-- Update strength and track which direction the strongest power is coming from
|
||||
if match and source_strength >= strength then
|
||||
strength = source_strength
|
||||
if #dirs ~= 0 then
|
||||
strongest_direction_hash = dirs[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return strength,HASH_REVERSES[strongest_direction_hash]
|
||||
end
|
||||
|
||||
local function update_node(pos, node)
|
||||
local node = node or mcl_util_force_get_node(pos)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
|
||||
-- Only do this processing of signal sinks and conductors
|
||||
local nodedef_mesecons = nodedef.mesecons
|
||||
if not nodedef_mesecons then return end
|
||||
|
||||
--print("Running update_node(pos="..vector_to_string(pos)..", node.name="..node.name..")")
|
||||
|
||||
-- Get input rules
|
||||
local input_rules = nil
|
||||
if nodedef_mesecons.conductor then
|
||||
input_rules = nodedef_mesecons.conductor.rules
|
||||
elseif nodedef_mesecons.effector then
|
||||
input_rules = nodedef_mesecons.effector.rules
|
||||
else
|
||||
-- No input rules, can't act
|
||||
--print("Unable to find input rules for "..node.name..": mesecons="..dump(nodedef_mesecons))
|
||||
return
|
||||
end
|
||||
if type(input_rules) == "function" then
|
||||
input_rules = input_rules(node)
|
||||
end
|
||||
|
||||
-- Calculate the maximum power feeding into this node
|
||||
local input_rules_hash = get_input_rules_hash(nodedef_mesecons, input_rules)
|
||||
--print("input_rules_hash="..dump(input_rules_hash)..", input_rules="..dump(input_rules))
|
||||
local strength,dir_hash = calculate_driven_strength(pos, input_rules_hash)
|
||||
--print("strength="..tostring(strength)..",dir_hash="..tostring(dir_hash))
|
||||
|
||||
-- Don't do any processing inf the actual strength at this node has changed
|
||||
local node_multipower = get_node_multipower_data(pos)
|
||||
local last_strength = node_multipower.strength or 0
|
||||
|
||||
--[[
|
||||
print("At "..vector_to_string(pos).."("..node.name..") strength="..tostring(strength)..",last_strength="..tostring(last_strength))
|
||||
if last_strength == strength then
|
||||
print("No strength change")
|
||||
return
|
||||
end
|
||||
--]]
|
||||
|
||||
-- Determine the input rule that the strength is coming from (for mesecons compatibility; there are mods that depend on it)
|
||||
local rule = nil
|
||||
--print("input_rules="..dump(input_rules))
|
||||
--print("input_rules_hash="..dump(input_rules_hash))
|
||||
--print("dir_hash="..tostring(dir_hash))
|
||||
for i = 1,#input_rules do
|
||||
local input_rule = input_rules[i]
|
||||
local input_rule_hash = hash_from_direction(input_rule)
|
||||
if dir_hash == input_rule_hash then
|
||||
rule = input_rule
|
||||
break
|
||||
end
|
||||
end
|
||||
if not rule then
|
||||
--print("No rule found")
|
||||
return
|
||||
end
|
||||
|
||||
-- Update the state
|
||||
node_multipower.strength = strength
|
||||
|
||||
local sink = nodedef_mesecons.effector
|
||||
if sink then
|
||||
local new_node_name = nil
|
||||
--print("Updating "..node.name.." at "..vector_to_string(pos).."("..tostring(last_strength).."->"..tostring(strength)..")")
|
||||
-- Inform the node of changes
|
||||
if strength ~= 0 and last_strength == 0 then
|
||||
-- Handle activation
|
||||
local hook = sink.action_on
|
||||
if hook then
|
||||
mcl_util_call_safe(nil, hook, {pos, node, rule, strength})
|
||||
end
|
||||
if sink.onstate then
|
||||
new_node_name = sink.onstate
|
||||
end
|
||||
elseif strength == 0 and last_strength ~= 0 then
|
||||
-- Handle deactivation
|
||||
local hook = sink.action_off
|
||||
if hook then
|
||||
mcl_util_call_safe(nil, hook, {pos, node, rule, strength})
|
||||
end
|
||||
if sink.offstate then
|
||||
new_node_name = sink.offstate
|
||||
end
|
||||
end
|
||||
|
||||
-- handle signal level change notification
|
||||
local hook = sink.action_change
|
||||
if hook then
|
||||
mcl_util_call_safe(nil, hook, {pos, node, rule, strength})
|
||||
end
|
||||
if sink.strength_state then
|
||||
new_node_name = sink.strength_state[strength]
|
||||
end
|
||||
|
||||
-- Update the node
|
||||
if new_node_name and new_node_name ~= node.name then
|
||||
node.name = new_node_name
|
||||
minetest_swap_node(pos, node)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local conductor = nodedef_mesecons.conductor
|
||||
if conductor then
|
||||
-- Figure out if the node name changes based on the new state
|
||||
local new_node_name = nil
|
||||
if conductor.strength_state then
|
||||
new_node_name = conductor.strength_state[strength]
|
||||
elseif strength > 0 and conductor.onstate then
|
||||
new_node_name = conductor.onstate
|
||||
elseif strength == 0 and conductor.offstate then
|
||||
new_node_name = conductor.offstate
|
||||
end
|
||||
|
||||
-- Update the node
|
||||
if new_node_name and new_node_name ~= node.name then
|
||||
--[[
|
||||
print("Changing "..vector_to_string(pos).." from "..node.name.." to "..new_node_name..
|
||||
", strength="..tostring(strength)..",last_strength="..tostring(last_strength))
|
||||
print("node.name="..node.name..
|
||||
",conductor.onstate="..tostring(conductor.onstate)..
|
||||
",conductor.offstate="..tostring(conductor.offstate)
|
||||
)
|
||||
--]]
|
||||
node.param2 = strength * 4
|
||||
node.name = new_node_name
|
||||
minetest_swap_node(pos, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local POWERED_BLOCK_RULES = {
|
||||
vector.new( 1, 0, 0),
|
||||
vector.new(-1, 0, 0),
|
||||
vector.new( 0, 1, 0),
|
||||
vector.new( 0,-1, 0),
|
||||
vector.new( 0, 0, 1),
|
||||
vector.new( 0, 0,-1),
|
||||
}
|
||||
|
||||
local function get_positions_from_node_rules(pos, rules_type, list, powered)
|
||||
list = list or {}
|
||||
|
||||
local node = mcl_util_force_get_node(pos)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
local rules
|
||||
if nodedef.mesecons then
|
||||
-- Get mesecons rules
|
||||
if not nodedef.mesecons[rules_type] then
|
||||
minetest.log("info","Node "..node.name.." has no mesecons."..rules_type.." rules")
|
||||
return list
|
||||
end
|
||||
rules = nodedef.mesecons[rules_type].rules
|
||||
if type(rules) == "function" then rules = rules(node) end
|
||||
else
|
||||
-- The only blocks that don't support mesecon that propagate power are solid blocks that
|
||||
-- are powered by another device. Mesecons calls this 'spread'
|
||||
if not powered[minetest_hash_node_pos(pos)] then return list end
|
||||
if minetest_get_item_group(node.name,"solid") == 0 then return list end
|
||||
|
||||
rules = POWERED_BLOCK_RULES
|
||||
end
|
||||
|
||||
--print("rules="..dump(rules))
|
||||
|
||||
-- Convert to absolute positions
|
||||
for i=1,#rules do
|
||||
local rule = rules[i]
|
||||
local next_pos = vector_add(pos, rule)
|
||||
local next_pos_hash = minetest_hash_node_pos(next_pos)
|
||||
--print("\tnext: "..next_pos_str..", prev="..tostring(list[next_pos_str]))
|
||||
local dirs = list[next_pos_hash] or {}
|
||||
list[next_pos_hash] = dirs
|
||||
dirs[hash_from_direction(rule)] = true
|
||||
|
||||
-- Power solid blocks
|
||||
if rules[i].spread then
|
||||
powered[next_pos_hash] = true
|
||||
--print("powering "..vector_to_string(next_pos)
|
||||
end
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
vl_scheduler.register_function("vl_redstone:flow_power",function(task, source_pos, source_strength, distance)
|
||||
--print("Flowing lv"..tostring(source_strength).." power from "..vector_to_string(source_pos).." for "..tostring(distance).." blocks")
|
||||
local processed = {}
|
||||
local powered = {}
|
||||
local source_pos_hash = minetest_hash_node_pos(source_pos)
|
||||
processed[source_pos_hash] = true
|
||||
|
||||
-- Update the source node's redstone power
|
||||
local node_multipower = get_node_multipower_data(source_pos)
|
||||
node_multipower.strength = source_strength
|
||||
node_multipower.drive_strength = source_strength
|
||||
|
||||
-- Get rules
|
||||
local list = {}
|
||||
get_positions_from_node_rules(source_pos, "receptor", list, powered)
|
||||
--print("initial list="..dump(list))
|
||||
|
||||
for i=1,distance do
|
||||
local next_list = {}
|
||||
local strength = source_strength - (i - 1)
|
||||
if strength < 0 then strength = 0 end
|
||||
|
||||
for pos_hash,dir_list in pairs(list) do
|
||||
--print("Processing "..pos_str)
|
||||
|
||||
if not processed[pos_hash] then
|
||||
processed[pos_hash] = true
|
||||
|
||||
local pos = minetest_get_position_from_hash(pos_hash)
|
||||
|
||||
-- Update node power directly
|
||||
local node_multipower = get_node_multipower_data(pos)
|
||||
--local old_data = node_multipower.sources[source_pos_hash]
|
||||
--local old_strength = old_data and old_data[1] or 0
|
||||
--print("Changing "..vector.to_string(pos)..".source["..vector_to_string(source_pos).."] from "..tostring(old_strength).." to "..tostring(strength))
|
||||
--print("\tBefore node_multipower("..tostring(node_multipower)..")="..dump(node_multipower))
|
||||
--print("\tdir_list="..dump(dir_list))
|
||||
local dirs = {}
|
||||
for k,_ in pairs(dir_list) do
|
||||
dirs[#dirs+1] = k
|
||||
end
|
||||
node_multipower.sources[source_pos_hash] = {strength,dirs}
|
||||
--print("\tAfter node_multipower("..tostring(node_multipower)..")="..dump(node_multipower))
|
||||
|
||||
-- handle spread
|
||||
get_positions_from_node_rules(pos, "conductor", next_list, powered)
|
||||
|
||||
-- Update the position
|
||||
update_node(pos)
|
||||
end
|
||||
end
|
||||
|
||||
-- Continue onto the next set of nodes to process
|
||||
list = next_list
|
||||
end
|
||||
end)
|
||||
|
||||
function vl_redstone.set_power(pos, strength, delay, node)
|
||||
-- Get existing multipower data, but don't create the data if the strength is zero
|
||||
local no_create
|
||||
if strength == 0 then no_create = true end
|
||||
local node_multipower = get_node_multipower_data(pos, no_create)
|
||||
if not node_multipower then
|
||||
print("No node multipower, no_create="..tostring(no_create))
|
||||
return
|
||||
end
|
||||
|
||||
-- Determine how far we need to trace conductors
|
||||
local distance = node_multipower.drive_strength or 0
|
||||
|
||||
-- Don't perform an update if the power level is the same as before
|
||||
if distance == strength then
|
||||
print("Don't perform update distance="..tostring(distance)..",strength="..tostring(strength))
|
||||
return
|
||||
end
|
||||
|
||||
--print("previous="..tostring(distance)..", new="..tostring(strength))
|
||||
|
||||
-- Make the update distance the maximum of the new strength and the old strength
|
||||
if distance < strength then
|
||||
distance = strength
|
||||
end
|
||||
|
||||
-- Schedule an update
|
||||
vl_scheduler.add_task(delay or 0, "vl_redstone:flow_power", 2, {pos, strength, distance + 1, node})
|
||||
end
|
||||
|
||||
function vl_redstone.get_power(pos)
|
||||
local node_multipower = get_node_multipower_data(pos)
|
||||
return node_multipower.strength or 0
|
||||
end
|
||||
|
||||
function vl_redstone.on_placenode(pos, node)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
if not nodedef then return end
|
||||
if not nodedef.mesecons then return end
|
||||
local receptor = nodedef.mesecons.receptor
|
||||
if not receptor then return end
|
||||
|
||||
if receptor.state == mesecon.state.on then
|
||||
vl_redstone.set_power(pos, 15)
|
||||
else
|
||||
vl_redstone.set_power(pos, 0)
|
||||
end
|
||||
end
|
||||
function vl_redstone.on_dignode(pos, node)
|
||||
print("Dug node at "..vector.to_string(pos))
|
||||
|
||||
-- Node was dug, can't power anything
|
||||
-- This doesn't work because the node is gone and we don't know what we were powering
|
||||
-- TODO: get the rules here and use that for the first step
|
||||
vl_redstone.set_power(pos, 0, nil, node)
|
||||
end
|
||||
|
||||
-- Persist multipower data
|
||||
minetest.register_on_shutdown(function()
|
||||
for pos_hash,node_multipower in pairs(multipower_cache) do
|
||||
local pos = minetest_get_position_from_hash(pos_hash)
|
||||
local meta = minetest_get_meta(pos)
|
||||
meta:set_string("vl_redstone.multipower", minetest_serialize(node_multipower))
|
||||
end
|
||||
end)
|
||||
minetest.register_on_placenode(vl_redstone.on_placenode)
|
||||
minetest.register_on_dignode(vl_redstone.on_dignode)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
description = Redstone implementation
|
||||
name = vl_redstone
|
||||
depends = mcl_sounds, mcl_core, vl_scheduler
|
||||
optional_depends = doc
|
|
@ -48,6 +48,11 @@ minetest.register_node("mcl_core:cactus", {
|
|||
end),
|
||||
_mcl_blast_resistance = 0.4,
|
||||
_mcl_hardness = 0.4,
|
||||
_mcl_minecarts_on_enter_side = function(pos, _, _, _, cart_data)
|
||||
if mcl_minecarts then
|
||||
mcl_minecarts.kill_cart(cart_data)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_node("mcl_core:reeds", {
|
||||
|
|
|
@ -9,6 +9,8 @@ local function mcl_log(message)
|
|||
end
|
||||
end
|
||||
|
||||
mcl_hoppers = {}
|
||||
|
||||
--[[ BEGIN OF NODE DEFINITIONS ]]
|
||||
|
||||
local mcl_hoppers_formspec = table.concat({
|
||||
|
@ -32,6 +34,12 @@ local mcl_hoppers_formspec = table.concat({
|
|||
"listring[current_player;main]",
|
||||
})
|
||||
|
||||
local function get_reading(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local inv = meta:get_inventory()
|
||||
return mcl_comparators.read_inventory(inv, "main")
|
||||
end
|
||||
|
||||
local function straight_hopper_act(pos, node, active_object_count, active_count_wider)
|
||||
local timer = minetest.get_node_timer(pos)
|
||||
if timer:is_started() then
|
||||
|
@ -50,9 +58,15 @@ local function straight_hopper_act(pos, node, active_object_count, active_count_
|
|||
dst_def._mcl_hopper_act( dst_pos, dst_node, active_object_count, active_count_wider )
|
||||
end
|
||||
|
||||
mcl_util.hopper_push(pos, dst_pos)
|
||||
local pushed = mcl_util.hopper_push(pos, dst_pos)
|
||||
|
||||
local src_pos = vector.offset(pos, 0, 1, 0)
|
||||
mcl_util.hopper_pull(pos, src_pos)
|
||||
local pulled = mcl_util.hopper_pull(pos, src_pos)
|
||||
|
||||
if pushed or pulled then mcl_comparators.trigger_update(pos) end
|
||||
|
||||
-- Update comparators
|
||||
mcl_comparators.trigger_update(pos, node)
|
||||
end
|
||||
|
||||
local function bent_hopper_act(pos, node, active_object_count, active_object_count_wider)
|
||||
|
@ -92,8 +106,103 @@ local function bent_hopper_act(pos, node, active_object_count, active_object_cou
|
|||
|
||||
local src_pos = vector.offset(pos, 0, 1, 0)
|
||||
mcl_util.hopper_pull(pos, src_pos)
|
||||
|
||||
-- Update comparators
|
||||
mcl_comparators.trigger_update(pos, node)
|
||||
end
|
||||
|
||||
--[[
|
||||
Returns true if an item was pushed to the minecart
|
||||
]]
|
||||
local function hopper_push_to_mc(mc_ent, dest_pos, inv_size)
|
||||
if not mcl_util.metadata_last_act(minetest.get_meta(dest_pos), "hopper_push_timer", 1) then return false end
|
||||
|
||||
local dest_inv = mcl_entity_invs.load_inv(mc_ent, inv_size)
|
||||
if not dest_inv then
|
||||
mcl_log("No inv")
|
||||
return false
|
||||
end
|
||||
|
||||
local meta = minetest.get_meta(dest_pos)
|
||||
local inv = meta:get_inventory()
|
||||
if not inv then
|
||||
mcl_log("No dest inv")
|
||||
return
|
||||
end
|
||||
|
||||
mcl_log("inv. size: " .. mc_ent._inv_size)
|
||||
for i = 1, mc_ent._inv_size, 1 do
|
||||
local stack = inv:get_stack("main", i)
|
||||
|
||||
mcl_log("i: " .. tostring(i))
|
||||
mcl_log("Name: [" .. tostring(stack:get_name()) .. "]")
|
||||
mcl_log("Count: " .. tostring(stack:get_count()))
|
||||
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
|
||||
|
||||
if not stack:get_name() or stack:get_name() ~= "" then
|
||||
if dest_inv:room_for_item("main", stack:peek_item()) then
|
||||
mcl_log("Room so unload")
|
||||
dest_inv:add_item("main", stack:take_item())
|
||||
inv:set_stack("main", i, stack)
|
||||
|
||||
-- Take one item and stop until next time
|
||||
return
|
||||
else
|
||||
mcl_log("no Room")
|
||||
end
|
||||
|
||||
else
|
||||
mcl_log("nothing there")
|
||||
end
|
||||
end
|
||||
end
|
||||
--[[
|
||||
Returns true if an item was pulled from the minecart
|
||||
]]
|
||||
local function hopper_pull_from_mc(mc_ent, dest_pos, inv_size)
|
||||
if not mcl_util.metadata_last_act(minetest.get_meta(dest_pos), "hopper_pull_timer", 1) then return false end
|
||||
|
||||
local inv = mcl_entity_invs.load_inv(mc_ent, inv_size)
|
||||
if not inv then
|
||||
mcl_log("No inv")
|
||||
return false
|
||||
end
|
||||
|
||||
local dest_meta = minetest.get_meta(dest_pos)
|
||||
local dest_inv = dest_meta:get_inventory()
|
||||
if not dest_inv then
|
||||
mcl_log("No dest inv")
|
||||
return false
|
||||
end
|
||||
|
||||
mcl_log("inv. size: " .. mc_ent._inv_size)
|
||||
for i = 1, mc_ent._inv_size, 1 do
|
||||
local stack = inv:get_stack("main", i)
|
||||
|
||||
mcl_log("i: " .. tostring(i))
|
||||
mcl_log("Name: [" .. tostring(stack:get_name()) .. "]")
|
||||
mcl_log("Count: " .. tostring(stack:get_count()))
|
||||
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
|
||||
|
||||
if not stack:get_name() or stack:get_name() ~= "" then
|
||||
if dest_inv:room_for_item("main", stack:peek_item()) then
|
||||
mcl_log("Room so unload")
|
||||
dest_inv:add_item("main", stack:take_item())
|
||||
inv:set_stack("main", i, stack)
|
||||
|
||||
-- Take one item and stop until next time, report that we took something
|
||||
return true
|
||||
else
|
||||
mcl_log("no Room")
|
||||
end
|
||||
|
||||
else
|
||||
mcl_log("nothing there")
|
||||
end
|
||||
end
|
||||
end
|
||||
mcl_hoppers.pull_from_minecart = hopper_pull_from_mc
|
||||
|
||||
-- Downwards hopper (base definition)
|
||||
|
||||
---@type node_definition
|
||||
|
@ -188,17 +297,61 @@ local def_hopper = {
|
|||
end
|
||||
end,
|
||||
on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
|
||||
mcl_comparators.trigger_update(pos)
|
||||
minetest.log("action", player:get_player_name() ..
|
||||
" moves stuff in mcl_hoppers at " .. minetest.pos_to_string(pos))
|
||||
end,
|
||||
on_metadata_inventory_put = function(pos, listname, index, stack, player)
|
||||
mcl_comparators.trigger_update(pos)
|
||||
minetest.log("action", player:get_player_name() ..
|
||||
" moves stuff to mcl_hoppers at " .. minetest.pos_to_string(pos))
|
||||
end,
|
||||
on_metadata_inventory_take = function(pos, listname, index, stack, player)
|
||||
mcl_comparators.trigger_update(pos)
|
||||
minetest.log("action", player:get_player_name() ..
|
||||
" takes stuff from mcl_hoppers at " .. minetest.pos_to_string(pos))
|
||||
end,
|
||||
_mcl_minecarts_on_enter_below = function(pos, cart, next_dir)
|
||||
-- Only pull to containers
|
||||
if cart and cart.groups and (cart.groups.container or 0) ~= 0 then
|
||||
cart:add_node_watch(pos)
|
||||
hopper_push_to_mc(cart, pos, 5)
|
||||
end
|
||||
end,
|
||||
_mcl_minecarts_on_enter_above = function(pos, cart, next_dir)
|
||||
-- Only push to containers
|
||||
if cart and cart.groups and (cart.groups.container or 0) ~= 0 then
|
||||
cart:add_node_watch(pos)
|
||||
hopper_pull_from_mc(cart, pos, 5)
|
||||
end
|
||||
end,
|
||||
_mcl_minecarts_on_leave_above = function(pos, cart, next_dir)
|
||||
if not cart then return end
|
||||
|
||||
cart:remove_node_watch(pos)
|
||||
end,
|
||||
_mcl_minecarts_node_on_step = function(pos, cart, dtime, cartdata)
|
||||
if not cart then
|
||||
minetest.log("warning", "trying to process hopper-to-minecart movement without luaentity")
|
||||
return
|
||||
end
|
||||
|
||||
local cart_pos = mcl_minecarts.get_cart_position(cartdata)
|
||||
if not cart_pos then return false end
|
||||
if vector.distance(cart_pos, pos) > 1.5 then
|
||||
cart:remove_node_watch(pos)
|
||||
return
|
||||
end
|
||||
if vector.direction(pos,cart_pos).y > 0 then
|
||||
-- The cart is above us, pull from minecart
|
||||
hopper_pull_from_mc(cart, pos, 5)
|
||||
else
|
||||
hopper_push_to_mc(cart, pos, 5)
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
_mcl_comparators_get_reading = get_reading,
|
||||
sounds = mcl_sounds.node_sound_metal_defaults(),
|
||||
|
||||
_mcl_blast_resistance = 4.8,
|
||||
|
@ -390,20 +543,83 @@ local def_hopper_side = {
|
|||
end
|
||||
end,
|
||||
on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
|
||||
mcl_comparators.trigger_update(pos)
|
||||
minetest.log("action", player:get_player_name() ..
|
||||
" moves stuff in mcl_hoppers at " .. minetest.pos_to_string(pos))
|
||||
" moves stuff in mcl_hoppers at (1) " .. minetest.pos_to_string(pos))
|
||||
end,
|
||||
on_metadata_inventory_put = function(pos, listname, index, stack, player)
|
||||
mcl_comparators.trigger_update(pos)
|
||||
minetest.log("action", player:get_player_name() ..
|
||||
" moves stuff to mcl_hoppers at " .. minetest.pos_to_string(pos))
|
||||
" moves stuff to mcl_hoppers at (2) " .. minetest.pos_to_string(pos))
|
||||
end,
|
||||
on_metadata_inventory_take = function(pos, listname, index, stack, player)
|
||||
mcl_comparators.trigger_update(pos)
|
||||
minetest.log("action", player:get_player_name() ..
|
||||
" takes stuff from mcl_hoppers at " .. minetest.pos_to_string(pos))
|
||||
" takes stuff from mcl_hoppers at (3) " .. minetest.pos_to_string(pos))
|
||||
end,
|
||||
on_rotate = on_rotate,
|
||||
sounds = mcl_sounds.node_sound_metal_defaults(),
|
||||
|
||||
_mcl_minecarts_on_enter_below = function(pos, cart, next_dir)
|
||||
-- Only push to containers
|
||||
if cart and cart.groups and (cart.groups.container or 0) ~= 0 then
|
||||
cart:add_node_watch(pos)
|
||||
hopper_pull_from_mc(cart, pos, 5)
|
||||
end
|
||||
end,
|
||||
_mcl_minecarts_on_leave_below = function(pos, cart, next_dir)
|
||||
if not cart then return end
|
||||
|
||||
cart:remove_node_watch(pos)
|
||||
end,
|
||||
_mcl_minecarts_on_enter_side = function(pos, cart, next_dir, rail_pos)
|
||||
if not cart then return end
|
||||
|
||||
-- Only try to push to minecarts when the spout position is pointed at the rail
|
||||
local face = minetest.get_node(pos).param2
|
||||
local dst_pos = {}
|
||||
if face == 0 then
|
||||
dst_pos = vector.offset(pos, -1, 0, 0)
|
||||
elseif face == 1 then
|
||||
dst_pos = vector.offset(pos, 0, 0, 1)
|
||||
elseif face == 2 then
|
||||
dst_pos = vector.offset(pos, 1, 0, 0)
|
||||
elseif face == 3 then
|
||||
dst_pos = vector.offset(pos, 0, 0, -1)
|
||||
end
|
||||
if dst_pos ~= rail_pos then return end
|
||||
|
||||
-- Only push to containers
|
||||
if cart.groups and (cart.groups.container or 0) ~= 0 then
|
||||
cart:add_node_watch(pos)
|
||||
end
|
||||
|
||||
hopper_push_to_mc(cart, pos, 5)
|
||||
end,
|
||||
_mcl_minecarts_on_leave_side = function(pos, cart, next_dir)
|
||||
if not cart then return end
|
||||
|
||||
cart:remove_node_watch(pos)
|
||||
end,
|
||||
_mcl_minecarts_node_on_step = function(pos, cart, dtime, cartdata)
|
||||
if not cart then return end
|
||||
|
||||
local cart_pos = mcl_minecarts.get_cart_position(cartdata)
|
||||
if not cart_pos then return false end
|
||||
if vector.distance(cart_pos, pos) > 1.5 then
|
||||
cart:remove_node_watch(pos)
|
||||
return false
|
||||
end
|
||||
|
||||
if cart_pos.y == pos.y then
|
||||
hopper_push_to_mc(cart, pos, 5)
|
||||
elseif cart_pos.y > pos.y then
|
||||
hopper_pull_from_mc(cart, pos, 5)
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
|
||||
_mcl_blast_resistance = 4.8,
|
||||
_mcl_hardness = 3,
|
||||
}
|
||||
|
@ -435,87 +651,6 @@ minetest.register_node("mcl_hoppers:hopper_side_disabled", def_hopper_side_disab
|
|||
|
||||
--[[ END OF NODE DEFINITIONS ]]
|
||||
|
||||
local function hopper_pull_from_mc(mc_ent, dest_pos, inv_size)
|
||||
local inv = mcl_entity_invs.load_inv(mc_ent, inv_size)
|
||||
if not inv then
|
||||
mcl_log("No inv")
|
||||
return false
|
||||
end
|
||||
|
||||
local dest_meta = minetest.get_meta(dest_pos)
|
||||
local dest_inv = dest_meta:get_inventory()
|
||||
if not dest_inv then
|
||||
mcl_log("No dest inv")
|
||||
return
|
||||
end
|
||||
|
||||
mcl_log("inv. size: " .. mc_ent._inv_size)
|
||||
for i = 1, mc_ent._inv_size, 1 do
|
||||
local stack = inv:get_stack("main", i)
|
||||
|
||||
mcl_log("i: " .. tostring(i))
|
||||
mcl_log("Name: [" .. tostring(stack:get_name()) .. "]")
|
||||
mcl_log("Count: " .. tostring(stack:get_count()))
|
||||
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
|
||||
|
||||
if not stack:get_name() or stack:get_name() ~= "" then
|
||||
if dest_inv:room_for_item("main", stack:peek_item()) then
|
||||
mcl_log("Room so unload")
|
||||
dest_inv:add_item("main", stack:take_item())
|
||||
inv:set_stack("main", i, stack)
|
||||
|
||||
-- Take one item and stop until next time
|
||||
return
|
||||
else
|
||||
mcl_log("no Room")
|
||||
end
|
||||
|
||||
else
|
||||
mcl_log("nothing there")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function hopper_push_to_mc(mc_ent, dest_pos, inv_size)
|
||||
local dest_inv = mcl_entity_invs.load_inv(mc_ent, inv_size)
|
||||
if not dest_inv then
|
||||
mcl_log("No inv")
|
||||
return false
|
||||
end
|
||||
|
||||
local meta = minetest.get_meta(dest_pos)
|
||||
local inv = meta:get_inventory()
|
||||
if not inv then
|
||||
mcl_log("No dest inv")
|
||||
return
|
||||
end
|
||||
|
||||
mcl_log("inv. size: " .. mc_ent._inv_size)
|
||||
for i = 1, mc_ent._inv_size, 1 do
|
||||
local stack = inv:get_stack("main", i)
|
||||
|
||||
mcl_log("i: " .. tostring(i))
|
||||
mcl_log("Name: [" .. tostring(stack:get_name()) .. "]")
|
||||
mcl_log("Count: " .. tostring(stack:get_count()))
|
||||
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
|
||||
|
||||
if not stack:get_name() or stack:get_name() ~= "" then
|
||||
if dest_inv:room_for_item("main", stack:peek_item()) then
|
||||
mcl_log("Room so unload")
|
||||
dest_inv:add_item("main", stack:take_item())
|
||||
inv:set_stack("main", i, stack)
|
||||
|
||||
-- Take one item and stop until next time
|
||||
return
|
||||
else
|
||||
mcl_log("no Room")
|
||||
end
|
||||
|
||||
else
|
||||
mcl_log("nothing there")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[ BEGIN OF ABM DEFINITONS ]]
|
||||
|
||||
|
@ -555,7 +690,7 @@ minetest.register_abm({
|
|||
and (hm_pos.z >= pos.z - DIST_FROM_MC and hm_pos.z <= pos.z + DIST_FROM_MC) then
|
||||
mcl_log("Minecart close enough")
|
||||
if entity.name == "mcl_minecarts:hopper_minecart" then
|
||||
hopper_pull_from_mc(entity, pos, 5)
|
||||
--hopper_pull_from_mc(entity, pos, 5)
|
||||
elseif entity.name == "mcl_minecarts:chest_minecart" or entity.name == "mcl_boats:chest_boat" then
|
||||
hopper_pull_from_mc(entity, pos, 27)
|
||||
end
|
||||
|
@ -564,7 +699,7 @@ minetest.register_abm({
|
|||
and (hm_pos.z >= pos.z - DIST_FROM_MC and hm_pos.z <= pos.z + DIST_FROM_MC) then
|
||||
mcl_log("Minecart close enough")
|
||||
if entity.name == "mcl_minecarts:hopper_minecart" then
|
||||
hopper_push_to_mc(entity, pos, 5)
|
||||
--hopper_push_to_mc(entity, pos, 5)
|
||||
elseif entity.name == "mcl_minecarts:chest_minecart" or entity.name == "mcl_boats:chest_boat" then
|
||||
hopper_push_to_mc(entity, pos, 27)
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
name = mcl_hoppers
|
||||
description = It's just a clone of Minecraft hoppers, functions nearly identical to them minus mesecons making them stop and the way they're placed.
|
||||
depends = mcl_core, mcl_formspec, mcl_sounds, mcl_util, mcl_dye
|
||||
depends = mcl_core, mcl_formspec, mcl_sounds, mcl_util, mcl_dye, mcl_comparators
|
||||
optional_depends = doc, screwdriver
|
||||
|
|
|
@ -3,17 +3,38 @@
|
|||
|
||||
-- Adapted for MineClone 2!
|
||||
|
||||
-- Imports
|
||||
local create_minecart = mcl_minecarts.create_minecart
|
||||
local get_cart_data = mcl_minecarts.get_cart_data
|
||||
local save_cart_data = mcl_minecarts.save_cart_data
|
||||
|
||||
-- Node names (Don't use aliases!)
|
||||
tsm_railcorridors.nodes = {
|
||||
dirt = "mcl_core:dirt",
|
||||
chest = "mcl_chests:chest",
|
||||
rail = "mcl_minecarts:rail",
|
||||
rail = "mcl_minecarts:rail_v2",
|
||||
torch_floor = "mcl_torches:torch",
|
||||
torch_wall = "mcl_torches:torch_wall",
|
||||
cobweb = "mcl_core:cobweb",
|
||||
spawner = "mcl_mobspawners:spawner",
|
||||
}
|
||||
|
||||
local update_rail_connections = mcl_minecarts.update_rail_connections
|
||||
local rails_to_update = {}
|
||||
tsm_railcorridors.on_place_node = {
|
||||
[tsm_railcorridors.nodes.rail] = function(pos, node)
|
||||
rails_to_update[#rails_to_update + 1] = pos
|
||||
end,
|
||||
}
|
||||
tsm_railcorridors.on_start = function()
|
||||
rails_to_update = {}
|
||||
end
|
||||
tsm_railcorridors.on_finish = function()
|
||||
for _,pos in pairs(rails_to_update) do
|
||||
update_rail_connections(pos, {legacy = true, ignore_neighbor_connections = true})
|
||||
end
|
||||
end
|
||||
|
||||
local mg_name = minetest.get_mapgen_setting("mg_name")
|
||||
|
||||
if mg_name == "v6" then
|
||||
|
@ -37,19 +58,31 @@ else
|
|||
end
|
||||
|
||||
|
||||
-- TODO: Use minecart with chest instead of normal minecart
|
||||
tsm_railcorridors.carts = { "mcl_minecarts:minecart" }
|
||||
tsm_railcorridors.carts = { "mcl_minecarts:chest_minecart", "mcl_minecarts:hopper_minecart", "mcl_minecarts:minecart" }
|
||||
local has_loot = {
|
||||
["mcl_minecarts:chest_minecart"] = true,
|
||||
["mcl_minecarts:hopper_minceart"] = true,
|
||||
}
|
||||
|
||||
function tsm_railcorridors.on_construct_cart(pos, cart)
|
||||
-- TODO: Fill cart with treasures
|
||||
function tsm_railcorridors.create_cart_staticdata(entity_id,pos, pr)
|
||||
local uuid = create_minecart(entity_id, pos, vector.new(1,0,0))
|
||||
|
||||
-- This is it? There's this giant hack announced in
|
||||
-- the other file and I grep for the function and it's
|
||||
-- a stub? :)
|
||||
-- Fill the cart with loot
|
||||
local cartdata = get_cart_data(uuid)
|
||||
if cartdata and has_loot[entity_id] then
|
||||
local items = tsm_railcorridors.get_treasures(pr)
|
||||
|
||||
-- The path here using some minetest.after hackery was
|
||||
-- deactivated in init.lua - reactivate when this does
|
||||
-- something the function is called RecheckCartHack.
|
||||
-- Convert from ItemStack to itemstrings
|
||||
for k,item in pairs(items) do
|
||||
items[k] = item:to_string()
|
||||
end
|
||||
cartdata.inventory = items
|
||||
|
||||
print("cartdata = "..dump(cartdata))
|
||||
save_cart_data(uuid)
|
||||
end
|
||||
|
||||
return minetest.serialize({ uuid=uuid, seq=1 })
|
||||
end
|
||||
|
||||
-- Fallback function. Returns a random treasure. This function is called for chests
|
||||
|
@ -102,11 +135,11 @@ function tsm_railcorridors.get_treasures(pr)
|
|||
stacks_min = 3,
|
||||
stacks_max = 3,
|
||||
items = {
|
||||
{ itemstring = "mcl_minecarts:rail", weight = 20, amount_min = 4, amount_max = 8 },
|
||||
{ itemstring = "mcl_minecarts:rail_v2", weight = 20, amount_min = 4, amount_max = 8 },
|
||||
{ itemstring = "mcl_torches:torch", weight = 15, amount_min = 1, amount_max = 16 },
|
||||
{ itemstring = "mcl_minecarts:activator_rail", weight = 5, amount_min = 1, amount_max = 4 },
|
||||
{ itemstring = "mcl_minecarts:detector_rail", weight = 5, amount_min = 1, amount_max = 4 },
|
||||
{ itemstring = "mcl_minecarts:golden_rail", weight = 5, amount_min = 1, amount_max = 4 },
|
||||
{ itemstring = "mcl_minecarts:activator_rail_v2", weight = 5, amount_min = 1, amount_max = 4 },
|
||||
{ itemstring = "mcl_minecarts:detector_rail_v2", weight = 5, amount_min = 1, amount_max = 4 },
|
||||
{ itemstring = "mcl_minecarts:golden_rail_v2", weight = 5, amount_min = 1, amount_max = 4 },
|
||||
}
|
||||
},
|
||||
-- non-MC loot: 50% chance to add a minecart, offered as alternative to spawning minecarts on rails.
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
local pairs = pairs
|
||||
local tonumber = tonumber
|
||||
|
||||
tsm_railcorridors = {}
|
||||
tsm_railcorridors = {
|
||||
after = {},
|
||||
}
|
||||
|
||||
-- Load node names
|
||||
dofile(minetest.get_modpath(minetest.get_current_modname()).."/gameconfig.lua")
|
||||
|
@ -170,6 +172,10 @@ local function SetNodeIfCanBuild(pos, node, check_above, can_replace_rail)
|
|||
(can_replace_rail and name == tsm_railcorridors.nodes.rail)
|
||||
) then
|
||||
minetest.set_node(pos, node)
|
||||
local after = tsm_railcorridors.on_place_node[node.name]
|
||||
if after then
|
||||
after(pos, node)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
@ -394,34 +400,6 @@ local function PlaceChest(pos, param2)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
-- This function checks if a cart has ACTUALLY been spawned.
|
||||
-- To be calld by minetest.after.
|
||||
-- This is a workaround thanks to the fact that minetest.add_entity is unreliable as fuck
|
||||
-- See: https://github.com/minetest/minetest/issues/4759
|
||||
-- FIXME: Kill this horrible hack with fire as soon you can.
|
||||
|
||||
-- Why did anyone activate it in the first place? It doesn't
|
||||
-- have a function seeing as there are no chest minecarts yet.
|
||||
--[[
|
||||
local function RecheckCartHack(params)
|
||||
local pos = params[1]
|
||||
local cart_id = params[2]
|
||||
-- Find cart
|
||||
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 1)) do
|
||||
if obj and obj:get_luaentity().name == cart_id then
|
||||
-- Cart found! We can now safely call the callback func.
|
||||
-- (calling it earlier has the danger of failing)
|
||||
minetest.log("info", "[tsm_railcorridors] Cart spawn succeeded: "..minetest.pos_to_string(pos))
|
||||
tsm_railcorridors.on_construct_cart(pos, obj)
|
||||
return
|
||||
end
|
||||
end
|
||||
minetest.log("info", "[tsm_railcorridors] Cart spawn FAILED: "..minetest.pos_to_string(pos))
|
||||
end
|
||||
--]]
|
||||
|
||||
|
||||
-- Try to place a cobweb.
|
||||
-- pos: Position of cobweb
|
||||
-- needs_check: If true, checks if any of the nodes above, below or to the side of the cobweb.
|
||||
|
@ -944,16 +922,13 @@ local function spawn_carts()
|
|||
-- See <https://github.com/minetest/minetest/issues/4759>
|
||||
local cart_id = tsm_railcorridors.carts[cart_type]
|
||||
minetest.log("info", "[tsm_railcorridors] Cart spawn attempt: "..minetest.pos_to_string(cpos))
|
||||
minetest.add_entity(cpos, cart_id)
|
||||
local cart_staticdata = nil
|
||||
|
||||
-- This checks if the cart is actually spawned, it's a giant hack!
|
||||
-- Note that the callback function is also called there.
|
||||
-- TODO: Move callback function to this position when the
|
||||
-- minetest.add_entity bug has been fixed.
|
||||
-- Try to create cart staticdata
|
||||
local hook = tsm_railcorridors.create_cart_staticdata
|
||||
if hook then cart_staticdata = hook(cart_id, cpos, pr) end
|
||||
|
||||
-- minetest.after(3, RecheckCartHack, {cpos, cart_id})
|
||||
-- This whole recheck logic leads to a stub right now
|
||||
-- it can be reenabled when chest carts are a thing.
|
||||
minetest.add_entity(cpos, cart_id, cart_staticdata)
|
||||
end
|
||||
end
|
||||
carts_table = {}
|
||||
|
@ -1123,7 +1098,15 @@ mcl_structures.register_structure("mineshaft",{
|
|||
end
|
||||
if p.y > -10 then return true end
|
||||
InitRandomizer(blockseed)
|
||||
|
||||
local hook = tsm_railcorridors.on_start
|
||||
if hook then hook() end
|
||||
|
||||
create_corridor_system(p, pr)
|
||||
|
||||
local hook = tsm_railcorridors.on_finish
|
||||
if hook then hook() end
|
||||
|
||||
return true
|
||||
end,
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ tsm_railcorridors_probability_chest (Chest probability) float 0.05 0.0 1.0
|
|||
#of finding a cart in rail corridors with high rail damage will be lower.
|
||||
#NOTE: Due to a bug in Minetest <https://github.com/minetest/minetest/issues/4759>
|
||||
#carts often fail to spawn even if they should.
|
||||
tsm_railcorridors_probability_cart (Cart probability) float 0.0 0.0 1.0
|
||||
tsm_railcorridors_probability_cart (Cart probability) float 0.05 0.0 1.0
|
||||
|
||||
#If enabled, cobwebs may be placed in some corridors.
|
||||
#Currently, cobwebs are only supported with the Mobs Redo mod.
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
local table = table
|
||||
|
||||
local storage = minetest.get_mod_storage()
|
||||
|
||||
-- Player state for public API
|
||||
mcl_playerinfo = {}
|
||||
local player_mod_metadata = {}
|
||||
|
||||
-- Get node but use fallback for nil or unknown
|
||||
local function node_ok(pos, fallback)
|
||||
|
@ -21,8 +24,6 @@ local function node_ok(pos, fallback)
|
|||
return fallback
|
||||
end
|
||||
|
||||
local time = 0
|
||||
|
||||
local function get_player_nodes(player_pos)
|
||||
local work_pos = table.copy(player_pos)
|
||||
|
||||
|
@ -43,11 +44,10 @@ local function get_player_nodes(player_pos)
|
|||
return node_stand, node_stand_below, node_head, node_feet, node_head_top
|
||||
end
|
||||
|
||||
local time = 0
|
||||
minetest.register_globalstep(function(dtime)
|
||||
|
||||
time = time + dtime
|
||||
|
||||
-- Run the rest of the code every 0.5 seconds
|
||||
time = time + dtime
|
||||
if time < 0.5 then
|
||||
return
|
||||
end
|
||||
|
@ -76,6 +76,23 @@ minetest.register_globalstep(function(dtime)
|
|||
|
||||
end)
|
||||
|
||||
function mcl_playerinfo.get_mod_meta(player_name, modname)
|
||||
-- Load the player's metadata
|
||||
local meta = player_mod_metadata[player_name]
|
||||
if not meta then
|
||||
meta = minetest.deserialize(storage:get_string(player_name))
|
||||
end
|
||||
if not meta then
|
||||
meta = {}
|
||||
end
|
||||
player_mod_metadata[player_name] = meta
|
||||
|
||||
-- Get the requested module's section of the metadata
|
||||
local mod_meta = meta[modname] or {}
|
||||
meta[modname] = mod_meta
|
||||
return mod_meta
|
||||
end
|
||||
|
||||
-- set to blank on join (for 3rd party mods)
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
|
@ -87,7 +104,6 @@ minetest.register_on_joinplayer(function(player)
|
|||
node_stand_below = "",
|
||||
node_head_top = "",
|
||||
}
|
||||
|
||||
end)
|
||||
|
||||
-- clear when player leaves
|
||||
|
@ -96,3 +112,9 @@ minetest.register_on_leaveplayer(function(player)
|
|||
|
||||
mcl_playerinfo[name] = nil
|
||||
end)
|
||||
|
||||
minetest.register_on_shutdown(function()
|
||||
for name,data in pairs(player_mod_metadata) do
|
||||
storage:set_string(name, minetest.serialize(data))
|
||||
end
|
||||
end)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 145 B |
Loading…
Reference in New Issue