New Hang Out Micro AI and test scenario
This commit is contained in:
parent
0ed4441b09
commit
bce61017f1
5 changed files with 376 additions and 1 deletions
126
data/ai/micro_ais/ais/mai_hang_out_engine.lua
Normal file
126
data/ai/micro_ais/ais/mai_hang_out_engine.lua
Normal file
|
@ -0,0 +1,126 @@
|
|||
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 or 170000
|
||||
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
|
||||
}
|
|
@ -592,12 +592,22 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
required_keys = { "filter", "filter_location" }
|
||||
optional_keys = { "release_all_units_at_goal", "release_unit_at_goal", "unique_goals", "use_straight_line" }
|
||||
CA_parms = {
|
||||
{ -- Note: do not define max_score
|
||||
{
|
||||
ca_id = 'goto', eval_name = 'mai_goto_eval', exec_name = 'mai_goto_exec',
|
||||
max_score = cfg.ca_score or 300000
|
||||
}
|
||||
}
|
||||
|
||||
--------- 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 = 'hang_out', eval_name = 'mai_hang_out_eval', exec_name = 'mai_hang_out_exec',
|
||||
max_score = cfg.ca_score or 170000
|
||||
}
|
||||
}
|
||||
|
||||
-- If we got here, none of the valid ai_types was specified
|
||||
else
|
||||
H.wml_error("unknown value for ai_type= in [micro_ai]")
|
||||
|
|
184
data/ai/micro_ais/scenarios/hang-out.cfg
Normal file
184
data/ai/micro_ais/scenarios/hang-out.cfg
Normal file
|
@ -0,0 +1,184 @@
|
|||
#textdomain wesnoth-ai
|
||||
|
||||
[test]
|
||||
id=hang-out
|
||||
name= _ "Hang Out"
|
||||
next_scenario=micro_ai_test
|
||||
|
||||
map_data="{multiplayer/maps/4p_Castle_Hopping_Isle.map}"
|
||||
|
||||
{DEFAULT_SCHEDULE}
|
||||
turns=-1
|
||||
victory_when_enemies_defeated=no
|
||||
|
||||
[side]
|
||||
side=1
|
||||
controller=ai
|
||||
id=Bad Outlaw
|
||||
type=Outlaw
|
||||
x,y=2,19
|
||||
persistent=no
|
||||
|
||||
team_name=Outlaw
|
||||
user_team_name= _ "team_name^Bad Outlaw"
|
||||
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]
|
||||
side=2
|
||||
controller=human
|
||||
id=Good Bandit
|
||||
type=Bandit
|
||||
x,y=16,2
|
||||
persistent=no
|
||||
|
||||
team_name=Bandit
|
||||
user_team_name= _ "team_name^Good Bandit"
|
||||
recruit=Thief
|
||||
|
||||
gold=200
|
||||
[/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 hang-out}
|
||||
|
||||
# Goal signpost for Rossauba
|
||||
{PLACE_IMAGE "scenery/signpost.png" 36 19}
|
||||
{SET_LABEL 36 19 _"Outlaw moves here"}
|
||||
|
||||
# Change some of the terrain for this demonstration
|
||||
[terrain]
|
||||
x=4,15,23
|
||||
y=19,20,20
|
||||
terrain=Aa
|
||||
[/terrain]
|
||||
[terrain]
|
||||
x=0,1
|
||||
y=20-22,21-22
|
||||
terrain=Mm
|
||||
[/terrain]
|
||||
[terrain]
|
||||
x=18,20
|
||||
y=19,19
|
||||
terrain=Wwf
|
||||
[/terrain]
|
||||
[terrain]
|
||||
x,y=21-22,16-17
|
||||
terrain=Ww
|
||||
[/terrain]
|
||||
|
||||
[micro_ai]
|
||||
side=1
|
||||
ai_type=hang_out
|
||||
action=add
|
||||
|
||||
[avoid]
|
||||
terrain=C*,H*,M*,A*,S*,*^F*
|
||||
[/avoid]
|
||||
[mobilize_condition]
|
||||
[have_unit]
|
||||
side=1
|
||||
count=7-99
|
||||
[/have_unit]
|
||||
[/mobilize_condition]
|
||||
[/micro_ai]
|
||||
|
||||
[micro_ai]
|
||||
side=1
|
||||
ai_type=messenger_escort
|
||||
action=add
|
||||
|
||||
id=Bad Outlaw
|
||||
ca_score=165000
|
||||
waypoint_x=16,22,36
|
||||
waypoint_y=19,19,19
|
||||
[/micro_ai]
|
||||
[/event]
|
||||
|
||||
[event]
|
||||
name=start
|
||||
|
||||
{MESSAGE (Good Bandit) "" "" _"That outlaw over there is going to run for the keep in the southeast. He's only going to recruit for three rounds before he'll start moving and he and his footpads are much faster than we are. Let's make haste or we'll never catch him.
|
||||
|
||||
Note: This scenario uses a combination of two Micro AIs, the Hang Out Micro AI which makes the Side 2 units remain around the keep for two turns (while moving off castle tiles to allow for recruiting) and the Messenger Escort AI which takes over after that. 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= _ "Get into the outlaw's way before he can make it to the south-eastern keep"
|
||||
[objective]
|
||||
description= _ "Death of Bad Outlaw"
|
||||
condition=win
|
||||
[/objective]
|
||||
[objective]
|
||||
description= _ "Death of Good Bandit"
|
||||
condition=lose
|
||||
[/objective]
|
||||
[objective]
|
||||
description= _ "Bad Outlaw makes it to the signpost"
|
||||
condition=lose
|
||||
[/objective]
|
||||
[/objectives]
|
||||
[/event]
|
||||
|
||||
[event]
|
||||
name=die
|
||||
[filter]
|
||||
id=Bad Outlaw
|
||||
[/filter]
|
||||
|
||||
{MESSAGE (Good Bandit) "" "" _"We got him! Now whatever it is we are fighting for is safe."}
|
||||
|
||||
# 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]
|
||||
|
||||
[event]
|
||||
name=moveto
|
||||
[filter]
|
||||
id=Bad Outlaw
|
||||
x,y=36,19
|
||||
[/filter]
|
||||
|
||||
{MESSAGE (Bad Outlaw) "" "" _"I made it! Now we can keep fighting for whatever it is that we are fighting for."}
|
||||
[endlevel]
|
||||
result=defeat
|
||||
[/endlevel]
|
||||
[/event][/test]
|
|
@ -82,6 +82,9 @@
|
|||
{PLACE_IMAGE "scenery/signpost.png" 12 18}
|
||||
{SET_LABEL 12 18 _"Goto"}
|
||||
|
||||
{PLACE_IMAGE "scenery/signpost.png" 13 19}
|
||||
{SET_LABEL 13 19 _"Hang Out and Messenger"}
|
||||
|
||||
{VARIABLE scenario_name micro_ai_test}
|
||||
|
||||
# Menu items explaining the different scenarios
|
||||
|
@ -294,6 +297,21 @@
|
|||
{MESSAGE Grnk "" _"Goto Micro AI demo" _"This scenario contains several example usages of the Goto Micro AI, which is a highly configurable method of sending a unit (or units) to a location or set of locations. The units to be moved are defined using a Standard Unit Filter, while the goto locations are given in a Standard Location Filter."}
|
||||
[/command]
|
||||
[/set_menu_item]
|
||||
|
||||
[set_menu_item]
|
||||
id=m14_hangout
|
||||
description= _ "Hang Out Micro AI demo"
|
||||
image=units/human-outlaws/bandit.png~CROP(22,14,24,24)
|
||||
[filter_location]
|
||||
x,y=13,19
|
||||
[/filter_location]
|
||||
[show_if]
|
||||
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
|
||||
[/show_if]
|
||||
[command]
|
||||
{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]
|
||||
[/event]
|
||||
|
||||
[event]
|
||||
|
@ -554,4 +572,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=13,19
|
||||
[/filter]
|
||||
|
||||
[endlevel]
|
||||
result=victory
|
||||
next_scenario=hang-out
|
||||
bonus=no
|
||||
carryover_percentage=0
|
||||
carryover_report=no
|
||||
linger_mode=no
|
||||
replay_save=no
|
||||
[/endlevel]
|
||||
[/event]
|
||||
[/test]
|
||||
|
|
|
@ -217,3 +217,23 @@
|
|||
{RCA_STAGE}
|
||||
[/ai]
|
||||
#enddef
|
||||
|
||||
#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
|
||||
|
||||
[ai]
|
||||
id=hang_out
|
||||
description=_"Hang Out Micro AI"
|
||||
version=10710
|
||||
[engine]
|
||||
name="lua"
|
||||
code= <<
|
||||
local ai = ...
|
||||
return wesnoth.require("ai/micro_ais/ais/mai_hang_out_engine.lua").init(ai)
|
||||
>>
|
||||
[/engine]
|
||||
{RCA_STAGE}
|
||||
[/ai]
|
||||
#enddef
|
||||
|
|
Loading…
Add table
Reference in a new issue