local math_random = math.random local math_pi = math.pi local math_floor = math.floor local math_round = math.round local vector_multiply = vector.multiply local vector_add = vector.add local vector_new = vector.new local vector_distance = vector.distance local minetest_yaw_to_dir = minetest.yaw_to_dir local minetest_get_item_group = minetest.get_item_group local minetest_get_node = minetest.get_node local minetest_line_of_sight = minetest.line_of_sight local DOUBLE_PI = math.pi * 2 local THIRTY_SECONDTH_PI = DOUBLE_PI * 0.03125 --a simple helper function which is too small to move into movement.lua local quick_rotate = function(self,dtime) self.yaw = self.yaw + THIRTY_SECONDTH_PI if self.yaw > DOUBLE_PI then self.yaw = self.yaw - DOUBLE_PI end end --a simple helper function for rounding --http://lua-users.org/wiki/SimpleRound function round2(num, numDecimalPlaces) return tonumber(string.format("%." .. (numDecimalPlaces or 0) .. "f", num)) end --[[ _ _ | | | | | | __ _ _ __ __| | | | / _` | '_ \ / _` | | |___| (_| | | | | (_| | \_____/\__,_|_| |_|\__,_| ]]-- --this is basically reverse jump_check local cliff_check = function(self,dtime) --mobs will flip out if they are falling without this if self.object:get_velocity().y ~= 0 then return false end local pos = self.object:get_pos() local dir = minetest_yaw_to_dir(self.yaw) local collisionbox = self.object:get_properties().collisionbox local radius = collisionbox[4] + 0.5 dir = vector_multiply(dir,radius) local free_fall, blocker = minetest_line_of_sight( {x = pos.x + dir.x, y = pos.y, z = pos.z + dir.z}, {x = pos.x + dir.x, y = pos.y - self.fear_height, z = pos.z + dir.z}) return free_fall end -- state switching logic (stand, walk, run, attacks) local land_state_list_wandering = {"stand", "walk"} local land_state_switch = function(self, dtime) --do math before sure not attacking, following, or running away so continue --doing random walking for mobs if all states are not met self.state_timer = self.state_timer - dtime --only run away if self.skittish and self.state == "run" then self.run_timer = self.run_timer - dtime if self.run_timer > 0 then return end --continue end --ignore everything else if breeding if self.breed_lookout_timer and self.breed_lookout_timer > 0 then self.state = "breed" return --reset the state timer to get the mob out of --the breed state elseif self.state == "breed" then self.state_timer = 0 end --ignore everything else if following if mobs.check_following(self) and (not self.breed_lookout_timer or (self.breed_lookout_timer and self.breed_lookout_timer == 0)) and (not self.breed_timer or (self.breed_timer and self.breed_timer == 0)) then self.state = "follow" return --reset the state timer to get the mob out of --the follow state - not the cleanest option --but the easiest elseif self.state == "follow" then self.state_timer = 0 end --only attack if self.hostile and self.attacking then self.state = "attack" return end --if finally reached here then do random wander if self.state_timer <= 0 then self.state_timer = math.random(4,10) + math.random() self.state = land_state_list_wandering[math.random(1,#land_state_list_wandering)] end end -- states are executed here local land_state_execution = function(self,dtime) --[[ -- this is a debug which shows the timer and makes mobs breed 100 times faster print(self.breed_timer) if self.breed_timer > 0 then self.breed_timer = self.breed_timer - (dtime * 100) if self.breed_timer <= 0 then self.breed_timer = 0 end end ]]-- --no collisionbox exception if not self.object:get_properties() then return end --timer to time out looking for mate if self.breed_lookout_timer and self.breed_lookout_timer > 0 then self.breed_lookout_timer = self.breed_lookout_timer - dtime --looking for mate failed if self.breed_lookout_timer <= 0 then self.breed_lookout_timer = 0 end end --cool off after breeding if self.breed_timer and self.breed_timer > 0 then self.breed_timer = self.breed_timer - dtime --do this to skip the first check, using as switch if self.breed_timer <= 0 then self.breed_timer = 0 end end local pos = self.object:get_pos() local collisionbox = self.object:get_properties().collisionbox --get the center of the mob pos.y = pos.y + (collisionbox[2] + collisionbox[5] / 2) local current_node = minetest_get_node(pos).name local float_now = false --recheck if in water or lava if minetest_get_item_group(current_node, "water") ~= 0 or minetest_get_item_group(current_node, "lava") ~= 0 then float_now = true end --make slow falling mobs fall slow if self.fall_slow then if self.object:get_velocity().y < 0 then --lua is acting really weird so we have to help it if round2(self.object:get_acceleration().y, 1) == -self.gravity then self.object:set_acceleration(vector_new(0,0,0)) mobs.mob_fall_slow(self) end else if round2(self.object:get_acceleration().y, 1) == 0 then self.object:set_acceleration(vector_new(0,-self.gravity,0)) end end end if self.state == "stand" then --do animation mobs.set_mob_animation(self, "stand") --set the velocity of the mob mobs.set_velocity(self,0) --animation fixes for explosive mobs if self.attack_type == "explode" then mobs.reverse_explosion_animation(self,dtime) end mobs.lock_yaw(self) elseif self.state == "follow" then --always look at players mobs.set_yaw_while_following(self) --check distance local distance_from_follow_person = vector_distance(self.object:get_pos(), self.following_person:get_pos()) local distance_2d = mobs.get_2d_distance(self.object:get_pos(), self.following_person:get_pos()) --don't push the player if too close --don't spin around randomly if self.follow_distance < distance_from_follow_person and self.minimum_follow_distance < distance_2d then mobs.set_mob_animation(self, "run") mobs.set_velocity(self,self.run_velocity) if mobs.jump_check(self) == 1 then mobs.jump(self) end else mobs.set_mob_animation(self, "stand") mobs.set_velocity(self,0) end elseif self.state == "walk" then self.walk_timer = self.walk_timer - dtime --reset the walk timer if self.walk_timer <= 0 then --re-randomize the walk timer self.walk_timer = math.random(1,6) + math.random() --set the mob into a random direction self.yaw = (math_random() * (math.pi * 2)) end --do animation mobs.set_mob_animation(self, "walk") --enable rotation locking mobs.movement_rotation_lock(self) --check for nodes to jump over local node_in_front_of = mobs.jump_check(self) if node_in_front_of == 1 then mobs.jump(self) --turn if on the edge of cliff --(this is written like this because unlike --jump_check which simply tells the mob to jump --this requires a mob to turn, removing the --ease of a full implementation for it in a single --function) elseif node_in_front_of == 2 or (self.fear_height ~= 0 and cliff_check(self,dtime)) then --turn 45 degrees if so quick_rotate(self,dtime) --stop the mob so it doesn't fall off mobs.set_velocity(self,0) end --only move forward if path is clear if node_in_front_of == 0 or node_in_front_of == 1 then --set the velocity of the mob mobs.set_velocity(self,self.walk_velocity) end --animation fixes for explosive mobs if self.attack_type == "explode" then mobs.reverse_explosion_animation(self,dtime) end elseif self.state == "run" then --do animation mobs.set_mob_animation(self, "run") --enable rotation locking mobs.movement_rotation_lock(self) --check for nodes to jump over local node_in_front_of = mobs.jump_check(self) if node_in_front_of == 1 then mobs.jump(self) --turn if on the edge of cliff --(this is written like this because unlike --jump_check which simply tells the mob to jump --this requires a mob to turn, removing the --ease of a full implementation for it in a single --function) elseif node_in_front_of == 2 or (self.fear_height ~= 0 and cliff_check(self,dtime)) then --turn 45 degrees if so quick_rotate(self,dtime) --stop the mob so it doesn't fall off mobs.set_velocity(self,0) end --only move forward if path is clear if node_in_front_of == 0 or node_in_front_of == 1 then --set the velocity of the mob mobs.set_velocity(self,self.run_velocity) end elseif self.state == "attack" then --execute mob attack type if self.attack_type == "explode" then mobs.explode_attack_walk(self, dtime) elseif self.attack_type == "punch" then mobs.punch_attack_walk(self,dtime) elseif self.attack_type == "projectile" then mobs.projectile_attack_walk(self,dtime) end elseif self.state == "breed" then mobs.breeding_effect(self) local mate = mobs.look_for_mate(self) --found a mate if mate then mobs.set_yaw_while_breeding(self,mate) mobs.set_velocity(self, self.walk_velocity) --smoosh together basically if vector_distance(self.object:get_pos(), mate:get_pos()) <= self.breed_distance then mobs.set_mob_animation(self, "stand") if self.special_breed_timer == 0 then self.special_breed_timer = 2 --breeding takes 2 seconds end self.special_breed_timer = self.special_breed_timer - dtime if self.special_breed_timer <= 0 then --pop a baby out, it's a miracle! local baby_pos = vector.divide(vector.add(self.object:get_pos(), mate:get_pos()), 2) local baby_mob = minetest.add_entity(pos, self.name, minetest.serialize({baby = true, grow_up_timer = self.grow_up_goal, bred = true})) self.special_breed_timer = 0 self.breed_lookout_timer = 0 self.breed_timer = self.breed_timer_cooloff mate:get_luaentity().special_breed_timer = 0 mate:get_luaentity().breed_lookout_timer = 0 mate:get_luaentity().breed_timer = self.breed_timer_cooloff -- can reuse because it's the same mob end else mobs.set_mob_animation(self, "walk") end --couldn't find a mate, just stand there until the player pushes it towards one --or the timer runs out else mobs.set_mob_animation(self, "stand") mobs.set_velocity(self,0) end end if float_now then mobs.float(self) end end --[[ _____ _ / ___| (_) \ `--.__ ___ _ __ ___ `--. \ \ /\ / / | '_ ` _ \ /\__/ /\ V V /| | | | | | | \____/ \_/\_/ |_|_| |_| |_| ]]-- -- state switching logic (stand, walk, run, attacks) local swim_state_list_wandering = {"stand", "swim"} local swim_state_switch = function(self, dtime) self.state_timer = self.state_timer - dtime if self.state_timer <= 0 then self.state_timer = math.random(4,10) + math.random() self.state = swim_state_list_wandering[math.random(1,#swim_state_list_wandering)] end end --check if a mob needs to turn while swimming local swim_turn_check = function(self,dtime) local pos = self.object:get_pos() pos.y = pos.y + 0.1 local dir = minetest_yaw_to_dir(self.yaw) local collisionbox = self.object:get_properties().collisionbox local radius = collisionbox[4] + 0.5 vector_multiply(dir, radius) local test_dir = vector.add(pos,dir) local green_flag_1 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") ~= 0 return(green_flag_1) end --this is to swap the built in engine acceleration modifier local swim_physics_swapper = function(self,inside_swim_node) --should be swimming, gravity is applied, switch to floating if inside_swim_node and self.object:get_acceleration().y ~= 0 then self.object:set_acceleration(vector_new(0,0,0)) --not be swim, gravity isn't applied, switch to falling elseif not inside_swim_node and self.object:get_acceleration().y == 0 then self.pitch = 0 self.object:set_acceleration(vector_new(0,-self.gravity,0)) end end local random_pitch_multiplier = {-1,1} -- states are executed here local swim_state_execution = function(self,dtime) local pos = self.object:get_pos() pos.y = pos.y + self.object:get_properties().collisionbox[5] local current_node = minetest_get_node(pos).name local inside_swim_node = false --quick scan everything to see if inside swim node for _,id in pairs(self.swim_in) do if id == current_node then inside_swim_node = true break end end --turn gravity on or off swim_physics_swapper(self,inside_swim_node) --swim properly if inside swim node if inside_swim_node then if self.state == "stand" then --do animation mobs.set_mob_animation(self, "stand") mobs.set_swim_velocity(self,0) if self.tilt_swim then mobs.set_static_pitch(self) end mobs.lock_yaw(self) elseif self.state == "swim" then self.walk_timer = self.walk_timer - dtime --reset the walk timer if self.walk_timer <= 0 then --re-randomize the walk timer self.walk_timer = math.random(1,6) + math.random() --set the mob into a random direction self.yaw = (math_random() * (math.pi * 2)) --create a truly random pitch, since there is no easy access to pitch math that I can find self.pitch = math_random() * math.random(1,3) * random_pitch_multiplier[math_random(1,2)] end --do animation mobs.set_mob_animation(self, "walk") --do a quick turn to make mob continuously move --if in a fish tank or something if swim_turn_check(self,dtime) then quick_rotate(self,dtime) end mobs.set_swim_velocity(self,self.walk_velocity) --only enable tilt swimming if enabled if self.tilt_swim then mobs.set_dynamic_pitch(self) end --enable rotation locking mobs.movement_rotation_lock(self) end --flop around if not inside swim node else --do animation mobs.set_mob_animation(self, "stand") mobs.flop(self) if self.tilt_swim then mobs.set_static_pitch(self) end end end --[[ ______ _ | ___| | | |_ | |_ _ | _| | | | | | | | | | |_| | \_| |_|\__, | __/ | |___/ ]]-- -- state switching logic (stand, walk, run, attacks) local fly_state_list_wandering = {"stand", "fly"} local fly_state_switch = function(self, dtime) if self.hostile and self.attacking then self.state = "attack" return end self.state_timer = self.state_timer - dtime if self.state_timer <= 0 then self.state_timer = math.random(4,10) + math.random() self.state = fly_state_list_wandering[math.random(1,#fly_state_list_wandering)] end end --check if a mob needs to turn while flying local fly_turn_check = function(self,dtime) local pos = self.object:get_pos() pos.y = pos.y + 0.1 local dir = minetest_yaw_to_dir(self.yaw) local collisionbox = self.object:get_properties().collisionbox local radius = collisionbox[4] + 0.5 vector_multiply(dir, radius) local test_dir = vector.add(pos,dir) local green_flag_1 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") ~= 0 return(green_flag_1) end --this is to swap the built in engine acceleration modifier local fly_physics_swapper = function(self,inside_fly_node) --should be flyming, gravity is applied, switch to floating if inside_fly_node and self.object:get_acceleration().y ~= 0 then self.object:set_acceleration(vector_new(0,0,0)) --not be fly, gravity isn't applied, switch to falling elseif not inside_fly_node and self.object:get_acceleration().y == 0 then self.pitch = 0 self.object:set_acceleration(vector_new(0,-self.gravity,0)) end end local random_pitch_multiplier = {-1,1} -- states are executed here local fly_state_execution = function(self,dtime) local pos = self.object:get_pos() local current_node = minetest_get_node(pos).name local inside_fly_node = minetest_get_item_group(current_node, "solid") == 0 local float_now = false --recheck if in water or lava if minetest_get_item_group(current_node, "water") ~= 0 or minetest_get_item_group(current_node, "lava") ~= 0 then inside_fly_node = false float_now = true end --turn gravity on or off fly_physics_swapper(self,inside_fly_node) --fly properly if inside fly node if inside_fly_node then if self.state == "stand" then --do animation mobs.set_mob_animation(self, "stand") mobs.set_fly_velocity(self,0) if self.tilt_fly then mobs.set_static_pitch(self) end mobs.lock_yaw(self) elseif self.state == "fly" then self.walk_timer = self.walk_timer - dtime --reset the walk timer if self.walk_timer <= 0 then --re-randomize the walk timer self.walk_timer = math.random(1,6) + math.random() --set the mob into a random direction self.yaw = (math_random() * (math.pi * 2)) --create a truly random pitch, since there is no easy access to pitch math that I can find self.pitch = math_random() * math.random(1,3) * random_pitch_multiplier[math_random(1,2)] end --do animation mobs.set_mob_animation(self, "walk") --do a quick turn to make mob continuously move --if in a bird cage or something if fly_turn_check(self,dtime) then quick_rotate(self,dtime) end if self.tilt_fly then mobs.set_dynamic_pitch(self) end mobs.set_fly_velocity(self,self.walk_velocity) --enable rotation locking mobs.movement_rotation_lock(self) elseif self.state == "attack" then --execute mob attack type --if self.attack_type == "explode" then --mobs.explode_attack_fly(self, dtime) --elseif self.attack_type == "punch" then --mobs.punch_attack_fly(self,dtime) if self.attack_type == "projectile" then mobs.projectile_attack_fly(self,dtime) end end else --make the mob float if self.floats and float_now then mobs.set_velocity(self, 0) mobs.float(self) if self.tilt_fly then mobs.set_static_pitch(self) end end end end --[[ ___ |_ | | |_ _ _ __ ___ _ __ | | | | | '_ ` _ \| '_ \ /\__/ / |_| | | | | | | |_) | \____/ \__,_|_| |_| |_| .__/ | | |_| ]]-- --check if a mob needs to turn while jumping local jump_turn_check = function(self,dtime) local pos = self.object:get_pos() pos.y = pos.y + 0.1 local dir = minetest_yaw_to_dir(self.yaw) local collisionbox = self.object:get_properties().collisionbox local radius = collisionbox[4] + 0.5 vector_multiply(dir, radius) local test_dir = vector.add(pos,dir) local green_flag_1 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") ~= 0 return(green_flag_1) end -- state switching logic (stand, jump, run, attacks) local jump_state_list_wandering = {"stand", "jump"} local jump_state_switch = function(self, dtime) self.state_timer = self.state_timer - dtime if self.state_timer <= 0 then self.state_timer = math.random(4,10) + math.random() self.state = jump_state_list_wandering[math.random(1,#jump_state_list_wandering)] end end -- states are executed here local jump_state_execution = function(self,dtime) local pos = self.object:get_pos() local collisionbox = self.object:get_properties().collisionbox --get the center of the mob pos.y = pos.y + (collisionbox[2] + collisionbox[5] / 2) local current_node = minetest_get_node(pos).name local float_now = false --recheck if in water or lava if minetest_get_item_group(current_node, "water") ~= 0 or minetest_get_item_group(current_node, "lava") ~= 0 then float_now = true end if self.state == "stand" then --do animation mobs.set_mob_animation(self, "stand") --set the velocity of the mob mobs.set_velocity(self,0) mobs.lock_yaw(self) elseif self.state == "jump" then self.walk_timer = self.walk_timer - dtime --reset the jump timer if self.walk_timer <= 0 then --re-randomize the jump timer self.walk_timer = math.random(1,6) + math.random() --set the mob into a random direction self.yaw = (math_random() * (math.pi * 2)) end --do animation mobs.set_mob_animation(self, "walk") --enable rotation locking mobs.movement_rotation_lock(self) --jumping mobs are more loosey goosey if node_in_front_of == 1 then quick_rotate(self,dtime) end --only move forward if path is clear mobs.jump_move(self,self.walk_velocity) elseif self.state == "run" then print("run") elseif self.state == "attack" then print("attack") end if float_now then mobs.float(self) end end --[[ ___ ___ _ _ _ | \/ | (_) | | (_) | . . | __ _ _ _ __ | | ___ __ _ _ ___ | |\/| |/ _` | | '_ \ | | / _ \ / _` | |/ __| | | | | (_| | | | | | | |___| (_) | (_| | | (__ \_| |_/\__,_|_|_| |_| \_____/\___/ \__, |_|\___| __/ | |___/ ]]-- --the main loop mobs.mob_step = function(self, dtime) --do not continue if non-existent if not self or not self.object or not self.object:get_luaentity() then self.object:remove() return false end --despawn mechanism --don't despawned tamed or bred mobs if not self.tamed and not self.bred then self.lifetimer = self.lifetimer - dtime if self.lifetimer <= 0 then self.lifetimer = self.lifetimer_reset if not mobs.check_for_player_within_area(self, 64) then --print("removing in MAIN LOGIC!") self.object:remove() return end end end --color modifier which coincides with the pause_timer if self.old_health and self.health < self.old_health then self.object:set_texture_mod("^[colorize:red:120") end self.old_health = self.health --do death logic (animation, poof, explosion, etc) if self.health <= 0 then mobs.death_logic(self, dtime) --this is here because the mob must continue to move --while stunned before coming to a complete halt even during --the death tilt if self.pause_timer > 0 then self.pause_timer = self.pause_timer - dtime --perfectly reset pause_timer if self.pause_timer < 0 then self.pause_timer = 0 end end return end --baby grows up if self.baby then --print(self.grow_up_timer) --catch missing timer if not self.grow_up_timer then self.grow_up_timer = self.grow_up_goal end self.grow_up_timer = self.grow_up_timer - dtime --baby grows up! if self.grow_up_timer <= 0 then self.grow_up_timer = 0 mobs.baby_grow_up(self) end end --do custom mob instructions if self.do_custom then -- when false skip going any further if self.do_custom(self, dtime) == false then --this needs to be here or the mob becomes immortal if self.pause_timer > 0 then self.pause_timer = self.pause_timer - dtime --perfectly reset pause_timer if self.pause_timer <= 0 then self.pause_timer = 0 self.object:set_texture_mod("") end end --this overrides internal lua collision detection return end end local attacking = nil --scan for players within eyesight if self.hostile then --true for line_of_sight is debug attacking = mobs.detect_closest_player_within_radius(self,true,self.view_range,self.eye_height) --go get the closest player if attacking then self.memory = 6 --6 seconds of memory --set initial punch timer if self.attacking == nil then if self.attack_type == "punch" then self.punch_timer = -1 end end self.attacking = attacking --no player in area elseif self.memory > 0 then --try to remember self.memory = self.memory - dtime --get if memory player is within viewing range if self.attacking and self.attacking:is_player() then local distance = vector_distance(self.object:get_pos(), self.attacking:get_pos()) if distance > self.view_range then self.memory = 0 end --out of viewing range, forget em else self.memory = 0 end if self.memory <= 0 then --reset states when coming out of hostile state if self.attacking ~= nil then self.state_timer = -1 end self.attacking = nil self.memory = 0 end end end --count down hostile cooldown timer when no players in range if self.neutral and self.hostile and not attacking and self.hostile_cooldown_timer then self.hostile_cooldown_timer = self.hostile_cooldown_timer - dtime if self.hostile_cooldown_timer <= 0 then self.hostile = false self.hostile_cooldown_timer = 0 end end --mob is stunned after being hit if self.pause_timer > 0 then self.pause_timer = self.pause_timer - dtime --don't break eye contact if self.hostile and self.attacking then mobs.set_yaw_while_attacking(self) end --perfectly reset pause_timer if self.pause_timer <= 0 then self.pause_timer = 0 self.object:set_texture_mod("") end --stop walking mobs from falling through the water if not self.jump_only and not self.swim and not self.fly then local pos = self.object:get_pos() local collisionbox = self.object:get_properties().collisionbox --get the center of the mob pos.y = pos.y + (collisionbox[2] + collisionbox[5] / 2) local current_node = minetest_get_node(pos).name --recheck if in water or lava if minetest_get_item_group(current_node, "water") ~= 0 or minetest_get_item_group(current_node, "lava") ~= 0 then mobs.float(self) end end --stop projectile mobs from being completely disabled while stunned if self.projectile_timer and self.projectile_timer > 0.01 then self.projectile_timer = self.projectile_timer - dtime if self.projectile_timer < 0.01 then self.projectile_timer = 0.01 end end return -- don't allow collision detection --do normal ai else --jump only (like slimes) if self.jump_only then jump_state_switch(self, dtime) jump_state_execution(self, dtime) --swimming elseif self.swim then swim_state_switch(self, dtime) swim_state_execution(self, dtime) --flying elseif self.fly then fly_state_switch(self, dtime) fly_state_execution(self,dtime) --regular mobs that walk around else land_state_switch(self, dtime) land_state_execution(self,dtime) end end -- can mob be pushed, if so calculate direction -- do this last (overrides everything) if self.pushable then mobs.collision(self) end self.old_velocity = self.object:get_velocity() self.old_pos = self.object:get_pos() end