Introduce 18 different Micro AIs.

This includes the [micro_ai] tag, the code for the Micro AI engines
and 14 test scenarios.  Full documentation at
http://wiki.wesnoth.org/Micro_AIs
This commit is contained in:
Matthias Schoeck 2012-12-29 20:05:55 +00:00
parent 528aebd76f
commit b138e47184
35 changed files with 9460 additions and 0 deletions

View file

@ -50,6 +50,7 @@
{ai/scenarios/scenario-test_move_to_targets.cfg}
{ai/scenarios/scenario-lua-ai.cfg}
{ai/scenarios/scenario-no_engine.cfg}
{ai/micro_ais/scenarios/}
#endif
[textdomain]

View file

@ -0,0 +1,48 @@
fai 'lurker_moves.fai'
# run_file('ai/micro_ais/ais/lurker_moves.fai') #
def is_swamp(map, xx, yy)
# Tests whether terrain at xx,yy is swamp_water #
map(
filter( map.terrain, (x=xx) and (y=yy)),
self.id
)[0] = 'swamp_water';
def reachable_swamp(unit_loc,map)
# get all reachable swamp locs for unit at unit_loc #
# exclude own location #
filter(
unit_moves( unit_loc ),
(is_swamp( map, x, y )) and (self != unit_loc)
);
def random(array)
# Picks a random elements of 'array' #
array[(1d size(array) - 1)];
def reachable_enemies_next_to_swamp(unit_loc,attacks,map)
# Returns all the attacks for enemies that the unit at 'unit_loc' #
# can reach and that are next to a swamp hex #
filter(
map( attacks.attacks, self),
(move_from = unit_loc) and (is_swamp( map, attack_from.x, attack_from.y))
);
def weakest_defender(attacks)
# Get the enemy with the lowest HP #
choose( attacks, -unit_at(defender).hitpoints
);
# Attack if possible, otherwise random move #
if( size(possible_attacks) != 0,
weakest_defender(possible_attacks),
if( size(swamp_in_reach) != 0,
move(me.loc,random(swamp_in_reach)),
end)
)
where possible_attacks = reachable_enemies_next_to_swamp( me.loc, my_attacks,map)
where swamp_in_reach = reachable_swamp(me.loc,map)
faiend

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,618 @@
return {
init = function(ai)
local bottleneck_defense = {}
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local LS = wesnoth.require "lua/location_set.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
function bottleneck_defense:is_my_territory(map, enemy_map)
-- Create map that contains 'true' for all hexes that are
-- on the AI's side of the map
-- Get copy of leader to do pathfinding from each hex to the
-- front-line hexes, both own (stored in 'map') and enemy (enemy_map) front-line hexes
-- If there is no leader, use first unit found
local unit = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'yes' }[1]
if (not unit) then unit = wesnoth.get_units { side = wesnoth.current.side }[1] end
local dummy_unit = wesnoth.copy_unit(unit)
local territory_map = LS.create()
local w,h,b = wesnoth.get_map_size()
for x = 1,w do
for y = 1,h do
-- The hex might have been covered already previously
if (not territory_map:get(x,y)) then
dummy_unit.x, dummy_unit.y = x, y
-- Find lowest movement cost to own front-line hexes
local min_cost, best_path = 9e99, {}
map:iter(function(xm, ym, v)
local path, cost = wesnoth.find_path(dummy_unit, xm, ym, { ignore_units = true })
if (cost < min_cost) then
min_cost, best_path = cost, path
end
end)
-- And the same to the enemy front line
local min_cost_enemy, best_path_enemy = 9e99, {}
enemy_map:iter(function(xm, ym, v)
local path, cost = wesnoth.find_path(dummy_unit, xm, ym, { ignore_units = true })
if (cost < min_cost_enemy) then
min_cost_enemy, best_path_enemy = cost, path
end
end)
-- We can set this for the entire path
-- for efficiency reasons (this is pretty slow, esp. on large maps)
if (min_cost < min_cost_enemy) then
for i,p in ipairs(best_path) do
territory_map:insert(p[1], p[2], true)
end
else -- We do need to use 0's in this case though, false won't work
for i,p in ipairs(best_path_enemy) do
territory_map:insert(p[1], p[2], 0)
end
end
end
end
end
-- Now we need to go over it again and delete all the zeros
territory_map:iter(function(x, y, v)
if (territory_map:get(x, y) == 0) then territory_map:remove(x, y) end
end)
return territory_map
end
function bottleneck_defense:triple_from_keys(key_x, key_y, max_value)
-- Turn 'key_x= key_y=' comma-separated lists into a location set
local coords = {}
for x in string.gmatch(key_x, "%d+") do
table.insert(coords, { x })
end
local i = 1
for y in string.gmatch(key_y, "%d+") do
table.insert(coords[i], y)
table.insert(coords[i], max_value + 10 - i * 10) -- the rating
i = i + 1
end
return AH.LS_of_triples(coords)
end
function bottleneck_defense:create_positioning_map(max_value)
-- Create the positioning maps for the healers and leaders, if not given by WML keys
-- Max_value: the rating value for the first hex in the set
-- First, find all locations adjacent to def_map
-- This might include hexes on the line itself, but
-- only store those that are not in enemy territory
local map = LS.create()
self.data.def_map:iter( function(x, y, v)
for xa, ya in H.adjacent_tiles(x, y) do
if self.data.is_my_territory:get(xa, ya) then
-- This rating adds up the scores of all the adjacent def_map hexes
local rating = self.data.def_map:get(x, y) or 0
rating = rating + (map:get(xa, ya) or 0)
map:insert(xa, ya, rating)
end
end
end)
-- We need to sort the map, and assign descending values
local locs = AH.to_triples(map)
table.sort(locs, function(a, b) return a[3] > b[3] end)
for i,l in ipairs(locs) do l[3] = max_value + 10 - i * 10 end
map = AH.LS_of_triples(locs)
-- Finally, we merge the defense map into this, as healers/leaders (by default)
-- can take position on the front line
map:union_merge(self.data.def_map,
function(x, y, v1, v2) return v1 or v2 end
)
return map
end
function bottleneck_defense:get_rating(unit, x, y, has_leadership, is_healer)
-- Calculate rating of a unit at the given coordinates
-- Don't want to extract is_healer and has_leadership inside this function, as it is very slow
-- thus they are provided as parameters from the calling function
local rating = 0
-- Defense positioning rating
-- We exclude healers/leaders here, as we don't necessarily want them on the front line
if (not is_healer) and (not has_leadership) then
rating = self.data.def_map:get(x, y) or 0
end
-- Healer positioning rating
if is_healer then
local healer_rating = self.data.healer_map:get(x, y) or 0
if (healer_rating > rating) then rating = healer_rating end
end
-- Leadership unit positioning rating
if has_leadership then
local leadership_rating = self.data.leadership_map:get(x, y) or 0
-- If leadership unit is injured -> prefer hexes next to healers
if (unit.hitpoints < unit.max_hitpoints) then
for xa, ya in H.adjacent_tiles(x, y) do
local adj = wesnoth.get_unit(xa, ya)
if adj and (adj.__cfg.usage == "healer") then
leadership_rating = leadership_rating + 100
break
end
end
end
if (leadership_rating > rating) then rating = leadership_rating end
end
-- Injured unit positioning
if (unit.hitpoints < unit.max_hitpoints) then
local healing_rating = self.data.healing_map:get(x, y) or 0
if (healing_rating > rating) then rating = healing_rating end
end
-- If this did not produce a positive rating, we add a
-- distance-based rating, to get units to the bottleneck in the first place
if (rating <= 0) and self.data.is_my_territory:get(x, y) then
local combined_dist = 0
self.data.def_map:iter(function(x_def, y_def, v)
combined_dist = combined_dist + H.distance_between(x, y, x_def, y_def)
end)
combined_dist = combined_dist / self.data.def_map:size()
rating = 1000 - combined_dist * 10.
end
-- Now add the unit specific rating
if (rating > 0) then
rating = rating + unit.hitpoints/10. + unit.experience/100.
end
return rating
end
function bottleneck_defense:move_out_of_way(unit)
-- Find the best move out of the way for a unit
-- and choose the shortest possible move
-- Returns nil if no move was found
-- just a sanity check: unit to move out of the way needs to be on our side:
if (unit.side ~= wesnoth.current.side) then return nil end
local reach = wesnoth.find_reach(unit)
-- Find all the occupied hexes, by any unit
-- (too slow if we do this inside the loop for a specific hex)
local all_units = wesnoth.get_units { }
local occ_hexes = LS:create()
for i,u in ipairs(all_units) do
occ_hexes:insert(u.x, u.y)
end
-- find the closest unoccupied reachable hex in the east
local best_reach, best_hex = -1, {}
for i,r in ipairs(reach) do
if self.data.is_my_territory:get(r[1], r[2]) and (not occ_hexes:get(r[1], r[2])) then
-- Best hex to move out of way to:
-- (r[3] > best_reach) : move shorter than previous best move
if (r[3] > best_reach) then
best_reach, best_hex = r[3], { r[1], r[2] }
end
end
end
--print("Best reach: ",unit.id, best_reach, best_hex[1], best_hex[2])
if best_reach > -1 then return best_hex end
end
function bottleneck_defense:bottleneck_move_eval(cfg)
-- Check whether the side leader should be included or not
if cfg.active_side_leader and (not self.data.side_leader_activated) then
local can_still_recruit = false -- enough gold left for another recruit?
local recruit_list = wesnoth.sides[wesnoth.current.side].recruit
for i,recruit_type in ipairs(recruit_list) do
local cost = wesnoth.unit_types[recruit_type].cost
local current_gold = wesnoth.sides[wesnoth.current.side].gold
if (cost <= current_gold) then
can_still_recruit = true
break
end
end
if (not can_still_recruit) then self.data.side_leader_activated = true end
end
-- Now find all units, including the leader or not, depending on situation and settings
local units = {}
if self.data.side_leader_activated then
units = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0'
}
else
units = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'no',
formula = '$this_unit.moves > 0'
}
end
-- If there's no units with moves left, nothing to be done here
if (not units[1]) then return 0 end
-- Set up the arrays that tell the AI where to defend the bottleneck
-- Get the x and y coordinates (this assumes that cfg.x and cfg.y have the same length)
self.data.def_map = self:triple_from_keys(cfg.x, cfg.y, 10000)
--AH.put_labels(self.data.def_map)
--W.message {speaker="narrator", message="Defense map" }
-- Get the territory map, describing which hex is on AI's side of the bottleneck
-- This one is a bit expensive, esp. on large maps -> don't delete every move and reuse
-- However, after a reload, self.data.is_my_territory is an empty string
-- -> need to recalculate in that case also (the reason is that is_my_territory is not a WML table)
if (not self.data.is_my_territory) or (type(self.data.is_my_territory) == 'string') then
local enemy_map = self:triple_from_keys(cfg.enemy_x, cfg.enemy_y, 10000)
self.data.is_my_territory = self:is_my_territory(self.data.def_map, enemy_map)
end
--AH.put_labels(self.data.is_my_territory)
--W.message {speaker="narrator", message="Territory map" }
-- Setting up healer positioning map
if cfg.healer_x and cfg.healer_y then
-- If healer_x,healer_y are given, extract locs from there
self.data.healer_map = self:triple_from_keys(cfg.healer_x, cfg.healer_y, 5000)
else
-- Otherwise create the map here
self.data.healer_map = self:create_positioning_map(5000)
end
-- Use def_map values for any healer hexes that are defined in def_map as well
self.data.healer_map:inter_merge(self.data.def_map,
function(x, y, v1, v2) return v2 or v1 end
)
--AH.put_labels(self.data.healer_map)
--W.message {speaker="narrator", message="Healer map" }
-- Setting up leadership position map
-- If leadership_x, leadership_y are not given, we create the leadership positioning array
if cfg.leadership_x and cfg.leadership_y then
-- If leadership_x,leadership_y are given, extract locs from there
self.data.leadership_map = self:triple_from_keys(cfg.leadership_x, cfg.leadership_y, 4000)
else
-- Otherwise create the map here
self.data.leadership_map = self:create_positioning_map(4000)
end
-- Use def_map values for any leadership hexes that are defined in def_map as well
self.data.leadership_map:inter_merge(self.data.def_map,
function(x, y, v1, v2) return v2 or v1 end
)
--AH.put_labels(self.data.leadership_map)
--W.message {speaker="narrator", message="Leadership map" }
-- healing map: positions next to healers, needs to be calculated each move
-- Healers get moved with higher priority, so don't need to check their MP
local healers = wesnoth.get_units { side = wesnoth.current.side, ability = "healing" }
self.data.healing_map = LS.create()
for i,h in ipairs(healers) do
for x, y in H.adjacent_tiles(h.x, h.y) do
-- Cannot be on the line, and needs to be in own territory
if self.data.is_my_territory:get(x, y) then
local min_dist = 9e99
self.data.def_map:iter( function(xd, yd, vd)
local dist_line = H.distance_between(x, y, xd, yd)
if (dist_line < min_dist) then min_dist = dist_line end
end)
if (min_dist > 0) then
self.data.healing_map:insert(x, y, 3000 + min_dist) -- farther away from enemy is good
end
end
end
end
--AH.put_labels(self.data.healing_map)
--W.message {speaker="narrator", message="Healing map" }
-- Now on to evaluating possible moves:
-- First, get the rating of all units in their current positions
-- A move is only considered if it improves the overall rating,
-- that is, its rating must be higher than:
-- 1. the rating of the unit on the target hex (if there is one)
-- 2. the rating of the currently considered unit on its current hex
local all_units = wesnoth.get_units { side = wesnoth.current.side }
local occ_hexes = LS.create()
for i,u in ipairs(all_units) do
-- Is this a healer or leadership unit?
local is_healer = (u.__cfg.usage == "healer")
local has_leadership = AH.has_ability(u, "leadership")
local rating = self:get_rating(u, u.x, u.y, has_leadership, is_healer)
occ_hexes:insert(u.x, u.y, rating)
-- A unit that cannot move any more, (or at least cannot move out of the way)
-- must be considered to have a very high rating (it's in the best position
-- it can possibly achieve)
local best_move_away = self:move_out_of_way(u)
if (not best_move_away) then occ_hexes:insert(u.x, u.y, 20000) end
end
--AH.put_labels(occ_hexes)
--W.message {speaker="narrator", message="occupied hexes" }
-- Find all attack positions next to enemies
-- This is done up here rather than in the loop, because it's too slow otherwise
local enemies = AH.get_live_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
local attacks = {}
for i,e in ipairs(enemies) do
for x,y in H.adjacent_tiles(e.x, e.y) do
if self.data.is_my_territory:get(x,y) then
local unit_in_way = wesnoth.get_unit(x, y)
local data = { x = x, y = y,
defender = e,
defender_level = wesnoth.unit_types[e.type].level,
unit_in_way = unit_in_way
}
table.insert(attacks, data)
end
end
end
-- Go through all units with moves left
-- Variables to store best unit/move
local max_rating, best_unit, best_hex = 0, {}, {}
for i,u in ipairs(units) do
-- Is this a healer or leadership unit?
local is_healer = (u.__cfg.usage == "healer")
local has_leadership = AH.has_ability(u, "leadership")
local current_rating = self:get_rating(u, u.x, u.y, has_leadership, is_healer)
--print("Finding move for:",u.id, u.x, u.y, current_rating)
-- Find the best move for all hexes the unit can reach ...
local reach = wesnoth.find_reach(u)
for i,r in ipairs(reach) do
local rating = self:get_rating(u, r[1], r[2], has_leadership, is_healer)
--print(" ->",r[1],r[2],rating,occ_hexes:get(r[1], r[2]))
--reach_map:insert(r[1], r[2], rating)
-- A move is only considered if it improves the overall rating,
-- that is, its rating must be higher than:
-- 1. the rating of the unit on the target hex (if there is one)
-- 2. the rating of the currently considered unit on its current hex
if occ_hexes:get(r[1], r[2]) and (occ_hexes:get(r[1], r[2]) >= rating) then rating = 0 end
if (rating <= current_rating) then rating = 0 end
--print(" ->",r[1],r[2],rating,occ_hexes:get(r[1], r[2]))
-- If the target hex is occupied, give it a (very) small penalty
if occ_hexes:get(r[1], r[2]) then rating = rating - 0.001 end
-- Now only valid and possible moves should have a rating > 0
if rating > max_rating then
max_rating, best_unit, best_hex = rating, u, { r[1], r[2] }
end
-- Finally, we check whether a level-up attack is possible from this hex
-- Level-up-attacks will always get a rating greater than any move
-- So if this is the case, they are done first
for j,a in ipairs(attacks) do
-- Only do calc. if there's a theoretical chance for leveling up (speeds things up a lot)
if (a.x == r[1]) and (a.y == r[2]) and (u.max_experience - u.experience <= 8 * a.defender_level) then
--print('Evaluating attack', u.id, a.x, a.y, a.defender.x, a.defender.y)
-- For this one, we really want to go through all weapons individually
-- as the best chosen automatically might not be the best for this specific task
local n_weapon = 0
for weapon in H.child_range(u.__cfg, "attack") do
n_weapon = n_weapon + 1
local att_stats, def_stats = BC.simulate_combat_loc(u, { a.x, a.y }, a.defender, n_weapon)
-- Level-up attack when:
-- 1. max_experience-experience <= target.level and chance to die = 0
-- 2. max_experience-experience <= target.level*8 and chance to die = 0
-- and chance to kill > 66% and remaining av hitpoints > 20
-- #1 is a definite level up, #2 is not, so #1 gets priority
local lu_rating = 0
if (u.max_experience - u.experience <= a.defender_level) then
if (att_stats.hp_chance[0] == 0) then
-- weakest enemy is best (favors stronger weapon)
lu_rating = 15000 - def_stats.average_hp
end
else
if (u.max_experience - u.experience <= 8 * a.defender_level) and (att_stats.hp_chance[0] == 0) and (def_stats.hp_chance[0] >= 0.66) and (att_stats.average_hp >= 20) then
-- strongest attacker and weakest enemy is best
lu_rating = 14000 + att_stats.average_hp - def_stats.average_hp/2.
end
end
-- Very small penalty if there's a unit in the way
-- We also need to check whether this unit can actually move out of the way
if a.unit_in_way then
local moow = self:move_out_of_way(a.unit_in_way)
if moow then
lu_rating = lu_rating - 0.001
else
lu_rating = 0
end
end
--print("Level-up rating:",lu_rating)
if (lu_rating > max_rating) then
max_rating, best_unit, best_hex = lu_rating, u, { r[1], r[2] }
-- The following are also needed in this case
-- We don't have to worry about unsetting them, as LU attacks
-- always have higher priority than any other move
self.data.lu_defender = a.defender
self.data.lu_weapon = n_weapon
end
end
end
end
end
--AH.put_labels(reach_map)
--W.message { speaker = u.id, message = 'My rating map' }
end
--print("Best move:",best_unit.id,max_rating,best_hex[1],best_hex[2])
-- Finally, if there's another unit in the best location,
-- moving it out of the way becomes the best move
-- It has been checked that this is possible
local unit_in_way = wesnoth.get_units { x = best_hex[1], y = best_hex[2],
{ "not", { id = best_unit.id } }
}[1]
if unit_in_way then
best_hex = self:move_out_of_way(unit_in_way)
best_unit = unit_in_way
--print("Moving out of way:", best_unit.id, best_hex[1], best_hex[2])
-- Also need to delete the level-up attack fields
-- They will be reset on the next turn
self.data.lu_defender = nil
self.data.lu_weapon = nil
end
-- Set the variables for the exec() function
if max_rating == 0 then
-- In this case we take MP away from all units
-- This is done so that the RCA AI CAs can be kept in place
self.data.bottleneck_moves_done = true
else
self.data.bottleneck_moves_done = false
self.data.unit = best_unit
self.data.hex = best_hex
end
return 300000
end
function bottleneck_defense:bottleneck_move_exec()
if self.data.bottleneck_moves_done then
local units = {}
if self.data.side_leader_activated then
units = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0'
}
else
units = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'no',
formula = '$this_unit.moves > 0'
}
end
for i,u in ipairs(units) do
ai.stopunit_moves(u)
end
else
--print("Moving unit:",self.data.unit.id, self.data.unit.x, self.data.unit.y, " ->", best_hex[1], best_hex[2], " -- turn:", wesnoth.current.turn)
if (self.data.unit.x ~= self.data.hex[1]) or (self.data.unit.y ~= self.data.hex[2]) then -- test needed for level-up move
ai.move(self.data.unit, self.data.hex[1], self.data.hex[2]) -- don't want full move, as this might be stepping out of the way
end
-- If this is a move for a level-up attack, do the attack also
if self.data.lu_defender then
--print("Level-up attack",self.data.unit.id, self.data.lu_defender.id, self.data.lu_weapon)
--W.message {speaker=self.data.unit.id, message="Level-up attack" }
ai.attack(self.data.unit, self.data.lu_defender, self.data.lu_weapon)
end
end
-- Now delete almost everything
-- Keep: self.data.is_my_territory, self.data.side_leader_activated
self.data.unit, self.data.hex = nil, nil
self.data.lu_defender, self.data.lu_weapon = nil, nil
self.data.bottleneck_moves_done = nil
self.data.def_map, self.data.healer_map, self.data.leadership_map, self.data.healing_map = nil, nil, nil, nil
end
function bottleneck_defense:bottleneck_attack_eval()
-- All units with attacks_left and enemies next to them
-- This will be much easier once the 'attacks' variable is implemented
local attackers = wesnoth.get_units {
side = wesnoth.current.side, formula = "$this_unit.attacks_left > 0",
{ "filter_adjacent", {
{ "filter_side", { { "enemy_of", {side = wesnoth.current.side} } } }
} }
}
--print("\n\nAttackers:",#attackers)
if (not attackers[1]) then return 0 end
-- Now loop through the attackers, and all attacks for each
local max_rating, best_att, best_tar, best_weapon = 0, {}, {}, -1
for i,a in ipairs(attackers) do
local targets = wesnoth.get_units {
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
{ "filter_adjacent", { id = a.id } }
}
--print(" ",a.id,#targets)
for j,t in ipairs(targets) do
local n_weapon = 0
for weapon in H.child_range(a.__cfg, "attack") do
n_weapon = n_weapon + 1
local att_stats, def_stats = wesnoth.simulate_combat(a, n_weapon, t)
local rating = 0
-- This is an acceptable attack if:
-- 1. There is no counter attack
-- 2. Probability of death is >=67% for enemy, 0% for attacker
if (att_stats.hp_chance[a.hitpoints] == 1)
or ((def_stats.hp_chance[0] >= 0.67) and (att_stats.hp_chance[0] == 0))
then
rating = 1000 + t.max_hitpoints + def_stats.hp_chance[0]*100 + att_stats.average_hp - def_stats.average_hp
-- if there's a chance to make the kill, unit closest to leveling up goes first, otherwise the other way around
if (def_stats.hp_chance[0] >= 0.67) then
rating = rating + (a.experience - a.max_experience) / 10.
else
rating = rating - (a.experience - a.max_experience) / 10.
end
end
--print(a.id, t.id,weapon.name, rating)
if rating > max_rating then
max_rating, best_att, best_tar, best_weapon = rating, a, t, n_weapon
end
end
end
end
--print("Best attack:",best_att.id, best_tar.id, max_rating, best_weapon)
if max_rating == 0 then
-- In this case we take attacks away from all units
-- This is done so that the RCA AI CAs can be kept in place
self.data.bottleneck_attacks_done = true
else
self.data.bottleneck_attacks_done = false
self.data.attacker = best_att
self.data.target = best_tar
self.data.weapon = best_weapon
end
return 290000
end
function bottleneck_defense:bottleneck_attack_exec()
if self.data.bottleneck_attacks_done then
local units = wesnoth.get_units { side = wesnoth.current.side, formula = '$this_unit.attacks_left > 0' }
for i,u in ipairs(units) do
ai.stopunit_attacks(u)
end
else
--W.message {speaker=self.data.attacker.id, message="Attacking" }
ai.attack(self.data.attacker, self.data.target, self.data.weapon)
end
self.data.attacker, self.data.target, self.data.weapon = nil, nil, nil
self.data.bottleneck_attacks_done = nil
end
return bottleneck_defense
end
}

View file

@ -0,0 +1,250 @@
return {
init = function(ai)
local guardians = {}
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
----- The coward guardian AI -----
function guardians:coward_eval(cfg)
local unit = wesnoth.get_units{ id = cfg.id }[1]
-- Don't need to check if unit exists as this is a sticky CA
if unit.moves > 0 then
return 300000
else
return 0
end
end
-- cfg parameters: id, distance, seek_x, seek_y, avoid_x, avoid_y
function guardians:coward_exec(cfg)
--print("Coward exec " .. cfg.id)
local unit = wesnoth.get_units{ id = cfg.id }[1]
local reach = wesnoth.find_reach(unit)
-- enemy units within reach
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", {x = unit.x, y = unit.y, radius = cfg.distance} }
}
-- if no enemies are within reach: keep unit from doing anything and exit
if not enemies[1] then
ai.stopunit_all(unit)
return
end
-- Go through all hexes the unit can reach
for i,r in ipairs(reach) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = unit.id } } }[1]
if not occ_hex then
-- Find combined distance weighting of all enemy units within distance
local value = 0
for j,e in ipairs(enemies) do
local d = H.distance_between(r[1], r[2], e.x, e.y)
value = value + 1/ d^2
end
--wesnoth.fire("label", {x=r[1], y=r[2], text = math.floor(value*1000) } )
-- Store this weighting in the third field of each 'reach' element
reach[i][3] = value
else
reach[i][3] = 9999
end
end
-- Sort 'reach' by values, smallest first
table.sort(reach, function(a, b) return a[3] < b[3] end )
-- Select those within factor 2 of the minimum
local best_pos = AH.filter(reach, function(tmp) return tmp[3] < reach[1][3]*2 end)
-- Now take 'seek' and 'avoid' into account
for i,b in ipairs(best_pos) do
-- weighting based on distance from 'seek' and 'avoid'
local ds = AH.generalized_distance(b[1], b[2], cfg.seek_x, cfg.seek_y)
local da = AH.generalized_distance(b[1], b[2], cfg.avoid_x, cfg.avoid_y)
--items.place_image(b[1], b[2], "items/ring-red.png")
local value = 1 / (ds+1) - 1 / (da+1)^2 * 0.75
--wesnoth.fire("label", {x=b[1], y=b[2], text = math.floor(value*1000) } )
best_pos[i][3] = value
end
-- Sort 'best_pos" by value, largest first
table.sort(best_pos, function(a, b) return a[3] > b[3] end)
-- and select all those that have the maximum score
local best_overall = AH.filter(best_pos, function(tmp) return tmp[3] == best_pos[1][3] end)
-- As final step, if there are more than one remaining locations,
-- we take the one with the minimum score in the distance-from_enemy criterion
local min, mx, my = 9999, 0, 0
for i,b in ipairs(best_overall) do
--items.place_image(b[1], b[2], "items/ring-white.png")
local value = 0
for j,e in ipairs(enemies) do
local d = H.distance_between(b[1], b[2], e.x, e.y)
value = value + 1/d^2
end
if value < min then
min = value
mx,my = b[1], b[2]
end
end
--items.place_image(mx, my, "items/ring-gold.png")
-- (mx,my) is the position to move to
if (mx ~= unit.x or my ~= unit.y) then
AH.movefull_stopunit(ai, unit, mx, my)
end
-- Get unit again, just in case it was killed by a moveto event
local unit = wesnoth.get_units{ id = cfg.id }[1]
if unit then ai.stopunit_all(unit) end
end
----- The return guardian AI -----
function guardians:return_guardian_eval(cfg)
local unit = wesnoth.get_units { id = cfg.id }[1]
-- Don't need to check if unit exists as this is a sticky CA
if (unit.x ~= cfg.return_x) or (unit.y ~= cfg.return_y) then
return 100010
else
return 99990
end
end
function guardians:return_guardian_exec(cfg)
local unit = wesnoth.get_units { id = cfg.id }[1]
--print("Exec guardian move",unit.id)
local nh = AH.next_hop(unit, cfg.return_x, cfg.return_y)
if unit.moves~=0 then
AH.movefull_stopunit(ai, unit, nh)
end
end
----- The stationed guardian AI -----
function guardians:stationed_guardian_eval(cfg)
local unit = wesnoth.get_units { id = cfg.id }[1]
-- Don't need to check if unit exists as this is a sticky CA
if (unit.moves > 0) then
return 100010
else
return 0
end
end
-- cfg parameters: id, distance, s_x, s_y, g_x, g_y
function guardians:stationed_guardian_exec(cfg)
-- (s_x,s_y): coordinates where unit is stationed; tries to move here if there is nobody to attack
-- (g_x,g_y): location that the unit guards
local unit = wesnoth.get_units { id = cfg.id }[1]
-- find if there are enemies within 'distance'
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", {x = unit.x, y = unit.y, radius = cfg.distance} }
}
-- if no enemies are within 'distance': keep unit from doing anything and exit
if not enemies[1] then
--print("No enemies close -> sleeping:",unit.id)
ai.stopunit_moves(unit)
return
end
-- Otherwise, unit will either attack or move toward station
--print("Guardian unit waking up",unit.id)
-- enemies must be within 'distance' of guard, (s_x,s_y) *and* (g_x,g_y)
-- simultaneous for guard to attack
local target = {}
local min_dist = 9999
for i,e in ipairs(enemies) do
local ds = H.distance_between(cfg.station_x, cfg.station_y, e.x, e.y)
local dg = H.distance_between(cfg.guard_x, cfg.guard_y, e.x, e.y)
-- If valid target found, save the one with the shortest distance from (g_x,g_y)
if (ds <= cfg.distance) and (dg <= cfg.distance) and (dg < min_dist) then
--print("target:", e.id, ds, dg)
target = e
min_dist = dg
end
end
-- If a valid target was found, unit attacks this target, or moves toward it
if (min_dist ~= 9999) then
--print ("Go for enemy unit:", target.id)
-- Find tiles adjacent to the target, and save the one that our unit
-- can reach with the highest defense rating
local best_defense, attack_loc = -9e99, {}
for x,y in H.adjacent_tiles(target.x, target.y) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=x, y=y, { "not", { id = unit.id } } }[1]
if not occ_hex then
-- defense rating of the hex
local defense = 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y))
--print(x,y,defense)
local nh = AH.next_hop(unit, x, y)
-- if this is best defense rating and unit can reach it, save this location
if (nh[1] == x) and (nh[2] == y) and (defense > best_defense) then
best_defense, attack_loc = defense, {x, y}
end
end
end
-- If a valid hex was found: move there and attack
if (best_defense ~= -9e99) then
--print("Attack at:",attack_loc[1],attack_loc[2],best_defense)
AH.movefull_stopunit(ai, unit, attack_loc)
-- There should be an ai.check_attack_action() here in case something weird is
-- done in a 'moveto' event.
ai.attack(unit, target)
else -- otherwise move toward that enemy
--print("Cannot reach target, moving toward it")
local reach = wesnoth.find_reach(unit)
-- Go through all hexes the unit can reach, find closest to target
local nh = {} -- cannot use next_hop here since target hex is occupied by enemy
local min_dist = 9999
for i,r in ipairs(reach) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = unit.id } } }[1]
if not occ_hex then
local d = H.distance_between(r[1], r[2], target.x, target.y)
if d < min_dist then
min_dist = d
nh = {r[1], r[2]}
end
end
end
-- Finally, execute the move toward the target
AH.movefull_stopunit(ai, unit, nh)
end
-- If no enemy within the target zone, move toward station position
else
--print "Move toward station"
local nh = AH.next_hop(unit, cfg.station_x, cfg.station_y)
AH.movefull_stopunit(ai, unit, nh)
end
-- Get unit again, just in case something was done to it in a 'moveto' or 'attack' event
local unit = wesnoth.get_units{ id = cfg.id }[1]
if unit then ai.stopunit_moves(unit) end
-- If there are attacks left and unit ended up next to an enemy, we'll leave this to RCA AI
end
return guardians
end
}

View file

@ -0,0 +1,225 @@
return {
init = function(ai)
local healer_support = {}
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local LS = wesnoth.require "lua/location_set.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
-----------------------------------------------------------------
----------------- The Healer Support CAs ------------------------
-----------------------------------------------------------------
------ Initialize healer support at beginning of turn -----------
-- Set variables and aspects correctly at the beginning of the turn
-- This will be blacklisted after first execution each turn
function healer_support:initialize_healer_support_eval()
local score = 999990
return score
end
function healer_support:initialize_healer_support_exec()
--print(' Initializing healer_support at beginning of Turn ' .. wesnoth.current.turn)
-- First, modify the attacks aspect to exclude healers
-- Always delete the attacks aspect first, so that we do not end up with 100 copies of the facet
W.modify_ai {
side = wesnoth.current.side,
action = "try_delete",
path = "aspect[attacks].facet[no_healers_attack]"
}
-- Then set the aspect to exclude healers
W.modify_ai {
side = wesnoth.current.side,
action = "add",
path = "aspect[attacks].facet",
{ "facet", {
name = "ai_default_rca::aspect_attacks",
id = "no_healers_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_own", { { "not", { ability = "healing" } } } }
} }
}
-- We also need to set the return score of healer moves to happen _after_ combat at beginning of turn
self.data.HS_return_score = 95000
end
------ Let healers participate in attacks -----------
-- After attacks by all other units are done, reset things so that healers can attack, if desired
-- This will be blacklisted after first execution each turn
function healer_support:healers_can_attack_eval()
local score = 99990
return score
end
function healer_support:healers_can_attack_exec()
--print(' Letting healers participate in attacks from now on')
--local leader = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'yes' }[1]
--W.message { speaker = leader.id, message = "I'm done with the RCA AI combat CA for all other units, letting healers participate now (if they cannot find a support position)." }
-- Delete the attacks aspect
--print("Deleting attacks aspect")
W.modify_ai {
side = wesnoth.current.side,
action = "try_delete",
path = "aspect[attacks].facet[no_healers_attack]"
}
-- We also reset the variable containing the return score of the healers CA
-- This will make it use its default value
self.data.HS_return_score = nil
end
------ Place healers -----------
function healer_support:healer_support_eval(cfg)
-- Should happen with higher priority than attacks, except at beginning of turn,
-- when we want attacks done first
-- This is done so that it is possible for healers to attack, if they do not
-- find an appropriate hex to back up other units
local score = 105000
if self.data.HS_return_score then score = self.data.HS_return_score end
--print('healer_support score:', score)
cfg = cfg or {}
local healers = wesnoth.get_units { side = wesnoth.current.side, ability = "healing",
formula = '$this_unit.moves > 0'
}
if (not healers[1]) then return 0 end
local healers_noMP = wesnoth.get_units { side = wesnoth.current.side, ability = "healing",
formula = '$this_unit.moves = 0'
}
local all_units = wesnoth.get_units{ side = wesnoth.current.side }
local healees, units_MP = {}, {}
for i,u in ipairs(all_units) do
-- Potential healees are units with MP that don't already have a healer (also without MP) next to them
-- Also, they cannot be on a village or regenerate
if (u.moves == 0) and (not wesnoth.match_unit(u, {ability = "regenerates"})) then
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(u.x, u.y)).village
if (not is_village) then
local healee = true
for j,h in ipairs(healers_noMP) do
if (H.distance_between(u.x, u.y, h.x, h.y) == 1) then
--print('Already next to healer:', u.x, u.y, h.x, h.y)
healee = false
break
end
end
if healee then table.insert(healees, u) end
end
else
table.insert(units_MP,u)
end
end
--print('#healees, #units_MP', #healees, #units_MP)
-- Take all units with moves left off the map, for enemy path finding
for i,u in ipairs(units_MP) do wesnoth.extract_unit(u) end
-- Enemy attack map
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
local enemy_attack_map = BC.get_attack_map(enemies)
--AH.put_labels(enemy_attack_map.units)
-- Put units back out there
for i,u in ipairs(units_MP) do wesnoth.put_unit(u.x, u.y, u) end
-- Now find the best healer move
local max_rating, best_hex = -9e99, {}
for i,h in ipairs(healers) do
--local rating_map = LS.create()
local reach = wesnoth.find_reach(h)
for j,r in ipairs(reach) do
local rating, adjacent_healer = 0
-- Only consider hexes that are next to at least one noMP unit that
-- - either can be attacked by an enemy (15 points per enemy)
-- - or has non-perfect HP (1 point per missing HP)
-- Also, hex must be unoccupied by another unit, of course
local unit_in_way = wesnoth.get_unit(r[1], r[2])
if (not unit_in_way) or ((unit_in_way.x == h.x) and (unit_in_way.y == h.y)) then
for k,u in ipairs(healees) do
if (H.distance_between(u.x, u.y, r[1], r[2]) == 1) then
-- !!!!!!! These ratings have to be positive or the method doesn't work !!!!!!!!!
rating = rating + u.max_hitpoints - u.hitpoints
-- If injured_units_only = true then don't count units with full HP
if (u.max_hitpoints - u.hitpoints > 0) or (not cfg.injured_units_only) then
rating = rating + 15 * (enemy_attack_map.units:get(u.x, u.y) or 0)
end
end
end
end
-- Number of enemies that can threaten the healer at that position
-- This has to be no larger than cfg.max_threats for hex to be considered
local enemies_in_reach = enemy_attack_map.units:get(r[1], r[2]) or 0
-- If this hex fulfills those requirements, 'rating' is now greater than 0
-- and we do the rest of the rating, otherwise set rating to below max_rating
if (rating == 0) or (enemies_in_reach > (cfg.max_threats or 9999)) then
rating = max_rating - 1
else
-- Strongly discourage hexes that can be reached by enemies
rating = rating - enemies_in_reach * 1000
-- All else being more or less equal, prefer villages and strong terrain
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(r[1], r[2])).village
if is_village then rating = rating + 2 end
local defense = 100 - wesnoth.unit_defense(h, wesnoth.get_terrain(r[1], r[2]))
rating = rating + defense / 10.
--rating_map:insert(r[1], r[2], rating)
end
if (rating > max_rating) then
max_rating, best_healer, best_hex = rating, h, {r[1], r[2]}
end
end
--AH.put_labels(rating_map)
--W.message { speaker = h.id, message = 'Healer rating map for me' }
end
--print('best unit move', best_hex[1], best_hex[2], max_rating)
-- Only move healer if a good move as found
-- Be aware that this means that other CAs will move the healers if not
if (max_rating > -9e99) then
self.data.HS_unit, self.data.HS_hex = best_healer, best_hex
return score
end
return 0
end
function healer_support:healer_support_exec()
-- Only show this message in the healer_support scenario in AI-Demos
local scenario = wesnoth.get_variable("scenario_name")
if (scenario == 'healer_support') then
W.message { speaker = self.data.HS_unit.id, message = 'Moving in to back injured and/or threatened units' }
end
AH.movefull_outofway_stopunit(ai, self.data.HS_unit, self.data.HS_hex)
self.data.HS_unit, self.data.HS_hex = nil, nil
end
return healer_support
end
}

View file

@ -0,0 +1,107 @@
return {
init = function(ai)
local lurkers = {}
local LS = wesnoth.require "lua/location_set.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
function lurkers:lurker_attack_eval(cfg)
-- If any lurker has moves left, we return score just above standard combat CA
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.lurkers }, formula = '$this_unit.moves > 0'
}
local eval = 0
if units[1] then eval = 100010 end
--print("Lurker eval: ",eval)
return eval
end
function lurkers:lurker_attack_exec(cfg)
-- We simply pick the first of the lurkers, they have no strategy
local me = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.lurkers }, formula = '$this_unit.moves > 0'
}[1]
--print("me at:" .. me.x .. "," .. me.y)
-- Potential targets
local targets = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
-- sort targets by hitpoints (lurkers choose lowest HP target)
table.sort(targets, function (a,b) return (a.hitpoints < b.hitpoints) end)
--print("Number of potential targets:", #targets)
-- all reachable hexes
local reach = LS.of_pairs( wesnoth.find_reach(me.x, me.y) )
-- all reachable attack hexes
local reachable_attack_terrain =
LS.of_pairs( wesnoth.get_locations {
{"and", {x = me.x, y = me.y, radius = me.moves} },
{"and", cfg.attack_terrain}
} )
reachable_attack_terrain:inter(reach)
--print(" reach: " .. reach:size() .. " reach_attack: " .. reachable_attack_terrain:size())
-- need to restrict that to reachable and not occupied by an ally (except own position)
local reachable_attack_terrain = reachable_attack_terrain:filter(function(x, y, v)
local occ_hex = wesnoth.get_units { x = x, y = y, { "not", { x = me.x, y = me.y } } }[1]
return not occ_hex
end)
--print(" reach: " .. reach:size() .. " reach_attack no allies: " .. reachable_attack_terrain:size())
-- Attack the weakest reachable enemy
local attacked = false -- Need this, because unit might die in attack
for j, target in ipairs(targets) do
-- Get reachable attack terrain next to target unit
local rattack_nt_target = LS.of_pairs(wesnoth.get_locations { x=target.x, y=target.y, radius=1 } )
rattack_nt_target:inter(reachable_attack_terrain)
--print(" targets: " .. target.x .. "," .. target.y .. " adjacent attack terrain: " .. rattack_nt_target:size())
-- if we found a reachable enemy, attack it
-- since they are sorted by hitpoints, we can simply attack the first enemy found and break the loop
if rattack_nt_target:size() > 0 then
-- Choose one of the possible attack locations at random
local rand = AH.random(1, rattack_nt_target:size())
local dst = rattack_nt_target:to_stable_pairs()
AH.movefull_stopunit(ai, me, dst[rand])
ai.attack(dst[rand][1], dst[rand][2], target.x, target.y)
attacked = true
break
end
end
-- If unit has moves left (that is, it didn't attack), go to random wander terrain hex
-- Check first that unit wasn't killed in the attack
if (not attacked) and (not cfg.stationary) then
local reachable_wander_terrain =
LS.of_pairs( wesnoth.get_locations {
{"and", {x = me.x, y = me.y, radius = me.moves} },
{"and", (cfg.wander_terrain or cfg.attack_terrain)}
} )
reachable_wander_terrain:inter(reach)
-- get one of the reachable wander terrain hexes randomly
local rand = AH.random(1, reachable_wander_terrain:size())
--print(" reach_wander no allies: " .. reachable_wander_terrain:size() .. " rand #: " .. rand)
local dst = reachable_wander_terrain:to_stable_pairs()
if dst[1] then
dst = dst[rand]
else
dst = { me.x, me.y }
end
AH.movefull_stopunit(ai, me, dst)
end
-- If the unit has moves or attacks left at this point, take them away
if me and me.valid then ai.stopunit_all(me) end
end
return lurkers
end
}

View file

@ -0,0 +1,365 @@
return {
init = function(ai)
local messenger_escort = {}
-- Moves a messenger toward goal coordinates while protecting him and
-- clearing his way with other units, if necessary
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local LS = wesnoth.require "lua/location_set.lua"
function messenger_escort:find_enemies_in_way(unit, goal_x, goal_y)
-- Returns the first unit on or next to the path of the messenger
-- unit: proxy table for the messenger unit
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
-- Returns proxy table for the first unit found, or nil if none was found
local path, cost = wesnoth.find_path(unit, goal_x, goal_y, { ignore_units = true })
-- If unit cannot get there:
if cost >= 42424242 then return end
-- Exclude the hex the unit is currently on
table.remove(path, 1)
-- Is there an enemy unit on the first path hex itself?
-- This would be caught by the adjacent hex check later, but not in the right order
local enemy = wesnoth.get_units { x = path[1][1], y = path[1][2],
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } }
}[1]
if enemy then
--print(' enemy on first path hex:',enemy.id)
return enemy
end
-- After that, go through adjacent hexes of all the other path hexes
for i, p in ipairs(path) do
local sub_path, sub_cost = wesnoth.find_path( unit, p[1], p[2], { ignore_units = true })
if sub_cost <= unit.moves then
-- Check for enemy units on one of the adjacent hexes (which includes 2 hexes on path)
for x, y in H.adjacent_tiles(p[1], p[2]) do
local enemy = wesnoth.get_units { x = x, y = y,
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } }
}[1]
if enemy then
--print(' enemy next to path hex:',enemy.id)
return enemy
end
end
else -- If we've reached the end of the path for this turn
return
end
end
-- If no unit was found, return nil
return
end
function messenger_escort:find_clearing_attack(unit, goal_x, goal_y)
-- Check if an enemy is in the way of the messenger
-- If so, find attack that would "clear" that enemy out of the way
-- unit: proxy table for the messenger unit
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
-- Returns proxy table containing the attack, or nil if none was found
local enemy_in_way = self:find_enemies_in_way(unit, goal_x, goal_y)
-- If none found, don't attack, just move
if not enemy_in_way then return end
local max_rating, best_attack = -9e99, {}
--print('Finding attacks on',enemy_in_way.name,enemy_in_way.id)
-- Find all units that can attack this enemy
local my_units = wesnoth.get_units{ side = wesnoth.current.side, formula = '$this_unit.attacks_left > 0',
{ "not", { id = unit.id } }
}
--print('#my_units', #my_units)
if (not my_units[1]) then return end
local my_attacks = AH.get_attacks(my_units, { simulate_combat = true })
for i, att in ipairs(my_attacks) do
if (att.target.x == enemy_in_way.x) and (att.target.y == enemy_in_way.y) then
-- Rating: expected HP of attacker and defender
local rating = att.att_stats.average_hp - 2 * att.def_stats.average_hp
--print(' rating:', rating)
if (rating > max_rating) then
max_rating = rating
best_attack = att
end
end
end
-- If attack on this enemy_in_way is possible, return it
if (max_rating > -9e99) then return best_attack end
-- If we got here, that means there's an enemy in the way, but none of the units can reach it
--> try to fight our way to that enemy
--print('Find different attack to get to enemy in way')
for i, att in ipairs(my_attacks) do
-- Rating: expected HP of attacker and defender
local rating = att.att_stats.average_hp - 2 * att.def_stats.average_hp
-- plus, give a huge bonus for closeness to enemy_in_way
local tmp_defender = wesnoth.get_unit(att.target.x, att.target.y)
local dist = H.distance_between(enemy_in_way.x, enemy_in_way.y, tmp_defender.x, tmp_defender.y)
--print(' distance:',enemy_in_way.id, tmp_defender.id, dist)
rating = rating + 100. / dist
--print(' rating:', rating)
if (rating > max_rating) then
max_rating = rating
best_attack = att
end
end
if (max_rating > -9e99) then
return best_attack
else
return
end
end
-----------------------
function messenger_escort:attack_eval(cfg)
-- Attack units in the path of the messenger
-- id: id of the messenger unit
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
local messenger = wesnoth.get_units{ side = wesnoth.current.side, id = cfg.id }[1]
if (not messenger) then return 0 end
-- Set up the waypoints
cfg.waypoint_x = AH.split(cfg.waypoint_x, ",")
cfg.waypoint_y = AH.split(cfg.waypoint_y, ",")
local waypoints = {}
for i = 1,#cfg.waypoint_x do
waypoints[i] = { tonumber(cfg.waypoint_x[i]), tonumber(cfg.waypoint_y[i]) }
end
-- Variable to store which waypoint to go to next (persistent)
if (not self.data.next_waypoint) then self.data.next_waypoint = 1 end
-- If we're within 3 hexes of the next waypoint, we go on to the one after that
-- except if that one's the last one already
local dist_wp = H.distance_between(messenger.x, messenger.y,
waypoints[self.data.next_waypoint][1], waypoints[self.data.next_waypoint][2]
)
if (dist_wp <= 3) and (self.data.next_waypoint < #waypoints) then
self.data.next_waypoint = self.data.next_waypoint + 1
end
-- See if there's an enemy in the way that should be attacked
local attack = self:find_clearing_attack(messenger,
waypoints[self.data.next_waypoint][1], waypoints[self.data.next_waypoint][2]
)
if attack then
self.data.best_attack = attack
return 300000
end
return 0
end
function messenger_escort:attack_exec()
local attacker = wesnoth.get_unit(self.data.best_attack.src.x, self.data.best_attack.src.y)
local defender = wesnoth.get_unit(self.data.best_attack.target.x, self.data.best_attack.target.y)
AH.movefull_stopunit(ai, attacker, self.data.best_attack.dst.x, self.data.best_attack.dst.y)
ai.attack(attacker, defender)
self.data.best_attack = nil
end
-----------------------
function messenger_escort:messenger_move_eval(cfg)
-- Move the messenger (unit with passed id) toward goal, attack adjacent unit if possible
-- without retaliation or little expected damage with high chance of killing the enemy
local messenger = wesnoth.get_units{ id = cfg.id, formula = '$this_unit.moves > 0' }[1]
if messenger then return 290000 end
return 0
end
function messenger_escort:messenger_move_exec(cfg)
local messenger = wesnoth.get_units{ id = cfg.id, formula = '$this_unit.moves > 0' }[1]
-- Set up the waypoints
cfg.waypoint_x = AH.split(cfg.waypoint_x, ",")
cfg.waypoint_y = AH.split(cfg.waypoint_y, ",")
local waypoints = {}
for i = 1,#cfg.waypoint_x do
waypoints[i] = { tonumber(cfg.waypoint_x[i]), tonumber(cfg.waypoint_y[i]) }
end
-- Variable to store which waypoint to go to next (persistent)
if (not self.data.next_waypoint) then self.data.next_waypoint = 1 end
-- If we're within 3 hexes of the next waypoint, we go on to the one after that
-- except if that one's the last one already
local dist_wp = H.distance_between(messenger.x, messenger.y,
waypoints[self.data.next_waypoint][1], waypoints[self.data.next_waypoint][2]
)
if (dist_wp <= 3) and (self.data.next_waypoint < #waypoints) then
self.data.next_waypoint = self.data.next_waypoint + 1
end
-- In case an enemy is on the waypoint hex:
local x, y = wesnoth.find_vacant_tile(
waypoints[self.data.next_waypoint][1], waypoints[self.data.next_waypoint][2], messenger
)
local next_hop = AH.next_hop(messenger, x, y)
-- Compare this to the "ideal path"
local path, cost = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })
local opt_hop, opt_cost = {messenger.x, messenger.y}, 0
for i, p in ipairs(path) do
local sub_path, sub_cost = wesnoth.find_path(messenger, p[1], p[2])
if sub_cost > messenger.moves then
break
else
local unit_in_way = wesnoth.get_unit(p[1], p[2])
if not unit_in_way then
opt_hop, nh_cost = p, sub_cost
end
end
end
--print(next_hop[1], next_hop[2], opt_hop[1], opt_hop[2])
-- Now compare how long it would take from the end of both of these options
local x1, y1 = messenger.x, messenger.y
wesnoth.put_unit(next_hop[1], next_hop[2], messenger)
local tmp, cost1 = wesnoth.find_path(messenger, x, y, {ignore_units = 'yes'})
wesnoth.put_unit(opt_hop[1], opt_hop[2], messenger)
local tmp, cost2 = wesnoth.find_path(messenger, x, y, {ignore_units = 'yes'})
wesnoth.put_unit(x1, y1, messenger)
--print(cost1, cost2)
-- If cost2 is significantly less, that means that the other path might overall be faster
-- even though it is currently blocked
if (cost2 + 4 < cost1) then next_hop = opt_hop end
--print(next_hop[1], next_hop[2])
if next_hop and ((next_hop[1] ~= messenger.x) or (next_hop[2] ~= messenger.y)) then
ai.move(messenger, next_hop[1], next_hop[2])
else
ai.stopunit_moves(messenger)
end
-- We also test whether an attack without retaliation or with little damage is possible
local targets = wesnoth.get_units {
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
{ "filter_adjacent", { id = cfg.id } }
}
local max_rating, best_tar, best_weapon = -9e99, {}, -1
for i,t in ipairs(targets) do
local n_weapon = 0
for weapon in H.child_range(messenger.__cfg, "attack") do
n_weapon = n_weapon + 1
local att_stats, def_stats = wesnoth.simulate_combat(messenger, n_weapon, t)
local rating = -9e99
-- This is an acceptable attack if:
-- 1. There is no counter attack
-- 2. Probability of death is >=67% for enemy, 0% for attacker (default values)
local enemy_death_chance = cfg.enemy_death_chance or 0.67
local messenger_death_chance = cfg.messenger_death_chance or 0
if (att_stats.hp_chance[messenger.hitpoints] == 1)
or (def_stats.hp_chance[0] >= tonumber(enemy_death_chance)) and (att_stats.hp_chance[0] <= tonumber(messenger_death_chance))
then
rating = t.max_hitpoints + def_stats.hp_chance[0]*100 + att_stats.average_hp - def_stats.average_hp
end
--print(messenger.id, t.id,weapon.name, rating)
if rating > max_rating then
max_rating, best_tar, best_weapon = rating, t, n_weapon
end
end
end
local target = wesnoth.get_units {
x = cfg.waypoint_x[#cfg.waypoint_x],
y = cfg.waypoint_y[#cfg.waypoint_y],
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
{ "filter_adjacent", { id = cfg.id } }
}[1]
if max_rating > -9e99 then
ai.attack(messenger, best_tar, best_weapon)
elseif target then
ai.attack(messenger, target)
end
-- Finally, make sure unit is really done after this
ai.stopunit_attacks(messenger)
end
-----------------------
function messenger_escort:other_move_eval(cfg)
-- Move other units close to messenger, and in between messenger and enemies
-- The messenger has moved at this time, so we don't need to exclude him
-- But we check that he exist (not for this scenario, but for others)
local messenger = wesnoth.get_units{ side = wesnoth.current.side, id = cfg.id }[1]
if (not messenger) then return 0 end
local my_units = wesnoth.get_units{ side = wesnoth.current.side, formula = '$this_unit.moves > 0' }
if my_units[1] then return 280000 end
return 0
end
function messenger_escort:other_move_exec(cfg)
local messenger = wesnoth.get_units{ id = cfg.id }[1]
local my_units = wesnoth.get_units{ side = wesnoth.current.side, formula = '$this_unit.moves > 0' }
-- Simply move units one at a time
local next_unit = my_units[1]
local reach = LS.of_pairs(wesnoth.find_reach(next_unit))
-- Distance from messenger for each hex the unit can reach
local dist_messenger = AH.distance_map({messenger}, reach)
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
-- Rating (in the end, we pick the _minimum _rating):
-- 1. Minimize distance from enemies
local rating = AH.distance_map(enemies, reach)
-- 2. This one favors hexes in between messenger and enemies
rating:union_merge(dist_messenger, function(x, y, v1, v2)
return v1 + v2*#enemies
end)
-- 3. Strongly prefer hexes close to the messenger
rating:union_merge(dist_messenger, function(x, y, v1, v2)
return v1 + v2^2
end)
--AH.put_labels(rating)
-- Now find hex with minimum value that is unoccupied
min_rating, best_hex = 9e99, {}
rating:iter(function(x, y, r)
local unit_in_way = wesnoth.get_units{ x = x, y = y, { "not", { id = next_unit.id } } }[1]
if (not unit_in_way) and (r < min_rating) then
min_rating, best_hex = r, { x, y }
end
end)
-- and move the unit there
AH.movefull_stopunit(ai, next_unit, best_hex)
end
return messenger_escort
end
}

View file

@ -0,0 +1,146 @@
return {
init = function(ai)
local patrol = {}
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
function patrol:patrol_eval(cfg)
local patrol = wesnoth.get_units({ id = cfg.id })[1]
-- Don't need to check if unit exists as this is a sticky CA
if (patrol.moves > 0) then return 300000 end
return 0
end
function patrol:patrol_exec(cfg)
local patrol = wesnoth.get_units( { id = cfg.id } )[1]
cfg.waypoint_x = AH.split(cfg.waypoint_x, ",")
cfg.waypoint_y = AH.split(cfg.waypoint_y, ",")
local n_wp = #cfg.waypoint_x -- just for convenience
-- Set up waypoints, taking into account whether 'reverse' is set
-- This works even the first time, when self.data.id_reverse is not set yet
local waypoints = {}
if self.data[patrol.id..'_reverse'] then
for i = 1,n_wp do
waypoints[i] = { tonumber(cfg.waypoint_x[n_wp-i+1]), tonumber(cfg.waypoint_y[n_wp-i+1]) }
end
else
for i = 1,n_wp do
waypoints[i] = { tonumber(cfg.waypoint_x[i]), tonumber(cfg.waypoint_y[i]) }
end
end
-- if not set, set next location (first move)
-- This needs to be in WML format, so that it persists over save/load cycles
if (not self.data[patrol.id..'_x']) then
self.data[patrol.id..'_x'] = waypoints[1][1]
self.data[patrol.id..'_y'] = waypoints[1][2]
self.data[patrol.id..'_reverse'] = false
end
while patrol.moves > 0 do
-- Check whether one of the enemies to be attacked is next to the patroller
-- If so, don't move, but attack that enemy
local enemies = wesnoth.get_units {
id = cfg.attack,
{ "filter_adjacent", { id = cfg.id } },
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
}
if next(enemies) then break end
-- Also check whether we're next to any unit (enemy or ally) which is on the next waypoint
local unit_on_wp = wesnoth.get_units {
x = self.data[patrol.id..'_x'],
y = self.data[patrol.id..'_y'],
{ "filter_adjacent", { id = cfg.id } }
}[1]
for i,wp in ipairs(waypoints) do
-- If the patrol is on a waypoint or adjacent to one that is occupied by any unit
if ((patrol.x == wp[1]) and (patrol.y == wp[2]))
or (unit_on_wp and ((unit_on_wp.x == wp[1]) and (unit_on_wp.y == wp[2])))
then
if (i == n_wp) then
-- Move him to the first one (or reverse route), if he's on the last waypoint
-- Unless cfg.one_time_only is set
if cfg.one_time_only then
self.data[patrol.id..'_x'] = waypoints[n_wp][1]
self.data[patrol.id..'_y'] = waypoints[n_wp][2]
else
-- Go back to first WP or reverse direction
if cfg.out_and_back then
self.data[patrol.id..'_x'] = waypoints[n_wp-1][1]
self.data[patrol.id..'_y'] = waypoints[n_wp-1][2]
-- We also need to reverse the waypoints right here, as this might not be the end of the move
self.data[patrol.id..'_reverse'] = not self.data[patrol.id..'_reverse']
local tmp_wp = {}
for i,wp in ipairs(waypoints) do tmp_wp[n_wp-i+1] = wp end
waypoints = tmp_wp
else
self.data[patrol.id..'_x'] = waypoints[1][1]
self.data[patrol.id..'_y'] = waypoints[1][2]
end
end
else
-- ... else move him on the next waypoint
self.data[patrol.id..'_x'] = waypoints[i+1][1]
self.data[patrol.id..'_y'] = waypoints[i+1][2]
end
end
end
-- If we're on the last waypoint on one_time_only is set, stop here
if cfg.one_time_only and
(patrol.x == waypoints[n_wp][1]) and (patrol.y == waypoints[n_wp][2])
then
ai.stopunit_moves(patrol)
else -- otherwise move toward next WP
local x, y = wesnoth.find_vacant_tile(self.data[patrol.id..'_x'], self.data[patrol.id..'_y'], patrol)
local nh = AH.next_hop(patrol, x, y)
if nh and ((nh[1] ~= patrol.x) or (nh[2] ~= patrol.y)) then
ai.move(patrol, nh[1], nh[2])
else
ai.stopunit_moves(patrol)
end
end
end
-- Attack unit on the last waypoint under all circumstances if cfg.one_time_only is set
local enemies = {}
if cfg.one_time_only then
enemies = wesnoth.get_units{
x = waypoints[n_wp][1],
y = waypoints[n_wp][2],
{ "filter_adjacent", { id = cfg.id } },
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
}
end
-- Otherwise attack adjacent enemy (if specified)
if (not next(enemies)) then
enemies = wesnoth.get_units{
id = cfg.attack,
{ "filter_adjacent", { id = cfg.id } },
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
}
end
if next(enemies) then
for i,v in ipairs(enemies) do
ai.attack(patrol, v)
break
end
end
-- Check that patrol is not killed
if patrol and patrol.valid then ai.stopunit_all(patrol) end
end
return patrol
end
}

View file

@ -0,0 +1,434 @@
return {
init = function(ai)
local protect_unit = {}
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local LS = wesnoth.require "lua/location_set.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
----- The parameter selection dialog ------------
-- We are not using this here, but are keeping the code as a demonstration
-- how to set up an interactive parameter selection dialog
local T = H.set_wml_tag_metatable {}
local _ = wesnoth.textdomain "wesnoth"
local tooltip_enemy_weight = _"Enemy unit weight: The (negative) weight given to each enemy unit that can reach a potential target location. Default: 100"
local tooltip_my_unit_weight = _"My unit weight: The (positive) weight given to each of the AI's units that can reach a potential target location. Default: 1"
local tooltip_distance_weight = _"Goal distance weight: The (negative) weight for each step the unit is away from its goal location.\nDefault: 3 -- thus, by default, being a step closer to the goal is as important as being in reach of 3 of AI's units."
local tooltip_terrain_weight = _"Terrain defense weight: The (positive) weight of the terrain defense rating for a potential target location.\nDefault: 0.1 -- thus, by default, a difference of 30 in terrain defense rating is as important as being a step closer to the goal."
local tooltip_bearing = _"Bearing: Everything else being equal, move protected unit toward or away from enemy groups. Default: toward"
protect_unit.dialog = {
T.tooltip { id = "tooltip_large" },
T.helptip { id = "tooltip_large" },
T.grid {
T.row {
T.column { horizontal_alignment = "left", border = "all", border_size = 5,
T.label { definition = "title", label = _"Set AI parameters" }
}
},
T.row {
T.column { horizontal_alignment = "left", border = "all", border_size = 5,
T.label { label = _"Click on 'Close' without changing anything to use defaults.\nAll weights must be 0 or larger. Invalid inputs are converted to default values.\nTooltips enabled for all parameters." }
}
},
T.row { T.column { T.grid {
T.row {
T.column { horizontal_alignment = "right", border_size = 5, border = "all",
T.label { label = _"Enemy unit weight (default: 100)", tooltip = tooltip_enemy_weight } },
T.column { horizontal_alignment = "left", border_size = 5, border = "all",
T.text_box {id = "enemy_weight", tooltip = tooltip_enemy_weight }
}
},
T.row {
T.column { horizontal_alignment = "right", border_size = 5, border = "all",
T.label { label = _"My unit weight (default: 1)", tooltip = tooltip_my_unit_weight } },
T.column { horizontal_alignment = "left", border_size = 5, border = "all",
T.text_box {id = "my_unit_weight", tooltip = tooltip_my_unit_weight }
}
},
T.row {
T.column { horizontal_alignment = "right", border_size = 5, border = "all",
T.label { label = _"Goal distance weight (default: 3)", tooltip = tooltip_distance_weight } },
T.column { horizontal_alignment = "left", border_size = 5, border = "all",
T.text_box {id = "distance_weight", tooltip = tooltip_distance_weight }
}
},
T.row {
T.column { horizontal_alignment = "right", border_size = 5, border = "all",
T.label { label = _"Terrain defense weight (default: 0.1)", tooltip = tooltip_terrain_weight } },
T.column { horizontal_alignment = "left", border_size = 5, border = "all",
T.text_box {id = "terrain_weight", tooltip = tooltip_terrain_weight }
}
},
T.row {
T.column { horizontal_alignment = "right", border_size = 5, border = "all",
T.label { label = "Bearing (default: toward)", tooltip = tooltip_bearing }
},
T.column { horizontal_alignment = "left",
T.horizontal_listbox { id = "bearing",
T.list_definition { T.row { T.column { border_size = 5, border = "all",
T.toggle_button { id = "direction", tooltip = tooltip_bearing }
} } },
T.list_data {
T.row { horizontal_alignment = "left", T.column { label = "toward enemy" } },
T.row { horizontal_alignment = "left", T.column { label = "away from enemy" } }
}
}
}
}
} } }
},
click_dismiss = true
}
function protect_unit.preshow()
wesnoth.set_dialog_value(protect_unit.data.enemy_weight or 100., "enemy_weight")
wesnoth.set_dialog_value(protect_unit.data.my_unit_weight or 1., "my_unit_weight")
wesnoth.set_dialog_value(protect_unit.data.distance_weight or 3., "distance_weight")
wesnoth.set_dialog_value(protect_unit.data.my_unit_weight or 0.1, "terrain_weight")
local tmp_bear = protect_unit.data.bearing or 1
if (tmp_bear ~= 1) then tmp_bear = 2 end -- -1 in code, but Option #2 in widget
wesnoth.set_dialog_value (tmp_bear, "bearing")
end
function protect_unit.postshow()
local tmp = tonumber(wesnoth.get_dialog_value("enemy_weight")) or -1
if (tmp < 0) then tmp = 100 end
protect_unit.data.enemy_weight = tmp
local tmp = tonumber(wesnoth.get_dialog_value("my_unit_weight")) or -1
if (tmp < 0) then tmp = 1 end
protect_unit.data.my_unit_weight = tmp
local tmp = tonumber(wesnoth.get_dialog_value("distance_weight")) or -1
if (tmp < 0) then tmp = 3 end
protect_unit.data.distance_weight = tmp
local tmp = tonumber(wesnoth.get_dialog_value("terrain_weight")) or -1
if (tmp < 0) then tmp = 0.1 end
protect_unit.data.terrain_weight = tmp
local tmp = tonumber(wesnoth.get_dialog_value("bearing")) or 1
if (tmp ~= 1) then tmp = -1 end -- -1 in code, but Option #2 in widget
protect_unit.data.bearing = tmp
end
--------- The actual AI functions -----------
function protect_unit:finish_eval(cfg)
-- If a unit can make it to the goal, this is the first thing that happens
for i,id in ipairs(cfg.id) do
local unit = wesnoth.get_units{ id = id, formula = '$this_unit.moves > 0' }[1]
if unit then
local path, cost = wesnoth.find_path(unit, cfg.goal_x[i], cfg.goal_y[i])
if (cost <= unit.moves) and ((unit.x ~= cfg.goal_x[i]) or (unit.y ~= cfg.goal_y[i])) then
self.data.unit = unit
self.data.goal = { cfg.goal_x[i], cfg.goal_y[i] }
return 300000
end
end
end
return 0
end
function protect_unit:finish_exec(...)
AH.movefull_stopunit(ai, self.data.unit, self.data.goal)
self.data.unit = nil
self.data.goal = nil
end
function protect_unit:move_eval(cfg)
-- Always 94000 if one of the units can still move
local units = {}
for i,id in ipairs(cfg.id) do
table.insert(units, wesnoth.get_units{ id = id, formula = '$this_unit.moves > 0' }[1])
end
----- For the time being, we disable the dialog ----
-- If AI parameters are not set, bring up the dialog
-- For demo scenario only, delete for real use
--if (not self.data.enemy_weight) then
-- W.message { speaker = "narrator", image = "wesnoth-icon.png", message = "Before we get going, you can set some of the AI parameters. If you want to work with the default values, just click on 'Close' in the following dialog." }
-- local r = wesnoth.show_dialog(self.dialog, self.preshow, self.postshow)
-- local tmp = 'toward enemy'
-- if (self.data.bearing == -1) then tmp = 'away from enemy' end
-- W.message { speaker = "narrator", image = "wesnoth-icon.png", caption = "Parameters set to:", message = "Enemy unit weight = " .. self.data.enemy_weight .. "\nMy unit weight = " .. self.data.my_unit_weight .. "\nGoal distance weight = " .. self.data.distance_weight .. "\nTerrain defense weight = " .. self.data.terrain_weight .. "\nBearing: " .. tmp }
--end
if units[1] then
return 94000
else
return 0
end
end
function protect_unit:move_exec(cfg)
-- Find and execute best (safest) move toward goal
local units = {}
for i,id in ipairs(cfg.id) do
table.insert(units, wesnoth.get_units{ id = id, formula = '$this_unit.moves > 0' }[1])
end
-- Need to take the units off the map, as they don't count into the map scores
-- (as long as they can still move)
for i,u in ipairs(units) do wesnoth.extract_unit(u) end
-- All the units on the map
-- Counts all enemies, but only own units (not allies)
local my_units = wesnoth.get_units {side = wesnoth.current.side}
local enemy_units = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
-- My attack map
local MAM = BC.get_attack_map(my_units).units -- enemy attack map
--AH.put_labels(MAM)
--W.message {speaker="narrator", message="My attack map" }
-- Enemy attack map
local EAM = BC.get_attack_map(enemy_units).units -- enemy attack map
--AH.put_labels(EAM)
--W.message {speaker="narrator", message="Enemy attack map" }
-- Now put the units back out there
for i,u in ipairs(units) do wesnoth.put_unit(u.x, u.y, u) end
-- We move the weakest (fewest HP unit) first
local unit = AH.choose(units, function(tmp) return -tmp.hitpoints end)
--print("Moving: ",unit.id)
-- Also need the goal for this unit
local goal = {}
for i,id in ipairs(cfg.id) do
if (unit.id == id) then goal = { cfg.goal_x[1], cfg.goal_y[i] } end
end
--print("Goal:",goal[1],goal[2])
-- Reachable hexes
local reach_map = AH.get_reachable_unocc(unit)
--AH.put_labels(reach_map)
--W.message {speaker="narrator", message="Unit reach map" }
-- Now calculate the enemy inverse distance map
-- This is done here because we only need it for the hexes the unit can reach
-- Enemy distance map
local EIDM = AH.inverse_distance_map(enemy_units, reach_map)
--AH.put_labels(EIDM)
--W.message {speaker="narrator", message="Enemy inverse distance map" }
-- Get a terrain defense map of reachable hexes
local TDM = LS.create()
reach_map:iter(function(x, y, data)
TDM:insert(x, y, 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y)))
end)
--AH.put_labels(TDM)
--W.message {speaker="narrator", message="Terrain defense map" }
-- And finally, the goal distance map
local GDM = LS.create()
reach_map:iter(function(x, y, data)
GDM:insert(x, y, H.distance_between(x, y, goal[1], goal[2]))
end)
--AH.put_labels(GDM)
--W.message {speaker="narrator", message="Goal distance map" }
-- Configuration parameters -- can be set in the (currently disabled) dialog above
local enemy_weight = self.data.enemy_weight or 100.
local my_unit_weight = self.data.my_unit_weight or 1.
local distance_weight = self.data.distance_weight or 3.
local terrain_weight = self.data.terrain_weight or 0.1
local bearing = self.data.bearing or 1
-- If there are no enemies left, only distance to goal matters
-- This is to avoid rare situations where moving toward goal is canceled by moving away from own units
if (not enemy_units[1]) then
enemy_weight = 0
my_unit_weight = 0
distance_weight = 3
terrain_weight = 0
end
local max_rating, best_hex = -9e99, -1
local rating_map = LS.create() -- Also set up rating map, so that it can be displayed
for ind,r in pairs(reach_map.values) do
-- Most important: stay away from enemy: default weight=100 per enemy unit
-- Staying close to own troops: default weight=1 per own unit (allies don't count)
local rating =
(MAM.values[ind] or 0) * my_unit_weight
- (EAM.values[ind] or 0) * enemy_weight
-- Distance to goal is second most important thing: weight=3 per hex
rating = rating - GDM.values[ind] * distance_weight
-- Note: rating will usually be negative, but that's ok (the least negative hex wins)
-- Terrain rating. Difference of 30 in defense should be worth ~1 step toward goal
rating = rating + TDM.values[ind] * terrain_weight
-- Tie breaker: closer to or farther from enemy
rating = rating + (EIDM.values[ind] or 0) / 10. * bearing
if (rating > max_rating) then
max_rating, best_hex = rating, ind
end
rating_map.values[ind] = rating
end
--AH.put_labels(rating_map)
--W.message {speaker="narrator", message="Rating" }
--print("Best rating, hex:", max_rating, best_hex)
AH.movefull_stopunit(ai, unit, AH.get_LS_xy(best_hex))
end
function protect_unit:attack_eval(cfg)
-- Find possible attacks for the units
-- This is set up very conservatively
-- If unit can die in the worst case, it is not done, even if _really_ unlikely
local units = {}
for i,id in ipairs(cfg.id) do
table.insert(units, wesnoth.get_units{ id = id, formula = '$this_unit.attacks_left > 0' }[1])
end
if (not units[1]) then return 0 end
local attacks = AH.get_attacks(units, { simulate_combat = true })
if (not attacks[1]) then return 0 end
--print('#attacks',#attacks,ids)
-- All enemy units
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
-- For retaliation calculation:
-- Find all hexes enemies can attack on their next turn
local enemy_attacks = {}
for i,e in ipairs(enemies) do
local attack_map = BC.get_attack_map_unit(e).units
table.insert(enemy_attacks, { enemy = e, attack_map = attack_map })
end
-- Set up a retaliation table, as many pairs of attacks will be the same
local retal_table = {}
local max_rating, best_attack = -9e99, {}
for i,a in pairs(attacks) do
--print(i,a.dst.x,a.dst.y)
--print(' chance to die:',a.att_stats.hp_chance[0])
-- Only consider if there is no chance to die or to be poisoned or slowed
if ((a.att_stats.hp_chance[0] == 0) and (a.att_stats.poisoned == 0) and (a.att_stats.slowed == 0)) then
-- Get maximum possible retaliation possible by enemies on next turn
local my_unit = wesnoth.get_unit(a.src.x, a.src.y)
local max_retal = 0
for j,ea in ipairs(enemy_attacks) do
local can_attack = ea.attack_map:get(a.dst.x, a.dst.y)
if can_attack then
-- Check first if this attack combination has already been calculated
local str = (a.src.x + a.src.y * 1000) .. '-' .. (a.target.x + a.target.y * 1000)
--print(str)
if retal_table[str] then -- If so, use saved value
--print(' retal already calculated: ',str,retal_table[str])
max_retal = max_retal + retal_table[str]
else -- if not, calculate it and save value
-- Go thru all weapons, as "best weapon" might be different later on
local n_weapon = 0
local min_hp = my_unit.hitpoints
for weapon in H.child_range(ea.enemy.__cfg, "attack") do
n_weapon = n_weapon + 1
-- Terrain does not matter for this, we're only interested in the maximum damage
local att_stats, def_stats = wesnoth.simulate_combat(ea.enemy, n_weapon, my_unit)
-- Find minimum HP of our unit
-- find the minimum hp outcome
-- Note: cannot use ipairs() because count starts at 0
local min_hp_weapon = my_unit.hitpoints
for hp,chance in pairs(def_stats.hp_chance) do
if ((chance > 0) and (hp < min_hp_weapon)) then
min_hp_weapon = hp
end
end
if (min_hp_weapon < min_hp) then min_hp = min_hp_weapon end
end
--print(' min_hp:',min_hp, ' max damage:',my_unit.hitpoints-min_hp)
max_retal = max_retal + my_unit.hitpoints - min_hp
retal_table[str] = my_unit.hitpoints - min_hp
end
end
end
--print(' max retaliation:',max_retal)
-- and add this to damage possible on this attack
-- Note: cannot use ipairs() because count starts at 0
local min_hp = 1000
for hp,chance in pairs(a.att_stats.hp_chance) do
--print(hp,chance)
if ((chance > 0) and (hp < min_hp)) then
min_hp = hp
end
end
local min_outcome = min_hp - max_retal
--print(' min hp this attack:',min_hp)
--print(' ave hp defender: ',a.def_stats.average_hp)
--print(' min_outcome',min_outcome)
-- If this is >0, consider the attack
if (min_outcome > 0) then
local rating = min_outcome + a.att_stats.average_hp - a.def_stats.average_hp
--print(' rating:',rating,' min_outcome',min_outcome)
if (rating > max_rating) then
max_rating, best_attack = rating, a
end
end
end
end
--print('Max_rating:', max_rating)
if (max_rating > -9e99) then
self.data.best_attack = best_attack
return 95000
else
return 0
end
end
function protect_unit:attack_exec()
local attacker = wesnoth.get_unit(self.data.best_attack.src.x, self.data.best_attack.src.y)
local defender = wesnoth.get_unit(self.data.best_attack.target.x, self.data.best_attack.target.y)
--W.message {speaker=attacker.id, message="Attacking" }
AH.movefull_stopunit(ai, attacker, self.data.best_attack.dst.x, self.data.best_attack.dst.y)
ai.attack(attacker, defender)
self.data.best_attack = nil
end
return protect_unit
end
}

View file

@ -0,0 +1,129 @@
return {
init = function(ai)
local AH = wesnoth.require("ai/lua/ai_helper.lua")
local recruit_cas = {}
local internal_recruit_cas = {}
local internal_params = {}
-- The following external engine creates the CA functions recruit_rushers_eval and recruit_rushers_exec
-- It also exposes find_best_recruit and find_best_recruit_hex for use by other recruit engines
wesnoth.require("ai/lua/generic-recruit_engine.lua").init(ai, internal_recruit_cas, internal_params)
function recruit_cas:rusher_recruit_eval(cfg)
internal_params.randomness = cfg.randomness
return internal_recruit_cas:recruit_rushers_eval()
end
function recruit_cas:rusher_recruit_exec(cfg)
return internal_recruit_cas:recruit_rushers_exec()
end
local recruit
function recruit_cas:random_recruit_eval(cfg)
-- Random recruiting from all the units the side has
-- Check if leader is on keep
local leader = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'yes' }[1]
if (not leader) or (not wesnoth.get_terrain_info(wesnoth.get_terrain(leader.x, leader.y)).keep) then
return 0
end
-- Check if there is space left for recruiting
local width, height, border = wesnoth.get_map_size()
local castle = {
locs = wesnoth.get_locations {
x = "1-"..width, y = "1-"..height,
{ "and", {
x = leader.x, y = leader.y, radius = 200,
{ "filter_radius", { terrain = 'C*^*,K*^*,*^Kov,*^Cov' } }
}}
}
}
local no_space = true
for i,c in ipairs(castle.locs) do
local unit = wesnoth.get_unit(c[1], c[2])
if (not unit) then
no_space = false
break
end
end
if no_space then return 0 end
-- Set up the probability array
local probability, prob_sum = {}, 0
-- Go through all the types listed in [probability] tags (which can be comma-separated lists)
for i,tmp in ipairs(cfg.type) do
tmp = AH.split(tmp, ",")
for j,t in ipairs(tmp) do
-- If this type is in the recruit list, add it
for k,r in ipairs(wesnoth.sides[wesnoth.current.side].recruit) do
if (r == t) then
probability[t] = { value = cfg.probability[i] }
prob_sum = prob_sum + cfg.probability[i]
break
end
end
end
end
-- Now we add in all the unit types not listed in [probability] tags
for i,r in ipairs(wesnoth.sides[wesnoth.current.side].recruit) do
if (not probability[r]) then
probability[r] = { value = 1 }
prob_sum =prob_sum + 1
end
end
-- Now eliminate all those that are too expensive (unless cfg.skip_low_gold_recruiting is set)
if cfg.skip_low_gold_recruiting then
for typ,prob in pairs(probability) do
if (wesnoth.unit_types[typ].cost > wesnoth.sides[wesnoth.current.side].gold) then
--print('Eliminating:', typ)
prob_sum = prob_sum - prob.value
probability[typ] = nil
end
end
end
-- Now set up the min/max values for each type
-- This needs to be done manually as the order of pairs() is not guaranteed
local cum_prob, n_recruits = 0, 0
for typ,prob in pairs(probability) do
probability[typ].p_i = cum_prob
cum_prob = cum_prob + prob.value / prob_sum * 1e6
probability[typ].p_f = cum_prob
n_recruits = n_recruits + 1
end
-- Now we're ready to pick on of those
-- We always call the exec function, no matter if the selected unit is affordable
-- The point is that this will blacklist the CA if an unaffordable recruit was
-- chosen -> no cheaper recruits will be selected in subsequent calls
if (n_recruits > 0) then
local rand_prob = AH.random(1e6)
for typ,prob in pairs(probability) do
if (prob.p_i <= rand_prob) and (rand_prob < prob.p_f) then
recruit = typ
break
end
end
else
recruit = wesnoth.sides[wesnoth.current.side].recruit[1]
end
return 180000
end
function recruit_cas:random_recruit_exec()
-- Let this function blacklist itself if the chosen recruit is too expensive
if wesnoth.unit_types[recruit].cost <= wesnoth.sides[wesnoth.current.side].gold then
ai.recruit(recruit)
end
end
return recruit_cas
end
}

View file

@ -0,0 +1,768 @@
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local AH = wesnoth.require("ai/lua/ai_helper.lua")
function add_CAs(side, CA_parms)
-- Add the candidate actions defined in 'CA_parms' to the AI of 'side'
-- CA_parms is an array of tables, one for each CA to be added
--
-- Required keys for CA_parms:
-- - id: is used for both CA id and name
-- - eval_name: name of the evaluation function
-- - exec_name: name of the execution function
--
-- Optional keys for CA_parms:
-- - cfg_str: a configuration string (in form of a Lua WML table), to be passed to eval and exec functions
-- Note: we pass the same string to both functions, even if it contains unnecessary parameters for one or the other
-- - max_score: maximum score the CA can return
for i,parms in ipairs(CA_parms) do
cfg_str = parms.cfg_str or ''
local CA = {
engine = "lua",
id = parms.id,
name = parms.id,
max_score = parms.max_score, -- This works even if parms.max_score is nil
evaluation = "return (...):" .. parms.eval_name .. "(" .. cfg_str .. ")",
execution = "(...):" .. parms.exec_name .. "(" .. cfg_str .. ")"
}
if parms.sticky then
CA.sticky = "yes"
CA.unit_x = parms.unit_x
CA.unit_y = parms.unit_y
end
W.modify_ai {
side = side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", CA }
}
end
end
function delete_CAs(side, CA_parms)
-- Delete the candidate actions defined in 'CA_parms' from the AI of 'side'
-- CA_parms is an array of tables, one for each CA to be removed
-- We can simply pass the one used for add_CAs(), although only the
-- CA_parms.id field is needed
for i,parms in ipairs(CA_parms) do
W.modify_ai {
side = side,
action = "try_delete",
path = "stage[main_loop].candidate_action[" .. parms.id .. "]"
}
end
end
function add_aspects(side, aspect_parms)
-- Add the aspects defined in 'aspect_parms' to the AI of 'side'
-- aspect_parms is an array of tables, one for each aspect to be added
--
-- Required keys for aspect_parms:
-- - aspect: the aspect name (e.g. 'attacks' or 'aggression')
-- - facet: A table describing the facet to be added
--
-- Examples of facets:
-- 1. Simple aspect, e.g. aggression
-- { value = 0.99 }
--
-- 2. Composite aspect, e.g. attacks
-- { name = "ai_default_rca::aspect_attacks",
-- id = "dont_attack",
-- invalidate_on_gamestate_change = "yes",
-- { "filter_own", {
-- type = "Dark Sorcerer"
-- } }
-- }
for i,parms in ipairs(aspect_parms) do
W.modify_ai {
side = side,
action = "add",
path = "aspect[" .. parms.aspect .. "].facet",
{ "facet", parms.facet }
}
end
end
function delete_aspects(side, aspect_parms)
-- Delete the aspects defined in 'aspect_parms' from the AI of 'side'
-- aspect_parms is an array of tables, one for each CA to be removed
-- We can simply pass the one used for add_aspects(), although only the
-- aspect_parms.id field is needed
for i,parms in ipairs(aspect_parms) do
W.modify_ai {
side = side,
action = "try_delete",
path = "aspect[attacks].facet[" .. parms.id .. "]"
}
end
end
function CA_action(action, side, CA_parms)
if (action == 'add') then add_CAs(side, CA_parms) end
if (action == 'delete') then delete_CAs(side, CA_parms) end
if (action == 'change') then
delete_CAs(side, CA_parms)
add_CAs(side, CA_parms)
end
end
function wesnoth.wml_actions.micro_ai(cfg)
-- Set up the [micro_ai] tag functionality for each Micro AI
-- Check that the required common keys are all present and set correctly
if (not cfg.ai_type) then H.wml_error("[micro_ai] missing required ai_type= key") end
if (not cfg.side) then H.wml_error("[micro_ai] missing required side= key") end
if (not cfg.action) then H.wml_error("[micro_ai] missing required action= key") end
if (cfg.action ~= 'add') and (cfg.action ~= 'delete') and (cfg.action ~= 'change') then
H.wml_error("[micro_ai] invalid value for action=. Allowed values: add, delete or change")
end
--------- Healer Support Micro AI - side-wide AI ------------------------------------
if (cfg.ai_type == 'healer_support') then
local cfg_hs = {}
if (cfg.action ~= 'delete') then
-- Optional keys
cfg_hs.aggression = cfg.aggression or 1.0
cfg_hs.injured_units_only = cfg.injured_units_only
cfg_hs.max_threats = cfg.max_threats
end
-- Set up the CA add/delete parameters
local CA_parms = {
{
id = 'initialize_healer_support', eval_name = 'initialize_healer_support_eval', exec_name = 'initialize_healer_support_exec',
max_score = 999990, cfg_str = ''
},
{
id = 'healer_support', eval_name = 'healer_support_eval', exec_name = 'healer_support_exec',
max_score = 105000, cfg_str = AH.serialize(cfg_hs)
},
}
-- 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 (cfg.aggression ~= 0) then
table.insert(CA_parms,
{
id = 'healers_can_attack', eval_name = 'healers_can_attack_eval', exec_name = 'healers_can_attack_exec',
max_score = 99990, cfg_str = ''
}
)
end
-- Add, delete or change the CAs
CA_action(cfg.action, cfg.side, CA_parms)
return
end
--------- Bottleneck Defense Micro AI - side-wide AI ------------------------------------
if (cfg.ai_type == 'bottleneck_defense') then
local cfg_bd = {}
if (cfg.action ~= 'delete') then
-- Required keys
if (not cfg.x) or (not cfg.y) then
H.wml_error("Bottleneck Defense Micro AI missing required x= and/or y= key")
end
if (not cfg.enemy_x) or (not cfg.enemy_y) then
H.wml_error("Bottleneck Defense Micro AI missing required enemy_x= and/or enemy_y= key")
end
cfg_bd.x, cfg_bd.y = cfg.x, cfg.y
cfg_bd.enemy_x, cfg_bd.enemy_y = cfg.enemy_x, cfg.enemy_y
-- Optional keys
cfg_bd.healer_x = cfg.healer_x
cfg_bd.healer_y = cfg.healer_y
cfg_bd.leadership_x = cfg.leadership_x
cfg_bd.leadership_y = cfg.leadership_y
cfg_bd.active_side_leader = cfg.active_side_leader
end
-- Set up the CA add/delete parameters
local CA_parms = {
{
id = 'bottleneck_move', eval_name = 'bottleneck_move_eval', exec_name = 'bottleneck_move_exec',
max_score = 300000, cfg_str = AH.serialize(cfg_bd)
},
{
id = 'bottleneck_attack', eval_name = 'bottleneck_attack_eval', exec_name = 'bottleneck_attack_exec',
max_score = 290000, cfg_str = ''
}
}
-- Add, delete or change the CAs
CA_action(cfg.action, cfg.side, CA_parms)
return
end
--------- Messenger Escort Micro AI - side-wide AI ------------------------------------
if (cfg.ai_type == 'messenger_escort') then
local cfg_me = {}
if (cfg.action ~= 'delete') then
-- Required keys
if (not cfg.id) then
H.wml_error("Messenger Escort Micro AI missing required id= key")
end
if (not cfg.waypoint_x) or (not cfg.waypoint_y) then
H.wml_error("Messenger Escort Micro AI missing required waypoint_x= and/or waypoint_y= key")
end
cfg_me.id = cfg.id
cfg_me.waypoint_x, cfg_me.waypoint_y = cfg.waypoint_x, cfg.waypoint_y
-- Optional keys
cfg_me.enemy_death_chance = cfg.enemy_death_chance
cfg_me.messenger_death_chance = cfg.messenger_death_chance
end
local CA_parms = {
{
id = 'attack', eval_name = 'attack_eval', exec_name = 'attack_exec',
max_score = 300000, cfg_str = AH.serialize(cfg_me)
},
{
id = 'messenger_move', eval_name = 'messenger_move_eval', exec_name = 'messenger_move_exec',
max_score = 290000, cfg_str = AH.serialize(cfg_me)
},
{
id = 'other_move', eval_name = 'other_move_eval', exec_name = 'other_move_exec',
max_score = 280000, cfg_str = AH.serialize(cfg_me)
},
}
-- Add, delete or change the CAs
CA_action(cfg.action, cfg.side, CA_parms)
return
end
--------- Lurkers Micro AI - side-wide AI ------------------------------------
if (cfg.ai_type == 'lurkers') then
local cfg_lurk = {}
if (cfg.action ~= "delete") then
-- Required keys
cfg = cfg.__parsed
local required_keys = {"lurkers", "attack_terrain"}
local optional_keys = {"stationary", "wander_terrain"}
for k, v in pairs(required_keys) do
local child = H.get_child(cfg, v)
if (not cfg[v]) and (not child) then
H.wml_error("Lurker AI missing required " .. v .. "= key")
end
cfg_lurk[v] = cfg[v]
if child then cfg_lurk[v] = child end
end
for k, v in pairs(optional_keys) do
cfg_lurk[v] = cfg[v]
local child = H.get_child(cfg, v)
if child then cfg_lurk[v] = child end
end
end
local CA_parms = {
{
id = 'lurker_moves_lua', eval_name = 'lurker_attack_eval', exec_name = 'lurker_attack_exec',
max_score = 100010, cfg_str = AH.serialize(cfg_lurk)
},
}
-- Add, delete or change the CAs
CA_action(cfg.action, cfg.side, CA_parms)
return
end
--------- Protect Unit Micro AI - side-wide AI ------------------------------------
if (cfg.ai_type == 'protect_unit') then
local cfg_pu = { id = {}, goal_x = {}, goal_y = {} }
if (cfg.action ~= 'delete') then
-- Required keys
for u in H.child_range(cfg, "unit") do
if (not u.id) then
H.wml_error("Protect Unit Micro AI [unit] tag missing required id= key")
end
if (not u.goal_x) then
H.wml_error("Protect Unit Micro AI [unit] tag missing required goal_x= key")
end
if (not u.goal_y) then
H.wml_error("Protect Unit Micro AI [unit] tag missing required goal_y= key")
end
table.insert(cfg_pu.id, u.id)
table.insert(cfg_pu.goal_x, u.goal_x)
table.insert(cfg_pu.goal_y, u.goal_y)
end
if (not cfg_pu.id[1]) then
H.wml_error("Protect Unit Micro AI missing required [unit] tag")
end
end
local unit_ids_str = 'dummy'
for i,id in ipairs(cfg_pu.id) do
unit_ids_str = unit_ids_str .. ',' .. id
end
local aspect_parms = {
{
aspect = "attacks",
facet = {
name = "ai_default_rca::aspect_attacks",
id = "dont_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_own", {
{ "not", {
id = unit_ids_str
} }
} }
}
}
}
local CA_parms = {
{
id = 'finish', eval_name = 'finish_eval', exec_name = 'finish_exec',
max_score = 300000, cfg_str = AH.serialize(cfg_pu)
},
{
id = 'attack', eval_name = 'attack_eval', exec_name = 'attack_exec',
max_score = 95000, cfg_str = AH.serialize(cfg_pu)
},
{
id = 'move', eval_name = 'move_eval', exec_name = 'move_exec',
max_score = 94000, cfg_str = AH.serialize(cfg_pu)
}
}
add_aspects(cfg.side, aspect_parms)
-- Add, delete or change the CAs
CA_action(cfg.action, cfg.side, CA_parms)
-- Optional key
if cfg.disable_move_leader_to_keep then
W.modify_ai {
side = side,
action = "try_delete",
path = "stage[main_loop].candidate_action[move_leader_to_keep]"
}
end
if (cfg.action == "delete") then
delete_aspects(cfg.side, aspect_parms)
-- We also need to add the move_leader_to_keep CA back in
-- This works even if it was not removed, it simply overwrites the existing CA
W.modify_ai {
side = side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="move_leader_to_keep",
engine="cpp",
name="ai_default_rca::move_leader_to_keep_phase",
max_score=160000,
score=160000
} }
}
end
return
end
--------- Micro AI Guardian - BCA AIs -----------------------------------
if (cfg.ai_type == 'guardian_unit') then
-- We handle these types of guardians here: stationed, return, coward
if (not cfg.guardian_type) then H.wml_error("[micro_ai] missing required guardian_type= key") end
local guardian_type = cfg.guardian_type
-- Since this is a BCA, the unit id needs to be present even for removal
if (not cfg.id) then H.wml_error("[micro_ai] missing required id= key") end
-- Set up the cfg array
local cfg_guardian = { guardian_type = guardian_type }
local required_keys, optional_keys = {}, {}
required_keys["stationed_guardian"] = { "id", "distance", "station_x", "station_y", "guard_x", "guard_y" }
optional_keys["stationed_guardian"] = {}
required_keys["coward"] = { "id", "distance" }
optional_keys["coward"] = { "seek_x", "seek_y","avoid_x","avoid_y" }
required_keys["return_guardian"] = { "id", "return_x", "return_y" }
optional_keys["return_guardian"] = {}
if (cfg.action~='delete') then
--Check that we know about this type of guardian
if (not required_keys[guardian_type]) then
H.wml_error("[micro_ai] unknown value for guardian_type= key: '" .. guardian_type .."'")
end
--Add in the required keys, which could be scalars or WML tag contents
cfg = cfg.__parsed
for k,v in pairs(required_keys[guardian_type]) do
local child = H.get_child(cfg, v)
if (not cfg[v]) and (not child) then
H.wml_error("[micro_ai] ".. guardian_type .." missing required " .. v .. "= key")
end
-- Insert scalar parameters
cfg_guardian[v] = cfg[v]
-- Insert WML tags
if child then cfg_guardian[v] = child end
end
--Add in the optional keys, which could be scalars or WML tag contents
for k,v in pairs(optional_keys[guardian_type]) do
-- Insert scalar parameters
cfg_guardian[v] = cfg[v]
-- Insert WML tags
local child = H.get_child(cfg, v)
if child then cfg_guardian[v] = child end
end
end
local max_scores = {}
max_scores["stationed_guardian"] = 100010
max_scores["coward"] = 300000
local unit = wesnoth.get_units { id=cfg.id }[1]
local CA_parms = {
{
id = guardian_type .. '_' .. cfg.id, eval_name = guardian_type .. '_eval', exec_name = guardian_type .. '_exec',
max_score = max_scores[guardian_type], sticky = true, unit_x = unit.x, unit_y = unit.y, cfg_str = AH.serialize(cfg_guardian)
},
}
-- Add, delete or change the CAs
CA_action(cfg.action, cfg.side, CA_parms)
return
end
--------- Micro AI Animals - side-wide and BCA AIs ------------------------------------
if (cfg.ai_type == 'animals') then
-- We handle these types of animal AIs here:
-- BCAs: hunter_unit
-- side-wide AIs: wolves, wolves_multipack, big_animals, forest_animals, swarm, herding
if (not cfg.animal_type) then H.wml_error("[micro_ai] missing required animal_type= key") end
local animal_type = cfg.animal_type
-- For the BCAs, the unit id needs to be present even for removal
if (animal_type == "hunter_unit") then
if (not cfg.id) then H.wml_error("[micro_ai] missing required id= key") end
end
-- Set up the cfg array
local cfg_animals = { animal_type = animal_type }
local required_keys, optional_keys = {}, {}
-- This list does not contain id because we check for that differently
required_keys["hunter_unit"] = { "id", "home_x", "home_y" }
optional_keys["hunter_unit"] = { "hunting_ground", "rest_turns", "show_messages" }
required_keys["wolves"] = { "predators", "prey" }
optional_keys["wolves"] = { "avoid_type" }
required_keys["wolves_multipacks"] = {}
optional_keys["wolves_multipacks"] = { "type", "pack_size", "show_pack_number" }
required_keys["big_animals"] = { "big_animals"}
optional_keys["big_animals"] = { "avoid_unit", "goal_terrain", "wander_terrain" }
required_keys["forest_animals"] = {}
optional_keys["forest_animals"] = { "rabbit_type", "rabbit_number", "rabbit_enemy_distance", "rabbit_hole_img",
"tusker_type", "tusklet_type", "deer_type", "wander_terrain"
}
required_keys["swarm"] = {}
optional_keys["swarm"] = { "scatter_distance", "vision_distance", "enemy_distance" }
required_keys["herding"] = { "herding_perimeter", "herd", "herders", "herd_x", "herd_y" }
optional_keys["herding"] = { "attention_distance", "attack_distance" }
if (cfg.action~='delete') then
--Check that we know about this type of animal AI
if (not required_keys[animal_type]) then
H.wml_error("[micro_ai] unknown value for animal_type= key: '" .. animal_type .."'")
end
--Add in the required keys, which could be scalars or WML tag contents
cfg = cfg.__parsed
for k,v in pairs(required_keys[animal_type]) do
local child = H.get_child(cfg, v)
if (not cfg[v]) and (not child) then
H.wml_error("[micro_ai] ".. animal_type .." missing required " .. v .. "= key")
end
-- Insert scalar parameters
cfg_animals[v] = cfg[v]
-- Insert WML tags
if child then cfg_animals[v] = child end
end
--Add in the optional keys, which could be scalars or WML tag contents
for k,v in pairs(optional_keys[animal_type]) do
-- Insert scalar parameters
cfg_animals[v] = cfg[v]
-- Insert WML tags
local child = H.get_child(cfg, v)
if child then cfg_animals[v] = child end
end
end
local CA_parms = {}
if (cfg_animals.animal_type == 'big_animals') then
CA_parms = {
{
id = "big_animal", eval_name = 'big_eval', exec_name = 'big_exec',
max_score = 300000, cfg_str = AH.serialize(cfg_animals)
}
}
end
if (cfg_animals.animal_type == 'wolves') then
CA_parms = {
{
id = "wolves", eval_name = 'wolves_eval', exec_name = 'wolves_exec',
max_score = 95000, cfg_str = AH.serialize(cfg_animals)
},
{
id = "wolves_wander", eval_name = 'wolves_wander_eval', exec_name = 'wolves_wander_exec',
max_score = 90000, cfg_str = AH.serialize(cfg_animals)
}
}
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_animals.avoid_type
} }
} }
}
}
}
if (cfg.action == "delete") then
delete_aspects(cfg_animals.side, wolves_aspects)
else
add_aspects(cfg_animals.side, wolves_aspects)
end
end
if (cfg_animals.animal_type == 'herding') then
CA_parms = {
{
id = "close_enemy", eval_name = 'herding_attack_close_enemy_eval', exec_name = 'herding_attack_close_enemy_exec',
max_score = 300000, cfg_str = AH.serialize(cfg_animals)
},
{
id = "sheep_runs_enemy", eval_name = 'sheep_runs_enemy_eval', exec_name = 'sheep_runs_enemy_exec',
max_score = 295000, cfg_str = AH.serialize(cfg_animals)
},
{
id = "sheep_runs_dog", eval_name = 'sheep_runs_dog_eval', exec_name = 'sheep_runs_dog_exec',
max_score = 290000, cfg_str = AH.serialize(cfg_animals)
},
{
id = "herd_sheep", eval_name = 'herd_sheep_eval', exec_name = 'herd_sheep_exec',
max_score = 280000, cfg_str = AH.serialize(cfg_animals)
},
{
id = "sheep_move", eval_name = 'sheep_move_eval', exec_name = 'sheep_move_exec',
max_score = 270000, cfg_str = AH.serialize(cfg_animals)
},
{
id = "dog_move", eval_name = 'dog_move_eval', exec_name = 'dog_move_exec',
max_score = 260000, cfg_str = AH.serialize(cfg_animals)
}
}
end
if (cfg_animals.animal_type == 'forest_animals') then
CA_parms = {
{
id = "new_rabbit", eval_name = 'new_rabbit_eval', exec_name = 'new_rabbit_exec',
max_score = 310000, cfg_str = AH.serialize(cfg_animals)
},
{
id = "tusker_attack", eval_name = 'tusker_attack_eval', exec_name = 'tusker_attack_exec',
max_score = 300000, cfg_str = AH.serialize(cfg_animals)
},
{
id = "move", eval_name = 'forest_animals_move_eval', exec_name = 'forest_animals_move_exec',
max_score = 290000, cfg_str = AH.serialize(cfg_animals)
},
{
id = "tusklet", eval_name = 'tusklet_eval', exec_name = 'tusklet_exec',
max_score = 280000, cfg_str = AH.serialize(cfg_animals)
}
}
end
if (cfg_animals.animal_type == 'swarm') then
CA_parms = {
{
id = "scatter_swarm", eval_name = 'scatter_swarm_eval', exec_name = 'scatter_swarm_exec',
max_score = 300000, cfg_str = AH.serialize(cfg_animals)
},
{
id = "move_swarm", eval_name = 'move_swarm_eval', exec_name = 'move_swarm_exec',
max_score = 290000, cfg_str = AH.serialize(cfg_animals)
}
}
end
if (cfg_animals.animal_type == 'wolves_multipacks') then
CA_parms = {
{
id = "wolves_multipacks_attack", eval_name = 'wolves_multipacks_attack_eval', exec_name = 'wolves_multipacks_attack_exec',
max_score = 300000, cfg_str = AH.serialize(cfg_animals)
},
{
id = "wolves_multipacks_wander_eval", eval_name = 'wolves_multipacks_wander_eval', exec_name = 'wolves_multipacks_wander_exec',
max_score = 290000, cfg_str = AH.serialize(cfg_animals)
}
}
end
if (cfg_animals.animal_type == 'hunter_unit') then
local unit = wesnoth.get_units { id=cfg_animals.id }[1]
CA_parms = {
{
id = "hunter_unit_" .. cfg_animals.id, eval_name = 'hunter_unit_eval', exec_name = 'hunter_unit_exec',
max_score = 300000, sticky = true, unit_x = unit.x, unit_y = unit.y, cfg_str = AH.serialize(cfg_animals)
}
}
end
-- Add, delete or change the CAs
CA_action(cfg.action, cfg.side, CA_parms)
return
end
--------- Patrol Micro AI - BCA AI ------------------------------------
if (cfg.ai_type == 'patrol_unit') then
local cfg_p = {}
-- Required keys - for both add and delete actions
if (not cfg.id) then
H.wml_error("Patrol Micro AI missing required id= key")
end
cfg_p.id = cfg.id
if (cfg.action ~= 'delete') then
-- Required keys - add action only
if (not cfg.waypoint_x) or (not cfg.waypoint_y) then
H.wml_error("Patrol Micro AI missing required waypoint_x/waypoint_y= key")
end
cfg_p.waypoint_x = cfg.waypoint_x
cfg_p.waypoint_y = cfg.waypoint_y
-- Optional keys
cfg_p.attack = cfg.attack
cfg_p.one_time_only = cfg.one_time_only
cfg_p.out_and_back = cfg.out_and_back
end
local unit = wesnoth.get_units { id=cfg_p.id }[1]
local CA_parms = {
{
id = "patrol_unit_" .. cfg_p.id, eval_name = 'patrol_eval', exec_name = 'patrol_exec',
max_score = 300000, sticky = true, unit_x = unit.x, unit_y = unit.y, cfg_str = AH.serialize(cfg_p)
},
}
-- Add, delete or change the CAs
CA_action(cfg.action, cfg.side, CA_parms)
return
end
--------- Recruiting Micro AI - side-wide AI ------------------------------------
if (cfg.ai_type == 'recruiting') then
local cfg_recruiting = {}
local recruit_CA = { id = "recruit", max_score = 180000 }
if cfg.recruiting_type then
if cfg.recruiting_type == "rushers" then
recruit_CA.eval_name = 'rusher_recruit_eval'
recruit_CA.exec_name = 'rusher_recruit_exec'
cfg_recruiting.randomness = cfg.randomness
elseif cfg.recruiting_type == "random" then
recruit_CA.eval_name = 'random_recruit_eval'
recruit_CA.exec_name = 'random_recruit_exec'
cfg_recruiting.skip_low_gold_recruiting = cfg.skip_low_gold_recruiting
-- Add the 'probability' tags
cfg_recruiting.type, cfg_recruiting.probability = {}, {}
for p in H.child_range(cfg, "probability") do
if (not p.type) then
H.wml_error("Random Recruiting Micro AI [probability] tag missing required type= key")
end
if (not p.probability) then
H.wml_error("Random Recruiting Micro AI [probability] tag missing required probability= key")
end
table.insert(cfg_recruiting.type, p.type)
table.insert(cfg_recruiting.probability, p.probability)
end
else
H.wml_error("[micro_ai] unknown value for recruiting_type= key")
end
elseif cfg.action ~= 'delete' then
H.wml_error("[micro_ai] missing required recruiting_type= key")
end
recruit_CA.cfg_str = AH.serialize(cfg_recruiting)
CA_action(cfg.action, cfg.side, {recruit_CA})
if cfg.action == 'add' then
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[recruitment]"
}
elseif cfg.action == 'delete' then
-- We need to add the recruitment CA back in
-- This works even if it was not removed, it simply overwrites the existing CA
W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="recruitment",
engine="cpp",
name="ai_default_rca::aspect_recruitment_phase",
max_score=180000,
score=180000
} }
}
end
return
end
----------------------------------------------------------------
-- If we got here, none of the valid ai_types was specified
H.wml_error("invalid ai_type= in [micro_ai]")
end

View file

@ -0,0 +1,96 @@
return {
init = function(ai)
local priority_target = {}
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
function priority_target:change_attacks_aspect(target_id)
-- Set 'attacks' aspect so that only unit with id=target_id
-- is attacked if it can be reached, delete aspect otherwise
-- The following can be simplified significantly once the 'attacks' variable is available
-- All units that have attacks left (but are not leaders)
local attackers = wesnoth.get_units{side = wesnoth.current.side, canrecruit = "no", formula = "$this_unit.attacks_left > 0"}
--print("\nAttackers:",#attackers)
-- This gets set to >0 if unit that can attack target is found
local target_in_reach = 0
-- See if any of those units can reach our target(s)
for i,u in ipairs(attackers) do
-- Need to find reachable hexes that are
-- 1. next to target
-- 2. not occupied by an allied unit (except for unit itself)
W.store_reachable_locations {
{ "filter", { id = u.id } },
{ "filter_location", {
{ "filter_adjacent_location", {
{ "filter", { id = target_id } }
} },
{ "not", {
{ "filter", { { "not", { id = u.id } } } }
} }
} },
moves = "current",
variable = "tmp_locs"
}
local tir = H.get_variable_array("tmp_locs")
W.clear_variable { name = "tmp_locs" }
--print("reachable locs:",u.id,#tir)
-- If unit can reach a target -> set variable to 1
if (#tir > 0) then target_in_reach = 1 end
end
-- Always delete the attacks aspect first, so that we do not end up with 100 copies of the facet
--print("Deleting attacks aspect")
W.modify_ai {
side = wesnoth.current.side,
action = "try_delete",
path = "aspect[attacks].facet[limited_attack]"
}
-- Also delete aggression, caution - for the same reason
W.modify_ai {
side = wesnoth.current.side,
action = "try_delete",
path = "aspect[aggression].facet[*]"
}
W.modify_ai {
side = wesnoth.current.side,
action = "try_delete",
path = "aspect[caution].facet[*]"
}
-- If the target is in reach, set the 'attacks' aspect accordingly ...
if (target_in_reach > 0) then
--print("Setting attacks aspect")
W.modify_ai {
side = wesnoth.current.side,
action = "add",
path = "aspect[attacks].facet",
{ "facet", {
name = "ai_default_rca::aspect_attacks",
id = "limited_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_enemy", { id = target_id } }
} }
}
-- We also want to set
-- 'aggression=1' and 'caution=0', otherwise there could be turns on which nothing happens
W.modify_side {
side = wesnoth.current.side,
{ "ai", { aggression = 1, caution = 0 } }
}
end
return 0
end
return priority_target
end
}

View file

@ -0,0 +1,22 @@
return {
init = function(ai)
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local urudin = { }
-- This is taken almost literally from 'Ka'lian under Attack' in 'Legend of Wesmere'
function urudin:retreat()
local urudin = wesnoth.get_units({side = 3, id="Urudin"})[1]
if urudin and urudin.valid then
local mhp, hp = urudin.max_hitpoints, urudin.hitpoints
local turn = wesnoth.current.turn
if (turn >= 5) or (hp < mhp / 2) then
AH.movefull_stopunit(ai, urudin, 33, 8)
end
end
end
return urudin
end
}

View file

@ -0,0 +1,38 @@
border_size=1
usage=map
Gg , Gg , Gll^Fp , Gll , Gll , Gg , Gs , Gs , Hh^Fp , Gg , Mm , Ms , Ms^Xm , Wo , Wo , Ms , Ms , Ms , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Hh , Hh , Hh , Hh , Gs , Gs , Gs , Gd , Gs , Gd , Hhd , Hhd , Hhd , Rd , Rd , Hhd , Hhd , Rd , Rd , Hd , Hhd , Rd , Dd , Hd , Dd , Dd , Hhd , Dd^Edp , Dd , Hd , Dd , Hd , Dd , Dd
Gg , Gll^Fp , Gs , Gll^Fp , Gs , Gs^Fms , Gll , Gg , Gll^Fp , Hh^Fp , Hh , Mm , Ms , Ha , Wo , Ms , Ha , Ms , Ms , Mm , Mm , Mm , Mm , Hh , Gg , Hh , Gs , Hh , Gs , Gs , Gs , Gs , Gs , Rd , Gd , Rd , Gd , Rd , Hhd , Hhd , Hhd , Hhd , Rd , Rd , Rd^Esd , Hhd , Dd^Edpp , Rd , Hd , Rd , Dd^Vdt , Rd^Dr , Dd , Dd , Hd , Hd , Dd , Hd , Hd
Gg , Gll , Gll^Fp , Gll , Gs^Fms , Gs , Gs , Gll , Gs , Gs , Gg , Mm , Mm , Ms , Ms , Ha , Ha , Ha , Ms , Mm , Mm , Mm , Mm , Gg , Mm , Gs , Hh , Hh , Hh , Gs^Es , Gs , Gs , Gs , Gd , Hhd , Hhd , Hhd^Vhhr , Gd , Gd , Rd , Gd , Hhd , Dd , Rd^Dr , Rd , Dd , Dd , Rd , Rd , Dd , Rd , Hd , Hd , Dd , Hd , Dd , Dd , Dd , Dd^Dc
Gg , Gll^Fp , Gll^Fp , Gll^Fp , Gs , Gll^Fp , Gs , Gs^Fms , Gs , Hh^Fp , Gg , Hh , Hh^Fp , Mm , Ha , Ms , Ms , Ms , Ms , Mm , Mm , Hh , Gg^Efm , Mm , Mm , Mm , Hh , Hh , Gg , Gg , Gs , Gs , Gd , Rd , Hhd , Hhd , Gd , Rd , Hhd , Rd , Rd , Rd , Rd , Dd , Hd , Hhd , Rd , Dd , Dd , Dd , Rd^Esd , Hd , Rd , Hd , Dd , Hd , Dd , Dd , Hd
Gg , Gll^Fp , Gll^Fp , Gs^Fms , Gll , Gs , Gll^Fp , Gg , Gs , Gs , Gg , Gll^Fp , Gs^Fms , Hh , Hh , Hh , Hh , Ms , Ms , Mm , Mm , Mm , Mm , Mm , Hh , Hh , Hh , Hh , Gs , Gs , Gs , Gs , Gs , Gd , Gd , Hhd , Hhd , Rd , Gd , Gd , Hhd , Hhd , Rd , Hhd , Hhd , Rd , Hd , Dd , Rd , Rd , Dd , Hd , Dd^Edpp , Dd , Rd , Dd , Rd , Dd^Edp , Hhd
Gs , Gs^Fms , Gs , Gg , Gg , Gs^Vl , Gll^Fp , Gs , Gs^Fms , Gg , Gg , Gll^Fp , Gg , Hh^Fp , Gg , Hh , Hh , Mm , Mm , Ms , Mm , Hh , Mm , Hh , Mm , Hh , Gg , Gg , Gg , Gs , Gg , Gs , Gs , Gd , Gs , Rd , Hhd , Gd , Hhd , Hhd , Gd , Gd , Hhd , Gd , Rd , Rd , Dd , Rd , Hhd , Hhd , Hd , Rd , Hhd , Rd , Hd , Hd , Dd , Hd , Hd
Gll^Fp , Gs , Gs , Gs^Fms , Gll^Fp , Gll , Gs^Fms , Gg , Gs , Gll^Fp , Gs^Fms , Gg , Hh^Fp , Gs , Gg , Hh^Fp , Gg^Efm , Hh^Fp , Hh , Mm , Mm , Mm , Mm , Mm , Mm , Gg , Hh , Hh , Hh , Gs , Gs , Gs , Gs , Gs , Gd , Gd , Hhd , Hhd , Hhd , Gd , Rd , Gd , Rd , Rd , Dd , Rd , Hhd , Rd , Rd , Rd^Esd , Dd , Hhd , Rd , Dd , Rd^Esd , Hd , Hhd , Hhd , Hhd
Gg , Gs , Gg , Gll^Fp , Gs , Gs , Gs , Gs , Gs , Gs^Fms , Gg , Gs , Gg , Gg , Gs^Fms , Gg , Gg , Hh^Fp , Hh^Fds , Hh , Hh , Mm , Mm , Mm , Hh , Hh , Gg , Hh , Hh , Gs , Gs , Gs , Gs , Gd , Gd , Gd , Gd , Gd , Rd , Rd , Rd , Rd , Hhd , Gd , Gd , Rd , Dd , Rd , Rd^Esd , Dd , Rd , Rd , Dd , Rd^Esd , Rd , Hhd , Dd , Rd , Hhd
Gg , Gs , Gs , Gs^Fms , Gll^Fp , Gg , Gs^Fms , Gs , Gs , Gs^Fds , Gs , Gs , Gs , Gg , Gs , Gg , Gs , Hh^Fp , Gg , Gg , Hh , Mm , Mm , Mm , Mm , Hh , Hh , Gg , Gs , Gg , Gs , Gs , Gg , Gs , Gd , Hhd , Hhd , Hhd , Hhd , Rd , Gd , Rd , Gd , Hhd , Gd , Rd , Rd , Hd , Rd , Rd , Dd , Rd , Hhd , Hhd , Hhd , Dd , Dd , Rd , Hhd
Gg , Gll^Fp , Gll , Gg , Gll^Fp , Gs , Gg , Gll^Fp , Gs^Fms , Gg , Gll^Fp , Gg , Gs^Fms , Gs^Fms , Gs , Gs , Gs^Fms , Gg , Gg , Gg , Hh^Fp , Hh , Hh^Vhh , Mm , Hh , Hh , Hh , Gs , Gg , Gg , Hh , Gg , Hh , Hh , Hh , Gd , Gd , Gd , Gd , Hhd , Hhd , Gd , Rd , Hhd , Rd , Rd , Dd , Rd , Rd , Hhd , Rd , Rd , Md , Md , Hhd , Hhd , Hhd , Hhd , Hh
Gs^Fms , Gll , Gs , Gs , Gll^Fp , Gll , Gs , Gs , Gs , Gll^Fp , Gg , Gs^Fms , Gg , Gs^Fms , Gg , Gs , Gs^Fms , Gg , Gg , Gll^Fp , Gg^Efm , Hh , Hh , Mm , Mm , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Gd , Hh , Gd , Gd , Hhd , Gd , Hhd , Gd , Gd , Rd , Rd , Rd , Rd^Dr , Gd , Hhd , Hhd , Md , Md , Hhd , Hh , Md , Md , Md
Gll^Fp , Gg , Gs^Fms , Gs , Gs , Gs^Fms , Gll^Fp , Gs^Fms , Gs^Fms , Gs , Gs , Gs^Fms , Gg , Gs^Fms , Gll^Fp , Gg , Gll^Fp , Gs^Fms , Gg , Gg , Gs , Hh^Fds , Gs , Hh , Mm , Mm , Mm , Hh , Mm , Mm , Mm , Mm , Mm , Hh , Hh , Gs , Gs , Hh , Hhd , Gd , Hhd , Gd , Gd , Rd , Hhd , Rd , Rd , Gd , Rd , Rd , Hhd , Md , Md , Md , Md , Md , Mm , Md , Mm
Gg , Gs^Fms , Gll^Fp , Gs , Gs , Gg , Gg , Gs^Fds , Gs , Gs , Gg , Gs , Gs , Gs^Fds , Gs , Gs , Gg , Gs^Fds , Gs^Fms , Gg , Gll^Fp , Gg , Hh , Hh , Hh , Mm , Mm , Mm , Mm , Mm , Hh , Mm , Hh , Mm , Gs , Gs , Gs , Hh , Hh , Hh , Hhd , Hhd , Hhd , Rd , Hhd , Gd , Gd , Hhd , Gd^Es , Gd , Gd , Hhd^Vhhr , Md , Md , Md , Mm , Mm , Mm , Hh
Gg , Gll , Gg , Gg , Gg , Gs , Gs , Gs^Fms , Gs^Fms , Gs^Fms , Gll^Fp , Gg , Gg , Gs , Gs^Fms , Gg , Gs , Gg , Gg , Gg , Gg , Gs^Fms , Gg , Gg , Hh , Hh^Fp , Hh , Mm , Hh , Gg , Mm , Hh , Hh , Hh , Gg , Hh , Gs , Hh , Gs , Gd , Hh , Hhd , Hhd , Gd , Gd , Rd , Gd , Hhd , Hh , Hhd , Hh , Hhd , Md , Md , Md , Mm , Hh , Hh , Gg
Gs^Fms , Gs^Fms , Gg , Gs^Fms , Gs^Fms , Gs^Fms , Gg , Gll^Fp , Gs^Fms , Gg^Efm , Gs^Fms , Gs^Fms , Gs^Fms , Gs , Gs , Gg , Gll^Fp , Gs , Gg , Gs^Fms , Gs^Fms , Gs^Fms , Gg , Gg , Gg , Gg , Gg , Hh , Gg , Hh , Gs , Gs , Gs , Gs , Gs , Hh , Hh , Hh , Gs , Hh , Gs , Gd , Hhd , Hhd , Hh , Gd , Hh , Hhd , Md , Md , Md , Md , Md , Md , Md , Mm , Hh , Hh , Gg
Gg , Gg , Gs , Gs^Fms , Gs , Gs , Gs , Gs , Gs^Fms , Gll^Fp , Gg , Gg , Gs , Gs , Gll^Fp , Gg , Gs^Fms , Gs^Fms , Gs^Fms , Gs , Gs , Gs^Fms , Gg , Gs^Fms , Gg , Gs^Fms , Gg , Gg , Gs , Gs , Gg , Hh , Md , Hh^Vhh , Mm , Mm , Mm , Hh , Hh , Hh , Md , Hh , Hh , Hhd , Hh , Md , Md , Md , Md , Md , Md , Md , Md , Md , Mm , Hh , Hh , Hh , Gg
Gll , Gs , Gs , Gg , Gs^Fms , Gs , Gs , Gs^Fms , Gg , Gs^Fms , Gs^Fms , Gs^Fms , Gs^Fms , Gs^Fms , Gg , Gs^Fms , Gg , Gll^Fp , Gs^Fms , Gg , Gg , Gs , Gs , Gg , Gs^Fms , Gs , Gs , Gs , Gg , Gg , Hh , Hh , Hh , Md , Md , Mm , Md , Hh , Hh , Gg , Md , Hh , Md , Hh , Md , Md , Ms , Md , Md , Md , Hh , Md , Hh , Mm , Hh , Mm , Hh , Hh , Gg
Gll^Fp , Gs , Gs , Gs , Gg , Gg , Gs , Gg , Gs^Fms , Gs , Gs , Gg , Gll^Fp , Gll , Gs , Gs^Fms , Gs^Fms , Gs^Fms , Gs^Fds , Gs^Fms , Gg , Gg , Gg , Gs^Fms , Gs^Fms , Gs^Fms , Gg , Gs , Gg , Gs^Fms , Gg , Hh , Hh , Hh , Hh , Md , Md , Hh , Md , Md , Md , Md , Md , Md , Md , Ms , Ha , Ms , Mm , Md , Hh , Mm , Hh , Mm , Mm , Mm , Mm , Gs , Gg
Gll , Gll , Gg , Gs , Gs^Fms , Gs^Fms , Gs^Fms , Gs , Gg , Gg , Gs^Fms , Gs , Gs , Gs , Gg , Gg , Gs , Gll^Fp , Gs^Fms , Gs^Fms , Gs^Fms , Gs , Gs , Gs^Fms , Gs , Gg , Gs^Fms , Gg , Gs^Fms , Gg , Gs^Fms , Gg , Gs , Gs , Gg , Hh , Hh , Md , Mm , Mm , Mm , Mm , Mm , Md , Mm , Mm , Wo , Ms , Mm , Mm , Hh^Vhh , Hh , Hh , Gg , Hh , Mm , Hh , Hh , Gs
Gll^Fp , Gs , Gs^Fms , Gs^Fms , Gs^Vh , Gs^Fms , Gg , Gs , Gll^Fp , Gs^Fds , Gs^Fds , Gg , Gs^Fms , Gs^Fms , Gg , Gs^Fms , Gll^Fp , Gs^Fms , Gs , Gs^Vc , Gs^Fms , Gg , Gs^Fds , Gg , Gg , Gg , Gg , Gs^Fms , Gs^Fms , Gs^Fms , Gg , Gg , Gs^Fds , Gll^Fp , Gg , Hh , Gg , Gg , Hh , Hh , Hh , Mm , Hh , Mm , Hh , Hh , Ww , Hh , Hh , Hh , Gg , Hh , Gs , Gg , Hh , Mm , Mm , Gs , Hh
Gll^Fp , Gg , Gs , Gs^Fms , Gs , Gs^Fms , Gs , Gs^Fms , Gg , Gg^Efm , Gg , Gg , Gs^Fms , Gg , Gg , Gll^Fp , Gs^Fms , Gg^Efm , Gll^Fp , Gs^Fds , Gs^Fms , Gs^Fms , Gg , Gs^Fms , Gs^Fds , Gg , Gs^Fds , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg^Efm , Hh , Hh , Hh , Hh , Wo , Ww , Gg^Efm , Hh , Gg , Gs , Gg , Hh , Hh , Gs , Gs , Hh , Hh , Chr , Rp
Gs^Fms , Gg , Gs , Gs , Gg , Gs , Gs , Gs^Fms , Gs^Fms , Gs^Fds , Gs , Gs , Gg , Gs^Fms , Gs^Fms , Gs^Fds , Gs^Fms , Gs^Fms , Gg , Gs , Gg , Gg , Gg , Gg , Gs^Fds , Gg , Gg , Gg , Gs^Fds , Gs^Fms , Gg , Gg , Gs^Fds , Gg , Gg , Gg , Ww , Gg , Wwf , Ww , Ww , Ww , Wo , Ww , Gg , Hh , Gg , Hh , Gg , Gs , Gg , Gg , Gs , Hh , Gs , Gs , Re , Rp , Chr
Gll^Fp , Gll , Gs , Gs^Fms , Gll^Fp , Gs , Gs , Gg , Gs , Gg , Gs , Gg , Gs^Fms , Gg , Gs^Fds , Gs , Gs , Gg , Gs^Fms , Gs^Fms , Gs^Fds , Gg , Gs^Fds , Gs^Fds , Gg , Gg , Gg , Gg , Gg , Gs^Fds , Gg , Gg , Ww , Gg , Ww , Ww , Gg , Ww , Gg , Gg , Gg , Hh , Hh , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gs , Gs , Gg , Gg , Rp , Rp , Hh , Hh , Hh
Gs^Fms , Gll^Fp , Gs^Fms , Gs^Fms , Gll , Gg , Gg , Gg , Gg , Gs^Fms , Gg , Gg , Gs^Fds , Gg , Gg , Gg , Gs^Fms , Gg , Gg , Gs^Fms , Gg , Gs^Fds , Wo , Wo , Ww , Gs^Fms , Ww , Ww , Ww , Gg , Ww , Ww , Gs^Fds , Ww , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gs , Gs , Gs , Gg , Rr , Gs , Hh , Hh , Hh , Hh
Gg , Gg , Gs , Gs , Gs^Fms , Gs^Fms , Gll^Fp , Gs^Fms , Gg , Gs^Fds , Gg , Gs^Fds , Gg , Gg , Gs^Fds , Gg , Gs^Fms , Gg , Gs^Fms , Gg , Wo , Wo , Gg , Gg , Gg , Ww , Gs^Fds , Gg , Gg , Ww , Gg , Gg , Rb , Gg , Rb , Gg , Gg , Gg , Gs , Gg , Gg , Gg , Gg , Gg , Gg , Gs , Gg , Gg , Gs , Gg , Gs , Gg , Re , Rp , Gg , Hh , Hh , Hh , Gg
Gg , Gs^Fds , Gll^Fp , Gs , Gs^Fms , Gg , Gs^Fms , Gll^Fp , Gs^Fms , Gs , Gs^Fms , Gs^Fms , Gg , Gs^Fms , Gs^Fms , Gs^Fds , Gg , Gs^Fms , Gs^Fms , Gg , Wo , Gg^Efm , Gg , Gs^Fms , Gg , Gg , Gg , Gg , Gg , Gg , Rb , Rb , Gg , Rb , Gg , Rb , Rb , Gg , Gg , Gg , Gg , Gs , Gg , Gg , Gg , Gs , Gs , Gs , Gg , Gg , Gg , Gg , Rp , Gs , Hh , Gg , Gg , Hh , Hh
Gg , Gs , Gs , Gg , Gg , Gs , Gs^Fms , Gll^Fp , Gg , Gs , Gs , Gs^Fms , Gs^Fds , Gs^Fms , Gs , Gg , Gg , Gs^Fms , Gg , Gg , Wo , Gs^Fms , Gg , Gg , Gg , Gs^Fds , Gs^Fds , Gg , Rb , Rb , Gs , Gg , Gg , Gg , Gg , Gg , Gg , Rb , Rb , Gs , Gg , Gg , Gs , Gg , Gg , Gs , Gs , Gs , Gs , Gs , Gs , Gg , Rp , Hh , Hh , Gs , Hh , Hh , Gg
Gs^Fms , Gs , Gs , Gs^Fms , Gs , Gs , Gs^Fms , Gs^Fms , Gg , Gs^Fms , Gg , Gs^Fms , Gs , Gs^Fms , Gs , Gg , Gs^Fms , Gs^Fms , Gg , Gs^Fds , Ss , Wo , Wo , Gg , Gg , Gg , Rb , Rb , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Rb , Gg , Gg , Gs , Gg , Gs , Gg , Gg , Gs , Gs , Gg , Gs , Gs , Rp , Gg , Gs , Hh , Gg , Gs , Gg
Gg , Gs , Gs^Fds , Gs^Fds , Gs , Gs^Fms , Gg , Gs , Gs^Fds , Gs^Fms , Gg , Gs^Fds , Gs^Fds , Gg , Gg , Gg , Gg , Gg , Gs^Fms , Gs^Fds , Gg , Gg , Gs^Fms , Ww , Gs^Fds , Rb , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gs , Gs , Gg , Rb , Gg , Gg , Gg , Gs , Gg , Gs , Gs , Gs , Gs , Gg , Gs , Rp , Gs , Gs , Gg , Gs , Gs , Hh , Gs
Gs , Gs^Fds , Gs^Fms , Gg , Gs , Gs , Gs^Fms , Gs , Gg , Gs , Gs , Gs^Fms , Gg , Gs^Fms , Gg , Gs^Fms , Gs , Gg , Gs^Fds , Gs^Fms , Gg , Gg , Gs^Fds , Wo , Gg , Rb , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gs , Gg , Rb , Gg , Gs , Gs , Gs , Gs , Gs , Gs , Gs , Gs , Gs , Gg , Rp , Gg , Gg , 1 Gs , Gs , Gg , Gg , Gg
Gs , Gs , Gs , Gg , Gs^Fms , Gg , Gs^Fms , Gll^Fp , Gs^Fms , Gs^Vl , Gs^Fms , Gs^Fms , Gs^Fms , Gg , Gg , Gs^Fms , Gs^Fds , Gg , Gg , Gg , Gg , Gg , Wo , Ww , Gg , Rb , Gg , Gg , Gg , Gg , Gs , Gg , Gs , Gg , Gg , Gs , Gg , Gs , Gg , Rb , Gg , Gg , Gs , Gs , Gg , Gs , Gg , Gs , Gs , Gg , Gg , Re , Gg , Gs , Gs , Gs , Gs , Gs , Gg
Gs , Gs^Fms , Gs^Fds , Gg , Gs , Gg , Gs^Fms , Gs^Fms , Gs , Gs^Fms , Gg , Gs , Gs , Gs , Gg , Gs^Fds , Gg , Gg , Gg , Gs^Fds , Gg , Gg , Wo , Gg , Gg , Rb , Gg , Gg , Gg , Gg , Gg , Gs , Gg , Gg , Rb , Gg , Rb , Gg , Rb , Rb , Gg , Gg , Gs , Gg , Gg , Gs , Gs , Gs , Gs , Gs , Rp , Rp , Gg , Gg , Gg , Gg , Gg , Gs , Rb^Eff
Gg , Gs^Fds , Gs^Fds , Gs , Gg , Gg , Gg , Gs , Gs , Gs^Fms , Gg , Gs^Fms , Gg , Gg , Gg , Gs^Fms , Gg , Gg , Gg , Gs^Fms , Wo , Wo , Gg^Efm , Gg , Gg , Rb , Rb , Gg , Rb , Gg , Rb , Gg , Rb , Rb , Gg , Rb , Gg , Rb , Gg , Gg , Gg , Gs , Gg , Gs , Gs , Gs , Gs , Gs , Gg , Gs , Re , Gs , Gg , Gg , Ww , Gg , Rb^Eff , Rb^Eff , Rb^Gvs
Gll , Gs^Fms , Gs , Gs , Gll^Fp , Gg , Gs^Fds , Gs^Fms , Gg , Gs , Gs^Fds , Gg , Gg , Gs^Fms , Gs^Fms , Gs^Fds , Gs^Fds , Gg , Gs^Fds , Gs^Fds , Wo , Gs^Fds , Gs^Fms , Gs^Fds , Gg , Gg , Gs^Fds , Rb , Gg , Rb , Gs , Rb , Gs , Gs , Gg , Gs , Gs , Gs , Gs , Gg , Gs , Gs^Vh , Re , Gs , Gg , Gg , Gs , Gg , Re , Re , Gs , Gs^Vc , Gg , Ww , Rb^Eff , Rb^Eff , Rb^Gvs , Rb^Gvs , Rb^Gvs
Gg , Gll , Gg , Gll^Fp , Gs^Fms , Gs^Fms , Gg , Gg , Gs^Fms , Gs^Fms , Gs^Fms , Gg , Gs^Fms , Gs^Fds , Gg , Gg , Gs^Fms , Gs^Fds , Gg , Gs^Fds , Wo , Ss , Gg , Gg , Gg , Gg , Gs^Fds , Gg , Gg , Gs , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Re , Re , Gs , Gg , Gg , Re , Gg , Gg , Gg , Rb^Eff , Rb^Eff , Rb^Gvs , Rb^Gvs , Rb^Gvs , Rb^Gvs , Rb^Gvs

View file

@ -0,0 +1,17 @@
border_size=1
usage=map
Hhd , Gd , Hhd , Gd , Gd^Edp , Gg , Hhd , Gg , Gd , Gg , Gs , Hhd , Gs , Gs , Hhd , Hh , Mm^Xm , Mm^Xm , Hh , Gg , Gg^Eff , Gg^Eff , Rb^Gvs , Rb^Gvs , Rb^Gvs
Gd^Edpp , Hhd , Gs , Gd , Gd , Hhd , Gd , Gs , Hhd , Gd , Hhd , Gd , Hhd , Hhd , Hh , Hh , Mm^Xm , Hh , Gg , Gg^Eff , Gg^Eff , Re , Rb , Rb^Gvs , Rb
Hhd , Gd , Gs , Hhd , Gs , Gd^Es , Hhd , Gd , Hhd , Gd , Gd , Gs , Hh , Hhd , Mm^Xm , Mm^Xm , Hh , Hh , Gg , Gs , Hh , Gg^Eff , Gg^Eff , Rb^Gvs , Rb^Gvs
Gd^Es , Hhd , Gd , Gd , Gd , Hhd , Gd , Gs , Gd , Gd , Hhd , Gs , Hhd , Mm^Xm , Hh , Hh , Gs , Gg , Gs , Gs , Gg , Gg , Gs , Gg^Eff , Gg^Eff
Gd , Gd^Edpp , Gd , Hhd , Gd , Gs , Gs , Gd , Gd , Gs , Gs , Gs , Hh , Mm^Xm , Hh , Gg , Gg , Hh , Gs , Gg , Hh , Gg , Gg , Gg , Gg
Gd , Gd , Gd , Gd , Gd , Gd , Gd , Gs^Es , Gs , Gs , Hh , Gs , Hh , Mm^Xm , Hh , Gg , Gs , Gg , Gs , Gg , Gg , Gg , Gg , Gg , Rb
Gd , Gd , Ce , Gd , Rd , Gd , Rd , Gd , Re , Gd , Gs , Gs , Hhd , Mm^Xm , Hh , Gs , Gg , Gg , Gg , Gg , Gg , Gg , Ce , Rb , Gg
Rd , 2 Ke , Ce , Ce , Gd , Re , Gd , Re , Gs , Rd , Rb , Gs , Hh , Mm^Xm , Hh , Gg , Rb , Rb , Rb , Rb , Rb , Ce , Ce , 1 Ke , Ww
Gd , Gd , Gd , Gd , Gd , Gd , Gd , Gs , Gs , Gs , Gs , Re , Re , Hh , Rb , Rb , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Wo , Wo
Gd , Gs , Gd^Edpp , Gd , Gd , Hhd , Gd , Gd , Gs , Hhd , Hh , Gs , Hh , Rb , Hh , Gs , Gg , Gg , Gg , Gg , Gg , Gg , Ww , Ww , Wo
Gs , Gd , Gs , Gs , Hhd , Gs , Gd , Gs , Gs , Gs , Gs , Hh , Mm^Xm , Mm^Xm , Hh , Gg , Gg , Gs , Ww , Ww , Wo , Ww , Gg , Ww , Wo
Gd , Gs , Gd^Es , Gd , Gd , Gd , Gd , Gd , Gs , Gs , Hh , Hh , Mm^Xm , Hh , Gg , Gg , Gg , Ww , Gg , Gg , Gg , Sm , Ss , Ww , Ww
Gd^Edp , Gd , Gd , Gd , Gs , Gs , Hhd , Gs , Gs , Gs , Hh , Mm^Xm , Ms^Xm , Hh , Gg^Efm , Ss , Ww , Ww , Gg^Em , Ss , Ss , Gg , Ss , Gg , Ss
Gg , Gd , Gg , Gs , Gs , Gd , Gs , Gs , Gg , Gg , Hh , Mm^Xm , Ww , Hh , Wo , Wo , Gg , Ss , Gg , Ss , Gg^Efm , Gg , Gg , Gg , Gg

View file

@ -0,0 +1,23 @@
border_size=1
usage=map
Gs , Gg , Gg , Gg , Gs , Gs , Gs , Gs , Gg , Gg , Gg , Gg , Gg , Rb , Gg , Gg , Gs , Gs , Gg , Gg , Ss , Gg , Ss , Gg , Gg , Ss , Gg , Ss , Ww , Ds , Ww , Ww , Ds , Gg , Gg , Gg , Gg , Gg , Gg , Ss , Gg , Gg , Ss , Ss , Gg , Ss
Gs , Gs , Gs , Gs , Gg , Gg , Gs , Gs , Gg , Gg , Gg , Gg , Gg , Rb , Gg , Gg , Gg , Gg , Ss , Gg , Gg , Gg , Gg , Ss , Sm , Ss , Ww , Ww , Ss , Ww , Ww , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Ss , Ss , Gg , Ss , Ss , Gg , Ss , Gg
Gg , Gs , Gg , Gg , Ss , Gs , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Rb , Gg , Gg , Gg , Ss , Ww , Ww , Ww , Wo , Wo , Wo , Wo , Ww , Ss , Ss , Ss^Vm , Wwf , Ww , Gg , Ss , Ss , Ss , Ss , Gg , Ss , Gg , Gg , Ss , Gg^Efm , Gg , Ss , Ss , Gg
Ss , Gg , Gg , Gg , Gg , Ss , Ss , Gg , Ss , Ss , Gs , Gg , Ww , Rb , Ww , Ww , Wwf , Ww , Ss , Gg , Gg , Gg , Gg , Ss , Wo , Gg , Ss , Gg , Gg , Wo , Ss , Sm , Gg , Gg , Ss , Gg^Efm , Gg , Ss , Ss , Gg , Gg , Gg , Gg , Gg , Ss , Ss
Ss , Ss , Gg , Ss , Ss , Gg , Ss , Gg , Gg , Ss , Wwf , Ww , Ww , Wwf , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Ww , Wo , Gg , Ss , Gg , Gg , Ss , Ww , Ss , Ss , Ss , Ss , Gg , Ss , Gg , Gg , Gg , Ss , Ss , Ww , Gs^Vc , Ss , Gg , Ss
Gg , Ss , Gg , Gg , Ss , Ss , Gg , Gg , Ww , Wo , Gg , Gg , Gg , Rb , Gg , Ss , Gg , Ss , Ss , Ss , Gg , Ww , Ww , Gg , Ss , Gg , Ds , Gg , Ww , Ww , Gg , Gg , Ss , Gg , Gg , Gg , Ss , Gg , Gg , Ss , Gg , Gg , Wo , Ww , Ww , Ww
Gg , Gg , Ww , Ww , Ww , Wwg , Ww , Ww , Gg , Gg , Ss , Gg , Gg , Re , Gg , Ss , Ss , Gg , Gg , Ss , Gg , Ww , Gg , Gg , Chs , 1 Khs , Chs , Ss , Ww , Ss , Ss , Ss , Ss , Gg , Gg , Ss , Ss , Sm , Ss , Ss , Ww , Ww , Gg , Wo , Ss , Ss
Wwf , Ww , Ss , Ww , Gg^Efm , Sm , Ww , Ww , Gs , Ss , Gg , Ss , Ss , Rb , Gg , Ss , Gg , Ss , Ss , Ds , Ww , Wwf , Gg , Ss , Gg , Chs , Gg , Ww , Ww , Ww , Ww , Ss , Sm , Ss , Gg , Ss , Wo , Wo , Ww , Ww , Ss , Ss , Ss , Sm , Ss , Gg
Ds , Ss , Gg , Gg , Ss , Ss , Ww , Gg , Gg , Ss , Ss , Ss , Gg , Rb , Ss , Ss , Ss , Sm , Ss , Ss , Ww , Wo , Gg , Ss , Ss , Gg , Gg^Efm , Gg , Ww , Ww , Ww , Ss , Ss , Ss , Ww , Wo , Ss , Ss , Ss , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Ww , Ds , Ss , Gg , Gg , Ss , Ww , Gg , Gg , Ss , Ss , Gg^Efm , Gg , Rb , Gg , Ss , Ss , Ss^Vm , Sm , Gg , Wo , Ww , Gg , Ss , Ss , Ss , Ss , Ss , Ww , Ww , Wo , Ww , Ww , Ww , Ss , Gg , Gg , Ss , Ss , Gg , Gg , Gg , Gs , Gs , Gg , Gg
Ww , Ww , 5 Khw , Ww , Ww , Ww , Gg , Gg , Ss , Ss , Ss^Vm , Ss , Ss , Rb , Gg , Gg , Ss , Ss , Ss , Wwf , Ww , Gg , Ss , Gg , Gg , Ss , Ss , Gg , Ss , Ww , Wo , Ww , Ww , Ss , Ss , Gg , Ss , Ss , Gg , Gg , Gs , Gs , Gg , Gg , Gg , Hh
Ww , Chw , Chw , Chw , Ww , Wwf , Gg , Ss , Ss , Ss , Ss , Ss , Gg , Rb , Rb , Ss , Ss , Gg , Gg , Gg , Ww , Ss , Ss , Ss , Ss , Ss , Ss , Wwf , Ss , Wo , Ww , Wo , Ww , Ww , Ww , Gg , Gg , Gg , Gg , Gg , Gs , Gg , Gs , Gg , Hh , Gs
Ww , Ww , Ww , Ww , Ww , Ds , Gg , Ss , Gg , Ss , Ss , Gg , Ss , Ss , Rb , Gg , Ss , Ss , Gg , Wo , Gg , Ss , Gg , Gg , Ss , Gg , Ss , Ss , Ss , Gg , Wo , Wo , Ww , Ww , Ww , Ww , Gg , Gg , Gs , Gg , Gg , Gs , Gs^Es , Gs , Hh , Hh
Wwr , Wwr , Wwr , Ww , Ww , Ds , Ds , Gg , Gg , Gg , Ss , Ss , Ss , Gg , Rb , Ss , Ss , Ss , Ds , Wo , Gg , Gg , Gg , Gg , Ds , Ss , Ss , Gg , Gg , Gg , Ww , Ww , Ww , Wo , Ww , Ww , Ds , Dd , Gg , Gs , Gg , Hh , Hh , Gs , Ds , Ds
Ww , Ww , Wwr , Ww , Ww , Ww , Ww , Ds , Ss , Ss , Gg , Ss , Ss , Gg , Rb , Gg , Gg , Ww , Ww , Wo , Gg , Gg , Ww , Ds , Ww^Vm , Ww , Ds , Ss , Gg , Ss , Wwf , Ww , Ww , Wo , Ww , Ww , Ww , Dd , Gs , Gs , Gs , Hh , Ds , Ds , Ww , Ww
Ww , Ww , Ww , Wwr , Ww , Ww , Ww , Ww , Ww , Ss , Ss , Gg , Ss , Ss , Rb , Gg , Ds , Ss , Ww , Wwf , Wo , Ww , Gg , Ww , Ww , Ww , Ww , Ww , Ds , Ww , Wo , Ww , Ww , Wo , Ww , Ww , Ww , Gg , Gs , Dd , Ds , Ds , Ww , Ww , Ww , Ww
Wwr , Wwr , Ww , Ww , Wwr , Ww , Wwf , Ww , Ds , Gg , Gg , Gg , Gg , Gg , Rb , Ds , Ww , Ww , Gg , Ss , Wo , Ww , Ww , Ww , Ww , Ww , Wwr , Ww , Ww , Wwf , Wo , Ww , Wo , Wo , Wo , Ww , Gs , Gs , Gs , Dd , Ds , Ww , Ww , Ww , Ww , Ww
Wo , Wo , Wo , Ww , Wwr , Ww , Ww , Wwf , Ds , Gg , Gg , Gs , Gs , Re , Gg , Gg , Ww , Ww , Ww , Ww , Ww , Wwf , Ww , Ww , Wwr , Ww , Ww , Wwr , Ww , Wwf , Wo , Ww , Wo , Wo , Ww , Ww , Ds , Gg , Gg , Ds , Ww , Ww , Ww , Ww , Ww , Ww
Wo , Wo , Ww , Wo , Ww^Vm , Ww , Ww , Ww , Ww , Gg , Gg , Gs , Gg , Rb , Gg , Wwf , Wwf , Ww , Gg , Ww , Ww , Ww , Ww , Ww , Ww , Wwr , Ww , Ww , Ww , Ww , Wo , Ww , Ww , Wo , Wo , Ww , Ww , Gs^Vc , Hh , Ww , Ww , Ww , Ww , Ww , Hh , Hh
Wo , Wo , Wo , Wwr , Wo , Ww , Wo , Ww , Wwf , Ds , Ww , Wwf , Rb , Re , Gg , Wwf , Gs^Vc , Ww , Gg , Gs , Ww , Gg , Ww , Ww , Ww , Ww , Ww , Wwr , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Ww

View file

@ -0,0 +1,35 @@
border_size=1
usage=map
Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Rb , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh
Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Rb , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh
Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh^Fp , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Hh , Hh , Hh , Hh , Rb , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh^Fp , Hh^Fp , Hh^Fp
Mm , Hh^Fp , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Hh , Hh , Hh , Rb , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh^Fp , Mm , Mm , Mm
Mm , Mm , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Hh , Hh , Rb , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh^Fp , Hh^Fp , Mm , Mm , Mm
Xu , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Hh^Fp , Hh^Fp , Rb , Hh , Hh , Hh , Hh , Hh^Fp , Hh , Hh^Fp , Hh^Fp , Mm , Mm , Mm^Xm , Mm , Mm
Xu , Xu , Xu , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Hh , Hh^Fp , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm^Xm , Mm^Xm , Mm
Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Rb , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm
Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Rb , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm
Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Hh^Fp , Hh , Hh , Hh , Hh^Fp , Hh , Hh , Rb , Rb , Hh , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm
Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Hh^Fp , Hh^Fp , Hh , Hh , Hh , Hh , Hh^Fp , Hh , Rb , Hh , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm
Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Hh , Hh^Fp , Hh , Rb , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm
Mm^Xm , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Rb , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm
Mm^Xm , Xu , Xu , Xu , Xu , Xu , Uu , Xu , Uu , Xu , Xu , Xu , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Hh , Hh^Fp , Hh^Fp , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm
Xu , Mm^Xm , Mm^Xm , Uu , Uu , Uu , Xu , Uu , Xu , Uu , Xu , Xu , Xu , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Rb , Hh , Hh , Hh , Hh , Hh , Hh^Fp , Mm , Mm , Mm , Mm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm
Uu , Uu , Mm^Xm , Xu , Xu , Xu , Xu , Xu , Xu , Uu , Uu , Xu , Xu , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Hh , Hh^Fp , Hh , Rb , Hh , Hh^Fp , Hh , Hh , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm
Xu , Xu , Xu , Xu , Uu^Vud , Xu , Uu , Xu , Xu , Xu , Xu , Uu , Xu , Xu , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Hh^Fp , Hh^Fp , Hh , Rb , Rb , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm , Mm^Xm
Uu , Uu , Uu , Uu , Uu , Uu , Re , Xu , Xu , Xu , Xu , Uu , Uu , Xu , Mm , Mm , Hh , Hh^Fp , Hh , Hh , Hh , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Rb , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm^Xm , Mm , Mm , Mm
Xu , Re , Re , Uu , Ch , Uu , Uu , Re , Re , Xu , Xu , Xu , Xu , Mm , Mm , Hh , Mm , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Rb , Rb , Hh , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm , Mm , Mm , Mm
Xu , Xu , Xu , Xu , 1 Kh , Ch , Uu , Uu , Uu , Uu^Vud , Xu , Xu , Mm , Mm , Hh , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Vhh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Rb , Rb , Gs^Vh , Rb , Rb , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm
Xu , Xu , Xu , Xu , Xu , Ch , Re , Uu , Uu , Uu , Re , Xu , Mm , Mm , Mm , Hh , Hh , Hh , Hh , Hh^Fp , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh , Rb , Gs , Ww , Gs , Gs , Rb , Rb , Gs^Vh , Rb , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm
Xu , Xu , Xu , Uu , Uu , Uu , Uu^Vud , Re , Uu , Uu , Mm , Mm , Mm , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Rb , Gs , Gs , Ww , Ww , Gs , Rb , Hh^Fp , Rb , Hh^Fp , Rb , Rb , Hh^Fp , Hh^Fp , Mm , Mm
Xu , Xu , Xu , Xu , Xu , Xu , Uu , Re , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Gs^Vh , Hh^Fp , Hh^Fp , Hh^Fp , Hh , Hh^Fp , Hh , Hh , Hh , Hh , Hh , Rb , Rb , Gs^Vh , Gs , Ww , Gs , Rb , Hh^Fp , Hh^Fp , Hh^Fp , Gs^Vh , Rb , Hh^Fp , Hh^Fp , Mm , Mm
Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Rb , Rb , Rb , Rb , Hh , Rb , Rb , Rb , Hh , Hh , Hh , Hh , Rb , Rb , Gs^Vh , Rb , Rb , Hh^Fp , Hh^Fp , Rb , Rb , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm
Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Gs^Vh , Hh^Fp , Hh^Fp , Rb , Gs , Gs , Gs , Rb , Rb , Hh , Rb , Rb , Hh , Rb , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Rb , Gs^Vh , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm
Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Dd^Vdt , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Rb , Gs^Vh , Ww , Ww , Gs^Vh , Rb , Rb , Rb , Rb , Hh , Hh , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Rb , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm
Xu , Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Mm , Hh^Fp , Mm , Hh^Fp , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Ww , Gs^Vh , Gs , Rb , Hh , Hh , Rb , Rb , Hh^Fp , Ch , Hh^Fp , Rb , Rb , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm , Xu
Xu , Xu , Xu , Xu , Uu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Rb , Hh^Fp , Hh , Hh , Hh , Hh^Fp , Ch , 2 Kh , Ch , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm , Mm , Xu
Xu , Xu , Uu , Uu , Uu , Uu , Uu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Mm , Mm , Mm , Hh^Fp , Mm , Hh^Fp , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Ch , Ch , Ch , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm , Xu , Xu , Xu
Xu , Xu , Uu , Uu , Uu , Uu , Uu , Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm , Mm , Xu , Xu , Xu , Xu
Xu , Xu , Uu , Uu , Uu , Uu , Uu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Hh^Fp , Hh^Fp , Hh^Fp , Mm , Mm , Mm , Mm , Mm , Mm , Xu , Xu , Xu , Xu , Xu
Xu , Xu , Xu , Uu , Uu , Uu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Xu , Mm , Mm , Mm , Xu , Xu , Xu , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Mm , Xu , Xu , Xu , Xu , Xu , Xu , Xu

View file

@ -0,0 +1,19 @@
border_size=1
usage=map
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Gg , Gg , Ce , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Gg , Gg , 2 Ke , Ce , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Ce , Ce , Gg , Gg
Gg , Gg , Ce , Ce , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Ce , 1 Ke , Gg , Gg
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Ce , Gg , Gg
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg

View file

@ -0,0 +1,313 @@
#textdomain wesnoth
[test]
id=The_Elves_Besieged
name= _ "The Elves Besieged"
next_scenario=micro_ai_test
victory_when_enemies_defeated=no
map_data="{campaigns/Heir_To_The_Throne/maps/01_The_Elves_Besieged.map}"
turns=-1
{DEFAULT_SCHEDULE}
# Chantal can't be killed for storyline reasons.
# Prevent AIs from getting too close.
#define AVOID_CHANTAL
[avoid]
x,y=2-4,22-24
[/avoid]
#enddef
{STARTING_VILLAGES 5 10}
{STARTING_VILLAGES 6 10}
{STARTING_VILLAGES 1 3}
{STARTING_VILLAGES_AREA 6 7 10 6}
{STARTING_VILLAGES_AREA 5 17 3 1}
{STARTING_VILLAGES_AREA 6 9 40 10}
{STARTING_VILLAGES_AREA 5 26 31 5}
{STARTING_VILLAGES_AREA 5 29 46 1}
# wmllint: recognize Kalenz
[side]
type=Spearman
id=Konrad
name= _"Konrad"
unrenamable=yes
side=1
persistent=no
canrecruit=yes
controller=ai
[modifications]
{TRAIT_QUICK}
[/modifications]
recruit=Elvish Scout,Elvish Fighter,Elvish Archer,Elvish Shaman
gold=100
team_name=elves
user_team_name=_"Rebels"
[unit]
id=Delfador
name= _ "Delfador"
unrenamable=yes
type=Elder Mage
side=1
x=19
y=23
{IS_HERO}
[modifications]
{TRAIT_LOYAL}
{TRAIT_INTELLIGENT}
[/modifications]
[/unit]
{FLAG_VARIANT long}
{MICRO_AI_PROTECT_UNIT}
# Also have them target Side 2 preferentially, in order to go in that direction
[ai]
[goal]
[criteria] #NOTE: this is a SUF, because we're targeting a unit
side=3
[/criteria]
value=1000
[/goal]
[/ai]
[/side]
#macro which tells the AI not to start thinking about
#whether it's a bad idea to send trolls into the forest
#against elves, and just do it
#define EBESIEGED_RECRUITMENT
[ai]
recruitment_ignore_bad_movement=yes
recruitment_ignore_bad_combat=yes
simple_targeting=yes
[/ai]
#enddef
[side]
type=Orcish Warlord
id=Urug-Telfar
name= _ "Urug-Telfar"
side=2
persistent=no
canrecruit=yes
recruit=Orcish Warrior,Goblin Knight,Goblin Pillager,Orcish Crossbowman,Orcish Assassin,Troll
gold=400
[ai]
recruitment_pattern=scout,fighter,mixed fighter,archer
{AVOID_CHANTAL}
[/ai]
{EBESIEGED_RECRUITMENT}
team_name=orcs
user_team_name=_"Orcs"
{FLAG_VARIANT ragged}
[/side]
[side]
type=Orcish Warlord
id=Knafa-Tan
name= _ "Knafa-Tan"
side=3
persistent=no
canrecruit=yes
recruit=Orcish Warrior,Wolf Rider,Orcish Crossbowman,Orcish Assassin,Troll
{EBESIEGED_RECRUITMENT}
team_name=orcs
user_team_name=_"Orcs"
gold=200
[ai]
{AVOID_CHANTAL}
[/ai]
{FLAG_VARIANT ragged}
[/side]
[side]
type=Orcish Warlord
id=Maga-Knafa
name= _ "Maga-Knafa"
side=4
persistent=no
canrecruit=yes
recruit=Orcish Warrior,Wolf Rider,Orcish Crossbowman,Troll Warrior,Orcish Slayer
{EBESIEGED_RECRUITMENT}
team_name=orcs
user_team_name=_"Orcs"
gold=200
[ai]
{AVOID_CHANTAL}
[/ai]
{FLAG_VARIANT ragged}
[/side]
#allies of Konrad
[side]
type=Elvish Champion
id=Galdrad
name= _ "Galdrad"
side=5
persistent=no
canrecruit=yes
recruit=Elvish Fighter,Elvish Archer,Elvish Ranger
[ai]
recruitment_pattern=fighter,archer,mixed fighter
[/ai]
gold=170
team_name=elves
user_team_name=_"Rebels"
{FLAG_VARIANT long}
[/side]
[side]
type=Elvish Shyde
id=Chantal
name= _ "Chantal"
profile=portraits/chantal-shyde.png
side=6
persistent=no
canrecruit=yes
recruit=Elvish Shaman,Elvish Archer,Elvish Fighter
[ai]
passive_leader="yes"
passive_leader_shares_keep="yes"
recruitment_pattern=archer,archer,fighter,fighter,healer
[/ai]
gold=170
team_name=elves
user_team_name=_"Rebels"
{FLAG_VARIANT long}
[/side]
[side] # This side is only here because we need one persistent side for the game to go on
side=7
controller=null
persistent=yes
save_id=Grnk
hidden=yes
[/side]
[event]
name=start
{VARIABLE scenario_name The_Elves_Besieged}
[micro_ai]
# Required keys of [micro_ai] tag
side=1
ai_type=protect_unit
action=add
[unit]
id=Delfador
goal_x,goal_y=1,2
[/unit]
[unit]
id=Konrad
goal_x,goal_y=1,1
[/unit]
disable_move_leader_to_keep=true
[/micro_ai]
[message]
speaker=Konrad
message= _ "Master Delfador! Look, there are orcs coming from all directions! What shall we do?"
[/message]
[message]
speaker=Delfador
message= _ "There are too many to fight, far too many. We must escape!"
[/message]
{MESSAGE narrator "wesnoth-icon.png" "" _"This is a reenactment of scenario ""The Elves Besieged"" of the mainline campaign ""Heir to the Throne"", just that the AI is playing Konrad's side here. The goal is to move Konrad to the signpost in the northwest, while keeping both Konrad and Delfador alive. The same AI as in scenario ""Protect Unit"" is used.
Note: The Protect Unit AI is coded as a Micro AI. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."}
{PLACE_IMAGE "scenery/signpost.png" 1 1}
{SET_LABEL 1 1 _"Move Konrad here"}
[scroll_to_unit]
id=Konrad
[/scroll_to_unit]
[/event]
[event]
name=moveto
[filter]
x=1
y=1
id=Konrad
[/filter]
[message]
speaker=unit
message= _ "Very well, we have made it this far! But where do we go next?"
[/message]
[message]
speaker=Delfador
message= _ "In HttT, we would travel north now, and try to make it to the Isle of Alduin. But for this demo campaign, we'll call it good here."
[/message]
# So that game goes on to next scenario
[modify_side]
side=7
controller=human
[/modify_side]
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/event]
[event]
name=last breath
[filter]
id=Konrad
[/filter]
[message]
speaker=unit
message= _ "I... I dont think I can make it anymore."
[/message]
[message]
speaker=Delfador
message= _ "Prince... you must keep fighting! Nooooooo!"
[/message]
[message]
speaker=unit
message= _ "It is over. I am doomed..."
[/message]
[endlevel]
result=defeat
[/endlevel]
[/event]
[event]
name=last breath
[filter]
id=Delfador
[/filter]
[message]
speaker=unit
message= _ "I have... have failed in my duty to protect the prince! I am defeated."
[/message]
[message]
speaker=Konrad
message= _ "Dont die, Delfador! Please, you have to stay alive!"
[/message]
[message]
speaker=unit
message= _ "Ugh!"
[/message]
[endlevel]
result=defeat
[/endlevel]
[/event]
[event]
name=time over
[message]
speaker=Delfador
message= _ "Oh, no! We have run out of time, they have arrived with reinforcements..."
[/message]
[/event]
[/test]

View file

@ -0,0 +1,465 @@
#textdomain wesnoth
#define ANIMAL_AI_DESCRIPTIONS1
_"<u>General</u>: These AI's are set up to simulate (to some extent) how these animals behave in real life. This includes that they are animals, meaning that they are not super smart. As an example, the wolves generally hunt in a pack, but are easily distracted by prey coming into range. They are also decent, but not great at cornering deer. For the most part, this is intentional.
<u>Bears (replaced by Ghasts), Spiders and Yetis</u> mostly just wander in their respective parts of the map. They stay out of each other's way (and out of the way of the dogs), but do attack each other if cornered. They attack the other, weaker animals (deer etc.) if those are within range.
<u>Wolves</u> hunt in a pack. They actively go after the closest deer (as long as it stays in the forest) and try to corner it (not always super successfully), but are easily distracted by other prey coming into range. The wolves try to avoid getting into the range of bears, spiders, dogs and the yeti, except when they are going in for an attack. If you let them move for long enough, they will also learn that it is not healthy to attack the tusklets. When no deer is left, they wander randomly. Note that, unlike the Wolves Multipack AI (used in a different scenario), this Wolves AI combines all wolves of the side in the same pack."
#enddef
#define ANIMAL_AI_DESCRIPTIONS2
_"Each <u>Deer (replaced by Vampire Bats)</u> wanders randomly on forest tiles, except when enemies get in its (the deer's) range, in which case it flees to the farthest point it can reach.
<u>Tuskers (replaced by Ogres)</u> exhibit the same behavior as deer, except when an enemy is next to one of the tusklets. This enemy will then experience the full wrath of an irate boar.
<u>Tusklets (replaced by Young Ogres)</u> blindly follow the closest adult tusker, except when there is no tusker left, in which case they behave the same as deer.
<u>Rabbits (replaced by Rats)</u> also wander randomly, but in addition disappear into their holes (replaced by straw bales; if any are within reach) when enemies are close. They reappear out of their holes at the beginning of the turn, if it is safe.
<u>Sheep dogs (replaced by Footpads)</u> try to keep their sheep safe. This involves keeping them inside the area outlined by the path the dogs have worn into the meadow, positioning themselves in between the sheep and approaching enemies, and attacking the enemies if those get too close. You might have to let the scenario play for quite some time before you get to see an interesting dog/wolf interaction. If no active herding or protecting move is needed, the dogs go to a random location on the path.
<u>Sheep (replaced by Troll Whelps)</u> wander aimlessly except when a sheep dog is next to them, in which case they run away from the dog. The dogs exploit this by positioning themselves on the outside of the sheep, if possible. Sheep also run away from approaching enemies. The sheep, dogs and forest creatures (deer, tuskers, rabbits) have learned that they are no threat to each other and leave each other alone, demonstrating the enormous self control of a well trained sheep dog."
#enddef
[test]
id=animals
name=_"Animals"
next_scenario=micro_ai_test
map_data="{ai/micro_ais/maps/animals.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=human
hidden=no
name=Rutburt
id=Rutburt
max_moves=99
type=Outlaw
persistent=no
team_name=humans
user_team_name=_"Humans"
canrecruit=yes
recruit=Ruffian,Footpad,Thug,Poacher
gold=0
[/side]
[side]
side=2
controller=ai
no_leader=yes
persistent=no
team_name=forest
user_team_name=_"Forest Creatures"
gold=0
income=-2
{MICRO_AI_ANIMALS}
[/side]
[side]
side=3
controller=ai
no_leader=yes
persistent=no
team_name=ghasts
user_team_name=_"Ghasts"
gold=0
income=-2
{MICRO_AI_ANIMALS}
[/side]
[side]
side=4
controller=ai
no_leader=yes
persistent=no
team_name=spiders
user_team_name=_"Spiders"
gold=0
income=-2
{MICRO_AI_ANIMALS}
[/side]
[side]
side=5
controller=ai
no_leader=yes
persistent=no
team_name=yetis
user_team_name=_"Yetis"
gold=0
income=-2
{MICRO_AI_ANIMALS}
[/side]
[side]
side=6
controller=ai
no_leader=yes
persistent=no
team_name=wolves
user_team_name=_"Wolves"
gold=0
income=-2
{MICRO_AI_ANIMALS}
[/side]
[side]
side=7
controller=ai
no_leader=yes
persistent=no
team_name=whelps,forest
user_team_name=_"Whelps"
gold=0
income=-2
{MICRO_AI_ANIMALS}
[/side]
[side] # This side is only here because we need one persistent side for the game to go on
side=8
controller=null
persistent=yes
save_id=Grnk
hidden=yes
[/side]
# Put all the units and markers out there
[event]
name=prestart
{VARIABLE scenario_name animals}
{SCATTER_UNITS 3 "Vampire Bat" 1 (x,y,terrain=6-99,12-99,G*^F*) (side,random_traits=2,no)}
{NOTRAIT_UNIT 2 "Ogre" 6 20}
{NOTRAIT_UNIT 2 "Ogre" 7 21}
{NOTRAIT_UNIT 2 "Young Ogre" 7 20}
{NOTRAIT_UNIT 2 "Young Ogre" 8 21}
# Do this separately, so that there will definitely be two
{SCATTER_UNITS 1 "Ghast" 1 (x,y,terrain=1-10,1-11,G*) (side,random_traits=3,no)}
{SCATTER_UNITS 1 "Ghast" 1 (x,y,terrain=11-20,1-11,G*) (side,random_traits=3,no)}
{SCATTER_UNITS 1 "Giant Spider" 1 (x,y=51-57,1-8) (side,random_traits=4,no)}
{SCATTER_UNITS 1 "Giant Spider" 1 (x,y=36-50,1-9) (side,random_traits=4,no)}
{SCATTER_UNITS 1 "Yeti" 1 (x,y,terrain=11-25,1-9,M*) (side,random_traits=5,no)}
{NOTRAIT_UNIT 6 "Wolf" 1 33}
{NOTRAIT_UNIT 6 "Wolf" 2 32}
{NOTRAIT_UNIT 6 "Wolf" 2 33}
{NOTRAIT_UNIT 7 "Footpad" 34 24}
{NOTRAIT_UNIT 7 "Footpad" 25 32}
{NOTRAIT_UNIT 7 "Footpad" 36 31}
{NOTRAIT_UNIT 7 "Troll Whelp" 35 27}
{NOTRAIT_UNIT 7 "Troll Whelp" 29 28}
{NOTRAIT_UNIT 7 "Troll Whelp" 30 29}
{NOTRAIT_UNIT 7 "Troll Whelp" 28 30}
# Need to make sure these won't be on the border !!!!
# Other dimension are carefully chosen to give good distribution on map
# and not inside th Troll Whelp herding area
{SCATTER_IMAGE (
terrain=Gg,Gs
x,y=19-39,17-32
[not]
x,y=26-38,25-32
[/not]
) 3 items/straw-bale1.png}
{SCATTER_IMAGE (
terrain=Gg,Gs
x,y=19-39,17-32
[not]
x,y=26-38,25-32
[/not]
) 3 "items/straw-bale1.png~FL()"}
{SCATTER_IMAGE (
terrain=Gg,Gs
x,y=40-57,17-29
) 2 items/straw-bale1.png}
{SCATTER_IMAGE (
terrain=Gg,Gs
x,y=40-57,17-29
) 2 "items/straw-bale1.png~FL()"}
# The right-click menu items
[set_menu_item]
id=m01_end_animals
description=_"End scenario"
image=items/ring-red.png~CROP(26,26,20,20)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals animals}
[/show_if]
[command]
# So that game goes on to next scenario
[modify_side]
side=8
controller=human
[/modify_side]
{MESSAGE narrator "wesnoth-icon.png" "" _"Well, that was that."}
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/command]
[/set_menu_item]
[micro_ai]
side=2
ai_type=animals
action=add
animal_type=forest_animals
deer_type=Vampire Bat
rabbit_type=Giant Rat
tusker_type=Ogre
tusklet_type=Young Ogre
[wander_terrain]
terrain=*^F*
[/wander_terrain]
[/micro_ai]
# Set up the big_animals micro AI for the Ghasts
[micro_ai]
side=3
ai_type=animals
action=add
animal_type=big_animals
[big_animals]
type=Ghast
[/big_animals]
[avoid_unit]
type=Yeti,Giant Spider,Ghast,Footpad
[/avoid_unit]
[goal_terrain]
x,y=1-40,1-18
[/goal_terrain]
[wander_terrain]
terrain=*
[/wander_terrain]
[/micro_ai]
# Set up the big_animals micro AI for side 4 (the spiders)
[micro_ai]
side=4
ai_type=animals
action=add
animal_type=big_animals
[big_animals]
type=Giant Spider
[/big_animals]
[avoid_unit]
type=Yeti,Giant Spider,Ghast,Footpad
[/avoid_unit]
[goal_terrain]
terrain=H*
[/goal_terrain]
[wander_terrain]
terrain=*
[/wander_terrain]
[/micro_ai]
# Set up the big_animals micro AI for the Yeti
[micro_ai]
side=5
ai_type=animals
action=add
animal_type=big_animals
[big_animals]
type=Yeti
[/big_animals]
[avoid_unit]
type=Yeti,Giant Spider,Ghast,Footpad
[/avoid_unit]
[goal_terrain]
terrain=M*
[/goal_terrain]
[wander_terrain]
terrain=M*,M*^*,H*,H*^*
[/wander_terrain]
[/micro_ai]
# Set up the wolves micro AI
[micro_ai]
side=6
ai_type=animals
action=add
animal_type=wolves
[predators]
type=Wolf
[/predators]
[prey]
type=Vampire Bat
[/prey]
avoid_type=Yeti,Giant Spider,Ghast,Footpad
[/micro_ai]
# Set up the Troll Whelp micro AI
[micro_ai]
side=7
ai_type=animals
action=add
animal_type=herding
[herd]
type=Troll Whelp
[/herd]
[herders]
type=Footpad
[/herders]
herd_x,herd_y=32,28
[herding_perimeter]
terrain=Rb
[/herding_perimeter]
[/micro_ai]
[/event]
[event]
name=start
{MESSAGE narrator "wesnoth-icon.png" "Important Note" _"<span color='#A00000'>Important:</span> The animal Micro AIs in this scenario are written for a number of animal unit types that do not exist in Wesnoth mainline, such as bears, sheep and sheep dogs, or deer. In this test scenario, theses units have been replaced by mainline units. If you want to get the ""full experience"" with the original animal unit types, check out the 'Animals' scenario in the 'AI Modification Demos' add-on."}
{MESSAGE narrator "wesnoth-icon.png" "" _"This is a fun little scenario with a bunch of different animal AIs, mostly for watching only. The animal AIs behave as follows:
"+{ANIMAL_AI_DESCRIPTIONS1}}
{MESSAGE narrator "wesnoth-icon.png" "" {ANIMAL_AI_DESCRIPTIONS2}}
[message]
speaker=narrator
caption=_"Question for the Player"
image=wesnoth-icon.png
message=_"It is possible to include a human-controlled Side 1, so that the action stops once every turn for looking around (or to mess with things in debug mode).
Note that there is no end to this scenario. For demonstration purposes, any unit that is killed is replaced by another unit of the same type at the beginning of the next turn. In order to end the scenario, there's a right-click option - but that only works in human-controlled mode. In AI-only mode, you have to press 'Esc' or reload a previous savefile.
Also note: The Animal AIs are coded as Micro AIs. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."
[option]
message=_"<span font='16'>I'll just watch the animals.</span>"
[command]
[modify_side]
side=1
controller=null
hidden=yes
[/modify_side]
[kill]
side=1
[/kill]
[/command]
[/option]
[option]
message=_"<span font='16'>I want to have control of Side 1.</span>"
[/option]
[/message]
[objectives]
side=1
summary=_"Watch the animals do their things"
[objective]
description=_"Use right-click option"
condition=win
[/objective]
[objective]
description=_"Death of Rutburt"
condition=lose
[/objective]
[note]
description=_"Check out the right-click menu options for additional actions"
[/note]
[/objectives]
[/event]
# After first Ogre attack on wolves, wolves do not attack those any more
[event]
name=attack end
[filter]
side=2
type=Ogre
[/filter]
[filter_second]
side=6
type=Wolf
[/filter_second]
{MESSAGE $second_unit.id "" "" _"Yowl!
Translation: Those Ogres are mean! We better stay away from them and their young."}
[micro_ai]
side=6
ai_type=animals
action=change
animal_type=wolves
[predators]
type=Wolf
[/predators]
[prey]
type=Vampire Bat
[/prey]
avoid_type=Yeti,Giant Spider,Ghast,Footpad,Ogre,Young Ogre
[/micro_ai]
[/event]
# At the beginning of each side turn, put another unit one out there, if it was lost
# This is just for the sake of keeping the scenario going indefinitely
#define NEW_UNIT_SIDE_TURN SIDE TYPE COUNT X Y
[event]
name=side {SIDE} turn
first_time_only=no
[if]
[have_unit]
type={TYPE}
count={COUNT}
[/have_unit]
[then]
{NOTRAIT_UNIT {SIDE} {TYPE} {X} {Y}}
[/then]
[/if]
[/event]
#enddef
{NEW_UNIT_SIDE_TURN 2 "Vampire Bat" "0-2" 18 33}
{NEW_UNIT_SIDE_TURN 2 "Ogre" "0-1" 1 20}
{NEW_UNIT_SIDE_TURN 2 "Young Ogre" "0-1" 1 21}
{NEW_UNIT_SIDE_TURN 3 Ghast "0-1" 1 1}
{NEW_UNIT_SIDE_TURN 4 "Giant Spider" "0-1" 57 1}
{NEW_UNIT_SIDE_TURN 5 Yeti 0 15 1}
{NEW_UNIT_SIDE_TURN 6 Wolf "0-2" 1 33}
{NEW_UNIT_SIDE_TURN 7 Footpad "0-2" 30 33}
{NEW_UNIT_SIDE_TURN 7 "Troll Whelp" "0-3" 34 33}
[/test]

View file

@ -0,0 +1,204 @@
#textdomain wesnoth
[test]
id=bottleneck-defense
name=_"Bottleneck Defense"
next_scenario=micro_ai_test
map_data="{ai/micro_ais/maps/bottleneck-defense.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=ai
id=LuaAI
type=Lieutenant
persistent=no
canrecruit=yes
recruit=Spearman,Bowman
gold=125
[unit]
type=White Mage
x,y=23,6
[/unit]
{MICRO_AI_BOTTLENECK_DEFENSE}
[/side]
[side]
side=2
controller=ai
type=Orcish Leader
id=Big Bad Orc
name=_"Big Bad Orc"
persistent=no
canrecruit=yes
recruit=Orcish Archer,Orcish Grunt
gold=400
[ai]
aggression=1.0
[/ai]
[/side]
[side] # This side is only here because we need one persistent side for the game to go on
side=3
controller=null
persistent=yes
save_id=Grnk
hidden=yes
[/side]
[event]
name=prestart
{VARIABLE scenario_name bottleneck-defense}
# Set up the Bottleneck Defense Micro AI
[micro_ai]
side=1
ai_type=bottleneck_defense
action=add
x=14,14,14
y= 7, 9, 8
enemy_x=13,13
enemy_y= 8, 9
healer_x=14,14,15,15
healer_y= 7, 9, 8, 9
leadership_x=14,14,15,15
leadership_y= 7, 9, 9 ,8
active_side_leader=yes
[/micro_ai]
[/event]
[event]
name=start
{MESSAGE LuaAI "" "" _"All right, chaps. Those orcs need to be stopped."}
{STORE_UNIT_VAR (id=Big Bad Orc) profile profile}
{MESSAGE (Big Bad Orc) "$profile~FL()~RIGHT()" "" _"They there! We them get!"}
{CLEAR_VARIABLE profile}
{MESSAGE LuaAI "" "" _"We need to hold that pass for as long as we can. Let's put our strongest fighters on the front line and bring injured units to the back for healing. If we're careful enough, we might even win this battle. I'll join you as soon as I'm done recruiting and do my share of the fighting.
Note: The Bottleneck Defense AI is coded as a Micro AI. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."}
[message]
speaker=narrator
caption=_"Question for the Player"
image=wesnoth-icon.png
message=_"In this scenario, the AI playing the humans in the east is instructed to form a defensive line at the pass and hold off the orcs for as long as possible. Do you want to play the orc side or let the default (RCA) AI do that?"
[option]
message=_"<span font='16'>I'll watch the two AI's fight it out.</span>"
[/option]
[option]
message=_"<span font='16'>I'll play the orcs.</span>"
[command]
[modify_side]
side=2
controller=human
[/modify_side]
[/command]
[/option]
[/message]
[objectives]
summary=_"Take the pass"
[objective]
description=_"Defeat all humans"
condition=win
[/objective]
[objective]
description=_"Death of Big Bad Orc"
condition=lose
[/objective]
[objective]
description=_"Only one orc remains"
condition=lose
[/objective]
[/objectives]
[/event]
# White Mage has to hang out at keep for one turn
[event]
name=side 1 turn 1 refresh
{MODIFY_UNIT (type=White Mage) moves 0}
[/event]
# When leader dies: message, then keep fighting
[event]
name=last_breath
[filter]
id=LuaAI
[/filter]
[if]
[have_unit]
side=1
[/have_unit]
[then]
{MESSAGE LuaAI "" "" _"I may have fallen, but we will continue to defend the pass to the last man!"}
[/then]
[/if]
[/event]
# When the last unit on one side dies, end the scenario
[event]
name=die
first_time_only=no
[if]
[not] # If all pass defenders have died
[have_unit]
side=1
[/have_unit]
[/not]
[or] # or if all orcs (except their leader) have died
[have_unit]
side=2
count=1
[/have_unit]
[/or]
[then]
[kill]
id=$unit.id
[/kill]
# So that game goes on to next scenario
[modify_side]
side=3
controller=human
[/modify_side]
{MESSAGE narrator "wesnoth-icon.png" "" _"Well, that was that."}
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/then]
[/if]
[/event]
# Spearmen only advance to Javelineers
[event]
name=recruit
first_time_only=no
[filter]
type=Spearman
[/filter]
{MODIFY_UNIT id=$unit.id advances_to Javelineer}
[/event]
[/test]

View file

@ -0,0 +1,150 @@
#textdomain wesnoth
[test]
id=dragon
name=_"Dragon"
next_scenario=micro_ai_test
map_data="{multiplayer/maps/2p_Fallenstar_Lake.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=ai
no_leader=yes
team_name=Rowck
user_team_name=_"Rowck"
persistent=no
gold=200
{MICRO_AI_ANIMALS}
[/side]
[side]
side=2
type=Dread Bat
controller=human
id=Dreadful Bat
name=_"Dreadful Bat"
canrecruit=yes
recruit=Vampire Bat,Giant Rat,Giant Scorpion
persistent=no
gold=1000
[/side]
# Put all the units and markers out there
[event]
name=prestart
{VARIABLE scenario_name dragon}
{PLACE_IMAGE "scenery/signpost.png" 40 5}
{SET_LABEL 40 5 _"End Scenario"}
{PLACE_IMAGE "scenery/signpost.png" 3 15}
{SET_LABEL 3 15 _"Rowck's Home"}
[unit]
id=Rowck
name=_"Rowck"
unrenamable=yes
type=Fire Dragon
side=1
x,y=3,15
[/unit]
# Set up the dragon micro AI
[micro_ai]
side=1
ai_type=animals
action=add
animal_type=hunter_unit
id=Rowck
[hunting_ground]
x,y=5-30,1-15
[/hunting_ground]
home_x,home_y=3,15
rest_turns=2
show_messages=yes
[/micro_ai]
[/event]
[event]
name=start
{STORE_UNIT_VAR (id=Dreadful Bat) profile profile}
{MESSAGE (Dreadful Bat) "" "" _"Be careful to stay out of the way of that dragon. He's a mean one."}
{CLEAR_VARIABLE profile}
{MESSAGE Rowck "" "" _"Hi there. I am Rowck and here is what I do:
When hungry, I move around part of the map in a random wander until I get into range of an enemy. If enemies are within range, I attack and devour the weakest of them. After that, I retreat to my rest location, where I stay for a certain number of turns or until fully healed.
A few details (features, not bugs, but can be changed if desired):
- If my way home is blocked on the return, the normal RCA AI takes over my behavior.
- I will, however, attack any enemy occupying my rest hex, if I can get there.
- A kill only makes me go home when I am the attacker, not as defender.
- Occasionally I will not move at all while wandering (a dragon has to rest sometimes!)
Note: The Hunter AI is coded as a Micro AI. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."}
[objectives]
summary=_"Move the bats around to explore how Rowck reacts"
[objective]
description=_"Defeat Rowck"
condition=win
[/objective]
[objective]
description=_"Move the lead bat to the signpost"
condition=win
[/objective]
[objective]
description=_"Death of the bat leader"
condition=lose
[/objective]
[/objectives]
[/event]
# The events finishing the scenario
[event]
name=die
[filter]
id=Rowck
[/filter]
[fire_event]
name=end_scenario
[/fire_event]
[/event]
[event]
name=moveto
[filter]
id=Dreadful Bat
x,y=40,5
[/filter]
[fire_event]
name=end_scenario
[/fire_event]
[/event]
[event]
name=end_scenario
{MESSAGE (Dreadful Bat) "" "" _"I'm out of here."}
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/event]
[/test]

View file

@ -0,0 +1,513 @@
#textdomain wesnoth
#define HOME_GUARDIAN X Y
# Meant to be used as a suffix to a unit-generating macro call.
# The previously-generated unit will treat (X,Y) as its home.
[+unit]
[ai]
[vars]
home_loc="loc({X},{Y})"
[/vars]
[/ai]
[/unit]
#enddef
[test]
id=guardians
name=_"Guardians"
next_scenario=micro_ai_test
map_data="{/multiplayer/maps/4p_Paths_of_Daggers.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=human
id=Kraa
name=_"Kraa"
unrenamable=yes
type=Gryphon
x,y=3,5
team_name=gryphons
user_team_name=_"Gryphons"
recruit=Gryphon
persistent=no
gold=200
[/side]
# Guardians
[side]
side=2
type=Orcish Leader
id=Another Bad Orc
name=_"Another Bad Orc"
x,y=33,24
canrecruit=yes
recruit=Orcish Archer,Orcish Grunt
persistent=no
gold=30
[ai]
[modify_ai]
side=1
action=add
path=stage[main_loop].candidate_action[]
[candidate_action]
engine=fai
name=go_home
id=go_home
type=movement
evaluation="if( (null != me.vars.home_loc), {AI_CA_MOVE_TO_TARGETS_SCORE}+10, 0)"
action="if( (me.loc != me.vars.home_loc), move(me.loc, next_hop(me.loc, me.vars.home_loc)), move(me.loc, me.loc)#do not move this turn#)"
[/candidate_action]
[/modify_ai]
[/ai]
{MICRO_AI_GUARDIAN}
[/side]
# Put all the units and markers out there
[event]
name=prestart
{VARIABLE scenario_name guardians}
{PLACE_IMAGE "scenery/signpost.png" 1 6}
{SET_LABEL 1 6 _"End Scenario"}
# A couple gryphons
{GENERIC_UNIT 1 Gryphon 4 6}
{GENERIC_UNIT 1 Gryphon 5 4}
# The normal guardians
[unit]
type=Dwarvish Guardsman
side=2
id=guardian1
name=_"Guardian 1"
x,y=12,1
ai_special=guardian
[variables]
label=_"ai_special=guardian"
[/variables]
[/unit]
[unit]
type=Dwarvish Guardsman
side=2
id=guardian2
name=_"Guardian 2"
x,y=15,1
ai_special=guardian
[variables]
label=_"ai_special=guardian"
[/variables]
[/unit]
# The cowards
[unit]
type=Giant Rat
side=2
id=coward1
name=_"Coward 1"
x,y=14,21
[modifications]
{TRAIT_QUICK}
[/modifications]
[variables]
label=_"coward r=5"
[/variables]
[/unit]
[micro_ai]
side=2
ai_type=guardian_unit
action=add
guardian_type=coward
id=coward1
distance=5
[/micro_ai]
[unit]
type=Giant Rat
side=2
id=coward2
name=_"Coward 2"
x,y=16,21
[modifications]
{TRAIT_QUICK}
[/modifications]
[variables]
label=_"coward r=5
s=24 5"
[/variables]
[/unit]
[micro_ai]
side=2
ai_type=guardian_unit
action=add
guardian_type=coward
id=coward2
distance=5
seek_x,seek_y=24,5
[/micro_ai]
[unit]
type=Giant Rat
side=2
id=coward3
name=_"Coward 3"
x,y=18,21
[modifications]
{TRAIT_QUICK}
[/modifications]
[variables]
label=_" coward r=5
s=24,5 a=24,15"
[/variables]
[/unit]
[micro_ai]
side=2
ai_type=guardian_unit
action=add
guardian_type=coward
id=coward3
distance=5
seek_x,seek_y=24,5
avoid_x,avoid_y=24,15
[/micro_ai]
[unit]
type=Giant Rat
side=2
id=coward4
name=_"Coward 4"
x,y=12,21
[modifications]
{TRAIT_QUICK}
[/modifications]
[variables]
label=_"coward r=5
s=32,--"
[/variables]
[/unit]
[micro_ai]
side=2
ai_type=guardian_unit
action=add
guardian_type=coward
id=coward4
distance=4
seek_x=32
[/micro_ai]
{SET_LABEL 15 19 _"Move gryphons here to see different coward reactions"}
{PLACE_IMAGE "items/gohere.png" 13 20}
{PLACE_IMAGE "items/gohere.png" 17 20}
# The return guardians
[unit]
type=Troll
side=2
id=return1
name=_"Return Guardian 1"
x,y=20,2
[variables]
label=_"return 20,2"
[/variables]
[/unit]
[micro_ai]
side=2
ai_type=guardian_unit
action=add
guardian_type=return_guardian
id=return1
return_x,return_y=20,2
[/micro_ai]
[unit]
type=Troll Whelp
side=2
id=return2
name=_"Return Guardian 2"
x,y=21,9
[variables]
label=_"return 21,9"
[/variables]
[/unit]
[micro_ai]
side=2
ai_type=guardian_unit
action=add
guardian_type=return_guardian
id=return2
return_x,return_y=21,9
[/micro_ai]
# The home guards
[unit]
type=Troll Whelp
side=2
id=home1
name=_"Home Guard 1"
x,y=19,2
[variables]
label=_"home 19,2"
[/variables]
[/unit]
{HOME_GUARDIAN 19 2}
[unit]
type=Troll
side=2
id=home 2
name=_"Home Guard 2"
x,y=21,10
[variables]
label=_"home 21,10"
[/variables]
[/unit]
{HOME_GUARDIAN 21 10}
# The stationed guardians
[unit]
type=Skeleton Archer
side=2
id=stationed1
name=_"Stationed Guardian 1"
x,y=1,12
[variables]
label=_" stationed r=4
s=2,14 g=3,13"
[/variables]
[/unit]
[micro_ai]
side=2
ai_type=guardian_unit
action=add
guardian_type=stationed_guardian
id=stationed1
distance=4
station_x,station_y=2,14
guard_x,guard_y=3,13
[/micro_ai]
[unit]
type=Skeleton
side=2
id=stationed2
name=_"Stationed Guardian 2"
x,y=6,14
[variables]
label=_" stationed r=4
s=4,14 g=7,13"
[/variables]
[/unit]
[micro_ai]
side=2
ai_type=guardian_unit
action=add
guardian_type=stationed_guardian
id=stationed2
distance=4
station_x,station_y=4,14
guard_x,guard_y=3,13
[/micro_ai]
{SET_LABEL 3 13 _"Guarded Location"}
{SET_LABEL 2 14 _"Station 1"}
{SET_LABEL 4 14 _"Station 2"}
# Set initial label for each units
[store_unit]
[filter]
side=2
[/filter]
variable=tmp_units
kill=no
[/store_unit]
{FOREACH tmp_units i}
{SET_LABEL $tmp_units[$i].x $tmp_units[$i].y $tmp_units[$i].variables.label}
{NEXT i}
{CLEAR_VARIABLE tmp_units}
# The right-click menu items
[set_menu_item]
id=m01_guardian
description=_"Standard WML Guardian"
image=units/dwarves/guard.png~CROP(28,24,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals guardians}
[/show_if]
[command]
{MESSAGE narrator "portraits/dwarves/transparent/guard.png" _"Standard WML Guardian" _"This is the built-in WML guardian coded using 'ai_special=guardian'. These guardians attack if there is an enemy within their movement range, otherwise they do nothing (except maybe retreating to a village for healing)."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m02_return
description=_"Return Guardian"
image=units/trolls/grunt.png~CROP(31,7,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals guardians}
[/show_if]
[command]
{MESSAGE narrator "portraits/trolls/transparent/troll.png" _"Return Guardian" _"A 'return guardian' is a variation of the standard Wesnoth guardian. It has an assigned guard position (GP) to which it returns after attacks on approaching enemies:
- If at GP with no enemy in reach, do nothing.
- If at GP with enemy in reach, leave attack to default AI (note that this may include not attacking if the enemy is deemed too strong).
- If not at GP, return there, no matter whether an enemy is in reach or not.
- If enemies are blocking your way back, do your best to get there anyway.
- If you end up next to an enemy on the way back, attack after the move."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m03_home
description=_"Home Guard"
image=units/trolls/grunt.png~CROP(31,7,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals guardians}
[/show_if]
[command]
{MESSAGE narrator "portraits/trolls/transparent/troll.png" _"Home Guard" _"A 'home guard' is a variant on the 'guardian' AI special. With this variant, the unit has an assigned 'home' location, and will return there if not involved in combat and if not going to a village, whether for healing or to capture it this turn. (By contrast, the standard guardian AI will cause the unit to stay where it last attacked.) This differs from 'return guardian' in that a home guard will press the attack, possibly getting drawn quite far from 'home', rather than returning after each attack. (It can also be lured away by a string of closely-placed villages, but that is something a map builder can control.)
This also demonstrates how to combine candidate actions from Formula AI and Lua AI in one side. The home guard is written in Formula AI, while the return and stationed guardians and the cowards are written in Lua AI. In addition the non-guardian units of the side follow the default AI behavior."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m04_stationed
description=_"Stationed Guardian"
image=units/undead-skeletal/archer.png~CROP(24,16,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals guardians}
[/show_if]
[command]
{MESSAGE narrator "portraits/undead/transparent/archer.png" _"Stationed Guardian" _"A 'stationed guardian' is another variation of the standard Wesnoth guardian with a somewhat more complex behavior than that of the 'return guardian'. Two positions are defined for it, a 'station' and a 'guarded location', as well as a 'distance'. The behavior is as follows:
- If no enemy is within 'distance' of the guard's current position, do nothing.
- Otherwise: If an enemy is within 'distance' of the guard, but not also within the same distance of the guarded location and the station (all of this simultaneously), move the guard in the direction of the station.
- Otherwise:
- Pick the enemy unit that is closest to the guarded location.
- If we can reach it, pick the adjacent hex with the highest defense rating and attack from there.
- If not in reach, move toward this unit."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m05_coward
description=_"Coward"
image=units/monsters/giant-rat.png~CROP(30,30,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals guardians}
[/show_if]
[command]
{MESSAGE narrator "units/monsters/giant-rat.png" _"Coward" _"Cowards are units that, like guardians, sit around doing nothing until an enemy comes into range. Unlike guardians, however, they run away once enemies approach. Applications might be wild animals, unarmed civilians getting in the way of a battle, etc. The coward macro can be called with two optional locations, 'seek' and 'avoid':
- If neither is given, the coward retreats to the position farthest away from the approaching enemies.
- If 'seek' is given, it preferentially goes toward that location (but getting away from enemies takes priority).
- If 'avoid' is given, it in addition tries to avoid that location (with both maximizing distance from enemies and going toward 'seek' taking priority).
- Both 'seek' and 'avoid' may consist of only one coordinate ('x' or 'y'), in which case not a single hex, but a line of hexes is sought or avoided."}
[/command]
[/set_menu_item]
[/event]
[event]
name=start
{STORE_UNIT_VAR id=Kraa profile profile}
{MESSAGE Kraa "$profile~FL()~RIGHT()" "" _"Kraahhh!!!!"}
{MESSAGE (Another Bad Orc) "" "" _"They there! We them get!"}
{MESSAGE Kraa "$profile~FL()~RIGHT()" "" _"Gryphons of the High Plains, look at all these enemies. They don't behave normally. Most of them don't move at all unless we get close. Let's check out how they react to us.
Note to the player: the right-click context menu provides information about each of the units' behavior.
Another note: Most of the Guardian AIs are coded as Micro AIs. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."}
{CLEAR_VARIABLE profile}
[objectives]
summary=_"Move the Gryphons around to explore how the guardians react"
[objective]
description=_"Defeat all enemy units"
condition=win
[/objective]
[objective]
description=_"Move Kraa to the signpost"
condition=win
[/objective]
[objective]
description=_"Death of Kraa"
condition=lose
[/objective]
[note]
description=_"Check out the right-click menu options for information on each guardian type"
[/note]
[/objectives]
[/event]
# Reset the label for moving units
[event]
name=moveto
first_time_only=no
[filter]
side=2
[/filter]
{REMOVE_LABEL $x2 $y2}
{SET_LABEL $x1 $y1 $unit.variables.label}
[/event]
# The events finishing the scenario
[event]
name=die
first_time_only=no
[if]
[not]
[have_unit]
side=2
[/have_unit]
[/not]
[then]
[kill]
id=$unit.id
[/kill]
[fire_event]
name=end_scenario
[/fire_event]
[/then]
[/if]
[/event]
[event]
name=moveto
[filter]
id=Kraa
x,y=1,6
[/filter]
[fire_event]
name=end_scenario
[/fire_event]
[/event]
[event]
name=end_scenario
{MESSAGE Kraa "" "" _"Gryphons of the High Plains, it is time to return to said plains. Follow me."}
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/event]
[/test]

View file

@ -0,0 +1,113 @@
#textdomain wesnoth
[test]
id=healer_support
name=_"Healer Support"
next_scenario=micro_ai_test
map_data="{multiplayer/maps/2p_The_Freelands.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=yes
[side]
side=1
controller=ai
id=Rebels1
type=Elvish Ranger
team_name=Rebels1
user_team_name=_"Rebels 1"
recruit=Elvish Archer,Elvish Fighter,Elvish Scout,Elvish Shaman,Mage,Merman Hunter,Wose
persistent=no
gold=400
{MICRO_AI_HEALER_SUPPORT}
[/side]
[side]
side=2
controller=ai
id=Rebels2
type=Elvish Marksman
team_name=Rebels2
user_team_name=_"Rebels 2"
recruit=Elvish Archer,Elvish Fighter,Elvish Scout,Elvish Shaman,Mage,Merman Hunter,Wose
persistent=no
gold=400
{MICRO_AI_HEALER_SUPPORT}
[/side]
[side] # This side is only here because we need one persistent side for the game to go on
side=3
controller=null
persistent=yes
save_id=Grnk
hidden=yes
[/side]
# Prestart actions
[event]
name=prestart
{VARIABLE scenario_name healer_support}
# Set up the healer support micro AIs
[micro_ai]
side=1
ai_type=healer_support
action=add
injured_units_only=yes
max_threats = 0
[/micro_ai]
[micro_ai]
side=2
ai_type=healer_support
action=add
aggression=0
[/micro_ai]
[/event]
[event]
name=start
{MESSAGE Rebels1 "" "" _"In this scenario, we demonstrate the use of the Healer Support Micro AI. This AI configures the healers of a side to stay behind the battle lines and heal injured and/or threatened units rather than participate in the attacks under all circumstances. It includes several configurable options (which are set differently for the two sides in this scenario) that determine how aggressive/careful the healers are, whether they also attack, how much risk they are willing to take, etc.
For clarity, each healer announces her upcoming support move. If you don't want to see that each time, just hit 'esc' when it happens the first time.
Note: The Healer Support AI is coded as a Micro AI. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."}
[/event]
# Stop if the leader of one side died
[event]
name=die
first_time_only=no
[filter]
canrecruit=yes
[/filter]
{MESSAGE $unit.id "" "" _"Argh! They got us..."}
# So that game goes on to next scenario
[modify_side]
side=3
controller=human
[/modify_side]
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/event]
[/test]

View file

@ -0,0 +1,527 @@
#textdomain wesnoth
#define MINIMUM CONTAINER VAR
# Get the minimum value of CONTAINER.VAR
# Result is returned in variable 'minimum'
# Index is returned in min_index
# It only works for values smaller than 99999
# Seed for the minimum variable
{VARIABLE minimum 99999}
# Go through container variable
{FOREACH {CONTAINER} i_min}
{IF_VAR {CONTAINER}[$i_min].{VAR} less_than $minimum (
[then]
{VARIABLE minimum ${CONTAINER}[$i_min].{VAR}}
{VARIABLE min_index $i_min}
[/then]
)}
#{DEBUG "$i_min: $minimum (${CONTAINER}[$i_min].{VAR})"}
{NEXT i_min}
#enddef
#define LURKER_MOVES SIDE ENEMY_SIDES
# The events that move the Saurian Skirmishers each turn
# This is not left to the AI, because they can only stop on swamps, but move across other terrain
# This means they cannot be used on a human player side either
# Lurkers have no moves left each turn, instead, they are moved "manually" here
[event]
name=side {SIDE} turn refresh
first_time_only=no
# No moves each turn
{MODIFY_UNIT (side,type={SIDE},Saurian Skirmisher) moves 0}
# Store all the Lurkers of the side
[store_unit]
[filter]
side={SIDE}
type=Saurian Skirmisher
[/filter]
variable=stored_lurkers
[/store_unit]
# For each Lurker, we do:
{FOREACH stored_lurkers i_l}
#{DEBUG "Lurker $i_l"}
# Store reachable swamp locations next to an enemy
# This should include the current location
[store_reachable_locations]
[filter]
id=$stored_lurkers[$i_l].id
[/filter]
[filter_location]
terrain=S* # swamp
[not] # unoccupied by other unit
[filter]
[not]
id=$stored_lurkers[$i_l].id
[/not]
[/filter]
[/not]
[filter_adjacent_location] # next to enemy
[filter]
side={ENEMY_SIDES}
[/filter]
[/filter_adjacent_location]
[/filter_location]
moves=max
variable=stored_locs
[/store_reachable_locations]
#{DEBUG " reachable with enemy adjacent: $stored_locs.length"}
# Now find all those enemies and store
# Doesn't matter if some enemies are stored several times
{FOREACH stored_locs i_e}
[store_unit]
[filter]
side={ENEMY_SIDES}
[filter_location]
[filter_adjacent_location]
x,y=$stored_locs[$i_e].x,$stored_locs[$i_e].y
[/filter_adjacent_location]
[/filter_location]
[/filter]
variable=adj_enemies
mode=append
[/store_unit]
#{DEBUG " $i_e: enemies adjacent to current location: $adj_enemies.length"}
{NEXT i_e}
{IF_VAR adj_enemies.length equals 0 (
[then] # if there is no reachable enemy
# Simply pick a random reachable swamp hex (Lurkers are pretty stupid)
# (there is always at least the hex the unit is on)
[store_reachable_locations]
[filter]
id=$stored_lurkers[$i_l].id
[/filter]
[filter_location]
terrain=S* # swamp
[not] # unoccupied by other unit
[filter]
[not]
id=$stored_lurkers[$i_l].id
[/not]
[/filter]
[/not]
[/filter_location]
moves=max
variable=stored_locs
[/store_reachable_locations]
#{DEBUG "Reachable hexes: $stored_locs.length"}
[/then]
[else] # if there are reachable enemies
# We simply find the enemies with minimum hitpoints (Lurkers are pretty stupid)
{MINIMUM adj_enemies hitpoints}
# Unfortunately, now we need to store the reachable location again
[store_reachable_locations]
[filter]
id=$stored_lurkers[$i_l].id
[/filter]
[filter_location]
terrain=S* # swamp
[not] # unoccupied by other unit
[filter]
[not]
id=$stored_lurkers[$i_l].id
[/not]
[/filter]
[/not]
[filter_adjacent_location]
[filter]
x,y=$adj_enemies[$min_index].x,$adj_enemies[$min_index].y
[/filter]
[/filter_adjacent_location]
[/filter_location]
moves=max
variable=stored_locs
[/store_reachable_locations]
#{DEBUG " final possible reachable locations: $stored_locs.length"}
[/else]
)}
# Move unit to one of those at random
{RANDOM "0..$($stored_locs.length-1)"}
#{DEBUG "$stored_lurkers[$i_l].x $stored_lurkers[$i_l].y : $random"}
[if] # only if different from current position
{VARIABLE_CONDITIONAL stored_locs[$random].x not_equals $stored_lurkers[$i_l].x}
[or]
{VARIABLE_CONDITIONAL stored_locs[$random].y not_equals $stored_lurkers[$i_l].y}
[/or]
[then]
{MOVE_UNIT id=$stored_lurkers[$i_l].id $stored_locs[$random].x $stored_locs[$random].y}
[/then]
[/if]
# Need to clear adj_enemies inside the loop, as mode=append
{CLEAR_VARIABLE adj_enemies,minimum,min_index,random,stored_locs}
{NEXT i_l}
{CLEAR_VARIABLE stored_lurkers}
# Attack is then left to the AI
[/event]
#enddef
[test]
id=lurkers
name=_"Lurkers of the Swamp"
next_scenario=micro_ai_test
map_data="{ai/micro_ais/maps/lurkers.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=human
id=Pekzs
name=Pekzs
unrenamable=yes
type=Saurian Soothsayer
persistent=no
team_name=Pekzs
user_team_name=_"Pekzs"
recruit=Saurian Augur,Saurian Skirmisher
gold=261
[/side]
# Micro AI Lurkers
[side]
side=2
controller=ai
type=Saurian Oracle
x,y=10,18
max_moves,max_attacks=0,0
persistent=no
team_name=lurkers_mai2
user_team_name=_"Micro AI Lurkers (saurians, stationary)"
gold=0
income=-2
{MICRO_AI_LURKERS}
[/side]
# Micro AI Lurkers
[side]
side=3
controller=ai
type=Saurian Oracle
x,y=10,17
max_moves,max_attacks=0,0
persistent=no
team_name=lurkers_mai3
user_team_name=_"Micro AI Lurkers (saurians, wanderers)"
gold=0
income=-2
{MICRO_AI_LURKERS}
[/side]
# Micro AI Lurkers
[side]
side=4
controller=ai
type=Naga Warrior
x,y=11,18
max_moves,max_attacks=0,0
persistent=no
team_name=lurkers_mai4
user_team_name=_"Micro AI Lurkers (nagas)"
gold=0
income=-2
{MICRO_AI_LURKERS}
[/side]
# WML Lurkers
[side]
side=5
controller=ai
type=Saurian Oracle
x,y=12,18
max_moves,max_attacks=0,0
persistent=no
team_name=lurkers_wml
user_team_name=_"WML Lurkers (saurians)"
gold=0
income=-2
[/side]
# Formula AI Lurkers
[side]
side=6
controller=ai
type=Saurian Oracle
x,y=12,17
max_moves,max_attacks=0,0
persistent=no
team_name=lurkers_fai
user_team_name=_"Formula AI Lurkers (saurians)"
gold=0
income=-2
[ai]
version=10710
[stage]
id=main_loop
name=ai_default_rca::candidate_action_evaluation_loop
[candidate_action]
engine=fai
name=lurker_moves_fai
id=lurker_moves_fai
max_score=300000
type=movement
[filter]
me="filter(input, (input.type = 'Saurian Skirmisher'))"
[/filter]
evaluation=300000
action="{ai/micro_ais/ais/lurker_moves.fai}"
[/candidate_action]
[/stage]
[/ai]
[/side]
# Prestart - put all the lurkers out there and set up the AIs
[event]
name=prestart
{VARIABLE scenario_name lurkers}
# Place some random lurkers
{SCATTER_UNITS 3 "Saurian Skirmisher" 1 (x,terrain=1-6,S*) (side=2)}
{SCATTER_UNITS 3 "Saurian Skirmisher" 1 (x,terrain=12-19,S*) (side=3)}
{SCATTER_UNITS 3 "Naga Fighter" 1 (y,terrain=18,W*) (side=4)}
{SCATTER_UNITS 3 "Saurian Skirmisher" 1 (x,terrain=21-29,S*) (side=5)}
{SCATTER_UNITS 3 "Saurian Skirmisher" 1 (x,terrain=32-99,S*) (side=6)}
# Hide the other sides' leaders; they are only there so that side color shows up in Status menu
[hide_unit]
side=2,3,4,5,6
canrecruit=yes
[/hide_unit]
# The Micro AI lurkers
[micro_ai]
side=2
ai_type=lurkers
action=add
[lurkers]
type=Saurian Skirmisher
[/lurkers]
[attack_terrain]
terrain=S*
[/attack_terrain]
stationary=yes
[/micro_ai]
[micro_ai]
side=3
ai_type=lurkers
action=add
[lurkers]
type=Saurian Skirmisher
[/lurkers]
[attack_terrain]
terrain=S*
[/attack_terrain]
[/micro_ai]
[micro_ai]
side=4
ai_type=lurkers
action=add
[lurkers]
type=Naga Fighter
[/lurkers]
[attack_terrain]
terrain=W*,S*
[/attack_terrain]
[wander_terrain]
terrain=W*
[/wander_terrain]
[/micro_ai]
# The WML lurkers
{LURKER_MOVES 5 (1,2,3,4,6)}
{PLACE_IMAGE "scenery/signpost.png" 27 3}
{SET_LABEL 27 3 _"End Scenario"}
# Menu items to place additional lurkers by hand
[set_menu_item]
id=m01_menu_lurker2
description=_"Place a Side 2 lurker"
image=units/saurians/skirmisher/skirmisher.png~CROP(28,25,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals lurkers}
[/show_if]
[command]
{UNIT 2 (Saurian Skirmisher) $x1 $y1 ()}
[/command]
[/set_menu_item]
[set_menu_item]
id=m01_menu_lurker3
description=_"Place a Side 3 lurker"
image=units/saurians/skirmisher/skirmisher.png~CROP(28,25,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals lurkers}
[/show_if]
[command]
{UNIT 3 (Saurian Skirmisher) $x1 $y1 ()}
[/command]
[/set_menu_item]
[set_menu_item]
id=m01_menu_lurker4
description=_"Place a Side 4 lurker"
image=units/nagas/fighter.png~CROP(25,19,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals lurkers}
[/show_if]
[command]
{UNIT 4 (Naga Fighter) $x1 $y1 ()}
[/command]
[/set_menu_item]
[set_menu_item]
id=m01_menu_lurker5
description=_"Place a Side 5 lurker"
image=units/saurians/skirmisher/skirmisher.png~CROP(28,25,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals lurkers}
[/show_if]
[command]
{UNIT 5 (Saurian Skirmisher) $x1 $y1 ()}
[/command]
[/set_menu_item]
[set_menu_item]
id=m01_menu_lurker6
description=_"Place a Side 6 lurker"
image=units/saurians/skirmisher/skirmisher.png~CROP(28,25,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals lurkers}
[/show_if]
[command]
{UNIT 6 (Saurian Skirmisher) $x1 $y1 ()}
[/command]
[/set_menu_item]
[/event]
# Start
[event]
name=start
{MESSAGE Pekzs "" "" _"In this scenario we demonstrate the Lurker Micro AI. A lurker is a unit that is capable of moving across most terrains, but that only stops on and attacks from specific terrain. It might also have the ability to hide on this terrain (which is the reason why this is called the Lurker AI).
Lurkers move individually without any strategy and always attack the weakest enemy within their reach. If no enemy is in reach, the lurker does a random move instead - or it just sits and waits (lurks)."}
{MESSAGE Pekzs "" "" _"Three different lurker behaviors are set up here using the [micro_ai] tag with different parameters:
Side 2 (blue): saurians attacking only from swamp. If no enemy is in range, they do not move.
Side 3 (green): saurians attacking only from swamp. If no enemy is in range, they wander randomly (on swamp only).
Side 4 (purple): nagas wandering only on water terrain, but attacking from both water and swamp.
We also added two other sides, which demonstrate lurker behavior coded in WML (Side 5, gray) and Formula AI (Side 6, brown)."}
{MESSAGE narrator "wesnoth-icon.png" _"Notes" _"You can use the right-click context menu to add additional lurkers.
Any unit not adjacent to swamp (and water, for the nagas) is safe from the lurkers, thus it is easy to keep Pekzs from being attacked.
The Lua Lurker AI is coded as a Micro AI. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."}
[objectives]
side=1
summary=_"Watch the lurkers move around and fight them if you want"
[objective]
description=_"Defeat all lurkers"
condition=win
[/objective]
[objective]
description=_"Move Pekzs to the signpost"
condition=win
[/objective]
[objective]
description=_"Death of Pekzs"
condition=lose
[/objective]
[note]
description=_"Right-click on unoccupied swamp hexes to add more lurkers"
[/note]
[/objectives]
[/event]
# The events finishing the scenario
# 1: When all Lurkers are dead
[event]
name=die
first_time_only=no
[if]
[not]
[have_unit]
side=2,3,4,5,6
[/have_unit]
[/not]
[then]
[kill]
id=$unit.id
[/kill]
[fire_event]
name=end_scenario
[/fire_event]
[/then]
[/if]
[/event]
# 2: When Pekzs moves to the signpost
[event]
name=moveto
[filter]
id=Pekzs
x,y=27,3
[/filter]
[fire_event]
name=end_scenario
[/fire_event]
[/event]
[event]
name=end_scenario
{MESSAGE Pekzs "" "" _"Zzanksss for helping me wizz zzossse lurkerss. Hope to sssee you again ssometime."}
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/event]
[/test]

View file

@ -0,0 +1,200 @@
#textdomain wesnoth
[test]
id=messenger-escort
name=_"Messenger Escort"
next_scenario=micro_ai_test
map_data="{ai/micro_ais/maps/messenger-escort.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=human
name=_"Vanak"
id=Vanak
type=Orcish Ruler
persistent=no
team_name=Vanak
user_team_name=_"Orcs"
recruit=Orcish Grunt,Orcish Archer,Orcish Assassin,Wolf Rider
gold=100
[/side]
[side]
side=2
controller=ai
no_leader=yes
persistent=no
team_name=messenger
user_team_name=_"Messenger"
gold=0
income=-2
{MICRO_AI_MESSENGER_ESCORT}
[/side]
[side] # This side is only here because we need one persistent side for the game to go on
side=3
controller=null
persistent=yes
save_id=Grnk
hidden=yes
[/side]
# Prestart actions
[event]
name=prestart
{VARIABLE scenario_name messenger-escort}
[micro_ai]
side=2
ai_type=messenger_escort
action=add
id=messenger
waypoint_x=31,24,27,28
waypoint_y=20,14,7,1
[/micro_ai]
# Put the messenger side units out there
[unit]
type=Dragoon
id=messenger
name=_"Messenger"
side=2
x,y=30,27
random_traits=no
overlays=misc/hero-icon.png
[/unit]
{NOTRAIT_UNIT 2 Dragoon 30 26}
[unit]
type=Cavalryman
side=2
x,y=29,27
[modifications]
{TRAIT_QUICK}
[/modifications]
[/unit]
[unit]
type=Cavalryman
side=2
x,y=31,27
[modifications]
{TRAIT_QUICK}
[/modifications]
[/unit]
[unit]
type=Cavalryman
side=2
x,y=29,28
[modifications]
{TRAIT_QUICK}
[/modifications]
[/unit]
# And the orcs
{NOTRAIT_UNIT 1 (Orcish Grunt) 14 18}
{NOTRAIT_UNIT 1 (Orcish Archer) 15 19}
{NOTRAIT_UNIT 1 (Orcish Grunt) 14 17}
{NOTRAIT_UNIT 1 (Orcish Archer) 12 19}
{NOTRAIT_UNIT 1 (Orcish Archer) 12 20}
[unit]
type=Wolf Rider
side=1
x,y=10,20
[modifications]
{TRAIT_QUICK}
[/modifications]
[/unit]
# waypoints for AI
{PLACE_IMAGE "scenery/signpost.png" 31 20}
{SET_LABEL 31 20 _"Messanger Waypoint 1"}
{PLACE_IMAGE "scenery/signpost.png" 24 14}
{SET_LABEL 24 14 _"Messenger Waypoint 2"}
{PLACE_IMAGE "scenery/signpost.png" 27 7}
{SET_LABEL 27 7 _"Messenger Waypoint 3"}
# Goal signpost for AI
{PLACE_IMAGE "scenery/signpost.png" 28 1}
{SET_LABEL 28 1 _"AI moves Messenger here"}
[/event]
[event]
name=start
{STORE_UNIT_VAR id=Vanak profile profile}
{MESSAGE Vanak "$profile~FL()~RIGHT()" "" _"They there! We them get!"}
{CLEAR_VARIABLE profile}
{MESSAGE messenger "" "" _"Men, I need to get to that signpost in the north, to get the message to our leader. Let's head up there as quickly as we can."}
{MESSAGE narrator "wesnoth-icon.png" _"Notes" _"The Messenger Escort AI will try to move the dragoon messenger to the signpost in the north, while protecting him as well as possible with the other units. Vanak's orcs need to stop him.
Note that the messenger route is set up through a series of waypoints here simply to demonstrate how to use waypoints. On this map, using only a single waypoint at the end of the route would work just as well (or probably even better).
Also note that the messenger does not have to get exactly to each signpost (except for the last one), getting close is good enough.
The Messenger Escort AI is coded as a Micro AI. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."}
[objectives]
side=1
[objective]
description=_"Defeat the messenger"
condition=win
[/objective]
[objective]
description=_"Messenger gets to the signpost"
condition=lose
[/objective]
[objective]
description=_"Death of Vanak"
condition=lose
[/objective]
[/objectives]
[/event]
# When Messenger makes it to the signpost: defeat
[event]
name=moveto
[filter]
id=messenger
x,y=28,1
[/filter]
{MESSAGE messenger "" "" _"I made it! Now our people will be safe."}
[endlevel]
result=defeat
[/endlevel]
[/event]
# When Messenger dies: victory
[event]
name=last breath
[filter]
id=messenger
[/filter]
{MESSAGE messenger "" "" _"Nooo! All is lost. We will never stop the orcs now!"}
# So that game goes on to next scenario
[modify_side]
side=3
controller=human
[/modify_side]
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/event]
[/test]

View file

@ -0,0 +1,281 @@
#textdomain wesnoth
[test]
id=patrols
name=_"Patrols"
next_scenario=micro_ai_test
map_data="{multiplayer/maps/2p_Sullas_Ruins.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=human
id=Gertburt
name=_"Gertburt"
unrenamable=yes
type=Outlaw
x,y=13,8
persistent=no
team_name=bandits
user_team_name=_"Bandits"
recruit=Ruffian,Footpad,Thug,Poacher
gold=200
[/side]
# Patrol AI
[side]
side=2
controller=ai
no_leader=yes
persistent=no
team_name=Konrad
user_team_name=_"Konrad"
gold=0
income=-2
{MICRO_AI_PATROL}
[/side]
# Urudin's side
# This is taken almost literally from 'Ka'lian under Attack' in 'Legend of Wesmere'
[side]
side=3
controller=ai
no_leader=yes
persistent=no
team_name=Urudin
user_team_name=_"Urudin"
gold=0
recruit=""
[ai]
version=10703
[engine]
name=lua
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/urudin_engine.lua").init(ai)
>>
[/engine]
[stage]
id=leader_retreat
engine=lua
name=leader_retreat
#retreat on > half HP lost or turn>=3
code="(...):retreat()"
[/stage]
[stage]
name=ai_default_rca::candidate_action_evaluation_loop
id=simple_main_loop
{AI_CA_COMBAT}
{AI_CA_SIMPLE_MOVE_TO_TARGETS}
[/stage]
[/ai]
[/side]
# Put all the patrol units and labels/items out there
[event]
name=prestart
{VARIABLE scenario_name patrols}
[unit]
type=Spearman
side=2
id=Konrad
name=_"Konrad"
x,y=5,7
random_traits=no
[/unit]
[unit]
type=Longbowman
side=2
id=patrol1
random_traits=no
x,y=14,12
[/unit]
[unit]
type=Swordsman
side=2
id=patrol2
random_traits=no
x,y=17,12
[/unit]
[unit]
type="Orcish Slayer"
id=Urudin
name= _ "Urudin"
side=3
x,y=22,4
[/unit]
[micro_ai]
side=2
ai_type=patrol_unit
action=add
id=Konrad
waypoint_x=9,24,25
waypoint_y=21,23,15
one_time_only=yes
attack=Gertburt
[/micro_ai]
[micro_ai]
side=2
ai_type=patrol_unit
action=add
id=patrol1
waypoint_x=14,22,22,14,14
waypoint_y=12,12,18,19,12
out_and_back=yes
[/micro_ai]
[micro_ai]
side=2
ai_type=patrol_unit
action=add
id=patrol2
waypoint_x=14,14,22,22
waypoint_y=12,19,18,12
attack=xxxx # don't attack anybody
[/micro_ai]
{PLACE_IMAGE "scenery/signpost.png" 11 4}
{SET_LABEL 11 4 _"End Scenario"}
{PLACE_IMAGE "scenery/signpost.png" 9 21}
{SET_LABEL 9 21 _"Konrad Waypoint 1"}
{PLACE_IMAGE "scenery/signpost.png" 24 23}
{SET_LABEL 24 23 _"Konrad Waypoint 2"}
{PLACE_IMAGE "scenery/signpost.png" 25 15}
{SET_LABEL 25 15 _"Konrad Final Waypoint"}
{PLACE_IMAGE "scenery/signpost.png" 14 12}
{SET_LABEL 14 12 _"Patrol Waypoint 1"}
{PLACE_IMAGE "scenery/signpost.png" 14 19}
{SET_LABEL 14 19 _"Patrol Waypoint 2"}
{PLACE_IMAGE "scenery/signpost.png" 22 18}
{SET_LABEL 22 18 _"Patrol Waypoint 3"}
{PLACE_IMAGE "scenery/signpost.png" 22 12}
{SET_LABEL 22 12 _"Patrol Waypoint 4"}
{PLACE_IMAGE "scenery/signpost.png" 33 8}
{SET_LABEL 33 8 _"Urudin retreats here"}
[/event]
[event]
name=start
{STORE_UNIT_VAR id=Konrad profile profile}
{MESSAGE Konrad "$profile~FL()~RIGHT()" "" _"Hello! I'm a Konrad impostor. We are going to demonstrate the Patrol AI to you in this scenario.
I am heading for the keep east of the central mountain via a couple waypoints in the south. I will stay there once I get there. By contrast, those two fellas in the center are perpetually circling the mountain, one of them always in the same direction, the other changing directions after every lap.
All of this is implemented by use of the same [micro_ai] tag."}
{CLEAR_VARIABLE profile}
{MESSAGE Urudin "" "" _"And I am Urudin. I will attack my enemies for a few turns, but will retreat toward the right edge of the map if my hitpoints are below half of maximum or by Turn 5, whatever happens first.
This is an AI separate from the Patrols of Side 2."}
{MESSAGE narrator "wesnoth-icon.png" _"Notes" _"You, as the player, are in charge of Gertburt's bandits in this scenario. You can either simply watch the patrols move around, or you can move units into their way. The three patrol units are all instructed to behave differently when facing enemy units:
Konrad only attacks Gertburt, or any enemy unit that blocks his final waypoint.
The Swordsman never attacks at all.
The Longbowman attacks any enemy unit he ends up next to at the end of his move.
They all have in common, however, that getting to their next waypoint takes priority over attacking. They will thus prefer to move around enemies rather than straight for them. Also, if a waypoint is occupied by a unit they are not instructed to attack, they will (eventually) abandon that waypoint once they get close enough and move on to the next one.
The Patrol AI controlling all Side 2 units is coded as a Micro AI. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."}
[objectives]
summary=_"Watch the patrols, attack them etc."
[objective]
description=_"Defeat all enemy units"
condition=win
[/objective]
[objective]
description=_"Move Gertburt to the signpost"
condition=win
[/objective]
[objective]
description=_"Death of Gertburt"
condition=lose
[/objective]
[/objectives]
[/event]
# When Konrad gets to the end of his route, display a message
[event]
name=moveto
first_time_only=no
[filter]
id=Konrad
x,y=25,15
[/filter]
{MESSAGE Konrad "" "" _"Well, that was fun! I'll just hang out here now and watch those two guys walk and walk and ..."}
[/event]
# The events finishing the scenario
[event]
name=die
first_time_only=no
[if]
[not]
[have_unit]
side=2,3
[/have_unit]
[/not]
[then]
[kill]
id=$unit.id
[/kill]
[fire_event]
name=end_scenario
[/fire_event]
[/then]
[/if]
[/event]
[event]
name=moveto
[filter]
id=Gertburt
x,y=11,4
[/filter]
[fire_event]
name=end_scenario
[/fire_event]
[/event]
[event]
name=end_scenario
{MESSAGE Gertburt "" "" _"Let's go home, chaps."}
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/event]
[/test]

View file

@ -0,0 +1,250 @@
#textdomain wesnoth
[test]
id=protect-unit
name=_"Protect Unit"
next_scenario=micro_ai_test
map_data="{ai/micro_ais/maps/protect-unit.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=ai
name=_"Langzhar"
id=Langzhar
type=Lieutenant
persistent=no
team_name=Langzhar
user_team_name=_"Langzhar"
recruit=Spearman,Bowman
gold=200
{MICRO_AI_PROTECT_UNIT}
[/side]
[side]
side=2
controller=ai
name=_"Koorzhar"
id=Koorzhar
type=Lieutenant
persistent=no
team_name=Koorzhar
user_team_name=_"Koorzhar"
recruit=Spearman,Bowman
gold=175
[ai]
[goal]
[criteria]
id=Rossauba
[/criteria]
value=100
[/goal]
[/ai]
{ai/aliases/stable_singleplayer.cfg}
[ai]
version=10710
[engine]
name="lua"
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/priority_target_engine.lua").init(ai)
>>
[/engine]
[modify_ai]
side=2
action=add
path=stage[main_loop].candidate_action[]
[candidate_action]
engine=lua
name=change_attacks_aspect
id=change_attacks_aspect
max_score=999999
evaluation="return (...):change_attacks_aspect('Rossauba')"
execution="(...):change_attacks_aspect()"
[/candidate_action]
[/modify_ai]
[/ai]
[/side]
[side] # This side is only here because we need one persistent side for the game to go on
side=3
controller=null
persistent=yes
save_id=Grnk
hidden=yes
[/side]
# Prestart actions
[event]
name=prestart
{VARIABLE scenario_name protect-unit}
[micro_ai]
# Required keys of [micro_ai] tag
side=1
ai_type=protect_unit
action=add
[unit]
id=Rossauba
goal_x,goal_y=1,1
[/unit]
[/micro_ai]
# Put the mage out there
[unit]
type=Elder Mage
id=Rossauba
name=Rossauba
side=1
x,y=20,9
upkeep=loyal
overlays=misc/hero-icon.png
[/unit]
# Goal signpost for Rossauba
{PLACE_IMAGE "scenery/signpost.png" 1 1}
{SET_LABEL 1 1 _"Move Rossauba here"}
[/event]
[event]
name=start
{STORE_UNIT_VAR id=Koorzhar profile profile}
{MESSAGE Koorzhar "$profile~FL()~RIGHT()" "" _"There's that traitor wizard. Let's get him."}
{CLEAR_VARIABLE profile}
{MESSAGE Langzhar "" "" _"Men, you know the deal. We must protect Rossauba under all circumstances. Even my survival is not as important."}
{MESSAGE Rossauba "" "" _"That's very kind of you, but ..."}
{MESSAGE Langzhar "" "" _"No buts! You stay behind the lines and do not engage in battle unless there is no risk to your life, is that understood? And get to that signpost in the northwest if it is safe."}
[message]
speaker=narrator
caption=_"Question for the Player"
image=wesnoth-icon.png
message=_"In this scenario, the AI playing the humans in the east (Langzhar) is instructed to protect the wizard Rossauba, while moving him safely to the signpost. On the other side, Koorzhar's units (in the west) will primarily attack Rossauba, even if a better target is available. Do you want to play either of the sides or let the AI's battle it out among themselves?
Note: The Protect Unit AI is coded as a Micro AI. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."
[option]
message=_"<span font='16'>I'll watch the two AI's fight it out</span>"
[/option]
[option]
message=_"<span font='16'>I'll play Langzhar's side (to see how Koorzhar's units target Rossauba)</span>"
[command]
[modify_side]
side=1
controller=human
[/modify_side]
[/command]
[/option]
[option]
message=_"<span font='16'>I'll play Koorzhar's side (to see how Langzhar's units protect Rossauba)</span>"
[command]
[modify_side]
side=2
controller=human
[/modify_side]
[/command]
[/option]
[/message]
[objectives]
side=1
summary=_"Protect Rossauba while moving him to the signpost"
[objective]
description=_"Rossauba makes it to the signpost"
condition=win
[/objective]
[objective]
description=_"Death of Rossauba"
condition=lose
[/objective]
[objective]
description=_"Death of Langzhar"
condition=lose
[/objective]
[/objectives]
[objectives]
side=2
summary=_"Get rid of that traitor wizard Rossauba"
[objective]
description=_"Defeat Rossauba"
condition=win
[/objective]
[objective]
description=_"Rossauba makes it to the signpost"
condition=lose
[/objective]
[objective]
description=_"Death of Koorzhar"
condition=lose
[/objective]
[/objectives]
[/event]
# Delay Rossauba by one turn
[event]
name=side 1 turn 1 refresh
{MODIFY_UNIT id=Rossauba moves 0}
[/event]
# All the end scenario events involving Rossauba are treated as victories
# (since the player could play either side)
# -> only defeat: when side leaders die in human-controlled mode
# When Rossauba dies:
[event]
name=last breath
[filter]
id=Rossauba
[/filter]
{MESSAGE Rossauba "" "" _"I held out for as long as I could."}
[fire_event]
name=end_scenario
[/fire_event]
[/event]
# When Rossauba makes it to the signpost:
[event]
name=moveto
[filter]
id=Rossauba
x,y=1,1
[/filter]
{MESSAGE Rossauba "" "" _"I made it"}
[fire_event]
name=end_scenario
[/fire_event]
[/event]
[event]
name=end_scenario
# So that game goes on to next scenario
[modify_side]
side=3
controller=human
[/modify_side]
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/event]
[/test]

View file

@ -0,0 +1,121 @@
#textdomain wesnoth
[test]
id=recruiting
name=_"Recruiting"
next_scenario=micro_ai_test
map_data="{ai/micro_ais/maps/protect-unit.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=ai
name=_"Langzhar"
id=Langzhar
type=Lieutenant
persistent=no
team_name=Langzhar
user_team_name=_"Langzhar"
recruit=Peasant,Spearman,Swordsman,Bowman,Longbowman,Cavalryman,Dragoon,Heavy Infantryman,Shock Trooper,Mage,White Mage
gold=1000
{MICRO_AI_RECRUITING}
[/side]
[side]
side=2
controller=ai
name=_"Koorzhar"
id=Koorzhar
type=Lieutenant
persistent=no
team_name=Koorzhar
user_team_name=_"Koorzhar"
recruit=Peasant,Spearman,Swordsman,Bowman,Longbowman,Cavalryman,Dragoon,Heavy Infantryman,Shock Trooper,Mage,White Mage
gold=1000
[/side]
[side] # This side is only here because we need one persistent side for the game to go on
side=3
controller=null
persistent=yes
save_id=Grnk
hidden=yes
[/side]
# Prestart actions
[event]
name=prestart
{VARIABLE scenario_name recruiting}
[micro_ai]
side=1
ai_type=recruiting
action=add
recruiting_type=random
[probability]
type=Swordsman,Peasant
probability=8
[/probability]
skip_low_gold_recruiting=yes
[/micro_ai]
[/event]
[event]
name=start
{STORE_UNIT_VAR id=Koorzhar profile profile}
{MESSAGE Koorzhar "$profile~FL()~RIGHT()" "" _"This is a very simple scenario that can be used to test out different recruiting patterns."}
{CLEAR_VARIABLE profile}
{MESSAGE Langzhar "" "" _"Just watch the recruiting of both sides and see if it is what you would expect. The recruitment lists cover level 0 to level 2 units, in order to make differences more obvious."}
{MESSAGE narrator "wesnoth-icon.png" "Notes" _"If you have not changed anything, Side 1 uses the Random Recruiting AI, with swordsmen and peasants having been given higher probability than the other units. This is not meant as a good recruitment pattern, it simply serves as a demonstration how to use the AI.
Side 2 uses Wesnoth' default recruiting.
Also note that several Recruiting AIs are available as Micro AIs. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."}
[/event]
# Stop if this was the last death on this side
[event]
name=die
first_time_only=no
[if]
[not]
[have_unit]
count=1-9999
side=$unit.side
[/have_unit]
[/not]
[then]
{MESSAGE narrator "wesnoth-icon.png" "" _"Well, that was that."}
# So that game goes on to next scenario
[modify_side]
side=3
controller=human
[/modify_side]
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/then]
[/if]
[/event]
[/test]

View file

@ -0,0 +1,522 @@
#textdomain wesnoth
[test]
id=micro_ai_test
name=_"Micro AI Tests"
next_scenario=null
map_data="{multiplayer/maps/Dark_Forecast.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=human
id=Grnk
name=_"Grnk the Frail"
gender=male
unrenamable=yes
type=Goblin Spearman
max_moves=99
x,y=13,15
team_name=Grnk
user_team_name=_"Grnk"
persistent=yes
save_id=Grnk
[modifications]
{TRAIT_QUICK}
[/modifications]
village_gold=0
{GOLD 24 22 19}
income=-2 # No income whatsoever
[/side]
# The labels and signposts to go on to the next scenario
[event]
name=prestart
{PLACE_IMAGE "scenery/signpost.png" 9 13}
{SET_LABEL 9 13 _"Animals"}
{PLACE_IMAGE "scenery/signpost.png" 10 12}
{SET_LABEL 10 12 _"Wolves"}
{PLACE_IMAGE "scenery/signpost.png" 11 12}
{SET_LABEL 11 12 _"Swarm"}
{PLACE_IMAGE "scenery/signpost.png" 12 11}
{SET_LABEL 12 11 _"Dragon"}
{PLACE_IMAGE "scenery/signpost.png" 13 11}
{SET_LABEL 13 11 _"Guardians"}
{PLACE_IMAGE "scenery/signpost.png" 14 10}
{SET_LABEL 14 10 _"Lurkers"}
{PLACE_IMAGE "scenery/signpost.png" 8 13}
{SET_LABEL 8 13 _"Protect Unit"}
{PLACE_IMAGE "scenery/signpost.png" 8 14}
{SET_LABEL 8 14 _"HttT: The Elves Besieged"}
{PLACE_IMAGE "scenery/signpost.png" 8 15}
{SET_LABEL 8 15 _"Bottleneck"}
{PLACE_IMAGE "scenery/signpost.png" 8 16}
{SET_LABEL 8 16 _"Messenger"}
{PLACE_IMAGE "scenery/signpost.png" 9 17}
{SET_LABEL 9 17 _"Patrols"}
{PLACE_IMAGE "scenery/signpost.png" 10 17}
{SET_LABEL 10 17 _"Recruiting"}
{PLACE_IMAGE "scenery/signpost.png" 11 18}
{SET_LABEL 11 18 _"Healers"}
{VARIABLE scenario_name micro_ai_test}
# Menu items explaining the different scenarios
[set_menu_item]
id=m01_menu_bottleneck_defense
description=_"Bottleneck Defense Micro AI demo"
image=units/human-loyalists/lieutenant.png~CROP(16,7,36,36)~SCALE(24,24)
[filter_location]
x,y=8,15
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Bottleneck Defense Micro AI Demo" _"In the Bottleneck Defense Micro AI scenario, a small group of human soldiers is instructed to hold a pass against a large horde of orcs. You can either watch them fight it out against the standard RCA AI or take over the orc side."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m02_swamp_lurkers
description=_"Swamp Lurker Micro AI demo"
image=units/monsters/wolf.png~CROP(40,29,24,24)
[filter_location]
x,y=14,10
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Swamp Lurker Micro AI demo" _"Swamp lurkers are dumb, impulse-driven creatures which can move across most terrain, but only stop on swamp. They move individually without any strategy and always attack the weakest enemy within their reach. If no enemy is in reach, the lurker does a random move instead."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m03_guardians
description=_"Guardian Micro AI demo"
image=items/buckler.png~CROP(24,24,24,24)
[filter_location]
x,y=13,11
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Guardian Micro AI demo" _"In 'Guardians', several variations of the standard Wesnoth guardian are shown, including a ""coward"" unit that runs away from any approaching unit (an ""inverse guardian"", in a way)."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m04_patrol
description=_"Patrol Micro AI demo"
image=units/goblins/wolf-rider.png~CROP(22,11,42,42)~SCALE(24,24)
[filter_location]
x,y=9,17
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Patrol Micro AI demo" _"'Patrols' contains AI modifications for units following patrol routes."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m05_recruiting
description=_"Recruiting Tests Micro AI demo"
image=units/human-loyalists/lieutenant.png~CROP(16,7,36,36)~SCALE(24,24)
[filter_location]
x,y=10,17
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Recruiting Tests Micro AI demo" _"A simple scenario set up for the sole purpose of testing different recruiting patterns."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m05_protect
description=_"Protect Unit Micro AI demo"
image=units/human-magi/red-mage.png~CROP(22,12,24,24)
[filter_location]
x,y=8,13
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Protect Unit Micro AI Demo" _"This scenario demonstrates one side protecting a wizard while moving him to a goal location. At the same time, the other side is modified to do priority attacks on the wizard, even if a better target (by the default AI criteria) is available. You can watch the two AIs fight it out, or take control of either side to explore how the opposing AI behaves."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m05a_protect
description=_"HttT: The Elves Besieged Micro AI demo"
image=units/human-magi/red-mage.png~CROP(22,12,24,24)
[filter_location]
x,y=8,14
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"HttT: The Elves Besieged Micro AI demo" _"This is a reenactment of scenario ""The Elves Besieged"" of the mainline campaign ""Heir to the Throne"", just that the AI is playing Konrad's side here. The same algorithm as for scenario ""Protect Unit"" is used."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m06_messenger
description=_"Messenger Escort Micro AI demo"
image=units/human-loyalists/cavalryman.png~CROP(33,27,24,24)
[filter_location]
x,y=8,16
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Messenger Escort Micro AI demo" _"'Messenger Escort' has the AI actively protect a messenger while he makes his way to the edge of the map. The escort will also try to open the path for the messenger if there are enemies in the way."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m08_animals
description=_"Animals Micro AI demo"
image=units/monsters/wolf.png~CROP(40,29,24,24)
[filter_location]
x,y=9,13
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Animals Micro AI demo" _"This scenario demonstrates a number of different animals following customized AI behavior, including wolves hunting deer in packs; dogs herding sheep; bears, spiders, yetis, boar and rabbits wandering and hunting/avoiding each other."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m09_wolves
description=_"Wolves Micro AI demo"
image=units/monsters/wolf.png~CROP(40,29,24,24)
[filter_location]
x,y=10,12
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Wolves Micro AI demo" _"Another demonstration of wolves wandering and attacking in packs, with a different behavior from that in 'Animals'."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m10_swarm
description=_"Swarm Micro AI demo"
image=units/undead/bat-se-4.png~CROP(24,16,24,24)
[filter_location]
x,y=11,12
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Swarm Micro AI demo" _"This scenario features bats moving around semi-randomly in a swarm."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m11_dragon
description=_"Dragon Micro AI demo"
image=units/monsters/fire-dragon.png~CROP(102,60,48,48)~SCALE(24,24)
[filter_location]
x,y=12,11
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Dragon Micro AI demo" _"This scenario features a fire dragon displaying a hunt-and-rest behavior."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m30_healer_support
description=_"Healer support Micro AI demo"
image=units/elves-wood/shaman.png~CROP(26,19,24,24)
[filter_location]
x,y=11,18
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Healer Support Micro AI demo" _"This scenario contains a simple demonstration of setting up the Healer Support Micro AI, which uses the healers of a side to back up injured or threatened units rather than having them participate in combat under all circumstances."}
[/command]
[/set_menu_item]
[/event]
[event]
name=start
{SCROLL_TO 13 18}
{MESSAGE Grnk "" "" _"Move me to any of the signposts to go to a Micro AI demonstration.
Information about each demonstration can be accessed by right-clicking on the respective signpost."}
[objectives]
[objective]
description=_"Move Grnk to one of the signposts"
condition=win
[/objective]
[note]
description=_"Right-click on a signpost to get information about the scenario"
[/note]
[/objectives]
[/event]
# Events for going on to next scenarios, when Grnk goes to a signpost
[event]
name=moveto
[filter]
x,y=8,15
[/filter]
[endlevel]
result=victory
next_scenario=bottleneck-defense
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=14,10
[/filter]
[endlevel]
result=victory
next_scenario=lurkers
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=9,13
[/filter]
[endlevel]
result=victory
next_scenario=animals
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=10,12
[/filter]
[endlevel]
result=victory
next_scenario=wolves
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=11,12
[/filter]
[endlevel]
result=victory
next_scenario=swarm
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=12,11
[/filter]
[endlevel]
result=victory
next_scenario=dragon
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=13,11
[/filter]
[endlevel]
result=victory
next_scenario=guardians
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=9,17
[/filter]
[endlevel]
result=victory
next_scenario=patrols
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=10,17
[/filter]
[endlevel]
result=victory
next_scenario=recruiting
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=8,13
[/filter]
[endlevel]
result=victory
next_scenario=protect-unit
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=8,14
[/filter]
[endlevel]
result=victory
next_scenario=The_Elves_Besieged
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=8,16
[/filter]
[endlevel]
result=victory
next_scenario=messenger-escort
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=11,18
[/filter]
[endlevel]
result=victory
next_scenario=healer_support
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[/test]

View file

@ -0,0 +1,195 @@
#textdomain wesnoth
[test]
id=swarm
name=_"Swarm"
next_scenario=micro_ai_test
map_data="{ai/micro_ais/maps/animals.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=human
hidden=no
name=Kraa
id=Kraa
type=Gryphon
max_moves=99
side=1
persistent=no
team_name=gryphons
user_team_name=_"Gryphons"
canrecruit=yes
recruit=Ruffian,Footpad,Thug,Poacher
gold=0
[/side]
[side]
side=2
controller=ai
no_leader=yes
persistent=no
team_name=bats
user_team_name=_"Bats"
gold=0
income=-2
{MICRO_AI_ANIMALS}
[/side]
[side] # This side is only here because we need one persistent side for the game to go on
side=3
controller=null
persistent=yes
save_id=Grnk
hidden=yes
[/side]
# Put all the units and markers out there
[event]
name=prestart
{VARIABLE scenario_name swarm}
{REPEAT 12 {GENERIC_UNIT 2 "Vampire Bat" 37 26}}
# The right-click menu items
[set_menu_item]
id=m01_end_swarm
description=_"End scenario"
image=items/ring-red.png~CROP(26,26,20,20)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals swarm}
[/show_if]
[command]
{MESSAGE narrator "wesnoth-icon.png" "" _"Well, that was that."}
# So that game goes on to next scenario
[modify_side]
side=4
controller=human
[/modify_side]
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/command]
[/set_menu_item]
[set_menu_item]
id=m02_new_gryphon
description=_"Place Side 1 Gryphon"
image=units/monsters/gryphon.png~CROP(38,36,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals swarm}
[/show_if]
[command]
{UNIT 1 Gryphon $x1 $y1 (max_moves=99)}
[/command]
[/set_menu_item]
[set_menu_item]
id=m03_new_bat
description=_"Place Side 2 Bat"
image=units/undead/bat-se-4.png~CROP(24,16,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals swarm}
[/show_if]
[command]
{GENERIC_UNIT 2 (Vampire Bat) $x1 $y1}
[/command]
[/set_menu_item]
[set_menu_item]
id=m05_kill_unit
description=_"Kill Unit under Cursor"
image=items/potion-poison.png~CROP(24,29,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals swarm}
[/show_if]
[command]
[kill]
x,y=$x1,$y1
[/kill]
[/command]
[/set_menu_item]
# Set up the swarm micro AI
[micro_ai]
side=2
ai_type=animals
action=add
animal_type=swarm
[/micro_ai]
[/event]
[event]
name=start
{MESSAGE narrator "wesnoth-icon.png" "" _"This scenario features bats moving around in a swarm. Without adjacent enemies, they simply try to stay together and at a certain distance from enemies. However, if an enemy unit is close to any bat, the swarm scatters. This is particular fun to watch when one places an enemy unit in the middle of the swarm. After being scattered, the swarm members slowly rejoin, but not in a very organized way. Sub-swarms or individual bats might roam around for quite some time before they find their way back. It is also possible that individual bats (or small groups) split off from the larger swarm at times.
The player controls a side of gryphons, each of which is given 99 moves so that the reaction of the swarm to enemies can be tested easily. There are also several right-click options, for example for adding bats or gryphons or for taking units off the map.
Note: The Swarm AI is coded as a Micro AI. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."}
[objectives]
side=1
summary=_"Watch the bats move around and fight them if you want"
[objective]
description=_"Defeat all bats"
condition=win
[/objective]
[objective]
description=_"Use right-click option"
condition=win
[/objective]
[objective]
description=_"Death of all gryphons"
condition=lose
[/objective]
[note]
description=_"Check out the right-click menu options for additional actions"
[/note]
[/objectives]
[/event]
# Stop if this was the last death on this side
[event]
name=die
first_time_only=no
[if]
[not]
[have_unit]
side=$unit.side
[/have_unit]
[/not]
[then]
{MESSAGE narrator "wesnoth-icon.png" "" _"Well, that was that. If you observed any behavior that could be improved, please let us know at our forum thread: http://tinyurl.com/AI-mods"}
# So that game goes on to next scenario
[modify_side]
side=3
controller=human
[/modify_side]
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/then]
[/if]
[/event]
[/test]

View file

@ -0,0 +1,276 @@
#textdomain wesnoth
[test]
id=wolves
name=_"Wolves"
next_scenario=micro_ai_test
map_data="{ai/micro_ais/maps/animals.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=human
hidden=no
name=Rutburt
id=Rutburt
type=Outlaw
max_moves=99
side=1
persistent=no
team_name=humans
user_team_name=_"Humans"
canrecruit=yes
recruit=Ruffian,Footpad,Thug,Poacher
gold=0
[/side]
[side]
side=2
controller=ai
no_leader=yes
persistent=no
team_name=wolves
user_team_name=_"Wolves"
gold=0
income=-2
{MICRO_AI_ANIMALS}
[/side]
[side]
side=3
controller=ai
no_leader=yes
persistent=no
team_name=wolves3
user_team_name=_"More Wolves"
gold=0
income=-3
{MICRO_AI_ANIMALS}
[/side]
[side] # This side is only here because we need one persistent side for the game to go on
side=4
controller=null
persistent=yes
save_id=Grnk
hidden=yes
[/side]
# Put all the units and markers out there
[event]
name=prestart
{VARIABLE scenario_name wolves}
{SCATTER_UNITS 11 "Wolf" 1 (
x=1-30
y=1-33
) (
side=2
random_traits=yes
)}
{SCATTER_UNITS 10 "Wolf" 1 (
x=25-57
y=1-33
) (
side=3
random_traits=yes
)}
# The right-click menu items
[set_menu_item]
id=m01_end_wolves
description=_"End scenario"
image=items/ring-red.png~CROP(26,26,20,20)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals wolves}
[/show_if]
[command]
{MESSAGE narrator "wesnoth-icon.png" "" _"Well, that was that."}
# So that game goes on to next scenario
[modify_side]
side=4
controller=human
[/modify_side]
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/command]
[/set_menu_item]
[set_menu_item]
id=m02_new_peasant
description=_"Place Side 1 Peasant"
image=units/human-peasants/peasant.png~CROP(23,18,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals wolves}
[/show_if]
[command]
{GENERIC_UNIT 1 Peasant $x1 $y1}
[/command]
[/set_menu_item]
[set_menu_item]
id=m03_new_wolf2
description=_"Place Side 2 Wolf"
image=units/monsters/wolf.png~CROP(40,29,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals wolves}
[/show_if]
[command]
{GENERIC_UNIT 2 Wolf $x1 $y1}
[/command]
[/set_menu_item]
[set_menu_item]
id=m04_new_wolf3
description=_"Place Side 3 Wolf"
image=units/monsters/wolf.png~CROP(40,29,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals wolves}
[/show_if]
[command]
{GENERIC_UNIT 3 Wolf $x1 $y1}
[/command]
[/set_menu_item]
[set_menu_item]
id=m05_kill_unit
description=_"Kill Unit under Cursor"
image=items/potion-poison.png~CROP(24,29,24,24)
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals wolves}
[/show_if]
[command]
[kill]
x,y=$x1,$y1
[/kill]
[/command]
[/set_menu_item]
# Set up the wolves_multipacks micro AI for side 2
[micro_ai]
side=2
ai_type=animals
action=add
animal_type=wolves_multipacks
show_pack_number=yes
pack_size=4
[/micro_ai]
# Set up the wolves_multipacks micro AI for side 3
[micro_ai]
side=3
ai_type=animals
action=add
animal_type=wolves_multipacks
show_pack_number=yes
[/micro_ai]
[/event]
[event]
name=start
{MESSAGE narrator "wesnoth-icon.png" "" _"This scenario features a different kind of wolf behavior from 'Animals'. First, there can be an arbitrary number of wolf packs and the pack size on each side is a free parameter (set to 3 for Side 2 and 4 for Side 3 in this scenario). At the beginning of the scenario, close wolves are grouped into packs in a semi-methodical way. Wolves of the same pack begin by joining each other on the map. After that, they stay together until only one wolf is left, which then tries to join up with an incomplete pack or with other single wolves. Individual wolves entering the map during the scenario behave in that way as well.
Second, wolves do not actively hunt here. For the most part they just wander (often long distance). However, the pack ferociously (and without regard for its own health) attacks any enemy units that come into range, as long as that does not mean separating the pack by more than a few hexes. Staying together, or joining with a new wolf assigned to the pack, is the only thing that takes priority over satisfying the wolves' thirst for blood.
To emphasize which wolf belongs to which pack, the pack number will be displayed below each wolf in this scenario once the AI takes control of a side the first time."}
[message]
speaker=narrator
caption=_"Question for the Player"
image=wesnoth-icon.png
message=_"It is possible to include a human-controlled Side 1, so that the action stops once every turn for looking around (or for messing with things in debug mode). In human-controlled mode, several options are available through the right-click menu, such as adding additional wolves to either side, taking wolves off the map, adding peasants to the human-controlled side or ending the scenario. This enables easy exploring of the wolf AI behavior under different circumstances.
Note that the leader of the human-controlled side, Rutburt, can move 99 hexes per turn, so that it is always possible to keep him out of harm's way.
Also note that the wolves AI is coded as a Micro AI. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."
[option]
message=_"<span font='16'>I'll just watch the two wolf sides.</span>"
[command]
[modify_side]
side=1
controller=null
hidden=yes
[/modify_side]
[kill]
side=1
[/kill]
[/command]
[/option]
[option]
message=_"<span font='16'>I want to have control of Side 1.</span>"
[/option]
[/message]
[objectives]
side=1
summary=_"Watch the wolves move around and fight each other"
[objective]
description=_"No wolves left on one side"
condition=win
[/objective]
[objective]
description=_"Use right-click option"
condition=win
[/objective]
[objective]
description=_"Death of Rutburt"
condition=lose
[/objective]
[note]
description=_"Check out the right-click menu options for additional actions"
[/note]
[/objectives]
[/event]
# Stop if this was the last death on this side
[event]
name=die
first_time_only=no
[if]
[not]
[have_unit]
count=1-9999
side=$unit.side
[/have_unit]
[/not]
[then]
{MESSAGE narrator "wesnoth-icon.png" "" _"Well, that was that."}
# So that game goes on to next scenario
[modify_side]
side=4
controller=human
[/modify_side]
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/then]
[/if]
[/event]
[/test]

View file

@ -19,5 +19,6 @@
code=<<
wesnoth.dofile 'lua/backwards-compatibility.lua'
wesnoth.dofile 'lua/wml-tags.lua'
wesnoth.dofile 'ai/micro_ais/ais/micro_ais_wml_tags.lua'
>>
[/lua]

View file

@ -0,0 +1,199 @@
#textdomain wesnoth
#define RCA_STAGE
# The standard RCA stage with its candidate actions; same for all Micro AIs
[stage]
id=main_loop
name=ai_default_rca::candidate_action_evaluation_loop
{AI_CA_GOTO}
{AI_CA_RECRUITMENT}
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_COMBAT}
{AI_CA_HEALING}
{AI_CA_VILLAGES}
{AI_CA_RETREAT}
{AI_CA_MOVE_TO_TARGETS}
{AI_CA_PASSIVE_LEADER_SHARES_KEEP}
[/stage]
#enddef
#define MICRO_AI_HEALER_SUPPORT
# Sets up the healer support Micro AI for a side
# Include this macro in the side definition
# Needs to be in [side], does not work in [modify_side] in BfW 1.10
[ai]
id=healer_support
description="Healer Support Micro AI"
version=10710
[engine]
name="lua"
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/mai_healer_support_engine.lua").init(ai)
>>
[/engine]
{RCA_STAGE}
[/ai]
#enddef
#define MICRO_AI_BOTTLENECK_DEFENSE
# Sets up the bottleneck defense Micro AI for a side
# Include this macro in the side definition
# Needs to be in [side], does not work in [modify_side] in BfW 1.10
[ai]
id=bottleneck_defense
description="Bottleneck Defense Micro AI"
version=10710
[engine]
name="lua"
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/mai_bottleneck_defense_engine.lua").init(ai)
>>
[/engine]
{RCA_STAGE}
[/ai]
#enddef
#define MICRO_AI_MESSENGER_ESCORT
# Sets up the messenger escort Micro AI for a side
# Include this macro in the side definition
# Needs to be in [side], does not work in [modify_side] in BfW 1.10
[ai]
id=messenger_escort
description="Messenger Escort Micro AI"
version=10710
[engine]
name="lua"
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/mai_messenger_escort_engine.lua").init(ai)
>>
[/engine]
{RCA_STAGE}
[/ai]
#enddef
#define MICRO_AI_ANIMALS
# Sets up the animals Micro AIs for a side
# Include this macro in the side definition
# Needs to be in [side], does not work in [modify_side] in BfW 1.10
[ai]
id=animals
description="Animals Micro AI"
version=10710
[engine]
name="lua"
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/mai_animals_engine.lua").init(ai)
>>
[/engine]
{RCA_STAGE}
[/ai]
#enddef
#define MICRO_AI_GUARDIAN
# Sets up the guardian Micro AIs for a side
# Include this macro in the side definition
# Needs to be in [side], does not work in [modify_side] in BfW 1.10
[ai]
id=guardian
description="Guardian Micro AI"
version=10710
[engine]
name="lua"
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/mai_guardian_engine.lua").init(ai)
>>
[/engine]
{RCA_STAGE}
[/ai]
#enddef
#define MICRO_AI_PROTECT_UNIT
# Sets up the protect unit Micro AI for a side
# Include this macro in the side definition
# Needs to be in [side], does not work in [modify_side] in BfW 1.10
[ai]
id=protect_unit
description="Protect Unit Micro AI"
version=10710
[engine]
name="lua"
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/mai_protect_unit_engine.lua").init(ai)
>>
[/engine]
{RCA_STAGE}
[/ai]
#enddef
#define MICRO_AI_PATROL
# Sets up the patrol Micro AI for a side
# Include this macro in the side definition
# Needs to be in [side], does not work in [modify_side] in BfW 1.10
[ai]
id=patrol
description="Patrol Micro AI"
version=10710
[engine]
name="lua"
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/mai_patrol_engine.lua").init(ai)
>>
[/engine]
{RCA_STAGE}
[/ai]
#enddef
#define MICRO_AI_LURKERS
# Sets up the lurkers Micro AI for a side
# Include this macro in the side definition
# Needs to be in [side], does not work in [modify_side] in BfW 1.10
[ai]
id=lurkers
description="Lurkers Micro AI"
version=10710
[engine]
name="lua"
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/mai_lurkers_engine.lua").init(ai)
>>
[/engine]
{RCA_STAGE}
[/ai]
#enddef
#define MICRO_AI_RECRUITING
# Sets up the recruiting Micro AI for a side
# Include this macro in the side definition
# Needs to be in [side], does not work in [modify_side] in BfW 1.10
[ai]
id=recruiting
description="Recruiting Micro AI"
version=10710
[engine]
name="lua"
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/mai_recruit_engine.lua").init(ai)
>>
[/engine]
{RCA_STAGE}
[/ai]
#enddef