Merge pull request #764 from wesnoth/ai_high_xp_attack
New high_xp_attack candidate action for default AI
This commit is contained in:
commit
56d30fc82b
19 changed files with 509 additions and 52 deletions
|
@ -1,5 +1,8 @@
|
|||
Version 1.13.5+dev:
|
||||
* AI:
|
||||
* Added new high_xp_attack candidate action to default AI. This CA performs
|
||||
attacks on enemy units so close to leveling that the default AI's combat CA
|
||||
would not attack them.
|
||||
* New Micro AI: Assassin Squad AI
|
||||
* Campaigns:
|
||||
* Eastern Invasion:
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
{AI_CA_RECRUITMENT}
|
||||
{AI_CA_MOVE_LEADER_TO_GOALS}
|
||||
{AI_CA_MOVE_LEADER_TO_KEEP}
|
||||
{AI_CA_HIGH_XP_ATTACK}
|
||||
{AI_CA_COMBAT}
|
||||
{AI_CA_HEALING}
|
||||
{AI_CA_VILLAGES}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
{AI_CA_RECRUITMENT}
|
||||
{AI_CA_MOVE_LEADER_TO_GOALS}
|
||||
{AI_CA_MOVE_LEADER_TO_KEEP}
|
||||
{AI_CA_HIGH_XP_ATTACK}
|
||||
{AI_CA_COMBAT}
|
||||
{AI_CA_HEALING}
|
||||
{AI_CA_VILLAGES}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#{AI_CA_RECRUITMENT}
|
||||
{AI_CA_MOVE_LEADER_TO_GOALS}
|
||||
{AI_CA_MOVE_LEADER_TO_KEEP}
|
||||
{AI_CA_HIGH_XP_ATTACK}
|
||||
{AI_CA_COMBAT}
|
||||
{AI_CA_HEALING}
|
||||
{AI_CA_VILLAGES}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
{AI_CA_RECRUITMENT}
|
||||
{AI_CA_MOVE_LEADER_TO_GOALS}
|
||||
{AI_CA_MOVE_LEADER_TO_KEEP}
|
||||
{AI_CA_HIGH_XP_ATTACK}
|
||||
{AI_CA_COMBAT}
|
||||
{AI_CA_HEALING}
|
||||
{AI_CA_VILLAGES}
|
||||
|
|
300
data/ai/lua/ca_high_xp_attack.lua
Normal file
300
data/ai/lua/ca_high_xp_attack.lua
Normal file
|
@ -0,0 +1,300 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
-- Evaluation process:
|
||||
--
|
||||
-- Find all enemy units that could be caused to level up by an attack
|
||||
-- - If only units that would cause them to level up can attack, CA score = 100,010.
|
||||
-- This means the attack will be done before the default AI attacks, so that AI
|
||||
-- units do not get used otherwise by the default AI.
|
||||
-- - If units that would not cause a leveling can also attack, CA score = 99,990,
|
||||
-- meaning we see whether the default AI attacks that unit with one of those first.
|
||||
-- We also check whether it is possible to move an own unit out of the way
|
||||
--
|
||||
-- Attack rating:
|
||||
-- 0. If the CTD (chance to die) of the AI unit is larger than the value of
|
||||
-- aggression for the side, do not do the attack
|
||||
-- 1. Otherwise, if the attack might result in a kill, do that preferentially:
|
||||
-- rating = CTD of defender - CTD of attacker
|
||||
-- 2. Otherwise, if the enemy is poisoned, do not attack (wait for it
|
||||
-- weaken and attack on a later turn)
|
||||
-- 3. Otherwise, calculate damage done to enemy (as if it were not leveling) and
|
||||
-- own unit, expressed in partial loss of unit value (gold) and minimize both.
|
||||
-- Damage to enemy is minimized because we want to level it with the weakest AI unit,
|
||||
-- so that we can follow up with stronger units. In addition, use of poison or
|
||||
-- slow attacks is strongly discouraged. See code for exact equations.
|
||||
|
||||
local ca_attack_highxp = {}
|
||||
|
||||
function ca_attack_highxp:evaluation(cfg, data)
|
||||
-- Note: (most of) the code below is set up to maximize speed. Do not
|
||||
-- "simplify" this unless you understand exactly what that means
|
||||
|
||||
-- E.g., getting all units, plus looping over them, is much faster than using a filter
|
||||
local all_units = wesnoth.get_units()
|
||||
|
||||
local max_unit_level = 0
|
||||
local units = {}
|
||||
for _,unit in ipairs(all_units) do
|
||||
if (unit.side == wesnoth.current.side) and (unit.attacks_left > 0) and (#unit.attacks > 0) then
|
||||
table.insert(units, unit)
|
||||
|
||||
local level = wesnoth.unit_types[unit.type].level
|
||||
if (level > max_unit_level) then
|
||||
max_unit_level = level
|
||||
end
|
||||
end
|
||||
end
|
||||
if (not units[1]) then return 0 end
|
||||
|
||||
-- Mark enemies as potential targets if they are close enough to an AI unit
|
||||
-- that could trigger them leveling up; this is not a sufficient criterion,
|
||||
-- but it is much faster than path finding, so it is done for preselection.
|
||||
local target_infos = {}
|
||||
for i_t,enemy in ipairs(all_units) do
|
||||
if wesnoth.is_enemy(wesnoth.current.side, enemy.side) then
|
||||
local XP_to_levelup = enemy.max_experience - enemy.experience
|
||||
if (max_unit_level >= XP_to_levelup) then
|
||||
local potential_target = false
|
||||
local ind_attackers, ind_other_units = {}, {}
|
||||
for i_u,unit in ipairs(units) do
|
||||
if (H.distance_between(enemy.x, enemy.y, unit.x, unit.y) <= unit.moves + 1) then
|
||||
if (wesnoth.unit_types[unit.type].level >= XP_to_levelup) then
|
||||
potential_target = true
|
||||
table.insert(ind_attackers, i_u)
|
||||
else
|
||||
table.insert(ind_other_units, i_u)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if potential_target then
|
||||
local target_info = {
|
||||
ind_target = i_t,
|
||||
ind_attackers = ind_attackers,
|
||||
ind_other_units = ind_other_units
|
||||
}
|
||||
table.insert(target_infos, target_info)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if (not target_infos[1]) then return 0 end
|
||||
|
||||
-- The following location sets are used so that we at most need to call
|
||||
-- find_reach() and wesnoth.copy_unit() once per unit
|
||||
local reaches = LS.create()
|
||||
local attacker_copies = LS.create()
|
||||
|
||||
local aggression = ai.get_aggression()
|
||||
local max_ca_score, max_rating, best_attack = 0, 0
|
||||
for _,target_info in ipairs(target_infos) do
|
||||
local target = all_units[target_info.ind_target]
|
||||
local can_force_level = {}
|
||||
local attack_hexes = LS.create()
|
||||
for xa,ya in H.adjacent_tiles(target.x, target.y) do
|
||||
local unit_in_way = wesnoth.get_unit(xa, ya)
|
||||
|
||||
if unit_in_way then
|
||||
if (unit_in_way.side == wesnoth.current.side) then
|
||||
local uiw_reach
|
||||
if reaches:get(unit_in_way.x, unit_in_way.y) then
|
||||
uiw_reach = reaches:get(unit_in_way.x, unit_in_way.y)
|
||||
else
|
||||
uiw_reach = wesnoth.find_reach(unit_in_way)
|
||||
reaches:insert(unit_in_way.x, unit_in_way.y, uiw_reach)
|
||||
end
|
||||
|
||||
-- Check whether the unit to move out of the way has an unoccupied hex to move to.
|
||||
-- We do not deal with cases where a unit can move out of the way for a
|
||||
-- unit that is moving out of the way of the initial unit (etc.).
|
||||
local can_move = false
|
||||
for _,uiw_loc in ipairs(uiw_reach) do
|
||||
-- Unit in the way of the unit in the way
|
||||
local uiw_uiw = wesnoth.get_unit(uiw_loc[1], uiw_loc[2])
|
||||
if (not uiw_uiw) then
|
||||
can_move = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if (not can_move) then
|
||||
-- Keep this case as the unit in the way might be a potential attacker
|
||||
attack_hexes:insert(xa, ya, unit_in_way.id)
|
||||
else
|
||||
attack_hexes:insert(xa, ya, 'can_move_away')
|
||||
end
|
||||
end
|
||||
else
|
||||
attack_hexes:insert(xa, ya, 'empty')
|
||||
end
|
||||
end
|
||||
|
||||
attack_hexes:iter(function(xa, ya, occupied)
|
||||
for _,i_a in ipairs(target_info.ind_attackers) do
|
||||
local attacker = units[i_a]
|
||||
if (occupied == 'empty') or (occupied == 'can_move_away') then
|
||||
-- If the hex is not blocked, check all potential attackers
|
||||
local reach
|
||||
if reaches:get(attacker.x, attacker.y) then
|
||||
reach = reaches:get(attacker.x, attacker.y)
|
||||
else
|
||||
reach = wesnoth.find_reach(attacker)
|
||||
reaches:insert(attacker.x, attacker.y, reach)
|
||||
end
|
||||
|
||||
for _,loc in ipairs(reach) do
|
||||
if (loc[1] == xa) and (loc[2] == ya) then
|
||||
local tmp = {
|
||||
ind_attacker = i_a,
|
||||
dst = { x = xa, y = ya },
|
||||
src = { x = attacker.x, y = attacker.y }
|
||||
}
|
||||
table.insert(can_force_level, tmp)
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
-- If hex is blocked by own units, check whether this unit
|
||||
-- is one of the potential attackers
|
||||
if (attacker.id == occupied) then
|
||||
local tmp = {
|
||||
ind_attacker = i_a,
|
||||
dst = { x = xa, y = ya },
|
||||
src = { x = attacker.x, y = attacker.y }
|
||||
}
|
||||
|
||||
table.insert(can_force_level, tmp)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- If a leveling attack is possible, check whether any of the other
|
||||
-- units (those with too low a level to force leveling up) can get there
|
||||
local ca_score = 100010
|
||||
|
||||
attack_hexes:iter(function(xa, ya, occupied)
|
||||
if (ca_score == 100010) then -- cannot break out of the iteration with goto
|
||||
for _,i_u in ipairs(target_info.ind_other_units) do
|
||||
local unit = units[i_u]
|
||||
if (occupied == 'empty') or (occupied == 'can_move_away') then
|
||||
-- If the hex is not blocked, check if unit can get there
|
||||
local reach
|
||||
if reaches:get(unit.x, unit.y) then
|
||||
reach = reaches:get(unit.x, unit.y)
|
||||
else
|
||||
reach = wesnoth.find_reach(unit)
|
||||
reaches:insert(unit.x, unit.y, reach)
|
||||
end
|
||||
|
||||
for _,loc in ipairs(reach) do
|
||||
if (loc[1] == xa) and (loc[2] == ya) then
|
||||
ca_score = 99990
|
||||
goto found_unit
|
||||
end
|
||||
end
|
||||
else
|
||||
-- If hex is blocked by own units, check whether this unit
|
||||
-- is one of the potential attackers
|
||||
if (unit.id == occupied) then
|
||||
ca_score = 99990
|
||||
goto found_unit
|
||||
end
|
||||
end
|
||||
end
|
||||
-- It is sufficient to find one unit that can get to any attack hex
|
||||
::found_unit::
|
||||
end
|
||||
end)
|
||||
|
||||
if (ca_score >= max_ca_score) then
|
||||
for _,attack_info in ipairs(can_force_level) do
|
||||
local attacker = units[attack_info.ind_attacker]
|
||||
local attacker_copy
|
||||
if attacker_copies:get(attacker.x, attacker.y) then
|
||||
attacker_copy = attacker_copies:get(attacker.x, attacker.y)
|
||||
else
|
||||
attacker_copy = wesnoth.copy_unit(attacker)
|
||||
attacker_copies:insert(attacker.x, attacker.y, attacker_copy)
|
||||
end
|
||||
|
||||
attacker_copy.x = attack_info.dst.x
|
||||
attacker_copy.y = attack_info.dst.y
|
||||
|
||||
-- Choose the attacker that would do the *least* damage.
|
||||
-- We want the damage distribution here as if the target were not to level up
|
||||
-- the chance to die is the same in either case
|
||||
local old_experience = target.experience
|
||||
target.experience = 0
|
||||
local att_stats, def_stats, att_weapon = wesnoth.simulate_combat(attacker_copy, target)
|
||||
target.experience = old_experience
|
||||
|
||||
local rating = -1000
|
||||
if (att_stats.hp_chance[0] <= aggression) then
|
||||
if (def_stats.hp_chance[0] > 0) then
|
||||
rating = 5000 + def_stats.hp_chance[0] - att_stats.hp_chance[0]
|
||||
elseif target.status.poisoned then
|
||||
rating = -1002
|
||||
else
|
||||
rating = 1000
|
||||
|
||||
local enemy_value_loss = (target.hitpoints - def_stats.average_hp) / target.max_hitpoints
|
||||
enemy_value_loss = enemy_value_loss * wesnoth.unit_types[target.type].cost
|
||||
|
||||
-- We want the _least_ damage to the enemy, so the minus sign is no typo!
|
||||
rating = rating - enemy_value_loss
|
||||
|
||||
local own_value_loss = (attacker_copy.hitpoints - att_stats.average_hp) / attacker_copy.max_hitpoints
|
||||
own_value_loss = own_value_loss + att_stats.hp_chance[0]
|
||||
own_value_loss = own_value_loss * wesnoth.unit_types[attacker_copy.type].cost
|
||||
|
||||
rating = rating - own_value_loss
|
||||
|
||||
-- Strongly discourage poison or slow attacks
|
||||
if att_weapon.poisons or att_weapon.slows then
|
||||
rating = rating - 100
|
||||
end
|
||||
|
||||
-- Minor penalty if the attack hex is occupied
|
||||
if (attack_hexes:get(attack_info.dst.x, attack_info.dst.y) == 'can_move_away') then
|
||||
rating = rating - 0.001
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (rating > max_rating)
|
||||
or ((rating > 0) and (ca_score > max_ca_score))
|
||||
then
|
||||
max_rating = rating
|
||||
max_ca_score = ca_score
|
||||
best_attack = attack_info
|
||||
best_attack.target = { x = target.x, y = target.y }
|
||||
best_attack.ca_score = ca_score
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if best_attack then
|
||||
data.XP_attack = best_attack
|
||||
end
|
||||
|
||||
return max_ca_score
|
||||
end
|
||||
|
||||
function ca_attack_highxp:execution(cfg, data)
|
||||
local attacker = wesnoth.get_unit(data.XP_attack.src.x, data.XP_attack.src.y)
|
||||
local defender = wesnoth.get_unit(data.XP_attack.target.x, data.XP_attack.target.y)
|
||||
|
||||
AH.movefull_outofway_stopunit(ai, attacker, data.XP_attack.dst.x, data.XP_attack.dst.y)
|
||||
|
||||
if (not attacker) or (not attacker.valid) then return end
|
||||
if (not defender) or (not defender.valid) then return end
|
||||
AH.checked_attack(ai, attacker, defender)
|
||||
|
||||
data.XP_attack = nil
|
||||
end
|
||||
|
||||
return ca_attack_highxp
|
|
@ -7,7 +7,7 @@ function ca_healer_may_attack:evaluation()
|
|||
-- 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
|
||||
|
||||
local score = 99990
|
||||
local score = 99900
|
||||
return score
|
||||
end
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ function ca_return_guardian:evaluation(cfg)
|
|||
local guardian = get_guardian(cfg)
|
||||
if guardian then
|
||||
if (guardian.x == cfg.return_x) and (guardian.y == cfg.return_y) then
|
||||
return cfg.ca_score - 20
|
||||
return cfg.ca_score - 200
|
||||
else
|
||||
return cfg.ca_score
|
||||
end
|
||||
|
|
|
@ -71,6 +71,11 @@ function wesnoth.micro_ais.fast_ai(cfg)
|
|||
}
|
||||
else
|
||||
if (not cfg.skip_combat_ca) then
|
||||
W.modify_ai {
|
||||
side = cfg.side,
|
||||
action = "try_delete",
|
||||
path = "stage[main_loop].candidate_action[high_xp_attack]"
|
||||
}
|
||||
W.modify_ai {
|
||||
side = cfg.side,
|
||||
action = "try_delete",
|
||||
|
|
|
@ -34,7 +34,7 @@ function wesnoth.micro_ais.return_guardian(cfg)
|
|||
local optional_keys = { "id", "[filter]" }
|
||||
local CA_parms = {
|
||||
ai_id = 'mai_return_guardian',
|
||||
{ ca_id = 'move', location = 'ca_return_guardian.lua', score = cfg.ca_score or 100010 }
|
||||
{ ca_id = 'move', location = 'ca_return_guardian.lua', score = cfg.ca_score or 100100 }
|
||||
}
|
||||
return required_keys, optional_keys, CA_parms
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ function wesnoth.micro_ais.healer_support(cfg)
|
|||
-- The healers_can_attack CA is only added to the table if aggression ~= 0
|
||||
-- But: make sure we always try removal
|
||||
if (cfg.action == 'delete') or (tonumber(cfg.aggression) ~= 0) then
|
||||
table.insert(CA_parms, { ca_id = 'may_attack', location = 'ca_healer_may_attack.lua', score = 99990 })
|
||||
table.insert(CA_parms, { ca_id = 'may_attack', location = 'ca_healer_may_attack.lua', score = 99900 })
|
||||
end
|
||||
return {}, optional_keys, CA_parms
|
||||
end
|
|
@ -71,6 +71,7 @@ Gs^Fp , Gs^Fp , Wwf , Wwf , Mm , Rd
|
|||
{AI_CA_GOTO}
|
||||
{AI_CA_MOVE_LEADER_TO_GOALS}
|
||||
{AI_CA_MOVE_LEADER_TO_KEEP}
|
||||
{AI_CA_HIGH_XP_ATTACK}
|
||||
{AI_CA_COMBAT}
|
||||
{AI_CA_HEALING}
|
||||
{AI_CA_VILLAGES}
|
||||
|
|
174
data/ai/scenarios/scenario-high_xp_attack.cfg
Normal file
174
data/ai/scenarios/scenario-high_xp_attack.cfg
Normal file
|
@ -0,0 +1,174 @@
|
|||
#textdomain wesnoth-ai
|
||||
|
||||
[test]
|
||||
id=high_xp_attack
|
||||
name="High XP Attack"
|
||||
|
||||
map_data="{campaigns/The_Hammer_of_Thursagan/maps/12_The_Underlevels.map}"
|
||||
{DEFAULT_SCHEDULE}
|
||||
turns=-1
|
||||
victory_when_enemies_defeated=yes
|
||||
|
||||
[side]
|
||||
side=1
|
||||
controller=human
|
||||
id=player
|
||||
name="Sly Player"
|
||||
type=Necromancer
|
||||
x,y=17,58
|
||||
persistent=no
|
||||
facing=sw
|
||||
|
||||
team_name=player
|
||||
user_team_name="Sly Player"
|
||||
recruit=Skeleton,Skeleton Archer
|
||||
|
||||
gold=0
|
||||
[/side]
|
||||
|
||||
[side]
|
||||
type=Dwarvish Steelclad
|
||||
id=dwarf
|
||||
name="Fearless AI Leader"
|
||||
side=2
|
||||
x,y=8,53
|
||||
facing=se
|
||||
|
||||
team_name=dwarves
|
||||
user_team_name="Fearless AI"
|
||||
recruit=Dwarvish Fighter,Dwarvish Scout,Dwarvish Thunderer
|
||||
|
||||
gold=0
|
||||
[/side]
|
||||
|
||||
# Prestart actions
|
||||
[event]
|
||||
name=prestart
|
||||
|
||||
[terrain]
|
||||
x,y=21,55
|
||||
terrain=Rd
|
||||
[/terrain]
|
||||
|
||||
{UNIT 1 (Skeleton) 6 58 (random_traits,experience=no,33)}
|
||||
{UNIT 2 (Dwarvish Scout) 6 56 (random_traits=no)}
|
||||
{UNIT 2 (Dwarvish Scout) 4 58 (random_traits=no)}
|
||||
{UNIT 2 (Dwarvish Fighter) 8 59 (random_traits=no)}
|
||||
|
||||
{UNIT 1 (Skeleton) 23 55 (random_traits,experience,hitpoints=no,34,10)}
|
||||
{UNIT 2 (Dwarvish Scout) 27 54 (random_traits=no)}
|
||||
|
||||
# Groups of units with enemy 2 XP from leveling
|
||||
{UNIT 1 (Revenant) 8 29 (random_traits,experience=no,83)}
|
||||
{UNIT 2 (Dwarvish Scout) 5 29 (random_traits=no)}
|
||||
{UNIT 2 (Dwarvish Scout) 5 30 (random_traits=no)}
|
||||
{UNIT 2 (Dwarvish Steelclad) 6 29 (random_traits=no)}
|
||||
|
||||
{UNIT 1 (Revenant) 18 29 (random_traits,experience=no,83)}
|
||||
{UNIT 2 (Dwarvish Fighter) 21 29 (random_traits,hitpoints=no,2)}
|
||||
{UNIT 2 (Dwarvish Fighter) 21 30 (random_traits,hitpoints=no,2)}
|
||||
{UNIT 2 (Dwarvish Runesmith) 20 29 (random_traits=no)}
|
||||
|
||||
# Move-out-of-way triple
|
||||
{UNIT 1 (Skeleton) 20 54 (random_traits,experience=no,34)}
|
||||
{UNIT 2 (Dwarvish Runesmith) 19 54 (random_traits=no)}
|
||||
{UNIT 2 (Dwarvish Scout) 14 51 (random_traits=no)}
|
||||
|
||||
# Revenant - Fighter pairs
|
||||
{UNIT 1 (Revenant) 32 41 (random_traits,experience=no,84)}
|
||||
{UNIT 1 (Revenant) 46 41 (random_traits,experience,hitpoints=no,84,25)}
|
||||
{UNIT 2 (Dwarvish Fighter) 35 40 (random_traits,hitpoints=no,7)}
|
||||
{UNIT 2 (Dwarvish Fighter) 43 40 (random_traits,hitpoints=no,18)}
|
||||
|
||||
# Poisoned enemies
|
||||
{UNIT 1 (Ghoul) 52 55 (random_traits,experience,hitpoints=no,34,16)}
|
||||
[+unit]
|
||||
[status]
|
||||
poisoned=yes
|
||||
[/status]
|
||||
[/unit]
|
||||
{UNIT 1 (Ghoul) 56 55 (random_traits,experience,hitpoints=no,34,24)}
|
||||
[+unit]
|
||||
[status]
|
||||
poisoned=yes
|
||||
[/status]
|
||||
[/unit]
|
||||
{UNIT 2 (Dwarvish Fighter) 54 54 (random_traits=no)}
|
||||
{UNIT 2 (Dwarvish Fighter) 54 55 (random_traits=no)}
|
||||
[/event]
|
||||
|
||||
[event]
|
||||
name=start
|
||||
|
||||
[message]
|
||||
id=player
|
||||
message="Hahaha! I have placed my units at strategic choke points and given them XP close to leveling. I am safe from the stupid Wesnoth AI."
|
||||
[/message]
|
||||
|
||||
[message]
|
||||
id=dwarf
|
||||
message="You're in for a nasty surprise ..."
|
||||
[/message]
|
||||
|
||||
[message]
|
||||
speaker=narrator
|
||||
image="wesnoth-icon.png"
|
||||
caption="Note"
|
||||
message="This is a test scenario for a new AI algorithm that attacks units close to leveling. A few test cases are already set up on the map, but it is really expected that you add more units and/or change hitpoints and experience using debug commands to try out other situations."
|
||||
[/message]
|
||||
|
||||
[message]
|
||||
x,y=52,55
|
||||
message="Poisoned units are only attacked if there is a chance to kill them. Otherwise we simply wait and let the poison do its work."
|
||||
[/message]
|
||||
|
||||
[message]
|
||||
x,y=19,54
|
||||
message="The scout in the northwest is the better choice for forcing the skeleton to level up, so I'll move out of the way for him."
|
||||
[/message]
|
||||
|
||||
[message]
|
||||
x,y=8,29
|
||||
message="Here we have a unit 2 XP from leveling with both L1 and L2 AI units in reach. In this case, we wait to see what the default AI does. After the default AI attacks with one of the L1 units and the enemy is 1 XP from leveling, we execute a level-up attack."
|
||||
[/message]
|
||||
[message]
|
||||
x,y=18,29
|
||||
message="This is an equivalent setup, except that the default AI chooses not to attack with the (weakened) L1 units. In this case, we execute the level-up attack with the L2 unit after the default AI is done."
|
||||
[/message]
|
||||
|
||||
[message]
|
||||
x,y=35,40
|
||||
message="There's a high chance that I will die in attacking that revenant, so the AI will not attack with aggression=0.4 (the default). By contrast, with aggression=1 (which you can set in a moment), it does attack."
|
||||
[/message]
|
||||
[message]
|
||||
x,y=43,40
|
||||
message="I have a much lower chance to die and will attack even with the default setting for aggression."
|
||||
[/message]
|
||||
|
||||
[message]
|
||||
speaker=narrator
|
||||
image=wesnoth-icon.png
|
||||
message="What value should we use for aggression for the AI side?"
|
||||
[option]
|
||||
message="aggression 0.4 (the default)"
|
||||
[/option]
|
||||
[option]
|
||||
message="aggression 1.0"
|
||||
[command]
|
||||
[modify_side]
|
||||
side=2
|
||||
[ai]
|
||||
aggression=1
|
||||
[/ai]
|
||||
[/modify_side]
|
||||
[/command]
|
||||
[/option]
|
||||
[/message]
|
||||
|
||||
[objectives]
|
||||
[note]
|
||||
description="Modify the units on the map as desired, then end the turn"
|
||||
[/note]
|
||||
[/objectives]
|
||||
[/event]
|
||||
[/test]
|
|
@ -75,8 +75,6 @@
|
|||
[/side]
|
||||
|
||||
{STARTING_VILLAGES 2 10}
|
||||
{AI_FORCE_ATTACK_HIGH_XP_UNITS_SETUP}
|
||||
{AI_FORCE_ATTACK_HIGH_XP_UNITS 2}
|
||||
|
||||
[side]
|
||||
type=Orcish Slayer
|
||||
|
@ -107,7 +105,6 @@
|
|||
[/side]
|
||||
|
||||
{STARTING_VILLAGES 4 10}
|
||||
{AI_FORCE_ATTACK_HIGH_XP_UNITS 4}
|
||||
|
||||
[side]
|
||||
type=Elvish Marshal
|
||||
|
@ -134,7 +131,6 @@
|
|||
[/side]
|
||||
|
||||
{STARTING_VILLAGES 5 6}
|
||||
{AI_FORCE_ATTACK_HIGH_XP_UNITS 5}
|
||||
|
||||
[side]
|
||||
type=Elvish Marshal
|
||||
|
@ -151,7 +147,6 @@
|
|||
[/side]
|
||||
|
||||
{STARTING_VILLAGES 6 8}
|
||||
{AI_FORCE_ATTACK_HIGH_XP_UNITS 6}
|
||||
|
||||
{SOTBE_TRACK {JOURNEY_04_NEW} }
|
||||
|
||||
|
|
|
@ -70,8 +70,6 @@
|
|||
[/side]
|
||||
|
||||
{STARTING_VILLAGES 2 28}
|
||||
{AI_FORCE_ATTACK_HIGH_XP_UNITS_SETUP}
|
||||
{AI_FORCE_ATTACK_HIGH_XP_UNITS 2}
|
||||
|
||||
[story]
|
||||
[part]
|
||||
|
|
|
@ -199,44 +199,3 @@
|
|||
[/unstore_unit]
|
||||
[/event]
|
||||
#enddef
|
||||
|
||||
#define AI_FORCE_ATTACK_HIGH_XP_UNITS_SETUP
|
||||
# Function needed for Micro AI which forces attacks on units 1 XP from leveling
|
||||
# Goes directly into scenario toplevel, but only once per scenario
|
||||
[event]
|
||||
name=preload
|
||||
first_time_only=no
|
||||
[lua]
|
||||
code=<<
|
||||
function close_to_advancing(unit)
|
||||
if (unit.experience >= unit.max_experience-1) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
>>
|
||||
[/lua]
|
||||
[/event]
|
||||
#enddef
|
||||
|
||||
#define AI_FORCE_ATTACK_HIGH_XP_UNITS SIDE
|
||||
# Micro AI which forces attacks on units 1 XP from leveling
|
||||
# Goes directly into scenario toplevel, one macro per side
|
||||
[event]
|
||||
name=prestart
|
||||
[micro_ai]
|
||||
side={SIDE}
|
||||
ai_type=simple_attack
|
||||
action=add
|
||||
|
||||
ca_score=100001
|
||||
[filter]
|
||||
canrecruit=no
|
||||
[/filter]
|
||||
[filter_second]
|
||||
lua_function = "close_to_advancing"
|
||||
[/filter_second]
|
||||
[/micro_ai]
|
||||
[/event]
|
||||
#enddef
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
location="campaigns/The_Rise_Of_Wesnoth/ai/ca_aggressive_attack_no_suicide.lua"
|
||||
[/candidate_action]
|
||||
)}
|
||||
{MODIFY_AI_DELETE_CANDIDATE_ACTION 2 main_loop high_xp_attack}
|
||||
{MODIFY_AI_DELETE_CANDIDATE_ACTION 2 main_loop combat}
|
||||
[/ai]
|
||||
{FLAG_VARIANT wood-elvish}
|
||||
|
@ -115,6 +116,7 @@
|
|||
location="campaigns/The_Rise_Of_Wesnoth/ai/ca_aggressive_attack_no_suicide.lua"
|
||||
[/candidate_action]
|
||||
)}
|
||||
{MODIFY_AI_DELETE_CANDIDATE_ACTION 3 main_loop high_xp_attack}
|
||||
{MODIFY_AI_DELETE_CANDIDATE_ACTION 2 main_loop combat}
|
||||
[/ai]
|
||||
{FLAG_VARIANT knalgan}
|
||||
|
@ -157,6 +159,7 @@
|
|||
location="campaigns/The_Rise_Of_Wesnoth/ai/ca_aggressive_attack_no_suicide.lua"
|
||||
[/candidate_action]
|
||||
)}
|
||||
{MODIFY_AI_DELETE_CANDIDATE_ACTION 4 main_loop high_xp_attack}
|
||||
{MODIFY_AI_DELETE_CANDIDATE_ACTION 2 main_loop combat}
|
||||
[/ai]
|
||||
{FLAG_VARIANT long}
|
||||
|
|
|
@ -553,6 +553,7 @@
|
|||
#{AI_CA_RECRUITMENT}
|
||||
{AI_CA_MOVE_LEADER_TO_GOALS}
|
||||
{AI_CA_MOVE_LEADER_TO_KEEP}
|
||||
{AI_CA_HIGH_XP_ATTACK}
|
||||
{AI_CA_COMBAT}
|
||||
{AI_CA_HEALING}
|
||||
{AI_CA_VILLAGES}
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
120000
|
||||
#enddef
|
||||
|
||||
#define AI_CA_HIGH_XP_ATTACK_MAX_SCORE
|
||||
100010
|
||||
#enddef
|
||||
|
||||
#define AI_CA_COMBAT_SCORE
|
||||
100000
|
||||
#enddef
|
||||
|
@ -93,6 +97,16 @@
|
|||
[/candidate_action]
|
||||
#enddef
|
||||
|
||||
#define AI_CA_HIGH_XP_ATTACK
|
||||
[candidate_action]
|
||||
id=high_xp_attack
|
||||
engine=lua
|
||||
name=ai_default_rca::high_xp_attack
|
||||
location="ai/lua/ca_high_xp_attack.lua"
|
||||
max_score={AI_CA_HIGH_XP_ATTACK_MAX_SCORE}
|
||||
[/candidate_action]
|
||||
#enddef
|
||||
|
||||
#define AI_CA_COMBAT
|
||||
# RCA AI candidate action: combat
|
||||
[candidate_action]
|
||||
|
|
Loading…
Add table
Reference in a new issue