Hello,
I want an allied AI to follow behind the main character and to fight just like the Enemy AIs when they encounter the hero.
The Enemy AI would have to detect the Allied AI the same way as they detect the hero. Also, some options for the enemies to attack decide on who to attack or randomly attack maybe.
Features for Allied AI:
-Follow behind the hero
-Attack enemies based on distance or an option for the hero to call the Allied AI to attack the Enemy Ai the hero is attacking.
-Random attack decision
Enemy AI feature:
-Able to attack the hero or Allied AI based on Distance or an option for the leader/boss to call the enemy AI to attack the hero/Allied AI the leader/boss is attacking.
-Random attack decision
It can be that I would just have to flip the Enemy script around, but some glitched on TeleTransporters might occur.
I am currently working on exactly this.
However, my time is really limited during this part of the year.
I do have some code...
But it is still a WIP.
Things to look up on the Solarus API
Custom Entities...
They are the way to go.. However they do require some coding knowledge of Lua.
I'll have my code posted some time next year after my first dev cycle is over.
Good Luck...
Other key word topics on the forum are... companions, allies and AI.
Well, I guess I'll do it when I get the time. Thanks for the tips.
If you want I'll post the code I have presently. But do remember it is a wip with some funky search methods that will be simplified.
No reason to start from zero!
Let me know and I'll attach it.
PG
I think I'll try it out first.
Code Attached: This is the WIP for my General ALLY Code. With some coding goals. In the search code I identify an entity by name. However... this part of the code is archaic and "for entities" loops are a lot simpler. The collision technique is with link's sword so there are some built in collision detection with enemies.
local entity = ...
--Entity todo:...
-- PUT BEHAVIOR SCRIPT INTO A SEPARATE FILE...
-- PUT INTERACTION SCRIPT INTO A SEPARATE FILE...
-- update behavior script with variables for targeting #'s (default: 100)
-- local attitude = script with variables for targeting #'s (default: 100)
-- create local strength/health function...
-- create local morale function...
-- create global speed function...
-- game:set_value("p_" .. i .. "_distance_to_hero", entity_slots[i])
--local behavior = require("entities/lib/behavior")
--local interactions = require("entities/lib/interactions")?
-- behavior variables...
local game = entity:get_game()
local map = entity:get_map()
local m = sol.movement.create("target")
local direction = entity:get_direction()
local hero = game:get_map():get_entity("hero")
local hero_x, hero_y, hero_layer = hero:get_position()
local self_x, self_y, self_layer = entity:get_position()
local target_dt = nil
local target_dth = nil
local target_h = nil
local ally_dt = nil
local ally_dth = nil
local ally_h = nil
local rock_dt = nil
local rock_dth = nil
local rock_h = nil
-- BEHAVIOR VARIABLES...
local target_dt_distance = nil
local target_h_distance = nil
local target_dth_distance = nil
local target_a_distance = nil
local ally_dt_distance = nil
local ally_h_distance = nil
local ally_dth_distance = nil
local rock_dt_distance = nil
local rock_h_distance = nil
local rock_dth_distance = nil
local rock_a_distance = nil
-- BEHAVIOR ASSIGNMENTS
-- enemy behaviour sliders
target_dt_distance = 100
target_h_distance = 100
target_dth_distance = 100
target_a_distance = 100
-- ally behaviour sliders
ally_dt_distance = 100
ally_h_distance = 100
ally_dth_distance = 100
-- Projectile behaviour sliders
rock_dt_distance = 100
rock_h_distance = 100
rock_dth_distance = 100
rock_a_distance = 100
-- more on the list to be added later...
local going_enemy = false
local properties = {}
--local shield_sprite = nil
local weapon_sprite = nil
local towards_hero = false
local away_hero = false
local attacked_hero = false
local towards_enemy = false
local near_hero = false
local weapon_attack = nil
local current_xy = {}
local xspeed = 0
local yspeed = 0
local timer, enemy_check, shoot_timer, health_timer
function entity:set_properties(prop)
properties = prop
properties.life = 10
properties.damage = 2
properties.play_hero_seen_sound = false
properties.stopped_speed = 0
properties.normal_speed = 64
properties.faster_speed = 128
properties.weapon = "knife"
-- can be knife, pistol, shotgun, grenade, ect...
properties.regen = 0
end
function entity:on_created()
-- main_sprite =
self:create_sprite("allies/a_butterfly")
self:set_size(16, 16)
self:set_origin(8, 13)
x, y, layer = hero:get_position()
self:set_position(x, y, layer)
self:get_sprite():set_animation("walking")
-- shield_sprite = self:create_sprite(properties.shield_sprite)
end
function entity:on_restarted()
--current_xy.x, current_xy.y = self:get_position()
end
--function entity:on_position_changed(x, y)
-- if weapon ~= nil then
-- local dx = x - current_xy.x
-- local dy = y - current_xy.y
-- local weapon_x, weapon_y = weapon:get_position()
-- weapon:set_position(weapon_x + dx, weapon_y + dy)
-- end
-- current_xy.x, current_xy.y = x, y
--end
-- PUT THIS BEHAVIOR SCRIPT INTO A SEPARATE FILE...
-- PUT INTERACTION SCRIPT INTO A SEPARATE FILE...
function entity:begin()
-- allies:create(game)
-- item:set_savegame_variable("e102")
timer = sol.timer.start(entity, 500, function()
-- Checks for the presence, and location of enemies and allies on the map in relationship to self, ally and hero. The first look...
-- ENEMY sight... turned into save game variables.
-- Naming System... e: enemy, p: projectile,
-- sw: switch, d: door, s: stairs, n: npc, pi: pickable, b: boss
print ("INPUT BEGIN")
print(target_dt)
print(target_dth)
print(ally_dt)
print(ally_dth)
local entity_slots = {}
for i = 1, 25 do
local hero = game:get_map():get_entity("hero")
local hero_x, hero_y, hero_layer = hero:get_position()
local self_x, self_y, self_layer = entity:get_position()
local distance_hero = math.abs((hero_x+hero_y)-(self_x+self_y))
local target = game:get_map():get_entity("e_" .. i .. "")
local ally = game:get_map():get_entity("a_" .. i .. "")
local rock = game:get_map():get_entity("p_" .. i .. "")
-- local door = game:get_map():get_entity("d_" .. i .. "")
-- local switch = game:get_map():get_entity("sw_" .. i .. "")
-- local npc = game:get_map():get_entity("n_" .. i .. "")
-- local pickable = game:get_map():get_entity("pi_" .. i .. "")
-- local stairs = game:get_map():get_entity("s_" .. i .. "")
-- local boss = game:get_map():get_entity("b_" .. i .. "")
-- local destructible = game:get_map():get_entity("de_" .. i .. "")
-- local sort_door = door:get_type()
-- local sort_switch = switch:get_type()
-- local sort_npc = npc:get_type()
-- local sort_pickable = pickable:get_type()
-- local sort_stairs = stairs:get_type()
-- local sort_destructible = destructible:get_type()
if target ~= nil then
entity_slots[i] = target
local breed = target:get_breed()
local t_x, t_y, t_layer = target:get_position()
local distance_target = math.abs((t_x+t_y)-(self_x+self_y))
local distance_to_hero = math.abs((hero_x+hero_y)-(t_x+t_y))
local distance_hero = math.abs((hero_x+hero_y)-(self_x+self_y))
if distance_target <= target_dt_distance then
target_dt = ("e_" .. i .. "")
print(target_dt)
print("enemy_detected")
elseif distance_to_hero <= target_dth_distance then
target_dth = ("e_" .. i .. "")
print(target_dth)
print("enemy_attacking_hero")
elseif distance_hero <= target_h_distance then
target_dh = ("hero")
print(target_dh)
print("enemy regrouping_with_hero")
end
-- print("B_targeting_finished...")
elseif ally ~= nil then
local sort_ally = ally:get_type()
local model = ally:get_model()
local a_x, a_y, a_layer = ally:get_position()
local distance_ally = math.abs((a_x+a_y)-(self_x+self_y))
local distance_to_hero = math.abs((hero_x+hero_y)-(a_x+a_y))
local distance_hero = math.abs((hero_x+hero_y)-(self_x+self_y))
if distance_ally <= ally_dt_distance then
ally_dt = ("a_" .. i .. "")
print(ally_dt)
-- print("B_ally_targeting_dt")
elseif distance_to_hero >= ally_dth_distance then
ally_dth = ("a_" .. i .. "")
print(ally_dth)
-- print("B_ally_targeting_dth")
elseif distance_hero <= ally_h_distance then
ally_dh = ("hero")
print(ally_dh)
print("ally regrouping_with_hero")
end
elseif rock ~= nil then
local sort_rock = rock:get_type()
local p_x, p_y, p_layer = rock:get_position()
local distance_rock = math.abs((p_x+p_y)-(self_x+self_y))
local distance_hero = math.abs((hero_x+hero_y)-(self_x+self_y))
local distance_to_hero = math.abs((hero_x+hero_y)-(p_x+p_y))
if distance_rock <= rock_dt_distance then
rock_dt = ("p_" .. i .. "")
print(rock_dt)
print("B_rock_targeting_dt")
elseif distance_to_hero >= rock_dt_distance then
rock_dth = ("p_" .. i .. "")
print(rock_dth)
print("B_rock_targeting_dth")
elseif distance_hero <= rock_h_distance then
target_dh = ("hero")
print(rock_h_distance)
print("regrouping_with_hero")
end
-- print("B_rock_targeting_finished...")
end
end
local hero = game:get_map():get_entity("hero")
local hero_x, hero_y, hero_layer = hero:get_position()
local npc_x, npc_y, npc_layer = entity:get_position()
local distance_hero = math.abs((hero_x+hero_y)-(npc_x+npc_y))
local m = sol.movement.create("target")
local direction = entity:get_direction()
local health = properties.life
if map:has_entities("e_") then
if target_dt ~= nil and target_dth == nil and distance_hero < 200 then
local target_in_range = game:get_map():get_entity(target_dt)
if target_in_range ~= nil then
local tir_x, tir_y, tir_layer = target_in_range:get_position()
print("go_enemy")
entity:go_enemy()
end
elseif target_dt ~= nil and target_dth ~= nil and distance_hero < 200 then
local target_d_to_hero = game:get_map():get_entity(target_dth)
if target_d_to_hero ~= nil then
local tdth_x, tdth_y, tdth_layer = target_d_to_hero:get_position()
print("defend_hero")
entity:defend_hero()
end
elseif target_dt ~= nil and target_dth ~= nil and distance_hero > 100 then
print("group_hero")
entity:go_hero()
elseif map:has_entities("a_") and ally_dt ~= nil and ally_dt ~= "a_1" then
local ally_in_range = game:get_map():get_entity(ally_dt)
if ally_in_range ~= nil then
local air_x, air_y, air_layer = ally_in_range:get_position()
print("go_ally")
entity:go_ally()
end
elseif ally_dt ~= nil and ally_dth ~= nil then
local ally_in_range = game:get_map():get_entity(ally_dt)
if ally_in_range ~= nil then
local air_x, air_y, air_layer = ally_in_range:get_position()
print("go_ally")
entity:go_ally()
end
elseif ally_dth ~= nil and target_dth == nil or ally_dth ~= nil and target_dt == nil then
local ally_d_to_hero = game:get_map():get_entity(ally_dth)
if ally_d_to_hero ~= nil then
local adth_x, adth_y, adth_layer = ally_d_to_hero:get_position()
print("go_hero")
entity:go_hero()
end
elseif rock_dt ~= nil then
local rock_in_range = game:get_map():get_entity(rock_dt)
if rock_in_range ~= nil then
local rir_x, rir_y, rir_layer = rock_in_range:get_position()
print("go_dodge")
entity:dodge()
end
elseif rock_dth ~= nil and target_dth ~= nil then
local rock_d_to_hero = game:get_map():get_entity(rock_dth)
if rock_d_to_hero ~= nil then
local rdth_x, rdth_y, rdth_layer = rock_d_to_hero:get_position()
print("go_block")
entity:block()
end
else
print("else__go_hero")
entity:go_hero()
end
else
print("NO ENEMIES...else__go_hero")
entity:go_ally()
print ("sort_ally")
print (sort_ally)
end
return true
end)
end
function entity:go_hero()
print("following hero...")
local hero_x, hero_y, hero_layer = hero:get_position()
local npc_x, npc_y, npc_layer = entity:get_position()
local distance_hero = math.abs((hero_x+hero_y)-(npc_x+npc_y))
if distance_hero > 64 then
m:set_ignore_obstacles(false)
m:set_target(hero_x, hero_y, hero_layer)
m:set_speed(64)
m:start(entity)
entity:get_sprite():set_animation("walking")
else
m:set_ignore_obstacles(false)
m:set_target(hero_x, hero_y, hero_layer)
m:set_speed(0)
m:start(entity)
entity:get_sprite():set_animation("stopped")
end
going_enemy = false
away_hero = false
towards_hero = true
near_hero = false
end
function entity:defend_hero()
if target_dth ~= nil then
local target_in_range = game:get_map():get_entity(target_dth)
local tir_x, tir_y, tir_layer = target_in_range:get_position()
local npc_x, npc_y, npc_layer = entity:get_position()
local distance_enemy = math.abs((tir_x+tir_y)-(npc_x+npc_y))
if distance_enemy > 24 then
--FUNCTION: GO_ENEMY
m:set_ignore_obstacles(false)
m:set_target(tir_x, tir_y, tir_layer)
m:set_speed(128)
m:start(entity)
entity:get_sprite():set_animation("walking")
going_enemy = true
away_hero = false
towards_hero = false
near_hero = false
print("Going enemy")
else
--FUNCTION: ATTACK
weapon_attack = map:create_custom_entity{
name = "w_knife",
x = npc_x,
y = npc_y,
layer = npc_layer,
direction = direction,
sprite = "allies/w_knife",
}
self.created = true
weapon_attack:set_position(npc_x, npc_y, npc_layer)
print("attacking enemy")
-- FUNCTION: WEAPON_POWER
weapon_attack:add_collision_test("sprite", function(weapon_attack, other)
if other:get_type() == "enemy" then
other:hurt(8)
weapon_attack:remove()
else
weapon_attack:remove()
end
end)
print("sword swing...")
--FUNCTION: STOPPED
m:set_ignore_obstacles(false)
m:set_target(tir_x, tir_y)
m:set_speed(0)
m:start(entity)
entity:get_sprite():set_animation("stopped")
going_enemy = false
away_hero = false
towards_hero = false
near_enemy = true
end
end
end
function entity:go_enemy()
if target_dt ~= nil and target_dth == nil then
local target_in_range = game:get_map():get_entity(target_dt)
local tir_x, tir_y, tir_layer = target_in_range:get_position()
local npc_x, npc_y, npc_layer = entity:get_position()
local distance_enemy = math.abs((tir_x+tir_y)-(npc_x+npc_y))
if distance_enemy > 24 then
--FUNCTION: GO_ENEMY
m:set_ignore_obstacles(false)
m:set_target(tir_x, tir_y, tir_layer)
m:set_speed(128)
m:start(entity)
entity:get_sprite():set_animation("walking")
going_enemy = true
away_hero = false
towards_hero = false
near_hero = false
print("Going enemy")
else
--FUNCTION: ATTACK
weapon_attack = map:create_custom_entity{
name = "w_knife",
x = npc_x,
y = npc_y,
layer = npc_layer,
direction = direction,
sprite = "allies/w_knife",
}
self.created = true
weapon_attack:set_position(npc_x, npc_y, npc_layer)
print("attacking enemy")
-- FUNCTION: WEAPON_POWER
weapon_attack:add_collision_test("sprite", function(weapon_attack, other)
if other:get_type() == "enemy" then
other:hurt(8)
weapon_attack:remove()
else
weapon_attack:remove()
end
end)
print("sword swing...")
--FUNCTION: STOPPED
m:set_ignore_obstacles(false)
m:set_target(tir_x, tir_y)
m:set_speed(0)
m:start(entity)
entity:get_sprite():set_animation("stopped")
going_enemy = false
away_hero = false
towards_hero = false
near_enemy = true
end
end
end
function entity:go_ally()
-- Buggy code... I have to differentiate between the allies... by identifying this ally. by getting their hud slot... it is much easier.
-- local air_x, air_y, air_layer = ally_dt:get_position()
local npc_x, npc_y, npc_layer = entity:get_position()
-- local distance_ally = math.abs((air_x+air_y)-(npc_x+npc_y))
-- if map:get_entities_count("a_") > 1 then
-- m:set_ignore_obstacles(false)
-- m:set_target(air_x + 32, air_y + 32, air_layer)
-- m:set_speed(64)
-- m:start(entity)
-- entity:get_sprite():set_animation("walking")
-- going_enemy = false
-- going_ally = true
-- away_hero = false
-- towards_hero = true
-- near_hero = false
-- elseif map:get_entities_count("a_") < 2 then
entity:go_hero()
end
function entity:block()
local rir_x, rir_y, rir_layer = rock_dt:get_position()
local npc_x, npc_y, npc_layer = entity:get_position()
local distance_enemy = math.abs((rir_x+rir_y)-(npc_x+npc_y))
m:set_ignore_obstacles(false)
m:set_target(rir_x, rir_y, rir_layer)
m:set_speed(128)
m:start(entity)
entity:get_sprite():set_animation("walking")
going_enemy = false
going_ally = true
away_hero = false
towards_hero = true
near_hero = false
end
function entity:dodge()
local rdth_x, rdth_y, rdth_layer = rock_dth:get_position()
local npc_x, npc_y, npc_layer = entity:get_position()
local distance_enemy = math.abs((rdth_x+rdth_y)-(npc_x+npc_y))
m:set_ignore_obstacles(false)
m:set_target(rdth_x + 128, rdth_y + 128, rdth_layer)
m:set_speed(128)
m:start(entity)
entity:get_sprite():set_animation("walking")
going_enemy = false
going_ally = true
away_hero = false
towards_hero = true
near_hero = false
end
function entity:hurt()
end
function entity:health()
end
function entity:on_created()
self:set_drawn_in_y_order(true)
self:set_can_traverse("hero", false)
self:set_traversable_by("hero", true)
entity:begin()
end
-- Behavior function ends.
-- PUT INTERACTION SCRIPT INTO A SEPARATE FILE...
end
function entity:on_movement_changed(movement)
local direction = movement:get_direction4()
entity:get_sprite():set_direction(direction)
end
The other part is in the game_manager.lua insert in the
function game:on_map_changed(map)
if game:get_value("a1010") and game:get_value("a1011")
then
ally2 = map:create_custom_entity{
name = "a_2",
x = x,
y = x,
layer = layer,
direction = direction,
sprite = "allies/a_pi",
model = "ally_pi"
}
self.created = true
ally2:set_position(x, y, layer)
local ally2 = true
local allynumber = 2
end
Crude but it is a WIP.
Good luck.
PG