dofile(minetest.get_modpath("adaptive_ai").."/kobold/kobold.lua") local kobolds = adaptive_ai.kobolds local stats = kobolds.stats local helper = adaptive_ai.helper local calc = adaptive_ai.calc local population = adaptive_ai.population local random = math.random local floor = math.floor local chat_log = adaptive_ai.chat_log local ai_on_step = adaptive_ai.ai_on_step local sort_nils_out = helper.sort_nils_out local get_surface = helper.search_surface local pop = helper.pop local shuffle = helper.shuffle local distance = calc.dist_euclidean local kobold_from_lines = kobolds.from_lines local kobold_score = kobolds.score local kobold_decide = kobolds.decide local kobold_create = kobolds.create local get_from_census = population.get_from_census local create_tribe = population.create_tribe local add_to_tribe = population.add_to_tribe local empty_population = population.empty local do_jump = helper.mobs.do_jump local set_velocity = helper.mobs.set_velocity local is_at_cliff = helper.mobs.is_at_cliff local set_yaw = helper.mobs.set_yaw local flight_check = helper.mobs.flight_check local set_animation = helper.mobs.set_animation local adjacent = helper.adjacent local file_tag = kobolds.file_tag local popul_path = population.path local train_path = adaptive_ai.modpath.."/training/" local pi = math.pi local actions = kobolds.actions local yaws = { 0, pi, 3*pi/2, pi/2, -- North, South, East, West 7*pi/4, 5*pi/4, pi/4, 3*pi/4 -- NE, SE, NW, SW } local food_nodes = {"adaptive_ai:moondrum_mushroom"} local founders = nil local min_founders = 4 local timer = 0 -- Out of Mobs_Redo -- Equal to do_states(self, dtime) for self.state == "walk" -- except without turning to random directions when safe local function state_move(self, dtime) local yaw = self.object:get_yaw() or 0 local s = self.object:get_pos() local lp = nil -- is there something I need to avoid? if self.water_damage > 0 and self.lava_damage > 0 then lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"}) elseif self.water_damage > 0 then lp = minetest.find_node_near(s, 1, {"group:water"}) elseif self.lava_damage > 0 then lp = minetest.find_node_near(s, 1, {"group:lava"}) end if lp then -- if mob in water or lava then look for land if (self.lava_damage and minetest.registered_nodes[self.standing_in].groups.lava) or (self.water_damage and minetest.registered_nodes[self.standing_in].groups.water) then lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone", "group:sand", node_ice, node_snowblock}) -- did we find land? if lp then local vec = { x = lp.x - s.x, z = lp.z - s.z } yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate if lp.x > s.x then yaw = yaw + pi end -- look towards land and jump/move in that direction yaw = set_yaw(self, yaw, 6) local jumped = do_jump(self) if jumped then chat_log("Jumping while moving") end set_velocity(self, self.walk_velocity) else yaw = yaw + random(-0.5, 0.5) end else local vec = { x = lp.x - s.x, z = lp.z - s.z } yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate if lp.x > s.x then yaw = yaw + pi end end yaw = set_yaw(self, yaw, 8) end -- stand for great fall in front local temp_is_cliff = is_at_cliff(self) if self.facing_fence == true or temp_is_cliff then set_velocity(self, 0) self.state = "stand" set_animation(self, "stand") else set_velocity(self, self.walk_velocity) if flight_check(self) and self.animation and self.animation.fly_start and self.animation.fly_end then set_animation(self, "fly") else set_animation(self, "walk") end end end local function move_creature(self, yaw_id) if self.state == "stand" or self.state == "walk" or self.state == "move" then self.object:set_yaw(yaws[yaw_id]) set_velocity(self, self.walk_velocity) self.state = "move" end return false end local function load_founders() local file = io.open(train_path..kobolds.trained_tag..".txt", "r") local counter = 0 if file then founders = {} local reading, kobold = false, {} for line in file:lines() do counter = counter + 1 if not reading then reading = line == file_tag[1] if reading then kobold = {} table.insert(kobold,line) end else reading = line ~= file_tag[2] table.insert(kobold,line) if not reading then kobold = kobold_from_lines(kobold) kobold = kobold_create(kobold) table.insert(founders, kobold) chat_log("Founder loaded: "..kobold.name) end end end file:close() end if #founders == 0 then --error("Founders not found") end end load_founders() local function get_founders(n) chat_log(#founders.." founders") founders = founders or {} local f while #founders < min_founders do f = {} kobold_create(f) table.insert(founders, f) end if #founders > min_founders then n = n or min_founders local tribe_founders = {} shuffle(founders) for i=1,n do table.insert(tribe_founders, founders[i]) end return tribe_founders else return founders end end local function load_kobold(self) chat_log("Loading from census "..(self.nametag and "true" or "false")) local old_self = get_from_census(self) if old_self then self.kobold = old_self if not old_self.genes then error("No genes") end chat_log(old_self.name.." loaded "..#old_self.genes.." genes") self.timer = self.walk_velocity return true else --self.kobold = nil --chat_log("Nil kobold") return false end end local function set_kobold(self, kobold) self.kobold = kobold self.nametag = kobold.name end local function setup_kobold(self, mother, father) if load_kobold(self) then return else local kobold = {} kobold_create(kobold, mother, father) set_kobold(self, kobold) end return false end local function breed_kobold(self) local mother = self.kobold if not mother.tribe_id then --error("Breeding with no tribe.") return end local tribe_id = mother.tribe_id local tribe = population.tribes[tribe_id] if tribe then local popul_size = tribe:popul_size() if popul_size < tribe.size * 2 then local p = mother.pos p = {x=(p.x), y=(p.y), z=(p.z)} p.x = p.x + (random(0,1) - 0.5)*2 p.z = p.z + (random(0,1) - 0.5)*2 local father = get_couple(tribe_id, self.kobold) local child = place_kobold(p) setup_kobold(child, mother, father) child = child.kobold child.tribe_id = tribe_id population.add_to_tribe(tribe_id, child) return true end end return false end kobolds.breed_kobold = breed_kobold local kobold_decisions = { function(self, dtime) -- idle set_velocity(self, 0) end, function(self, dtime) -- wander local n = random(1,#yaws) local move = move_creature(self, n) local kobold = self.kobold if move then kobold.hunger = kobold.hunger - 1 self.state = "walk" end end, function(self, dtime) -- eat local kobold = self.kobold if kobold.food and #kobold.food > 0 then local satiate = pop(kobold.food) kobold.hunger = kobold.hunger + satiate kobold.hp = self.object:get_hp() + 1 self.object:set_hp(kobold.hp) end end, function(self, dtime) -- breed local kobold = self.kobold kobold.fitness = kobold.fitness + 2 kobold.hunger = kobold.hunger - 10 kobolds.breed_kobold(self) end, function(self, dtime) -- move North local move = move_creature(self, 1) self.state = move and "move" or self.state end, function(self, dtime) -- move South local move = move_creature(self, 2) self.state = move and "move" or self.state end, function(self, dtime) -- move East local move = move_creature(self, 3) self.state = move and "move" or self.state end, function(self, dtime) -- move West local move = move_creature(self, 4) self.state = move and "move" or self.state end, function(self, dtime) -- move NE local move = move_creature(self, 5) self.state = move and "move" or self.state end, function(self, dtime) -- move SE local move = move_creature(self, 6) self.state = move and "move" or self.state end, function(self, dtime) -- move NW local move = move_creature(self, 7) self.state = move and "move" or self.state end, function(self, dtime) -- move SW local move = move_creature(self, 8) self.state = move and "move" or self.state end, } local manage_kobold = function(self, dtime) local kobold = self.kobold if kobold then if kobold.delete or kobold.hp <= 0 then self.object:remove() return end kobold.hp = self.object:get_hp() self.timer = self.timer + dtime if kobold.hp > 0 then local p = self.object:get_pos() p = {x=floor(p.x+0.5), y=floor(p.y+0.5), z=floor(p.z+0.5)} kobold.pos = p local food_pos = minetest.find_node_near(self.object:get_pos(), self.reach, food_nodes) if food_pos then --chat_log("Pocketing food from "..food_pos.x..","..food_pos.z) if kobold.closest_food and kobold.closest_food.x == food_pos.x and kobold.closest_food.z == food_pos.z then kobold.closest_food = nil end table.insert(kobold.food, random(2,5)) minetest.set_node(food_pos, {name = "air"}) end food_pos = minetest.find_node_near(self.object:get_pos(), self.view_range, food_nodes) if food_pos then --chat_log("Smelling good food") food_pos = {x=floor(food_pos.x+0.5),y=floor(food_pos.y+0.5),z=floor(food_pos.z+0.5)} if food_pos and (not kobold.closest_food or distance(p, food_pos) < distance(p, kobold.closest_food)) then kobold.closest_food = food_pos end end if not kobold.action or self.timer >= self.walk_velocity then self.timer = 0 --chat_log("Time to decide for "..kobold.name) kobold.hunger = kobold.hunger - dtime local neighbors = {} local pos, pos2 = {} for i=1,#adjacent do pos.x = p.x + adjacent[i].x pos.y = p.y pos.z = p.z + adjacent[i].z pos2 = get_surface(pos) neighbors[i] = pos2 and {x=pos2.x,y=pos2.y,z=pos2.z} or {x=pos.x, y=self.kobold.climb + 1,z=pos.z} end chat_log("Closest food "..(kobold.closest_food and kobold.closest_food.x-p.x..","..kobold.closest_food.z-p.z or "none")) local decision, d_id = kobold_decide(kobold, neighbors) chat_log(kobold.name.." decides "..(decision and actions[decision] or "nothing").." -- "..(d_id or "error")) kobold.action = decision if decision then kobold_decisions[decision](self, dtime) else if not kobold.action then kobold.action = 1 kobold_decisions[1](self, dtime) end end kobold.fitness = kobold.fitness + kobold_score(kobold) end if kobold.hunger < 0 then kobold.hunger = 0 kobold.hp = self.object:get_hp() - 1 self.object:set_hp(kobold.hp) end end else chat_log("No kobold") end end local function custom_on_step(self, dtime) manage_kobold(self, dtime) if self.state == "move" then state_move(self, dtime) end do_jump(self, dtime) --ai_on_step(self, dtime) return false end local function place_kobold(p) local ent = adaptive_ai:place_ai({ name = "adaptive_ai:kobold", pos = p, }) return ent end local function place_tribe(p) local roots = {x=p.x, y=p.y-2, z=p.z} local ents = {} local popul = create_tribe(p, "adaptive_ai:kobold", get_founders(), kobolds.stats.view_range, kobolds.clone, 5) local ent, pos, c c = 0 for i,k in ipairs(popul) do pos = get_surface(k.pos) ent = place_kobold(pos) set_kobold(ent, k) --chat_log("Placing "..ent.nametag.."!") ent.kobold.pos = pos end chat_log("Placed tribe!") minetest.set_node(roots, {name = "adaptive_ai:moondrum_roots"}) end adaptive_ai:register_ai("adaptive_ai:kobold", { stepheight = 0.8, hp_min = kobolds.stats.hp_max, hp_max = kobolds.stats.hp_max, armor = 100, walk_velocity = 1.5, run_velocity = 2, jump = true, jump_height = kobolds.stats.jump, view_range = kobolds.stats.view_range, damage = 2, knock_back = true, fear_height = kobolds.stats.fear_height, floats = 1, reach = 1, attack_type = "dogfight", makes_footstep_sound = true, visual = "cube", visual_size = {x=0.8, y=1.6}, textures = { "aa_imp_top.png", "aa_imp_top.png", "aa_imp_side.png", "aa_imp_side.png", "aa_imp_face.png", "aa_imp_side.png", }, collisionbox = {-0.5,-0.8,-0.5, 0.5,0.8,0.5}, walk_chance = 1, actions = kobolds.actions, action_managers = kobold_decisions, on_spawn = load_kobold, do_custom = custom_on_step, asexual = false, breed = kobold_create, }) minetest.register_chatcommand("kobold", { func = function(name, param) local player = minetest.get_player_by_name(name) local pos = player and player:getpos() or nil if pos then place_tribe(pos) end end }) minetest.register_chatcommand("empty_camps", { func = function(name, param) empty_population() end }) minetest.register_chatcommand("founders", { func = function(name, param) for i,f in ipairs(founders) do chat_log("Founder "..f.name) end end })