Compare commits
187 Commits
28d2dcbbf3
...
01f168a511
Author | SHA1 | Date |
---|---|---|
teknomunk | 01f168a511 | |
teknomunk | d1c4b3ddc2 | |
teknomunk | bceb070849 | |
teknomunk | cd69597c6c | |
teknomunk | 4f913f4264 | |
teknomunk | 4a88f976f9 | |
teknomunk | 4e923219c7 | |
teknomunk | b653fab1ff | |
teknomunk | af24855d18 | |
teknomunk | 38e5aada31 | |
teknomunk | 6ccfca2387 | |
teknomunk | 1d10fdcac2 | |
teknomunk | a8314b1356 | |
teknomunk | 4e970a5ea1 | |
teknomunk | c7a829d59a | |
teknomunk | e364722864 | |
teknomunk | e204e0fd3e | |
teknomunk | b39482fe32 | |
teknomunk | e0e343d86b | |
teknomunk | 6183e71b12 | |
teknomunk | e44ff54195 | |
teknomunk | 517e5765ee | |
teknomunk | 08419ccacf | |
teknomunk | 0f1208d7b4 | |
teknomunk | d1452f44de | |
teknomunk | 63edda42a7 | |
teknomunk | 735ef088ce | |
teknomunk | f99a3a8e26 | |
teknomunk | b964fc3372 | |
teknomunk | 66727aedda | |
teknomunk | 70bb65ac1c | |
teknomunk | c33fb223da | |
teknomunk | b6ca0e6af5 | |
teknomunk | 665e8fec52 | |
teknomunk | 16889c5d38 | |
teknomunk | 9615d44332 | |
teknomunk | fac22d53d4 | |
teknomunk | d568adb931 | |
teknomunk | 45ce26124c | |
teknomunk | 90c6fd9f75 | |
teknomunk | 8334ccf4aa | |
teknomunk | 96f6624684 | |
teknomunk | 166c19e8f3 | |
teknomunk | 90f51af90e | |
teknomunk | f3d2c8b877 | |
teknomunk | 6849bcc518 | |
teknomunk | b815f82de5 | |
teknomunk | 3feadcb9f9 | |
teknomunk | a389ecebc4 | |
teknomunk | e8b5c9f9de | |
teknomunk | 166cd93298 | |
teknomunk | 0b5f6544ce | |
teknomunk | e6a0f01cde | |
teknomunk | 2c460d5e30 | |
teknomunk | c1398b52cc | |
teknomunk | 125ca644b5 | |
teknomunk | da5ab60545 | |
teknomunk | 7959108245 | |
teknomunk | fdf673162a | |
teknomunk | 6dc721f9cf | |
teknomunk | af0e4d12af | |
teknomunk | 46028b1fc0 | |
teknomunk | 7be3659fe1 | |
teknomunk | c6eb2f23d5 | |
teknomunk | 578de9c398 | |
teknomunk | 11d8e46e96 | |
teknomunk | 9f8559a642 | |
teknomunk | 418efda348 | |
teknomunk | a65d043e15 | |
teknomunk | ac44d93aa4 | |
teknomunk | 68e7a6e02c | |
teknomunk | 46ec4db298 | |
teknomunk | 2b6dba20be | |
teknomunk | 2610754ee6 | |
teknomunk | 44875440eb | |
teknomunk | 60ef68b7c7 | |
teknomunk | c3c6d2861e | |
teknomunk | 474eed5e8f | |
teknomunk | 845d92aef6 | |
teknomunk | 17360f42de | |
teknomunk | 2447632d57 | |
teknomunk | 200f72c2f1 | |
teknomunk | d8ad11781b | |
teknomunk | 2e20ea21bd | |
teknomunk | 0734ce73cd | |
teknomunk | efd171252f | |
teknomunk | d2f8676df2 | |
teknomunk | f86ff41554 | |
teknomunk | 252179c695 | |
teknomunk | 6cf8c640ca | |
teknomunk | 12df6f37dc | |
teknomunk | b7ba0c8607 | |
teknomunk | c7fd179a35 | |
teknomunk | c69ab674d3 | |
teknomunk | 5f5ec96394 | |
teknomunk | 3320fe768b | |
teknomunk | 4e27774679 | |
teknomunk | 4943fff0d5 | |
teknomunk | 7aa83ac15c | |
teknomunk | 88ffa84dc0 | |
teknomunk | a4c60cfa4a | |
teknomunk | b815253704 | |
teknomunk | 233e253a5d | |
teknomunk | 39f22e3769 | |
teknomunk | 556c29cf0c | |
teknomunk | 8702911da8 | |
teknomunk | b02b68148c | |
teknomunk | 75304d2e75 | |
teknomunk | 8478269dd9 | |
teknomunk | 35af50bce0 | |
teknomunk | d5c9a56e74 | |
teknomunk | 641626e505 | |
teknomunk | 64760f7d9d | |
teknomunk | 2259f5249b | |
teknomunk | 81c77f039e | |
teknomunk | 00dccd1823 | |
teknomunk | f18bd9b8e7 | |
teknomunk | 8773e515d6 | |
teknomunk | d02d1b7b05 | |
teknomunk | d7294d156d | |
teknomunk | 0b7a6c444f | |
teknomunk | 126b667527 | |
teknomunk | 95fc97e6b8 | |
teknomunk | b8496d1ad3 | |
teknomunk | fc64ff5c41 | |
teknomunk | e9116457bc | |
teknomunk | 70e94d3745 | |
teknomunk | baf58dbf21 | |
teknomunk | 8a1170120c | |
teknomunk | f346b95620 | |
teknomunk | 2468acb27b | |
teknomunk | 44b46b0763 | |
teknomunk | 408d17d2b1 | |
teknomunk | ea6cb325aa | |
teknomunk | 9365e16a6d | |
teknomunk | 5cc06160fb | |
teknomunk | 09c3c5efe5 | |
teknomunk | ed6ef684b2 | |
teknomunk | 74345fb555 | |
teknomunk | 8bd7efc25c | |
teknomunk | 19a9e9de1b | |
teknomunk | b6d30919cf | |
teknomunk | 1e621ab0e7 | |
teknomunk | cd32b7865f | |
teknomunk | 0050b2dd62 | |
teknomunk | 397ad0c64f | |
teknomunk | 83ad76d3ba | |
teknomunk | f122e38f3c | |
teknomunk | 698d038e10 | |
teknomunk | 9b89b200aa | |
teknomunk | 1fb1865052 | |
teknomunk | 66164e1f0e | |
teknomunk | 865a278554 | |
teknomunk | c13c800cd8 | |
teknomunk | bb8a2ee637 | |
teknomunk | a67203c378 | |
teknomunk | 0828372339 | |
teknomunk | f476e43084 | |
teknomunk | d45bf2d65f | |
teknomunk | 89867adbfe | |
teknomunk | 7c898db3a2 | |
teknomunk | afbe257bee | |
teknomunk | c64d9eea02 | |
kno10 | b540e6c77b | |
kno10 | d49426d453 | |
the-real-herowl | 2b7b7f1872 | |
kno10 | b5afa34469 | |
kno10 | ebf6cf32e8 | |
kno10 | a8318f6600 | |
kno10 | fa7a7f4e81 | |
kno10 | c097c65262 | |
kno10 | 220a7b06e6 | |
kno10 | 540a070c59 | |
kno10 | 78a958db4e | |
kno10 | e9453d6210 | |
kno10 | 9376cf92b1 | |
kno10 | c4030115c4 | |
kno10 | e1ace4ad01 | |
the-real-herowl | e3b7847df1 | |
Mikita Wiśniewski | f86a641dfa | |
Mikita Wiśniewski | 084741b733 | |
Mikita Wiśniewski | d5bc0613d8 | |
Loveaabb | f26c34e65f | |
Loveaabb | 04e29c5796 | |
Elias Åström | 45ae170447 | |
Elias Åström | d0d1217dec | |
Elias Åström | cffc8e0145 |
|
@ -73,6 +73,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 = {}
|
||||
|
||||
|
@ -359,13 +368,7 @@ function mcl_util.hopper_push(pos, dst_pos)
|
|||
return ok
|
||||
end
|
||||
|
||||
-- Try pulling from source inventory to hopper inventory
|
||||
---@param pos Vector
|
||||
---@param src_pos Vector
|
||||
function mcl_util.hopper_pull(pos, src_pos)
|
||||
local hop_inv = minetest.get_meta(pos):get_inventory()
|
||||
local hop_list = 'main'
|
||||
|
||||
function mcl_util.hopper_pull_to_inventory(hop_inv, hop_list, src_pos, pos)
|
||||
-- Get node pos' for item transfer
|
||||
local src = minetest.get_node(src_pos)
|
||||
if not minetest.registered_nodes[src.name] then return end
|
||||
|
@ -381,7 +384,7 @@ function mcl_util.hopper_pull(pos, src_pos)
|
|||
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, nil, 1)
|
||||
stack_id = mcl_util.select_stack(src_inv, src_list, hop_inv, hop_list)
|
||||
end
|
||||
|
||||
if stack_id ~= nil then
|
||||
|
@ -391,6 +394,12 @@ function mcl_util.hopper_pull(pos, src_pos)
|
|||
end
|
||||
end
|
||||
end
|
||||
-- Try pulling from source inventory to hopper inventory
|
||||
---@param pos Vector
|
||||
---@param src_pos Vector
|
||||
function mcl_util.hopper_pull(pos, src_pos)
|
||||
return mcl_util.hopper_pull_to_inventory(minetest.get_meta(pos):get_inventory(), "main", src_pos, pos)
|
||||
end
|
||||
|
||||
local function drop_item_stack(pos, stack)
|
||||
if not stack or stack:is_empty() then return end
|
||||
|
@ -1131,3 +1140,89 @@ if not vector.in_area then
|
|||
(pos.z >= min.z) and (pos.z <= max.z)
|
||||
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
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# Legacy Code Support Functions
|
||||
|
||||
## `vl_legacy.deprecated(description, replacement)`
|
||||
|
||||
Creates a wrapper that logs calls to deprecated function.
|
||||
|
||||
Arguments:
|
||||
* `description`: The text logged when the deprecated function is called.
|
||||
* `replacement`: The function that should be called instead. This is invoked passing
|
||||
along the parameters exactly as provided.
|
||||
|
||||
## `vl_legacy.register_item_conversion`
|
||||
|
||||
Allows automatic conversion of items.
|
||||
|
||||
Arguments:
|
||||
* `old`: Itemstring to be converted
|
||||
* `new`: New item string
|
||||
|
||||
## `vl_legacy.convert_node(pos, node)`
|
||||
|
||||
Converts legacy nodes to newer versions.
|
||||
|
||||
Arguments:
|
||||
* `pos`: Position of the node to attempt conversion
|
||||
* `node`: Node definition to convert. The node will be loaded from map data if `nil`.
|
||||
|
||||
The node definition for the old node must contain the field `_vl_legacy_convert` with
|
||||
a value that is either a `function(pos, node)` or `string` for this call to have any
|
||||
affect. If a function is provided, the function is called with `pos` and `node` as
|
||||
arguments. If a string is provided, a node name conversion will occur.
|
||||
|
||||
This mod provides an LBM and ABM that will automatically call this function for nodes
|
||||
with `group:legacy` set.
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
local mod = {}
|
||||
vl_legacy = mod
|
||||
|
||||
function mod.deprecated(description, func)
|
||||
return function(...)
|
||||
minetest.log("warning",description .. debug.traceback())
|
||||
return func(...)
|
||||
end
|
||||
end
|
||||
|
||||
local item_conversions = {}
|
||||
mod.registered_item_conversions = item_conversions
|
||||
|
||||
function mod.register_item_conversion(old, new, func)
|
||||
item_conversions[old] = {new, func}
|
||||
end
|
||||
function mod.convert_inventory_lists(lists)
|
||||
for _,list in pairs(lists) do
|
||||
for i = 1,#list do
|
||||
local itemstack = list[i]
|
||||
local conversion = itemstack and item_conversions[itemstack:get_name()]
|
||||
if conversion then
|
||||
local new_name,func = conversion[1],conversion[2]
|
||||
if func then
|
||||
func(itemstack)
|
||||
else
|
||||
itemstack:set_name(new_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
function mod.convert_inventory(inv)
|
||||
local lists = inv:get_lists()
|
||||
mod.convert_inventory_lists(lists)
|
||||
inv:set_lists(lists)
|
||||
end
|
||||
function mod.convert_node(pos, node)
|
||||
local node = node or minetest.get_node(pos)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
local convert = node_def._vl_legacy_convert_node
|
||||
if type(convert) == "function" then
|
||||
convert(pos, node)
|
||||
elseif type(convert) == "string" then
|
||||
node.name = convert
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
mod.convert_inventory(player:get_inventory())
|
||||
end)
|
||||
|
||||
minetest.register_lbm({
|
||||
name = "vl_legacy:convert_container_inventories",
|
||||
nodenames = "group:container",
|
||||
run_at_every_load = true,
|
||||
action = function(pos, node)
|
||||
local meta = minetest.get_meta(pos)
|
||||
mod.convert_inventory(meta:get_inventory())
|
||||
end
|
||||
})
|
||||
minetest.register_lbm({
|
||||
name = "vl_legacy:convert_nodes",
|
||||
nodenames = "group:legacy",
|
||||
run_at_every_load = true,
|
||||
action = mod.convert_node,
|
||||
})
|
||||
minetest.register_abm({
|
||||
label = "Convert Legacy Nodes",
|
||||
nodenames = "group:legacy",
|
||||
interval = 5,
|
||||
chance = 1,
|
||||
action = mod.convert_node,
|
||||
})
|
|
@ -0,0 +1,3 @@
|
|||
name = vl_legacy
|
||||
author = teknomunk
|
||||
description = API to ease conversion of items, deprecated function logging and similar functions
|
|
@ -27,28 +27,33 @@ local inv_callbacks = {
|
|||
}
|
||||
|
||||
function mcl_entity_invs.load_inv(ent,size)
|
||||
mcl_log("load_inv")
|
||||
if not ent._inv_id then return end
|
||||
mcl_log("load_inv 2")
|
||||
local inv = minetest.get_inventory({type="detached", name=ent._inv_id})
|
||||
if not inv then
|
||||
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
|
||||
local lists = ent:_mcl_entity_invs_load_items()
|
||||
vl_legacy.convert_inventory_lists(lists)
|
||||
inv:set_list("main", lists)
|
||||
elseif ent._items then
|
||||
vl_legacy.convert_inventory_lists(ent._items)
|
||||
inv:set_list("main",ent._items)
|
||||
end
|
||||
else
|
||||
mcl_log("load_inv 4")
|
||||
end
|
||||
return inv
|
||||
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
|
||||
|
@ -108,7 +113,11 @@ function mcl_entity_invs.show_inv_form(ent,player,text)
|
|||
|
||||
local playername = player:get_player_name()
|
||||
|
||||
minetest.show_formspec(playername, ent._inv_id, load_default_formspec (ent, text))
|
||||
-- Workaround: wait at least 50ms to ensure that the detached inventory exists before
|
||||
-- the formspec attempts to use it. (See https://git.minetest.land/VoxeLibre/VoxeLibre/issues/4670#issuecomment-84875)
|
||||
minetest.after(0.05, function()
|
||||
minetest.show_formspec(playername, ent._inv_id, load_default_formspec (ent, text))
|
||||
end)
|
||||
end
|
||||
|
||||
local function drop_inv(ent)
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
name = mcl_entity_invs
|
||||
author = cora
|
||||
depends = mcl_formspec
|
||||
depends = mcl_formspec, vl_legacy
|
||||
|
|
|
@ -840,6 +840,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
|
||||
|
@ -892,6 +893,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
|
||||
|
@ -985,7 +987,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 a 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,666 @@
|
|||
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 movement = dofile(modpath.."/movement.lua")
|
||||
assert(movement.do_movement)
|
||||
assert(movement.do_detached_movement)
|
||||
assert(movement.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
|
||||
mod.detach_driver = detach_driver
|
||||
|
||||
function mod.kill_cart(staticdata, killer)
|
||||
local pos
|
||||
mcl_log("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
|
||||
|
||||
|
||||
-- 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
|
||||
|
||||
if puncher:get_player_control().sneak then
|
||||
mod.kill_cart(staticdata, puncher)
|
||||
return
|
||||
end
|
||||
|
||||
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 staticdata.seq and self._seq < staticdata.seq then
|
||||
--print("removing cart #"..staticdata.uuid.." with sequence number mismatch")
|
||||
self.object:remove()
|
||||
self._removed = true
|
||||
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
|
||||
movement.do_detached_movement(self, dtime)
|
||||
else
|
||||
mod.update_cart_orientation(self)
|
||||
end
|
||||
end
|
||||
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
|
||||
movement.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
|
||||
movement.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)
|
||||
mod.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,139 @@
|
|||
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",
|
||||
},
|
||||
glow = 15,
|
||||
})
|
||||
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
|
||||
local pos = mod.get_cart_position(self._staticdata) or self.object:get_pos()
|
||||
if pos then
|
||||
tnt.smoke_step(pos)
|
||||
end
|
||||
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,36 @@
|
|||
local vector = vector
|
||||
local mod = mcl_minecarts
|
||||
local table_merge = mcl_util.table_merge
|
||||
|
||||
local 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 function force_get_node(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
if node.name ~= "ignore" then return node end
|
||||
|
||||
--local time_start = minetest.get_us_time()
|
||||
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)
|
||||
--minetest.log("force_get_node() voxel_manip section took "..((minetest.get_us_time()-time_start)*1e-6).." seconds")
|
||||
return {
|
||||
name = minetest.get_name_from_content_id(data[vi]),
|
||||
param = param_data[vi],
|
||||
param2 = param2_data[vi]
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
function mcl_minecarts:get_sign(z)
|
||||
if z == 0 then
|
||||
|
@ -10,158 +42,471 @@ 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])
|
||||
function mcl_minecarts.is_rail(self, pos, railtype)
|
||||
-- Compatibility with mcl_minecarts:is_rail() usage
|
||||
if self ~= mcl_minecarts then
|
||||
railtype = pos
|
||||
pos = self
|
||||
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
|
||||
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 apply_connection_rules(node, nodedef, pos, rules, connections)
|
||||
-- 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
|
||||
end
|
||||
|
||||
local function is_rail_end_connected(pos, dir)
|
||||
-- Handle new track types that have track-specific direction handler
|
||||
local node = force_get_node(pos)
|
||||
local get_next_dir = get_path(minetest.registered_nodes,node.name,"_mcl_minecarts","get_next_dir")
|
||||
if not get_next_dir then return false end
|
||||
|
||||
return get_next_dir(pos, dir, node) == dir
|
||||
end
|
||||
|
||||
local function bend_straight_rail(pos, towards)
|
||||
dir = CONNECTIONS[i]
|
||||
local node = force_get_node(pos)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
|
||||
-- Only bend rails
|
||||
local rail_type = minetest.get_item_group(node.name, "rail")
|
||||
if rail_type == 0 then return end
|
||||
|
||||
-- Only bend unbent rails
|
||||
if not nodedef._mcl_minecarts then return end
|
||||
if node.name ~= nodedef._mcl_minecarts.base_name then return end
|
||||
|
||||
-- only bend rails that have at least one free end
|
||||
local dir1 = minetest.fourdir_to_dir(node.param2)
|
||||
local dir2 = minetest.fourdir_to_dir((node.param2+2)%4)
|
||||
local dir1_connected = is_rail_end_connected(pos + dir1, dir2)
|
||||
local dir2_connected = is_rail_end_connected(pos + dir2, dir1)
|
||||
if dir1_connected and dir2_connected then return end
|
||||
|
||||
-- TODO: bend the rail
|
||||
local connections = {
|
||||
vector.direction(pos, towards),
|
||||
}
|
||||
if dir1_connected then
|
||||
connections[#connections+1] = dir1
|
||||
end
|
||||
if dir2_connected then
|
||||
connections[#connections+1] = dir2
|
||||
end
|
||||
local connections_mask = 0
|
||||
for i = 1,#CONNECTIONS do
|
||||
for j = 1,#connections do
|
||||
if CONNECTIONS[i] == connections[j] then
|
||||
connections_mask = bit.bor(connections_mask, bit.lshift(1, i -1))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local rules = HORIZONTAL_RULES_BY_RAIL_GROUP[nodedef.groups.rail]
|
||||
apply_connection_rules(node, nodedef, pos, rules, connections_mask)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
if not (opt and opt.no_bend_straights) then
|
||||
for i = 1,#CONNECTIONS do
|
||||
bend_straight_rail(vector.add(pos, CONNECTIONS[i]), pos)
|
||||
end
|
||||
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
|
||||
|
||||
apply_connection_rules(node, nodedef, pos, rules, connections)
|
||||
|
||||
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 get_next_dir = get_path(minetest.registered_nodes,node.name,"_mcl_minecarts","get_next_dir")
|
||||
if not get_next_dir then return dir end
|
||||
|
||||
dir = 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(self, pos_, dir)
|
||||
-- Compatibility with mcl_minecarts:get_rail_direction() usage
|
||||
if self ~= mcl_minecarts then
|
||||
dir = pos_
|
||||
pos_ = self
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
if new_dir.y ~= 0 then return new_dir end
|
||||
|
||||
-- Check four 45 degree movement
|
||||
local next_rails_dir = get_rail_direction_inner(vector.add(pos, new_dir), new_dir)
|
||||
if next_rails_dir.y == 0 and vector.equals(next_rails_dir, dir) and not vector.equals(new_dir, next_rails_dir) then
|
||||
return vector.add(new_dir, next_rails_dir)
|
||||
end
|
||||
|
||||
return new_dir
|
||||
end
|
||||
|
||||
local _2_pi = math.pi * 2
|
||||
local _half_pi = math.pi * 0.5
|
||||
local _quart_pi = math.pi * 0.5
|
||||
local pi = math.pi
|
||||
local rot_debug = {}
|
||||
function mod.update_cart_orientation(self)
|
||||
local staticdata = self._staticdata
|
||||
local dir = staticdata.dir
|
||||
|
||||
-- Calculate an angle from the x,z direction components
|
||||
local rot_y = math.atan2( dir.z, dir.x ) + ( 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 old_rot = vector.new(rot)
|
||||
rot.y = (rot.y - _half_pi + _2_pi) % _2_pi
|
||||
if not rot then return end
|
||||
|
||||
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
|
||||
staticdata.rot_adjust = ( ( staticdata.rot_adjust or 0 ) + pi ) % _2_pi
|
||||
else
|
||||
rot.y = rot_y
|
||||
end
|
||||
|
||||
-- Forward/backwards tilt (pitch)
|
||||
if dir.y < 0 then
|
||||
rot.x = -_quart_pi
|
||||
elseif dir.y > 0 then
|
||||
rot.x = _quart_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
|
||||
|
||||
rot.y = (rot.y + _half_pi) % _2_pi
|
||||
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
|
||||
if not staticdata.connected_at then return end
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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, vl_legacy
|
||||
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,605 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
local submod = {}
|
||||
|
||||
-- Constants
|
||||
local mcl_debug,DEBUG = mcl_util.make_mcl_logger("mcl_logging_minecart_debug", "Minecart Debug")
|
||||
--DEBUG = false
|
||||
--mcl_debug,DEBUG = function(msg) print(msg) end,true
|
||||
|
||||
-- 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
|
||||
submod.handle_cart_enter = handle_cart_enter
|
||||
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 look_directions = {
|
||||
mod.north,
|
||||
mod.west,
|
||||
mod.south,
|
||||
mod.east,
|
||||
}
|
||||
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 = look_directions[ctrl.look or 0] or mod.north
|
||||
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
|
||||
local rail_accel = node_def._rail_acceleration
|
||||
if type(rail_accel) == "function" then
|
||||
acceleration = (rail_accel(pos, staticdata) or 0) * 4
|
||||
else
|
||||
acceleration = rail_accel * 4
|
||||
end
|
||||
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
|
||||
|
||||
-- Calculate acceleration
|
||||
local v_0 = staticdata.velocity or 0
|
||||
local a = 0
|
||||
if staticdata.ahead or staticdata.behind then
|
||||
-- Calculate acceleration of the entire train
|
||||
local count = 0
|
||||
for cart in mod.train_cars(staticdata) do
|
||||
count = count + 1
|
||||
if cart.behind then
|
||||
a = a + calculate_acceleration(cart)
|
||||
end
|
||||
end
|
||||
a = a / count
|
||||
else
|
||||
a = calculate_acceleration(staticdata)
|
||||
end
|
||||
|
||||
-- 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
|
||||
if timestep <= 0.01 then
|
||||
reverse_direction(staticdata)
|
||||
end
|
||||
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 < 0.025 then
|
||||
v_1 = 0
|
||||
end
|
||||
|
||||
-- Calculate x_1
|
||||
local x_1 = x_0 + (timestep * v_0 + 0.5 * a * timestep * timestep) / vector.length(staticdata.dir)
|
||||
|
||||
-- Update position and velocity of the minecart
|
||||
staticdata.velocity = v_1
|
||||
staticdata.distance = x_1
|
||||
|
||||
if DEBUG 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
|
||||
|
||||
function submod.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
|
||||
|
||||
function submod.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
|
||||
|
||||
-- Reset pitch
|
||||
local rot = self.object:get_rotation()
|
||||
rot.y = 0
|
||||
self.object:set_rotation(rot)
|
||||
|
||||
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
|
||||
|
||||
-- Reset pitch if still not attached
|
||||
local rot = self.object:get_rotation()
|
||||
rot.x = 0
|
||||
self.object:set_rotation(rot)
|
||||
end
|
||||
|
||||
--return do_movement, do_detatched_movement
|
||||
return submod
|
||||
|
|
@ -1,53 +1,407 @@
|
|||
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 = minetest.get_node(pointed_thing.under)
|
||||
local node_name = node.name
|
||||
|
||||
-- Don't allow placing rail above rail
|
||||
if minetest.get_item_group(node_name,"rail") ~= 0 then
|
||||
return itemstack
|
||||
end
|
||||
|
||||
-- Handle right-clicking nodes with right-click handlers
|
||||
if placer and not placer:get_player_control().sneak then
|
||||
local node_def = minetest.registered_nodes[node_name] or {}
|
||||
local on_rightclick = node_def and node_def.on_rightclick
|
||||
if on_rightclick then
|
||||
return on_rightclick(pointed_thing.under, node, placer, itemstack, pointed_thing) or itemstack
|
||||
end
|
||||
end
|
||||
|
||||
-- Place the rail
|
||||
return minetest.item_place_node(itemstack, placer, pointed_thing)
|
||||
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
|
||||
|
||||
local function make_mesecons(base_name, suffix, base_mesecons)
|
||||
if not base_mesecons then
|
||||
if suffix == "_tee_off" or suffix == "_tee_on" then
|
||||
base_mesecons = {}
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local mesecons = table.copy(base_mesecons)
|
||||
|
||||
if suffix == "_tee_off" then
|
||||
mesecons.effector = base_mesecons.effector and table.copy(base_mesecons.effector) or {}
|
||||
|
||||
local old_action_on = base_mesecons.effector and base_mesecons.effector.action_on
|
||||
mesecons.effector.action_on = function(pos, node)
|
||||
if old_action_on then old_action_on(pos, node) end
|
||||
|
||||
node.name = base_name.."_tee_on"
|
||||
minetest.set_node(pos, node)
|
||||
end
|
||||
mesecons.effector.rules = mesecons.effector.rules or mesecon.rules.alldirs
|
||||
elseif suffix == "_tee_on" then
|
||||
mesecons.effector = base_mesecons.effector and table.copy(base_mesecons.effector) or {}
|
||||
|
||||
local old_action_off = base_mesecons.effector and base_mesecons.effector.action_off
|
||||
mesecons.effector.action_off = function(pos, node)
|
||||
if old_action_off then old_action_off(pos, node) end
|
||||
|
||||
node.name = base_name.."_tee_off"
|
||||
minetest.set_node(pos, node)
|
||||
end
|
||||
mesecons.effector.rules = mesecons.effector.rules or mesecon.rules.alldirs
|
||||
end
|
||||
|
||||
if mesecons.conductor then
|
||||
mesecons.conductor = table.copy(base_mesecons.conductor)
|
||||
|
||||
if mesecons.conductor.onstate then
|
||||
mesecons.conductor.onstate = base_mesecons.conductor.onstate..suffix
|
||||
end
|
||||
if base_mesecons.conductor.offstate then
|
||||
mesecons.conductor.offstate = base_mesecons.conductor.offstate..suffix
|
||||
end
|
||||
end
|
||||
|
||||
minetest.log("mesecons for "..base_name..suffix.." is "..dump(mesecons))
|
||||
return mesecons
|
||||
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",
|
||||
suffix = "",
|
||||
},
|
||||
})
|
||||
|
||||
-- Sloped variant
|
||||
mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_sloped,
|
||||
suffix = "_sloped",
|
||||
},
|
||||
mesecons = make_mesecons(base_name, "_sloped", def.mesecons),
|
||||
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,
|
||||
suffix = "",
|
||||
},
|
||||
}))
|
||||
|
||||
-- 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",
|
||||
suffix = "_corner",
|
||||
},
|
||||
mesecons = make_mesecons(base_name, "_corner", def.mesecons),
|
||||
}))
|
||||
|
||||
-- Tee variants
|
||||
mod.register_rail(base_name.."_tee_off", table_merge(table.copy(base_def),{
|
||||
tiles = { tiles[3] },
|
||||
mesecons = make_mesecons(base_name, "_tee_off", def.mesecons),
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_tee_off,
|
||||
railtype = "tee",
|
||||
suffix = "_tee_off",
|
||||
},
|
||||
}))
|
||||
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",
|
||||
suffix = "_tee_on",
|
||||
},
|
||||
mesecons = make_mesecons(base_name, "_tee_on", def.mesecons),
|
||||
}))
|
||||
|
||||
-- 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",
|
||||
suffix = "_sloped",
|
||||
},
|
||||
mesecons = make_mesecons(base_name, "_sloped", def.mesecons),
|
||||
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",
|
||||
suffix = "_cross",
|
||||
},
|
||||
mesecons = make_mesecons(base_name, "_cross", def.mesecons),
|
||||
}))
|
||||
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 +418,73 @@ 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",
|
||||
}
|
||||
local function convert_legacy_curvy_rails(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
|
||||
for old,new in pairs(CURVY_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, legacy = 1 },
|
||||
tiles = { new_def.tiles[1], new_def.tiles[1], new_def.tiles[1], new_def.tiles[1] },
|
||||
_vl_legacy_convert_node = convert_legacy_curvy_rails
|
||||
})
|
||||
vl_legacy.register_item_conversion(old, new)
|
||||
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",
|
||||
}
|
||||
local function convert_legacy_straight_rail(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
|
||||
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, legacy = 1 },
|
||||
tiles = { new_def.tiles[1], new_def.tiles[1], new_def.tiles[1], new_def.tiles[1] },
|
||||
_vl_legacy_convert_node = convert_legacy_straight_rail,
|
||||
})
|
||||
vl_legacy.register_item_conversion(old, new)
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
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_curves_rail("mcl_minecarts:activator_rail_v2", {
|
||||
"mcl_minecarts_rail_activator.png",
|
||||
"mcl_minecarts_rail_activator_curved.png",
|
||||
"mcl_minecarts_rail_activator_t_junction.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 = 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,
|
||||
},
|
||||
},
|
||||
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_curves_rail("mcl_minecarts:activator_rail_v2_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_t_junction_powered.png",
|
||||
"mcl_minecarts_rail_activator_crossing_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,
|
||||
},
|
||||
},
|
||||
_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,70 @@
|
|||
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_curves_rail("mcl_minecarts:detector_rail_v2",{
|
||||
"mcl_minecarts_rail_detector.png",
|
||||
"mcl_minecarts_rail_detector_curved.png",
|
||||
"mcl_minecarts_rail_detector_t_junction.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 = 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 node_def = minetest.registered_nodes[node.name]
|
||||
node.name = "mcl_minecarts:detector_rail_v2_on"..node_def._mcl_minecarts.suffix
|
||||
minetest.set_node( pos, node )
|
||||
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_curves_rail("mcl_minecarts:detector_rail_v2_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_t_junction_powered.png",
|
||||
"mcl_minecarts_rail_detector_crossing_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 node_def = minetest.registered_nodes[node.name]
|
||||
node.name = "mcl_minecarts:detector_rail_v2"..node_def._mcl_minecarts.suffix
|
||||
minetest.set_node( pos, node )
|
||||
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,83 @@
|
|||
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_curves_rail("mcl_minecarts:golden_rail_v2",{
|
||||
"mcl_minecarts_rail_golden.png",
|
||||
"mcl_minecarts_rail_golden_curved.png",
|
||||
"mcl_minecarts_rail_golden_t_junction.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 = 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,
|
||||
},
|
||||
},
|
||||
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_curves_rail("mcl_minecarts:golden_rail_v2_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_t_junction_powered.png",
|
||||
"mcl_minecarts_rail_golden_crossing_powered.png",
|
||||
},{
|
||||
_doc_items_create_entry = false,
|
||||
_rail_acceleration = function(pos, staticdata)
|
||||
if staticdata.velocity ~= 0 then
|
||||
return 4
|
||||
end
|
||||
|
||||
local dir = mod.get_rail_direction(pos, staticdata.dir, nil, nil, staticdata.railtype)
|
||||
local node_a = minetest.get_node(vector.add(pos, dir))
|
||||
local node_b = minetest.get_node(vector.add(pos, -dir))
|
||||
local has_adjacent_solid = minetest.get_item_group(node_a.name, "solid") ~= 0 or
|
||||
minetest.get_item_group(node_b.name, "solid") ~= 0 or
|
||||
minetest.get_item_group(node_a.name, "stair") ~= 0 or
|
||||
minetest.get_item_group(node_b.name, "stair") ~= 0
|
||||
|
||||
if has_adjacent_solid then
|
||||
return 4
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end,
|
||||
_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,
|
||||
},
|
||||
},
|
||||
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,147 @@
|
|||
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
|
||||
function mod.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
|
||||
local train_cars = mod.train_cars
|
||||
|
||||
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)
|
||||
-- Only update from the back
|
||||
if staticdata.behind or not staticdata.ahead then return end
|
||||
|
||||
-- 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 = staticdata.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
|
||||
|
|
@ -5,6 +5,7 @@ local validate_vector = mcl_util.validate_vector
|
|||
local active_particlespawners = {}
|
||||
local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
|
||||
local DEFAULT_FALL_SPEED = -9.81*1.5
|
||||
local PI_THIRD = math.pi / 3 -- 60 degrees
|
||||
|
||||
local PATHFINDING = "gowp"
|
||||
|
||||
|
@ -294,86 +295,66 @@ function mcl_mobs:set_animation(self, anim)
|
|||
self:set_animation(anim)
|
||||
end
|
||||
|
||||
local function dir_to_pitch(dir)
|
||||
--local dir2 = vector.normalize(dir)
|
||||
local xz = math.abs(dir.x) + math.abs(dir.z)
|
||||
return -math.atan2(-dir.y, xz)
|
||||
end
|
||||
|
||||
local function who_are_you_looking_at (self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
if self.order == "sleep" then
|
||||
self._locked_object = nil
|
||||
return
|
||||
end
|
||||
|
||||
local stop_look_at_player_chance = math.random(833/self.curiosity)
|
||||
-- was 10000 - div by 12 for avg entities as outside loop
|
||||
|
||||
local stop_look_at_player = stop_look_at_player_chance == 1
|
||||
local stop_look_at_player = math.random() * 833 <= self.curiosity
|
||||
|
||||
if self.attack then
|
||||
if not self.target_time_lost then
|
||||
self._locked_object = self.attack
|
||||
else
|
||||
self._locked_object = nil
|
||||
end
|
||||
self._locked_object = not self.target_time_lost and self.attack or nil
|
||||
elseif self.following then
|
||||
self._locked_object = self.following
|
||||
elseif self._locked_object then
|
||||
if stop_look_at_player then
|
||||
--minetest.log("Stop look: ".. self.name)
|
||||
self._locked_object = nil
|
||||
end
|
||||
if stop_look_at_player then self._locked_object = nil end
|
||||
elseif not self._locked_object then
|
||||
if mcl_util.check_dtime_timer(self, dtime, "step_look_for_someone", 0.2) then
|
||||
--minetest.log("Change look check: ".. self.name)
|
||||
|
||||
-- For the wither this was 20/60=0.33, so probably need to rebalance and divide rates.
|
||||
-- but frequency of check isn't good as it is costly. Making others too infrequent requires testing
|
||||
local chance = 150/self.curiosity
|
||||
|
||||
if chance < 1 then chance = 1 end
|
||||
local look_at_player_chance = math.random(chance)
|
||||
|
||||
-- was 5000 but called in loop based on entities. so div by 12 as estimate avg of entities found,
|
||||
-- then div by 20 as less freq lookup
|
||||
|
||||
local look_at_player = look_at_player_chance == 1
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 8)) do
|
||||
if obj:is_player() and vector.distance(pos,obj:get_pos()) < 4 then
|
||||
--minetest.log("Change look to player: ".. self.name)
|
||||
if obj:is_player() and vector.distance(pos, obj:get_pos()) < 4 then
|
||||
self._locked_object = obj
|
||||
break
|
||||
elseif obj:is_player() or (obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity()) then
|
||||
if look_at_player then
|
||||
--minetest.log("Change look to mob: ".. self.name)
|
||||
elseif obj:is_player() or (obj:get_luaentity() and self ~= obj:get_luaentity() and obj:get_luaentity().name == self.name) then
|
||||
-- For the wither this was 20/60=0.33, so probably need to rebalance and divide rates.
|
||||
-- but frequency of check isn't good as it is costly. Making others too infrequent requires testing
|
||||
-- was 5000 but called in loop based on entities. so div by 12 as estimate avg of entities found,
|
||||
-- then div by 20 as less freq lookup
|
||||
if math.random() * 150 <= self.curiosity then
|
||||
self._locked_object = obj
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
function mob_class:check_head_swivel(dtime)
|
||||
if not self.head_swivel or type(self.head_swivel) ~= "string" then return end
|
||||
|
||||
who_are_you_looking_at(self, dtime)
|
||||
|
||||
who_are_you_looking_at (self, dtime)
|
||||
local newr, oldp, oldr = vector.zero(), nil, nil
|
||||
if self.object.get_bone_override then -- minetest >= 5.9
|
||||
local ov = self.object:get_bone_override(self.head_swivel)
|
||||
oldp, oldr = ov.position.vec, ov.rotation.vec
|
||||
else -- minetest < 5.9
|
||||
oldp, oldr = self.object:get_bone_position(self.head_swivel)
|
||||
oldr = vector.apply(oldr, math.rad) -- old API uses radians
|
||||
end
|
||||
|
||||
local final_rotation = vector.zero()
|
||||
local oldp,oldr = self.object:get_bone_position(self.head_swivel)
|
||||
|
||||
if self._locked_object and (self._locked_object:is_player() or self._locked_object:get_luaentity()) and self._locked_object:get_hp() > 0 then
|
||||
local locked_object = self._locked_object
|
||||
if locked_object and (locked_object:is_player() or locked_object:get_luaentity()) and locked_object:get_hp() > 0 then
|
||||
local _locked_object_eye_height = 1.5
|
||||
if self._locked_object:get_luaentity() then
|
||||
_locked_object_eye_height = self._locked_object:get_luaentity().head_eye_height
|
||||
end
|
||||
if self._locked_object:is_player() then
|
||||
_locked_object_eye_height = self._locked_object:get_properties().eye_height
|
||||
if locked_object:is_player() then
|
||||
_locked_object_eye_height = locked_object:get_properties().eye_height
|
||||
elseif locked_object:get_luaentity() then
|
||||
_locked_object_eye_height = locked_object:get_luaentity().head_eye_height
|
||||
end
|
||||
if _locked_object_eye_height then
|
||||
|
||||
local self_rot = self.object:get_rotation()
|
||||
-- If a mob is attached, should we really be messing with what they are looking at?
|
||||
-- Should this be excluded?
|
||||
|
@ -381,40 +362,48 @@ function mob_class:check_head_swivel(dtime)
|
|||
self_rot = self.object:get_attach():get_rotation()
|
||||
end
|
||||
|
||||
local player_pos = self._locked_object:get_pos()
|
||||
local direction_player = vector.direction(vector.add(self.object:get_pos(), vector.new(0, self.head_eye_height*.7, 0)), vector.add(player_pos, vector.new(0, _locked_object_eye_height, 0)))
|
||||
local mob_yaw = math.deg(-(-(self_rot.y)-(-minetest.dir_to_yaw(direction_player))))+self.head_yaw_offset
|
||||
local mob_pitch = math.deg(-dir_to_pitch(direction_player))*self.head_pitch_multiplier
|
||||
local ps = self.object:get_pos()
|
||||
ps.y = ps.y + self.head_eye_height * .7
|
||||
local pt = locked_object:get_pos()
|
||||
pt.y = pt.y + _locked_object_eye_height
|
||||
local dir = vector.direction(ps, pt)
|
||||
local mob_yaw = self_rot.y + math.atan2(dir.x, dir.z) + self.head_yaw_offset
|
||||
local mob_pitch = math.asin(-dir.y) * self.head_pitch_multiplier
|
||||
|
||||
if (mob_yaw < -60 or mob_yaw > 60) and not (self.attack and self.state == "attack" and not self.runaway) then
|
||||
final_rotation = vector.multiply(oldr, 0.9)
|
||||
if (mob_yaw < -PI_THIRD or mob_yaw > PI_THIRD) and not (self.attack and self.state == "attack" and not self.runaway) then
|
||||
newr = vector.multiply(oldr, 0.9)
|
||||
elseif self.attack and self.state == "attack" and not self.runaway then
|
||||
if self.head_yaw == "y" then
|
||||
final_rotation = vector.new(mob_pitch, mob_yaw, 0)
|
||||
newr = vector.new(mob_pitch, mob_yaw, 0)
|
||||
elseif self.head_yaw == "z" then
|
||||
final_rotation = vector.new(mob_pitch, 0, -mob_yaw)
|
||||
newr = vector.new(mob_pitch, 0, -mob_yaw)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
if self.head_yaw == "y" then
|
||||
final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, ((mob_yaw-oldr.y)*.3)+oldr.y, 0)
|
||||
newr = vector.new((mob_pitch-oldr.x)*.3+oldr.x, (mob_yaw-oldr.y)*.3+oldr.y, 0)
|
||||
elseif self.head_yaw == "z" then
|
||||
final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, 0, -(((mob_yaw-oldr.y)*.3)+oldr.y)*3)
|
||||
newr = vector.new((mob_pitch-oldr.x)*.3+oldr.x, 0, ((mob_yaw-oldr.y)*.3+oldr.y)*-3)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif not self._locked_object and math.abs(oldr.y) > 3 and math.abs(oldr.x) < 3 then
|
||||
final_rotation = vector.multiply(oldr, 0.9)
|
||||
else
|
||||
--final_rotation = vector.new(0,0,0)
|
||||
elseif not locked_object and math.abs(oldr.y) > 0.05 and math.abs(oldr.x) < 0.05 then
|
||||
newr = vector.multiply(oldr, 0.9)
|
||||
end
|
||||
|
||||
-- 0.02 is about 1.14 degrees tolerance, to update less often
|
||||
local newp = vector.new(0, self.bone_eye_height, self.horizontal_head_height)
|
||||
if math.abs(oldr.x-newr.x) + math.abs(oldr.y-newr.y) + math.abs(oldr.z-newr.z) < 0.02 and vector.equals(oldp, newp) then return end
|
||||
if self.object.get_bone_override then -- minetest >= 5.9
|
||||
self.object:set_bone_override(self.head_swivel, {
|
||||
position = { vec = newp, absolute = true },
|
||||
rotation = { vec = newr, absolute = true } })
|
||||
else -- minetest < 5.9
|
||||
-- old API uses degrees not radians
|
||||
self.object:set_bone_position(self.head_swivel, newp, vector.apply(newr, math.deg))
|
||||
end
|
||||
|
||||
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horizontal_head_height), final_rotation)
|
||||
end
|
||||
|
||||
|
||||
|
||||
function mob_class:set_animation_speed()
|
||||
local v = self.object:get_velocity()
|
||||
if v then
|
||||
|
|
|
@ -141,7 +141,7 @@ function mcl_mobs.register_mob(name, def)
|
|||
local final_def = {
|
||||
use_texture_alpha = def.use_texture_alpha,
|
||||
head_swivel = def.head_swivel or nil, -- bool to activate this function
|
||||
head_yaw_offset = def.head_yaw_offset or 0, -- for wonkey model bones
|
||||
head_yaw_offset = math.rad(def.head_yaw_offset or 0), -- for wonkey model bones
|
||||
head_pitch_multiplier = def.head_pitch_multiplier or 1, --for inverted pitch
|
||||
bone_eye_height = def.bone_eye_height or 1.4, -- head bone offset
|
||||
head_eye_height = def.head_eye_height or def.bone_eye_height or 0, -- how hight aproximatly the mobs head is fromm the ground to tell the mob how high to look up at the player
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -257,10 +257,10 @@ function ARROW_ENTITY.on_step(self, dtime)
|
|||
mcl_burning.set_on_fire(obj, 5)
|
||||
end
|
||||
if not self._in_player and not self._blocked then
|
||||
obj:punch(self.object, 1.0, {
|
||||
full_punch_interval=1.0,
|
||||
damage_groups={fleshy=self._damage},
|
||||
}, self.object:get_velocity())
|
||||
mcl_util.deal_damage(obj, self._damage, {type = "arrow", source = self._shooter, direct = self.object})
|
||||
if self._extra_hit_func then
|
||||
self._extra_hit_func(obj)
|
||||
end
|
||||
if obj:is_player() then
|
||||
if not mcl_shields.is_blocking(obj) then
|
||||
local placement
|
||||
|
|
|
@ -168,7 +168,7 @@ S("The speed and damage of the arrow increases the longer you charge. The regula
|
|||
itemstack:get_meta():set_string("active", "true")
|
||||
return itemstack
|
||||
end,
|
||||
groups = {weapon=1,weapon_ranged=1,bow=1,enchantability=1},
|
||||
groups = {weapon=1,weapon_ranged=1,bow=1,cannot_block=1,enchantability=1},
|
||||
_mcl_uses = 385,
|
||||
})
|
||||
|
||||
|
@ -216,7 +216,7 @@ for level=0, 2 do
|
|||
wield_scale = mcl_vars.tool_wield_scale,
|
||||
stack_max = 1,
|
||||
range = 0, -- Pointing range to 0 to prevent punching with bow :D
|
||||
groups = {not_in_creative_inventory=1, not_in_craft_guide=1, bow=1, enchantability=1},
|
||||
groups = {not_in_creative_inventory=1, not_in_craft_guide=1, bow=1, cannot_block=1, enchantability=1},
|
||||
-- Trick to disable digging as well
|
||||
on_use = function() return end,
|
||||
on_drop = function(itemstack, dropper, pos)
|
||||
|
|
|
@ -158,7 +158,7 @@ S("The speed and damage of the arrow increases the longer you charge. The regula
|
|||
itemstack:get_meta():set_string("active", "true")
|
||||
return itemstack
|
||||
end,
|
||||
groups = {weapon=1,weapon_ranged=1,crossbow=1,enchantability=1},
|
||||
groups = {weapon=1,weapon_ranged=1,crossbow=1,cannot_block=1,enchantability=1},
|
||||
_mcl_uses = 326,
|
||||
})
|
||||
|
||||
|
@ -193,7 +193,7 @@ S("The speed and damage of the arrow increases the longer you charge. The regula
|
|||
itemstack:get_meta():set_string("active", "true")
|
||||
return itemstack
|
||||
end,
|
||||
groups = {weapon=1,weapon_ranged=1,crossbow=1,enchantability=1,not_in_creative_inventory=1},
|
||||
groups = {weapon=1,weapon_ranged=1,crossbow=1,cannot_block=1,enchantability=1,not_in_creative_inventory=1},
|
||||
_mcl_uses = 326,
|
||||
})
|
||||
|
||||
|
@ -238,7 +238,7 @@ for level=0, 2 do
|
|||
wield_scale = mcl_vars.tool_wield_scale,
|
||||
stack_max = 1,
|
||||
range = 0, -- Pointing range to 0 to prevent punching with bow :D
|
||||
groups = {not_in_creative_inventory=1, not_in_craft_guide=1, bow=1, enchantability=1},
|
||||
groups = {not_in_creative_inventory=1, not_in_craft_guide=1, cannot_block=1, bow=1, enchantability=1},
|
||||
-- Trick to disable digging as well
|
||||
on_use = function() return end,
|
||||
on_drop = function(itemstack, dropper, pos)
|
||||
|
|
|
@ -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", {
|
||||
|
@ -135,4 +140,4 @@ minetest.register_node("mcl_core:reeds", {
|
|||
end,
|
||||
_mcl_blast_resistance = 0,
|
||||
_mcl_hardness = 0,
|
||||
})
|
||||
})
|
||||
|
|
|
@ -171,7 +171,8 @@ minetest.register_craft({
|
|||
},
|
||||
})
|
||||
|
||||
mcl_farming:add_plant("plant_beetroot", "mcl_farming:beetroot", {"mcl_farming:beetroot_0", "mcl_farming:beetroot_1", "mcl_farming:beetroot_2"}, 68, 3)
|
||||
-- beetroots grow at 2/3rd of the default speed
|
||||
mcl_farming:add_plant("plant_beetroot", "mcl_farming:beetroot", {"mcl_farming:beetroot_0", "mcl_farming:beetroot_1", "mcl_farming:beetroot_2"}, 8.7012, 35)
|
||||
|
||||
if minetest.get_modpath("doc") then
|
||||
for i = 1, 2 do
|
||||
|
|
|
@ -118,7 +118,7 @@ minetest.register_craft({
|
|||
}
|
||||
})
|
||||
|
||||
mcl_farming:add_plant("plant_carrot", "mcl_farming:carrot", {"mcl_farming:carrot_1", "mcl_farming:carrot_2", "mcl_farming:carrot_3", "mcl_farming:carrot_4", "mcl_farming:carrot_5", "mcl_farming:carrot_6", "mcl_farming:carrot_7"}, 25, 20)
|
||||
mcl_farming:add_plant("plant_carrot", "mcl_farming:carrot", {"mcl_farming:carrot_1", "mcl_farming:carrot_2", "mcl_farming:carrot_3", "mcl_farming:carrot_4", "mcl_farming:carrot_5", "mcl_farming:carrot_6", "mcl_farming:carrot_7"}, 5.8013, 35)
|
||||
|
||||
if minetest.get_modpath("doc") then
|
||||
for i=2,7 do
|
||||
|
|
|
@ -123,10 +123,10 @@ local stem_def = {
|
|||
}
|
||||
|
||||
-- Register stem growth
|
||||
mcl_farming:add_plant("plant_melon_stem", "mcl_farming:melontige_unconnect", {"mcl_farming:melontige_1", "mcl_farming:melontige_2", "mcl_farming:melontige_3", "mcl_farming:melontige_4", "mcl_farming:melontige_5", "mcl_farming:melontige_6", "mcl_farming:melontige_7"}, 30, 5)
|
||||
mcl_farming:add_plant("plant_melon_stem", "mcl_farming:melontige_unconnect", {"mcl_farming:melontige_1", "mcl_farming:melontige_2", "mcl_farming:melontige_3", "mcl_farming:melontige_4", "mcl_farming:melontige_5", "mcl_farming:melontige_6", "mcl_farming:melontige_7"}, 5.8014, 35)
|
||||
|
||||
-- Register actual melon, connected stems and stem-to-melon growth
|
||||
mcl_farming:add_gourd("mcl_farming:melontige_unconnect", "mcl_farming:melontige_linked", "mcl_farming:melontige_unconnect", stem_def, stem_drop, "mcl_farming:melon", melon_base_def, 25, 15, "mcl_farming_melon_stem_connected.png^[colorize:#FFA800:127")
|
||||
mcl_farming:add_gourd("mcl_farming:melontige_unconnect", "mcl_farming:melontige_linked", "mcl_farming:melontige_unconnect", stem_def, stem_drop, "mcl_farming:melon", melon_base_def, 5.8015, 35, "mcl_farming_melon_stem_connected.png^[colorize:#FFA800:127")
|
||||
|
||||
-- Items and crafting
|
||||
minetest.register_craftitem("mcl_farming:melon_item", {
|
||||
|
|
|
@ -135,7 +135,7 @@ minetest.register_craft({
|
|||
cooktime = 10,
|
||||
})
|
||||
|
||||
mcl_farming:add_plant("plant_potato", "mcl_farming:potato", {"mcl_farming:potato_1", "mcl_farming:potato_2", "mcl_farming:potato_3", "mcl_farming:potato_4", "mcl_farming:potato_5", "mcl_farming:potato_6", "mcl_farming:potato_7"}, 19.75, 20)
|
||||
mcl_farming:add_plant("plant_potato", "mcl_farming:potato", {"mcl_farming:potato_1", "mcl_farming:potato_2", "mcl_farming:potato_3", "mcl_farming:potato_4", "mcl_farming:potato_5", "mcl_farming:potato_6", "mcl_farming:potato_7"}, 5.8016, 35)
|
||||
|
||||
minetest.register_on_item_eat(function (hp_change, replace_with_item, itemstack, user, pointed_thing)
|
||||
|
||||
|
|
|
@ -180,10 +180,10 @@ if minetest.get_modpath("mcl_armor") then
|
|||
end
|
||||
|
||||
-- Register stem growth
|
||||
mcl_farming:add_plant("plant_pumpkin_stem", "mcl_farming:pumpkintige_unconnect", {"mcl_farming:pumpkin_1", "mcl_farming:pumpkin_2", "mcl_farming:pumpkin_3", "mcl_farming:pumpkin_4", "mcl_farming:pumpkin_5", "mcl_farming:pumpkin_6", "mcl_farming:pumpkin_7"}, 30, 5)
|
||||
mcl_farming:add_plant("plant_pumpkin_stem", "mcl_farming:pumpkintige_unconnect", {"mcl_farming:pumpkin_1", "mcl_farming:pumpkin_2", "mcl_farming:pumpkin_3", "mcl_farming:pumpkin_4", "mcl_farming:pumpkin_5", "mcl_farming:pumpkin_6", "mcl_farming:pumpkin_7"}, 5.8017, 35)
|
||||
|
||||
-- Register actual pumpkin, connected stems and stem-to-pumpkin growth
|
||||
mcl_farming:add_gourd("mcl_farming:pumpkintige_unconnect", "mcl_farming:pumpkintige_linked", "mcl_farming:pumpkintige_unconnect", stem_def, stem_drop, "mcl_farming:pumpkin", pumpkin_base_def, 30, 15, "mcl_farming_pumpkin_stem_connected.png^[colorize:#FFA800:127")
|
||||
mcl_farming:add_gourd("mcl_farming:pumpkintige_unconnect", "mcl_farming:pumpkintige_linked", "mcl_farming:pumpkintige_unconnect", stem_def, stem_drop, "mcl_farming:pumpkin", pumpkin_base_def, 5.8018, 35, "mcl_farming_pumpkin_stem_connected.png^[colorize:#FFA800:127")
|
||||
|
||||
-- Steal function to properly disconnect a carved pumpkin
|
||||
pumpkin_face_base_def.after_destruct = minetest.registered_nodes["mcl_farming:pumpkin"].after_destruct
|
||||
|
|
|
@ -5,73 +5,99 @@
|
|||
--
|
||||
local math = math
|
||||
local vector = vector
|
||||
local random = math.random
|
||||
local floor = math.floor
|
||||
|
||||
local plant_lists = {}
|
||||
mcl_farming.plant_lists = plant_lists -- export
|
||||
local plant_nodename_to_id_list = {}
|
||||
local plant_nodename_to_id = {} -- map nodes to plants
|
||||
local plant_step_from_name = {} -- map nodes to growth steps
|
||||
|
||||
local time_speed = tonumber(minetest.settings:get("time_speed")) or 72
|
||||
local time_multiplier = time_speed > 0 and (86400 / time_speed) or 0
|
||||
local growth_factor = tonumber(minetest.settings:get("vl_plant_growth")) or 1.0
|
||||
|
||||
local function get_intervals_counter(pos, interval, chance)
|
||||
if time_multiplier == 0 then return 0 end
|
||||
-- "wall clock time", so plants continue to grow while sleeping
|
||||
local current_game_time = (minetest.get_day_count() + minetest.get_timeofday()) * time_multiplier
|
||||
local approx_interval = math.max(interval, 1) * math.max(chance, 1)
|
||||
|
||||
local meta = minetest.get_meta(pos)
|
||||
local last_game_time = meta:get_float("last_gametime")
|
||||
if last_game_time < 1 then
|
||||
last_game_time = current_game_time - approx_interval * 0.5
|
||||
elseif last_game_time == current_game_time then
|
||||
current_game_time = current_game_time + approx_interval
|
||||
-- wetness of the surroundings
|
||||
-- dry farmland = 1 point
|
||||
-- wet farmland = 3 points
|
||||
-- center point gives + 1 point, so 2 resp. 4
|
||||
-- neighbors only 25%
|
||||
local function get_moisture_level(pos)
|
||||
local n = vector.offset(pos, 0, -1, 0)
|
||||
local totalm = 1
|
||||
for z = -1,1 do
|
||||
n.z = pos.z + z
|
||||
for x = -1,1 do
|
||||
n.x = pos.x + x
|
||||
local ndef = minetest.registered_nodes[minetest.get_node(n).name]
|
||||
local soil = ndef and ndef.groups.soil
|
||||
if soil and soil >= 2 then
|
||||
local m = soil > 2 and 3 or 1
|
||||
-- corners have less weight
|
||||
if x ~= 0 or z ~= 0 then m = m * 0.25 end
|
||||
totalm = totalm + m
|
||||
end
|
||||
end
|
||||
end
|
||||
meta:set_float("last_gametime", current_game_time)
|
||||
return (current_game_time - last_game_time) / approx_interval
|
||||
return totalm
|
||||
end
|
||||
|
||||
local function get_avg_light_level(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
-- EWMA would use a single variable:
|
||||
-- local avg = meta:get_float("avg_light")
|
||||
-- avg = avg + (node_light - avg) * 0.985
|
||||
-- meta.set_float("avg_light", avg)
|
||||
local summary = meta:get_int("avg_light_summary")
|
||||
local counter = meta:get_int("avg_light_count")
|
||||
if counter > 99 then
|
||||
summary, counter = math.ceil(summary * 0.5), 50
|
||||
-- moisture penalty function:
|
||||
-- 0.5 if both on the x axis and the z axis at least one of the same plants grows
|
||||
-- 0.5 if at least one diagonal neighbor is the same
|
||||
-- 1.0 otherwise
|
||||
-- we cannot use the names directly, because growth is encoded in the names
|
||||
local function get_same_crop_penalty(pos)
|
||||
local name = minetest.get_node(pos).name
|
||||
local plant = plant_nodename_to_id[name]
|
||||
if not plant then return 1 end
|
||||
local n = vector.copy(pos)
|
||||
-- check adjacent positions, avoid vector allocations and reduce node accesses
|
||||
n.x = pos.x - 1
|
||||
local dx = plant_nodename_to_id[minetest.get_node(n).name] == plant
|
||||
n.x = pos.x + 1
|
||||
dx = dx or plant_nodename_to_id[minetest.get_node(n).name] == plant
|
||||
if dx then -- no need to check z otherwise
|
||||
n.x = pos.x
|
||||
n.z = pos.z - 1
|
||||
local dz = plant_nodename_to_id[minetest.get_node(n).name] == plant
|
||||
n.z = pos.z + 1
|
||||
dz = dz or plant_nodename_to_id[minetest.get_node(n).name] == plant
|
||||
if dz then return 0.5 end
|
||||
end
|
||||
local node_light = minetest.get_node_light(pos)
|
||||
if node_light ~= nil then
|
||||
summary, counter = summary + node_light, counter + 1
|
||||
meta:set_int("avg_light_summary", summary)
|
||||
meta:set_int("avg_light_count", counter)
|
||||
end
|
||||
return math.ceil(summary / counter)
|
||||
-- check diagonals, clockwise
|
||||
n.x, n.z = pos.x - 1, pos.z - 1
|
||||
if plant_nodename_to_id[minetest.get_node(n).name] == plant then return 0.5 end
|
||||
n.x = pos.x + 1
|
||||
if plant_nodename_to_id[minetest.get_node(n).name] == plant then return 0.5 end
|
||||
n.z = pos.z + 1
|
||||
if plant_nodename_to_id[minetest.get_node(n).name] == plant then return 0.5 end
|
||||
n.x = pos.x - 1
|
||||
if plant_nodename_to_id[minetest.get_node(n).name] == plant then return 0.5 end
|
||||
return 1
|
||||
end
|
||||
|
||||
function mcl_farming:add_plant(identifier, full_grown, names, interval, chance)
|
||||
interval = growth_factor > 0 and (interval / growth_factor) or 0
|
||||
local plant_info = {}
|
||||
plant_info.full_grown = full_grown
|
||||
plant_info.names = names
|
||||
plant_info.interval = interval
|
||||
plant_info.chance = chance
|
||||
plant_nodename_to_id[full_grown] = identifier
|
||||
for _, nodename in pairs(names) do
|
||||
plant_nodename_to_id_list[nodename] = identifier
|
||||
plant_nodename_to_id[nodename] = identifier
|
||||
end
|
||||
plant_info.step_from_name = {}
|
||||
for i, name in ipairs(names) do
|
||||
plant_info.step_from_name[name] = i
|
||||
plant_step_from_name[name] = i
|
||||
end
|
||||
plant_lists[identifier] = plant_info
|
||||
if interval == 0 then return end -- growth disabled
|
||||
minetest.register_abm({
|
||||
label = string.format("Farming plant growth (%s)", identifier),
|
||||
nodenames = names,
|
||||
interval = interval,
|
||||
chance = chance,
|
||||
action = function(pos, node)
|
||||
local low_speed = minetest.get_node(vector.offset(pos, 0, -1, 0)).name ~= "mcl_farming:soil_wet"
|
||||
mcl_farming:grow_plant(identifier, pos, node, 1, false, low_speed)
|
||||
mcl_farming:grow_plant(identifier, pos, node, 1, false)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
@ -81,40 +107,30 @@ end
|
|||
-- pos: Position
|
||||
-- node: Node table
|
||||
-- stages: Number of stages to advance (optional, defaults to 1)
|
||||
-- ignore_light: if true, ignore light requirements for growing
|
||||
-- low_speed: grow more slowly (not wet), default false
|
||||
-- ignore_light_water: if true, ignore light and water requirements for growing
|
||||
-- Returns true if plant has been grown by 1 or more stages.
|
||||
-- Returns false if nothing changed.
|
||||
function mcl_farming:grow_plant(identifier, pos, node, stages, ignore_light, low_speed)
|
||||
stages = stages or 1
|
||||
function mcl_farming:grow_plant(identifier, pos, node, stages, ignore_light_water)
|
||||
-- number of missed interval ticks, for catch-up in block loading
|
||||
local plant_info = plant_lists[identifier]
|
||||
local intervals_counter = get_intervals_counter(pos, plant_info.interval, plant_info.chance)
|
||||
if stages > 0 then intervals_counters = intervals_counter - 1 end
|
||||
if low_speed then -- 10% speed approximately
|
||||
if intervals_counter < 1.01 and math.random(0, 9) > 0 then return false end
|
||||
intervals_counter = intervals_counter / 10
|
||||
end
|
||||
if not ignore_light and intervals_counter < 1.5 then
|
||||
local light = minetest.get_node_light(pos)
|
||||
if not light or light < 10 then return false end
|
||||
end
|
||||
|
||||
if intervals_counter >= 1.5 then
|
||||
local average_light_level = get_avg_light_level(pos)
|
||||
if average_light_level < 0.1 then return false end
|
||||
if average_light_level < 10 then
|
||||
intervals_counter = intervals_counter * average_light_level / 10
|
||||
if not plant_info then return end
|
||||
if not ignore_light_water then
|
||||
if (minetest.get_node_light(pos, 0.5) or 0) < 0 then return false end -- day light
|
||||
local odds = floor(25 / (get_moisture_level(pos) * get_same_crop_penalty(pos))) + 1
|
||||
for i = 1,stages do
|
||||
-- compared to info from the MC wiki, our ABM runs a third as often, hence we use triple the chance
|
||||
if random() * odds >= 3 then stages = stages - 1 end
|
||||
end
|
||||
end
|
||||
|
||||
local step = plant_info.step_from_name[node.name]
|
||||
if step == nil then return false end
|
||||
stages = stages + math.floor(intervals_counter)
|
||||
if stages == 0 then return false end
|
||||
local new_node = { name = plant_info.names[step + stages] or plant_info.full_grown }
|
||||
new_node.param = node.param
|
||||
new_node.param2 = node.param2
|
||||
minetest.set_node(pos, new_node)
|
||||
local step = plant_step_from_name[node.name]
|
||||
if step == nil then return false end
|
||||
minetest.set_node(pos, {
|
||||
name = plant_info.names[step + stages] or plant_info.full_grown,
|
||||
param = node.param,
|
||||
param2 = node.param2,
|
||||
})
|
||||
return true
|
||||
end
|
||||
|
||||
|
@ -157,40 +173,13 @@ end
|
|||
|
||||
|
||||
function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, stem_itemstring, stem_def, stem_drop, gourd_itemstring, gourd_def, grow_interval, grow_chance, connected_stem_texture)
|
||||
grow_interval = growth_factor > 0 and (grow_interval / growth_factor) or 0
|
||||
local connected_stem_names = {
|
||||
connected_stem_basename .. "_r",
|
||||
connected_stem_basename .. "_l",
|
||||
connected_stem_basename .. "_t",
|
||||
connected_stem_basename .. "_b" }
|
||||
|
||||
-- Connect the stem at stempos to the first neighboring gourd block.
|
||||
-- No-op if not a stem or no gourd block found
|
||||
local function try_connect_stem(stempos)
|
||||
local stem = minetest.get_node(stempos)
|
||||
if stem.name ~= full_unconnected_stem then return false end
|
||||
-- four directions, but avoid table allocation
|
||||
local neighbor = vector.offset(stempos, 1, 0, 0)
|
||||
if minetest.get_node(neighbor).name == gourd_itemstring then
|
||||
minetest.swap_node(stempos, { name = connected_stem_names[1] })
|
||||
return true
|
||||
end
|
||||
local neighbor = vector.offset(stempos, -1, 0, 0)
|
||||
if minetest.get_node(neighbor).name == gourd_itemstring then
|
||||
minetest.swap_node(stempos, { name = connected_stem_names[2] })
|
||||
return true
|
||||
end
|
||||
local neighbor = vector.offset(stempos, 0, 0, 1)
|
||||
if minetest.get_node(neighbor).name == gourd_itemstring then
|
||||
minetest.swap_node(stempos, { name = connected_stem_names[3] })
|
||||
return true
|
||||
end
|
||||
local neighbor = vector.offset(stempos, 0, 0, -1)
|
||||
if minetest.get_node(neighbor).name == gourd_itemstring then
|
||||
minetest.swap_node(stempos, { name = connected_stem_names[4] })
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Register gourd
|
||||
if not gourd_def.after_destruct then
|
||||
gourd_def.after_destruct = function(blockpos, oldnode)
|
||||
|
@ -200,34 +189,21 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
|
|||
local stempos = vector.offset(blockpos, -1, 0, 0)
|
||||
if minetest.get_node(stempos).name == connected_stem_names[1] then
|
||||
minetest.swap_node(stempos, { name = full_unconnected_stem })
|
||||
try_connect_stem(stempos)
|
||||
end
|
||||
local stempos = vector.offset(blockpos, 1, 0, 0)
|
||||
if minetest.get_node(stempos).name == connected_stem_names[2] then
|
||||
minetest.swap_node(stempos, { name = full_unconnected_stem })
|
||||
try_connect_stem(stempos)
|
||||
end
|
||||
local stempos = vector.offset(blockpos, 0, 0, -1)
|
||||
if minetest.get_node(stempos).name == connected_stem_names[3] then
|
||||
minetest.swap_node(stempos, { name = full_unconnected_stem })
|
||||
try_connect_stem(stempos)
|
||||
end
|
||||
local stempos = vector.offset(blockpos, 0, 0, 1)
|
||||
if minetest.get_node(stempos).name == connected_stem_names[4] then
|
||||
minetest.swap_node(stempos, { name = full_unconnected_stem })
|
||||
try_connect_stem(stempos)
|
||||
end
|
||||
end
|
||||
end
|
||||
if not gourd_def.on_construct then
|
||||
function gourd_def.on_construct(blockpos)
|
||||
-- Connect all unconnected stems at full size
|
||||
try_connect_stem(vector.offset(blockpos, 1, 0, 0))
|
||||
try_connect_stem(vector.offset(blockpos, -1, 0, 0))
|
||||
try_connect_stem(vector.offset(blockpos, 0, 0, 1))
|
||||
try_connect_stem(vector.offset(blockpos, 0, 0, -1))
|
||||
end
|
||||
end
|
||||
minetest.register_node(gourd_itemstring, gourd_def)
|
||||
|
||||
-- Register unconnected stem
|
||||
|
@ -243,8 +219,8 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
|
|||
stem_def.drop = stem_def.drop or stem_drop
|
||||
stem_def.groups = stem_def.groups or { dig_immediate = 3, not_in_creative_inventory = 1, plant = 1, attached_node = 1, dig_by_water = 1, destroy_by_lava_flow = 1 }
|
||||
stem_def.sounds = stem_def.sounds or mcl_sounds.node_sound_leaves_defaults()
|
||||
stem_def.on_construct = stem_def.on_construct or try_connect_stem
|
||||
minetest.register_node(stem_itemstring, stem_def)
|
||||
plant_nodename_to_id[stem_itemstring] = stem_itemstring
|
||||
|
||||
-- Register connected stems
|
||||
|
||||
|
@ -307,22 +283,14 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
|
|||
sounds = mcl_sounds.node_sound_leaves_defaults(),
|
||||
_mcl_blast_resistance = 0,
|
||||
})
|
||||
plant_nodename_to_id[connected_stem_names[i]] = stem_itemstring
|
||||
|
||||
if minetest.get_modpath("doc") then
|
||||
doc.add_entry_alias("nodes", full_unconnected_stem, "nodes", connected_stem_names[i])
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for a suitable spot to grow
|
||||
local function check_neighbor_soil(blockpos)
|
||||
if minetest.get_node(blockpos).name ~= "air" then return false end
|
||||
local floorpos = vector.offset(blockpos, 0, -1, 0)
|
||||
local floorname = minetest.get_node(floorpos).name
|
||||
if floorname == "mcl_core:dirt" then return true end
|
||||
local floordef = minetest.registered_nodes[floorname]
|
||||
return floordef.groups.grass_block or floordef.groups.dirt or (floordef.groups.soil or 0) >= 2
|
||||
end
|
||||
|
||||
if grow_interval == 0 then return end
|
||||
minetest.register_abm({
|
||||
label = "Grow gourd stem to gourd (" .. full_unconnected_stem .. " → " .. gourd_itemstring .. ")",
|
||||
nodenames = { full_unconnected_stem },
|
||||
|
@ -330,39 +298,27 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
|
|||
interval = grow_interval,
|
||||
chance = grow_chance,
|
||||
action = function(stempos)
|
||||
local light = minetest.get_node_light(stempos)
|
||||
if not light or light <= 10 then return end
|
||||
-- Check the four neighbors and filter out neighbors where gourds can't grow
|
||||
local neighbor, dir, nchance = nil, -1, 1 -- reservoir sampling
|
||||
if nchance == 1 or math.random(1, nchance) == 1 then
|
||||
local blockpos = vector.offset(stempos, 1, 0, 0)
|
||||
if check_neighbor_soil(blockpos) then
|
||||
neighbor, dir, nchance = blockpos, 1, nchance + 1
|
||||
end
|
||||
end
|
||||
if nchance == 1 or math.random(1, nchance) == 1 then
|
||||
local blockpos = vector.offset(stempos, -1, 0, 0)
|
||||
if check_neighbor_soil(blockpos) then
|
||||
neighbor, dir, nchance = blockpos, 2, nchance + 1
|
||||
end
|
||||
end
|
||||
if nchance == 1 or math.random(1, nchance) == 1 then
|
||||
local blockpos = vector.offset(stempos, 0, 0, 1)
|
||||
if check_neighbor_soil(blockpos) then
|
||||
neighbor, dir, nchance = blockpos, 3, nchance + 1
|
||||
end
|
||||
end
|
||||
if nchance == 1 or math.random(1, nchance) == 1 then
|
||||
local blockpos = vector.offset(stempos, 0, 0, -1)
|
||||
if check_neighbor_soil(blockpos) then
|
||||
neighbor, dir, nchance = blockpos, 4, nchance + 1
|
||||
end
|
||||
end
|
||||
local light = minetest.get_node_light(stempos, 0.5)
|
||||
if not light or light < 9 then return end
|
||||
-- Pick one neighbor and check if it can be used to grow
|
||||
local dir = random(1, 4) -- pick direction at random
|
||||
local neighbor = (dir == 1 and vector.offset(stempos, 1, 0, 0))
|
||||
or (dir == 2 and vector.offset(stempos, -1, 0, 0))
|
||||
or (dir == 3 and vector.offset(stempos, 0, 0, 1))
|
||||
or vector.offset(stempos, 0, 0, -1)
|
||||
if minetest.get_node(neighbor).name ~= "air" then return end -- occupied
|
||||
-- check for suitable floor -- in contrast to MC, we think everything solid is fine
|
||||
local floorpos = vector.offset(neighbor, 0, -1, 0)
|
||||
local floorname = minetest.get_node(floorpos).name
|
||||
local floordef = minetest.registered_nodes[floorname]
|
||||
if not floordef or not floordef.walkable then return end
|
||||
|
||||
-- check moisture level
|
||||
local odds = floor(25 / (get_moisture_level(stempos) * get_same_crop_penalty(stempos))) + 1
|
||||
-- we triple the odds, and rather call the ABM less often
|
||||
if random() * odds >= 3 then return end
|
||||
|
||||
-- Gourd needs at least 1 free neighbor to grow
|
||||
if not neighbor then return end
|
||||
minetest.swap_node(stempos, { name = connected_stem_names[dir] })
|
||||
-- Place the gourd
|
||||
if gourd_def.paramtype2 == "facedir" then
|
||||
local p2 = (dir == 1 and 3) or (dir == 2 and 1) or (dir == 3 and 2) or 0
|
||||
minetest.add_node(neighbor, { name = gourd_itemstring, param2 = p2 })
|
||||
|
@ -371,8 +327,7 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
|
|||
end
|
||||
|
||||
-- Reset farmland, etc. to dirt when the gourd grows on top
|
||||
local floorpos = vector.offset(neighbor, 0, -1, 0)
|
||||
if minetest.get_item_group(minetest.get_node(floorpos).name, "dirtifies_below_solid") == 1 then
|
||||
if (floordef.groups.dirtifies_below_solid or 0) > 0 then
|
||||
minetest.set_node(floorpos, { name = "mcl_core:dirt" })
|
||||
end
|
||||
end,
|
||||
|
@ -409,10 +364,21 @@ minetest.register_lbm({
|
|||
nodenames = { "group:plant" },
|
||||
run_at_every_load = true,
|
||||
action = function(pos, node, dtime_s)
|
||||
local identifier = plant_nodename_to_id_list[node.name]
|
||||
local identifier = plant_nodename_to_id[node.name]
|
||||
if not identifier then return end
|
||||
local low_speed = minetest.get_node(vector.offset(pos, 0, -1, 0)).name ~= "mcl_farming:soil_wet"
|
||||
mcl_farming:grow_plant(identifier, pos, node, 0, false, low_speed)
|
||||
|
||||
local plant_info = plant_lists[identifier]
|
||||
if not plant_info then return end
|
||||
local rolls = floor(dtime_s / plant_info.interval)
|
||||
if rolls <= 0 then return end
|
||||
-- simulate how often the block will be ticked
|
||||
local stages = 0
|
||||
for i = 1,rolls do
|
||||
if random(1, plant_info.chance) == 1 then stages = stages + 1 end
|
||||
end
|
||||
if stages > 0 then
|
||||
mcl_farming:grow_plant(identifier, pos, node, stages, false)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
|
|
|
@ -15,10 +15,6 @@ minetest.register_node("mcl_farming:soil", {
|
|||
{-0.5, -0.5, -0.5, 0.5, 0.4375, 0.5},
|
||||
}
|
||||
},
|
||||
on_construct = function(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_int("wet", 0)
|
||||
end,
|
||||
groups = {handy=1,shovely=1, dirtifies_below_solid=1, dirtifier=1, soil=2, soil_sapling=1, deco_block=1 },
|
||||
sounds = mcl_sounds.node_sound_dirt_defaults(),
|
||||
_mcl_blast_resistance = 0.6,
|
||||
|
@ -38,10 +34,6 @@ minetest.register_node("mcl_farming:soil_wet", {
|
|||
{-0.5, -0.5, -0.5, 0.5, 0.4375, 0.5},
|
||||
}
|
||||
},
|
||||
on_construct = function(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_int("wet", 7)
|
||||
end,
|
||||
groups = {handy=1,shovely=1, not_in_creative_inventory=1, dirtifies_below_solid=1, dirtifier=1, soil=3, soil_sapling=1 },
|
||||
sounds = mcl_sounds.node_sound_dirt_defaults(),
|
||||
_mcl_blast_resistance = 0.6,
|
||||
|
@ -51,75 +43,54 @@ minetest.register_node("mcl_farming:soil_wet", {
|
|||
minetest.register_abm({
|
||||
label = "Farmland hydration",
|
||||
nodenames = {"mcl_farming:soil", "mcl_farming:soil_wet"},
|
||||
interval = 15,
|
||||
chance = 4,
|
||||
interval = 2.73,
|
||||
chance = 25,
|
||||
action = function(pos, node)
|
||||
-- Get wetness value
|
||||
local meta = minetest.get_meta(pos)
|
||||
local wet = meta:get_int("wet")
|
||||
if not wet then
|
||||
if node.name == "mcl_farming:soil" then
|
||||
wet = 0
|
||||
else
|
||||
wet = 7
|
||||
end
|
||||
end
|
||||
|
||||
-- Turn back into dirt when covered by solid node
|
||||
local above_node = minetest.get_node_or_nil({x=pos.x,y=pos.y+1,z=pos.z})
|
||||
if above_node then
|
||||
if minetest.get_item_group(above_node.name, "solid") ~= 0 then
|
||||
node.name = "mcl_core:dirt"
|
||||
minetest.set_node(pos, node)
|
||||
return
|
||||
end
|
||||
local above_node = minetest.get_node_or_nil(vector.offset(pos, 0, 1, 0))
|
||||
if above_node and minetest.get_item_group(above_node.name, "solid") ~= 0 then
|
||||
node.name = "mcl_core:dirt"
|
||||
minetest.set_node(pos, node)
|
||||
return
|
||||
end
|
||||
|
||||
-- Check an area of 9×2×9 around the node for nodename (9×9 on same level and 9×9 below)
|
||||
local function check_surroundings(pos, nodename)
|
||||
local nodes = minetest.find_nodes_in_area({x=pos.x-4,y=pos.y,z=pos.z-4}, {x=pos.x+4,y=pos.y+1,z=pos.z+4}, {nodename})
|
||||
return #nodes > 0
|
||||
end
|
||||
|
||||
if check_surroundings(pos, "group:water") then
|
||||
if node.name ~= "mcl_farming:soil_wet" then
|
||||
-- Make it wet
|
||||
-- in rain, become wet, do not decay
|
||||
if mcl_weather and mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then
|
||||
if node.name == "mcl_farming:soil" then
|
||||
node.name = "mcl_farming:soil_wet"
|
||||
minetest.set_node(pos, node)
|
||||
end
|
||||
else -- No water nearby.
|
||||
-- The decay branch (make farmland dry or turn back to dirt)
|
||||
return
|
||||
end
|
||||
|
||||
-- Don't decay while it's raining
|
||||
if mcl_weather.rain.raining then
|
||||
if mcl_weather.is_outdoor(pos) then
|
||||
return
|
||||
end
|
||||
end
|
||||
-- No decay near unloaded areas since these might include water.
|
||||
if not check_surroundings(pos, "ignore") then
|
||||
if wet <= 0 then
|
||||
--local n_def = minetest.registered_nodes[node.name] or nil
|
||||
local nn = minetest.get_node_or_nil({x=pos.x,y=pos.y+1,z=pos.z})
|
||||
if not nn or not nn.name then
|
||||
return
|
||||
end
|
||||
local nn_def = minetest.registered_nodes[nn.name] or nil
|
||||
-- Check an area of 9x2x9 around the node for nodename (9x9 on same level and 9x9 above)
|
||||
-- include "ignore" to detect unloaded blocks
|
||||
local nodes, counts = minetest.find_nodes_in_area(vector.offset(pos, -4, 0, -4), vector.offset(pos, 4, 1, 4), {"group:water", "ignore"})
|
||||
local ignore = counts.ignore or 0
|
||||
local has_water, fully_loaded = #nodes > ignore, ignore == 0
|
||||
|
||||
if nn_def and minetest.get_item_group(nn.name, "plant") == 0 then
|
||||
node.name = "mcl_core:dirt"
|
||||
minetest.set_node(pos, node)
|
||||
return
|
||||
end
|
||||
else
|
||||
if wet == 7 then
|
||||
node.name = "mcl_farming:soil"
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
-- Slowly count down wetness
|
||||
meta:set_int("wet", wet-1)
|
||||
end
|
||||
-- Hydrate by rain or water, do not decay
|
||||
if has_water then
|
||||
if node.name == "mcl_farming:soil" then
|
||||
node.name = "mcl_farming:soil_wet"
|
||||
minetest.set_node(pos, node)
|
||||
end
|
||||
return
|
||||
end
|
||||
-- No decay near unloaded areas (ignore) since these might include water.
|
||||
if not fully_loaded then return end
|
||||
|
||||
-- Decay: make wet farmland dry up
|
||||
if node.name == "mcl_farming:soil_wet" then
|
||||
node.name = "mcl_farming:soil"
|
||||
minetest.set_node(pos, node)
|
||||
return
|
||||
end
|
||||
-- Revert to dirt if wetness is 0, and no plant above
|
||||
local above = minetest.get_node_or_nil(vector.offset(pos, 0, 1, 0))
|
||||
if minetest.get_item_group(above.name, "plant") == 0 then
|
||||
node.name = "mcl_core:dirt"
|
||||
minetest.set_node(pos, node)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
|
|
@ -99,8 +99,8 @@ minetest.register_craftitem("mcl_farming:sweet_berry", {
|
|||
})
|
||||
minetest.register_alias("mcl_sweet_berry:sweet_berry", "mcl_farming:sweet_berry")
|
||||
|
||||
-- TODO: Find proper interval and chance values for sweet berry bushes. Current interval and chance values are copied from mcl_farming:beetroot which has similar growth stages.
|
||||
mcl_farming:add_plant("plant_sweet_berry_bush", "mcl_farming:sweet_berry_bush_3", {"mcl_farming:sweet_berry_bush_0", "mcl_farming:sweet_berry_bush_1", "mcl_farming:sweet_berry_bush_2"}, 68, 3)
|
||||
-- TODO: Find proper interval and chance values for sweet berry bushes. Current interval and chance values are copied from mcl_farming:beetroot which has similar growth stages, 2/3rd of the default.
|
||||
mcl_farming:add_plant("plant_sweet_berry_bush", "mcl_farming:sweet_berry_bush_3", {"mcl_farming:sweet_berry_bush_0", "mcl_farming:sweet_berry_bush_1", "mcl_farming:sweet_berry_bush_2"}, 8.7019, 35)
|
||||
|
||||
local function berry_damage_check(obj)
|
||||
local p = obj:get_pos()
|
||||
|
|
|
@ -99,7 +99,7 @@ minetest.register_node("mcl_farming:wheat", {
|
|||
}
|
||||
})
|
||||
|
||||
mcl_farming:add_plant("plant_wheat", "mcl_farming:wheat", {"mcl_farming:wheat_1", "mcl_farming:wheat_2", "mcl_farming:wheat_3", "mcl_farming:wheat_4", "mcl_farming:wheat_5", "mcl_farming:wheat_6", "mcl_farming:wheat_7"}, 25, 20)
|
||||
mcl_farming:add_plant("plant_wheat", "mcl_farming:wheat", {"mcl_farming:wheat_1", "mcl_farming:wheat_2", "mcl_farming:wheat_3", "mcl_farming:wheat_4", "mcl_farming:wheat_5", "mcl_farming:wheat_6", "mcl_farming:wheat_7"}, 5.8020, 35)
|
||||
|
||||
minetest.register_craftitem("mcl_farming:wheat_item", {
|
||||
description = S("Wheat"),
|
||||
|
|
|
@ -9,6 +9,8 @@ local function mcl_log(message)
|
|||
end
|
||||
end
|
||||
|
||||
mcl_hoppers = {}
|
||||
|
||||
--[[ BEGIN OF NODE DEFINITIONS ]]
|
||||
|
||||
local mcl_hoppers_formspec = table.concat({
|
||||
|
@ -52,7 +54,7 @@ local function straight_hopper_act(pos, node, active_object_count, active_count_
|
|||
|
||||
mcl_util.hopper_push(pos, dst_pos)
|
||||
local src_pos = vector.offset(pos, 0, 1, 0)
|
||||
mcl_util.hopper_pull(pos, src_pos)
|
||||
mcl_util.hopper_pull_to_inventory(minetest.get_meta(pos):get_inventory(), "main", src_pos, pos)
|
||||
end
|
||||
|
||||
local function bent_hopper_act(pos, node, active_object_count, active_object_count_wider)
|
||||
|
@ -91,9 +93,102 @@ local function bent_hopper_act(pos, node, active_object_count, active_object_cou
|
|||
end
|
||||
|
||||
local src_pos = vector.offset(pos, 0, 1, 0)
|
||||
mcl_util.hopper_pull(pos, src_pos)
|
||||
mcl_util.hopper_pull_to_inventory(inv, "main", src_pos, pos)
|
||||
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
|
||||
|
@ -199,6 +294,46 @@ local def_hopper = {
|
|||
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,
|
||||
sounds = mcl_sounds.node_sound_metal_defaults(),
|
||||
|
||||
_mcl_blast_resistance = 4.8,
|
||||
|
@ -404,6 +539,66 @@ local def_hopper_side = {
|
|||
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 +630,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 +669,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 +678,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
|
||||
|
|
|
@ -211,16 +211,37 @@ local function set_interact(player, interact)
|
|||
return
|
||||
end
|
||||
local meta = player:get_meta()
|
||||
if meta:get_int("mcl_privs:interact_revoked") ~= 1 then
|
||||
privs.interact = interact
|
||||
minetest.set_player_privs(player_name, privs)
|
||||
meta:set_int("mcl_privs:interact_revoked",0)
|
||||
|
||||
if interact and meta:get_int("mcl_shields:interact_revoked") ~= 0 then
|
||||
meta:set_int("mcl_shields:interact_revoked", 0)
|
||||
privs.interact = true
|
||||
elseif not interact then
|
||||
meta:set_int("mcl_shields:interact_revoked", privs.interact and 1 or 0)
|
||||
privs.interact = nil
|
||||
end
|
||||
|
||||
minetest.set_player_privs(player_name, privs)
|
||||
end
|
||||
|
||||
-- Prevent player from being able to circumvent interact privilage removal by
|
||||
-- using shield.
|
||||
minetest.register_on_priv_revoke(function(name, revoker, priv)
|
||||
if priv == "interact" and revoker then
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if not player then
|
||||
return
|
||||
end
|
||||
local meta = player:get_meta()
|
||||
meta:set_int("mcl_shields:interact_revoked", 0)
|
||||
end
|
||||
end)
|
||||
|
||||
local shield_hud = {}
|
||||
|
||||
local function remove_shield_hud(player)
|
||||
set_interact(player, true)
|
||||
playerphysics.remove_physics_factor(player, "speed", "shield_speed")
|
||||
|
||||
if not shield_hud[player] then return end --this function takes a long time. only run it when necessary
|
||||
player:hud_remove(shield_hud[player])
|
||||
shield_hud[player] = nil
|
||||
|
@ -231,9 +252,6 @@ local function remove_shield_hud(player)
|
|||
if not hf.wielditem then
|
||||
player:hud_set_flags({wielditem = true})
|
||||
end
|
||||
|
||||
playerphysics.remove_physics_factor(player, "speed", "shield_speed")
|
||||
set_interact(player, true)
|
||||
end
|
||||
|
||||
local function add_shield_entity(player, i)
|
||||
|
@ -251,6 +269,11 @@ local function remove_shield_entity(player, i)
|
|||
end
|
||||
end
|
||||
|
||||
local function is_rmb_conflicting_node(nodename)
|
||||
local nodedef = minetest.registered_nodes[nodename] or {}
|
||||
return nodedef.on_rightclick
|
||||
end
|
||||
|
||||
local function handle_blocking(player)
|
||||
local player_shield = mcl_shields.players[player]
|
||||
local rmb = player:get_player_control().RMB
|
||||
|
@ -259,14 +282,25 @@ local function handle_blocking(player)
|
|||
return
|
||||
end
|
||||
|
||||
local pointed_thing = mcl_util.get_pointed_thing(player, true)
|
||||
local wielded_stack = player:get_wielded_item()
|
||||
|
||||
local shield_in_offhand = mcl_shields.wielding_shield(player, 1)
|
||||
local shield_in_hand = mcl_shields.wielding_shield(player)
|
||||
local not_blocking = player_shield.blocking == 0
|
||||
|
||||
local pos = player:get_pos()
|
||||
if pointed_thing and pointed_thing.type == "node" then
|
||||
local pointed_node = minetest.get_node(pointed_thing.under)
|
||||
if minetest.get_item_group(pointed_node.name, "container") > 1
|
||||
or is_rmb_conflicting_node(pointed_node.name)
|
||||
or wielded_stack:get_definition().type == "node" then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if shield_in_hand then
|
||||
if not_blocking then
|
||||
minetest.after(0.25, function()
|
||||
minetest.after(0.05, function()
|
||||
if (not_blocking or not shield_in_offhand) and shield_in_hand and rmb then
|
||||
player_shield.blocking = 2
|
||||
set_shield(player, true, 2)
|
||||
|
@ -276,22 +310,15 @@ local function handle_blocking(player)
|
|||
player_shield.blocking = 2
|
||||
end
|
||||
elseif shield_in_offhand then
|
||||
local pointed_thing = mcl_util.get_pointed_thing(player, true)
|
||||
local offhand_can_block = (wielded_item(player) == "" or not pointed_thing)
|
||||
and (minetest.get_item_group(wielded_item(player), "bow") ~= 1 and minetest.get_item_group(wielded_item(player), "crossbow") ~= 1)
|
||||
|
||||
if pointed_thing and pointed_thing.type == "node" then
|
||||
if minetest.get_item_group(minetest.get_node(pointed_thing.under).name, "container") > 1 then
|
||||
return
|
||||
end
|
||||
end
|
||||
local offhand_can_block = minetest.get_item_group(wielded_item(player), "cannot_block") ~= 1
|
||||
|
||||
if not offhand_can_block then
|
||||
return
|
||||
end
|
||||
if not_blocking then
|
||||
minetest.after(0.25, function()
|
||||
if (not_blocking or not shield_in_hand) and shield_in_offhand and rmb and offhand_can_block then
|
||||
minetest.after(0.05, function()
|
||||
if (not_blocking or not shield_in_hand) and shield_in_offhand
|
||||
and rmb and offhand_can_block then
|
||||
player_shield.blocking = 1
|
||||
set_shield(player, true, 1)
|
||||
end
|
||||
|
@ -344,7 +371,7 @@ local function add_shield_hud(shieldstack, player, blocking)
|
|||
z_index = -200,
|
||||
})
|
||||
playerphysics.add_physics_factor(player, "speed", "shield_speed", 0.5)
|
||||
set_interact(player, nil)
|
||||
set_interact(player, false)
|
||||
end
|
||||
|
||||
local function update_shield_hud(player, blocking, shieldstack)
|
||||
|
|
|
@ -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
|
||||
|
@ -36,7 +57,11 @@ else
|
|||
end
|
||||
end
|
||||
|
||||
tsm_railcorridors.carts = { "mcl_minecarts:chest_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,
|
||||
}
|
||||
|
||||
-- This is called after a spawner has been placed by the game.
|
||||
-- Use this to properly set up the metadata and stuff.
|
||||
|
@ -46,17 +71,32 @@ function tsm_railcorridors.on_construct_spawner(pos)
|
|||
mcl_mobspawners.setup_spawner(pos, "mobs_mc:cave_spider", 0, 7)
|
||||
end
|
||||
|
||||
|
||||
-- This is called after a cart has been placed by the game.
|
||||
-- Use this to properly set up entity metadata and stuff.
|
||||
-- * entity_id - type of cart to create
|
||||
-- * pos: Position of cart
|
||||
-- * cart: Cart entity
|
||||
function tsm_railcorridors.on_construct_cart(_, cart, pr_carts)
|
||||
local l = cart:get_luaentity()
|
||||
local inv = mcl_entity_invs.load_inv(l,27)
|
||||
local items = tsm_railcorridors.get_treasures(pr_carts)
|
||||
mcl_loot.fill_inventory(inv, "main", items, pr_carts)
|
||||
mcl_entity_invs.save_inv(l)
|
||||
-- * pr: pseudorandom
|
||||
function tsm_railcorridors.create_cart_staticdata(entity_id, pos, pr)
|
||||
local uuid = create_minecart(entity_id, pos, vector.new(1,0,0))
|
||||
|
||||
-- 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)
|
||||
|
||||
-- TODO: determine if we should convert to use mcl_loot
|
||||
-- mcl_loot.fill_inventory(inv, "main", items, pr_carts)
|
||||
-- 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
|
||||
|
@ -104,11 +144,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")
|
||||
|
@ -169,6 +171,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
|
||||
|
@ -392,27 +398,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.
|
||||
local function RecheckCartHack(params)
|
||||
local pos = params[1]
|
||||
local cart_id = params[2]
|
||||
-- Find cart
|
||||
for _, obj in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
|
||||
if obj ~= nil 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, pr_carts)
|
||||
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.
|
||||
|
@ -935,13 +920,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 (supposedly in 5.9.0?)
|
||||
minetest.after(3, RecheckCartHack, {cpos, cart_id})
|
||||
-- Try to create cart staticdata
|
||||
local hook = tsm_railcorridors.create_cart_staticdata
|
||||
if hook then cart_staticdata = hook(cart_id, cpos, pr) end
|
||||
|
||||
minetest.add_entity(cpos, cart_id, cart_staticdata)
|
||||
end
|
||||
end
|
||||
carts_table = {}
|
||||
|
@ -950,7 +935,7 @@ end
|
|||
-- Start generation of a rail corridor system
|
||||
-- main_cave_coords is the center of the floor of the dirt room, from which
|
||||
-- all corridors expand.
|
||||
local function create_corridor_system(main_cave_coords)
|
||||
local function create_corridor_system(main_cave_coords, pr)
|
||||
|
||||
-- Dirt room size
|
||||
local maxsize = 6
|
||||
|
@ -1111,7 +1096,15 @@ mcl_structures.register_structure("mineshaft",{
|
|||
end
|
||||
if p.y > -10 then return true end
|
||||
InitRandomizer(blockseed)
|
||||
create_corridor_system(p)
|
||||
|
||||
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,
|
||||
|
||||
|
|
|
@ -30,17 +30,5 @@ for _, action in pairs({"grant", "revoke"}) do
|
|||
if priv == "fly" then
|
||||
meta:set_int("mcl_privs:fly_changed", 1)
|
||||
end
|
||||
|
||||
--[[
|
||||
so e.g. hackers who have been revoked of the interact privilege
|
||||
will not automatically get the interact privilege through the mcl shields code back
|
||||
]]
|
||||
if priv == "interact" then
|
||||
if action == "revoke" then
|
||||
meta:set_int("mcl_privs:interact_revoked", 1)
|
||||
else
|
||||
meta:set_int("mcl_privs:interact_revoked", 0)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -45,6 +45,9 @@ mcl_disabled_structures (Disabled structures) string
|
|||
# Comma separated list of disabled event names
|
||||
mcl_disabled_events (Disabled events) string
|
||||
|
||||
# Control the relative plant growth speed (default: 1)
|
||||
vl_plant_growth (Plant growth factor) float 1.0 0 100
|
||||
|
||||
[Players]
|
||||
# If enabled, players respawn at the bed they last lay on instead of normal
|
||||
# spawn.
|
||||
|
|
After Width: | Height: | Size: 241 B |
Before Width: | Height: | Size: 258 B After Width: | Height: | Size: 495 B |
Before Width: | Height: | Size: 278 B After Width: | Height: | Size: 517 B |
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 488 B |