diff --git a/mods/ENTITIES/mcl_minecarts/carts.lua b/mods/ENTITIES/mcl_minecarts/carts.lua index acd4aced5..a0af8d64c 100644 --- a/mods/ENTITIES/mcl_minecarts/carts.lua +++ b/mods/ENTITIES/mcl_minecarts/carts.lua @@ -1,6 +1,6 @@ local modname = minetest.get_current_modname() +local modpath = minetest.get_modpath(modname) local mod = mcl_minecarts -local storage = minetest.get_mod_storage() local S = minetest.get_translator(modname) local LOGGING_ON = minetest.settings:get_bool("mcl_logging_minecarts", false) @@ -11,41 +11,22 @@ local function mcl_log(message) end end --- Import external functions +-- 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 friction = 0.4 local MINECART_MAX_HP = 4 local PASSENGER_ATTACH_POSITION = vector.new(0, -1.75, 0) -local cart_data = {} -local cart_data_fail_cache = {} - -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 - end - - cart_data[uuid] = data - return data -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 -local function destroy_cart_data(uuid) - storage:set_string("cart-"..uuid,"") - cart_data[uuid] = nil - cart_data_fail_cache[uuid] = true -end - local function detach_minecart(self) local staticdata = self._staticdata @@ -61,499 +42,6 @@ local function try_detach_minecart(self) end end ---[[ - 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(self, pos, next_dir, event) - local staticdata = self._staticdata - - local dir = staticdata.dir - local right = vector.new( dir.z, dir.y, -dir.x) - local up = vector.new(0,1,0) - for _,check in ipairs(enter_exit_checks) do - 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, self, next_dir, pos) end - - -- global minecart hook - hook = mcl_minecarts[event..check[4]] - if hook then hook(check_pos, self, next_dir, node_def) end - end - end - - -- Handle cart-specific behaviors - local hook = self["_mcl_minecarts_"..event] - if hook then hook(self, pos) 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(self, pos, next_dir) - --print("entering "..tostring(pos)) - set_metadata_cart_status(pos, self._staticdata.uuid, 1) - handle_cart_enter_exit(self, pos, next_dir, "on_enter" ) -end -local function handle_cart_leave(self, pos, next_dir) - --print("leaving "..tostring(pos)) - set_metadata_cart_status(pos, self._staticdata.uuid, nil) - handle_cart_enter_exit(self, pos, next_dir, "on_leave" ) -end - -local function handle_cart_collision(cart1, prev_pos, next_dir) - -- 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 - if not get_cart_data(uuid) 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 - minetest.log("action","cart #"..cart1._staticdata.uuid.." collided with cart #"..cart_uuid.." at "..tostring(pos)) - - local cart2_aoid = mcl_util.get_active_object_id_from_uuid(cart_uuid) - local cart2 = minetest.luaentities[cart2_aoid] - if not cart2 then return end - - local cart1_staticdata = cart1._staticdata - local cart2_staticdata = cart2._staticdata - - local u1 = cart1_staticdata.velocity - local u2 = cart2_staticdata.velocity - local m1 = cart1_staticdata.mass - local m2 = cart2_staticdata.mass - - -- 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 = cart1_staticdata.dir -end - -local function handle_cart_node_watches(self, dtime) - local staticdata = self._staticdata - local watches = staticdata.node_watches or {} - local new_watches = {} - for _,node_pos in ipairs(watches) do - 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, self, dtime) then - new_watches[#new_watches+1] = node_pos - end - end - end - - staticdata.node_watches = new_watches -end - -local function update_cart_orientation(self,staticdata) - -- constants - local _2_pi = math.pi * 2 - local pi = math.pi - local dir = staticdata.dir - - -- Calculate an angle from the x,z direction components - local rot_y = math.atan2( dir.x, dir.z ) + ( staticdata.rot_adjust or 0 ) - if rot_y < 0 then - rot_y = rot_y + _2_pi - end - - -- Check if the rotation is a 180 flip and don't change if so - local rot = self.object:get_rotation() - local diff = math.abs((rot_y - ( rot.y + pi ) % _2_pi) ) - if diff < 0.001 or diff > _2_pi - 0.001 then - -- Update rotation adjust and recalculate the rotation - staticdata.rot_adjust = ( ( staticdata.rot_adjust or 0 ) + pi ) % _2_pi - rot.y = math.atan2( dir.x, dir.z ) + ( staticdata.rot_adjust or 0 ) - else - rot.y = rot_y - end - - -- Forward/backwards tilt (pitch) - if dir.y < 0 then - rot.x = -0.25 * pi - elseif dir.y > 0 then - rot.x = 0.25 * pi - else - rot.x = 0 - end - - if ( staticdata.rot_adjust or 0 ) < 0.01 then - rot.x = -rot.x - end - if dir.z ~= 0 then - rot.x = -rot.x - end - - self.object:set_rotation(rot) -end - -local function vector_away_from_players(self, staticdata) - local objs = minetest.get_objects_inside_radius(self.object:get_pos(), 1.1) - for n=1,#objs do - local obj = objs[n] - local player_name = obj:get_player_name() - if player_name and player_name ~= "" and not ( self._driver and self._driver == player_name ) then - return obj:get_pos() - self.object:get_pos() - end - end - - return nil -end - -local function direction_away_from_players(self, staticdata) - local diff = vector_away_from_players(self, staticdata) - if not diff then return 0 end - - local length = vector.distance(vector.new(0,0,0),diff) - local vec = diff / length - local force = vector.dot( vec, vector.normalize(staticdata.dir) ) - - -- Check if this would push past the end of the track and don't move it it would - -- This prevents an oscillation that would otherwise occur - local dir = staticdata.dir - if force > 0 then - dir = -dir - end - if mcl_minecarts:is_rail( staticdata.connected_at + dir ) then - if force > 0.5 then - return -length * 4 - elseif force < -0.5 then - return length * 4 - end - end - return 0 -end - -local function calculate_acceleration(self, 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 max_vel = mcl_minecarts.speed_max - - if self._go_forward then - acceleration = 4 - elseif self._brake 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 max_vel ) then - -- Standard friction - elseif node_def and node_def._rail_acceleration then - acceleration = node_def._rail_acceleration * 4 - end - - -- Factor in gravity after everything else - local gravity_strength = 2.45 --friction * 5 - if staticdata.dir.y < 0 then - acceleration = gravity_strength - friction - elseif staticdata.dir.y > 0 then - acceleration = -gravity_strength + friction - end - - return acceleration -end - -local function reverse_direction(self, staticdata) - -- Complete moving thru this block into the next, reverse direction, and put us back at the same position we were at - local next_dir = -staticdata.dir - staticdata.connected_at = staticdata.connected_at + staticdata.dir - staticdata.distance = 1 - staticdata.distance - - -- recalculate direction - local next_dir,_ = mcl_minecarts:get_rail_direction(staticdata.connected_at, next_dir, nil, nil, staticdata.railtype) - staticdata.dir = next_dir -end - -local function do_movement_step(self, dtime) - local staticdata = self._staticdata - if not staticdata.connected_at then return 0 end - - -- Calculate timestep remaiing in this block - local x_0 = staticdata.distance or 0 - local remaining_in_block = 1 - x_0 - local a = calculate_acceleration(self, staticdata) - local v_0 = staticdata.velocity - - -- Repel minecarts - local away = direction_away_from_players(self, staticdata) - if away > 0 then - v_0 = away - elseif away < 0 then - reverse_direction(self, staticdata) - v_0 = -away - end - - if DEBUG and ( v_0 > 0 or a ~= 0 ) then - print( " cart "..tostring(staticdata.uuid).. - ": a="..tostring(a).. - ",v_0="..tostring(v_0).. - ",x_0="..tostring(x_0).. - ",timestep="..tostring(timestep).. - ",dir="..tostring(staticdata.dir).. - ",connected_at="..tostring(staticdata.connected_at).. - ",distance="..tostring(staticdata.distance) - ) - end - - -- Not moving - if a == 0 and v_0 == 0 then return 0 end - - -- Movement equation with acceleration: x_1 = x_0 + v_0 * t + 0.5 * a * t*t - local timestep - local stops_in_block = false - local inside = v_0 * v_0 + 2 * a * remaining_in_block - if inside < 0 then - -- Would stop or reverse direction inside this block, calculate time to v_1 = 0 - timestep = -v_0 / a - stops_in_block = true - elseif a ~= 0 then - -- Setting x_1 = x_0 + remaining_in_block, and solving for t gives: - timestep = ( math.sqrt( v_0 * v_0 + 2 * a * remaining_in_block) - v_0 ) / a - else - timestep = remaining_in_block / v_0 - end - - -- Truncate timestep to remaining time delta - if timestep > dtime then - timestep = dtime - end - - -- Truncate timestep to prevent v_1 from being larger that speed_max - local v_max = mcl_minecarts.speed_max - if (v_0 ~= v_max) and ( v_0 + a * timestep > v_max) then - timestep = ( v_max - v_0 ) / a - end - - -- Prevent infinite loops - if timestep <= 0 then return 0 end - - -- Calculate v_1 taking v_max into account - local v_1 = v_0 + a * timestep - if v_1 > v_max then - v_1 = v_max - elseif v_1 < friction / 5 then - v_1 = 0 - end - - -- Calculate x_1 - local x_1 = x_0 + timestep * v_0 + 0.5 * a * timestep * timestep - - -- Update position and velocity of the minecart - staticdata.velocity = v_1 - staticdata.distance = x_1 - - if DEBUG and (1==0) and ( v_0 > 0 or a ~= 0 ) then - print( "- 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 - print( "Changing direction from "..tostring(staticdata.dir).." to "..tostring(next_dir)) - end - - -- Handle cart collisions - handle_cart_collision(self, pos, next_dir) - - -- Leave the old node - handle_cart_leave(self, old_pos, next_dir ) - - -- Enter the new node - handle_cart_enter(self, pos, next_dir) - - try_detach_minecart(self) - - -- Handle end of track - if next_dir == staticdata.dir * -1 and next_dir.y == 0 then - if DEBUG then print("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 then - -- Handle direction flip due to gravity - if DEBUG then print("Gravity flipped direction") end - - -- Velocity should be zero at this point - staticdata.velocity = 0 - - reverse_direction(self, staticdata) - - -- Intermediate movement - pos = staticdata.connected_at + staticdata.dir * staticdata.distance - else - -- Intermediate movement - pos = pos + staticdata.dir * staticdata.distance - end - - self.object:move_to(pos) - - -- Update cart orientation - update_cart_orientation(self,staticdata) - - -- Debug reporting - if DEBUG and ( v_0 > 0 or v_1 > 0 ) then - print( " cart #"..tostring(staticdata.uuid).. - ": a="..tostring(a).. - ",v_0="..tostring(v_0).. - ",v_1="..tostring(v_1).. - ",x_0="..tostring(x_0).. - ",x_1="..tostring(x_1).. - ",timestep="..tostring(timestep).. - ",dir="..tostring(staticdata.dir).. - ",pos="..tostring(pos).. - ",connected_at="..tostring(staticdata.connected_at).. - ",distance="..tostring(staticdata.distance) - ) - end - - -- Report the amount of time processed - return dtime - timestep -end - -local function do_movement( self, dtime ) - local staticdata = self._staticdata - - -- Allow the carts to be delay for the rest of the world to react before moving again - if ( staticdata.delay or 0 ) > dtime then - staticdata.delay = staticdata.delay - dtime - return - else - staticdata.delay = 0 - end - - -- Break long movements at block boundaries to make it - -- it impossible to jump across gaps due to server lag - -- causing large timesteps - while dtime > 0 do - local new_dtime = do_movement_step(self, dtime) - - -- Handle node watches here in steps to prevent server lag from changing behavior - handle_cart_node_watches(self, dtime - new_dtime) - - dtime = new_dtime - end -end - -local function do_detached_movement(self, dtime) - local staticdata = self._staticdata - - -- Make sure the object is still valid before trying to move it - if not self.object or not self.object:get_pos() then return end - - -- Apply physics - if mcl_physics then - mcl_physics.apply_entity_environmental_physics(self) - else - -- Simple physics - local friction = self.object:get_velocity() or vector.new(0,0,0) - friction.y = 0 - - local accel = vector.new(0,-9.81,0) -- gravity - accel = vector.add(accel, vector.multiply(friction,-0.9)) - self.object:set_acceleration(accel) - end - - local away = vector_away_from_players(self, staticdata) - if away then - local v = self.object:get_velocity() - self.object:set_velocity(v - away) - end - - -- Try to reconnect to rail - local pos_r = vector.round(self.object:get_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) - - -- Clear freebody movement - self.object:set_velocity(vector.new(0,0,0)) - self.object:set_acceleration(vector.new(0,0,0)) - end -end - local function detach_driver(self) if not self._driver then return @@ -569,133 +57,6 @@ local function detach_driver(self) end end -local function activate_normal_minecart(self) - detach_driver(self) - - -- Detach passenger - if self._passenger then - local mob = self._passenger.object - mob:set_detach() - end -end - -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 - -- Table for item-to-entity mapping. Keys: itemstring, Values: Corresponding entity ID local entity_mapping = {} @@ -741,11 +102,7 @@ function DEFAULT_CART_DEF:on_activate(staticdata, dtime_s) data.uuid = mcl_util.get_uuid(self.object) end local cd = get_cart_data(data.uuid) - if not cd then - cart_data[data.uuid] = data - cart_data_fail_cache[data.uuid] = nil - save_cart_data(data.uuid) - end + if not cd then update_cart_data(data) end -- Initialize if type(data) == "table" then @@ -875,13 +232,6 @@ function DEFAULT_CART_DEF:on_step(dtime) 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() - --[[ - detach_driver(self) - local drop = self.drop - for d = 1, #drop do - minetest.add_item(pos, drop[d]) - end - ]] self.object:remove() return end @@ -958,7 +308,7 @@ function mcl_minecarts.place_minecart(itemstack, pointed_thing, placer) local uuid = mcl_util.get_uuid(cart) data = make_staticdata( railtype, railpos, cart_dir ) data.uuid = uuid - cart_data[uuid] = data + update_cart_data(data) le._staticdata = data save_cart_data(le._staticdata.uuid) end @@ -1056,7 +406,7 @@ Register a minecart * on_activate_by_rail: Called when above activator rail * creative: If false, don't show in Creative Inventory ]] -local function register_minecart(def) +function mcl_minecarts.register_minecart(def) assert( def.drop, "def.drop is required parameter" ) assert( def.itemstring, "def.itemstring is required parameter" ) @@ -1081,427 +431,14 @@ local function register_minecart(def) minetest.register_craft(craft) end end +local register_minecart = mcl_minecarts.register_minecart --- Minecart -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 = function(self, clicker) - local name = clicker:get_player_name() - if not clicker or not clicker:is_player() then - return - end - local player_name = clicker:get_player_name() - if self._driver and player_name == self._driver then - --detach_driver(self) - elseif not self._driver and not clicker:get_player_control().sneak then - self._driver = player_name - self._start_pos = self.object:get_pos() - mcl_player.player_attached[player_name] = true - clicker:set_attach(self.object, "", vector.new(1,-1.75,-2), vector.new(0,0,0)) - mcl_player.player_attached[name] = true - 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(clicker, "actionbar", {text=S("Sneak to dismount"), color="white", stay=60}) - end - end, name) - end - end, - 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 -}) - --- Minecart with Chest -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) - --- Minecart with Furnace -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 - 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 -}) -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 -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 -}) - --- Minecart with Hopper -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) - local staticdata = self._staticdata - if (staticdata.hopper_delay or 0) > 0 then - return - end - - -- try to pull from containers into our inventory - local inv = mcl_entity_invs.load_inv(self,5) - local above_pos = pos + vector.new(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) - --- Minecart with TNT -local function activate_tnt_minecart(self, timer) - if self._boomtimer then - return - end - if timer then - self._boomtimer = timer - else - self._boomtimer = tnt.BOOMTIMER - end - self.object:set_properties({textures = { - "mcl_tnt_blink.png", - "mcl_tnt_blink.png", - "mcl_tnt_blink.png", - "mcl_tnt_blink.png", - "mcl_tnt_blink.png", - "mcl_tnt_blink.png", - "mcl_minecarts_minecart.png", - }}) - self._blinktimer = tnt.BLINKTIMER - minetest.sound_play("tnt_ignite", {pos = self.object:get_pos(), gain = 1.0, max_hear_distance = 15}, true) -end -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) - -- Update TNT stuff - if self._boomtimer then - -- Explode - self._boomtimer = self._boomtimer - dtime - local pos = self.object:get_pos() - if self._boomtimer <= 0 then - self.object:remove() - mcl_explosions.explode(pos, 6, { drop_chance = 1.0 }) - return - else - tnt.smoke_step(pos) - end - end - if self._blinktimer then - self._blinktimer = self._blinktimer - dtime - if self._blinktimer <= 0 then - self._blink = not self._blink - if self._blink then - self.object:set_properties({textures = - { - "default_tnt_top.png", - "default_tnt_bottom.png", - "default_tnt_side.png", - "default_tnt_side.png", - "default_tnt_side.png", - "default_tnt_side.png", - "mcl_minecarts_minecart.png", - }}) - else - self.object:set_properties({textures = - { - "mcl_tnt_blink.png", - "mcl_tnt_blink.png", - "mcl_tnt_blink.png", - "mcl_tnt_blink.png", - "mcl_tnt_blink.png", - "mcl_tnt_blink.png", - "mcl_minecarts_minecart.png", - }}) - end - self._blinktimer = tnt.BLINKTIMER - end - end - end, -}) +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") diff --git a/mods/ENTITIES/mcl_minecarts/carts/minecart.lua b/mods/ENTITIES/mcl_minecarts/carts/minecart.lua new file mode 100644 index 000000000..a28b0a8d3 --- /dev/null +++ b/mods/ENTITIES/mcl_minecarts/carts/minecart.lua @@ -0,0 +1,84 @@ +local modname = minetest.get_current_modname() +local S = minetest.get_translator(modname) + +local function activate_normal_minecart(self) + detach_driver(self) + + -- Detach passenger + if self._passenger then + local mob = self._passenger.object + mob:set_detach() + end +end + +mcl_minecarts.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 = function(self, clicker) + local name = clicker:get_player_name() + if not clicker or not clicker:is_player() then + return + end + local player_name = clicker:get_player_name() + if self._driver and player_name == self._driver then + --detach_driver(self) + elseif not self._driver and not clicker:get_player_control().sneak then + self._driver = player_name + self._start_pos = self.object:get_pos() + mcl_player.player_attached[player_name] = true + clicker:set_attach(self.object, "", vector.new(1,-1.75,-2), vector.new(0,0,0)) + mcl_player.player_attached[name] = true + 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(clicker, "actionbar", {text=S("Sneak to dismount"), color="white", stay=60}) + end + end, name) + end + end, + 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 +}) diff --git a/mods/ENTITIES/mcl_minecarts/carts/with_chest.lua b/mods/ENTITIES/mcl_minecarts/carts/with_chest.lua new file mode 100644 index 000000000..1f141eade --- /dev/null +++ b/mods/ENTITIES/mcl_minecarts/carts/with_chest.lua @@ -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) diff --git a/mods/ENTITIES/mcl_minecarts/carts/with_commandblock.lua b/mods/ENTITIES/mcl_minecarts/carts/with_commandblock.lua new file mode 100644 index 000000000..0dc05d490 --- /dev/null +++ b/mods/ENTITIES/mcl_minecarts/carts/with_commandblock.lua @@ -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 +}) diff --git a/mods/ENTITIES/mcl_minecarts/carts/with_furnace.lua b/mods/ENTITIES/mcl_minecarts/carts/with_furnace.lua new file mode 100644 index 000000000..f2c6355b6 --- /dev/null +++ b/mods/ENTITIES/mcl_minecarts/carts/with_furnace.lua @@ -0,0 +1,92 @@ +local modname = minetest.get_current_modname() +local S = minetest.get_translator(modname) + +-- Minecart with Furnace +mcl_minecarts.register_minecart({ + itemstring = "mcl_minecarts:furnace_minecart", + craft = { + output = "mcl_minecarts:furnace_minecart", + recipe = { + {"mcl_furnaces:furnace"}, + {"mcl_minecarts:minecart"}, + }, + }, + entity_id = "mcl_minecarts:furnace_minecart", + description = S("Minecart with Furnace"), + tt_help = nil, + longdesc = S("A minecart with furnace is a vehicle that travels on rails. It can propel itself with fuel."), + usagehelp = S("Place it on rails. If you give it some coal, the furnace will start burning for a long time and the minecart will be able to move itself. Punch it to get it moving.") .. "\n" .. + S("To obtain the minecart and furnace, punch them while holding down the sneak key."), + + initial_properties = { + mesh = "mcl_minecarts_minecart_block.b3d", + textures = { + "default_furnace_top.png", + "default_furnace_top.png", + "default_furnace_front.png", + "default_furnace_side.png", + "default_furnace_side.png", + "default_furnace_side.png", + "mcl_minecarts_minecart.png", + }, + }, + icon = "mcl_minecarts_minecart_furnace.png", + drop = {"mcl_minecarts:minecart", "mcl_furnaces:furnace"}, + on_rightclick = function(self, clicker) + local staticdata = self._staticdata + + -- Feed furnace with coal + if not clicker or not clicker:is_player() then + return + end + local held = clicker:get_wielded_item() + if minetest.get_item_group(held:get_name(), "coal") == 1 then + staticdata.fueltime = (staticdata.fueltime or 0) + 180 + + -- Trucate to 27 minutes (9 uses) + if staticdata.fueltime > 27*60 then + staticdata.fuel_time = 27*60 + end + + if not minetest.is_creative_enabled(clicker:get_player_name()) then + held:take_item() + local index = clicker:get_wield_index() + local inv = clicker:get_inventory() + inv:set_stack("main", index, held) + end + self.object:set_properties({textures = + { + "default_furnace_top.png", + "default_furnace_top.png", + "default_furnace_front_active.png", + "default_furnace_side.png", + "default_furnace_side.png", + "default_furnace_side.png", + "mcl_minecarts_minecart.png", + }}) + end + end, + on_activate_by_rail = nil, + creative = true, + _mcl_minecarts_on_step = function(self, dtime) + local staticdata = self._staticdata + + -- Update furnace stuff + if (staticdata.fueltime or 0) > 0 then + 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, +}) diff --git a/mods/ENTITIES/mcl_minecarts/carts/with_hopper.lua b/mods/ENTITIES/mcl_minecarts/carts/with_hopper.lua new file mode 100644 index 000000000..cb16b4cc6 --- /dev/null +++ b/mods/ENTITIES/mcl_minecarts/carts/with_hopper.lua @@ -0,0 +1,178 @@ +local modname = minetest.get_current_modname() +local modpath = minetest.get_modpath(modname) +local mod = mcl_minecarts +local S = minetest.get_translator(modname) + +local LOGGING_ON = minetest.settings:get_bool("mcl_logging_minecarts", false) +local function mcl_log(message) + if LOGGING_ON then + mcl_util.mcl_log(message, "[Minecarts]", true) + end +end + +local function hopper_take_item(self, dtime) + local pos = self.object:get_pos() + if not pos then return end + + if not self or self.name ~= "mcl_minecarts:hopper_minecart" then return end + + if mcl_util.check_dtime_timer(self, dtime, "hoppermc_take", 0.15) then + --minetest.log("The check timer was triggered: " .. dump(pos) .. ", name:" .. self.name) + else + --minetest.log("The check timer was not triggered") + return + end + + + local above_pos = vector.offset(pos, 0, 0.9, 0) + local objs = minetest.get_objects_inside_radius(above_pos, 1.25) + + if objs then + mcl_log("there is an itemstring. Number of objs: ".. #objs) + + for k, v in pairs(objs) do + local ent = v:get_luaentity() + + if ent and not ent._removed and ent.itemstring and ent.itemstring ~= "" then + local taken_items = false + + mcl_log("ent.name: " .. tostring(ent.name)) + mcl_log("ent pos: " .. tostring(ent.object:get_pos())) + + local inv = mcl_entity_invs.load_inv(self, 5) + if not inv then return false end + + local current_itemstack = ItemStack(ent.itemstring) + + mcl_log("inv. size: " .. self._inv_size) + if inv:room_for_item("main", current_itemstack) then + mcl_log("Room") + inv:add_item("main", current_itemstack) + ent.object:get_luaentity().itemstring = "" + ent.object:remove() + taken_items = true + else + mcl_log("no Room") + end + + if not taken_items then + local items_remaining = current_itemstack:get_count() + + -- This will take part of a floating item stack if no slot can hold the full amount + for i = 1, self._inv_size, 1 do + local stack = inv:get_stack("main", i) + + mcl_log("i: " .. tostring(i)) + mcl_log("Items remaining: " .. items_remaining) + mcl_log("Name: " .. tostring(stack:get_name())) + + if current_itemstack:get_name() == stack:get_name() then + mcl_log("We have a match. Name: " .. tostring(stack:get_name())) + + local room_for = stack:get_stack_max() - stack:get_count() + mcl_log("Room for: " .. tostring(room_for)) + + if room_for == 0 then + -- Do nothing + mcl_log("No room") + elseif room_for < items_remaining then + mcl_log("We have more items remaining than space") + + items_remaining = items_remaining - room_for + stack:set_count(stack:get_stack_max()) + inv:set_stack("main", i, stack) + taken_items = true + else + local new_stack_size = stack:get_count() + items_remaining + stack:set_count(new_stack_size) + mcl_log("We have more than enough space. Now holds: " .. new_stack_size) + + inv:set_stack("main", i, stack) + items_remaining = 0 + + ent.object:get_luaentity().itemstring = "" + ent.object:remove() + + taken_items = true + break + end + + mcl_log("Count: " .. tostring(stack:get_count())) + mcl_log("stack max: " .. tostring(stack:get_stack_max())) + --mcl_log("Is it empty: " .. stack:to_string()) + end + + if i == self._inv_size and taken_items then + mcl_log("We are on last item and still have items left. Set final stack size: " .. items_remaining) + current_itemstack:set_count(items_remaining) + --mcl_log("Itemstack2: " .. current_itemstack:to_string()) + ent.itemstring = current_itemstack:to_string() + end + end + end + + --Add in, and delete + if taken_items then + mcl_log("Saving") + mcl_entity_invs.save_inv(ent) + return taken_items + else + mcl_log("No need to save") + end + + end + end + end + + return false +end + +-- Minecart with Hopper +mod.register_minecart({ + itemstring = "mcl_minecarts:hopper_minecart", + craft = { + output = "mcl_minecarts:hopper_minecart", + recipe = { + {"mcl_hoppers:hopper"}, + {"mcl_minecarts:minecart"}, + }, + }, + entity_id = "mcl_minecarts:hopper_minecart", + description = S("Minecart with Hopper"), + tt_help = nil, + longdesc = nil, + usagehelp = nil, + initial_properties = { + mesh = "mcl_minecarts_minecart_hopper.b3d", + textures = { + "mcl_hoppers_hopper_inside.png", + "mcl_minecarts_minecart.png", + "mcl_hoppers_hopper_outside.png", + "mcl_hoppers_hopper_top.png", + }, + }, + icon = "mcl_minecarts_minecart_hopper.png", + drop = {"mcl_minecarts:minecart", "mcl_hoppers:hopper"}, + groups = { container = 1 }, + on_rightclick = nil, + on_activate_by_rail = nil, + _mcl_minecarts_on_enter = function(self, pos) + local staticdata = self._staticdata + if (staticdata.hopper_delay or 0) > 0 then + return + end + + -- try to pull from containers into our inventory + local inv = mcl_entity_invs.load_inv(self,5) + local above_pos = pos + vector.new(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) + diff --git a/mods/ENTITIES/mcl_minecarts/carts/with_tnt.lua b/mods/ENTITIES/mcl_minecarts/carts/with_tnt.lua new file mode 100644 index 000000000..fcbe03f8d --- /dev/null +++ b/mods/ENTITIES/mcl_minecarts/carts/with_tnt.lua @@ -0,0 +1,123 @@ +local modname = minetest.get_current_modname() +local modpath = minetest.get_modpath(modname) +local mod = mcl_minecarts +local S = minetest.get_translator(modname) + +-- Minecart with TNT +local function activate_tnt_minecart(self, timer) + if self._boomtimer then + return + end + if timer then + self._boomtimer = timer + else + self._boomtimer = tnt.BOOMTIMER + end + self.object:set_properties({textures = { + "mcl_tnt_blink.png", + "mcl_tnt_blink.png", + "mcl_tnt_blink.png", + "mcl_tnt_blink.png", + "mcl_tnt_blink.png", + "mcl_tnt_blink.png", + "mcl_minecarts_minecart.png", + }}) + self._blinktimer = tnt.BLINKTIMER + minetest.sound_play("tnt_ignite", {pos = self.object:get_pos(), gain = 1.0, max_hear_distance = 15}, true) +end +mod.register_minecart({ + itemstring = "mcl_minecarts:tnt_minecart", + craft = { + output = "mcl_minecarts:tnt_minecart", + recipe = { + {"mcl_tnt:tnt"}, + {"mcl_minecarts:minecart"}, + }, + }, + entity_id = "mcl_minecarts:tnt_minecart", + description = S("Minecart with TNT"), + tt_help = S("Vehicle for fast travel on rails").."\n"..S("Can be ignited by tools or powered activator rail"), + longdesc = S("A minecart with TNT is an explosive vehicle that travels on rail."), + usagehelp = S("Place it on rails. Punch it to move it. The TNT is ignited with a flint and steel or when the minecart is on an powered activator rail.") .. "\n" .. + S("To obtain the minecart and TNT, punch them while holding down the sneak key. You can't do this if the TNT was ignited."), + initial_properties = { + mesh = "mcl_minecarts_minecart_block.b3d", + textures = { + "default_tnt_top.png", + "default_tnt_bottom.png", + "default_tnt_side.png", + "default_tnt_side.png", + "default_tnt_side.png", + "default_tnt_side.png", + "mcl_minecarts_minecart.png", + }, + }, + icon = "mcl_minecarts_minecart_tnt.png", + drop = {"mcl_minecarts:minecart", "mcl_tnt:tnt"}, + on_rightclick = function(self, clicker) + -- Ingite + if not clicker or not clicker:is_player() then + return + end + if self._boomtimer then + return + end + local held = clicker:get_wielded_item() + if held:get_name() == "mcl_fire:flint_and_steel" then + if not minetest.is_creative_enabled(clicker:get_player_name()) then + held:add_wear(65535/65) -- 65 uses + local index = clicker:get_wield_index() + local inv = clicker:get_inventory() + inv:set_stack("main", index, held) + end + activate_tnt_minecart(self) + end + end, + on_activate_by_rail = activate_tnt_minecart, + creative = true, + _mcl_minecarts_on_step = function(self, dtime) + -- Update TNT stuff + if self._boomtimer then + -- Explode + self._boomtimer = self._boomtimer - dtime + local pos = self.object:get_pos() + if self._boomtimer <= 0 then + self.object:remove() + mcl_explosions.explode(pos, 6, { drop_chance = 1.0 }) + return + else + tnt.smoke_step(pos) + end + end + if self._blinktimer then + self._blinktimer = self._blinktimer - dtime + if self._blinktimer <= 0 then + self._blink = not self._blink + if self._blink then + self.object:set_properties({textures = + { + "default_tnt_top.png", + "default_tnt_bottom.png", + "default_tnt_side.png", + "default_tnt_side.png", + "default_tnt_side.png", + "default_tnt_side.png", + "mcl_minecarts_minecart.png", + }}) + else + self.object:set_properties({textures = + { + "mcl_tnt_blink.png", + "mcl_tnt_blink.png", + "mcl_tnt_blink.png", + "mcl_tnt_blink.png", + "mcl_tnt_blink.png", + "mcl_tnt_blink.png", + "mcl_minecarts_minecart.png", + }}) + end + self._blinktimer = tnt.BLINKTIMER + end + end + end, +}) diff --git a/mods/ENTITIES/mcl_minecarts/functions.lua b/mods/ENTITIES/mcl_minecarts/functions.lua index 0b0d664ce..6b8d067bc 100644 --- a/mods/ENTITIES/mcl_minecarts/functions.lua +++ b/mods/ENTITIES/mcl_minecarts/functions.lua @@ -312,3 +312,48 @@ function mcl_minecarts:get_rail_direction(pos_, dir, ctrl, old_switch, railtype) return dir end +function mcl_minecarts:update_cart_orientation() + local staticdata = self._staticdata + + -- constants + local _2_pi = math.pi * 2 + local pi = math.pi + local dir = staticdata.dir + + -- Calculate an angle from the x,z direction components + local rot_y = math.atan2( dir.x, dir.z ) + ( staticdata.rot_adjust or 0 ) + if rot_y < 0 then + rot_y = rot_y + _2_pi + end + + -- Check if the rotation is a 180 flip and don't change if so + local rot = self.object:get_rotation() + local diff = math.abs((rot_y - ( rot.y + pi ) % _2_pi) ) + if diff < 0.001 or diff > _2_pi - 0.001 then + -- Update rotation adjust and recalculate the rotation + staticdata.rot_adjust = ( ( staticdata.rot_adjust or 0 ) + pi ) % _2_pi + rot.y = math.atan2( dir.x, dir.z ) + ( staticdata.rot_adjust or 0 ) + else + rot.y = rot_y + end + + -- Forward/backwards tilt (pitch) + if dir.y < 0 then + rot.x = -0.25 * pi + elseif dir.y > 0 then + rot.x = 0.25 * pi + else + rot.x = 0 + end + + if ( staticdata.rot_adjust or 0 ) < 0.01 then + rot.x = -rot.x + end + if dir.z ~= 0 then + rot.x = -rot.x + end + + self.object:set_rotation(rot) +end + + diff --git a/mods/ENTITIES/mcl_minecarts/init.lua b/mods/ENTITIES/mcl_minecarts/init.lua index d0fbb0b64..8f8b6b4ef 100644 --- a/mods/ENTITIES/mcl_minecarts/init.lua +++ b/mods/ENTITIES/mcl_minecarts/init.lua @@ -1,11 +1,14 @@ local modname = minetest.get_current_modname() - +local modpath = minetest.get_modpath(modname) mcl_minecarts = {} local mod = mcl_minecarts -mcl_minecarts.modpath = minetest.get_modpath(modname) +mcl_minecarts.modpath = modpath + +-- Constants mcl_minecarts.speed_max = 10 mcl_minecarts.check_float_time = 15 +mcl_minecarts.FRICTION = 0.4 -dofile(mcl_minecarts.modpath.."/functions.lua") -dofile(mcl_minecarts.modpath.."/rails.lua") -dofile(mcl_minecarts.modpath.."/carts.lua") +for _,filename in pairs({"functions","rails","train","storage","carts"}) do + dofile(modpath.."/"..filename..".lua") +end diff --git a/mods/ENTITIES/mcl_minecarts/movement.lua b/mods/ENTITIES/mcl_minecarts/movement.lua new file mode 100644 index 000000000..da5f2b791 --- /dev/null +++ b/mods/ENTITIES/mcl_minecarts/movement.lua @@ -0,0 +1,496 @@ +local modname = minetest.get_current_modname() +local modpath = minetest.get_modpath(modname) +local mod = mcl_minecarts +local S = minetest.get_translator(modname) + +-- Constants +local DEBUG = false +local friction = mcl_minecarts.FRICTION + +-- Imports +local train_length = mod.train_length +local update_train = mod.update_train +local update_cart_orientation = mod.update_cart_orientation +local get_cart_data = mod.get_cart_data + +local function detach_minecart(self) + local staticdata = self._staticdata + + staticdata.connected_at = nil + self.object:set_velocity(staticdata.dir * staticdata.velocity) +end +mod.detach_minecart = detach_minecart + +local function try_detach_minecart(self) + local staticdata = self._staticdata + + local node = minetest.get_node(staticdata.connected_at) + if minetest.get_item_group(node.name, "rail") == 0 then + detach_minecart(self) + end +end + +--[[ + 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(self, pos, next_dir, event) + local staticdata = self._staticdata + + local dir = staticdata.dir + local right = vector.new( dir.z, dir.y, -dir.x) + local up = vector.new(0,1,0) + for _,check in ipairs(enter_exit_checks) do + 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, self, next_dir, pos) end + + -- global minecart hook + hook = mcl_minecarts[event..check[4]] + if hook then hook(check_pos, self, next_dir, node_def) end + end + end + + -- Handle cart-specific behaviors + local hook = self["_mcl_minecarts_"..event] + if hook then hook(self, pos) 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(self, pos, next_dir) + --print("entering "..tostring(pos)) + set_metadata_cart_status(pos, self._staticdata.uuid, 1) + handle_cart_enter_exit(self, pos, next_dir, "on_enter" ) +end +local function handle_cart_leave(self, pos, next_dir) + --print("leaving "..tostring(pos)) + set_metadata_cart_status(pos, self._staticdata.uuid, nil) + handle_cart_enter_exit(self, pos, next_dir, "on_leave" ) +end +local function handle_cart_node_watches(self, dtime) + local staticdata = self._staticdata + local watches = staticdata.node_watches or {} + local new_watches = {} + for _,node_pos in ipairs(watches) do + 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, self, dtime) then + new_watches[#new_watches+1] = node_pos + end + end + end + + staticdata.node_watches = new_watches +end + +local function handle_cart_collision(cart1, prev_pos, next_dir) + -- 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 + if not get_cart_data(uuid) 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 + minetest.log("action","cart #"..cart1._staticdata.uuid.." collided with cart #"..cart_uuid.." at "..tostring(pos)) + + local cart2_aoid = mcl_util.get_active_object_id_from_uuid(cart_uuid) + local cart2 = minetest.luaentities[cart2_aoid] + if not cart2 then return end + + -- Standard Collision Handling + local cart1_staticdata = cart1._staticdata + local cart2_staticdata = cart2._staticdata + + local u1 = cart1_staticdata.velocity + local u2 = cart2_staticdata.velocity + local m1 = cart1_staticdata.mass + local m2 = cart2_staticdata.mass + + if u2 == 0 and u1 < 1 and train_length(cart1) < 3 then + -- Link carts + cart2_staticdata.behind = cart1_staticdata.uuid + cart1_staticdata.ahead = cart1_staticdata.uuid + 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(self, staticdata) + local objs = minetest.get_objects_inside_radius(self.object:get_pos(), 1.1) + for n=1,#objs do + local obj = objs[n] + local player_name = obj:get_player_name() + if player_name and player_name ~= "" and not ( self._driver and self._driver == player_name ) then + return obj:get_pos() - self.object:get_pos() + end + end + + return nil +end + +local function direction_away_from_players(self, staticdata) + local diff = vector_away_from_players(self, staticdata) + if not diff then return 0 end + + local length = vector.distance(vector.new(0,0,0),diff) + local vec = diff / length + local force = vector.dot( vec, vector.normalize(staticdata.dir) ) + + -- Check if this would push past the end of the track and don't move it it would + -- This prevents an oscillation that would otherwise occur + local dir = staticdata.dir + if force > 0 then + dir = -dir + end + if mcl_minecarts:is_rail( staticdata.connected_at + dir ) then + if force > 0.5 then + return -length * 4 + elseif force < -0.5 then + return length * 4 + end + end + return 0 +end + +local function calculate_acceleration(self, 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 max_vel = mcl_minecarts.speed_max + + if self._go_forward then + acceleration = 4 + elseif self._brake 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 max_vel ) then + -- Standard friction + elseif node_def and node_def._rail_acceleration then + acceleration = node_def._rail_acceleration * 4 + end + + -- Factor in gravity after everything else + local gravity_strength = 2.45 --friction * 5 + if staticdata.dir.y < 0 then + acceleration = gravity_strength - friction + elseif staticdata.dir.y > 0 then + acceleration = -gravity_strength + friction + end + + return acceleration +end + +local function reverse_direction(self, staticdata) + -- Complete moving thru this block into the next, reverse direction, and put us back at the same position we were at + local next_dir = -staticdata.dir + staticdata.connected_at = staticdata.connected_at + staticdata.dir + staticdata.distance = 1 - staticdata.distance + + -- recalculate direction + local next_dir,_ = mcl_minecarts:get_rail_direction(staticdata.connected_at, next_dir, nil, nil, staticdata.railtype) + staticdata.dir = next_dir +end + +local function do_movement_step(self, dtime) + local staticdata = self._staticdata + if not staticdata.connected_at then return 0 end + + -- Calculate timestep remaiing in this block + local x_0 = staticdata.distance or 0 + local remaining_in_block = 1 - x_0 + local a = calculate_acceleration(self, staticdata) + local v_0 = staticdata.velocity + + -- Repel minecarts + local away = direction_away_from_players(self, staticdata) + if away > 0 then + v_0 = away + elseif away < 0 then + reverse_direction(self, staticdata) + v_0 = -away + end + + if DEBUG and ( v_0 > 0 or a ~= 0 ) then + print( " cart "..tostring(staticdata.uuid).. + ": a="..tostring(a).. + ",v_0="..tostring(v_0).. + ",x_0="..tostring(x_0).. + ",timestep="..tostring(timestep).. + ",dir="..tostring(staticdata.dir).. + ",connected_at="..tostring(staticdata.connected_at).. + ",distance="..tostring(staticdata.distance) + ) + end + + -- Not moving + if a == 0 and v_0 == 0 then return 0 end + + -- Movement equation with acceleration: x_1 = x_0 + v_0 * t + 0.5 * a * t*t + local timestep + local stops_in_block = false + local inside = v_0 * v_0 + 2 * a * remaining_in_block + if inside < 0 then + -- Would stop or reverse direction inside this block, calculate time to v_1 = 0 + timestep = -v_0 / a + stops_in_block = true + elseif a ~= 0 then + -- Setting x_1 = x_0 + remaining_in_block, and solving for t gives: + timestep = ( math.sqrt( v_0 * v_0 + 2 * a * remaining_in_block) - v_0 ) / a + else + timestep = remaining_in_block / v_0 + end + + -- Truncate timestep to remaining time delta + if timestep > dtime then + timestep = dtime + end + + -- Truncate timestep to prevent v_1 from being larger that speed_max + local v_max = mcl_minecarts.speed_max + if (v_0 ~= v_max) and ( v_0 + a * timestep > v_max) then + timestep = ( v_max - v_0 ) / a + end + + -- Prevent infinite loops + if timestep <= 0 then return 0 end + + -- Calculate v_1 taking v_max into account + local v_1 = v_0 + a * timestep + if v_1 > v_max then + v_1 = v_max + elseif v_1 < friction / 5 then + v_1 = 0 + end + + -- Calculate x_1 + local x_1 = x_0 + timestep * v_0 + 0.5 * a * timestep * timestep + + -- Update position and velocity of the minecart + staticdata.velocity = v_1 + staticdata.distance = x_1 + + if DEBUG and (1==0) and ( v_0 > 0 or a ~= 0 ) then + print( "- 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 + print( "Changing direction from "..tostring(staticdata.dir).." to "..tostring(next_dir)) + end + + -- Handle cart collisions + handle_cart_collision(self, pos, next_dir) + + -- Leave the old node + handle_cart_leave(self, old_pos, next_dir ) + + -- Enter the new node + handle_cart_enter(self, pos, next_dir) + + try_detach_minecart(self) + + -- Handle end of track + if next_dir == staticdata.dir * -1 and next_dir.y == 0 then + if DEBUG then print("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 then + -- Handle direction flip due to gravity + if DEBUG then print("Gravity flipped direction") end + + -- Velocity should be zero at this point + staticdata.velocity = 0 + + reverse_direction(self, staticdata) + + -- Intermediate movement + pos = staticdata.connected_at + staticdata.dir * staticdata.distance + else + -- Intermediate movement + pos = pos + staticdata.dir * staticdata.distance + end + + self.object:move_to(pos) + + -- Update cart orientation + update_cart_orientation(self) + + -- Debug reporting + if DEBUG and ( v_0 > 0 or v_1 > 0 ) then + print( " cart #"..tostring(staticdata.uuid).. + ": a="..tostring(a).. + ",v_0="..tostring(v_0).. + ",v_1="..tostring(v_1).. + ",x_0="..tostring(x_0).. + ",x_1="..tostring(x_1).. + ",timestep="..tostring(timestep).. + ",dir="..tostring(staticdata.dir).. + ",pos="..tostring(pos).. + ",connected_at="..tostring(staticdata.connected_at).. + ",distance="..tostring(staticdata.distance) + ) + end + + -- Report the amount of time processed + return dtime - timestep +end + +local function do_movement( self, dtime ) + local staticdata = self._staticdata + + -- Allow the carts to be delay for the rest of the world to react before moving again + if ( staticdata.delay or 0 ) > dtime then + staticdata.delay = staticdata.delay - dtime + return + else + staticdata.delay = 0 + end + + -- Break long movements at block boundaries to make it + -- it impossible to jump across gaps due to server lag + -- causing large timesteps + while dtime > 0 do + local new_dtime = do_movement_step(self, dtime) + + update_train(self) + + -- Handle node watches here in steps to prevent server lag from changing behavior + handle_cart_node_watches(self, dtime - new_dtime) + + dtime = new_dtime + end +end + +local function do_detached_movement(self, dtime) + local staticdata = self._staticdata + + -- Make sure the object is still valid before trying to move it + if not self.object or not self.object:get_pos() then return end + + -- Apply physics + if mcl_physics then + mcl_physics.apply_entity_environmental_physics(self) + else + -- Simple physics + local friction = self.object:get_velocity() or vector.new(0,0,0) + friction.y = 0 + + local accel = vector.new(0,-9.81,0) -- gravity + accel = vector.add(accel, vector.multiply(friction,-0.9)) + self.object:set_acceleration(accel) + end + + local away = vector_away_from_players(self, staticdata) + if away then + local v = self.object:get_velocity() + self.object:set_velocity(v - away) + end + + -- Try to reconnect to rail + local pos_r = vector.round(self.object:get_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) + + -- Clear freebody movement + self.object:set_velocity(vector.new(0,0,0)) + self.object:set_acceleration(vector.new(0,0,0)) + end +end + +--return do_movement, do_detatched_movement +return do_movement,do_detached_movement,handle_cart_enter + diff --git a/mods/ENTITIES/mcl_minecarts/rails.lua b/mods/ENTITIES/mcl_minecarts/rails.lua index d0a2eaa4a..b3d304b9b 100644 --- a/mods/ENTITIES/mcl_minecarts/rails.lua +++ b/mods/ENTITIES/mcl_minecarts/rails.lua @@ -188,7 +188,7 @@ function mod.register_rail(itemstring, ndef) 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)) + --print("registering rail "..itemstring.." with definition: "..dump(ndef)) -- Make registrations minetest.register_node(itemstring, ndef) diff --git a/mods/ENTITIES/mcl_minecarts/storage.lua b/mods/ENTITIES/mcl_minecarts/storage.lua new file mode 100644 index 000000000..f4ee1af51 --- /dev/null +++ b/mods/ENTITIES/mcl_minecarts/storage.lua @@ -0,0 +1,37 @@ +local storage = minetest.get_mod_storage() +local mod = mcl_minecarts + +local cart_data = {} +local cart_data_fail_cache = {} + +function mod.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 + end + + cart_data[uuid] = data + return data +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 + diff --git a/mods/ENTITIES/mcl_minecarts/train.lua b/mods/ENTITIES/mcl_minecarts/train.lua new file mode 100644 index 000000000..4e5163f01 --- /dev/null +++ b/mods/ENTITIES/mcl_minecarts/train.lua @@ -0,0 +1,5 @@ +function mcl_minecarts.update_train(cart) +end + +function mcl_minecarts.train_length(cart) +end