From 323b14db576e9fec6f714f907aec07264a47d57a Mon Sep 17 00:00:00 2001 From: Erik Schilling Date: Sun, 5 May 2013 10:35:36 +0200 Subject: Added a first very basic monster ai version The ai is similar to the old c++ version. Only the target searching is executed every 10 ticks only now to prevent performance issues with too many lua calls. --- example/abilities.xml | 11 +++ example/attributes.xml | 11 +++ example/monsters.xml | 3 + example/scripts/main.lua | 2 + example/scripts/monster/basic_ai.lua | 141 ++++++++++++++++++++++++++++------- example/scripts/monster/settings.lua | 28 +++++++ 6 files changed, 168 insertions(+), 28 deletions(-) create mode 100644 example/scripts/monster/settings.lua (limited to 'example') diff --git a/example/abilities.xml b/example/abilities.xml index 0cbcd23..5b8a380 100644 --- a/example/abilities.xml +++ b/example/abilities.xml @@ -12,4 +12,15 @@ useaction="attack" /> + + + diff --git a/example/attributes.xml b/example/attributes.xml index 425dd96..0ab2626 100644 --- a/example/attributes.xml +++ b/example/attributes.xml @@ -179,4 +179,15 @@ minimum="0" maximum="100" /> + + diff --git a/example/monsters.xml b/example/monsters.xml index 0a4e779..6881c65 100644 --- a/example/monsters.xml +++ b/example/monsters.xml @@ -111,6 +111,9 @@ exp: Tells how much experience point a monster is giving up magical-defence="0" gender="female" /> + + + diff --git a/example/scripts/main.lua b/example/scripts/main.lua index 7093f11..8a4c8af 100644 --- a/example/scripts/main.lua +++ b/example/scripts/main.lua @@ -12,6 +12,8 @@ require "scripts/crafting" require "scripts/attributes" require "scripts/items/candy" + +require "scripts/monster/basic_ai" require "scripts/monster/testmonster" require "scripts/status/jump" diff --git a/example/scripts/monster/basic_ai.lua b/example/scripts/monster/basic_ai.lua index 9c35f42..40fa851 100644 --- a/example/scripts/monster/basic_ai.lua +++ b/example/scripts/monster/basic_ai.lua @@ -7,6 +7,8 @@ local STROLL_TIMEOUT = 20 local STROLL_TIMEOUT_RANDOMNESS = 10 +local TARGET_SEARCH_DELAY = 10 + -- Wrapping the monster update callback in order to do the stroll ai here local old_on_update = MonsterClass.on_update local update_functions = {} @@ -14,18 +16,38 @@ function MonsterClass:on_update(callback) update_functions[self] = callback end -local stroll_timer = {} +local mob_stati = {} local angerlist = {} local mob_config = require "scripts/monster/settings" -local function find_target(mob, config) +local function calculate_position_priority(x1, y1, x2, y2, anger, range) + if math.floor(x1 / TILESIZE) == math.floor(x2 / TILESIZE) and + math.floor(y1 / TILESIZE) == math.floor(y2 / TILESIZE) + then + -- Both on the same tile + return anger * range + end + + local path_length = get_path_length(x1, y1, x2, y2, range, "w") + return (range - path_length) * anger +end + +local function update_attack_ai(mob, tick) + local config = mob_config[mob:name()] + local target local target_priority local attack_x, attack_y + local mob_status = mob_stati[mob] + local timer = mob_status.update_target_timer + if timer and timer > tick then + return false + end + for _, being in ipairs(get_beings_in_circle(mob, config.trackrange)) do - if being:type() == OBJECT_CHARACTER + if being:type() == TYPE_CHARACTER and being:action() ~= ACTION_DEAD then local anger = angerlist[being] or 0 @@ -35,55 +57,98 @@ local function find_target(mob, config) local possible_attack_positions = { { - x = being:x() - config.attack_distance or TILESIZE, - y = being:y() + x = being:x() - config.attack_distance, + y = being:y(), }, { - - x = being:x() - y = being:y() - config.attack_distance or TILESIZE, + x = being:x(), + y = being:y() - config.attack_distance, }, { - - x = being:x() + config.attack_distance or TILESIZE, + x = being:x() + config.attack_distance, y = being:y(), }, { - - x = being:x() - y = being:y() + config.attack_distance or TILESIZE, + x = being:x(), + y = being:y() + config.attack_distance, }, } for _, point in ipairs(possible_attack_positions) do - local priority = calculate_position_priority(mob:position(), point.x, point.y) + local priority = calculate_position_priority(mob:x(), + mob:y(), + point.x, + point.y, + anger, + config.trackrange) + + if not target or priority > target_priority then + target = being + target_priority = priority + attack_x, attack_y = point.x, point.y + end end - - - end end -end -local function stroll_update(mob, tick) - local stroll_tick = stroll_timer[mob] - local mobconfig = mob_config[mob:name()] + mob_status.update_target_timer = tick + TARGET_SEARCH_DELAY + if not target then + return false + end - local trackrange = mobconfig and mobconfig.trackrange or nil + local x, y = mob:position() + if x == attack_x and y == attack_y then + mob:use_ability(config.ability_id, target) + else + mob:walk(attack_x, attack_y) + end + return true +end +local function update_stroll_timer(mob_status, tick) + mob_status.stroll_timer = tick + STROLL_TIMEOUT + + math.random(STROLL_TIMEOUT_RANDOMNESS) +end + +local function update_stroll(mob, tick) + local mobconfig = mob_config[mob:name()] + + local mob_status = mob_stati[mob] local strollrange = mobconfig and mobconfig.strollrange or nil - if (not stroll_tick or stroll_tick <= tick) and strollrange then + if (not mob_status.stroll_timer or mob_status.stroll_timer <= tick) and + strollrange + then local x, y = mob:position() local destination_x = math.random(x - strollrange, x + strollrange) local destination_y = math.random(y - strollrange, y + strollrange) if is_walkable(destination_x, destination_y) then mob:walk(destination_x, destination_y) end - stroll_timer[mob] = tick + STROLL_TIMEOUT - + math.random(STROLL_TIMEOUT_RANDOMNESS) + update_stroll_timer(mob_status, tick) + end +end + +local function remove_mob(mob) + mob_stati[mob] = nil +end + +local function update(mob, tick) + local mob_status = mob_stati[mob] + if not mob_status then + mob_status = {} + mob_stati[mob] = mob_status + on_remove(mob, remove_mob) end + local stop_stroll = update_attack_ai(mob, tick) + if stop_stroll then + update_stroll_timer(mob_status, tick) + else + update_stroll(mob, tick) + end + + -- Call other update functions local monsterclass = get_monster_class(mob:monster_id()) local update_function = update_functions[monsterclass] if update_function then @@ -91,7 +156,27 @@ local function stroll_update(mob, tick) end end --- Register all update functions for strolling -for _, monsterclass in ipairs(get_monster_classes()) do - old_on_update(monsterclass, stroll_update) +local function mob_attack(mob, target, ability_id) + local hp = target:base_attribute(ATTR_HP) + local config = mob_config[mob:name()] + local dealt_damage = math.min(hp, config.damage) + if dealt_damage > 0 then + local v = hp - dealt_damage + target:set_base_attribute(ATTR_HP, hp - dealt_damage) + target:add_hit_taken(dealt_damage) + end +end + +local function mob_recharged(mob, ability_id) + mob_stati[mob].update_target_timer = 0 -- Enforce looking for new target +end + +local mob_attack_ability = + get_ability_info("Monster attack_Basic Monster strike") +mob_attack_ability:on_use(mob_attack) +mob_attack_ability:on_recharged(mob_recharged) + +-- Register all update functions for the ai +for _, monsterclass in pairs(get_monster_classes()) do + old_on_update(monsterclass, update) end diff --git a/example/scripts/monster/settings.lua b/example/scripts/monster/settings.lua new file mode 100644 index 0000000..b095a2d --- /dev/null +++ b/example/scripts/monster/settings.lua @@ -0,0 +1,28 @@ +return { + ["Maggot"] = { + strollrange = TILESIZE, + aggressive = false, + trackrange = 5 * TILESIZE, + attack_distance = TILESIZE, + }, + ["Scorpion"] = { + strollrange = 2 * TILESIZE, + aggressive = false, + trackrange = 5 * TILESIZE, + attack_distance = TILESIZE, + }, + ["Red Scorpion"] = { + strollrange = TILESIZE, + aggressive = true, + trackrange = 5 * TILESIZE, + attack_distance = TILESIZE, + ability_id = 2, + damage = 1, + }, + ["Green Slime"] = { + strollrange = TILESIZE, + aggressive = true, + trackrange = 5 * TILESIZE, + attack_distance = TILESIZE, + }, +} -- cgit