diff --git a/files/mobs/mobs_redo/README.txt b/files/mobs/mobs_redo/README.txt index 85c76d9..1b394ee 100644 --- a/files/mobs/mobs_redo/README.txt +++ b/files/mobs/mobs_redo/README.txt @@ -21,11 +21,6 @@ Copyright (C) 2019-2020 MultiCraft Development Team Graphics in this mod is NOT free and can be used only as part of the official MultiCraft build. Allowed to be used in non-official builds ONLY for personal use. -* Exceptions: -mobs_cobweb.png, mobs_nametag.png, mobs_rotten_flesh.png, mobs_shears.png -Copyright (C) 2013 Vattic -https://github.com/MultiCraft/MultiCraft/blob/master/doc/Other%20License.md#license-of-media-textures - License of sounds ----------------- Creative Commons sounds from Freesound.org diff --git a/files/mobs/mobs_redo/api.lua b/files/mobs/mobs_redo/api.lua index 2c74da2..2d8ca62 100644 --- a/files/mobs/mobs_redo/api.lua +++ b/files/mobs/mobs_redo/api.lua @@ -1,6 +1,6 @@ mobs = { mod = "redo", - version = "20200916", + version = "20201003", invis = minetest.global_exists("invisibility") and invisibility or {} } @@ -16,13 +16,7 @@ end local S = mobs.S --- creative check -local creative = minetest.settings:get_bool("creative_mode") -function mobs.is_creative(name) - return creative or minetest.check_player_privs(name, {creative = true}) -end - --- localize math functions +-- localize functions local abs = math.abs local atan = function(x) return x and math.atan(x) or 0 @@ -30,11 +24,6 @@ end local ceil = math.ceil local cos = math.cos local floor = math.floor -local add_vector = vector.add -local get_distance = vector.distance -local get_direction = vector.direction -local multiply = vector.multiply -local subtract = vector.subtract local min = math.min local max = math.max local pi = math.pi @@ -42,22 +31,36 @@ local rad = math.rad local random = math.random local sin = math.sin local square = math.sqrt +local vadd = vector.add +local vdistance = vector.distance +local vdirection = vector.direction +local vmultiply = vector.multiply +local vsubtract = vector.subtract +local table_copy = table.copy local table_remove = table.remove +local upper = string.upper +local settings = minetest.settings + +-- creative check +local creative = settings:get_bool("creative_mode") +function mobs.is_creative(name) + return creative or minetest.check_player_privs(name, {creative = true}) +end -- Load settings -local damage_enabled = minetest.settings:get_bool("enable_damage") -local mobs_spawn = true +local damage_enabled = settings:get_bool("enable_damage") +local mobs_spawn = settings:get_bool("mobs_spawn") ~= false local peaceful_only = false local disable_blood = true local mobs_griefing = true -local spawn_protected = true +local spawn_protected = settings:get_bool("mobs_spawn_protected") ~= false local remove_far = false -local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0 -local show_health = true -local max_per_block = tonumber(minetest.settings:get("max_objects_per_block")) +local difficulty = tonumber(settings:get("mob_difficulty")) or 1.0 +local show_health = settings:get_bool("mob_show_health") ~= false +local max_per_block = tonumber(settings:get("max_objects_per_block")) local singleplayer = minetest.is_singleplayer() local lifetime = 900 -- 15 min -local active_limit = tonumber(minetest.settings:get("mob_active_limit")) or 0 +local active_limit = tonumber(settings:get("mob_active_limit")) or 0 local active_mobs = 0 local spawn_interval = 20 @@ -150,6 +153,7 @@ local mob_class = { facing_fence = false, obj_is_mob = true } + local mob_class_meta = {__index = mob_class} -- play sound @@ -347,7 +351,8 @@ function mob_class:set_animation(anim, force) x = self.animation[anim .. "_start"], y = self.animation[anim .. "_end"] }, - self.animation[anim .. "_speed"] or self.animation.speed_normal or 15, + self.animation[anim .. "_speed"] or + self.animation.speed_normal or 15, 0, self.animation[anim .. "_loop"] ~= false) end @@ -377,7 +382,7 @@ local line_of_sight = function(self, pos1, pos2, stepsize) local nn = minetest.get_node(pos).name -- Target Distance (td) to travel - local td = get_distance(pos1, pos2) + local td = vdistance(pos1, pos2) -- Actual Distance (ad) traveled local ad = 0 @@ -393,7 +398,7 @@ local line_of_sight = function(self, pos1, pos2, stepsize) end -- Moves the analyzed pos - local d = get_distance(pos1, pos2) + local d = vdistance(pos1, pos2) npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y @@ -427,7 +432,7 @@ local new_line_of_sight = function(self, pos1, pos2, stepsize) if not pos1 or not pos2 then return end stepsize = stepsize or 1 - local stepv = multiply(get_direction(pos1, pos2), stepsize) + local stepv = vmultiply(vdirection(pos1, pos2), stepsize) local s, pos = minetest.line_of_sight(pos1, pos2, stepsize) -- normal walking and flying mobs can see you through air @@ -448,9 +453,9 @@ local new_line_of_sight = function(self, pos1, pos2, stepsize) while minetest.registered_nodes[nn] and not minetest.registered_nodes[nn].walkable do - npos1 = add_vector(npos1, stepv) + npos1 = vadd(npos1, stepv) - if get_distance(npos1, pos2) < stepsize then return true end + if vdistance(npos1, pos2) < stepsize then return true end -- scan again r, pos = minetest.line_of_sight(npos1, pos2, stepsize) @@ -518,10 +523,10 @@ function mob_class:attempt_flight_correction(override) end local escape_target = flyable_nodes[random(#flyable_nodes)] - local escape_direction = get_direction(pos, escape_target) + local escape_direction = vdirection(pos, escape_target) self.object:set_velocity( - vector.multiply(escape_direction, 1)) --self.run_velocity)) + vmultiply(escape_direction, 1)) --self.run_velocity)) return true end @@ -576,13 +581,10 @@ function mob_class:do_stay_near() if not self.stay_near then return false end local pos = self.object:get_pos() - if not pos then - return false - end local searchnodes = self.stay_near[1] local chance = self.stay_near[2] or 10 - if random(chance) > 1 then + if not pos or random(chance) > 1 then return false end @@ -1144,7 +1146,7 @@ local entity_physics = function(pos, radius) for n = 1, #objs do obj_pos = objs[n]:get_pos() - dist = get_distance(pos, obj_pos) + dist = vdistance(pos, obj_pos) if dist < 1 then dist = 1 end @@ -1484,7 +1486,7 @@ function mob_class:smart_mobs(s, p, dist, dtime) end, self) end - if abs(subtract(s, target_pos).y) > self.stepheight then + if abs(vsubtract(s, target_pos).y) > self.stepheight then if height_switcher then use_pathfind = true height_switcher = false @@ -1722,7 +1724,7 @@ function mob_class:general_attack() p = player:get_pos() sp = s - dist = get_distance(p, s) + dist = vdistance(p, s) -- aim higher to make looking up hills more realistic p.y = p.y + 1 @@ -1751,9 +1753,7 @@ function mob_class:do_runaway_from() end local s = self.object:get_pos() - if not s then - return - end + if not s then return end local p, sp, dist, pname local player, obj, min_player, name @@ -1791,7 +1791,7 @@ function mob_class:do_runaway_from() p.y = p.y + 1 sp.y = sp.y + 1 - dist = get_distance(p, s) + dist = vdistance(p, s) -- choose closest player/mob to runaway from if dist < min_dist @@ -1834,7 +1834,7 @@ function mob_class:follow_flop() end local player = minetest.get_player_by_name(name) if player and not mobs.invis[name] and - get_distance(player:get_pos(), s) < self.view_range then + vdistance(player:get_pos(), s) < self.view_range then self.following = player break end @@ -1872,7 +1872,7 @@ function mob_class:follow_flop() end if p then - local dist = get_distance(p, s) + local dist = vdistance(p, s) -- dont follow if out of range if dist > self.view_range then @@ -2073,8 +2073,8 @@ function mob_class:do_states(dtime) elseif self.state == "attack" then -- get mob and enemy positions and distance between local s = self.object:get_pos() - local p = self.attack:get_pos() - local dist = p and get_distance(p, s) or 500 + local p = self.attack and self.attack:get_pos() + local dist = p and vdistance(p, s) or 500 -- stop attacking if player out of range or invisible if dist > self.view_range @@ -2766,7 +2766,8 @@ function mob_class:mob_activate(staticdata, def, dtime) def.textures = {def.textures} end - self.base_texture = def.textures and def.textures[random(#def.textures)] + self.base_texture = def.textures and + def.textures[random(#def.textures)] self.base_mesh = def.mesh self.base_size = self.visual_size self.base_colbox = self.collisionbox @@ -2839,7 +2840,7 @@ function mob_class:mob_activate(staticdata, def, dtime) -- Armor groups (immortal = 1 for custom damage handling) local armor if type(self.armor) == "table" then - armor = table.copy(self.armor) + armor = table_copy(self.armor) armor.immortal = 1 else armor = {immortal = 1, fleshy = self.armor} @@ -2995,12 +2996,12 @@ function mob_class:on_step(dtime) dif = 2 * pi - dif -- need to add yaw = yaw + dif / self.delay else - yaw = yaw - dif / self.delay -- need to subtract + yaw = yaw - dif / self.delay -- need to vsubtract end elseif yaw < self.target_yaw then if dif > pi then dif = 2 * pi - dif - yaw = yaw - dif / self.delay -- need to subtract + yaw = yaw - dif / self.delay -- need to vsubtract else yaw = yaw + dif / self.delay -- need to add end @@ -3238,15 +3239,15 @@ function mobs:spawn_abm_check(pos, node, name) end -function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, -interval, chance, aoc, min_height, max_height, day_toggle, on_spawn) +function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval, + chance, aoc, min_height, max_height, day_toggle, on_spawn, map_load) -- Do mobs spawn at all? if not mobs_spawn or not mobs.spawning_mobs[name] then return end -- chance/spawn number override in minetest.conf for registered mob - local numbers = minetest.settings:get(name) + local numbers = settings:get(name) if numbers then numbers = numbers:split(",") @@ -3265,127 +3266,160 @@ interval, chance, aoc, min_height, max_height, day_toggle, on_spawn) mobs.spawning_mobs[name].aoc = aoc - minetest.register_abm({ - label = name .. " spawning", - nodenames = nodes, - neighbors = neighbors, - interval = interval, - chance = max(1, chance), - catch_up = false, - action = function(pos, node, active_object_count, active_object_count_wider) - -- is mob actually registered? - if not mobs.spawning_mobs[name] - or not minetest.registered_entities[name] then - -- print ("--- mob doesn't exist", name) - return - end + local spawn_action = function(pos, node, active_object_count, + active_object_count_wider) - -- are we over active mob limit - if active_limit > 0 and active_mobs >= active_limit then + -- use instead of abm's chance setting when using lbm + if map_load and random(max(1, chance)) > 1 then + return + end + + -- use instead of abm's neighbor setting when using lbm + if map_load and not minetest.find_node_near(pos, 1, neighbors) then +--print("--- lbm neighbors not found") + return + end + + -- is mob actually registered? + if not mobs.spawning_mobs[name] + or not minetest.registered_entities[name] then + -- print ("--- mob doesn't exist", name) + return + end + + -- are we over active mob limit + if active_limit > 0 and active_mobs >= active_limit then --print("--- active mob limit reached", active_mobs, active_limit) - return - end + return + end - -- additional custom checks for spawning mob - if mobs:spawn_abm_check(pos, node, name) then - return - end + -- additional custom checks for spawning mob + if mobs:spawn_abm_check(pos, node, name) then + return + end - -- do not spawn if too many entities in area - if active_object_count_wider >= max_per_block then - -- print("--- too many entities in area", active_object_count_wider) - return - end + -- do not spawn if too many entities in area + if active_object_count_wider + and active_object_count_wider >= max_per_block then + -- print("--- too many entities in area", active_object_count_wider) + return + end - -- get total number of this mob in area - local num_mob, is_pla = count_mobs(pos, name) + -- get total number of this mob in area + local num_mob, is_pla = count_mobs(pos, name) - if not is_pla then - -- print ("--- no players within active area, will not spawn " .. name) - return - end + if not is_pla then + -- print ("--- no players within active area, will not spawn " .. name) + return + end - if num_mob >= aoc then - -- print ("--- too many " .. name .. " in area", num_mob .. "/" .. aoc) - return - end + if num_mob >= aoc then + -- print ("--- too many " .. name .. " in area", num_mob .. "/" .. aoc) + return + end - -- mobs cannot spawn in protected areas when enabled - if spawn_protected - and minetest.is_protected(pos, "") then - -- print ("--- inside protected area", name) - return - end + -- mobs cannot spawn in protected areas when enabled + if spawn_protected + and minetest.is_protected(pos, "") then + -- print ("--- inside protected area", name) + return + end - -- if toggle set to nil then ignore day/night check - if day_toggle ~= nil then - local tod = (minetest.get_timeofday() or 0) * 24000 + -- if toggle set to nil then ignore day/night check + if day_toggle ~= nil then + local tod = (minetest.get_timeofday() or 0) * 24000 - if tod > 4500 and tod < 19500 then - -- daylight, but mob wants night - if not day_toggle then - -- print ("--- mob needs night", name) - return - end - else - -- night time but mob wants day - if day_toggle then - -- print ("--- mob needs day", name) - return - end + if tod > 4500 and tod < 19500 then + -- daylight, but mob wants night + if not day_toggle then + -- print ("--- mob needs night", name) + return end - end - - -- spawn above node - pos.y = pos.y + 1 - - -- are we spawning within height limits? - if pos.y > max_height - or pos.y < min_height then - -- print ("--- height limits not met", name, pos.y) - return - end - - -- are light levels ok? - if min_light ~= 0 or max_light ~= 15 then - local light = minetest.get_node_light(pos) - if not light - or light > max_light - or light < min_light then - -- print ("--- light limits not met", name, light) + else + -- night time but mob wants day + if day_toggle then + -- print ("--- mob needs day", name) return end end + end - -- do we have enough height clearance to spawn mob? - local ent = minetest.registered_entities[name] - local height = max(1, ceil( - (ent.collisionbox[5] or 0.25) - - (ent.collisionbox[2] or -0.25) - 1)) + -- spawn above node + pos.y = pos.y + 1 - for n = 0, height do - local pos2 = {x = pos.x, y = pos.y + n, z = pos.z} + -- are we spawning within height limits? + if pos.y > max_height + or pos.y < min_height then + -- print ("--- height limits not met", name, pos.y) + return + end - if minetest.registered_nodes[node_ok(pos2).name].walkable then - -- print ("--- inside block", name, node_ok(pos2).name) - return - end + -- are light levels ok? + if min_light ~= 0 or max_light ~= 15 then + local light = minetest.get_node_light(pos) + if not light + or light > max_light + or light < min_light then + -- print ("--- light limits not met", name, light) + return end + end - -- spawn mob 1/2 node above ground - pos.y = pos.y + 0.5 - local mob = minetest.add_entity(pos, name) + -- do we have enough height clearance to spawn mob? + local ent = minetest.registered_entities[name] + local height = max(1, ceil( + (ent.collisionbox[5] or 0.25) - + (ent.collisionbox[2] or -0.25) - 1)) + + for n = 0, height do + local pos2 = {x = pos.x, y = pos.y + n, z = pos.z} + + if minetest.registered_nodes[node_ok(pos2).name].walkable then + -- print ("--- inside block", name, node_ok(pos2).name) + return + end + end + + -- spawn mob 1/2 node above ground + pos.y = pos.y + 0.5 + local mob = minetest.add_entity(pos, name) -- print("[mobs] Spawned " .. name .. " at " -- .. minetest.pos_to_string(pos) .. " on " -- .. node.name .. " near " .. neighbors[1]) - if on_spawn then - local ent = mob:get_luaentity() - on_spawn(ent, pos) - end + if on_spawn then + on_spawn(mob:get_luaentity(), pos) end - }) + end + + + -- are we registering an abm or lbm? + if map_load then + minetest.register_lbm({ + name = name .. "_spawning", + label = name .. " spawning", + nodenames = nodes, + run_at_every_load = false, + + action = function(pos, node) + spawn_action(pos, node) + end + }) + else + minetest.register_abm({ + label = name .. " spawning", + nodenames = nodes, + neighbors = neighbors, + interval = interval, + chance = max(1, chance), + catch_up = false, + + action = function(pos, node, active_object_count, active_object_count_wider) + spawn_action(pos, node, active_object_count, active_object_count_wider) + end + }) + end end @@ -3403,7 +3437,8 @@ function mobs:spawn(def) def.min_height or -31000, def.max_height or 31000, def.day_toggle, - def.on_spawn) + def.on_spawn, + def.on_map_load) end @@ -3506,8 +3541,7 @@ end -- compatibility function function mobs:explosion(pos, radius) - local self = {sounds = {explode = "tnt_explode"}} - mobs:boom(self, pos, radius) + mobs:boom({sounds = {explode = "tnt_explode"}}, pos, radius) end @@ -3537,7 +3571,8 @@ function mobs:boom(self, pos, radius) else mobs:safe_boom(self, pos, radius) end -end]] +end +]] -- Mob spawning, returns true on successful placement local function spawn_mob(pos, mob, data, placer, drop) @@ -3645,7 +3680,7 @@ function mobs:register_egg(mob, desc, background, addegg, no_creative) -- register new spawn egg containing mob information minetest.register_craftitem(mob .. "_set", { - description = desc .. " " .. S"(Tamed)", + description = S("@1 (Tamed)", desc), inventory_image = invimg, groups = {spawn_egg = 2, not_in_creative_inventory = 1}, stack_max = 1, @@ -3657,7 +3692,8 @@ function mobs:register_egg(mob, desc, background, addegg, no_creative) local under = minetest.get_node(pointed_thing.under) local def = minetest.registered_nodes[under.name] if def and def.on_rightclick then - return def.on_rightclick(pointed_thing.under, under, placer, itemstack) + return def.on_rightclick( + pointed_thing.under, under, placer, itemstack) end local mob = itemstack:get_name():gsub("_set$", "") @@ -3684,7 +3720,8 @@ function mobs:register_egg(mob, desc, background, addegg, no_creative) local under = minetest.get_node(pointed_thing.under) local def = minetest.registered_nodes[under.name] if def and def.on_rightclick then - return def.on_rightclick(pointed_thing.under, under, placer, itemstack) + return def.on_rightclick( + pointed_thing.under, under, placer, itemstack) end local mob = itemstack:get_name():gsub("_set$", "") @@ -3700,9 +3737,9 @@ function mobs:register_egg(mob, desc, background, addegg, no_creative) }) end - +--[[ -- force capture a mob if space available in inventory, or drop as spawn egg ---[[function mobs:force_capture(self, clicker) +function mobs:force_capture(self, clicker) -- add special mob egg with all mob information local new_stack = ItemStack(self.name .. "_set") local tmp, t = {} @@ -3839,7 +3876,8 @@ function mobs:capture_mob(self, clicker, chance_hand, chance_net, end end return true -end]] +end +]] local mob_obj = {} @@ -3847,10 +3885,12 @@ local mob_sta = {} -- feeding, taming and breeding (thanks blert2112) function mobs:feed_tame(self, clicker, feed_count, breed, tame) + local name = clicker and clicker:get_player_name() or "" + -- can eat/tame with item in hand if self.follow and self:follow_holding(clicker) then -- if not in creative then take item - if not mobs.is_creative(clicker:get_player_name()) then + if not mobs.is_creative(name) then local item = clicker:get_wielded_item() item:take_item() clicker:set_wielded_item(item) @@ -3863,8 +3903,8 @@ function mobs:feed_tame(self, clicker, feed_count, breed, tame) self.health = self.hp_max if self.htimer < 1 then - local mob_name = self.name:split(":")[2]:split("_")[1]:gsub("^%l", string.upper) - minetest.chat_send_player(clicker:get_player_name(), + local mob_name = self.name:split(":")[2]:split("_")[1]:gsub("^%l", upper) + minetest.chat_send_player(name, S("\"@1\" at full health: @2", S(mob_name), tostring(self.health))) self.htimer = 5 @@ -3893,19 +3933,17 @@ function mobs:feed_tame(self, clicker, feed_count, breed, tame) if tame then if self.tamed == false then - local mob_name = self.name:split(":")[2]:split("_")[1]:gsub("^%l", string.upper) - minetest.chat_send_player(clicker:get_player_name(), - S("\"@1\" has been tamed!", - S(mob_name))) + local mob_name = self.name:split(":")[2]:split("_")[1]:gsub("^%l", upper) + minetest.chat_send_player(name, + S("\"@1\" has been tamed!", S(mob_name))) self:mob_sound("mobs_spell") end self.tamed = true if not self.owner or self.owner == "" then - local pn = clicker:get_player_name() - self.owner = pn + self.owner = name - local infotext = S("Owned by @1", S(pn)) + local infotext = S("Owned by @1", S(name)) self.infotext = infotext self.object:set_properties({ infotext = infotext @@ -3923,8 +3961,7 @@ function mobs:feed_tame(self, clicker, feed_count, breed, tame) -- if mob has been tamed you can name it with a nametag if item:get_name() == "mobs:nametag" - and clicker:get_player_name() == self.owner then - local name = clicker:get_player_name() + and name == self.owner then -- store mob and nametag stack in external variables mob_obj[name] = self @@ -3932,10 +3969,10 @@ function mobs:feed_tame(self, clicker, feed_count, breed, tame) local tag = self.nametag or "" minetest.show_formspec(name, "mobs_nametag", - "size[5,3]" - .. "field[1.35,1.25;2.9,1;name;" - .. S("Enter name:") .. ";" .. tag .. "]" - .. "button_exit[1.06,1.65;2.9,1;mob_rename;" .. S("Rename") .. "]") + "size[5,3]" .. + "field[1.35,1.25;2.9,1;name;" .. + S"Enter name:" .. ";" .. tag .. "]" .. + "button_exit[1.06,1.65;2.9,1;mob_rename;" .. S"Rename" .. "]") return true end @@ -3948,11 +3985,14 @@ end minetest.register_on_player_receive_fields(function(player, formname, fields) -- right-clicked with nametag and name entered? if formname == "mobs_nametag" - and fields.name and fields.name ~= "" then + and fields.name + and fields.name ~= "" then local name = player:get_player_name() if not mob_obj[name] - or not mob_obj[name].object then return end + or not mob_obj[name].object then + return + end -- make sure nametag is being used to name mob local item = player:get_wielded_item() diff --git a/files/mobs/mobs_redo/depends.txt b/files/mobs/mobs_redo/depends.txt index 864ec2b..7acee9b 100644 --- a/files/mobs/mobs_redo/depends.txt +++ b/files/mobs/mobs_redo/depends.txt @@ -1,3 +1,4 @@ default +player_api experience? pep? diff --git a/files/mobs/mobs_redo/init.lua b/files/mobs/mobs_redo/init.lua index 5a0e6cd..43d2ddb 100644 --- a/files/mobs/mobs_redo/init.lua +++ b/files/mobs/mobs_redo/init.lua @@ -3,6 +3,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/files/mobs/mobs_redo/locale/mobs.ru.tr b/files/mobs/mobs_redo/locale/mobs.ru.tr index dad983d..e6b2f79 100644 --- a/files/mobs/mobs_redo/locale/mobs.ru.tr +++ b/files/mobs/mobs_redo/locale/mobs.ru.tr @@ -13,7 +13,7 @@ Active Mob Limit Reached!=Достигнут Лимит Активных Моб Owned by @1=Владелец: @1 Player=Игрок -(Tamed)=(Прирученн.) +@1 (Tamed)=@1 (Прирученн.) Name Tag=Именная Метка Leather=Кожа Raw Meat=Сырое Мясо diff --git a/files/mobs/mobs_redo/mount.lua b/files/mobs/mobs_redo/mount.lua new file mode 100644 index 0000000..e39f50e --- /dev/null +++ b/files/mobs/mobs_redo/mount.lua @@ -0,0 +1,401 @@ +-- lib_mount by Blert2112 (edited by TenPlus1) + +local abs, cos, floor, sin, sqrt, pi = + math.abs, math.cos, math.floor, math.sin, math.sqrt, math.pi +------------------------------------------------------------------------------ + +-- +-- Helper functions +-- + +local node_ok = function(pos, fallback) + fallback = fallback or mobs.fallback_node + + local node = minetest.get_node_or_nil(pos) + + if node and minetest.registered_nodes[node.name] then + return node + end + + return {name = fallback} +end + + +local function node_is(pos) + local node = node_ok(pos) + + if node.name == "air" then + return "air" + end + + if minetest.get_item_group(node.name, "lava") ~= 0 then + return "lava" + end + + if minetest.get_item_group(node.name, "liquid") ~= 0 then + return "liquid" + end + + if minetest.registered_nodes[node.name].walkable == true 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 / abs(i) + end +end + + +local function get_velocity(v, yaw, y) + local x = -sin(yaw) * v + local z = cos(yaw) * v + + return {x = x, y = y, z = z} +end + + +local function get_v(v) + return 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 and entity.driver + and entity.driver == player then + entity.driver = nil + end + + player:set_detach() + player_api.player_attached[player:get_player_name()] = false + player:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0}) + player_api.set_animation(player, "stand", 30) + player:set_properties({visual_size = {x = 1, y = 1} }) + +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) + entity.player_rotation = entity.player_rotation or {x = 0, y = 0, z = 0} + entity.driver_attach_at = entity.driver_attach_at or {x = 0, y = 0, z = 0} + entity.driver_eye_offset = entity.driver_eye_offset or {x = 0, y = 0, z = 0} + entity.driver_scale = entity.driver_scale or {x = 1, y = 1} + + local rot_view = 0 + + if entity.player_rotation.y == 90 then + rot_view = pi / 2 + end + + local attach_at = entity.driver_attach_at + local eye_offset = entity.driver_eye_offset + entity.driver = player + + force_detach(player) + + player:set_attach(entity.object, "", attach_at, entity.player_rotation) + player_api.player_attached[player:get_player_name()] = true + player:set_eye_offset(eye_offset, {x = 0, y = 0, z = 0}) + + player:set_properties({ + visual_size = { + x = entity.driver_scale.x, + y = entity.driver_scale.y + } + }) + + minetest.after(0.2, function(name) + local player = minetest.get_player_by_name(name) + if player then + player_api.set_animation(player, "sit", 30) + end + end, player:get_player_name()) + + player:set_look_horizontal(entity.object:get_yaw() - rot_view) +end + + +function mobs.detach(player, offset) + force_detach(player) + + player_api.set_animation(player, "stand", 30) + + local pos = player:get_pos() + + pos = { + x = pos.x + offset.x, + y = pos.y + offset.y + 0.2, + z = pos.z + offset.z + } + + minetest.after(0.1, function(name, pos) + local player = minetest.get_player_by_name(name) + if player then + player:set_pos(pos) + end + end, player:get_player_name(), pos) +end + + +function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime) + local rot_view = 0 + + if entity.player_rotation.y == 90 then + rot_view = pi / 2 + end + + local acce_y = 0 + local velo = entity.object:get_velocity() + + 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 + + -- fix mob rotation + local horz = entity.driver:get_look_horizontal() or 0 + entity.object:set_yaw(horz - entity.rotate) + + 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 + mobs:set_animation(entity, stand_anim) + end + + return + end + + -- set moving animation + if moving_anim then + mobs: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:set_velocity({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 abs(entity.v) > max_spd then + entity.v = entity.v - get_sign(entity.v) + end + + -- Set position, velocity and acceleration + local p = entity.object:get_pos() + local new_velo + local new_acce = {x = 0, y = -9.81, 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" or ni == "lava" then + if ni == "lava" and entity.lava_damage ~= 0 then + entity.lava_counter = (entity.lava_counter or 0) + dtime + + if entity.lava_counter > 1 then + minetest.sound_play("default_punch", { + object = entity.object, + max_hear_distance = 5 + }) + + entity.object:punch(entity.object, 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = entity.lava_damage} + }, nil) + + entity.lava_counter = 0 + end + end + + 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 abs(velo.y) < 1 then + local pos = entity.object:get_pos() + pos.y = floor(pos.y) + 0.5 + entity.object:set_pos(pos) + velo.y = 0 + end + end + else + v = v * 0.25 + end + end + + new_velo = get_velocity(v, entity.object:get_yaw() - rot_view, velo.y) + new_acce.y = new_acce.y + acce_y + + entity.object:set_velocity(new_velo) + entity.object:set_acceleration(new_acce) + + entity.v2 = v +end + + +-- directional flying routine by D00Med (edited by TenPlus1) +function mobs.fly(entity, _, speed, shoots, arrow, moving_anim, stand_anim) + local ctrl = entity.driver:get_player_control() + local velo = entity.object:get_velocity() + local dir = entity.driver:get_look_dir() + local yaw = entity.driver:get_look_horizontal() + 1.57 -- offset fix between old and new commands + + if ctrl.up then + entity.object:set_velocity({ + x = dir.x * speed, + y = dir.y * speed + 2, + z = dir.z * speed + }) + + elseif ctrl.down then + entity.object:set_velocity({ + x = -dir.x * speed, + y = dir.y * speed + 2, + z = -dir.z * speed + }) + + elseif not ctrl.down or ctrl.up or ctrl.jump then + entity.object:set_velocity({x = 0, y = -2, z = 0}) + end + + entity.object:set_yaw(yaw + pi + pi / 2 - entity.rotate) + + -- firing arrows + if ctrl.LMB and ctrl.sneak and shoots then + local pos = entity.object:get_pos() + local obj = minetest.add_entity({ + x = pos.x + 0 + dir.x * 2.5, + y = pos.y + 1.5 + dir.y, + z = pos.z + 0 + dir.z * 2.5}, arrow) + + local ent = obj:get_luaentity() + if ent then + ent.switch = 1 -- for mob specific arrows + ent.owner_id = tostring(entity.object) -- so arrows dont hurt entity you are riding + local vec = {x = dir.x * 6, y = dir.y * 6, z = dir.z * 6} + yaw = entity.driver:get_look_horizontal() + obj:set_yaw(yaw + pi / 2) + obj:set_velocity(vec) + else + obj:remove() + end + end + + -- change animation if stopped + if velo.x == 0 and velo.y == 0 and velo.z == 0 then + mobs:set_animation(entity, stand_anim) + else + -- moving animation + mobs:set_animation(entity, moving_anim) + end +end diff --git a/files/mobs/mobs_redo/textures/mobs_cobweb.png b/files/mobs/mobs_redo/textures/mobs_cobweb.png index 6acc588..56d4163 100644 Binary files a/files/mobs/mobs_redo/textures/mobs_cobweb.png and b/files/mobs/mobs_redo/textures/mobs_cobweb.png differ diff --git a/files/mobs/mobs_redo/textures/mobs_nametag.png b/files/mobs/mobs_redo/textures/mobs_nametag.png index 51c9e95..4d97ff7 100644 Binary files a/files/mobs/mobs_redo/textures/mobs_nametag.png and b/files/mobs/mobs_redo/textures/mobs_nametag.png differ diff --git a/files/mobs/mobs_redo/textures/mobs_rotten_flesh.png b/files/mobs/mobs_redo/textures/mobs_rotten_flesh.png index b6889a3..8547835 100644 Binary files a/files/mobs/mobs_redo/textures/mobs_rotten_flesh.png and b/files/mobs/mobs_redo/textures/mobs_rotten_flesh.png differ diff --git a/files/mobs/mobs_redo/textures/mobs_shears.png b/files/mobs/mobs_redo/textures/mobs_shears.png index 12f7182..8d5b6be 100644 Binary files a/files/mobs/mobs_redo/textures/mobs_shears.png and b/files/mobs/mobs_redo/textures/mobs_shears.png differ