Skip to content

Commit

Permalink
MicroAIs: Refactor code to make it easy for add-ons to add new ones
Browse files Browse the repository at this point in the history
  • Loading branch information
CelticMinstrel committed Mar 27, 2016
1 parent 6ed3406 commit c19ddba
Show file tree
Hide file tree
Showing 12 changed files with 585 additions and 511 deletions.
3 changes: 3 additions & 0 deletions changelog
Expand Up @@ -138,6 +138,9 @@ Version 1.13.4+dev:
The table is read-only and raises an error if you attempt to write to it.
* The way to create Lua candidate actions has changed a little. Old code
will require minor changes.
* New wesnoth.micro_ais table contains the loaders for all Micro AIs.
New loaders can easily be installed by add-ons. See any built-in
micro AI (in ai/micro_ais/mai-defs/) for an example of how to do this.
* Wesnoth formula engine:
* Formulas in unit filters can now access nearly all unit attributes
* New syntax features:
Expand Down
133 changes: 133 additions & 0 deletions data/ai/micro_ais/mai-defs/animals.lua
@@ -0,0 +1,133 @@
local H = wesnoth.require "lua/helper.lua"
local MAIH = wesnoth.require("ai/micro_ais/micro_ai_helper.lua")

function wesnoth.micro_ais.big_animals(cfg)
local required_keys = { "filter"}
local optional_keys = { "avoid_unit", "filter_location", "filter_location_wander" }
local CA_parms = {
ai_id = 'mai_big_animals',
{ ca_id = "move", location = 'ca_big_animals.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end

function wesnoth.micro_ais.wolves(cfg)
local required_keys = { "filter", "filter_second" }
local optional_keys = { "attack_only_prey", "avoid_type" }
local score = cfg.ca_score or 90000
local CA_parms = {
ai_id = 'mai_wolves',
{ ca_id = "move", location = 'ca_wolves_move.lua', score = score },
{ ca_id = "wander", location = 'ca_wolves_wander.lua', score = score - 1 }
}

if cfg.attack_only_prey then
local wolves_aspects = {
{
aspect = "attacks",
facet = {
name = "ai_default_rca::aspect_attacks",
id = "dont_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_enemy", {
{ "and", H.get_child(cfg, "filter_second") }
} }
}
}
}
if (cfg.action == "delete") then
MAIH.delete_aspects(cfg.side, wolves_aspects)
else
MAIH.add_aspects(cfg.side, wolves_aspects)
end
elseif cfg.avoid_type then
local wolves_aspects = {
{
aspect = "attacks",
facet = {
name = "ai_default_rca::aspect_attacks",
id = "dont_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_enemy", {
{ "not", {
type=cfg.avoid_type
} }
} }
}
}
}
if (cfg.action == "delete") then
MAIH.delete_aspects(cfg.side, wolves_aspects)
else
MAIH.add_aspects(cfg.side, wolves_aspects)
end
end
return required_keys, optional_keys, CA_parms
end

function wesnoth.micro_ais.herding(cfg)
local required_keys = { "filter_location", "filter", "filter_second", "herd_x", "herd_y" }
local optional_keys = { "attention_distance", "attack_distance" }
local score = cfg.ca_score or 300000
local CA_parms = {
ai_id = 'mai_herding',
{ ca_id = "attack_close_enemy", location = 'ca_herding_attack_close_enemy.lua', score = score },
{ ca_id = "sheep_runs_enemy", location = 'ca_herding_sheep_runs_enemy.lua', score = score - 1 },
{ ca_id = "sheep_runs_dog", location = 'ca_herding_sheep_runs_dog.lua', score = score - 2 },
{ ca_id = "herd_sheep", location = 'ca_herding_herd_sheep.lua', score = score - 3 },
{ ca_id = "sheep_move", location = 'ca_herding_sheep_move.lua', score = score - 4 },
{ ca_id = "dog_move", location = 'ca_herding_dog_move.lua', score = score - 5 },
{ ca_id = "dog_stopmove", location = 'ca_herding_dog_stopmove.lua', score = score - 6 }
}
return required_keys, optional_keys, CA_parms
end

function wesnoth.micro_ais.forest_animals(cfg)
local optional_keys = { "rabbit_type", "rabbit_number", "rabbit_enemy_distance", "rabbit_hole_img",
"tusker_type", "tusklet_type", "deer_type", "filter_location"
}
local score = cfg.ca_score or 300000
local CA_parms = {
ai_id = 'mai_forest_animals',
{ ca_id = "new_rabbit", location = 'ca_forest_animals_new_rabbit.lua', score = score },
{ ca_id = "tusker_attack", location = 'ca_forest_animals_tusker_attack.lua', score = score - 1 },
{ ca_id = "move", location = 'ca_forest_animals_move.lua', score = score - 2 },
{ ca_id = "tusklet_move", location = 'ca_forest_animals_tusklet_move.lua', score = score - 3 }
}
return {}, optional_keys, CA_parms
end

function wesnoth.micro_ais.swarm(cfg)
local optional_keys = { "scatter_distance", "vision_distance", "enemy_distance" }
local score = cfg.ca_score or 300000
local CA_parms = {
ai_id = 'mai_swarm',
{ ca_id = "scatter", location = 'ca_swarm_scatter.lua', score = score },
{ ca_id = "move", location = 'ca_swarm_move.lua', score = score - 1 }
}
return {}, optional_keys, CA_parms
end

function wesnoth.micro_ais.wolves_multipacks(cfg)
local optional_keys = { "type", "pack_size", "show_pack_number" }
local score = cfg.ca_score or 300000
local CA_parms = {
ai_id = 'mai_wolves_multipacks',
{ ca_id = "attack", location = 'ca_wolves_multipacks_attack.lua', score = score },
{ ca_id = "wander", location = 'ca_wolves_multipacks_wander.lua', score = score - 1 }
}
return {}, optional_keys, CA_parms
end

function wesnoth.micro_ais.hunter(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Hunter [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "home_x", "home_y" }
local optional_keys = { "id", "filter", "filter_location", "rest_turns", "show_messages" }
local CA_parms = {
ai_id = 'mai_hunter',
{ ca_id = "move", location = 'ca_hunter.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end
12 changes: 12 additions & 0 deletions data/ai/micro_ais/mai-defs/bottleneck.lua
@@ -0,0 +1,12 @@

function wesnoth.micro_ais.bottleneck_defense(cfg)
local required_keys = { "x", "y", "enemy_x", "enemy_y" }
local optional_keys = { "healer_x", "healer_y", "leadership_x", "leadership_y", "active_side_leader" }
local score = cfg.ca_score or 300000
local CA_parms = {
ai_id = 'mai_bottleneck',
{ ca_id = 'move', location = 'ca_bottleneck_move.lua', score = score },
{ ca_id = 'attack', location = 'ca_bottleneck_attack.lua', score = score - 1 }
}
return required_keys, optional_keys, CA_parms
end
17 changes: 17 additions & 0 deletions data/ai/micro_ais/mai-defs/escort.lua
@@ -0,0 +1,17 @@
local H = wesnoth.require "lua/helper.lua"

function wesnoth.micro_ais.messenger_escort(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Messenger [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "waypoint_x", "waypoint_y" }
local optional_keys = { "id", "enemy_death_chance", "filter", "filter_second", "invert_order", "messenger_death_chance" }
local score = cfg.ca_score or 300000
local CA_parms = {
ai_id = 'mai_messenger',
{ ca_id = 'attack', location = 'ca_messenger_attack.lua', score = score },
{ ca_id = 'move', location = 'ca_messenger_move.lua', score = score - 1 },
{ ca_id = 'escort_move', location = 'ca_messenger_escort_move.lua', score = score - 2 }
}
return required_keys, optional_keys, CA_parms
end
115 changes: 115 additions & 0 deletions data/ai/micro_ais/mai-defs/fast.lua
@@ -0,0 +1,115 @@
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}

function wesnoth.micro_ais.fast_ai(cfg)
local optional_keys = {
"attack_hidden_enemies", "avoid", "dungeon_mode",
"filter", "filter_second", "include_occupied_attack_hexes",
"leader_additional_threat", "leader_attack_max_units", "leader_weight", "move_cost_factor",
"weak_units_first", "skip_combat_ca", "skip_move_ca", "threatened_leader_fights"
}
local CA_parms = {
ai_id = 'mai_fast',
{ ca_id = 'combat', location = 'ca_fast_combat.lua', score = 100000 },
{ ca_id = 'move', location = 'ca_fast_move.lua', score = 20000 },
{ ca_id = 'combat_leader', location = 'ca_fast_combat_leader.lua', score = 19900 }
}

-- Also need to delete/add some default CAs
if (cfg.action == 'delete') then
-- This can be done independently of whether these were removed earlier
W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="combat",
engine="cpp",
name="ai_default_rca::combat_phase",
max_score=100000,
score=100000
} }
}

W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="villages",
engine="cpp",
name="ai_default_rca::get_villages_phase",
max_score=60000,
score=60000
} }
}

W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="retreat",
engine="cpp",
name="ai_default_rca::retreat_phase",
max_score=40000,
score=40000
} }
}

W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="move_to_targets",
engine="cpp",
name="ai_default_rca::move_to_targets_phase",
max_score=20000,
score=20000
} }
}
else
if (not cfg.skip_combat_ca) then
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[combat]"
}
else
for i,parm in ipairs(CA_parms) do
if (parm.ca_id == 'combat') or (parm.ca_id == 'combat_leader') then
table.remove(CA_parms, i)
end
end
end

if (not cfg.skip_move_ca) then
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[villages]"
}

W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[retreat]"
}

W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[move_to_targets]"
}
else
for i,parm in ipairs(CA_parms) do
if (parm.ca_id == 'move') then
table.remove(CA_parms, i)
break
end
end
end
end
return {}, optional_keys, CA_parms
end
53 changes: 53 additions & 0 deletions data/ai/micro_ais/mai-defs/guardian.lua
@@ -0,0 +1,53 @@
local H = wesnoth.require "lua/helper.lua"

function wesnoth.micro_ais.stationed_guardian(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Stationed Guardian [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "distance", "station_x", "station_y" }
local optional_keys = { "id", "filter", "guard_x", "guard_y" }
local CA_parms = {
ai_id = 'mai_stationed_guardian',
{ ca_id = 'move', location = 'ca_stationed_guardian.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end

function wesnoth.micro_ais.zone_guardian(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Zone Guardian [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "filter_location" }
local optional_keys = { "id", "filter", "filter_location_enemy", "station_x", "station_y" }
local CA_parms = {
ai_id = 'mai_zone_guardian',
{ ca_id = 'move', location = 'ca_zone_guardian.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end

function wesnoth.micro_ais.return_guardian(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Return Guardian [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "return_x", "return_y" }
local optional_keys = { "id", "filter" }
local CA_parms = {
ai_id = 'mai_return_guardian',
{ ca_id = 'move', location = 'ca_return_guardian.lua', score = cfg.ca_score or 100010 }
}
return required_keys, optional_keys, CA_parms
end

function wesnoth.micro_ais.coward(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Coward [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "distance" }
local optional_keys = { "attack_if_trapped", "id", "filter", "filter_second", "seek_x", "seek_y","avoid_x","avoid_y" }
local CA_parms = {
ai_id = 'mai_coward',
{ ca_id = 'move', location = 'ca_coward.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end
17 changes: 17 additions & 0 deletions data/ai/micro_ais/mai-defs/healers.lua
@@ -0,0 +1,17 @@

function wesnoth.micro_ais.healer_support(cfg)
local optional_keys = { "aggression", "injured_units_only", "max_threats", "filter", "filter_second" }
-- Scores for this AI need to be hard-coded, it does not work otherwise
local CA_parms = {
ai_id = 'mai_healer',
{ ca_id = 'initialize', location = 'ca_healer_initialize.lua', score = 999990 },
{ ca_id = 'move', location = 'ca_healer_move.lua', score = 105000 },
}

-- The healers_can_attack CA is only added to the table if aggression ~= 0
-- But: make sure we always try removal
if (cfg.action == 'delete') or (tonumber(cfg.aggression) ~= 0) then
table.insert(CA_parms, { ca_id = 'may_attack', location = 'ca_healer_may_attack.lua', score = 99990 })
end
return {}, optional_keys, CA_parms
end

0 comments on commit c19ddba

Please sign in to comment.