Compare commits
168 Commits
b9942f3746
...
1ec732d646
Author | SHA1 | Date |
---|---|---|
teknomunk | 1ec732d646 | |
teknomunk | eda99be60e | |
teknomunk | a94a9e9bc2 | |
teknomunk | 9c8db2b995 | |
teknomunk | dba226e18c | |
teknomunk | db1f504f2e | |
teknomunk | 80699b405c | |
teknomunk | 66936c7f07 | |
teknomunk | 2295b88e12 | |
teknomunk | 99500513c5 | |
teknomunk | 937884083f | |
teknomunk | bd07311f08 | |
teknomunk | 5fc28cb614 | |
teknomunk | 73eee3e344 | |
teknomunk | 5b1b75b0fc | |
teknomunk | 1f83937de9 | |
teknomunk | c394864546 | |
teknomunk | 3ede8b4b7c | |
teknomunk | b5892419c5 | |
teknomunk | 6cdd72c485 | |
teknomunk | f4f0d01653 | |
teknomunk | 8622ee771f | |
teknomunk | 29a619555d | |
teknomunk | 2abb7b8f35 | |
teknomunk | 57bce03a33 | |
teknomunk | 53e94e1c38 | |
teknomunk | 26c034b985 | |
teknomunk | a9e1c5ad19 | |
teknomunk | 8780c8fca6 | |
teknomunk | 11ce4ad18f | |
teknomunk | f9136730ad | |
teknomunk | 89a0bd6e39 | |
teknomunk | 9ca053aa2e | |
teknomunk | 6142c89dd2 | |
teknomunk | 78ab49e971 | |
teknomunk | 7b8705acad | |
teknomunk | b2c74dfe1a | |
teknomunk | 2f4cbfca2d | |
teknomunk | cf4d6257ae | |
teknomunk | abda23c189 | |
teknomunk | 246d681880 | |
teknomunk | 48e610139b | |
teknomunk | 4303697e51 | |
teknomunk | 7d0345b785 | |
teknomunk | 1cec9d6436 | |
teknomunk | 68bda2840a | |
teknomunk | 4056f5b0e2 | |
teknomunk | d2ac38dba7 | |
teknomunk | be3bcbb426 | |
teknomunk | d12896c162 | |
teknomunk | bb0f0747ae | |
teknomunk | 644393c856 | |
teknomunk | 0f364016ee | |
teknomunk | 407133bdd3 | |
teknomunk | 133184c329 | |
teknomunk | 597147e9cd | |
teknomunk | a71ed25496 | |
teknomunk | 9ed1d0a059 | |
teknomunk | ffd3dbc7b1 | |
teknomunk | a7d8b52510 | |
teknomunk | a3b3585300 | |
teknomunk | b0f12dfabb | |
teknomunk | b8ed85a004 | |
teknomunk | edf3b54fd4 | |
teknomunk | fad40d9444 | |
teknomunk | d5ab0b5406 | |
teknomunk | 2cde46563d | |
teknomunk | e495dbc0a2 | |
teknomunk | 0af628a8d4 | |
teknomunk | 889c89c0e6 | |
teknomunk | a388b6ebff | |
teknomunk | 0368cf958b | |
teknomunk | 938c05e68e | |
teknomunk | 8bcf9388d4 | |
teknomunk | 4c6a10f613 | |
teknomunk | d250678689 | |
teknomunk | 57525d2072 | |
teknomunk | 9df1f9a588 | |
teknomunk | 17705348c3 | |
teknomunk | 6160534ed5 | |
teknomunk | 3881cd60f5 | |
teknomunk | d22aa7285d | |
teknomunk | c8513d5acd | |
teknomunk | 20bf40a6ec | |
teknomunk | b17710db9c | |
teknomunk | 1d9ba2211f | |
teknomunk | d41f2f03ce | |
teknomunk | 14b50faf92 | |
teknomunk | 464771b79d | |
teknomunk | 1f80628298 | |
teknomunk | 39ca793a43 | |
teknomunk | b084ec558d | |
teknomunk | 763c59a1a2 | |
teknomunk | 188aca8c12 | |
teknomunk | a0d8e5ad7e | |
teknomunk | e3ca5a84c8 | |
teknomunk | d9bc707800 | |
teknomunk | 8d15b8e99f | |
teknomunk | 9490e4a910 | |
teknomunk | b38d8c3fa9 | |
teknomunk | 643e6f3de6 | |
teknomunk | dd54270294 | |
teknomunk | 9927f54ab4 | |
teknomunk | 17a5b1eee3 | |
teknomunk | cf7350418f | |
teknomunk | 0511de8876 | |
teknomunk | ce8b79fbe2 | |
teknomunk | 41943522a5 | |
teknomunk | 38ee22c3bd | |
teknomunk | efe190e23c | |
teknomunk | 9aacafe52c | |
teknomunk | 8517885521 | |
teknomunk | 826a827db2 | |
teknomunk | 0aa45092cf | |
teknomunk | 9e16947080 | |
teknomunk | 90c8bf35b7 | |
teknomunk | 95d168d8bd | |
teknomunk | 76f4135dde | |
teknomunk | b2fd8d5339 | |
teknomunk | 7164063644 | |
teknomunk | 2724c25e90 | |
teknomunk | ef5feffb98 | |
teknomunk | 0fc7383221 | |
teknomunk | 5bf26706fa | |
teknomunk | b78c462306 | |
teknomunk | de59535dae | |
teknomunk | 7541ef8e03 | |
teknomunk | 61bb7f06b5 | |
teknomunk | 5c3e2c8f90 | |
teknomunk | d4b90a4bd5 | |
teknomunk | 9397e329e7 | |
teknomunk | d9db08d35b | |
teknomunk | d78c4593ec | |
teknomunk | 371ad8d737 | |
teknomunk | 0cb19e5218 | |
teknomunk | c17874bc48 | |
teknomunk | 1dd8f0efef | |
teknomunk | 6646c6e84f | |
teknomunk | 746780ab1f | |
teknomunk | c3b6cf758c | |
teknomunk | 58cdc1b66a | |
teknomunk | 390419e67c | |
teknomunk | c42302cfd2 | |
teknomunk | 5b1ebbff97 | |
teknomunk | c198257ae7 | |
teknomunk | 12ac5740d3 | |
teknomunk | cc84d5efa9 | |
teknomunk | 0b1252a60d | |
teknomunk | e04d82b80e | |
teknomunk | 88f5796cad | |
teknomunk | e82e5f805f | |
teknomunk | 8ee950d31e | |
teknomunk | 356c2c6418 | |
teknomunk | ce9f92e017 | |
teknomunk | 435141357d | |
teknomunk | 063c84d2ba | |
teknomunk | 7a87a8282d | |
teknomunk | 2dcb790ace | |
teknomunk | c9d6a417cd | |
teknomunk | d57d83009d | |
teknomunk | da4b8ba579 | |
teknomunk | 5271375843 | |
teknomunk | 669d9d0556 | |
teknomunk | 2bb1aa7241 | |
teknomunk | 6de06945c1 | |
teknomunk | 960309462f | |
teknomunk | c008d2ea3a | |
marro | 4dc5d0939c |
|
@ -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
|
||||
|
@ -1153,3 +1162,88 @@ function mcl_util.trace_nodes(pos, dir, allowed_nodes, limit)
|
|||
|
||||
return nil, limit, nil
|
||||
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()
|
||||
|
||||
-- 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,673 @@
|
|||
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 {}
|
||||
dir.y = 0
|
||||
dir = vector.normalize(dir)
|
||||
local impulse = vector.dot(staticdata.dir, vector.multiply(dir, damage * 4))
|
||||
minetest.log(dump({
|
||||
dir = dir,
|
||||
dir_len = vector.length(dir),
|
||||
damage = damage,
|
||||
impulse = impulse,
|
||||
}))
|
||||
if impulse < 0 and staticdata.velocity == 0 then
|
||||
mod.reverse_direction(staticdata)
|
||||
impulse = -impulse
|
||||
end
|
||||
|
||||
controls.impulse = impulse
|
||||
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,100 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local FURNACE_CART_SPEED = tonumber(minetest.settings:get("mcl_minecarts_furnace_speed")) or 4
|
||||
|
||||
-- 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
|
||||
for car in mcl_minecarts.train_cars(staticdata) do
|
||||
if car.velocity < FURNACE_CART_SPEED - 0.1 then -- Slightly less to allow train cars to maintain spacing
|
||||
car.velocity = FURNACE_CART_SPEED
|
||||
end
|
||||
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[1] 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)
|
||||
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
|
||||
|
||||
-- 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 dir
|
||||
|
||||
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.25
|
||||
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,609 @@
|
|||
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
|
||||
mod.break_train_at(staticdata)
|
||||
|
||||
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 (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
|
||||
|
||||
-- Apply velocity impulse
|
||||
local v_0 = staticdata.velocity or 0
|
||||
local ctrl = staticdata.controls or {}
|
||||
if ctrl.impulse then
|
||||
local impulse = ctrl.impulse
|
||||
ctrl.impulse = nil
|
||||
|
||||
local old_v_0 = v_0
|
||||
local new_v_0 = v_0 + impulse
|
||||
if new_v_0 > SPEED_MAX then
|
||||
new_v_0 = SPEED_MAX
|
||||
elseif new_v_0 < 0.025 then
|
||||
new_v_0 = 0
|
||||
end
|
||||
v_0 = new_v_0
|
||||
end
|
||||
|
||||
-- Calculate acceleration
|
||||
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
|
||||
if (v_0 < SPEED_MAX) and ( v_0 + a * timestep > SPEED_MAX) then
|
||||
timestep = ( SPEED_MAX - v_0 ) / a
|
||||
end
|
||||
|
||||
-- Prevent infinite loops
|
||||
if timestep <= 0 then return 0 end
|
||||
|
||||
-- Calculate v_1 taking SPEED_MAX into account
|
||||
local v_1 = v_0 + a * timestep
|
||||
if v_1 > SPEED_MAX then
|
||||
v_1 = SPEED_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)
|
||||
|
||||
-- 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
|
||||
-- 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
|
||||
local ndef = {
|
||||
drawtype = "raillike",
|
||||
tiles = tiles,
|
||||
is_ground_content = false,
|
||||
inventory_image = tiles[1],
|
||||
wield_image = tiles[1],
|
||||
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",
|
||||
walkable = false,
|
||||
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},
|
||||
},
|
||||
stack_max = 64,
|
||||
groups = groups,
|
||||
sounds = mcl_sounds.node_sound_metal_defaults(),
|
||||
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,
|
||||
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,
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
if def_extras then
|
||||
for k,v in pairs(def_extras) do
|
||||
ndef[k] = v
|
||||
},
|
||||
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
|
||||
|
|
@ -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
|
||||
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)
|
||||
|
@ -205,8 +68,6 @@ local function on_place(itemstack, placer, pointed_thing)
|
|||
return new_stack
|
||||
end
|
||||
|
||||
--local node = minetest.get_node(pointed_thing.under)
|
||||
|
||||
local privs = minetest.get_player_privs(placer:get_player_name())
|
||||
if not privs.maphack then
|
||||
minetest.chat_send_player(placer:get_player_name(), S("Placement denied. You need the “maphack” privilege to place command blocks."))
|
||||
|
@ -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")
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# textdomain: mcl_bone_meal
|
||||
Bone Meal=Farine d'Os
|
||||
Bone meal is a white dye and also useful as a fertilizer to speed up the growth of many plants.=La farine d'os est une teinture blanche et est également utile comme engrais pour accélérer la croissance de nombreuses plantes.
|
||||
Rightclick a sheep to turn its wool white. Rightclick a plant to speed up its growth. Note that not all plants can be fertilized like this. When you rightclick a grass block, tall grass and flowers will grow all over the place.=
|
||||
Cliquez avec le bouton droit sur un mouton pour blanchir sa laine. Cliquez avec le bouton droit sur une plante pour accélérer sa croissance. Cependant, toutes les plantes ne peuvent pas être fertilisées de cette manière. Lorsque vous cliquez avec le bouton droit sur un bloc d'herbe, les hautes herbes et les fleurs poussent autour.
|
||||
Rightclick a sheep to turn its wool white. Rightclick a plant to speed up its growth. Note that not all plants can be fertilized like this. When you rightclick a grass block, tall grass and flowers will grow all over the place.=Cliquez avec le bouton droit sur un mouton pour blanchir sa laine. Cliquez avec le bouton droit sur une plante pour accélérer sa croissance. Cependant, toutes les plantes ne peuvent pas être fertilisées de cette manière. Lorsque vous cliquez avec le bouton droit sur un bloc d'herbe, les hautes herbes et les fleurs poussent autour.
|
||||
Speeds up plant growth=Accélère la croissance des plantes
|
||||
|
|
|
@ -48,6 +48,11 @@ minetest.register_node("mcl_core:cactus", {
|
|||
end),
|
||||
_mcl_blast_resistance = 0.4,
|
||||
_mcl_hardness = 0.4,
|
||||
_mcl_minecarts_on_enter_side = function(pos, _, _, _, cart_data)
|
||||
if mcl_minecarts then
|
||||
mcl_minecarts.kill_cart(cart_data)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_node("mcl_core:reeds", {
|
||||
|
|
|
@ -9,6 +9,8 @@ local function mcl_log(message)
|
|||
end
|
||||
end
|
||||
|
||||
mcl_hoppers = {}
|
||||
|
||||
--[[ BEGIN OF NODE DEFINITIONS ]]
|
||||
|
||||
local mcl_hoppers_formspec = table.concat({
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -41,6 +62,10 @@ tsm_railcorridors.carts = {
|
|||
"mcl_minecarts:chest_minecart", "mcl_minecarts:chest_minecart",
|
||||
"mcl_minecarts:tnt_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.
|
||||
|
@ -50,19 +75,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)
|
||||
if inv then -- otherwise probably not a chest minecart
|
||||
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
|
||||
|
@ -110,11 +148,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,30 +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 RecheckCartHack = nil
|
||||
if not minetest.features.random_state_restore then -- proxy for minetest > 5.9.0, this feature will not be removed
|
||||
RecheckCartHack = function(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
|
||||
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.
|
||||
|
@ -938,17 +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))
|
||||
local obj = 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?)
|
||||
if RecheckCartHack then
|
||||
minetest.after(3, RecheckCartHack, {cpos, cart_id})
|
||||
else
|
||||
tsm_railcorridors.on_construct_cart(cpos, obj, pr_carts)
|
||||
end
|
||||
-- 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 = {}
|
||||
|
@ -957,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
|
||||
|
@ -1118,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,
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
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 |