diff --git a/api.txt b/api.txt index a9a1e51..de137f2 100644 --- a/api.txt +++ b/api.txt @@ -1,5 +1,5 @@ -MOB API (27th December 2016) +MOB API (29th December 2016) The mob api is a function that can be called on by other mods to add new animals or monsters into minetest. @@ -260,3 +260,149 @@ Useful Internal Variables 'self.gotten' true when sheep have been sheared or cows have been milked, a toggle switch which can be used for many functions 'self.child' true when mob is currently a child (when two mobs have bred and current mob is the outcome) 'self.hornytimer' background timer that controls breeding functions and mob childhood timings + + +Mobs can now be ridden by players and the following shows the functions and usage: + + +mobs:attach(self, player) + +This function attaches a player to the mob so it can be ridden. + + 'self' mob information + 'player' player information + + +mobs:detach(player, offset) + +This function will detach the player currently riding a mob to an offset position. + + 'player' player information + 'offset' position table containing offset values + + +mobs:drive(self, move_animation, stand_animation, can_fly) + +This function allows an attached player to move the mob around and animate it at same time. + + 'self' mob information + 'move_animation' string containing movement animation e.g. "walk" + 'stand_animation' string containing standing animation e.g. "stand" + 'can_fly' if true then jump and sneak controls will allow mob to fly up and down + + +Certain variables need to be set before using the above functions: + + 'self.v2' toggle switch + 'self.max_speed_forward' max speed mob can move forward + 'self.max_speed_reverse' max speed mob can move backwards + 'self.accel' acceleration speed + 'self.terrain_type' integer containing terrain mob can walk on (1 = water, 2 or 3 = land) + 'self.driver_attach_at' position offset for attaching player to mob + 'self.driver_eye_offset' position offset for attached player view + + +Here is an example mob to show how the above functions work: + + +-- rideable horse +mobs:register_mob("mob_horse:horse", { + type = "animal", + visual = "mesh", + visual_size = {x = 1.20, y = 1.20}, + mesh = "mobs_horse.x", + collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.25, 0.4}, + animation = { + speed_normal = 15, speed_run = 30, + stand_start = 25, stand_end = 75, + walk_start = 75, walk_end = 100, + run_start = 75, run_end = 100, + }, + textures = { + {"mobs_horse.png"}, + {"mobs_horsepeg.png"}, + {"mobs_horseara.png"} + }, + fear_height = 3, + runaway = true, + fly = false, + walk_chance = 60, + view_range = 5, + follow = {"farming:wheat"}, + passive = true, + hp_min = 12, + hp_max = 16, + armor = 200, + lava_damage = 5, + fall_damage = 5, + water_damage = 1, + makes_footstep_sound = true, + drops = { + {name = "mobs:meat_raw", chance = 1, min = 2, max = 3} + }, + + -- custom function uses self.v2 toggle to set ride variables + do_custom = function(self, dtime) + + if not self.v2 then + self.v2 = 0 + self.max_speed_forward = 6 + self.max_speed_reverse = 2 + self.accel = 6 + self.terrain_type = 3 + self.driver_attach_at = {x = 0, y = 20, z = -2} + self.driver_eye_offset = {x = 0, y = 3, z = 0} + end + + -- when riding mob call drive function to control + if self.driver then + mobs.drive(self, "walk", "stand", false) + return false -- skip rest of mob functions + end + + return true + end, + + on_rightclick = function(self, clicker) + + -- check for actual player + if not clicker or not clicker:is_player() then + return + end + + -- used to feed, tame and heal mob + if mobs:feed_tame(self, clicker, 10, true, true) then + return + end + + -- check if mob has been tamed and player is owner + if self.tamed and self.owner == clicker:get_player_name() then + + local inv = clicker:get_inventory() + + -- detach player when riding mob (add saddle to inventory) + if self.driver and clicker == self.driver then + + mobs.detach(clicker, {x = 1, y = 0, z = 1}) + + if inv:room_for_item("main", "mobs:saddle") then + inv:add_item("main", "mobs:saddle") + else + minetest.add_item(clicker.getpos(), "mobs:saddle") + end + + -- attach player to mob if not riding (take saddle from inventory) + elseif not self.driver then + + if clicker:get_wielded_item():get_name() == "mobs:saddle" then + + self.object:set_properties({stepheight = 1.1}) + mobs.attach(self, clicker) + inv:remove_item("main", "mobs:saddle") + end + end + end + + mobs:capture_mob(self, clicker, 0, 0, 80, false, nil) + end +}) diff --git a/crafts.lua b/crafts.lua index 4d32326..b0ac728 100644 --- a/crafts.lua +++ b/crafts.lua @@ -98,3 +98,18 @@ minetest.register_craft({ {"default:stone", "default:stone", "default:stone"}, } }) + +-- saddle +minetest.register_craftitem("mobs:saddle", { + description = "Saddle", + inventory_image = "mobs_saddle.png" +}) + +minetest.register_craft({ + output = "mobs:saddle", + recipe = { + {"mobs:leather", "mobs:leather", "mobs:leather"}, + {"mobs:leather", "default:steel_ingot", "mobs:leather"}, + {"mobs:leather", "default:steel_ingot", "mobs:leather"}, + } +}) diff --git a/init.lua b/init.lua index 52dadee..8a625b6 100644 --- a/init.lua +++ b/init.lua @@ -4,6 +4,9 @@ local path = minetest.get_modpath("mobs") -- Mob API dofile(path .. "/api.lua") +-- Rideable Mobs +dofile(path .. "/mount.lua") + -- Mob Items dofile(path .. "/crafts.lua") diff --git a/mount.lua b/mount.lua new file mode 100644 index 0000000..9bbfc67 --- /dev/null +++ b/mount.lua @@ -0,0 +1,343 @@ + +-- lib_mount by Blert2112 (edited by TenPlus1) + +local enable_crash = true +local crash_threshold = 6.5 -- ignored if enable_crash=false + +------------------------------------------------------------------------------ + +-- +-- Helper functions +-- + +local function node_is(pos) + + local node = minetest.get_node(pos) + + if node.name == "air" then + return "air" + end + + if minetest.get_item_group(node.name, "liquid") ~= 0 then + return "liquid" + end + + if minetest.get_item_group(node.name, "walkable") ~= 0 then + return "walkable" + end + + return "other" +end + + +local function get_sign(i) + + i = i or 0 + + 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 * v.x + v.z * v.z) +end + + +local function force_detach(player) + + local attached_to = player:get_attach() + + if not attached_to then + return + end + + local entity = attached_to:get_luaentity() + + if entity.driver + and entity.driver == player then + + entity.driver = nil + end + + player:set_detach() + default.player_attached[player:get_player_name()] = false + player:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0}) + default.player_set_animation(player, "stand" , 30) + +end + +------------------------------------------------------------------------------- + + +minetest.register_on_leaveplayer(function(player) + force_detach(player) +end) + +minetest.register_on_shutdown(function() + local players = minetest.get_connected_players() + for i = 1, #players do + force_detach(players[i]) + end +end) + +minetest.register_on_dieplayer(function(player) + force_detach(player) + return true +end) + +------------------------------------------------------------------------------- + +function mobs.attach(entity, player) + + local attach_at, eye_offset = {}, {} + + if not entity.player_rotation then + entity.player_rotation = {x = 0, y = 0, z = 0} + end + + local rot_view = 0 + + if entity.player_rotation.y == 90 then + rot_view = math.pi/2 + end + + if not entity.driver_attach_at then + entity.driver_attach_at = {x = 0, y = 0, z = 0} + end + + if not entity.driver_eye_offset then + entity.driver_eye_offset = {x = 0, y = 0, z = 0} + end + + attach_at = entity.driver_attach_at + eye_offset = entity.driver_eye_offset + entity.driver = player + + force_detach(player) + + player:set_attach(entity.object, "", attach_at, entity.player_rotation) + default.player_attached[player:get_player_name()] = true + player:set_eye_offset(eye_offset, {x = 0, y = 0, z = 0}) + + minetest.after(0.2, function() + default.player_set_animation(player, "sit" , 30) + end) + + player:set_look_yaw(entity.object:getyaw() - rot_view) +end + + +function mobs.detach(player, offset) + + force_detach(player) + default.player_set_animation(player, "stand" , 30) + + local pos = player:getpos() + + pos = {x = pos.x + offset.x, y = pos.y + 0.2 + offset.y, z = pos.z + offset.z} + + minetest.after(0.1, function() + player:setpos(pos) + end) +end + + +function mobs.drive(entity, moving_anim, stand_anim, can_fly) + + local rot_steer, rot_view = math.pi/2, 0 + + if entity.player_rotation.y == 90 then + rot_steer, rot_view = 0, math.pi/2 + end + + local acce_y = 0 + local velo = entity.object:getvelocity() + + entity.v = get_v(velo) * get_sign(entity.v) + + -- process controls + if entity.driver then + +--print ("---velo", get_v(velo)) + + local ctrl = entity.driver:get_player_control() + + -- move forwards + if ctrl.up then + + entity.v = entity.v + entity.accel / 10 + + -- move backwards + elseif ctrl.down then + + if entity.max_speed_reverse == 0 and entity.v == 0 then + return + end + + entity.v = entity.v - entity.accel / 10 + end + + --entity.object:setyaw(entity.driver:get_look_yaw() - rot_steer) + entity.object:setyaw(entity.driver:get_look_horizontal())-- - rot_steer) + + if can_fly then + + -- fly up + if ctrl.jump then + velo.y = velo.y + 1 + if velo.y > entity.accel then velo.y = entity.accel end + + elseif velo.y > 0 then + velo.y = velo.y - 0.1 + if velo.y < 0 then velo.y = 0 end + end + + -- fly down + if ctrl.sneak then + velo.y = velo.y - 1 + if velo.y < -entity.accel then velo.y = -entity.accel end + + elseif velo.y < 0 then + velo.y = velo.y + 0.1 + if velo.y > 0 then velo.y = 0 end + end + + else + + -- jump + if ctrl.jump then + + if velo.y == 0 then + velo.y = velo.y + entity.jump_height + acce_y = acce_y + (acce_y * 3) + 1 + end + end + + end + end + + -- if not moving then set animation and return + if entity.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then + + if stand_anim then + set_animation(entity, stand_anim) + end + + return + end + + -- set moving animation + if moving_anim then + set_animation(entity, moving_anim) + end + + -- Stop! + local s = get_sign(entity.v) + + entity.v = entity.v - 0.02 * s + + if s ~= get_sign(entity.v) then + + entity.object:setvelocity({x = 0, y = 0, z = 0}) + entity.v = 0 + return + end + + -- enforce speed limit forward and reverse + local max_spd = entity.max_speed_reverse + + if get_sign(entity.v) >= 0 then + max_spd = entity.max_speed_forward + end + + if math.abs(entity.v) > max_spd then + entity.v = entity.v - get_sign(entity.v) + end + + -- Set position, velocity and acceleration + local p = entity.object:getpos() + local new_velo = {x = 0, y = 0, z = 0} + local new_acce = {x = 0, y = -9.8, z = 0} + + p.y = p.y - 0.5 + + local ni = node_is(p) + local v = entity.v + + if ni == "air" then + + if can_fly == true then + new_acce.y = 0 + end + + elseif ni == "liquid" then + + if entity.terrain_type == 2 + or entity.terrain_type == 3 then + + new_acce.y = 0 + p.y = p.y + 1 + + if node_is(p) == "liquid" then + + if velo.y >= 5 then + velo.y = 5 + elseif velo.y < 0 then + new_acce.y = 20 + else + new_acce.y = 5 + end + else + if math.abs(velo.y) < 1 then + local pos = entity.object:getpos() + pos.y = math.floor(pos.y) + 0.5 + entity.object:setpos(pos) + velo.y = 0 + end + end + else + v = v*0.25 + end +-- elseif ni == "walkable" then +-- v = 0 +-- new_acce.y = 1 + end + + new_velo = get_velocity(v, entity.object:getyaw() - rot_view, velo.y) + new_acce.y = new_acce.y + acce_y + + entity.object:setvelocity(new_velo) + entity.object:setacceleration(new_acce) + + -- CRASH! + if enable_crash then + + local intensity = entity.v2 - v + + if intensity >= crash_threshold then + +--print("----------- crash", intensity) + + entity.object:punch(entity.object, 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = intensity} + }, nil) + + end + end + + entity.v2 = v +end diff --git a/readme.MD b/readme.MD index e48c9a5..487d7fa 100644 --- a/readme.MD +++ b/readme.MD @@ -23,6 +23,7 @@ Lucky Blocks: 9 Changelog: +- 1.33- Added functions to mount ride mobs (mobs.attach, mobs.detach, mobs.drive) many thanks to Blert2112 - 1.32- Added new spawn check to count specific mobs AND new minetest.conf setting to chance spawn chance and numbers, added ability to protect tamed mobs - 1.31- Added 'attack_animals' and 'specific_attack' flags for custom monster attacks, also 'mob_difficulty' .conf setting to make mobs harder. - 1.30- Added support for invisibility mod (mobs cant attack what they cant see), tweaked and tidied code diff --git a/textures/mobs_saddle.png b/textures/mobs_saddle.png new file mode 100644 index 0000000..e9d42f8 Binary files /dev/null and b/textures/mobs_saddle.png differ