mirror of
https://git.minetest.land/VoxeLibre/VoxeLibre.git
synced 2024-12-01 06:51:10 +01:00
437 lines
12 KiB
Lua
437 lines
12 KiB
Lua
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 DEBUG = false
|
|
local function mcl_log(message)
|
|
if LOGGING_ON then
|
|
mcl_util.mcl_log(message, "[Minecarts]", true)
|
|
end
|
|
end
|
|
|
|
-- Imports
|
|
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 do_movement,do_detached_movement,handle_cart_enter = dofile(modpath.."/movement.lua")
|
|
assert(do_movement)
|
|
assert(do_detached_movement)
|
|
assert(handle_cart_enter)
|
|
|
|
-- Constants
|
|
local max_step_distance = 0.5
|
|
local MINECART_MAX_HP = 4
|
|
local PASSENGER_ATTACH_POSITION = vector.new(0, -1.75, 0)
|
|
|
|
local function detach_driver(self)
|
|
if not self._driver then
|
|
return
|
|
end
|
|
mcl_player.player_attached[self._driver] = nil
|
|
local player = minetest.get_player_by_name(self._driver)
|
|
self._driver = nil
|
|
self._start_pos = nil
|
|
if player then
|
|
player:set_detach()
|
|
player:set_eye_offset(vector.new(0,0,0),vector.new(0,0,0))
|
|
mcl_player.player_set_animation(player, "stand" , 30)
|
|
end
|
|
end
|
|
|
|
-- Table for item-to-entity mapping. Keys: itemstring, Values: Corresponding entity ID
|
|
local entity_mapping = {}
|
|
|
|
local function make_staticdata( railtype, connected_at, dir )
|
|
return {
|
|
railtype = railtype,
|
|
connected_at = connected_at,
|
|
distance = 0,
|
|
velocity = 0,
|
|
dir = vector.new(dir),
|
|
mass = 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.get_uuid(self.object)
|
|
end
|
|
local cd = get_cart_data(data.uuid)
|
|
if not cd then update_cart_data(data) end
|
|
|
|
-- Initialize
|
|
if type(data) == "table" then
|
|
-- Migrate old data
|
|
if data._railtype then
|
|
data.railtype = data._railtype
|
|
data._railtype = nil
|
|
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
|
|
data.uuid = mcl_util.get_uuid(self.object)
|
|
|
|
self._staticdata = data
|
|
end
|
|
|
|
-- 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})
|
|
end
|
|
|
|
function DEFAULT_CART_DEF:add_node_watch(pos)
|
|
local staticdata = self._staticdata
|
|
local watches = staticdata.node_watches or {}
|
|
|
|
for _,watch in ipairs(watches) do
|
|
if watch == 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 _,node_pos in ipairs(watches) do
|
|
if node_pos ~= pos then
|
|
new_watches[#new_watches] = node_pos
|
|
end
|
|
end
|
|
staticdata.node_watches = new_watches
|
|
end
|
|
function DEFAULT_CART_DEF:on_step(dtime)
|
|
local staticdata = self._staticdata
|
|
if not staticdata then
|
|
staticdata = make_staticdata()
|
|
self._staticdata = staticdata
|
|
end
|
|
|
|
-- Regen
|
|
local hp = self.object:get_hp()
|
|
if hp < MINECART_MAX_HP then
|
|
if (staticdata.regen_timer or 0) > 0.5 then
|
|
hp = hp + 1
|
|
staticdata.regen_timer = staticdata.regen_timer - 1
|
|
end
|
|
staticdata.regen_timer = (staticdata.regen_timer or 0) + dtime
|
|
self.object:set_hp(hp)
|
|
else
|
|
staticdata.regen_timer = nil
|
|
end
|
|
|
|
-- Fix railtype field
|
|
local pos = self.object:get_pos()
|
|
if staticdata.connected_at and not staticdata.railtype then
|
|
local node = minetest.get_node(vector.floor(pos)).name
|
|
staticdata.railtype = minetest.get_item_group(node, "connect_to_raillike")
|
|
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
|
|
self._go_forward = ctrl.up
|
|
self._brake = ctrl.down
|
|
end
|
|
|
|
-- Give achievement when player reached a distance of 1000 nodes from the start position
|
|
if vector.distance(self._start_pos, pos) >= 1000 then
|
|
awards.unlock(self._driver, "mcl:onARail")
|
|
end
|
|
end
|
|
|
|
if staticdata.connected_at then
|
|
do_movement(self, dtime)
|
|
else
|
|
do_detached_movement(self, dtime)
|
|
end
|
|
|
|
-- TODO: move this into mcl_core:cactus _mcl_minecarts_on_enter_side
|
|
-- Drop minecart if it collides with a cactus node
|
|
local r = 0.6
|
|
for _, node_pos in pairs({{r, 0}, {0, r}, {-r, 0}, {0, -r}}) do
|
|
if minetest.get_node(vector.offset(pos, node_pos[1], 0, node_pos[2])).name == "mcl_core:cactus" then
|
|
self:on_death()
|
|
self.object:remove()
|
|
return
|
|
end
|
|
end
|
|
end
|
|
function DEFAULT_CART_DEF:on_death(killer)
|
|
local staticdata = self._staticdata
|
|
minetest.log("action", "cart #"..staticdata.uuid.." was killed")
|
|
|
|
detach_driver(self)
|
|
|
|
-- Detach passenger
|
|
if self._passenger then
|
|
local mob = self._passenger.object
|
|
mob:set_detach()
|
|
end
|
|
|
|
-- 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
|
|
|
|
-- Remove data
|
|
destroy_cart_data(staticdata.uuid)
|
|
|
|
-- Drop items
|
|
local drop = self.drop
|
|
if not killer or not minetest.is_creative_enabled(killer:get_player_name()) then
|
|
for d=1, #drop do
|
|
minetest.add_item(self.object:get_pos(), drop[d])
|
|
end
|
|
elseif killer and killer:is_player() then
|
|
local inv = killer:get_inventory()
|
|
for d=1, #drop do
|
|
if not inv:contains_item("main", drop[d]) then
|
|
inv:add_item("main", drop[d])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Place a minecart at pointed_thing
|
|
function mcl_minecarts.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 railtype, 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)
|
|
railtype = minetest.get_item_group(node.name, "connect_to_raillike")
|
|
cart_dir = mcl_minecarts:get_rail_direction(railpos, vector.new(1,0,0), nil, nil, railtype)
|
|
end
|
|
|
|
local entity_id = entity_mapping[itemstack:get_name()]
|
|
local cart = minetest.add_entity(spawn_pos, entity_id)
|
|
|
|
cart:set_yaw(minetest.dir_to_yaw(cart_dir))
|
|
|
|
-- Update static data
|
|
local le = cart:get_luaentity()
|
|
if le then
|
|
local uuid = mcl_util.get_uuid(cart)
|
|
data = make_staticdata( railtype, railpos, cart_dir )
|
|
data.uuid = uuid
|
|
update_cart_data(data)
|
|
le._staticdata = data
|
|
save_cart_data(le._staticdata.uuid)
|
|
end
|
|
|
|
-- Call placer
|
|
if le._mcl_minecarts_on_place then
|
|
le._mcl_minecarts_on_place(le, placer)
|
|
end
|
|
|
|
if railpos then
|
|
handle_cart_enter(le, railpos)
|
|
end
|
|
|
|
local pname = ""
|
|
if placer then
|
|
pname = placer:get_player_name()
|
|
end
|
|
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
|
|
|
|
mcl_minecarts.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 mcl_minecarts.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 = mcl_minecarts.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 mcl_minecarts.register_minecart(def)
|
|
assert( def.drop, "def.drop is required parameter" )
|
|
assert( def.itemstring, "def.itemstring is required parameter" )
|
|
|
|
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 = mcl_minecarts.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
|
|
|
|
minetest.register_globalstep(function(dtime)
|
|
-- TODO: handle periodically updating out-of-range carts
|
|
end)
|
|
|