Merge branch 'master' of github.com:wesnoth/wesnoth-old
This commit is contained in:
commit
07037d9969
10 changed files with 361 additions and 408 deletions
|
@ -1,126 +0,0 @@
|
|||
return {
|
||||
init = function(ai, existing_engine)
|
||||
|
||||
local engine = existing_engine or {}
|
||||
|
||||
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 engine:mai_hang_out_eval(cfg)
|
||||
cfg = cfg or {}
|
||||
|
||||
-- Return 0 if the mobilize condition has previously been met
|
||||
for mobilze in H.child_range(self.data, "hangout_mobilize_units") do
|
||||
if (mobilze.id == cfg.ca_id) then
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
-- Otherwise check if any of the mobilize conditions are now met
|
||||
if (cfg.mobilize_condition and wesnoth.eval_conditional(cfg.mobilize_condition))
|
||||
or (cfg.mobilize_on_gold_less_than and (wesnoth.sides[wesnoth.current.side].gold < cfg.mobilize_on_gold_less_than))
|
||||
then
|
||||
table.insert(self.data, { "hangout_mobilize_units" , { id = cfg.ca_id } } )
|
||||
|
||||
-- Need to unmark all units also
|
||||
local units = wesnoth.get_units { side = wesnoth.current.side, { "and", cfg.filter } }
|
||||
for i,u in ipairs(units) do
|
||||
u.variables.mai_hangout_moved = nil
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
local units = wesnoth.get_units { side = wesnoth.current.side,
|
||||
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
|
||||
}
|
||||
if units[1] then
|
||||
return cfg.ca_score
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function engine:mai_hang_out_exec(cfg)
|
||||
cfg = cfg or {}
|
||||
|
||||
local units = wesnoth.get_units { side = wesnoth.current.side,
|
||||
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
|
||||
}
|
||||
--print('#unit', #units)
|
||||
|
||||
-- Get the locations close to which the units should hang out
|
||||
-- cfg.filter_location defaults to the location of the side leader(s)
|
||||
local filter_location = cfg.filter_location or {
|
||||
{ "filter", { side = wesnoth.current.side, canrecruit = "yes" } }
|
||||
}
|
||||
local width, height = wesnoth.get_map_size()
|
||||
local locs = wesnoth.get_locations {
|
||||
x = '1-' .. width,
|
||||
y = '1-' .. height,
|
||||
{ "and", filter_location }
|
||||
}
|
||||
--print('#locs', #locs)
|
||||
|
||||
-- Get map for locations to be avoided (defaults to all castle terrain)
|
||||
local avoid = cfg.avoid or { terrain = 'C*,C*^*,*^C*' }
|
||||
local avoid_map = LS.of_pairs(wesnoth.get_locations(avoid))
|
||||
|
||||
local best_hex, best_unit, max_rating = {}, {}, -9e99
|
||||
for i,u in ipairs(units) do
|
||||
-- Only consider units that have not been marked yet
|
||||
if (not u.variables.mai_hangout_moved) then
|
||||
local best_hex_unit, max_rating_unit = {}, -9e99
|
||||
|
||||
-- Check out all unoccupied hexes the unit can reach
|
||||
local reach_map = AH.get_reachable_unocc(u)
|
||||
reach_map:iter( function(x, y, v)
|
||||
if (not avoid_map:get(x, y)) then
|
||||
for k,l in ipairs(locs) do
|
||||
-- Main rating is the distance from any of the goal hexes
|
||||
local rating = -H.distance_between(x, y, l[1], l[2])
|
||||
|
||||
-- Fastest unit moves first
|
||||
rating = rating + u.max_moves / 100.
|
||||
|
||||
-- Minor penalty for distance from current position of unit
|
||||
-- so that there's not too much shuffling around
|
||||
local rating = rating - H.distance_between(x, y, u.x, u.y) / 1000.
|
||||
|
||||
if (rating > max_rating_unit) then
|
||||
max_rating_unit = rating
|
||||
best_hex_unit = {x, y}
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Only consider a unit if the best hex found for it is not its current location
|
||||
if (best_hex_unit[1] ~= u.x) or (best_hex_unit[2] ~= u.y) then
|
||||
if (max_rating_unit > max_rating) then
|
||||
max_rating = max_rating_unit
|
||||
best_hex, best_unit = best_hex_unit, u
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
--print(best_unit.id, best_unit.x, best_unit.y, best_hex[1], best_hex[2], max_rating)
|
||||
|
||||
-- If no valid locations/units were found or all units are in their
|
||||
-- respective best locations already, we take moves away from all units
|
||||
if (max_rating == -9e99) then
|
||||
for i,u in ipairs(units) do
|
||||
ai.stopunit_moves(u)
|
||||
-- Also remove the markers
|
||||
u.variables.mai_hangout_moved = nil
|
||||
end
|
||||
else
|
||||
-- Otherwise move unit and mark as having been used
|
||||
ai.move(best_unit, best_hex[1], best_hex[2])
|
||||
best_unit.variables.mai_hangout_moved = true
|
||||
end
|
||||
end
|
||||
|
||||
return engine
|
||||
end
|
||||
}
|
|
@ -1,146 +0,0 @@
|
|||
return {
|
||||
init = function(ai, existing_engine)
|
||||
|
||||
local engine = existing_engine or {}
|
||||
|
||||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
function engine:mai_patrol_eval(cfg)
|
||||
local patrol = wesnoth.get_units({ id = cfg.id })[1]
|
||||
|
||||
-- Check if unit exists as sticky BCAs are not always removed successfully
|
||||
if patrol and (patrol.moves > 0) then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
function engine:mai_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 engine
|
||||
end
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
return {
|
||||
init = function(ai, existing_engine)
|
||||
|
||||
local engine = existing_engine or {}
|
||||
|
||||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
function engine:mai_simple_attack_eval(cfg)
|
||||
|
||||
-- Find all units that can attack and match the SUF
|
||||
local units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.attacks_left > 0',
|
||||
{ "and", cfg.filter }
|
||||
}
|
||||
|
||||
-- Eliminate units without attacks
|
||||
for i = #units,1,-1 do
|
||||
if (not H.get_child(units[i].__cfg, 'attack')) then
|
||||
table.remove(units, i)
|
||||
end
|
||||
end
|
||||
--print('#units', #units)
|
||||
if (not units[1]) then return 0 end
|
||||
|
||||
-- Get all possible attacks
|
||||
local attacks = AH.get_attacks(units, { include_occupied = true })
|
||||
--print('#attacks', #attacks)
|
||||
if (not attacks[1]) then return 0 end
|
||||
|
||||
-- If cfg.filter_second is set, set up a map (location set)
|
||||
-- of enemies that it is okay to attack
|
||||
local enemy_map
|
||||
if cfg.filter_second then
|
||||
local enemies = wesnoth.get_units {
|
||||
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} },
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
--print('#enemies', #enemies)
|
||||
if (not enemies[1]) then return 0 end
|
||||
|
||||
enemy_map = LS.create()
|
||||
for i,e in ipairs(enemies) do enemy_map:insert(e.x, e.y) end
|
||||
end
|
||||
|
||||
-- Now find the best of the possible attacks
|
||||
local max_rating, best_attack = -9e99, {}
|
||||
for i, att in ipairs(attacks) do
|
||||
local valid_target = true
|
||||
if cfg.filter_second and (not enemy_map:get(att.target.x, att.target.y)) then
|
||||
valid_target = false
|
||||
end
|
||||
|
||||
if valid_target then
|
||||
local attacker = wesnoth.get_unit(att.src.x, att.src.y)
|
||||
local enemy = wesnoth.get_unit(att.target.x, att.target.y)
|
||||
local dst = { att.dst.x, att.dst.y }
|
||||
|
||||
local rating = BC.attack_rating(attacker, enemy, dst)
|
||||
--print('rating:', rating, attacker.id, enemy.id)
|
||||
|
||||
if (rating > max_rating) then
|
||||
max_rating = rating
|
||||
best_attack = att
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (max_rating > -9e99) then
|
||||
self.data.attack = best_attack
|
||||
return cfg.ca_score
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
function engine:mai_simple_attack_exec()
|
||||
local attacker = wesnoth.get_unit(self.data.attack.src.x, self.data.attack.src.y)
|
||||
local defender = wesnoth.get_unit(self.data.attack.target.x, self.data.attack.target.y)
|
||||
|
||||
AH.movefull_outofway_stopunit(ai, attacker, self.data.attack.dst.x, self.data.attack.dst.y)
|
||||
ai.attack(attacker, defender)
|
||||
self.data.attack = nil
|
||||
end
|
||||
|
||||
return engine
|
||||
end
|
||||
}
|
|
@ -431,7 +431,7 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
if (not cfg.id) then
|
||||
H.wml_error("[micro_ai] tag (patrol_unit) is missing required parameter: id")
|
||||
end
|
||||
CA_parms = { { ca_id = "mai_patrol", score = cfg.ca_score or 300000, sticky = true } }
|
||||
CA_parms = { { ca_id = "mai_patrol", location = 'ai/micro_ais/cas/ca_patrol.lua', score = cfg.ca_score or 300000 } }
|
||||
|
||||
--------- Recruiting Micro AI - side-wide AI ------------------------------------
|
||||
elseif (cfg.ai_type == 'recruiting') then
|
||||
|
@ -496,12 +496,12 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
--------- Hang Out Micro AI - side-wide AI ------------------------------------
|
||||
elseif (cfg.ai_type == 'hang_out') then
|
||||
optional_keys = { "filter", "filter_location", "avoid", "mobilize_condition", "mobilize_on_gold_less_than" }
|
||||
CA_parms = { { ca_id = 'mai_hang_out', score = cfg.ca_score or 170000 } }
|
||||
CA_parms = { { ca_id = 'mai_hang_out', location = 'ai/micro_ais/cas/ca_hang_out.lua', score = cfg.ca_score or 170000 } }
|
||||
|
||||
--------- Simple Attack Micro AI - side-wide AI ---------------------------
|
||||
elseif (cfg.ai_type == 'simple_attack') then
|
||||
optional_keys = { "filter", "filter_second" }
|
||||
CA_parms = { { ca_id = 'mai_simple_attack', score = cfg.ca_score or 110000 } }
|
||||
CA_parms = { { ca_id = 'mai_simple_attack', location = 'ai/micro_ais/cas/ca_simple_attack.lua', score = cfg.ca_score or 110000 } }
|
||||
|
||||
-- If we got here, none of the valid ai_types was specified
|
||||
else
|
||||
|
|
121
data/ai/micro_ais/cas/ca_hang_out.lua
Normal file
121
data/ai/micro_ais/cas/ca_hang_out.lua
Normal file
|
@ -0,0 +1,121 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
local ca_hang_out = {}
|
||||
|
||||
function ca_hang_out:evaluation(ai, cfg, self)
|
||||
cfg = cfg or {}
|
||||
|
||||
-- Return 0 if the mobilize condition has previously been met
|
||||
for mobilze in H.child_range(self.data, "hangout_mobilize_units") do
|
||||
if (mobilze.id == cfg.ca_id) then
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
-- Otherwise check if any of the mobilize conditions are now met
|
||||
if (cfg.mobilize_condition and wesnoth.eval_conditional(cfg.mobilize_condition))
|
||||
or (cfg.mobilize_on_gold_less_than and (wesnoth.sides[wesnoth.current.side].gold < cfg.mobilize_on_gold_less_than))
|
||||
then
|
||||
table.insert(self.data, { "hangout_mobilize_units" , { id = cfg.ca_id } } )
|
||||
|
||||
-- Need to unmark all units also
|
||||
local units = wesnoth.get_units { side = wesnoth.current.side, { "and", cfg.filter } }
|
||||
for i,u in ipairs(units) do
|
||||
u.variables.mai_hangout_moved = nil
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
local units = wesnoth.get_units { side = wesnoth.current.side,
|
||||
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
|
||||
}
|
||||
if units[1] then
|
||||
return cfg.ca_score
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function ca_hang_out:execution(ai, cfg, self)
|
||||
cfg = cfg or {}
|
||||
|
||||
local units = wesnoth.get_units { side = wesnoth.current.side,
|
||||
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
|
||||
}
|
||||
--print('#unit', #units)
|
||||
|
||||
-- Get the locations close to which the units should hang out
|
||||
-- cfg.filter_location defaults to the location of the side leader(s)
|
||||
local filter_location = cfg.filter_location or {
|
||||
{ "filter", { side = wesnoth.current.side, canrecruit = "yes" } }
|
||||
}
|
||||
local width, height = wesnoth.get_map_size()
|
||||
local locs = wesnoth.get_locations {
|
||||
x = '1-' .. width,
|
||||
y = '1-' .. height,
|
||||
{ "and", filter_location }
|
||||
}
|
||||
--print('#locs', #locs)
|
||||
|
||||
-- Get map for locations to be avoided (defaults to all castle terrain)
|
||||
local avoid = cfg.avoid or { terrain = 'C*,C*^*,*^C*' }
|
||||
local avoid_map = LS.of_pairs(wesnoth.get_locations(avoid))
|
||||
|
||||
local best_hex, best_unit, max_rating = {}, {}, -9e99
|
||||
for i,u in ipairs(units) do
|
||||
-- Only consider units that have not been marked yet
|
||||
if (not u.variables.mai_hangout_moved) then
|
||||
local best_hex_unit, max_rating_unit = {}, -9e99
|
||||
|
||||
-- Check out all unoccupied hexes the unit can reach
|
||||
local reach_map = AH.get_reachable_unocc(u)
|
||||
reach_map:iter( function(x, y, v)
|
||||
if (not avoid_map:get(x, y)) then
|
||||
for k,l in ipairs(locs) do
|
||||
-- Main rating is the distance from any of the goal hexes
|
||||
local rating = -H.distance_between(x, y, l[1], l[2])
|
||||
|
||||
-- Fastest unit moves first
|
||||
rating = rating + u.max_moves / 100.
|
||||
|
||||
-- Minor penalty for distance from current position of unit
|
||||
-- so that there's not too much shuffling around
|
||||
local rating = rating - H.distance_between(x, y, u.x, u.y) / 1000.
|
||||
|
||||
if (rating > max_rating_unit) then
|
||||
max_rating_unit = rating
|
||||
best_hex_unit = {x, y}
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Only consider a unit if the best hex found for it is not its current location
|
||||
if (best_hex_unit[1] ~= u.x) or (best_hex_unit[2] ~= u.y) then
|
||||
if (max_rating_unit > max_rating) then
|
||||
max_rating = max_rating_unit
|
||||
best_hex, best_unit = best_hex_unit, u
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
--print(best_unit.id, best_unit.x, best_unit.y, best_hex[1], best_hex[2], max_rating)
|
||||
|
||||
-- If no valid locations/units were found or all units are in their
|
||||
-- respective best locations already, we take moves away from all units
|
||||
if (max_rating == -9e99) then
|
||||
for i,u in ipairs(units) do
|
||||
ai.stopunit_moves(u)
|
||||
-- Also remove the markers
|
||||
u.variables.mai_hangout_moved = nil
|
||||
end
|
||||
else
|
||||
-- Otherwise move unit and mark as having been used
|
||||
ai.move(best_unit, best_hex[1], best_hex[2])
|
||||
best_unit.variables.mai_hangout_moved = true
|
||||
end
|
||||
end
|
||||
|
||||
return ca_hang_out
|
141
data/ai/micro_ais/cas/ca_patrol.lua
Normal file
141
data/ai/micro_ais/cas/ca_patrol.lua
Normal file
|
@ -0,0 +1,141 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
local ca_patrol = {}
|
||||
|
||||
function ca_patrol:evaluation(ai, cfg)
|
||||
local patrol = wesnoth.get_units({ id = cfg.id })[1]
|
||||
|
||||
-- Check if unit exists as sticky BCAs are not always removed successfully
|
||||
if patrol and (patrol.moves > 0) then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
function ca_patrol:execution(ai, cfg, self)
|
||||
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 ca_patrol
|
86
data/ai/micro_ais/cas/ca_simple_attack.lua
Normal file
86
data/ai/micro_ais/cas/ca_simple_attack.lua
Normal file
|
@ -0,0 +1,86 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
local ca_simple_attack = {}
|
||||
|
||||
function ca_simple_attack:evaluation(ai, cfg, self)
|
||||
|
||||
-- Find all units that can attack and match the SUF
|
||||
local units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.attacks_left > 0',
|
||||
{ "and", cfg.filter }
|
||||
}
|
||||
|
||||
-- Eliminate units without attacks
|
||||
for i = #units,1,-1 do
|
||||
if (not H.get_child(units[i].__cfg, 'attack')) then
|
||||
table.remove(units, i)
|
||||
end
|
||||
end
|
||||
--print('#units', #units)
|
||||
if (not units[1]) then return 0 end
|
||||
|
||||
-- Get all possible attacks
|
||||
local attacks = AH.get_attacks(units, { include_occupied = true })
|
||||
--print('#attacks', #attacks)
|
||||
if (not attacks[1]) then return 0 end
|
||||
|
||||
-- If cfg.filter_second is set, set up a map (location set)
|
||||
-- of enemies that it is okay to attack
|
||||
local enemy_map
|
||||
if cfg.filter_second then
|
||||
local enemies = wesnoth.get_units {
|
||||
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} },
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
--print('#enemies', #enemies)
|
||||
if (not enemies[1]) then return 0 end
|
||||
|
||||
enemy_map = LS.create()
|
||||
for i,e in ipairs(enemies) do enemy_map:insert(e.x, e.y) end
|
||||
end
|
||||
|
||||
-- Now find the best of the possible attacks
|
||||
local max_rating, best_attack = -9e99, {}
|
||||
for i, att in ipairs(attacks) do
|
||||
local valid_target = true
|
||||
if cfg.filter_second and (not enemy_map:get(att.target.x, att.target.y)) then
|
||||
valid_target = false
|
||||
end
|
||||
|
||||
if valid_target then
|
||||
local attacker = wesnoth.get_unit(att.src.x, att.src.y)
|
||||
local enemy = wesnoth.get_unit(att.target.x, att.target.y)
|
||||
local dst = { att.dst.x, att.dst.y }
|
||||
|
||||
local rating = BC.attack_rating(attacker, enemy, dst)
|
||||
--print('rating:', rating, attacker.id, enemy.id)
|
||||
|
||||
if (rating > max_rating) then
|
||||
max_rating = rating
|
||||
best_attack = att
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (max_rating > -9e99) then
|
||||
self.data.attack = best_attack
|
||||
return cfg.ca_score
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
function ca_simple_attack:execution(ai, cfg, self)
|
||||
local attacker = wesnoth.get_unit(self.data.attack.src.x, self.data.attack.src.y)
|
||||
local defender = wesnoth.get_unit(self.data.attack.target.x, self.data.attack.target.y)
|
||||
|
||||
AH.movefull_outofway_stopunit(ai, attacker, self.data.attack.dst.x, self.data.attack.dst.y)
|
||||
ai.attack(attacker, defender)
|
||||
self.data.attack = nil
|
||||
end
|
||||
|
||||
return ca_simple_attack
|
|
@ -24,21 +24,6 @@
|
|||
recruit=Footpad
|
||||
|
||||
gold=200
|
||||
|
||||
[ai]
|
||||
version=10710
|
||||
[engine]
|
||||
name="lua"
|
||||
code= <<
|
||||
local ai = ...
|
||||
local engine = {}
|
||||
engine = wesnoth.require("ai/micro_ais/ais/mai_hang_out_engine.lua").init(ai, engine)
|
||||
engine = wesnoth.require("ai/micro_ais/ais/mai_messenger_escort_engine.lua").init(ai, engine)
|
||||
return engine
|
||||
>>
|
||||
[/engine]
|
||||
{RCA_STAGE}
|
||||
[/ai]
|
||||
[/side]
|
||||
|
||||
[side]
|
||||
|
|
|
@ -41,21 +41,6 @@
|
|||
|
||||
gold=0
|
||||
income=-2
|
||||
|
||||
[ai]
|
||||
version=10710
|
||||
[engine]
|
||||
name="lua"
|
||||
code= <<
|
||||
local ai = ...
|
||||
local engine = {}
|
||||
engine = wesnoth.require("ai/micro_ais/ais/mai_patrol_engine.lua").init(ai, engine)
|
||||
engine = wesnoth.require("ai/micro_ais/ais/mai_zone_guardian_engine.lua").init(ai, engine)
|
||||
return engine
|
||||
>>
|
||||
[/engine]
|
||||
{RCA_STAGE}
|
||||
[/ai]
|
||||
[/side]
|
||||
|
||||
# Urudin's side
|
||||
|
@ -176,17 +161,17 @@
|
|||
x,y=25,27
|
||||
[/unit]
|
||||
|
||||
[micro_ai]
|
||||
side=2
|
||||
ai_type=zone_guardian
|
||||
action=add
|
||||
#[micro_ai]
|
||||
# side=2
|
||||
# ai_type=zone_guardian
|
||||
# action=add
|
||||
|
||||
id=guard1
|
||||
ca_score=299997
|
||||
[filter_location]
|
||||
x,y=10-26,26-28
|
||||
[/filter_location]
|
||||
[/micro_ai]
|
||||
# id=guard1
|
||||
# ca_score=299997
|
||||
# [filter_location]
|
||||
# x,y=10-26,26-28
|
||||
# [/filter_location]
|
||||
#[/micro_ai]
|
||||
|
||||
{PLACE_IMAGE "scenery/signpost.png" 11 4}
|
||||
{SET_LABEL 11 4 _"End Scenario"}
|
||||
|
|
|
@ -36,8 +36,6 @@
|
|||
|
||||
canrecruit=yes
|
||||
gold=10000
|
||||
|
||||
{MICRO_AI_SIMPLE_ATTACK}
|
||||
[/side]
|
||||
|
||||
[side] # This side is only here because we need one persistent side for the game to go on
|
||||
|
|
Loading…
Add table
Reference in a new issue