--PROJECT GOALS --[[ List of individual goals 0 is mainly function ideas/notes on how to execute things 0.) only check nodes using voxelmanip when in new floored position 0.) minetest.line_of_sight(pos1, pos2, stepsize) to check if a mob sees player 0.) minetest.find_path(pos1,pos2,searchdistance,max_jump,max_drop,algorithm) 0.) do pathfinding by setting yaw towards the next point in the table 0.) only run this function if the goal entity/player is in a new node to prevent extreme lag 0.) sneaking mobs, if a mob is sneaking, vs running at you, make no walking sounds 0.) Health 0.) running particles 0.) when mob gets below 0.1 velocity do set velocity to make it stand still ONCE so mobs don't float and set acceleration to 0 1.) lightweight ai that walks around, stands, does something like eat grass 2.) make mobs drown if drown = true in definition 3.) Make mobs avoid other mobs and players when walking around ########4.) pathfinding, avoid walking off cliffs 5.) attacking players 6.) drop items 7.) utility mobs 8.) underwater and flying mobs 9.) pet mobs 10.) mobs climb ladders, and are affected by nodes like players are 11.) have mobs with player mesh able to equip armor and wield an item with armor mod Or in other words, mobs that add fun and aesthetic to the game, yet do not take away performance This is my first "professional" level mod which I hope to complete This is a huge undertaking, I will sometimes take breaks, days in between to avoid rage quitting, but will try to make updates as frequently as possible Rolling release to make updates in minetest daily (git) to bring out the best in the engine I use tab because it's the easiest for me to follow in my editor, I apologize if this is not the same for you I am also not the best speller, feel free to call me out on spelling mistakes CC0 to embrace the GNU principles followed by Minetest, you are allowed to relicense this to your hearts desires This mod is designed for the community's fun Idea sources https://en.wikipedia.org/wiki/Mob_(video_gaming) Help from tenplus1 https://github.com/tenplus1/mobs_redo/blob/master/api.lua Help from pilzadam https://github.com/PilzAdam/mobs/blob/master/api.lua --notes on how to create mobs the height will divide by 2 to find the height, make your mob mesh centered in the editor to fit centered in the collisionbox This is done to use the actual center of mobs in functions, same with width ]]-- --global to enable other mods/packs to utilize the ai open_ai = {} open_ai.register_mob = function(name,def) minetest.register_entity(name, { --Do simpler definition variables for ease of use collisionbox = {-def.width/2,-def.height/2,-def.width/2,def.width/2,def.height/2,def.width/2}, height = def.height, width = def.width, physical = def.physical, max_velocity = def.max_velocity, acceleration = def.acceleration, automatic_face_movement_dir = def.automatic_face_movement_dir, --for smoothness --Aesthetic variables visual = def.visual, mesh = def.mesh, textures = def.textures, makes_footstep_sound = def.makes_footstep_sound, animation = def.animation, --Behavioral variables behavior_timer = 0, --when this reaches behavior change goal, it changes states and resets behavior_timer_goal = 0, --randomly selects between min and max time to change direction behavior_change_min = def.behavior_change_min, behavior_change_max = def.behavior_change_max, update_timer = 0, follow_item = def.follow_item, --Physical variables old_position = nil, yaw = 0, jump_height = def.jump_height, --Pathfinding variables path = {}, target = "singleplayer", following = false, --what mobs do when created on_activate = function(self, staticdata, dtime_s) --debug for movement self.velocity = math.random(1,self.max_velocity)+math.random() self.behavior_timer_goal = math.random(self.behavior_change_min,self.behavior_change_max) local pos = self.object:getpos() pos.y = pos.y - (self.height/2) -- the bottom of the entity --self.old_position = vector.floor(pos) self.yaw = (math.random(0, 360)/360) * (math.pi*2) end, --decide wether an entity should jump or change direction jump = function(self) local pos = self.object:getpos() --find out if node is underneath local under_node = minetest.get_node({x=pos.x,y=pos.y-(self.height/2)-0.1,z=pos.z}).name if minetest.registered_nodes[under_node].walkable == false then --print("JUMP FAILURE") return end local vel = self.object:getvelocity() --commented out section is to use vel to get yaw dir, hence redeffing it as local yaw verus self.yaw local yaw = self.yaw--(math.atan(vel.z / vel.x) + math.pi / 2) --don't check if not moving instead change direction if yaw == yaw then --check for nan --turn it into usable position modifier local x = (math.sin(yaw) * -1)*1.5 local z = (math.cos(yaw))*1.5 local node = minetest.get_node({x=pos.x+x,y=pos.y,z=pos.z+z}).name if minetest.registered_nodes[node].walkable == true then --print("jump") self.object:setvelocity({x=vel.x,y=self.jump_height,z=vel.z}) end end end, --this runs everything that happens when a mob update timer resets update = function(self,dtime) self.update_timer = self.update_timer + dtime if self.update_timer >= 0.1 then self.update_timer = 0 self.jump(self) self.path_find(self) end end, --how a mob thinks behavior = function(self,dtime) self.behavior_timer = self.behavior_timer + dtime local vel = self.object:getvelocity() --debug to find node the mob exists in local testpos = self.object:getpos() testpos.y = testpos.y-- - (self.height/2) -- the bottom of the entity local vec_pos = vector.floor(testpos) -- the node that the mob exists in --debug test to change behavior if self.following == false and self.behavior_timer >= self.behavior_timer_goal then --print("Changed direction") --self.goal = {x=math.random(-self.max_velocity,self.max_velocity),y=math.random(-self.max_velocity,self.max_velocity),z=math.random(-self.max_velocity,self.max_velocity)} self.yaw = (math.random(0, 360)/360) * (math.pi*2) --double pi to allow complete rotation self.velocity = math.random(1,self.max_velocity)+math.random() self.behavior_timer_goal = math.random(self.behavior_change_min,self.behavior_change_max) self.behavior_timer = 0 print("randomly moving around") elseif self.following == true then print("following in behavior function") end end, -- how a mob moves around the world movement = function(self) --move mob to goal velocity using acceleration for smoothness local vel = self.object:getvelocity() local x = math.sin(self.yaw) * -self.velocity local z = math.cos(self.yaw) * self.velocity self.object:setacceleration({x=(x - vel.x)*self.acceleration,y=-10,z=(z - vel.z)*self.acceleration}) end, --check if a mob should follow a player when holding an item check_to_follow = function(self) --print(dump(self.follow_item)) local pos = self.object:getpos() for _,object in ipairs(minetest.env:get_objects_inside_radius(pos, 10)) do if object:is_player() then local item = object:get_wielded_item() if item:to_string() ~= "" and item:to_table().name == self.follow_item then self.following = true else self.following = false end end end end, --path finding towards goal - can be used to find food or water, or attack players or other mobs path_find = function(self) if self.following == true then self.velocity = self.max_velocity print("following player") local pos1 = self.object:getpos() local pos2 = minetest.get_player_by_name(self.target):getpos() -- this is the goal debug local path = minetest.find_path(pos1,pos2,5,1,3,"Dijkstra") --Debug to visualize mob paths if table.getn(self.path) > 0 then for _,pos in pairs(self.path) do minetest.add_particle({ pos = pos, velocity = {x=0, y=0, z=0}, acceleration = {x=0, y=0, z=0}, expirationtime = 0.5, size = 4, collisiondetection = false, vertical = false, texture = "heart.png", }) end end --debug pathfinding if path and table.getn(path) > 2 then self.path = path --print("going to player") local pos3 = self.path[3] minetest.add_particle({ pos = pos3, velocity = {x=0, y=0, z=0}, acceleration = {x=0, y=0, z=0}, expirationtime = 0.5, size = 4, collisiondetection = false, vertical = false, texture = "default_stone.png", }) local vec = {x=pos1.x-pos3.x, z=pos1.z-pos3.z} --print(vec.x,vec.z) self.yaw = math.atan(vec.z/vec.x)+ math.pi / 2 if pos3.x > pos1.x then self.yaw = self.yaw+math.pi end --end --if failed to update cost map then continue on old path elseif table.getn(self.path) > 3 then local pathlength = table.getn(self.path) local path = minetest.find_path(pos1,self.path[pathlength],5,1,3,"Dijkstra") if path and table.getn(path) > 2 then self.path = path local pos3 = self.path[3] minetest.add_particle({ pos = pos3, velocity = {x=0, y=0, z=0}, acceleration = {x=0, y=0, z=0}, expirationtime = 0.5, size = 4, collisiondetection = false, vertical = false, texture = "default_stone.png", }) local vec = {x=pos1.x-pos3.x, z=pos1.z-pos3.z} --print(vec.x,vec.z) self.yaw = math.atan(vec.z/vec.x)+ math.pi / 2 if pos3.x > pos1.x then self.yaw = self.yaw+math.pi end end end end end, set_animation = function(self) local vel = self.object:getvelocity() local speed = (math.abs(vel.x)+math.abs(vel.z))*self.animation.speed_normal --check this self.object:set_animation({x=self.animation.walk_start,y=self.animation.walk_end}, speed, 0, true) end, --what mobs do on each server step on_step = function(self,dtime) self.check_to_follow(self) self.behavior(self,dtime) self.update(self,dtime) self.set_animation(self) self.movement(self) end, }) end --this is a test mob which can be used to learn how to make mobs using open ai - uses pilzadam's sheep mesh open_ai.register_mob("open_ai:test",{ --mob physical variables height = 0.7, --divide by 2 for even height width = 0.7, --divide by 2 for even width physical = true, --if the mob collides with the world, false is useful for ghosts jump_height = 5, --how high a mob will jump --mob movement variables max_velocity = 3, --set the max velocity that a mob can move acceleration = 3, --how quickly a mob gets up to max velocity behavior_change_min = 3, -- the minimum time a mob will wait to change it's behavior behavior_change_max = 5, -- the max time a mob will wait to change it's behavior --mob aesthetic variables visual = "mesh", --can be changed to anything for flexibility mesh = "mobs_sheep.x", textures = {"mobs_sheep.png"}, animation = { --the animation keyframes and speed speed_normal = 10,--animation speed stand_start = 0,--standing animation start and end stand_end = 80, walk_start = 81,--walking animation start and end walk_end = 100, }, automatic_face_movement_dir = -90.0, --what direction the mob faces in makes_footstep_sound = true, --if a mob makes footstep sounds --mob behavior variables follow_item = "default:dry_grass_1", --if you're holding this a peaceful mob will follow you })