techage_modpack/minecart/cart_api.lua
2020-05-31 22:31:18 +02:00

451 lines
12 KiB
Lua

--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
Cart API for external cart definitions on a node based model
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
-- register cart here, because entity is already registered
minecart.register_cart_names("minecart:cart", "minecart:cart")
function minecart.register_cart_entity(entity_name, node_name, entity_def)
entity_def.velocity = {x=0, y=0, z=0} -- only used on punch
entity_def.old_dir = {x=1, y=0, z=0} -- random value to start the cart on punch
entity_def.old_pos = nil
entity_def.old_switch = 0
entity_def.node_name = node_name
minetest.register_entity(entity_name, entity_def)
-- register node for punching
minecart.register_cart_names(node_name, entity_name)
end
local function switch_to_node(pos, node_name, owner, param2, cargo)
local node = minetest.get_node(pos)
local rail = node.name
local ndef = minetest.registered_nodes[node_name]
if ndef then
node.name = node_name
node.param2 = param2
minetest.add_node(pos, node)
M(pos):set_string("removed_rail", rail)
M(pos):set_string("owner", owner)
if ndef.after_place_node then
ndef.after_place_node(pos)
end
if cargo and ndef.set_cargo then
ndef.set_cargo(pos, cargo)
end
end
end
function minecart.node_on_place(itemstack, placer, pointed_thing, node_name)
local under = pointed_thing.under
local node = minetest.get_node(under)
local udef = minetest.registered_nodes[node.name]
if udef and udef.on_rightclick and
not (placer and placer:is_player() and
placer:get_player_control().sneak) then
return udef.on_rightclick(under, node, placer, itemstack,
pointed_thing) or itemstack
end
if not pointed_thing.type == "node" then
return
end
local owner = placer:get_player_name()
local param2 = minetest.dir_to_facedir(placer:get_look_dir())
if carts:is_rail(pointed_thing.under) then
switch_to_node(pointed_thing.under, node_name, owner, param2)
elseif carts:is_rail(pointed_thing.above) then
switch_to_node(pointed_thing.above, node_name, owner, param2)
else
return
end
minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
{pos = pointed_thing.above})
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(placer:get_player_name())) then
itemstack:take_item()
end
return itemstack
end
function minecart.node_on_punch(pos, node, puncher, pointed_thing, entity_name, dir)
local ndef = minetest.registered_nodes[node.name]
local cargo = {}
-- Player digs cart by sneak-punch
if puncher and puncher:get_player_control().sneak then
-- Pick up cart
if ndef.can_dig and ndef.can_dig(pos, puncher) then
local inv = puncher:get_inventory()
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(puncher:get_player_name()))
or not inv:contains_item("main", node.name) then
local leftover = inv:add_item("main", node.name)
-- If no room in inventory add a replacement cart to the world
if not leftover:is_empty() then
minetest.add_item(pos, leftover)
end
end
node.name = M(pos):get_string("removed_rail")
if node.name == "" then
node.name = "carts:rail"
end
minetest.remove_node(pos)
minetest.add_node(pos, node)
end
return
end
-- start cart
node.name = M(pos):get_string("removed_rail")
if node.name ~= "" then
if ndef.get_cargo then
cargo = ndef.get_cargo(pos)
end
minetest.add_node(pos, node)
local obj = minetest.add_entity(pos, entity_name)
local owner = puncher and puncher:get_player_name()
minecart.add_cart_to_monitoring(obj, owner, cargo)
obj:punch(puncher or obj, 1, {
full_punch_interval = 1.0,
damage_groups = {fleshy = 1},
}, dir)
end
end
function minecart:on_activate(staticdata, dtime_s)
self.object:set_armor_groups({immortal=1})
end
function minecart:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
--print("on_punch", direction)
local pos = self.object:get_pos()
local vel = self.object:get_velocity()
local stopped = vector.equals(vel, {x=0, y=0, z=0})
-- running carts can't be punched
if not stopped then
return
end
if not self.railtype then
local node = minetest.get_node(pos).name
self.railtype = minetest.get_item_group(node, "connect_to_raillike")
end
-- Punched by non-authorized player
if puncher and self.owner and self.owner ~= puncher:get_player_name()
and not minetest.check_player_privs(puncher:get_player_name(), "minecart") then
return
end
-- Punched by non-player
if not puncher or not puncher:is_player() then
local cart_dir = carts:get_rail_direction(pos, direction, nil, nil, self.railtype)
if vector.equals(cart_dir, {x=0, y=0, z=0}) then
return
end
self.velocity = vector.multiply(cart_dir, 2)
self.punched = true
return
end
-- Player digs cart by sneak-punch
if puncher:get_player_control().sneak then
if self.sound_handle then
minetest.sound_stop(self.sound_handle)
end
-- Pick up cart
local node_name = self.node_name or "minecart:cart"
local inv = puncher:get_inventory()
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(puncher:get_player_name()))
or not inv:contains_item("main", node_name) then
local leftover = inv:add_item("main", node_name)
-- If no room in inventory add a replacement cart to the world
if not leftover:is_empty() then
minetest.add_item(self.object:get_pos(), leftover)
end
end
minecart.on_dig(self)
self.object:remove()
return
end
local punch_dir = carts:velocity_to_dir(puncher:get_look_dir())
punch_dir.y = 0
local cart_dir = carts:get_rail_direction(pos, punch_dir, nil, nil, self.railtype)
if vector.equals(cart_dir, {x=0, y=0, z=0}) then
return
end
self.velocity = vector.multiply(cart_dir, 2)
self.old_dir = cart_dir
self.punched = true
end
-- sound refresh interval = 1.0sec
local function rail_sound(self, dtime)
if not self.sound_ttl then
self.sound_ttl = 1.0
return
elseif self.sound_ttl > 0 then
self.sound_ttl = self.sound_ttl - dtime
return
end
self.sound_ttl = 1.0
if self.sound_handle then
local handle = self.sound_handle
self.sound_handle = nil
minetest.after(0.2, minetest.sound_stop, handle)
end
local vel = self.object:get_velocity() or {x=0, y=0, z=0}
local speed = vector.length(vel)
if speed > 0 then
self.sound_handle = minetest.sound_play(
"carts_cart_moving", {
object = self.object,
gain = (speed / carts.speed_max) / 2,
loop = true,
})
end
end
local function get_railparams(pos)
local node = minetest.get_node(pos)
return carts.railparams[node.name] or {}
end
local function rail_on_step(self, dtime)
local vel = self.object:get_velocity()
local pos = self.object:get_pos()
if self.punched then
minecart.start_run(self, pos, vel, self.driver)
vel = vector.add(vel, self.velocity)
self.object:set_velocity(vel)
self.old_dir.y = 0
elseif vector.equals(vel, {x=0, y=0, z=0}) then
if minecart.get_route_key(pos) then
local cargo = minecart.stopped(self, pos)
local param2 = minetest.dir_to_facedir(self.old_dir)
switch_to_node(vector.round(pos), self.node_name, self.owner, param2, cargo)
minecart.on_dig(self)
self.object:remove()
end
return
end
-- cart position correction on slopes
local rot = self.object:get_rotation()
if rot.x ~= 0 then
pos.y = pos.y - 0.5
end
local cart_dir = carts:velocity_to_dir(vel)
local same_dir = vector.equals(cart_dir, self.old_dir)
local update = {}
if self.old_pos and not self.punched and same_dir then
local flo_pos = vector.round(pos)
local flo_old = vector.round(self.old_pos)
if vector.equals(flo_pos, flo_old) then
-- Do not check one node multiple times
return
end
end
local ctrl, player
local stop_wiggle = false
if self.old_pos and same_dir then
-- Detection for "skipping" nodes (perhaps use average dtime?)
-- It's sophisticated enough to take the acceleration in account
local acc = self.object:get_acceleration()
local distance = dtime * (vector.length(vel) + 0.5 * dtime * vector.length(acc))
local new_pos, new_dir = carts:pathfinder(
pos, self.old_pos, self.old_dir, distance, ctrl,
self.old_switch, self.railtype
)
if new_pos then
-- No rail found: set to the expected position
pos = new_pos
update.pos = true
cart_dir = new_dir
end
elseif self.old_pos and self.old_dir.y ~= 1 and not self.punched then
-- Stop wiggle
stop_wiggle = true
end
local railparams
-- dir: New moving direction of the cart
-- switch_keys: Currently pressed L/R key, used to ignore the key on the next rail node
local dir, switch_keys = carts:get_rail_direction(
pos, cart_dir, ctrl, self.old_switch, self.railtype
)
------------------------------- changed
if switch_keys then
minecart.set_junction(self, pos, dir, switch_keys)
else
dir, switch_keys = minecart.get_junction(self, pos, dir)
end
------------------------------- changed
local dir_changed = not vector.equals(dir, self.old_dir)
local new_acc = {x=0, y=0, z=0}
if stop_wiggle or vector.equals(dir, {x=0, y=0, z=0}) then
vel = {x = 0, y = 0, z = 0}
local pos_r = vector.round(pos)
if not carts:is_rail(pos_r, self.railtype)
and self.old_pos then
pos = self.old_pos
elseif not stop_wiggle then
pos = pos_r
else
pos.y = math.floor(pos.y + 0.5)
end
update.pos = true
update.vel = true
else
-- Direction change detected
if dir_changed then
vel = vector.multiply(dir, math.abs(vel.x + vel.z))
update.vel = true
if dir.y ~= self.old_dir.y then
pos = vector.round(pos)
update.pos = true
end
end
-- Center on the rail
if dir.z ~= 0 and math.floor(pos.x + 0.5) ~= pos.x then
pos.x = math.floor(pos.x + 0.5)
update.pos = true
end
if dir.x ~= 0 and math.floor(pos.z + 0.5) ~= pos.z then
pos.z = math.floor(pos.z + 0.5)
update.pos = true
end
-- Slow down or speed up..
local acc = dir.y * -4.0
-- Get rail for corrected position
railparams = get_railparams(pos)
-- no need to check for railparams == nil since we always make it exist.
local speed_mod = railparams.acceleration
if speed_mod and speed_mod ~= 0 then
-- Try to make it similar to the original carts mod
acc = acc + speed_mod
else
-- Handbrake or coast
if ctrl and ctrl.down then
acc = acc - 3
else
acc = acc - 0.4
end
end
new_acc = vector.multiply(dir, acc)
end
-- Limits
local max_vel = carts.speed_max
for _, v in pairs({"x","y","z"}) do
if math.abs(vel[v]) > max_vel then
vel[v] = carts:get_sign(vel[v]) * max_vel
new_acc[v] = 0
update.vel = true
end
end
self.object:set_acceleration(new_acc)
self.old_pos = vector.round(pos)
if not vector.equals(dir, {x=0, y=0, z=0}) and not stop_wiggle then
self.old_dir = vector.new(dir)
end
self.old_switch = switch_keys
if self.punched then
self.punched = false
update.vel = true
end
railparams = railparams or get_railparams(pos)
if not (update.vel or update.pos) then
return
end
local yaw = 0
if self.old_dir.x < 0 then
yaw = math.pi/2*3
elseif self.old_dir.x > 0 then
yaw = math.pi/2
elseif self.old_dir.z < 0 then
yaw = math.pi
end
--self.object:set_yaw(yaw * math.pi)
local pitch = 0
if self.old_dir.z ~= 0 then
if dir.y == -1 then
pitch = -math.pi/4
elseif dir.y == 1 then
pitch = math.pi/4
end
else
if dir.y == -1 then
pitch = math.pi/4
elseif dir.y == 1 then
pitch = -math.pi/4
end
end
self.object:set_rotation({x = pitch, y = yaw, z = 0})
-- cart position correction on slopes
if pitch ~= 0 then
pos.y = pos.y + 0.5
update.pos = true
end
if update.vel then
self.object:set_velocity(vel)
end
if update.pos then
if dir_changed then
self.object:set_pos(pos)
else
self.object:move_to(pos)
end
end
end
function minecart:on_step(dtime)
rail_on_step(self, dtime)
rail_sound(self, dtime)
end