405 lines
12 KiB
Lua
405 lines
12 KiB
Lua
--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
|
|
})
|