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:
parent
528aebd76f
commit
b138e47184
35 changed files with 9460 additions and 0 deletions
|
@ -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]
|
||||
|
|
48
data/ai/micro_ais/ais/lurker_moves.fai
Normal file
48
data/ai/micro_ais/ais/lurker_moves.fai
Normal 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
|
1789
data/ai/micro_ais/ais/mai_animals_engine.lua
Normal file
1789
data/ai/micro_ais/ais/mai_animals_engine.lua
Normal file
File diff suppressed because it is too large
Load diff
618
data/ai/micro_ais/ais/mai_bottleneck_defense_engine.lua
Normal file
618
data/ai/micro_ais/ais/mai_bottleneck_defense_engine.lua
Normal 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
|
||||
}
|
250
data/ai/micro_ais/ais/mai_guardian_engine.lua
Normal file
250
data/ai/micro_ais/ais/mai_guardian_engine.lua
Normal 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
|
||||
}
|
225
data/ai/micro_ais/ais/mai_healer_support_engine.lua
Normal file
225
data/ai/micro_ais/ais/mai_healer_support_engine.lua
Normal 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
|
||||
}
|
107
data/ai/micro_ais/ais/mai_lurkers_engine.lua
Executable file
107
data/ai/micro_ais/ais/mai_lurkers_engine.lua
Executable 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
|
||||
}
|
365
data/ai/micro_ais/ais/mai_messenger_escort_engine.lua
Normal file
365
data/ai/micro_ais/ais/mai_messenger_escort_engine.lua
Normal 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
|
||||
}
|
146
data/ai/micro_ais/ais/mai_patrol_engine.lua
Normal file
146
data/ai/micro_ais/ais/mai_patrol_engine.lua
Normal 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
|
||||
}
|
434
data/ai/micro_ais/ais/mai_protect_unit_engine.lua
Normal file
434
data/ai/micro_ais/ais/mai_protect_unit_engine.lua
Normal 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
|
||||
}
|
129
data/ai/micro_ais/ais/mai_recruit_engine.lua
Normal file
129
data/ai/micro_ais/ais/mai_recruit_engine.lua
Normal 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
|
||||
}
|
768
data/ai/micro_ais/ais/micro_ais_wml_tags.lua
Normal file
768
data/ai/micro_ais/ais/micro_ais_wml_tags.lua
Normal 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
|
96
data/ai/micro_ais/ais/priority_target_engine.lua
Normal file
96
data/ai/micro_ais/ais/priority_target_engine.lua
Normal 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
|
||||
}
|
22
data/ai/micro_ais/ais/urudin_engine.lua
Normal file
22
data/ai/micro_ais/ais/urudin_engine.lua
Normal 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
|
||||
}
|
38
data/ai/micro_ais/maps/animals.map
Normal file
38
data/ai/micro_ais/maps/animals.map
Normal 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
|
17
data/ai/micro_ais/maps/bottleneck-defense.map
Normal file
17
data/ai/micro_ais/maps/bottleneck-defense.map
Normal 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
|
23
data/ai/micro_ais/maps/lurkers.map
Normal file
23
data/ai/micro_ais/maps/lurkers.map
Normal 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
|
35
data/ai/micro_ais/maps/messenger-escort.map
Normal file
35
data/ai/micro_ais/maps/messenger-escort.map
Normal 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
|
19
data/ai/micro_ais/maps/protect-unit.map
Normal file
19
data/ai/micro_ais/maps/protect-unit.map
Normal 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
|
313
data/ai/micro_ais/scenarios/The_Elves_Besieged.cfg
Normal file
313
data/ai/micro_ais/scenarios/The_Elves_Besieged.cfg
Normal 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 don’t 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= _ "Don’t 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]
|
465
data/ai/micro_ais/scenarios/animals.cfg
Normal file
465
data/ai/micro_ais/scenarios/animals.cfg
Normal 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]
|
204
data/ai/micro_ais/scenarios/bottleneck-defense.cfg
Normal file
204
data/ai/micro_ais/scenarios/bottleneck-defense.cfg
Normal 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]
|
150
data/ai/micro_ais/scenarios/dragon.cfg
Normal file
150
data/ai/micro_ais/scenarios/dragon.cfg
Normal 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]
|
513
data/ai/micro_ais/scenarios/guardians.cfg
Normal file
513
data/ai/micro_ais/scenarios/guardians.cfg
Normal 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]
|
113
data/ai/micro_ais/scenarios/healer_support.cfg
Normal file
113
data/ai/micro_ais/scenarios/healer_support.cfg
Normal 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]
|
527
data/ai/micro_ais/scenarios/lurkers.cfg
Normal file
527
data/ai/micro_ais/scenarios/lurkers.cfg
Normal 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]
|
200
data/ai/micro_ais/scenarios/messenger-escort.cfg
Normal file
200
data/ai/micro_ais/scenarios/messenger-escort.cfg
Normal 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]
|
281
data/ai/micro_ais/scenarios/patrols.cfg
Normal file
281
data/ai/micro_ais/scenarios/patrols.cfg
Normal 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]
|
250
data/ai/micro_ais/scenarios/protect-unit.cfg
Normal file
250
data/ai/micro_ais/scenarios/protect-unit.cfg
Normal 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]
|
121
data/ai/micro_ais/scenarios/recruiting.cfg
Normal file
121
data/ai/micro_ais/scenarios/recruiting.cfg
Normal 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]
|
522
data/ai/micro_ais/scenarios/scenario-micro_ai.cfg
Normal file
522
data/ai/micro_ais/scenarios/scenario-micro_ai.cfg
Normal 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]
|
195
data/ai/micro_ais/scenarios/swarm.cfg
Normal file
195
data/ai/micro_ais/scenarios/swarm.cfg
Normal 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]
|
276
data/ai/micro_ais/scenarios/wolves.cfg
Normal file
276
data/ai/micro_ais/scenarios/wolves.cfg
Normal 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]
|
|
@ -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]
|
||||
|
|
199
data/core/macros/ai_micro_ais.cfg
Normal file
199
data/core/macros/ai_micro_ais.cfg
Normal 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
|
Loading…
Add table
Reference in a new issue