adaptive-ai/goblin/goblin_integration.lua
2018-06-26 19:20:49 +02:00

514 lines
15 KiB
Lua

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
})