------------- ---- API ---- ------------- -- Ver 0.1 -- local random = math.random local pi = math.pi local abs = math.abs local ceil = math.ceil local vec_dist = vector.distance local abr = minetest.get_mapgen_setting('active_block_range') local function hitbox(self) return self.object:get_properties().collisionbox end local walkable_nodes = {} minetest.register_on_mods_loaded(function() for name in pairs(minetest.registered_nodes) do if name ~= "air" and name ~= "ignore" then if minetest.registered_nodes[name].walkable then table.insert(walkable_nodes,name) end end end end) function better_fauna.particle_spawner(pos, texture, type, min_pos, max_pos) type = type or "float" min_pos = min_pos or { x = pos.x - 2, y = pos.y - 2, z = pos.z - 2, } max_pos = max_pos or { x = pos.x + 2, y = pos.y + 2, z = pos.z + 2, } if type == "float" then minetest.add_particlespawner({ amount = 16, time = 0.25, minpos = min_pos, maxpos = max_pos, minvel = {x = 0, y = 0.2, z = 0}, maxvel = {x = 0, y = 0.25, z = 0}, minexptime = 0.75, maxexptime = 1, minsize = 4, maxsize = 4, texture = texture, glow = 1, }) elseif type == "splash" then minetest.add_particlespawner({ amount = 6, time = 0.25, minpos = {x = pos.x - 7/16, y = pos.y + 0.6, z = pos.z - 7/16}, maxpos = {x = pos.x + 7/16, y = pos.y + 0.6, z = pos.z + 7/16}, minvel = vector.new(-1, 2, -1), maxvel = vector.new(1, 5, 1), minacc = vector.new(0, -9.81, 0), maxacc = vector.new(0, -9.81, 0), minsize = 2, maxsize = 4, collisiondetection = true, texture = texture, }) end end --------------------------- -- Mob Control Functions -- --------------------------- local function can_fit(pos, width) local pos1 = vector.new(pos.x - width, pos.y, pos.z - width) local pos2 = vector.new(pos.x + width, pos.y, pos.z + width) for x = pos1.x, pos2.x do for y = pos1.y, pos2.y do for z = pos1.z, pos2.z do local p2 = vector.new(x, y, z) local node = minetest.get_node(p2) if minetest.registered_nodes[node.name].walkable then local p3 = vector.new(p2.x, p2.y + 1, p2.z) local node2 = minetest.get_node(p3) if minetest.registered_nodes[node2.name].walkable then return false end end end end end return true end local function move_from_wall(pos, width) local pos1 = vector.new(pos.x - width, pos.y, pos.z - width) local pos2 = vector.new(pos.x + width, pos.y, pos.z + width) for x = pos1.x, pos2.x do for y = pos1.y, pos2.y do for z = pos1.z, pos2.z do local p2 = vector.new(x, y, z) if can_fit(p2, width) then return p2 end end end end return pos end function better_fauna.find_path(pos, tpos, width) local endpos = tpos if not minetest.registered_nodes[minetest.get_node( vector.new(endpos.x, endpos.y - 1, endpos.z)) .name].walkable then local min = vector.subtract(endpos, 1) local max = vector.add(endpos, 1) local index_table = minetest.find_nodes_in_area_under_air( min, max, better_fauna.walkable_nodes) for _, i_pos in pairs(index_table) do if minetest.registered_nodes[minetest.get_node(i_pos) .name].walkable then endpos = vector.new(i_pos.x, i_pos.y + 1, i_pos.z) break end end end local path = minetest.find_path(pos, endpos, 32, 1, 1, "A*_noprefetch") if not path or #path < 2 then return end table.remove(path, 1) table.remove(path, #path) for i = #path, 1, -1 do if not path then return end if not can_fit(path[i], width + 1) then local clear = move_from_wall(path[i], width + 1) if clear and can_fit(clear, width) then path[i] = clear end end if #path > 3 then local pos1 = path[i - 2] local pos2 = path[i] -- Handle Diagonals if pos1 and pos2 and pos1.x ~= pos2.x and pos1.z ~= pos2.z then if minetest.line_of_sight(pos1, pos2) then local pos3 = vector.divide(vector.add(pos1, pos2), 2) if can_fit(pos, width) then table.remove(path, i - 1) end end end -- Reduce Straight Lines if pos1 and pos2 and pos1.x == pos2.x and pos1.z ~= pos2.z and pos1.y == pos2.y then if minetest.line_of_sight(pos1, pos2) then local pos3 = vector.divide(vector.add(pos1, pos2), 2) if can_fit(pos, width) then table.remove(path, i - 1) end end elseif pos1 and pos2 and pos1.x ~= pos2.x and pos1.z == pos2.z and pos1.y == pos2.y then if minetest.line_of_sight(pos1, pos2) then local pos3 = vector.divide(vector.add(pos1, pos2), 2) if can_fit(pos, width) then table.remove(path, i - 1) end end end end end if #path > 2 then if vector.distance(pos, path[2]) <= width + 1 then for i = 3, #path do path[i - 1] = path[i] end end end return path end function better_fauna.path_to_next_waypoint(self, tpos, speed_factor) speed_factor = speed_factor or 1 local pos = self.object:get_pos() local path_data = better_fauna.find_path(pos, tpos, 1) if not path_data or #path_data < 2 then return true end local pos2 = path_data[2] if pos2 then local yaw = self.object:get_yaw() local tyaw = minetest.dir_to_yaw(vector.direction(pos, pos2)) if abs(tyaw - yaw) > 0.1 then mobkit.lq_turn2pos(self, pos2) end mobkit.lq_dumbwalk(self, pos2, speed_factor) return true end end function better_fauna.feed_tame(self, clicker, feed_count, tame, breed) local item = clicker:get_wielded_item() local pos = self.object:get_pos() local mob_name = mob_core.get_name_proper(self.name) if mob_core.follow_holding(self, clicker) then if creative == false then item:take_item() clicker:set_wielded_item(item) end mobkit.heal(self, self.max_hp/feed_count) if self.hp >= self.max_hp then self.hp = self.max_hp end self.food = mobkit.remember(self, "food", self.food + 1) local minppos = vector.add(pos, hitbox(self)[4]) local maxppos = vector.subtract(pos, hitbox(self)[4]) local def = minetest.registered_items[item:get_name()] local texture = def.inventory_image if not texture or texture == "" then texture = def.wield_image if def.tiles then texture = def.tiles[1] end end texture = texture .. "^[resize:8x8" -- Crops image minetest.add_particlespawner({ amount = 12*self.height, time = 0.1, minpos = minppos, maxpos = maxppos, minvel = {x=-1, y=1, z=-1}, maxvel = {x=1, y=2, z=1}, minacc = {x=0, y=-5, z=0}, maxacc = {x=0, y=-9, z=0}, minexptime = 1, maxexptime = 1, minsize = 2*self.height, maxsize = 3*self.height, collisiondetection = true, vertical = false, texture = texture, }) if self.food >= feed_count then self.food = mobkit.remember(self, "food", 0) if tame and not self.tamed then mob_core.set_owner(self, clicker:get_player_name()) minetest.chat_send_player(clicker:get_player_name(), mob_name.." has been tamed!") mobkit.clear_queue_high(self) paleotest.particle_spawner(pos, "mob_core_green_particle.png", "float", min, max) end if breed then if self.child then return false end if self.breeding then return false end if self.breeding_cooldown <= 0 then self.breeding = true local min = vector.subtract(pos, 0.5) local max = vector.add(pos, 0.5) better_fauna.particle_spawner(pos, "heart.png", "float", min, max) end end end end return false end ------------------ -- HQ Functions -- ------------------ function better_fauna.hq_sporadic_flee(self, prty, player) local tyaw = 0 local init = true local timer = 1 if not player then return true end local func = function(self) if not mobkit.is_alive(player) then return true end if not mobkit.is_alive(self) then return true end local pos = mobkit.get_stand_pos(self) local yaw = self.object:get_yaw() local tpos = vector.add(pos, vector.multiply(minetest.yaw_to_dir(yaw), 4)) if init then tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos)) end if self._anim ~= "run" then mobkit.animate(self, "run") end if mobkit.is_queue_empty_low(self) then timer = timer - self.dtime if timer < 0 then tyaw = yaw - random(1.6, 3.2) timer = 1 end if abs(tyaw - yaw) > 0.1 then mobkit.turn2yaw(self, tyaw) end mobkit.go_forward_horizontal(self, self.max_speed) if timer <= 0 then return true end end end mobkit.queue_high(self, func, prty) end function better_fauna.hq_follow_player(self, prty, player) -- Follow Player if not player then return end if not mob_core.follow_holding(self, player) then return end local func = function(self) if not mobkit.is_alive(player) then mobkit.clear_queue_high(self) return true end if mobkit.is_queue_empty_low(self) then local pos = mobkit.get_stand_pos(self) local tpos = player:get_pos() if mob_core.follow_holding(self, player) then self.status = mobkit.remember(self, "status", "following") local dist = vec_dist(pos, tpos) local yaw = self.object:get_yaw() local tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos)) if dist > self.view_range then self.status = mobkit.remember(self, "status", "") return true end better_fauna.path_to_next_waypoint(self, tpos, 0.85) if vec_dist(pos, tpos) < hitbox(self)[4] + 2 then mobkit.lq_idle(self, 0.1, "stand") end else self.status = mobkit.remember(self, "status", "") mobkit.lq_idle(self, 0.1, "stand") return true end end end mobkit.queue_high(self, func, prty) end function better_fauna.get_nearby_mate(self, name) for _,obj in ipairs(self.nearby_objects) do if mobkit.is_alive(obj) and not obj:is_player() and obj:get_luaentity().name == name and obj:get_luaentity().gender ~= self.gender and obj:get_luaentity().breeding then return obj end end return end function better_fauna.hq_breed(self, prty) local mate = better_fauna.get_nearby_mate(self, self.name) if not mate then return end local speed_factor = 0.5 local func = function(self) if mobkit.is_queue_empty_low(self) then local pos = mobkit.get_stand_pos(self) local tpos = mate:get_pos() local dist = vec_dist(pos, tpos) - math.abs(hitbox(self)[4]) if dist > 1.5 then speed_factor = 0.5 else speed_factor = 0.1 end mob_core.goto_next_waypoint(self, tpos, speed_factor) if dist < 1.75 then self.breeding_time = self.breeding_time + 1 end if self.breeding_time >= 2 or mate:get_luaentity().breeding_time >= 2 then if self.gender == "female" then mob_core.spawn_child(pos, self.name) end self.breeding = false self.breeding_time = 0 self.breeding_cooldown = 300 mobkit.remember(self, "breeding", self.breeding) mobkit.remember(self, "breeding_time", self.breeding_time) mobkit.remember(self, "breeding_cooldown", self.breeding_cooldown) return true end end end mobkit.queue_high(self, func, prty) end function better_fauna.hq_fowl_breed(self, prty) local mate = better_fauna.get_nearby_mate(self, self.name) if not mate then return end local speed_factor = 0.5 local func = function(self) if mobkit.is_queue_empty_low(self) then local pos = mobkit.get_stand_pos(self) local tpos = mate:get_pos() local dist = vec_dist(pos, tpos) - math.abs(hitbox(self)[4]) if dist > 1.5 then speed_factor = 0.5 else speed_factor = 0.1 end mob_core.goto_next_waypoint(self, tpos, speed_factor) if dist < 1.75 then self.breeding_time = self.breeding_time + 1 end if self.breeding_time >= 2 or mate:get_luaentity().breeding_time >= 2 then if self.gender == "female" then minetest.add_particlespawner({ amount = 6, time = 0.25, minpos = {x = pos.x - 7/16, y = pos.y - 5/16, z = pos.z - 7/16}, maxpos = {x = pos.x + 7/16, y = pos.y - 5/16, z = pos.z + 7/16}, minvel = vector.new(-1, 2, -1), maxvel = vector.new(1, 5, 1), minacc = vector.new(0, -9.81, 0), maxacc = vector.new(0, -9.81, 0), collisiondetection = true, texture = "better_fauna_egg_fragment.png", }) mob_core.spawn_child(pos, self.name) end self.breeding = false self.breeding_time = 0 self.breeding_cooldown = 300 mobkit.remember(self, "breeding", self.breeding) mobkit.remember(self, "breeding_time", self.breeding_time) mobkit.remember(self, "breeding_cooldown", self.breeding_cooldown) return true end end end mobkit.queue_high(self, func, prty) end function better_fauna.hq_eat(self, prty) local func = function(self) local pos = mobkit.get_stand_pos(self) local under = vector.new(pos.x, pos.y - 1, pos.z) for _, node in ipairs(self.consumable_nodes) do if node.name == minetest.get_node(under).name then minetest.set_node(under, {name = node.replacement}) local def = minetest.registered_nodes[node.name] local texture = def.tiles[1] texture = texture .. "^[resize:8x8" minetest.add_particlespawner({ amount = 6, time = 0.1, minpos = vector.new( pos.x - 0.5, pos.y + 0.1, pos.z - 0.5 ), maxpos = vector.new( pos.x + 0.5, pos.y + 0.1, pos.z + 0.5 ), minvel = {x=-1, y=1, z=-1}, maxvel = {x=1, y=2, z=1}, minacc = {x=0, y=-5, z=0}, maxacc = {x=0, y=-9, z=0}, minexptime = 1, maxexptime = 1, minsize = 1, maxsize = 2, collisiondetection = true, vertical = false, texture = texture, }) self.gotten = false mobkit.remember(self, "gotten", self.gotten) return true else return true end end end mobkit.queue_high(self, func, prty) end --------------------------------- -- Entity Definition Functions -- --------------------------------- function better_fauna.on_step(self, dtime, moveresult) mob_core.on_step(self, dtime, moveresult) if mobkit.timer(self, 1) then if self.breeding_cooldown > 0 then self.breeding_cooldown = self.breeding_cooldown - 1 end mobkit.remember(self, "breeding_cooldown", self.breeding_cooldown) end end function better_fauna.on_activate(self, staticdata, dtime_s) mob_core.on_activate(self, staticdata, dtime_s) self.gotten = mobkit.recall(self, "gotten") or false self.attention_span = mobkit.recall(self, "attention_span") or 0 self.breeding = mobkit.recall(self, "breeding") or false self.breeding_time = mobkit.recall(self, "breeding_time") or 0 self.breeding_cooldown = mobkit.recall(self, "breeding_cooldown") or 0 end ------------- -- Physics -- ------------- function better_fauna.lightweight_physics(self) local vel = self.object:get_velocity() if self.isonground and not self.isinliquid then self.object:set_velocity({x= vel.x> 0.2 and vel.x*mobkit.friction or 0, y=vel.y, z=vel.z > 0.2 and vel.z*mobkit.friction or 0}) end if self.springiness and self.springiness > 0 then local vnew = vector.new(vel) if not self.collided then for _,k in ipairs({'y','z','x'}) do if vel[k]==0 and abs(self.lastvelocity[k])> 0.1 then vnew[k]=-self.lastvelocity[k]*self.springiness end end end if not vector.equals(vel,vnew) then self.collided = true else if self.collided then vnew = vector.new(self.lastvelocity) end self.collided = false end self.object:set_velocity(vnew) end local surface = nil local surfnodename = nil local spos = mobkit.get_stand_pos(self) spos.y = spos.y+0.01 local snodepos = mobkit.get_node_pos(spos) local surfnode = mobkit.nodeatpos(spos) while surfnode and surfnode.drawtype == 'liquid' do surfnodename = surfnode.name surface = snodepos.y+0.5 if surface > spos.y+self.height then break end snodepos.y = snodepos.y+1 surfnode = mobkit.nodeatpos(snodepos) end self.isinliquid = surfnodename if surface then local submergence = min(surface-spos.y,self.height)/self.height local buoyacc = mobkit.gravity*(self.buoyancy-submergence) mobkit.set_acceleration(self.object, {x=-vel.x*self.water_drag,y=buoyacc-vel.y*abs(vel.y)*0.4,z=-vel.z*self.water_drag}) else self.object:set_acceleration({x=0,y=-2.8,z=0}) end end