New Simple Attack Micro AI and test scenario
This commit is contained in:
parent
963705af50
commit
cc6831f9e6
5 changed files with 385 additions and 1 deletions
91
data/ai/micro_ais/ais/mai_simple_attack_engine.lua
Normal file
91
data/ai/micro_ais/ais/mai_simple_attack_engine.lua
Normal 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
|
||||
}
|
|
@ -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]")
|
||||
|
|
|
@ -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]
|
||||
|
|
233
data/ai/micro_ais/scenarios/simple_attack.cfg
Normal file
233
data/ai/micro_ais/scenarios/simple_attack.cfg
Normal 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]
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue