2015-06-29 20:55:56 +03:00
--
-- Helper functions
--
2017-06-13 00:21:41 +03:00
local function is_water ( pos )
local nn = minetest.get_node ( pos ) . name
return minetest.get_item_group ( nn , " water " ) ~= 0
end
local function get_sign ( i )
if i == 0 then
return 0
else
return i / math.abs ( i )
end
end
local function get_velocity ( v , yaw , y )
local x = - math.sin ( yaw ) * v
local z = math.cos ( yaw ) * v
return { x = x , y = y , z = z }
end
local function get_v ( v )
return math.sqrt ( v.x ^ 2 + v.z ^ 2 )
end
2017-06-20 16:21:44 +03:00
local boat_visual_size = { x = 3 , y = 3 }
-- Note: This mod assumes the default player visual_size is {x=1, y=1}
local driver_visual_size = { x = 1 / boat_visual_size.x , y = 1 / boat_visual_size.y }
2017-07-08 18:55:46 +03:00
local paddling_speed = 22
local boat_y_offset = 0.35
2017-06-20 16:21:44 +03:00
2017-06-13 00:21:41 +03:00
--
-- Boat entity
--
local boat = {
physical = true ,
-- Warning: Do not change the position of the collisionbox top surface,
-- lowering it causes the boat to fall through the world if underwater
collisionbox = { - 0.5 , - 0.35 , - 0.5 , 0.5 , 0.3 , 0.5 } ,
visual = " mesh " ,
2017-06-13 02:39:21 +03:00
mesh = " mcl_boats_boat.b3d " ,
2017-06-13 00:50:42 +03:00
textures = { " mcl_boats_texture_oak_boat.png " } ,
2017-06-20 16:21:44 +03:00
visual_size = boat_visual_size ,
2017-06-13 00:50:42 +03:00
2017-06-13 01:26:17 +03:00
_driver = nil , -- Attached driver (player) or nil if none
_v = 0 , -- Speed
_last_v = 0 , -- Temporary speed variable
_removed = false , -- If true, boat entity is considered removed (e.g. after punch) and should be ignored
_itemstring = " mcl_boats:boat " , -- Itemstring of the boat item (implies boat type)
2017-07-08 18:26:07 +03:00
_animation = 0 , -- 0: not animated; 1: paddling forwards; -1: paddling forwards
2017-06-13 00:21:41 +03:00
}
function boat . on_rightclick ( self , clicker )
if not clicker or not clicker : is_player ( ) then
return
end
local name = clicker : get_player_name ( )
2017-06-13 01:26:17 +03:00
if self._driver and clicker == self._driver then
self._driver = nil
2017-06-13 00:21:41 +03:00
clicker : set_detach ( )
2017-06-20 16:21:44 +03:00
clicker : set_properties ( { visual_size = { x = 1 , y = 1 } } )
2017-06-13 00:21:41 +03:00
mcl_player.player_attached [ name ] = false
mcl_player.player_set_animation ( clicker , " stand " , 30 )
2019-02-01 08:33:07 +03:00
local pos = clicker : get_pos ( )
2017-06-13 00:21:41 +03:00
pos = { x = pos.x , y = pos.y + 0.2 , z = pos.z }
2019-03-06 06:38:57 +03:00
clicker : set_pos ( pos )
2017-06-13 01:26:17 +03:00
elseif not self._driver then
2017-06-13 00:21:41 +03:00
local attach = clicker : get_attach ( )
if attach and attach : get_luaentity ( ) then
local luaentity = attach : get_luaentity ( )
2017-06-13 01:26:17 +03:00
if luaentity._driver then
luaentity._driver = nil
2017-06-13 00:21:41 +03:00
end
clicker : set_detach ( )
2017-06-20 16:21:44 +03:00
clicker : set_properties ( { visual_size = { x = 1 , y = 1 } } )
2017-06-13 00:21:41 +03:00
end
2017-06-13 01:26:17 +03:00
self._driver = clicker
2017-06-13 00:21:41 +03:00
clicker : set_attach ( self.object , " " ,
2019-03-06 07:11:49 +03:00
{ x = 0 , y = 0.42 , z = - 1 } , { x = 0 , y = 0 , z = 0 } )
2017-06-20 16:21:44 +03:00
clicker : set_properties ( { visual_size = driver_visual_size } )
2017-06-13 00:21:41 +03:00
mcl_player.player_attached [ name ] = true
2018-06-03 17:44:37 +03:00
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 )
2017-06-13 00:21:41 +03:00
end
2018-06-03 17:44:37 +03:00
end , name )
2019-03-06 06:38:57 +03:00
clicker : set_look_horizontal ( self.object : get_yaw ( ) )
2017-06-13 00:21:41 +03:00
end
end
function boat . on_activate ( self , staticdata , dtime_s )
self.object : set_armor_groups ( { immortal = 1 } )
2017-06-13 01:14:17 +03:00
local data = minetest.deserialize ( staticdata )
if type ( data ) == " table " then
2017-06-13 01:26:17 +03:00
self._v = data.v
self._last_v = self._v
2017-06-13 01:14:17 +03:00
self._itemstring = data.itemstring
self.object : set_properties ( { textures = data.textures } )
2017-06-13 00:21:41 +03:00
end
end
function boat . get_staticdata ( self )
2017-06-13 01:14:17 +03:00
return minetest.serialize ( {
2017-06-13 01:26:17 +03:00
v = self._v ,
2017-06-13 01:14:17 +03:00
itemstring = self._itemstring ,
textures = self.object : get_properties ( ) . textures
} )
2017-06-13 00:21:41 +03:00
end
function boat . on_punch ( self , puncher )
2017-06-13 01:26:17 +03:00
if not puncher or not puncher : is_player ( ) or self._removed then
2017-06-13 00:21:41 +03:00
return
end
2017-06-13 01:26:17 +03:00
if self._driver and puncher == self._driver then
self._driver = nil
2017-06-13 00:21:41 +03:00
puncher : set_detach ( )
2017-06-20 16:21:44 +03:00
puncher : set_properties ( { visual_size = { x = 1 , y = 1 } } )
2017-06-13 00:21:41 +03:00
mcl_player.player_attached [ puncher : get_player_name ( ) ] = false
end
2017-06-13 01:26:17 +03:00
if not self._driver then
self._removed = true
2017-06-13 00:50:42 +03:00
-- Drop boat as item on the ground after punching
2017-08-09 17:17:00 +03:00
if not minetest.settings : get_bool ( " creative_mode " ) then
2019-02-01 08:33:07 +03:00
minetest.add_item ( self.object : get_pos ( ) , self._itemstring )
2019-02-06 23:56:52 +03:00
else
local inv = puncher : get_inventory ( )
if not inv : contains_item ( " main " , self._itemstring ) then
inv : add_item ( " main " , self._itemstring )
end
2017-06-13 00:21:41 +03:00
end
2017-06-13 00:56:48 +03:00
self.object : remove ( )
2017-06-13 00:21:41 +03:00
end
end
function boat . on_step ( self , dtime )
2019-03-06 06:38:57 +03:00
self._v = get_v ( self.object : get_velocity ( ) ) * get_sign ( self._v )
2017-06-13 01:26:17 +03:00
if self._driver then
local ctrl = self._driver : get_player_control ( )
2019-03-06 06:38:57 +03:00
local yaw = self.object : get_yaw ( )
2017-06-13 00:21:41 +03:00
if ctrl.up then
2017-07-08 18:26:07 +03:00
-- Forwards
2017-06-13 01:26:17 +03:00
self._v = self._v + 0.1
2017-07-08 18:26:07 +03:00
-- Paddling animation
if self._animation ~= 1 then
self.object : set_animation ( { x = 0 , y = 40 } , paddling_speed , 0 , true )
self._animation = 1
end
2017-06-13 00:21:41 +03:00
elseif ctrl.down then
2017-07-08 18:26:07 +03:00
-- Backwards
2017-06-13 01:26:17 +03:00
self._v = self._v - 0.1
2017-07-08 18:26:07 +03:00
-- Paddling animation, reversed
if self._animation ~= - 1 then
self.object : set_animation ( { x = 0 , y = 40 } , - paddling_speed , 0 , true )
self._animation = - 1
end
else
-- Stop paddling animation if no control pressed
if self._animation ~= 0 then
self.object : set_animation ( { x = 0 , y = 40 } , 0 , 0 , true )
self._animation = 0
end
2017-06-13 00:21:41 +03:00
end
if ctrl.left then
2017-06-13 01:26:17 +03:00
if self._v < 0 then
2019-03-06 06:38:57 +03:00
self.object : set_yaw ( yaw - ( 1 + dtime ) * 0.03 )
2017-06-13 00:21:41 +03:00
else
2019-03-06 06:38:57 +03:00
self.object : set_yaw ( yaw + ( 1 + dtime ) * 0.03 )
2017-06-13 00:21:41 +03:00
end
elseif ctrl.right then
2017-06-13 01:26:17 +03:00
if self._v < 0 then
2019-03-06 06:38:57 +03:00
self.object : set_yaw ( yaw + ( 1 + dtime ) * 0.03 )
2017-06-13 00:21:41 +03:00
else
2019-03-06 06:38:57 +03:00
self.object : set_yaw ( yaw - ( 1 + dtime ) * 0.03 )
2017-06-13 00:21:41 +03:00
end
end
2017-07-08 18:26:07 +03:00
else
-- Stop paddling without driver
if self._animation ~= 0 then
self.object : set_animation ( { x = 0 , y = 40 } , 0 , 0 , true )
self._animation = 0
end
2017-06-13 00:21:41 +03:00
end
2019-03-06 06:38:57 +03:00
local velo = self.object : get_velocity ( )
2017-06-13 01:26:17 +03:00
if self._v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
2019-03-06 06:38:57 +03:00
self.object : set_pos ( self.object : get_pos ( ) )
2017-06-13 00:21:41 +03:00
return
end
2017-06-13 01:26:17 +03:00
local s = get_sign ( self._v )
self._v = self._v - 0.02 * s
if s ~= get_sign ( self._v ) then
2019-03-06 06:38:57 +03:00
self.object : set_velocity ( { x = 0 , y = 0 , z = 0 } )
2017-06-13 01:26:17 +03:00
self._v = 0
2017-06-13 00:21:41 +03:00
return
end
2017-06-13 01:26:17 +03:00
if math.abs ( self._v ) > 5 then
self._v = 5 * get_sign ( self._v )
2017-06-13 00:21:41 +03:00
end
2019-02-01 08:33:07 +03:00
local p = self.object : get_pos ( )
2017-07-08 18:55:46 +03:00
p.y = p.y - boat_y_offset
2017-06-13 00:21:41 +03:00
local new_velo
local new_acce = { x = 0 , y = 0 , z = 0 }
if not is_water ( p ) then
local nodedef = minetest.registered_nodes [ minetest.get_node ( p ) . name ]
if ( not nodedef ) or nodedef.walkable then
2017-06-13 01:26:17 +03:00
self._v = 0
2017-06-13 00:21:41 +03:00
new_acce = { x = 0 , y = 1 , z = 0 }
else
new_acce = { x = 0 , y = - 9.8 , z = 0 }
end
2019-03-06 06:38:57 +03:00
new_velo = get_velocity ( self._v , self.object : get_yaw ( ) ,
self.object : get_velocity ( ) . y )
self.object : set_pos ( self.object : get_pos ( ) )
2017-06-13 00:21:41 +03:00
else
p.y = p.y + 1
if is_water ( p ) then
2019-03-06 06:38:57 +03:00
local y = self.object : get_velocity ( ) . y
2017-06-13 00:21:41 +03:00
if y >= 5 then
y = 5
elseif y < 0 then
new_acce = { x = 0 , y = 20 , z = 0 }
else
new_acce = { x = 0 , y = 5 , z = 0 }
end
2019-03-06 06:38:57 +03:00
new_velo = get_velocity ( self._v , self.object : get_yaw ( ) , y )
self.object : set_pos ( self.object : get_pos ( ) )
2017-06-13 00:21:41 +03:00
else
new_acce = { x = 0 , y = 0 , z = 0 }
2019-03-06 06:38:57 +03:00
if math.abs ( self.object : get_velocity ( ) . y ) < 1 then
2019-02-01 08:33:07 +03:00
local pos = self.object : get_pos ( )
2017-07-08 18:55:46 +03:00
pos.y = math.floor ( pos.y ) + boat_y_offset
2019-03-06 06:38:57 +03:00
self.object : set_pos ( pos )
new_velo = get_velocity ( self._v , self.object : get_yaw ( ) , 0 )
2017-06-13 00:21:41 +03:00
else
2019-03-06 06:38:57 +03:00
new_velo = get_velocity ( self._v , self.object : get_yaw ( ) ,
self.object : get_velocity ( ) . y )
self.object : set_pos ( self.object : get_pos ( ) )
2017-06-13 00:21:41 +03:00
end
end
end
2019-03-06 06:38:57 +03:00
self.object : set_velocity ( new_velo )
self.object : set_acceleration ( new_acce )
2017-06-13 00:21:41 +03:00
end
2017-06-13 01:21:43 +03:00
-- Register one entity for all boat types
minetest.register_entity ( " mcl_boats:boat " , boat )
2017-06-13 00:21:41 +03:00
local boat_ids = { " boat " , " boat_spruce " , " boat_birch " , " boat_jungle " , " boat_acacia " , " boat_dark_oak " }
local names = { " Oak Boat " , " Spruce Boat " , " Birch Boat " , " Jungle Boat " , " Acacia Boat " , " Dark Oak Boat " }
2017-07-26 20:12:53 +03:00
local craftstuffs = { }
if minetest.get_modpath ( " mcl_core " ) then
craftstuffs = { " mcl_core:wood " , " mcl_core:sprucewood " , " mcl_core:birchwood " , " mcl_core:junglewood " , " mcl_core:acaciawood " , " mcl_core:darkwood " }
end
2017-06-13 00:21:41 +03:00
local images = { " oak " , " spruce " , " birch " , " jungle " , " acacia " , " dark_oak " }
2015-06-29 20:55:56 +03:00
2017-06-13 00:21:41 +03:00
for b = 1 , # boat_ids do
local itemstring = " mcl_boats: " .. boat_ids [ b ]
2015-06-29 20:55:56 +03:00
2017-06-13 01:33:31 +03:00
local longdesc , usagehelp , help , helpname
help = false
-- Only create one help entry for all boats
if b == 1 then
help = true
longdesc = " Boats are used to travel on the surface of water. "
usagehelp = " Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Rightclick the boat again to leave it, punch the boat to make it drop as an item. "
helpname = " Boat "
end
2017-06-13 00:21:41 +03:00
minetest.register_craftitem ( itemstring , {
description = names [ b ] ,
2017-06-13 01:33:31 +03:00
_doc_items_create_entry = help ,
_doc_items_entry_name = helpname ,
_doc_items_longdesc = longdesc ,
_doc_items_usagehelp = usagehelp ,
2017-06-13 00:21:41 +03:00
inventory_image = " mcl_boats_ " .. images [ b ] .. " _boat.png " ,
2017-01-16 21:10:18 +03:00
liquids_pointable = true ,
2017-01-20 06:54:09 +03:00
groups = { boat = 1 , transport = 1 } ,
2017-01-17 01:34:40 +03:00
stack_max = 1 ,
2017-01-16 21:10:18 +03:00
on_place = function ( itemstack , placer , pointed_thing )
if pointed_thing.type ~= " node " then
return
end
2017-03-02 17:44:31 +03:00
-- 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
2017-01-16 21:10:18 +03:00
if not is_water ( pointed_thing.under ) then
return
end
2017-07-08 18:55:46 +03:00
pointed_thing.under . y = pointed_thing.under . y + boat_y_offset
2017-06-13 01:21:43 +03:00
local boat = minetest.add_entity ( pointed_thing.under , " mcl_boats:boat " )
2017-06-13 00:50:42 +03:00
boat : get_luaentity ( ) . _itemstring = itemstring
boat : set_properties ( { textures = { " mcl_boats_texture_ " .. images [ b ] .. " _boat.png " } } )
2018-05-09 19:43:07 +03:00
boat : set_yaw ( placer : get_look_horizontal ( ) )
2017-08-09 17:17:00 +03:00
if not minetest.settings : get_bool ( " creative_mode " ) then
2017-01-16 21:10:18 +03:00
itemstack : take_item ( )
end
return itemstack
end ,
2018-02-02 00:45:19 +03:00
_on_dispense = function ( stack , pos , droppos , dropnode , dropdir )
local below = { x = droppos.x , y = droppos.y - 1 , z = droppos.z }
local belownode = minetest.get_node ( below )
-- Place boat as entity on or in water
if minetest.get_item_group ( dropnode.name , " water " ) ~= 0 or ( dropnode.name == " air " and minetest.get_item_group ( belownode.name , " water " ) ~= 0 ) then
minetest.add_entity ( droppos , " mcl_boats:boat " )
else
minetest.add_item ( droppos , stack )
end
end ,
2017-01-16 21:10:18 +03:00
} )
2017-06-13 00:21:41 +03:00
local c = craftstuffs [ b ]
2017-01-16 21:10:18 +03:00
minetest.register_craft ( {
2017-06-13 00:21:41 +03:00
output = itemstring ,
2017-01-16 21:10:18 +03:00
recipe = {
{ c , " " , c } ,
{ c , c , c } ,
} ,
} )
end
2015-06-29 20:55:56 +03:00
2017-01-10 08:43:07 +03:00
minetest.register_craft ( {
type = " fuel " ,
recipe = " group:boat " ,
burntime = 20 ,
} )
2017-06-13 01:18:51 +03:00
if minetest.get_modpath ( " doc_identifier " ) ~= nil then
doc.sub . identifier.register_object ( " mcl_boats:boat " , " craftitems " , " mcl_boats:boat " )
end