New Simple Attack Micro AI and test scenario

This commit is contained in:
mattsc 2013-09-01 07:34:24 -07:00
parent 963705af50
commit cc6831f9e6
5 changed files with 385 additions and 1 deletions

View file

@ -0,0 +1,91 @@
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
}

View file

@ -473,6 +473,11 @@ function wesnoth.wml_actions.micro_ai(cfg)
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 } }
--------- 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 } }
-- If we got here, none of the valid ai_types was specified
else
H.wml_error("unknown value for ai_type= in [micro_ai]")

View file

@ -85,6 +85,9 @@
{PLACE_IMAGE "scenery/signpost.png" 13 19}
{SET_LABEL 13 19 _"Hang Out and Messenger"}
{PLACE_IMAGE "scenery/signpost.png" 14 19}
{SET_LABEL 14 19 _"Simple Attack"}
{VARIABLE scenario_name micro_ai_test}
# Menu items explaining the different scenarios
@ -312,6 +315,21 @@
{MESSAGE Grnk "" _"Combined Hang Out and Messenger Escort Micro AI demo" _"This scenario is a demonstration of the Hang Out Micro AI which keeps units around a (customizable) location until a (customizable) condition is met. After that the units are released to follow other AI behavior. The scenario also shows how to combine two Micro AIs on the same side by having the Messenger Escort Micro AI take over at that point."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m14_simple_attack
description= _ "Simple Attack Micro AI demo"
image=units/undead/soulless.png~CROP(27,14,24,24)
[filter_location]
x,y=14,19
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Simple Attack Micro AI demo" _"This scenario demonstrates how certain attacks can be executed with higher priority than the standard Wesnoth attacks and how the AI can be forced to do attacks that it would otherwise avoid."}
[/command]
[/set_menu_item]
[/event]
[event]
@ -589,4 +607,21 @@ Information about each demonstration can be accessed by right-clicking on the re
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=14,19
[/filter]
[endlevel]
result=victory
next_scenario=simple_attack
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[/test]

View file

@ -0,0 +1,233 @@
#textdomain wesnoth-ai
[test]
id=simple_attack
name= _ "Simple Attack"
next_scenario=micro_ai_test
map_data="{multiplayer/maps/Dark_Forecast.map}"
{DEFAULT_SCHEDULE}
turns=-1
[side]
side=1
controller=human
id=Grospur
name= _ "Grospur"
type=General
x,y=15,14
persistent=no
canrecruit=yes
recruit=Swordsman,Longbowman,Spearman,Bowman
gold=100
[/side]
[side]
side=2
controller=ai
type=Ancient Lich
id=Uralt
name= _ "Uralt"
x,y=8,6
persistent=no
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
side=3
controller=null
persistent=yes
save_id=Grnk
hidden=yes
[/side]
[event]
name=prestart
{VARIABLE scenario_name simple_attack}
{UNIT 1 Longbowman 11 14 random_traits,experience=no,67}
{UNIT 1 Longbowman 15 12 random_traits,experience=no,67}
{UNIT 1 Longbowman 13 17 random_traits,experience=no,67}
{UNIT 1 (Master Bowman) 13 13 random_traits,experience=no,149}
{UNIT 1 (Master Bowman) 11 16 random_traits,experience=no,149}
{UNIT 1 (Master Bowman) 15 18 random_traits,experience=no,149}
{UNIT 1 Sergeant 14 14 id=sergeant}
{GENERIC_UNIT 1 Javelineer 15 13}
{GENERIC_UNIT 1 (Iron Mauler) 15 17}
{SCATTER_UNITS 12 "Soulless" 1 (x,y=5-9,8-22) (side=2)}
{SCATTER_UNITS 6 "Skeleton,Skeleton Archer" 1 (x,y=5-9,8-22) (side=2)}
[lua]
code=<<
function close_to_advancing(unit)
if (unit.experience >= unit.max_experience-1) then
return true
else
return false
end
end
>>
[/lua]
[micro_ai]
side=2
ai_type=simple_attack
action=add
ca_score=110001
[filter]
type=Soulless # No Walking Corpses; L0 units don't advance enemy
[/filter]
[filter_second]
lua_function = "close_to_advancing"
[/filter_second]
[/micro_ai]
[micro_ai]
side=2
ai_type=simple_attack
action=add
ca_score=110000
[filter]
type=Soulless,Walking Corpse
[/filter]
[/micro_ai]
[modify_side]
side=2
[ai]
aggression=1.0
caution=-9999
[/ai]
[/modify_side]
[/event]
[event]
name=start
{SCROLL_TO 11 16}
{MESSAGE sergeant "" "" _"General Grospur, what do we do? These undead will surely wipe us out."}
{MESSAGE Grospur "" "" _"Don't be such a chicken, Sergeant! I have placed units with lots of experience around the perimeter. The undead will not dare to attack them. And those few that sneak through... we can easily dispose of them once they make it inside.
<i>In other words, the Wesnoth AI does generally not attack units one XP from leveling if there is no chance of killing the unit with a single attack. However, some of the attacks by the undead are handle by the Simple Attack Micro AI in this scenario. General Grospur might be in for a surprise.</i>"}
[objectives]
summary= _ "Watch the undead take care of business"
[objective]
description= _ "Don't even try. You can't reach the Lich."
condition=win
[/objective]
[objective]
description= _ "Death of the last of Grospur's units"
condition=lose
[/objective]
[note]
description= _ "When your leader dies, side leadership passes on to another unit"
[/note]
[/objectives]
[/event]
# Guards don't get moves on Turn 1
[event]
name=turn refresh
[modify_unit]
[filter]
side=1
type=Longbowman,Swordsman
[/filter]
moves=0
[/modify_unit]
[/event]
[event]
name=attack
[filter_second]
side=1
lua_function = "close_to_advancing"
[/filter_second]
{MESSAGE $second_unit.id "" "" _"What the ... ?!? They are not supposed to attack me. That just doesn't happen in Wesnoth!"}
{MESSAGE Uralt "" "" _"Hahahahaha !! I have given special instruction to my Soulless to attack all you almost-advanced units first. Also watch how those same Soulless will throw themselves mercilessly at your pitiful soldiers after that, saving my more valuable skeleton minions for later. I have taken the term 'disposable units' to a whole new level. Watch in awe !!
<i>Translation: The undead side includes two instances of the Simple Attack Micro AI. The first makes the Soulless attack all units 1 XP from leveling up, such that they can be eliminated afterward. The second executes all remaining attacks possible by Soulless (and Walking Corpses), without regard for their own safety. Only after that does the default Wesnoth attack mechanism kick in to attack with the remaining units (skeletons).</i>"}
[/event]
# Put more undead out there when less than 25 left
[event]
name=side 2 turn end
first_time_only=no
[if]
[have_unit]
side=2
count=0-24
[/have_unit]
[then]
{MESSAGE Uralt "" "" _"Rise, minions!"}
{SCATTER_UNITS 6 "Soulless" 1 (x,y=5-9,8-22) (side=2)}
{SCATTER_UNITS 3 "Skeleton,Skeleton Archer" 1 (x,y=5-9,8-22) (side=2)}
[/then]
[/if]
[/event]
# When the leader dies, transfer leadership to another unit.
# If this was the last unit, end scenario.
[event]
name=die
first_time_only=no
[filter]
side=1
canrecruit=yes
[/filter]
[if]
[have_unit]
side=1
[/have_unit]
[then]
[store_unit]
[filter]
side=1
[/filter]
variable=tmp_units
[/store_unit]
{MODIFY_UNIT id=$tmp_units[1].id canrecruit yes}
{CLEAR_VARIABLE tmp_units}
[/then]
[else]
[kill]
id=$unit.id
[/kill]
# So that game goes on to next scenario
[modify_side]
side=3
controller=human
[/modify_side]
{MESSAGE Uralt "" "" _"And that's how the undead AI executes total annihilation ..."}
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/else]
[/if]
[/event]
[/test]

View file

@ -221,7 +221,7 @@
#define MICRO_AI_HANG_OUT
# Sets up the Hang Out 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
# Needs to be in [side], does not work in [modify_side]
[ai]
id=hang_out
@ -237,3 +237,23 @@
{RCA_STAGE}
[/ai]
#enddef
#define MICRO_AI_SIMPLE_ATTACK
# Sets up the Simple Attack Micro AI for a side
# Include this macro in the side definition
# Needs to be in [side], does not work in [modify_side]
[ai]
id=simple_attack
description=_"Simple Attack Micro AI"
version=10710
[engine]
name="lua"
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/mai_simple_attack_engine.lua").init(ai)
>>
[/engine]
{RCA_STAGE}
[/ai]
#enddef