Merge branch 'master' into asio_wesnothd

This commit is contained in:
loonycyborg 2016-04-07 17:17:21 +03:00
commit 94c8533e1f
4363 changed files with 460652 additions and 380035 deletions

2
.gitignore vendored
View file

@ -30,7 +30,7 @@ INSTALL.vcproj
PACKAGE.vcproj
ZERO_CHECK.vcproj
uninstall.vcproj
*.vcproj.*.user
projectfiles/VC*/*.user
projectfiles/VC[12]*
src/**/*.vcproj
/WindowsTimeout.ilk

View file

@ -11,13 +11,14 @@ compiler:
env:
- BUILD="-O0"
- BUILD="-O2"
- BUILD="C++11 -O0"
- BUILD="translations"
matrix:
exclude:
- compiler: gcc
env: BUILD="-O2"
- compiler: gcc
env: BUILD="C++14 -O2"
- compiler: gcc
env: BUILD="translations"
@ -32,17 +33,23 @@ before_install:
- export EXTRA_FLAGS_RELEASE="-O0"
- export WML_TEST_TIME=20
- export NLS=false
- export CXX11=false
- export CXXSTD=11
- if [ "$CXX" == "g++" ]; then export CXX=g++-4.7; fi
- if [ "$BUILD" == "-O2" ]; then export STRICT_COMPILATION=false; fi
- if [ "$BUILD" == "-O2" ]; then export EXTRA_FLAGS_RELEASE=""; fi
- if [ "$BUILD" == "-O2" ]; then export WML_TEST_TIME=15; fi
- if [ "$BUILD" == "C++11 -O0" ]; then export CXX11=true; fi
- if [ "$BUILD" == "C++11 -O0" ]; then export EXTRA_FLAGS_RELEASE="-O0 -Wno-literal-suffix -Wno-deprecated-declarations"; fi
- if [[ "$BUILD" == "C++11 -O0" ]] && [[ "$CXX" == "clang++" ]]; then export EXTRA_FLAGS_RELEASE="-O0 -Wno-literal-suffix -Wno-deprecated-declarations -Wno-deprecated-register"; fi
- if [ "$BUILD" == "C++11 -O0" ]; then export PLAY_TEST=false; fi
- if [ "$BUILD" == "C++11 -O0" ]; then export MP_TEST=false; fi
- if [ "$BUILD" == "C++14 -O2" ]; then export STRICT_COMPILATION=false; fi
- if [ "$BUILD" == "C++14 -O2" ]; then export EXTRA_FLAGS_RELEASE=""; fi
- if [ "$BUILD" == "C++14 -O2" ]; then export WML_TEST_TIME=15; fi
- if [ "$BUILD" == "C++14 -O2" ]; then export CXXSTD="1y"; fi
- if [ "$BUILD" == "-O0" ]; then export EXTRA_FLAGS_RELEASE="-O0 -Wno-deprecated-declarations"; fi
- if [[ "$BUILD" == "-O0" ]] && [[ "$CXX" == "clang++" ]]; then export EXTRA_FLAGS_RELEASE="-O0 -Wno-literal-suffix -Wno-deprecated-declarations -Wno-deprecated-register"; fi
- if [ "$BUILD" == "-O0" ]; then export PLAY_TEST=false; fi
- if [ "$BUILD" == "-O0" ]; then export MP_TEST=false; fi
- if [ "$BUILD" == "translations" ]; then export NLS=true; fi
- if [ "$BUILD" == "translations" ]; then export TARGETS="translations"; fi
@ -53,14 +60,14 @@ before_install:
install:
- travis_wait sudo apt-get update -qq
- travis_wait sudo apt-get install -qq libboost-filesystem-dev libboost-iostreams-dev libboost-random-dev libboost-program-options-dev libboost-regex-dev libboost-system-dev libboost-test-dev libboost-locale-dev libcairo2-dev libfribidi-dev libpango1.0-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-net1.2-dev libsdl-ttf2.0-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-net-dev libsdl2-ttf-dev gdb moreutils scons xvfb
- travis_wait sudo apt-get install -qq libboost-filesystem-dev libboost-iostreams-dev libboost-random-dev libboost-program-options-dev libboost-regex-dev libboost-system-dev libboost-test-dev libboost-locale-dev libboost-thread-dev libcairo2-dev libfribidi-dev libpango1.0-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-net1.2-dev libsdl-ttf2.0-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-net-dev libsdl2-ttf-dev gdb moreutils scons xvfb g++-4.7
script:
- ./utils/travis/check_utf8.sh
- ./utils/travis/utf8_bom_dog.sh
- $CXX --version
- echo "*Params* --- " "cxxtool=$CXX --debug=time build=release extra_flags_release="$EXTRA_FLAGS_RELEASE" strict=$STRICT_COMPILATION $TARGETS cxx0x=$CXX11 nls=$NLS jobs=2"
- scons cxxtool=$CXX --debug=time build=release extra_flags_config="$EXTRA_FLAGS_ALL" extra_flags_release="$EXTRA_FLAGS_RELEASE" strict=$STRICT_COMPILATION $TARGETS cxx0x=$CXX11 nls=$NLS jobs=2
- echo "*Params* --- " "cxxtool=$CXX --debug=time build=release extra_flags_release="$EXTRA_FLAGS_RELEASE" strict=$STRICT_COMPILATION $TARGETS cxx_std=$CXXSTD nls=$NLS jobs=2"
- scons cxxtool=$CXX --debug=time build=release extra_flags_config="$EXTRA_FLAGS_ALL" extra_flags_release="$EXTRA_FLAGS_RELEASE" strict=$STRICT_COMPILATION $TARGETS cxx_std=$CXXSTD nls=$NLS jobs=2
- "export DISPLAY=:99.0"
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1024x768x24"
- if [ "$CPP_TESTS" = true ]; then ./utils/travis/test_wrapper.sh; fi

View file

@ -49,7 +49,6 @@ option(ENABLE_CAMPAIGN_SERVER "Enable compilation of campaign server")
option(ENABLE_SERVER "Enable compilation of server" ON)
option(ENABLE_TOOLS "Enable building and installation of tools for artists and WML maintainers")
option(ENABLE_SDL2_TOOLS "Enable building and installation of tools for testing with SDL2" OFF)
option(ENABLE_SDL2 "Enable building the game with SDL2" ON)
option(ENABLE_TESTS "Build unit tests")
option(ENABLE_NLS "Enable building of translations" ON)
option(ENABLE_LOW_MEM "Reduce memory usage by removing extra functionality" OFF)
@ -60,17 +59,14 @@ option(ENABLE_LIBPNG "Enable support for writing png files (screenshots, images)
option(ENABLE_LIBINTL "Enable using libintl for translations instead of Boost.Locale library (not recommended)" OFF)
option(ENABLE_HISTORY "Enable using GNU history for history in lua console" ON)
if(ENABLE_SDL2)
if(UNIX AND NOT APPLE AND NOT CYGWIN)
find_package(SDL2 2.0.2 REQUIRED)
else (UNIX AND NOT APPLE AND NOT CYGWIN)
find_package(SDL2 2.0.4 REQUIRED)
endif (UNIX AND NOT APPLE AND NOT CYGWIN)
else(ENABLE_SDL2)
find_package(SDL 1.2.7 REQUIRED)
endif(ENABLE_SDL2)
find_package(Boost 1.36 REQUIRED COMPONENTS iostreams program_options regex system)
find_package(Boost 1.36 REQUIRED COMPONENTS iostreams program_options regex system thread)
find_package(Boost 1.40 REQUIRED COMPONENTS random)
# no, gettext executables are not required when NLS is deactivated
@ -79,17 +75,10 @@ find_package(Gettext)
find_package(X11)
if(NOT MSVC)
if(ENABLE_SDL2)
#needed to get some SDL2 defines in... (as of rev31694 -D_GNU_SOURCE=1 is required!)
set(SDL2_CONFIG "sdl2-config" CACHE STRING "Path to sdl2-config script")
exec_program(${SDL2_CONFIG} ARGS "--cflags" OUTPUT_VARIABLE SDL2_CFLAGS)
add_definitions(${SDL2_CFLAGS})
else(ENABLE_SDL2)
#needed to get some SDL defines in... (as of rev31694 -D_GNU_SOURCE=1 is required!)
set(SDL_CONFIG "sdl-config" CACHE STRING "Path to sdl-config script")
exec_program(${SDL_CONFIG} ARGS "--cflags" OUTPUT_VARIABLE SDL_CFLAGS)
add_definitions(${SDL_CFLAGS})
endif(ENABLE_SDL2)
#needed to get some SDL2 defines in... (as of rev31694 -D_GNU_SOURCE=1 is required!)
set(SDL2_CONFIG "sdl2-config" CACHE STRING "Path to sdl2-config script")
exec_program(${SDL2_CONFIG} ARGS "--cflags" OUTPUT_VARIABLE SDL2_CFLAGS)
add_definitions(${SDL2_CFLAGS})
endif(NOT MSVC)
if(NOT WIN32)
@ -256,13 +245,16 @@ if(MSVC AND NOT DEFINED CXX_FLAGS_MSVC)
endif(MSVC AND NOT DEFINED CXX_FLAGS_MSVC)
set(CXX_FLAGS_PROJECT)
check_compiler_has_flag(CXX_FLAGS_PROJECT "-std=c++98" HAS_COMPILER_FLAG_STD)
check_compiler_has_flag(CXX_FLAGS_PROJECT "-std=c++11" HAS_COMPILER_FLAG_STD)
check_compiler_has_flag(CXX_FLAGS_PROJECT "-W" HAS_COMPILER_FLAG_W)
# MSVC's -Wall is not like gcc's, it really enables *all* warnings which include zillions for system headers and doesn't make sense.
if(NOT MSVC)
check_compiler_has_flag(CXX_FLAGS_PROJECT "-Wall" HAS_COMPILER_FLAG_WALL)
endif(NOT MSVC)
### Set some extra var for C++11
add_definitions(-DHAVE_CXX0X)
### Set strict compiler flags.
@ -570,20 +562,11 @@ endif(ENABLE_DEBUG_WINDOW_LAYOUT)
#
if(ENABLE_TOOLS OR ENABLE_GAME OR ENABLE_TESTS)
if(ENABLE_SDL2)
find_package( SDL2_image 2.0.0 REQUIRED )
else(ENABLE_SDL2)
find_package( SDL_image 1.2 REQUIRED )
endif(ENABLE_SDL2)
find_package( SDL2_image 2.0.0 REQUIRED )
endif(ENABLE_TOOLS OR ENABLE_GAME OR ENABLE_TESTS)
if(ENABLE_GAME OR ENABLE_TESTS)
if(ENABLE_SDL2)
find_package( SDL2_mixer 2.0.0 REQUIRED )
find_package( SDL2_ttf 2.0.8 REQUIRED )
else(ENABLE_SDL2)
find_package( SDL_mixer 1.2.12 REQUIRED )
find_package( SDL_ttf 2.0.8 REQUIRED )
endif(ENABLE_SDL2)
find_package( SDL2_mixer 2.0.0 REQUIRED )
find_package( SDL2_ttf 2.0.8 REQUIRED )
if(NOT MSVC)
find_package(VorbisFile REQUIRED)
find_package( PkgConfig REQUIRED )
@ -594,11 +577,7 @@ if(ENABLE_GAME OR ENABLE_TESTS)
endif(ENABLE_GAME OR ENABLE_TESTS)
if(ENABLE_GAME OR ENABLE_SERVER OR ENABLE_CAMPAIGN_SERVER OR ENABLE_TESTS)
if(ENABLE_SDL2)
find_package( SDL2_net 2.0.0 REQUIRED )
else(ENABLE_SDL2)
find_package( SDL_net REQUIRED )
endif(ENABLE_SDL2)
find_package( SDL2_net 2.0.0 REQUIRED )
endif(ENABLE_GAME OR ENABLE_SERVER OR ENABLE_CAMPAIGN_SERVER OR ENABLE_TESTS)
if(ENABLE_TOOLS)
find_package( ZLIB REQUIRED )

View file

@ -31,7 +31,7 @@ PROJECT_NAME = "The Battle for Wesnoth"
# This could be handy for archiving the generated documentation or
# if some version control system is used.
PROJECT_NUMBER = 1.13.2+dev
PROJECT_NUMBER = 1.13.4+dev
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
# base path where the generated documentation will be put.

View file

@ -2,9 +2,9 @@ About
=====
**The Battle for Wesnoth** is a Free, turn-based tactical strategy game with a
high fantasy theme, featuring both single-player, and online/hotseat
multiplayer combat. Fight a desperate battle to reclaim the throne of Wesnoth,
or take hand in any number of other adventures.
high fantasy theme, featuring both singleplayer and online/hotseat multiplayer
combat. Fight a desperate battle to reclaim the throne of Wesnoth, or take
hand in any number of other adventures.
License

View file

@ -18,22 +18,57 @@ CHANGES
Example contents.
[/rasection]
[rasection="Support for SDL 1.2 has been dropped"]
SDL version 1.2.x has been removed in favour of SDL version 2.0.x. The build systems have been and all code has been removed that utilised verions 1.2.x.
[/rasection]
[rasection="WML/Lua compatibility changes and deprecations"]
[list]
[/list]
[/rasection]
[rasection="SDL2 support"]
The game now uses SDL2 for all operations. There is still a fall-back option of using SDL 1.2, but it is deprecated and slated for removal.
[rasection="Wesnoth Formula Language update"]
The Wesnoth formula language used by $(...) substitution, the formula= key in standard unit filters, and a few other places has received some significant updates. The changlog has the full list, but some highlights are:
[list]
[*]New operators for string concatenation, ranges, and testing containment
[*]New string interpolation syntax
[*]Dot operator can access identifier-friendly string keys in maps
[*]Function definitions work outside FormulaAI and GUI2
[*]Many new functions for math and string manipulation
[*]Exponentiation operator is now right-associative (meaning 2^3^2 now produces 512 rather than 64)
[*]Dice operator is now synced
[*]Improved formula debugger
[/list]
The "fai" and "faiend" keywords are deprecated in favour of "wfl" and "wflend" respectively. This is part of a move to more clearly distinguish [wiki]Wesnoth Formula Language[/wiki], the language itself, from [wiki]FormulaAI[/wiki], which is just one use of the language in Wesnoth.
The attributes leader, total_movement, movement_left, and states in WFL unit variables were renamed to canrecruit, max_moves, moves, and status respectively; this was done to make them match the Lua and WML names for the same things. Also, the special attribute in WFL weapon variables is now specials. The old names will continue to work for now, but they may be removed in a future version.
[/rasection]
[rasection="Hotkeys rely on scancodes"]
The hotkey implementation has been changed to rely on scancodes over characters. This means that the hotkey configuration now uses the same physical buttons on the keyboard, regardless of the keyboard layout.
[rasection="Changes to the AI"]
After several development cycles of adding new and/or experimental features to the AI engine, a long overdue AI code cleanup effort was undertaken in order to facilitate future AI maintenance and development. This included a refactoring and reorganization of the code of the current AI as well as the removal of non-functional, outdated or deprecated code.
As a result, the format for configuring some AI parameters has been simplified. The AI functionality has also been extended in several ways. For example, it now allows Lua facets and the [modify_ai] tag has been extended to allow editing the internal jobs of the recruitment_instructions aspect.
While making these changes, we tried to retain backward compatibility as much as feasible. Thus, most of the changes are invisible to the WML and custom AI developer and the vast majority of existing UMC code will continue to function as is. It is, however, unavoidable that some old AIs or AI configurations will require updating. A summary of those changes is given below. For more details, see the full changelog and the Wesnoth wiki. In addition, we have set up a separate [forum thread; create first post and add link] in which you can post any questions and problems you might encounter while updating your code.
[/rasection]
[rasection="New WML event"]
A new 'unit placed' event has been added, making it possible to better implement certain kinds of event-driven behavior. [wiki=EventWML]See the wiki for details[/wiki].
[/rasection]
[rawarn="AI compatibility breaking changes"]
Several old, deprecated AI components were removed:
[list]
[*]Several aspects that no longer did anything anyway: number_of_possible_recruits_to_force_recruit, recruitment_ignore_bad_combat, recruitment_ignore_bad_movement
[*]The recruitment aspect; however, this is automatically translated to a recruitment_instructions aspect, which means it will now be honored where it was previously ignored
[*]Stages that were either old and deprecated or experimental and unmaintained/broken: old recruitment, fallback, Strategy Formulation with RCA
[*]Candidate actions that were either old and deprecated or experimental and unmaintained/broken: old recruitment, simple move-to-targets, global fallback, Akihara recruitment, experimental recruitment
[*]The protect_my_unit [goal] type
[/list]
A number of changes were made to the way Lua AI components are declared in the WML and how the code is called by the engine. The wiki documents the new method in detail, but unfortunately these changes are likely to break some AIs. However, the fixes will be simple in most cases:
[list=1]
[*]If you used old-style Lua candidate actions (with execute= and evaluate= keys) together with an explicit [engine]name=lua tag, they will no longer work as written. In many cases, the fix will be simple - the code within [engine] frequently begins with a line similar to [c]local ai = ...[/c]. Simply removing this line will be sufficient to fix most such cases; if you accessed a "data" variable in the AI, see the Experimental AI definition (data/ai/ais/ai_generic_rush.cfg) for an example of how to fix it.
[*]If you used old-style Lua candidate actions without an explicit engine tag, you may need to remove a line similar to [c]local ai = ...[/c] from some of your code. In general, anything of the form [c]ai = <something>[/c] needs to be changed.
[*]If you used new-style (external) Lua candidate actions (with a location key), they will still work for the time being (provided that they use the exec_parms= and/or eval_parms= keys), but we recommend updating them nevertheless. The exec_parms= and eval_parms= keys should be combined into an [args] tag, and the argument list of your execution and evaluation functions should be changed from [c](ai, cfg, self)[/c] to [c](cfg, data)[/c]. The data parameter is the same as self.data was before; however, provided you do not have an explicit [engine] tag, self.data will continue to work.
[/list]
[/rawarn]
==========
KNOWN BUGS
@ -44,14 +79,12 @@ KNOWN BUGS
[list]
[*]The MP server has trouble with Local player types in campaigns (bug [bug]21965[/bug]). We have decided to postpone dealing with this. In the meantime, you might try assigning such sides to the host, or running multiple instances of Wesnoth.
[*]2p mp survival map Dark Forecast is bugged (24200).
[/list]
[*]Doubled-up GUI1 dialogs don't redraw the secondary in SDL2 (bug [bug]24294[/bug]).
[*]Window state handling inconsistent across different OSes in SDL2 builds (bug [bug]24270[/bug]).
[*]Area under Objectives not redrawn on resize (bug [bug]24261[/bug]).
[*]Menu and Action buttons disappear on resize (bug [bug]24260[/bug]).
[*]Wesnoth does not exist on the panel (bug [bug]24202[/bug]).
[*]SDL2 build handled input incorrectly once window focus is lost when menus are open (bug [bug]24212[/bug]).
[*]"Name of Game" bleeds onto "Player/Type" label when leaving/entering the app (bug [bug]24437[/bug]).
[*]Inconsistent transparency on credits screen (bug [bug]24428[/bug]).
[*]options become black on hover (bug [bug]24478[/bug]).
[*]Dialog In Replay Clearing Issue (bug [bug]24491[/bug]).
[/list]
[/raissue]
[raissue="Carried over from 1.12.x"]
@ -59,8 +92,6 @@ KNOWN BUGS
[list]
[*]The game can crash when planning recruits in Planning Mode.
[*]Its not possible to clear some default hotkeys with the Clear Hotkey option (bug [bug]21983[/bug]).
[*]Attempting to assign hotkeys including both the Ctrl and Alt modifiers does not work (bug [bug]22219[/bug]).
[/list]
[b]Bugs specific to Microsoft Windows:[/b]

View file

@ -103,13 +103,12 @@ opts.AddVariables(
BoolVariable('ccache', "Use ccache", False),
('ctool', 'Set c compiler command if not using standard compiler.'),
('cxxtool', 'Set c++ compiler command if not using standard compiler.'),
BoolVariable('cxx0x', 'Use C++0x features.', False),
EnumVariable('cxx_std', 'Target c++ std version', '11', ['11', '14', '1y']),
BoolVariable('openmp', 'Enable openmp use.', False),
BoolVariable("fast", "Make scons faster at cost of less precise dependency tracking.", False),
BoolVariable("lockfile", "Create a lockfile to prevent multiple instances of scons from being run at the same time on this working copy.", False),
BoolVariable("OS_ENV", "Forward the entire OS environment to scons", False),
BoolVariable("history", "Clear to disable GNU history support in lua console", True),
BoolVariable("sdl2", "Build with SDL2 support (experimental!)", True)
BoolVariable("history", "Clear to disable GNU history support in lua console", True)
)
#
@ -223,6 +222,7 @@ You can make the following special build targets:
data-dist = make data tarball as wesnoth-data.tar.bz2 (*).
binary-dist = make data tarball as wesnoth-binaries.tar.bz2 (*).
wesnoth-bundle = make Mac OS application bundle from game (*)
windows-installer = create Windows distribution with NSIS (*)
sanity-check = run a pre-release sanity check on the distribution.
manual = regenerate English-language manual and, possibly, localized manuals if appropriate xmls exist.
@ -363,31 +363,18 @@ if env["prereqs"]:
conf.CheckLib("vorbis")
conf.CheckLib("mikmod")
if env['sdl2']:
def have_sdl_net():
return \
conf.CheckSDL(require_version = SDL2_version) & \
conf.CheckSDL("SDL2_net", header_file = "SDL_net")
def have_sdl_other():
return \
conf.CheckSDL(require_version = SDL2_version) & \
conf.CheckSDL("SDL2_ttf", header_file = "SDL_ttf") & \
conf.CheckSDL("SDL2_mixer", header_file = "SDL_mixer") & \
conf.CheckSDL("SDL2_image", header_file = "SDL_image")
def have_sdl_net():
return \
conf.CheckSDL(require_version = SDL2_version) & \
conf.CheckSDL("SDL2_net", header_file = "SDL_net")
else:
def have_sdl_net():
return \
conf.CheckSDL(require_version = '1.2.10') & \
conf.CheckSDL('SDL_net')
def have_sdl_other():
return \
conf.CheckSDL(require_version = '1.2.10') & \
conf.CheckSDL("SDL_ttf", require_version = "2.0.8") & \
conf.CheckSDL("SDL_mixer", require_version = '1.2.12') & \
conf.CheckSDL("SDL_image", require_version = '1.2.0')
def have_sdl_other():
return \
conf.CheckSDL(require_version = SDL2_version) & \
conf.CheckSDL("SDL2_ttf", header_file = "SDL_ttf") & \
conf.CheckSDL("SDL2_mixer", header_file = "SDL_mixer") & \
conf.CheckSDL("SDL2_image", header_file = "SDL_image")
if env["libintl"]:
def have_i18n_prereqs():
@ -427,7 +414,8 @@ if env["prereqs"]:
conf.CheckPango("cairo", require_version = "1.21.3") & \
conf.CheckPKG("fontconfig") & \
conf.CheckBoost("program_options", require_version="1.35.0") & \
conf.CheckBoost("regex", require_version = "1.35.0") \
conf.CheckBoost("thread") & \
conf.CheckBoost("regex") \
or Warning("WARN: Client prerequisites are not met. wesnoth, cutter and exploder cannot be built")
have_X = False
@ -517,11 +505,8 @@ for env in [test_env, campaignd_env, client_env, env]:
if "gcc" in env["TOOLS"]:
env.AppendUnique(CCFLAGS = Split("-W -Wall"), CFLAGS = ["-std=c99"])
if env['cxx0x']:
env.AppendUnique(CXXFLAGS = "-std=c++0x")
env.Append(CPPDEFINES = "HAVE_CXX0X")
else:
env.AppendUnique(CXXFLAGS = "-std=c++98")
env.AppendUnique(CXXFLAGS = "-std=c++" + env["cxx_std"])
env.Append(CPPDEFINES = "HAVE_CXX0X")
if env['openmp']:
env.AppendUnique(CXXFLAGS = ["-fopenmp"], LIBS = ["gomp"])

368
changelog
View file

@ -1,4 +1,315 @@
Version 1.13.2+dev:
Version 1.13.4+dev:
* Language and i18n:
* Updated translations:
* Removed support for SDL 1.2. SDL 2 is now the only supported version.
* Terrains:
* Changed terrain code of Desert Mountains from Mdy to Mdd.
* WML engine:
* Fix some issues with [foreach]
* Fix some issues with backstab-like weapon specials
* Support [effect]times=<integer>
* Add highlight=yes|no to [scroll_to], [scroll_to_unit], [message]
Defaults to no in the first two cases, yes in the third
If yes, the target hex is outlined.
* New ~SCALE_INTO(w,h) IPF which preserves aspect ratio, using bilinear
interpolation scaling.
* New ~SCALE_INTO_SHARP(w,h) IPF which preserves aspect ratio, using
nearest neighbor scaling.
* Support delayed_variable_substitution= in [on_undo], [on_redo]
Note that this means $unit.x and $unit.y may not reflect the unit's
true location, so using [unstore_unit] on $unit may have unexpected effects.
This applies to $second_unit too. The $x1, $y1, $x2, $y2 variables work fine
though, so in most cases they can be used instead. Anything else in $unit
or $second_unit is also fine.
* formula= in SUF can now reference $other_unit via the formula variable "other"
* formula= now supported in location, side, and weapon filters
* Weapon filters now support number, parry, accuracy, and movement_used
* New [has_attack] in standard unit filters; supercedes has_weapon= and
uses full weapon filter.
* lua_function=var.member now works in SUF; however, 'var' still needs to
be a global variable.
* AiWML:
* Simplified aspect syntax which works for all aspects, present and future:
* All aspects with simple values can be specified as key=value
* Except attacks, all aspects with complex values have a simple tag form
containing only the aspect value (e.g. [avoid])
* All aspects, simple and complex, can be specified using a tag named by
the aspect, whose contents is the same as a corresponding [facet]
* The full [aspect] and [facet] syntax also still works
* [ai] configs no longer recognize the version= key
* ai_algorithm key now selects a preset AI; possible values include
"ai_default_rca", "experimental_ai", and "idle_ai", but custom AIs
defined by eras or modifications with an [ai] tag can also be used
* [leader_goal] now automatically sets facet ID for auto_remove
(Only if using simplified syntax; in full syntax, the ID must still be
specified in two places.)
* The AI config in the gamestate inspector is now split into multiple
sections according to the type of component.
* The following deprecated components have been removed:
* recruitment stage (name=ai_default::recruitment)
* old recruitment candidate action
(name=ai_default_rca::aspect_recruitment_phase)
* old simple move-to-targets candidate action
(name=ai_default_rca::simple_move_and_targeting_phase)
* number_of_possible_recruits_to_force_recruit aspect
* recruitment_ignore_bad_combat aspect
* recruitment_ignore_bad_movement aspect
* The recruitment aspect has been removed from the engine; however,
"recruitment" is now accepted as a synonym for
"recruitment_instructions". Old code should work without changes.
* [goal]name=protect_my_unit
* The following experimental components have been removed:
(Most of these were broken or non-functional)
* Experimental recruitment candidate action
This worked, but was worse than the default recruitment.
* Global fallback candidate action
* Akihara recruitment candidate action
* Fallback stage (functional but useless)
* Strategy Formulation with RCA stage
This worked, but was slow and unmaintained. If a maintainer
steps up, it may be restored later.
* Several of the development AIs available in debug mode in the
multiplayer setup menu
* The "Strong AI" has been renamed to "Default AI (RCA) with Alternate
Recruiting" and is now only available in debug mode.
* Lua components (goals, aspects, stages, and candidate actions)
now support an [args] subtag, which passes any sort of WML data
to the Lua code - this works similarly to [args] in a [lua] tag,
though not quite identically.
* Aspect syntax with [aspect] tag has been fully generalized so that
[aspect], [facet], and [default] are all exactly the same in terms of
what contents they expect. (Except that [facet] additionally takes
turns= and time_of_day=.) The most useful consequences of this:
* You can nest a [facet] inside another [facet] if the outer one has
name=composite_aspect
* You can define a [facet] with the Lua engine
* invalidate_on_tod_change= has been implemented for aspects. It applies
when the bonus resulting from time of day modifiers (excluding any
illumination abilities) changes at any location on the map. Thus, it
occurs on average less often than invalidate_on_turn_start and may
be best combined with an explicit invalidate_on_turn_start=no.
* Minor shorthands have been introduced in the recruitment_instructions
aspect:
* [pattern] tag is like [recruit] with pattern=yes
* [total] tag is like [recruit] with total=yes
* If no [recruit] tag is present, a default is added with importance=0.
This means recruitment will happen even with only [limit] tags.
* Extensions to [modify_ai]:
* [modify_ai]action=change works on aspects, using path=aspect[id].
Useful if you need to change all the facets at once, but note that
it also wipes the [default] facet.
* The [default] facet can be referenced as facet[default_facet].
This should rarely be needed, but is there in case it is.
It also makes them easily identifiable in the inspector.
* Automatically copy over the id= with action=change, if the
new component did not specify one.
* It can add, remove, and change jobs and limits in the
recruitment_instructions aspect. The path to use for this is
aspect[recruitment_instructions].facet[facet_id].recruit[recruit_id].
(For a limit, replace "recruit" with "limit".)
The [recruit] and [limit] tags now support id keys for this.
* Lua API:
* wesnoth.match_unit can now take a location (rather than a unit) as
the optional third parameter. This will cause the filter to consider
the unit to be on that location instead of its actual location.
This even works for units on a recall list.
* wesnoth.highlight_hex is no longer deprecated, but its effect is
slightly different from the old one. It outlines a hex, nothing more.
* wesnoth.select_hex is now deprecated in favour of the new
wesnoth.select_unit (or u:select_unit). The effect is almost the same
(with the exception that it does not outline the hex if true is
passed as the second argument), but this name change was done to
emphasize that it acts on a unit, more than a location.
* New wesnoth.set_time_of_day function which sets the current time
of day, taken either as the time ID (eg "second_watch") or the index
of the time in the overall schedule (eg, 1 would be dawn in the default
schedule). Optional second argument takes a time area ID, to set
local instead of global time.
* New wesnoth.add_fog and wesnoth.remove_fog functions allow changing fog
on the map. The [lift_fog] and [clear_fog] tags now use this.
* New wesnoth.add_sound_source, wesnoth.remove_sound_source, and
wesnoth.get_sound_source functions to allow manipulation of sound
sources. The [sound_source] and [remove_sound_source] now use these.
* New wesnoth.log function for printing log messages. The [wml_message]
and [deprecated_message] tags now use this.
* WML tables defined in Lua now accept string keys with array values
(where "array" is a table whose keys are all integers). This joins
the elements of the array with commas and produces a single string
value. eg {x = {1,2,3}} is equivalent to {x = "1,2,3"}.
* wesnoth.effects table can now be used to alter the behaviour of
built-in effects - for example, to add a new feature to
[effect]apply_to=attack. It also now supports effect descriptions,
for use by the [trait] tag.
* Additional fields in unit proxy:
* usage, cost - self-explanatory
* traits - list of the IDs of all traits
* abilities - list of the IDs of all abilities
* Additional fields in table returned by wesnoth.get_terrain_info:
* icon, editor_image, light
* Additional fields in unit type proxu:
* race, id, alignment
* attacks, which returns the same thing as unit.attacks
* abilities, same as unit.abilities
* LuaAI:
* The table returned by check_*() now has a "result" field which
gives a description of the action's result; similar to "status"
but more descriptive.
* Target tables now use a descriptive name for "type", instead of
an integer. This applies to the elements of the table returned by
ai.get_targets() and also to the elements of tables returned by
Lua goals. (However, Lua goals that used integers will continue
to work for now.)
* There are some compatibility-breaking changes to Lua candidate
actions - see the release notes or the wiki for full details.
* Lua AI code can now access the AI routines through the global ai
object. This object is only accessible to AI code; it does not exist
in the general Lua global scope.
* When executing Lua goals, aspects, or candidate action evaluations,
the ai is in "read-only" mode - the read_only key is set to true,
and functions that change the gamestate, such as ai.attack(), are
missing from the table. (ai.check_*() functions are still present.)
* The ai.aspects table provides access to every aspect known by the
engine, including several that previously did not have corresponding
ai.get_*() functions. You access them as ai.aspects.avoid, for example.
The table is read-only and raises an error if you attempt to write to it.
* The way to create Lua candidate actions has changed a little. Old code
will require minor changes.
* New wesnoth.micro_ais table contains the loaders for all Micro AIs.
New loaders can easily be installed by add-ons. See any built-in
micro AI (in ai/micro_ais/mai-defs/) for an example of how to do this.
* The attacks aspect can now be defined as a Lua aspect. The code
should return a table with keys "own" and "enemy", each of which may
be either a unit filter table or a function which takes a unit as a
parameter and returns true or false.
* Wesnoth formula engine:
* Formulas in unit filters can now access nearly all unit attributes
The following attributes were renamed (old names still work, for now):
leader -> canrecruit
total_movement -> max_moves
movement_left -> moves
states -> status
* Nearly all unit type, side, weapon, and terrain attributes available
to Lua code are now also exposed to WFL. The exceptions are mainly
translatable strings.
* Unit and side WML variables are now accessible under "wml_vars".
Since WML variables don't easily translate to formula variables, the
special attributes __all_children, __children, and __attributes provide
specialized views of the variables config as a list, string-list map,
and string-value map, respectively.
* The 'special' attribute of weapons was renamed to 'specials', and it now
contains the special IDs rather than their translateable names.
* New syntax features:
* String interpolation syntax. Within a formula string (enclosed in
'single quotes'), the syntax [some_formula] interpolates the result
of the inner formula into the string. (The simplest use case is
interpolating the values of variables.)
* String can now escape special characters:
['] single quote, [(] open square bracket, [)] close square bracket
* New 'in' operator which tests if a list contains an item or if a map
contains a key.
* New concatenation operator a..b which works on strings and lists
* New range operator a~b which produces a list of consecutive integers
This can also be used for "list slicing" - eg my_list[3~5] returns
a new list containing elements 3 through 5 of my_list.
* Lists can be used as an index for a list. This is "selection indexing"
and returns a new list with only the elements specified by the indexing
list.
* Function definitions (using the def keyword) are now supported in all
formula contexts, which means that they can be used outside FormulaAI.
However, non-FormulaAI functions are currently local to the formula
that declares them.
* Maps containing string keys that are valid identifiers can now be
indexed with the dot operator instead of the indexing operator.
* Strings can now be indexed via 'string'.char[n]. Also supported are
'string'.word[n] and 'string'.item[n] (the latter splits on commas)
* Changes to core functions:
* head() takes an optional argument - if present, a sublist is returned.
* abs(), max(), min() now work on decimal numbers
* reduce() function can specify an optional initial accumulator
This will be returned for an empty list instead of null.
* substring() function can now accept a negative size parameter
This counts backwards from the specified offset
A size of -1 is the same as 1.
* if() can take two arguments; returns null if the condition is false
* tolist() will now invert the effect of tomap()
* debug_print() now shows in console if debug mode is on
* New core functions:
* Trig functions tan, acos, asin, atan have been added. (Sin and cos
existed since at least 1.9 but were undocumented until very recently.)
* Other common math functions - root(), sqrt(), cbrt(), log(), exp()
* hypot(x,y) function calculates sqrt(x*x+y*y) with minimal error
* pi() returning the circle ratio
* tail() - opposite of head()
* reverse() function for strings and lists
* zip() function - converts [[1,2,3],[4,5,6]] to [[1,4],[2,5],[3,6]]
* take_while() function returns items from a list until the first one
that fails a condition
* find_string() locates a substring within a string
* replace() replaces a sequence within a string
* type() function checks the type of a formula result
* distance_between() - this was promoted from FormulaAI to core
* pair() function that produces a key-value pair suitable for
passing to tomap() - this also means key-value pairs are now
serializable (relevant in FormulaAI)
* Bugfixes:
* Dice operator is now synced (where possible)
* Modulus (%) operator now works on decimal numbers
* Exponentiation (^) operator is now right-associative
* Fix several math operations returning a very large negative number when
the operation was invalid (for example, (-2) ^ 0.5).
Now they return null instead.
* Formula debugger (accessed with the debug() function):
* Now works again, but is skipped unless in debug mode
* No longer explodes on formulas using less-than
* Some better information and formatting in the execution trace
* You can now step through "where" variable assignments
* FormulaAI no longer starts recruiting if a formula evaluates to the
string 'recruit'.
* Deprecated:
* The fai/faiend keywords are deprecated as part of a move to clearly
define the difference between "Wesnoth Formula Language", the language,
and "FormulaAI", the AI engine that uses the language. For now they
still work, but any future code should use wfl/wflend instead.
Use of the .fai file extension is still fine for FormulaAI code;
for other formula code in a separate file, .wfl is recommended instead.
* Miscellaneous and bug fixes:
* Resolve translated logo images not being used (bug #24357)
* Ported the "hexometer" tool from Bash to Python 3
Version 1.13.4:
* Language and i18n:
* Updated translations: British English, Russian
* Graphics:
* Improved or new terrain graphics: Mine Walls (replaces Hewn Cave Walls).
* User interface:
* Fix vertical alignment of individual attacks listed in the Attack Unit
dialog.
* Removed extra padding below unit stats in the Attack Unit dialog.
* Miscellaneous and bug fixes:
* Fix non-deterministic crashes in the Attack Unit dialog resulting from
invalid memory references (regression introduced in 1.13.3).
* Fix uninitialized variable in event handling code (bug #24498).
Version 1.13.3:
* Greatly improved SDL 2 support. SDL 2 is now used by default build when
building. This fixes the following bugs, among others:
* Bug #18112: Color cursors cause slow mouse movement at menus
* Bug #19666: When I resize windows during dialog I lose the menu buttons
* Bug #20332: Cursor not mapping correctly on Retina display when in Windowed mode
* Bug #23820: SDL 2 alpha blending issues
* Bug #23821: Text input is broken in GUI1 under SDL 2
* Bug #23908: SDL 2 SDL_BlitSurface causes crashes
* Bug #23918: UI graphics garbled on OS X 10.11 El Capitan
* Bug #23934: Resize actions are laggy
* Bug #24138: SDL 2 crash on resize after starting a game and returning to the title screen
* Bug #24209: Screen becomes black upon minimise and restore
* bug #24212: SDL 2 build handled input incorrectly once window focus is lost when menus are open
* Bug #24213: SDL 2 build leaves menu items stuck if window dimensions change while open
* Bug #24214: SDL 2 build handles fullscreen toggle badly
* Bug #24260: Menu and Action buttons disappear on resize
* Bug #24261: Area under Objectives not redrawn on resize
* Bug #24294: Doubled-up GUI1 dialogs don't redraw the secondary in SDL2
* Bug #24477: Segfault when launching Credits
* Campaigns:
* Liberty:
* Added some animations for the Rogue Mage line.
@ -12,39 +323,62 @@ Version 1.13.2+dev:
* Updated sprites for Naga Hunter, Naga Guardian line, Crab Man.
* Crab Man changed to Monster Crab.
* Graphics:
* Improved terrain graphics: Stones with Sand Drifts, Igloo Village.
* Improved or new terrain graphics: Stones with Sand Drifts, Igloo Village,
Adobe Village.
* Added option for toggling off water animations to Preferences -> Display.
* Language and i18n:
* Updated translations: British English, Russian, Swedish
* New translation: Asturian
* Updated translations: British English, Galician, Russian, Swedish
* Sound effects:
* Fixed various subtle timing problems with attack sounds.
* Terrains:
* New terrain: Desert Mountains (Mdy), Impassable Desert Mountains (Mdy^Xm),
Ruined Adobe Village (^Vdr).
* User Interface:
* GUI1 comboboxes now use the thinner menu frame style.
* Implemented a new GUI2 Attack dialog
* Added gui2 comboboxes.
* Removed prompt when purging the WML cache from Preferences.
* Implemented a new GUI2 Preferences dialog
* Implemented a new font scaling option on the Display panel.
* Selecting an entry in the friend/ignore list panel now copies it to the
input field; this makes it easier to edit friend/ignore notes.
* WML engine:
* Added new event "unit placed", which triggers when (and regardless of how)
a unit appears on the map.
* Added support for color= in [unstore_unit] and [print]
* removed network and network_ai controlles, whether a side is networked is
now stored in the is_local attribute.
* Eventnames (received in wesnoth.game_events.on_event) now have all their
spaces replaced with underscores.
* lua can now read/write the 'persistent' attribute of sides.
* lua can now read/write the 'alignment' attribute of units.
* Added {CURRENT_FILE} and {CURRENT_DIRECTORY} macros.
* add support for relative dirs in wesnoth.dofile/require
* Added name= and write_name= attributes in [item]
* Added description_alignment= key to [campaign]
* Fixed [put_to_recall_list] not working correctly (bug #24390)
* Miscellaneous and bug fixes:
* Fix the new log code on Windows to actually use Unicode-aware functions
in a couple of places so Wesnoth does not quit on startup when trying to
relocate the log file to a path with Unicode characters (bug #22897,
definitely fixed this time).
* Decreased high memory consumption caused by the animated water.
* Fix bug #23108: exclude aborted attacks from statistics
* imgcheck now runs on Python 3
* Fix bug #15259: Secondary click uses control-click instead of command-click in OS X
* New hi-res icon using new logo for OS X
* Greatly improved SDL2 support. SDL2 is now the default build.
* Fix bug #18112: Color cursor causes slow mouse movement at menus
* Fix bug #20332: Cursor not mapping correctly on Retina display when in Windowed mode
* Fix bug #24138: SDL2 crash on resize after starting a game and returning to title screen
* Fix bug #23908: SDL2 SDL_BlitSurface Causes Crashes
* Fix bug #23821: Text input is broken in GUI1 under SDL2
* Fix bug #23820: SDL 2 alpha blending issues
* Fix bug #23918: UI Graphics in OS X 10.11 El Capitan
* Fix bug #18112: Color cursor causes slow mouse movement at menus
* Fix bug #24214: SDL2 build handles fullscreen toggle badly
* Fix bug #23934: Resize actions are laggy
* Fix bug #24209: Screen becomes black upon minimise and restore
* Fix bug #24213: SDL2 build leaves menu items stuck if window dimensions change when open
* Removed the "wmlmove" Python tool
* Fixed [event] in [unit_type].
* Fixed oos caused by mp replay turn feature.
* Fixed oos bugs caused by plattform dependent rounding from double to int in lua.
* Fixed custom (lua-defined) scenario tags beeing removed from [replay_start]
* Fixed savefile bloat caused by unit variations (walking corpses)
* Fixed preferences file bloat caused by null-command hot-keys (bug #21969)
* Fixed clearing of default hot-keys not working (bugs #21983/#22218/#23981)
* Fix [for] not correctly handling the case when the array length changes
during iteration
* Fix variables in [message][command] (bug #24288)
Version 1.13.2:
* Add-ons client:
@ -158,7 +492,7 @@ Version 1.13.2:
* WML engine:
* controller= in side filters can now be used in mp games for unsynced code
* Added [effect] apply_to=max_expereince set=<value>
* Added enable_if= to mod and era events
* Added enable_if= to mod and era events
* Added $varname?default_value| in variable substitution
* Fixed side_for= parameter in [message]s with input
* New actionwml tag [on_undo] contains actionswml that is executed when the

View file

@ -56,15 +56,9 @@
#ifdef TEST
{scenario-test.cfg}
{scenario-formula.cfg}
{scenario-formula-recruitment.cfg}
{scenario-poisoning.cfg}
{scenario-leaders.cfg}
{scenario-movethrough.cfg}
{ai/scenarios/scenario-AI_Arena_small.cfg}
{ai/scenarios/scenario-test_move_to_targets.cfg}
{ai/scenarios/scenario-lua-ai.cfg}
{ai/scenarios/scenario-no_engine.cfg}
{ai/scenarios/}
{ai/micro_ais/scenarios/}
#define DONT_RELOAD_CORE
#enddef

View file

@ -1,28 +1,22 @@
#textdomain wesnoth
[advanced_preference]
field=compress_saves
# po: Associated save_compression_short^ entries need to be as short as
# po: possible to avoid stretching the advanced preferences list too
# po: much.
name= _ "Compress saved games"
type=combo
default=gzip
[option]
id=gzip
name= _ "save_compression^Gzip"
name_short= _ "save_compression_short^gzip"
description= _ "save_compression_desc^Default compression, faster"
[/option]
[option]
id=bzip2
name= _ "save_compression^Bzip2"
name_short= _ "save_compression_short^bzip2"
description= _ "save_compression_desc^Best compression, slower"
[/option]
[option]
id=none
name= _ "save_compression^No"
name_short= _ "save_compression_short^no"
description= _ "save_compression_desc^Large plain text files"
[/option]
[/advanced_preference]
@ -55,20 +49,6 @@
default=yes
[/advanced_preference]
[advanced_preference]
field=local_tod_lighting
name= _ "Local time of day area lighting"
type=boolean
default=yes
[/advanced_preference]
[advanced_preference]
field=startup_effect
name= _ "Show titlescreen animation"
type=boolean
default=no
[/advanced_preference]
[advanced_preference]
field=show_combat
name= _ "Show combat"
@ -87,13 +67,6 @@
step=5
[/advanced_preference]
[advanced_preference]
field=lobby_whisper_friends_only
name= _ "Accept whispers from friends only"
type=boolean
default=no
[/advanced_preference]
[advanced_preference]
field=lobby_auto_open_whisper_windows
name= _ "Auto-open whisper windows in lobby"
@ -108,14 +81,6 @@
default=yes
[/advanced_preference]
[advanced_preference]
field=flip_time
name= _ "Reverse time graphics"
description= _ "Choose whether the sun moves left-to-right or right-to-left"
type=boolean
default=no
[/advanced_preference]
[advanced_preference]
field=scroll_to_action
name= _ "Follow unit actions"
@ -205,6 +170,18 @@
default=no
[/advanced_preference]
[advanced_preference]
field=advanced_graphic_options
name= _ "Graphics scaling options"
type=custom
[/advanced_preference]
[advanced_preference]
field=orb_color
name= _ "Customize orb colors"
type=custom
[/advanced_preference]
#ifdef __UNUSED__
[advanced_preference]
field=joystick_support_enabled

View file

@ -8,7 +8,6 @@
id=ai_default_rca
description=_"Multiplayer_AI^Default AI (RCA)" # wmllint: no spellcheck
# RCA := Register Candidate Action; more info at http://forums.wesnoth.org/viewtopic.php?p=419625#p419625
version=10710
[stage]
id=main_loop
name=ai_default_rca::candidate_action_evaluation_loop

View file

@ -1,34 +0,0 @@
#textdomain wesnoth-ai
#ifndef AI_CA_GOTO
{core/macros/ai.cfg}
{core/macros/ai_candidate_actions.cfg}
#endif
[ai]
id=ai_default_rca_strong
description=_"Multiplayer_AI^Strong AI (RCA)" # wmllint: no spellcheck
# RCA := Register Candidate Action; more info at http://forums.wesnoth.org/viewtopic.php?p=419625#p419625
version=10710
[stage]
id=main_loop
name=ai_default_rca::candidate_action_evaluation_loop
{AI_CA_GOTO}
{AI_CA_RECRUITMENT}
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_COMBAT}
{AI_CA_HEALING}
{AI_CA_VILLAGES}
{AI_CA_RETREAT}
{AI_CA_MOVE_TO_TARGETS}
{AI_CA_LEADER_SHARES_KEEP}
[/stage]
### Aspects for a strong AI ###
{AI_SIMPLE_ALWAYS_ASPECT recruitment_diversity 0.8}
{AI_SIMPLE_ALWAYS_ASPECT recruitment_randomness 0}
{AI_SIMPLE_ALWAYS_ASPECT villages_per_scout 0}
{AI_ASPECT recruitment_save_gold {AI_DEACTIVATE_SAVE_GOLD} }
[/ai]

View file

@ -0,0 +1,33 @@
#textdomain wesnoth-ai
#ifndef AI_CA_GOTO
{core/macros/ai.cfg}
{core/macros/ai_candidate_actions.cfg}
#endif
[ai]
id=ai_default_rca_alternate_recruiting
description=_"Multiplayer_AI^Dev AI: Default AI (RCA) with Alternate Recruiting" # wmllint: no spellcheck
# RCA := Register Candidate Action; more info at http://forums.wesnoth.org/viewtopic.php?p=419625#p419625
[stage]
id=main_loop
name=ai_default_rca::candidate_action_evaluation_loop
{AI_CA_GOTO}
{AI_CA_RECRUITMENT}
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_COMBAT}
{AI_CA_HEALING}
{AI_CA_VILLAGES}
{AI_CA_RETREAT}
{AI_CA_MOVE_TO_TARGETS}
{AI_CA_LEADER_SHARES_KEEP}
[/stage]
### Aspects for a strong AI ###
{AI_SIMPLE_ALWAYS_ASPECT recruitment_diversity 0.8}
{AI_SIMPLE_ALWAYS_ASPECT recruitment_randomness 0}
{AI_SIMPLE_ALWAYS_ASPECT villages_per_scout 0}
{AI_ASPECT recruitment_save_gold {AI_DEACTIVATE_SAVE_GOLD} }
[/ai]

View file

@ -1,32 +0,0 @@
#textdomain wesnoth-ai
#ifndef AI_CA_GOTO
{core/macros/ai_candidate_actions.cfg}
#endif
[ai]
id=ai_old_recruitment
description=_"Multiplayer_AI^Dev AI: Default + Old Recruitment" # wmllint: no spellcheck
# RCA := Register Candidate Action; more info at http://forums.wesnoth.org/viewtopic.php?p=419625#p419625
version=10710
[stage]
id=main_loop
name=ai_default_rca::candidate_action_evaluation_loop
{AI_CA_GOTO}
[candidate_action]
id=recruitment
engine=cpp
name=ai_default_rca::aspect_recruitment_phase
max_score=180000
score=180000
[/candidate_action]
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_COMBAT}
{AI_CA_HEALING}
{AI_CA_VILLAGES}
{AI_CA_RETREAT}
{AI_CA_MOVE_TO_TARGETS}
{AI_CA_LEADER_SHARES_KEEP}
[/stage]
[/ai]

View file

@ -1,25 +0,0 @@
#textdomain wesnoth-ai
#ifndef AI_CA_GOTO
{core/macros/ai_candidate_actions.cfg}
#endif
[ai]
id=ai_sf_with_rca
description=_"Multiplayer_AI^Dev AI: Strategy formulation with RCA " # wmllint: no spellcheck
version=11300
[stage]
id=main_loop
name=testing_ai_default::strategy_formulation_with_rca
{AI_CA_GOTO}
{AI_CA_RECRUITMENT}
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_COMBAT}
{AI_CA_HEALING}
{AI_CA_VILLAGES}
{AI_CA_RETREAT}
{AI_CA_MOVE_TO_TARGETS}
{AI_CA_LEADER_SHARES_KEEP}
[/stage]
[/ai]

View file

@ -1,30 +0,0 @@
#textdomain wesnoth-ai
#ifndef AI_CA_GOTO
{core/macros/ai_candidate_actions.cfg}
#endif
[ai]
id=ai_akihara
description=_"Multiplayer_AI^Dev AI: Default + Experimental Recruitment (C++ Akihara)" # wmllint: no spellcheck
version=10800
[stage]
id=main_loop
name=ai_default_rca::candidate_action_evaluation_loop
{AI_CA_GOTO}
[candidate_action]
id=alternate_recruitment
engine=cpp
name=akihara_recruitment::recruitment
max_score={AI_CA_RECRUITMENT_SCORE}
score={AI_CA_RECRUITMENT_SCORE}
[/candidate_action]
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_COMBAT}
{AI_CA_HEALING}
{AI_CA_VILLAGES}
{AI_CA_RETREAT}
{AI_CA_MOVE_TO_TARGETS}
[/stage]
[/ai]

View file

@ -9,14 +9,17 @@
id=formula_ai # id is needed to uniquely identify a MP AI, it is not needed in the scenario AI
description=_"Multiplayer_AI^Dev AI: Default + Experimental Recruitment (Formula AI)" # wmllint: no spellcheck
# this description is, again, needed for MP AI (it shows in AI list under this description
version=10710 # no spaces here, version should be parsed as int. This version marker is a sign that ALL snippets of AI are written using new syntax
{AI_SIMPLE_FORMULA_AI_EXPERIMENTAL_RECRUITMENT}
[stage]
engine=fai
name=side_formulas
move="{ai/formula/new_recruitment.fai}"
[/stage]
[stage]
id=main_loop
name=ai_default_rca::candidate_action_evaluation_loop
{AI_CA_GOTO}
{AI_CA_RECRUITMENT}
#{AI_CA_RECRUITMENT}
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_COMBAT}

View file

@ -5,9 +5,8 @@
#endif
[ai]
id=default_ai_poisoning
id=formula_ai_poisoning
description=_"Multiplayer_AI^Dev AI: Default + Poisoning (Formula AI)" # wmllint: no spellcheck
version=10710
[stage]
id=main_loop
name=ai_default_rca::candidate_action_evaluation_loop

View file

@ -1,7 +1,10 @@
#textdomain wesnoth-ai
[ai]
id=ai_idle
id=idle_ai
description=_"Multiplayer_AI^Dev AI: Idle AI" # wmllint: no spellcheck
version=10703
#well, we can just fall back to idle ai. But we can just do nothing, and it will work (if no additional stages are brought in by other config sources - MP faction config, scenario config, era config, defaults, etc
# Needs to define at least one stage; otherwise, the default AI will be injected
# when used with [modify_side]switch_ai
[stage]
name=empty
[/stage]
[/ai]

View file

@ -1,30 +0,0 @@
#textdomain wesnoth-ai
#ifndef AI_CA_GOTO
{core/macros/ai_candidate_actions.cfg}
#endif
[ai]
id=testing_ai_recruitment
description=_"Multiplayer_AI^Dev AI: Default + Experimental Recruitment (C++)" # wmllint: no spellcheck
version=10800
[stage]
id=main_loop
name=ai_default_rca::candidate_action_evaluation_loop
{AI_CA_GOTO}
[candidate_action]
id=alternate_recruitment
engine=cpp
name=ai_default_rca::testing_recruitment_phase
max_score={AI_CA_RECRUITMENT_SCORE}
score={AI_CA_RECRUITMENT_SCORE}
[/candidate_action]
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_COMBAT}
{AI_CA_HEALING}
{AI_CA_VILLAGES}
{AI_CA_RETREAT}
{AI_CA_MOVE_TO_TARGETS}
[/stage]
[/ai]

View file

@ -35,7 +35,7 @@ def get_important_locations(ai* )
def enemy_leaders( ai* )
sum(
map( enemies, 'enemy',
filter( units_of_side[enemy], leader )
filter( units_of_side[enemy], canrecruit )
)
);
@ -123,7 +123,7 @@ def locally_normalize_to_lowest( input_map )
# Returns a List of all enemy leaders #
# (for some reason this function is defined twice) #
def enemy_leaders(ai*)
map( enemies, 'enemy_side', find(units_of_side[enemy_side], leader ) );
map( enemies, 'enemy_side', find(units_of_side[enemy_side], canrecruit ) );
# UNUSED FUNCTION #
# Returns a Map. #
@ -249,7 +249,7 @@ def movement_eval(ai*, recruits_id_map)
my_recruits_movement_cost( ai ),
tomap( recruits_id_map,
map( my_recruits,
total_movement
max_moves
)
)
),
@ -357,7 +357,7 @@ if( vars.side_terrain,
mark_important_locations( self,
calculate_map_ownership(
recruits_of_side,
map(filter(sum(units_of_side), leader), loc),
map(filter(sum(units_of_side), canrecruit), loc),
4, 7, 4
)
)

View file

@ -8,7 +8,7 @@ def opening(ai*)
move(loc(11,23), loc(14,22)) ],
if(turn = 2, [
move(loc(11,21),loc(13,17)),
if(unit_at(loc(11,22)).total_movement = 6,
if(unit_at(loc(11,22)).max_moves = 6,
move(loc(11,22),loc(13,18)),
move(loc(11,22),loc(15,19))),
move(loc(10,22),loc(7,19)),
@ -75,7 +75,7 @@ def rate_village_capture(ai*,src,dst) village_value(ai);
def rate_village_proximity(ai*, unit, dst)
if(distance = 1,
0,
village_value(ai)/(distance/unit.total_movement + 1))
village_value(ai)/(distance/unit.max_moves + 1))
where distance = distance_to_nearest_unowned_village(dst);
def rate_move(ai*,src,dst)

View file

@ -25,7 +25,7 @@ where desired_path = shortest_path( me.loc, me.vars.next_step );
def move_ahead(ai*, me)
if( enemy_units,
if( distance_between( closest_unit(ai, me).loc, me.loc ) > me.movement_left-1,
if( distance_between( closest_unit(ai, me).loc, me.loc ) > me.moves-1,
move_partial(
me.loc,
me.vars.next_step
@ -50,7 +50,7 @@ def patrol_move(ai*, me)
move_ahead(ai,me)
);
if( me.movement_left = 0,
if( me.moves = 0,
end,
if(attack,
attack,

View file

@ -8,6 +8,6 @@ def get_best_defense_loc(moves, attacker, enemy)
attack(me.loc, get_best_defense_loc(my_moves.moves, me, target), target.loc, att_weap)
where att_weap = index_of(['poison'],map(me.attacks,special))
where att_weap = index_of(['poison'],map(me.attacks,specials))
faiend

View file

@ -16,7 +16,7 @@ min( map(
)
) +
#leader is always a good target! #
if( target.leader, 50, 0 ) +
if( target.canrecruit, 50, 0 ) +
# consider target abilities if needed #
if( target.abilities,
if( index_of('regenerates', target.abilities) != -1, -10, 0 ) +

View file

@ -32,7 +32,7 @@ def get_important_locations(ai* )
def enemy_leaders( ai* )
sum(
map( enemies, 'enemy',
filter( units_of_side[enemy], leader )
filter( units_of_side[enemy], canrecruit )
)
);
@ -112,7 +112,7 @@ def locally_normalize_to_lowest( input_map )
# look for who we fight against #
def enemy_leaders(ai*)
map( enemies, 'enemy_side', find(units_of_side[enemy_side], leader ) );
map( enemies, 'enemy_side', find(units_of_side[enemy_side], canrecruit ) );
def distance_to_enemies(ai*)
@ -206,7 +206,7 @@ def combine_maps_div( map_A, map_B )
[ locally_normalize_to_highest(my_recruits_defense(self)),
tomap(map(my_recruits, id),map(my_recruits, hitpoints) ),
locally_normalize_to_lowest(my_recruits_movement_cost(self)),
tomap(map(my_recruits, id),map(my_recruits, total_movement))]
tomap(map(my_recruits, id),map(my_recruits, max_moves))]
#
def consider_unit_cost(ai*)
@ -233,7 +233,7 @@ def movement_eval(ai*, recruits_id_map)
my_recruits_movement_cost( ai ),
tomap( recruits_id_map,
map( my_recruits,
total_movement
max_moves
)
)
),
@ -324,7 +324,7 @@ if( vars.side_terrain,
mark_important_locations( self,
calculate_map_ownership(
recruits_of_side,
map(filter(sum(units_of_side), leader), loc),
map(filter(sum(units_of_side), canrecruit), loc),
4, 7, 4
)
)

View file

@ -6,10 +6,10 @@ if( size(shroud) = 0,
-5,
if( size(enemies_in_range) != 0,
-5,
if( me.total_movement > 5,
if( me.max_moves > 5,
50,
-5))))
where enemies_in_range = filter( enemy_units, 'enemy', distance_between( me.loc, enemy.loc ) < me.total_movement),
where enemies_in_range = filter( enemy_units, 'enemy', distance_between( me.loc, enemy.loc ) < me.max_moves),
shroud = find_shroud()
faiend

View file

@ -2,21 +2,11 @@
-- This is the engine used by the Lua AI when no engine is
-- defined specifically in the [side] tag
return {
get_ai = function(ai)
local my_ai = {}
-- This provides a cache level for the move map functions,
-- making them a bit easier to use
local ai_stdlib = wesnoth.require('ai/lua/stdlib.lua')
ai_stdlib.init(ai)
local ai_stdlib = wesnoth.require('ai/lua/stdlib.lua')
ai_stdlib.init(ai)
-- Make the ai table available to the eval/exec functions
function my_ai:get_ai()
return ai
end
-- Make the persistent data table available to the eval/exec functions
my_ai.data = {}
return my_ai
end
}
-- This is only returned for minor backwards compatibility
local p, d = ...
return {data = d}

View file

@ -2,15 +2,20 @@
example_ca = {}
function example_ca:eval(ai)
wesnoth.message("External eval says hi!")
return 10000
function example_ca:evaluation()
wesnoth.message("External CA evaluation says hi.")
return 10000
end
function example_ca:exec(ai)
wesnoth.message("External CA exec attacks!")
ai.attack(2, 12, 3, 12, 1, 1) -- showcasing the presence of the AI table
function example_ca:execution()
wesnoth.message("External CA execution attacks.")
-- Note that there is no check whether these attacks are possible.
-- The CA will therefore be blacklisted the second time it gets called.
ai.attack(2, 12, 3, 12, 1, 1)
ai.attack(3, 13, 3, 12, 2, 1)
ai.attack(3, 11, 3, 12)
end
return example_ca

View file

@ -440,9 +440,7 @@ return {
return score
end
function ai_cas:recruit_rushers_exec(ai_local)
if ai_local then ai = ai_local end
function ai_cas:recruit_rushers_exec()
if AH.show_messages() then W.message { speaker = 'narrator', message = 'Recruiting' } end
local enemy_counts = recruit_data.recruit.enemy_counts

View file

@ -1,33 +1,36 @@
function patrol_gen(n, wp) -- n is the name of the unit, like Kiressh
-- wp - a table of waypoint tables of form {x,y}
function patrol_gen(n, wp)
-- n is the name of the unit, like Kiressh
-- wp - a table of waypoint tables of form {x,y}
local unit = wesnoth.get_units({name=n})[1]
local x, y = unit.x, unit.y
local wpn = 1 --WayPoint Number - we have to remember which waypoint we are heading to
wesnoth.message('data/ai/lua/patrol.lua is deprecated. Use the Patrols Micro AI instead.')
if (x == wp[1].x and y == wp[1].y) then
wpn = wpn + 1
--w1, w2 = w2, w2 -- if we are standing on the first waypoint, swap them
end
local unit = wesnoth.get_units({name=n})[1]
--local waypoints = {w1, w2} -- this form might be just received from the args
local wpcount = # wp
local x, y = unit.x, unit.y
local wpn = 1 --WayPoint Number - we have to remember which waypoint we are heading to
local patrol = {}
patrol.exec = function()
x, y = unit.x, unit.y
if (x == wp[wpn].x and y == wp[wpn].y) then
wpn = wpn % wpcount + 1 -- advance by one waypoint(this construct loops in range [1, wpcount])
end
ai.move_full(unit, wp[wpn].x, wp[wpn].y) -- @note: should we change this to ai.move()?
end
patrol.eval = function()
return 300000
end
if (x == wp[1].x and y == wp[1].y) then
wpn = wpn + 1
--w1, w2 = w2, w2 -- if we are standing on the first waypoint, swap them
end
return patrol
--local waypoints = {w1, w2} -- this form might be just received from the args
local wpcount = # wp
local patrol = {}
patrol.exec = function()
x, y = unit.x, unit.y
if (x == wp[wpn].x and y == wp[wpn].y) then
wpn = wpn % wpcount + 1 -- advance by one waypoint(this construct loops in range [1, wpcount])
end
ai.move_full(unit, wp[wpn].x, wp[wpn].y) -- @note: should we change this to ai.move()?
end
patrol.eval = function()
return 300000
end
return patrol
end

View file

@ -1,35 +0,0 @@
Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Hh , Hh , Hh , Wo , Wo , Wo , Hh , Wo , Hh , Wo , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo
Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo
Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Ww , Wo , Ww , Wo , Ww , Wo , Ww , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo , Wo
Wo , Wo , Wo , Wo , Ww , Wo , Ww , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Gg , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo
Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo
Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo
Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Gs , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Gs , Gs , Gs , Gs , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Gs , Gs , Gs , Gs , Gs , Gs , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Gs , Gs , Gs , Gs , Ch , Ch , Ch , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Ch , Ch , Ch , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ch , Ch , Ch , Hh , Hh , Hh , Hh , Ww , Wo , Wo , Wo , Wo , Wo
Wo , Wo , Wo , Wo , Ww , Ww , Ww , Ww , Gs , Gs , Gs , Gs , Ch , 1 Kh , Ch , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Ch , 2 Kh , Ch , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ch , 3 Kh , Ch , Hh , Hh , Hh , Hh , Ww , Wo , Wo , Wo , Wo , Wo
Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Gs , Gs , Gs , Gs , Gs , Ch , Gs , Gs , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Ch , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ch , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Wo , Wo , Wo , Wo
Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Gs , Gs , Gs , Gs , Gs , Gs , Gs , Gs , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Wo , Wo , Wo , Wo
Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Gs , Gs , Gs , Gs , Gs , Gs , Gs , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Wo , Wo , Wo , Wo
Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Gs , Gs , Gs , Gs , Gs , Gs , Gs , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Gs , Gs , Gs , Gs , Gs , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Gs , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Wo , Wo , Wo
Wo , Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Ww , Ww , Wo , Wo , Wo , Wo , Wo
Wo , Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Ww , Wo , Wo , Wo , Wo , Wo , Wo , Wo
Wo , Wo , Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Wo , Ww , Wo , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Ww , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Ww , Ww , Ww , Ww , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Ww , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo
Wo , Wo , Wo , Wo , Wo , Wo , Wo , Ww , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Ww , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo
Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo
Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Wo , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Hh , Wo , Wo , Wo , Wo , Wo , Wo , Wo , Wo

View file

@ -6,27 +6,28 @@ local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
local function get_big_animals(cfg)
local big_animals = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and" , cfg.filter }
{ "and" , H.get_child(cfg, "filter") }
}
return big_animals
end
local ca_big_animals = {}
function ca_big_animals:evaluation(ai, cfg)
function ca_big_animals:evaluation(cfg)
if get_big_animals(cfg)[1] then return cfg.ca_score end
return 0
end
function ca_big_animals:execution(ai, cfg)
function ca_big_animals:execution(cfg)
-- Big animals just move toward a goal that gets (re)set occasionally
-- and attack whatever is in their range (except for some units that they avoid)
local big_animals = get_big_animals(cfg)
local avoid_tag = H.get_child(cfg, "avoid_unit")
local avoid_map = LS.create()
if cfg.avoid_unit then
if avoid_tag then
avoid_map = LS.of_pairs(wesnoth.get_locations { radius = 1,
{ "filter", { { "and", cfg.avoid_unit },
{ "filter", { { "and", avoid_tag },
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
} }
})
@ -38,7 +39,7 @@ function ca_big_animals:execution(ai, cfg)
-- Unit gets a new goal if none is set or on any move with a 10% random chance
local r = math.random(10)
if (not goal.goal_x) or (r == 1) then
local locs = AH.get_passable_locations(cfg.filter_location or {})
local locs = AH.get_passable_locations(H.get_child(cfg, "filter_location") or {})
local rand = math.random(#locs)
goal.goal_x, goal.goal_y = locs[rand][1], locs[rand][2]
@ -46,7 +47,7 @@ function ca_big_animals:execution(ai, cfg)
end
local reach_map = AH.get_reachable_unocc(unit)
local wander_terrain = cfg.filter_location_wander or {}
local wander_terrain = H.get_child(cfg, "filter_location_wander") or {}
reach_map:iter( function(x, y, v)
-- Remove tiles that do not comform to the wander terrain filter
if (not wesnoth.match_location(x, y, wander_terrain)) then

View file

@ -3,7 +3,7 @@ local H = wesnoth.require "lua/helper.lua"
local ca_bottleneck_attack = {}
function ca_bottleneck_attack:evaluation(ai, cfg, self)
function ca_bottleneck_attack:evaluation(cfg, data)
local attackers = AH.get_units_with_attacks {
side = wesnoth.current.side,
{ "filter_adjacent", {
@ -55,29 +55,29 @@ function ca_bottleneck_attack:evaluation(ai, cfg, self)
if (not best_attacker) then
-- In this case we take attacks away from all units
self.data.BD_bottleneck_attacks_done = true
data.BD_bottleneck_attacks_done = true
else
self.data.BD_bottleneck_attacks_done = false
self.data.BD_attacker = best_attacker
self.data.BD_target = best_target
self.data.BD_weapon = best_weapon
data.BD_bottleneck_attacks_done = false
data.BD_attacker = best_attacker
data.BD_target = best_target
data.BD_weapon = best_weapon
end
return cfg.ca_score
end
function ca_bottleneck_attack:execution(ai, cfg, self)
if self.data.BD_bottleneck_attacks_done then
function ca_bottleneck_attack:execution(cfg, data)
if data.BD_bottleneck_attacks_done then
local units = AH.get_units_with_attacks { side = wesnoth.current.side }
for _,unit in ipairs(units) do
AH.checked_stopunit_attacks(ai, unit)
end
else
AH.checked_attack(ai, self.data.BD_attacker, self.data.BD_target, self.data.BD_weapon)
AH.checked_attack(ai, data.BD_attacker, data.BD_target, data.BD_weapon)
end
self.data.BD_attacker, self.data.BD_target, self.data.BD_weapon = nil, nil, nil
self.data.BD_bottleneck_attacks_done = nil
data.BD_attacker, data.BD_target, data.BD_weapon = nil, nil, nil
data.BD_bottleneck_attacks_done = nil
end
return ca_bottleneck_attack

View file

@ -81,19 +81,19 @@ local function bottleneck_triple_from_keys(key_x, key_y, max_value)
return AH.LS_of_triples(coords)
end
local function bottleneck_create_positioning_map(max_value, self)
local function bottleneck_create_positioning_map(max_value, data)
-- Create the positioning maps for the healers and leaders, if not given by WML keys
-- @max_value: the rating value for the first hex in the set
-- self.data.BD_def_map must have been created when this function is called.
-- data.BD_def_map must have been created when this function is called.
-- Find all locations adjacent to def_map.
-- This might include hexes on the line itself.
-- Only store those that are not in enemy territory.
local map = LS.create()
self.data.BD_def_map:iter(function(x, y, v)
data.BD_def_map:iter(function(x, y, v)
for xa,ya in H.adjacent_tiles(x, y) do
if self.data.BD_is_my_territory:get(xa, ya) then
local rating = self.data.BD_def_map:get(x, y) or 0
if data.BD_is_my_territory:get(xa, ya) then
local rating = data.BD_def_map:get(x, y) or 0
rating = rating + (map:get(xa, ya) or 0)
map:insert(xa, ya, rating)
end
@ -108,14 +108,14 @@ local function bottleneck_create_positioning_map(max_value, self)
-- We merge the defense map into this, as healers/leaders (by default)
-- can take position on the front line
map:union_merge(self.data.BD_def_map,
map:union_merge(data.BD_def_map,
function(x, y, v1, v2) return v1 or v2 end
)
return map
end
local function bottleneck_get_rating(unit, x, y, has_leadership, is_healer, self)
local function bottleneck_get_rating(unit, x, y, has_leadership, is_healer, data)
-- Calculate rating of a unit @unit at coordinates (@x,@y).
-- Don't want to extract @is_healer and @has_leadership inside this function, as it is very slow.
-- Thus they are provided as parameters from the calling function.
@ -125,18 +125,18 @@ local function bottleneck_get_rating(unit, x, y, has_leadership, is_healer, self
-- Defense positioning rating
-- We exclude healers/leaders here, as we don't necessarily want them on the front line
if (not is_healer) and (not has_leadership) then
rating = self.data.BD_def_map:get(x, y) or 0
rating = data.BD_def_map:get(x, y) or 0
end
-- Healer positioning rating
if is_healer then
local healer_rating = self.data.BD_healer_map:get(x, y) or 0
local healer_rating = data.BD_healer_map:get(x, y) or 0
if (healer_rating > rating) then rating = healer_rating end
end
-- Leadership unit positioning rating
if has_leadership then
local leadership_rating = self.data.BD_leadership_map:get(x, y) or 0
local leadership_rating = data.BD_leadership_map:get(x, y) or 0
-- If leadership unit is injured -> prefer hexes next to healers
if (unit.hitpoints < unit.max_hitpoints) then
@ -154,18 +154,18 @@ local function bottleneck_get_rating(unit, x, y, has_leadership, is_healer, self
-- Injured unit positioning
if (unit.hitpoints < unit.max_hitpoints) then
local healing_rating = self.data.BD_healing_map:get(x, y) or 0
local healing_rating = data.BD_healing_map:get(x, y) or 0
if (healing_rating > rating) then rating = healing_rating end
end
-- If this did not produce a positive rating, we add a
-- distance-based rating, to get units to the bottleneck in the first place
if (rating <= 0) and self.data.BD_is_my_territory:get(x, y) then
if (rating <= 0) and data.BD_is_my_territory:get(x, y) then
local combined_dist = 0
self.data.BD_def_map:iter(function(x_def, y_def, v)
data.BD_def_map:iter(function(x_def, y_def, v)
combined_dist = combined_dist + H.distance_between(x, y, x_def, y_def)
end)
combined_dist = combined_dist / self.data.BD_def_map:size()
combined_dist = combined_dist / data.BD_def_map:size()
rating = 1000 - combined_dist * 10.
end
@ -177,7 +177,7 @@ local function bottleneck_get_rating(unit, x, y, has_leadership, is_healer, self
return rating
end
local function bottleneck_move_out_of_way(unit_in_way, self)
local function bottleneck_move_out_of_way(unit_in_way, data)
-- Find the best move out of the way for a unit @unit_in_way and choose the
-- shortest possible move. Returns nil if no move was found.
@ -193,7 +193,7 @@ local function bottleneck_move_out_of_way(unit_in_way, self)
local best_reach, best_hex = -9e99
for _,loc in ipairs(reach) do
if self.data.BD_is_my_territory:get(loc[1], loc[2]) and (not occ_hexes:get(loc[1], loc[2])) then
if data.BD_is_my_territory:get(loc[1], loc[2]) and (not occ_hexes:get(loc[1], loc[2])) then
-- Criterion: MP left after the move has been done
if (loc[3] > best_reach) then
best_reach, best_hex = loc[3], { loc[1], loc[2] }
@ -206,9 +206,9 @@ end
local ca_bottleneck_move = {}
function ca_bottleneck_move:evaluation(ai, cfg, self)
function ca_bottleneck_move:evaluation(cfg, data)
if cfg.active_side_leader and
(not MAISD.get_mai_self_data(self.data, cfg.ai_id, "side_leader_activated"))
(not MAISD.get_mai_self_data(data, cfg.ai_id, "side_leader_activated"))
then
local can_still_recruit = false -- Enough gold left for another recruit?
for _,recruit_type in ipairs(wesnoth.sides[wesnoth.current.side].recruit) do
@ -218,12 +218,12 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
end
end
if (not can_still_recruit) then
MAISD.set_mai_self_data(self.data, cfg.ai_id, { side_leader_activated = true })
MAISD.set_mai_self_data(data, cfg.ai_id, { side_leader_activated = true })
end
end
local units = {}
if MAISD.get_mai_self_data(self.data, cfg.ai_id, "side_leader_activated") then
if MAISD.get_mai_self_data(data, cfg.ai_id, "side_leader_activated") then
units = AH.get_units_with_moves { side = wesnoth.current.side }
else
units = AH.get_units_with_moves { side = wesnoth.current.side, canrecruit = 'no' }
@ -231,54 +231,54 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
if (not units[1]) then return 0 end
-- Set up the array that tells the AI where to defend the bottleneck
self.data.BD_def_map = bottleneck_triple_from_keys(cfg.x, cfg.y, 10000)
data.BD_def_map = bottleneck_triple_from_keys(cfg.x, cfg.y, 10000)
-- Territory map, describing which hex is on AI's side of the bottleneck
-- This one is a bit expensive, esp. on large maps -> don't delete every move and reuse
-- However, after a reload, self.data.BD_is_my_territory is an empty string
-- However, after a reload, data.BD_is_my_territory is an empty string
-- -> need to recalculate in that case also (the reason is that is_my_territory is not a WML table)
if (not self.data.BD_is_my_territory) or (type(self.data.BD_is_my_territory) == 'string') then
if (not data.BD_is_my_territory) or (type(data.BD_is_my_territory) == 'string') then
local enemy_map = bottleneck_triple_from_keys(cfg.enemy_x, cfg.enemy_y, 10000)
self.data.BD_is_my_territory = bottleneck_is_my_territory(self.data.BD_def_map, enemy_map)
data.BD_is_my_territory = bottleneck_is_my_territory(data.BD_def_map, enemy_map)
end
-- Healer positioning map
if cfg.healer_x and cfg.healer_y then
self.data.BD_healer_map = bottleneck_triple_from_keys(cfg.healer_x, cfg.healer_y, 5000)
data.BD_healer_map = bottleneck_triple_from_keys(cfg.healer_x, cfg.healer_y, 5000)
else
self.data.BD_healer_map = bottleneck_create_positioning_map(5000, self)
data.BD_healer_map = bottleneck_create_positioning_map(5000, data)
end
-- Use def_map values for any healer hexes that are defined in def_map as well
self.data.BD_healer_map:inter_merge(self.data.BD_def_map,
data.BD_healer_map:inter_merge(data.BD_def_map,
function(x, y, v1, v2) return v2 or v1 end
)
-- Leadership position map
if cfg.leadership_x and cfg.leadership_y then
self.data.BD_leadership_map = bottleneck_triple_from_keys(cfg.leadership_x, cfg.leadership_y, 4000)
data.BD_leadership_map = bottleneck_triple_from_keys(cfg.leadership_x, cfg.leadership_y, 4000)
else
self.data.BD_leadership_map = bottleneck_create_positioning_map(4000, self)
data.BD_leadership_map = bottleneck_create_positioning_map(4000, data)
end
-- Use def_map values for any leadership hexes that are defined in def_map as well
self.data.BD_leadership_map:inter_merge(self.data.BD_def_map,
data.BD_leadership_map:inter_merge(data.BD_def_map,
function(x, y, v1, v2) return v2 or v1 end
)
-- Healing map: positions next to healers
-- Healers get moved with higher priority, so don't need to check their MP
local healers = wesnoth.get_units { side = wesnoth.current.side, ability = "healing" }
self.data.BD_healing_map = LS.create()
data.BD_healing_map = LS.create()
for _,healer in ipairs(healers) do
for xa,ya in H.adjacent_tiles(healer.x, healer.y) do
-- Cannot be on the line, and needs to be in own territory
if self.data.BD_is_my_territory:get(xa, ya) then
if data.BD_is_my_territory:get(xa, ya) then
local min_dist = 9e99
self.data.BD_def_map:iter( function(xd, yd, vd)
data.BD_def_map:iter( function(xd, yd, vd)
local dist_line = H.distance_between(xa, ya, xd, yd)
if (dist_line < min_dist) then min_dist = dist_line end
end)
if (min_dist > 0) then
self.data.BD_healing_map:insert(xa, ya, 3000 + min_dist) -- Farther away from enemy is good
data.BD_healing_map:insert(xa, ya, 3000 + min_dist) -- Farther away from enemy is good
end
end
end
@ -299,13 +299,13 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
local is_healer = (unit.__cfg.usage == "healer")
local has_leadership = AH.has_ability(unit, "leadership")
local rating = bottleneck_get_rating(unit, unit.x, unit.y, has_leadership, is_healer, self)
local rating = bottleneck_get_rating(unit, unit.x, unit.y, has_leadership, is_healer, data)
current_rating_map:insert(unit.x, unit.y, rating)
-- A unit that cannot move any more, (or at least cannot move out of the way)
-- must be considered to have a very high rating (it's in the best position
-- it can possibly achieve)
local best_move_away = bottleneck_move_out_of_way(unit, self)
local best_move_away = bottleneck_move_out_of_way(unit, data)
if (not best_move_away) then current_rating_map:insert(unit.x, unit.y, 20000) end
end
@ -315,7 +315,7 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
local attacks = {}
for _,enemy in ipairs(enemies) do
for xa,ya in H.adjacent_tiles(enemy.x, enemy.y) do
if self.data.BD_is_my_territory:get(xa, ya) then
if data.BD_is_my_territory:get(xa, ya) then
local unit_in_way = wesnoth.get_unit(xa, ya)
local data = { x = xa, y = ya,
defender = enemy,
@ -346,7 +346,7 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
local reach = wesnoth.find_reach(unit)
for _,loc in ipairs(reach) do
local rating = bottleneck_get_rating(unit, loc[1], loc[2], has_leadership, is_healer, self)
local rating = bottleneck_get_rating(unit, loc[1], loc[2], has_leadership, is_healer, data)
-- A move is only considered if it improves the overall rating,
-- that is, its rating must be higher than:
@ -408,7 +408,7 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
-- Small penalty if there's a unit in the way
-- We also need to check whether this unit can actually move out of the way
if attack.unit_in_way then
if bottleneck_move_out_of_way(attack.unit_in_way, self) then
if bottleneck_move_out_of_way(attack.unit_in_way, data) then
level_up_rating = level_up_rating - 0.001
else
level_up_rating = 0
@ -417,8 +417,8 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
if (level_up_rating > max_rating) then
max_rating, best_unit, best_hex = level_up_rating, unit, { loc[1], loc[2] }
self.data.BD_level_up_defender = attack.defender
self.data.BD_level_up_weapon = n_weapon
data.BD_level_up_defender = attack.defender
data.BD_level_up_weapon = n_weapon
end
end
end
@ -428,7 +428,7 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
-- Set the variables for the exec() function
if (not best_hex) then
self.data.BD_bottleneck_moves_done = true
data.BD_bottleneck_moves_done = true
else
-- If there's another unit in the best location, moving it out of the way becomes the best move
local unit_in_way = wesnoth.get_units { x = best_hex[1], y = best_hex[2],
@ -436,23 +436,23 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
}[1]
if unit_in_way then
best_hex = bottleneck_move_out_of_way(unit_in_way, self)
best_hex = bottleneck_move_out_of_way(unit_in_way, data)
best_unit = unit_in_way
self.data.BD_level_up_defender = nil
self.data.BD_level_up_weapon = nil
data.BD_level_up_defender = nil
data.BD_level_up_weapon = nil
end
self.data.BD_bottleneck_moves_done = false
self.data.BD_unit, self.data.BD_hex = best_unit, best_hex
data.BD_bottleneck_moves_done = false
data.BD_unit, data.BD_hex = best_unit, best_hex
end
return cfg.ca_score
end
function ca_bottleneck_move:execution(ai, cfg, self)
if self.data.BD_bottleneck_moves_done then
function ca_bottleneck_move:execution(cfg, data)
if data.BD_bottleneck_moves_done then
local units = {}
if MAISD.get_mai_self_data(self.data, cfg.ai_id, "side_leader_activated") then
if MAISD.get_mai_self_data(data, cfg.ai_id, "side_leader_activated") then
units = AH.get_units_with_moves { side = wesnoth.current.side }
else
units = AH.get_units_with_moves { side = wesnoth.current.side, canrecruit = 'no' }
@ -462,23 +462,23 @@ function ca_bottleneck_move:execution(ai, cfg, self)
AH.checked_stopunit_moves(ai, unit)
end
else
if (self.data.BD_unit.x ~= self.data.BD_hex[1]) or (self.data.BD_unit.y ~= self.data.BD_hex[2]) then
if (data.BD_unit.x ~= data.BD_hex[1]) or (data.BD_unit.y ~= data.BD_hex[2]) then
-- Don't want full move, as this might be stepping out of the way
AH.checked_move(ai, self.data.BD_unit, self.data.BD_hex[1], self.data.BD_hex[2])
AH.checked_move(ai, data.BD_unit, data.BD_hex[1], data.BD_hex[2])
end
if (not self.data.BD_unit) or (not self.data.BD_unit.valid) then return end
if (not data.BD_unit) or (not data.BD_unit.valid) then return end
if self.data.BD_level_up_defender then
AH.checked_attack(ai, self.data.BD_unit, self.data.BD_level_up_defender, self.data.BD_level_up_weapon)
if data.BD_level_up_defender then
AH.checked_attack(ai, data.BD_unit, data.BD_level_up_defender, data.BD_level_up_weapon)
end
end
-- Now delete almost everything
-- Keep only self.data.BD_is_my_territory because it is very expensive
self.data.BD_unit, self.data.BD_hex = nil, nil
self.data.BD_level_up_defender, self.data.BD_level_up_weapon = nil, nil
self.data.BD_bottleneck_moves_done = nil
self.data.BD_def_map, self.data.BD_healer_map, self.data.BD_leadership_map, self.data.BD_healing_map = nil, nil, nil, nil
-- Keep only data.BD_is_my_territory because it is very expensive
data.BD_unit, data.BD_hex = nil, nil
data.BD_level_up_defender, data.BD_level_up_weapon = nil, nil
data.BD_bottleneck_moves_done = nil
data.BD_def_map, data.BD_healer_map, data.BD_leadership_map, data.BD_healing_map = nil, nil, nil, nil
end
return ca_bottleneck_move

View file

@ -2,7 +2,7 @@ local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_coward(cfg)
local filter = cfg.filter or { id = cfg.id }
local filter = H.get_child(cfg, "filter") or { id = cfg.id }
local coward = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", filter }
@ -13,17 +13,17 @@ end
local ca_coward = {}
function ca_coward:evaluation(ai, cfg)
function ca_coward:evaluation(cfg)
if get_coward(cfg) then return cfg.ca_score end
return 0
end
function ca_coward:execution(ai, cfg)
function ca_coward:execution(cfg)
local coward = get_coward(cfg)
local reach = wesnoth.find_reach(coward)
local filter_second =
cfg.filter_second
H.get_child(cfg, "filter_second")
or { { "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } } }
local enemies = wesnoth.get_units {
{ "and", filter_second },

View file

@ -1,6 +1,7 @@
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 T = H.set_wml_tag_metatable{}
-- Functions to perform fast evaluation of attacks and attack combinations.
-- The emphasis with all of this is on speed, not elegance.
@ -19,31 +20,156 @@ function ca_fast_attack_utils.get_avoid_map(cfg)
-- Get map of locations to be avoided.
-- Use [micro_ai][avoid] tag with priority over [ai][avoid].
-- If neither is given, return an empty location set.
-- Note that ai.get_avoid() cannot be used as it always returns an array,
-- even when the aspect is not set, and an empty array could also mean that
-- no hexes match the filter
local avoid_tag
local avoid_tag = H.get_child(cfg, "avoid")
if cfg.avoid then
avoid_tag = cfg.avoid
if not avoid_tag then
return LS.of_pairs(ai.aspects.avoid)
end
return LS.of_pairs(wesnoth.get_locations(avoid_tag))
end
local function attack_filter(which, filter, is_leader)
if (which == 'leader') then
which = 'own'
is_leader = true
end
if (which == 'own') then
return {
side = wesnoth.current.side,
canrecruit = is_leader,
{ "and", filter or {} }
}
elseif (which == 'enemy') then
return {
T.filter_side { T.enemy_of { side = wesnoth.current.side } },
{ "not", filter or {} }
}
else
return filter
end
end
ca_fast_attack_utils.build_attack_filter = attack_filter
local function get_attack_filter_from_aspect(aspect, which, data, is_leader)
if (aspect.name == "composite_aspect") then
--print("Found composite aspect")
for facet in H.child_range(aspect, 'facet') do
local active = true
if facet.turns then
active = false
local turns = AH.split(facet.turns)
local current_turn = tostring(wesnoth.current.turn)
--print("Found facet with turns requirement (current turn is '" .. current_turn .. "')")
for i,v in ipairs(turns) do
if current_turn == v then
--print(" Matched with '" .. v .. "'")
active = true
break
end
end
end
if facet.time_of_day then
active = false
local times = AH.split(facet.time_of_day)
local current_time = wesnoth.get_time_of_day().id
--print("Found facet with time requirement (current time is '" .. current_time .. "')")
for i,v in ipairs(times) do
if current_time == v then
--print(" Matched with '" .. v .. "'")
active = true
break
end
end
end
if active then
return get_attack_filter_from_aspect(facet, which, data, is_leader)
end
end
elseif (aspect.name == "lua_aspect") then
--print("Found lua aspect")
local filter = loadstring(aspect.code)(nil, H.get_child(aspect, 'args'), data)
if (type(filter[which]) == 'function') then
temporary_attacks_filter_fcn = filter[which]
local units = wesnoth.get_units(attack_filter(which, {
lua_function = 'temporary_attacks_filter_fcn'
}, is_leader))
temporary_attacks_filter_fcn = nil
return units
else
return wesnoth.get_units(attack_filter(which, filter[which], is_leader))
end
else -- Standard attacks aspect (though not name=standard_aspect)
--print("Found standard aspect")
return wesnoth.get_units(attack_filter(which,
H.get_child(aspect, 'filter_' .. which), is_leader))
end
return wesnoth.get_units(attack_filter(which, {}, is_leader))
end
function ca_fast_attack_utils.get_attackers(data, which)
local ai_tag = H.get_child(wesnoth.sides[wesnoth.current.side].__cfg, 'ai')
for aspect in H.child_range(ai_tag, 'aspect') do
if (aspect.id == 'attacks') then
if (which == 'leader') then
return get_attack_filter_from_aspect(aspect, 'own', data, true)
else
return get_attack_filter_from_aspect(aspect, which, data)
end
end
end
return {}
end
--[[
This is a benchmarking function to compare the old, incorrect method of
fetching the attacks aspect to the new method and the standard method.
It's meant to be called from the Lua console.
Example usage:
$ my_ai = wesnoth.debug_ai(1).ai
$ FAU = wesnoth.dofile "ai/micro_ais/cas/ca_fast_attack_utils.lua"
$ FAU.test_attacks(my_ai, 2000)
]]
function ca_fast_attack_utils.test_attacks(my_ai, times)
local t1, t2 = os.clock()
for i = 1,times do
my_ai.get_attacks()
end
t2 = os.clock()
print("get_attacks() executed in average time " .. (os.difftime(t2,t1) / times))
t1 = os.clock()
for i = 1,times do
local ai_tag = H.get_child(wesnoth.sides[wesnoth.current.side].__cfg, 'ai')
for aspect in H.child_range(ai_tag, 'aspect') do
if (aspect.id == 'avoid') then
if (aspect.id == 'attacks') then
local facet = H.get_child(aspect, 'facet')
if facet then
avoid_tag = H.get_child(facet, 'value')
wesnoth.get_units{
side = wesnoth.current.side,
canrecruit = false,
{ "and", H.get_child(facet, 'filter_own') }
}
wesnoth.get_units{
side = wesnoth.current.side,
canrecruit = false,
{ "and", H.get_child(facet, 'filter_enemy') }
}
end
end
end
end
if avoid_tag then
return LS.of_pairs(wesnoth.get_locations(avoid_tag))
else
return LS.create()
t2 = os.clock()
print("original sloppy method executed in time " .. (os.difftime(t2,t1) / times))
t1 = os.clock()
for i = 1,times do
ca_fast_attack_utils.get_attackers(nil, "own", false)
ca_fast_attack_utils.get_attackers(nil, "enemy", false)
end
t2 = os.clock()
print("new method executed in time " .. (os.difftime(t2,t1) / times))
end
function ca_fast_attack_utils.gamedata_setup()

View file

@ -5,51 +5,49 @@ local LS = wesnoth.require "lua/location_set.lua"
local ca_fast_combat = {}
function ca_fast_combat:evaluation(ai, cfg, self)
self.data.move_cache = { turn = wesnoth.current.turn }
self.data.gamedata = FAU.gamedata_setup()
function ca_fast_combat:evaluation(cfg, data)
data.move_cache = { turn = wesnoth.current.turn }
data.gamedata = FAU.gamedata_setup()
local filter_own = cfg.filter
local filter_enemy = cfg.filter_second
local filter_own = H.get_child(cfg, "filter")
local filter_enemy = H.get_child(cfg, "filter_second")
local excluded_enemies
local units_sorted = true
if (not filter_own) and (not filter_enemy) then
local ai_tag = H.get_child(wesnoth.sides[wesnoth.current.side].__cfg, 'ai')
for aspect in H.child_range(ai_tag, 'aspect') do
if (aspect.id == 'attacks') then
local facet = H.get_child(aspect, 'facet')
if facet then
filter_own = H.get_child(facet, 'filter_own')
filter_enemy = H.get_child(facet, 'filter_enemy')
end
end
if (not data.fast_combat_units) or (not data.fast_combat_units[1]) then
data.fast_combat_units = FAU.get_attackers(data, "own")
if (not data.fast_combat_units[1]) then return 0 end
units_sorted = false
end
excluded_enemies = FAU.get_attackers(data, "enemy")
else
if (not data.fast_combat_units) or (not data.fast_combat_units[1]) then
data.fast_combat_units = wesnoth.get_units(
FAU.build_attack_filter("own", filter_own)
)
if (not data.fast_combat_units[1]) then return 0 end
units_sorted = false
end
if filter_enemy then
excluded_enemies = wesnoth.get_units(
FAU.build_attack_filter("enemy", filter_enemy)
)
end
end
if (not self.data.fast_combat_units) or (not self.data.fast_combat_units[1]) then
self.data.fast_combat_units = wesnoth.get_units {
side = wesnoth.current.side,
canrecruit = 'no',
{ "and", filter_own }
}
if (not self.data.fast_combat_units[1]) then return 0 end
if not units_sorted then
-- For speed reasons, we'll go through the arrays from the end, so they are sorted backwards
if cfg.weak_units_first then
table.sort(self.data.fast_combat_units, function(a,b) return a.hitpoints > b.hitpoints end)
table.sort(data.fast_combat_units, function(a,b) return a.hitpoints > b.hitpoints end)
else
table.sort(self.data.fast_combat_units, function(a,b) return a.hitpoints < b.hitpoints end)
table.sort(data.fast_combat_units, function(a,b) return a.hitpoints < b.hitpoints end)
end
end
local excluded_enemies_map = LS.create()
-- Exclude enemies not matching [filter_enemy]
if filter_enemy then
local excluded_enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "not", filter_enemy }
}
if excluded_enemies then
for _,e in ipairs(excluded_enemies) do
excluded_enemies_map:insert(e.x, e.y)
end
@ -74,10 +72,10 @@ function ca_fast_combat:evaluation(ai, cfg, self)
-- Get the locations to be avoided
local avoid_map = FAU.get_avoid_map(cfg)
for i = #self.data.fast_combat_units,1,-1 do
local unit = self.data.fast_combat_units[i]
local unit_info = FAU.get_unit_info(unit, self.data.gamedata)
local unit_copy = FAU.get_unit_copy(unit.id, self.data.gamedata)
for i = #data.fast_combat_units,1,-1 do
local unit = data.fast_combat_units[i]
local unit_info = FAU.get_unit_info(unit, data.gamedata)
local unit_copy = FAU.get_unit_copy(unit.id, data.gamedata)
if (unit.attacks_left > 0) and (H.get_child(unit.__cfg, 'attack')) then
local attacks = AH.get_attacks({ unit }, { include_occupied = cfg.include_occupied_attack_hexes })
@ -89,16 +87,16 @@ function ca_fast_combat:evaluation(ai, cfg, self)
and (not avoid_map:get(attack.dst.x, attack.dst.y))
then
local target = wesnoth.get_unit(attack.target.x, attack.target.y)
local target_info = FAU.get_unit_info(target, self.data.gamedata)
local target_info = FAU.get_unit_info(target, data.gamedata)
local att_stat, def_stat = FAU.battle_outcome(
unit_copy, target, { attack.dst.x, attack.dst.y },
unit_info, target_info, self.data.gamedata, self.data.move_cache
unit_info, target_info, data.gamedata, data.move_cache
)
local rating, attacker_rating, defender_rating, extra_rating = FAU.attack_rating(
{ unit_info }, target_info, { { attack.dst.x, attack.dst.y } },
{ att_stat }, def_stat, self.data.gamedata,
{ att_stat }, def_stat, data.gamedata,
{
own_value_weight = own_value_weight,
leader_weight = cfg.leader_weight
@ -114,30 +112,30 @@ function ca_fast_combat:evaluation(ai, cfg, self)
end
if best_target then
self.data.fast_combat_unit_i = i
self.data.fast_target, self.data.fast_dst = best_target, best_dst
data.fast_combat_unit_i = i
data.fast_target, data.fast_dst = best_target, best_dst
return cfg.ca_score
end
end
end
self.data.fast_combat_units[i] = nil
data.fast_combat_units[i] = nil
end
return 0
end
function ca_fast_combat:execution(ai, cfg, self)
local unit = self.data.fast_combat_units[self.data.fast_combat_unit_i]
AH.movefull_outofway_stopunit(ai, unit, self.data.fast_dst.x, self.data.fast_dst.y)
function ca_fast_combat:execution(cfg, data)
local unit = data.fast_combat_units[data.fast_combat_unit_i]
AH.movefull_outofway_stopunit(ai, unit, data.fast_dst.x, data.fast_dst.y)
if (not unit) or (not unit.valid) then return end
if (not self.data.fast_target) or (not self.data.fast_target.valid) then return end
if (H.distance_between(unit.x, unit.y, self.data.fast_target.x, self.data.fast_target.y) ~= 1) then return end
if (not data.fast_target) or (not data.fast_target.valid) then return end
if (H.distance_between(unit.x, unit.y, data.fast_target.x, data.fast_target.y) ~= 1) then return end
AH.checked_attack(ai, unit, self.data.fast_target)
AH.checked_attack(ai, unit, data.fast_target)
self.data.fast_combat_units[self.data.fast_combat_unit_i] = nil
data.fast_combat_units[data.fast_combat_unit_i] = nil
end
return ca_fast_combat

View file

@ -5,7 +5,7 @@ local LS = wesnoth.require "lua/location_set.lua"
local ca_fast_combat_leader = {}
function ca_fast_combat_leader:evaluation(ai, cfg, self)
function ca_fast_combat_leader:evaluation(cfg, data)
-- Some parts of this are very similar to ca_fast_combat.lua.
-- However, for speed reasons we really want this to be a separate CA, so
-- that the (more expensive) calculations for keeping the leader safe only
@ -15,42 +15,34 @@ function ca_fast_combat_leader:evaluation(ai, cfg, self)
leader_attack_max_units = (cfg and cfg.leader_attack_max_units) or 3
leader_additional_threat = (cfg and cfg.leader_additional_threat) or 1
self.data.move_cache = { turn = wesnoth.current.turn }
self.data.gamedata = FAU.gamedata_setup()
data.move_cache = { turn = wesnoth.current.turn }
data.gamedata = FAU.gamedata_setup()
local filter_own = cfg.filter
local filter_enemy = cfg.filter_second
local filter_own = H.get_child(cfg, "filter")
local filter_enemy = H.get_child(cfg, "filter_second")
local excluded_enemies, leader
if (not filter_own) and (not filter_enemy) then
local ai_tag = H.get_child(wesnoth.sides[wesnoth.current.side].__cfg, 'ai')
for aspect in H.child_range(ai_tag, 'aspect') do
if (aspect.id == 'attacks') then
local facet = H.get_child(aspect, 'facet')
if facet then
filter_own = H.get_child(facet, 'filter_own')
filter_enemy = H.get_child(facet, 'filter_enemy')
end
end
leader = FAU.get_attackers(data, "leader")[1]
if (not leader) then return 0 end
excluded_enemies = FAU.get_attackers(data, "enemy")
else
leader = wesnoth.get_units(
FAU.build_attack_filter("leader", filter_own)
)[1]
if (not leader) then return 0 end
if filter_enemy then
excluded_enemies = wesnoth.get_units(
FAU.build_attack_filter("enemy", filter_enemy)
)
end
end
local leader = wesnoth.get_units {
side = wesnoth.current.side,
canrecruit = 'yes',
{ "and", filter_own }
}[1]
if (not leader) then return 0 end
if (leader.attacks_left == 0) or (not H.get_child(leader.__cfg, 'attack')) then return 0 end
local excluded_enemies_map = LS.create()
-- Exclude enemies not matching [filter_enemy]
if filter_enemy then
local excluded_enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "not", filter_enemy }
}
if excluded_enemies then
for _,e in ipairs(excluded_enemies) do
excluded_enemies_map:insert(e.x, e.y)
end
@ -108,8 +100,8 @@ function ca_fast_combat_leader:evaluation(ai, cfg, self)
end
end
local leader_info = FAU.get_unit_info(leader, self.data.gamedata)
local leader_copy = FAU.get_unit_copy(leader.id, self.data.gamedata)
local leader_info = FAU.get_unit_info(leader, data.gamedata)
local leader_copy = FAU.get_unit_copy(leader.id, data.gamedata)
-- If threatened_leader_fights=yes, check out the current threat (power only,
-- not units) on the AI leader
@ -156,16 +148,16 @@ function ca_fast_combat_leader:evaluation(ai, cfg, self)
if acceptable_attack then
local target = wesnoth.get_unit(attack.target.x, attack.target.y)
local target_info = FAU.get_unit_info(target, self.data.gamedata)
local target_info = FAU.get_unit_info(target, data.gamedata)
local att_stat, def_stat = FAU.battle_outcome(
leader_copy, target, { attack.dst.x, attack.dst.y },
leader_info, target_info, self.data.gamedata, self.data.move_cache
leader_info, target_info, data.gamedata, data.move_cache
)
local rating, attacker_rating, defender_rating, extra_rating = FAU.attack_rating(
{ leader_info }, target_info, { { attack.dst.x, attack.dst.y } },
{ att_stat }, def_stat, self.data.gamedata,
{ att_stat }, def_stat, data.gamedata,
{
own_value_weight = own_value_weight,
leader_weight = cfg.leader_weight
@ -182,8 +174,8 @@ function ca_fast_combat_leader:evaluation(ai, cfg, self)
end
if best_target then
self.data.leader = leader
self.data.fast_target, self.data.fast_dst = best_target, best_dst
data.leader = leader
data.fast_target, data.fast_dst = best_target, best_dst
return cfg.ca_score
end
end
@ -191,17 +183,17 @@ function ca_fast_combat_leader:evaluation(ai, cfg, self)
return 0
end
function ca_fast_combat_leader:execution(ai, cfg, self)
local leader = self.data.leader
AH.movefull_outofway_stopunit(ai, leader, self.data.fast_dst.x, self.data.fast_dst.y)
function ca_fast_combat_leader:execution(cfg, data)
local leader = data.leader
AH.movefull_outofway_stopunit(ai, leader, data.fast_dst.x, data.fast_dst.y)
if (not leader) or (not leader.valid) then return end
if (not self.data.fast_target) or (not self.data.fast_target.valid) then return end
if (H.distance_between(leader.x, leader.y, self.data.fast_target.x, self.data.fast_target.y) ~= 1) then return end
if (not data.fast_target) or (not data.fast_target.valid) then return end
if (H.distance_between(leader.x, leader.y, data.fast_target.x, data.fast_target.y) ~= 1) then return end
AH.checked_attack(ai, leader, self.data.fast_target)
AH.checked_attack(ai, leader, data.fast_target)
self.data.leader = nil
data.leader = nil
end
return ca_fast_combat_leader

View file

@ -4,14 +4,14 @@ local FAU = wesnoth.require "ai/micro_ais/cas/ca_fast_attack_utils.lua"
local ca_fast_move = {}
function ca_fast_move:evaluation(ai, cfg, self)
function ca_fast_move:evaluation(cfg)
local unit = AH.get_units_with_moves { side = wesnoth.current.side, canrecruit = 'no' }[1]
if unit then return 20000 end
return 0
end
function ca_fast_move:execution(ai, cfg, self)
function ca_fast_move:execution(cfg)
local move_cost_factor = cfg.move_cost_factor or 2.0
if (move_cost_factor < 1.1) then move_cost_factor = 1.1 end

View file

@ -32,12 +32,12 @@ end
local ca_forest_animals_move = {}
function ca_forest_animals_move:evaluation(ai, cfg)
function ca_forest_animals_move:evaluation(cfg)
if get_forest_animals(cfg)[1] then return cfg.ca_score end
return 0
end
function ca_forest_animals_move:execution(ai, cfg)
function ca_forest_animals_move:execution(cfg)
-- These animals run from any enemy
local forest_animals = get_forest_animals(cfg)
local enemies = wesnoth.get_units { { "filter_side", { { "enemy_of", {side = wesnoth.current.side } } } } }
@ -73,7 +73,7 @@ function ca_forest_animals_move:execution(ai, cfg)
end
-- If no close enemies, do a random move
local wander_terrain = cfg.filter_location or {}
local wander_terrain = H.get_child(cfg, "filter_location") or {}
if (not close_enemies[1]) then
local reach = AH.get_reachable_unocc(unit)
local width, height = wesnoth.get_map_size()
@ -156,7 +156,7 @@ function ca_forest_animals_move:execution(ai, cfg)
if unit and unit.valid
and (unit.type == rabbit_type) and hole_map:get(farthest_hex[1], farthest_hex[2])
then
local command = "wesnoth.put_unit(x1, y1)"
local command = "wesnoth.erase_unit(x1, y1)"
ai.synced_command(command, farthest_hex[1], farthest_hex[2])
end
end

View file

@ -4,7 +4,7 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_forest_animals_new_rabbit = {}
function ca_forest_animals_new_rabbit:evaluation(ai, cfg)
function ca_forest_animals_new_rabbit:evaluation(cfg)
-- Put new rabbits on map if there are fewer than cfg.rabbit_number
-- To end this, we'll let the CA black-list itself
@ -12,7 +12,7 @@ function ca_forest_animals_new_rabbit:evaluation(ai, cfg)
return cfg.ca_score
end
function ca_forest_animals_new_rabbit:execution(ai, cfg)
function ca_forest_animals_new_rabbit:execution(cfg)
local number = cfg.rabbit_number or 6
local rabbit_enemy_distance = cfg.rabbit_enemy_distance or 3
@ -59,11 +59,11 @@ function ca_forest_animals_new_rabbit:execution(ai, cfg)
x, y = wesnoth.find_vacant_tile(holes[i].x, holes[i].y)
end
local command = "wesnoth.put_unit(x1, y1, { side = "
local command = "wesnoth.put_unit({ side = "
.. wesnoth.current.side
.. ", type = '"
.. cfg.rabbit_type
.. "' })"
.. "' }, x1, y1)"
ai.synced_command(command, x, y)
end
end

View file

@ -19,7 +19,7 @@ end
local ca_forest_animals_tusker_attack = {}
function ca_forest_animals_tusker_attack:evaluation(ai, cfg)
function ca_forest_animals_tusker_attack:evaluation(cfg)
-- Check whether there is an enemy next to a tusklet and attack it ("protective parents" AI)
if (not cfg.tusker_type) or (not cfg.tusklet_type) then return 0 end
@ -28,7 +28,7 @@ function ca_forest_animals_tusker_attack:evaluation(ai, cfg)
return cfg.ca_score
end
function ca_forest_animals_tusker_attack:execution(ai, cfg)
function ca_forest_animals_tusker_attack:execution(cfg)
local tuskers = get_tuskers(cfg)
local adjacent_enemies = get_adjacent_enemies(cfg)

View file

@ -19,7 +19,7 @@ end
local ca_forest_animals_tusklet_move = {}
function ca_forest_animals_tusklet_move:evaluation(ai, cfg)
function ca_forest_animals_tusklet_move:evaluation(cfg)
-- Tusklets will simply move toward the closest tusker, without regard for anything else
-- Except if no tuskers are left, in which case ca_forest_animals_move takes over and does a random move
@ -29,7 +29,7 @@ function ca_forest_animals_tusklet_move:evaluation(ai, cfg)
return cfg.ca_score
end
function ca_forest_animals_tusklet_move:execution(ai, cfg)
function ca_forest_animals_tusklet_move:execution(cfg)
local tusklets = get_tusklets(cfg)
local tuskers = get_tuskers(cfg)

View file

@ -15,12 +15,12 @@ local function custom_cost(x, y, unit, enemy_map, enemy_attack_map, multiplier)
return move_cost
end
local ca_goto = {}
local ca_goto, GO_units, GO_locs = {}
function ca_goto:evaluation(ai, cfg, self)
function ca_goto:evaluation(cfg, data)
-- If cfg.release_all_units_at_goal is set, check whether the goal has
-- already been reached, in which case we do not do anything
if MAISD.get_mai_self_data(self.data, cfg.ai_id, "release_all") then
if MAISD.get_mai_self_data(data, cfg.ai_id, "release_all") then
return 0
end
@ -30,7 +30,7 @@ function ca_goto:evaluation(ai, cfg, self)
local all_locs = wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", cfg.filter_location }
{ "and", H.get_child(cfg, "filter_location") }
}
if (#all_locs == 0) then return 0 end
@ -40,17 +40,17 @@ function ca_goto:evaluation(ai, cfg, self)
if cfg.unique_goals then
-- First, some cleanup of previous turn data
local str = 'goal_taken_' .. (wesnoth.current.turn - 1)
local old_goals = MAISD.get_mai_self_data(self.data, cfg.ai_id)
local old_goals = MAISD.get_mai_self_data(data, cfg.ai_id)
for goal,_ in pairs(old_goals) do
if string.find(goal, str) then
old_goals[goal] = nil -- This also removes it from self.data
old_goals[goal] = nil -- This also removes it from data
end
end
-- Now on to the current turn
for _,loc in ipairs(all_locs) do
local str = 'goal_taken_' .. wesnoth.current.turn .. '_' .. loc[1] .. '_' .. loc[2]
if (not MAISD.get_mai_self_data(self.data, cfg.ai_id, str)) then
if (not MAISD.get_mai_self_data(data, cfg.ai_id, str)) then
table.insert(locs, loc)
end
end
@ -61,7 +61,7 @@ function ca_goto:evaluation(ai, cfg, self)
local all_units = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter }
{ "and", H.get_child(cfg, "filter") }
}
local units = {}
@ -76,14 +76,14 @@ function ca_goto:evaluation(ai, cfg, self)
end
if (not units[1]) then return 0 end
-- Now store units and locs in self.data, so that we don't need to duplicate this in the exec function
self.data.GO_units, self.data.GO_locs = units, locs
-- Now store units and locs, so that we don't need to duplicate this in the exec function
GO_units, GO_locs = units, locs
return cfg.ca_score
end
function ca_goto:execution(ai, cfg, self)
local units, locs = self.data.GO_units, self.data.GO_locs
function ca_goto:execution(cfg, data)
local units, locs = GO_units, GO_locs
local enemy_map, enemy_attack_map
if cfg.avoid_enemies then
@ -150,7 +150,7 @@ function ca_goto:execution(ai, cfg, self)
end
end
-- Make all hexes within the unit's current MP equaivalent
-- Make all hexes within the unit's current MP equivalent
if (cost <= unit.moves) then cost = 0 end
local rating = - cost
@ -174,7 +174,7 @@ function ca_goto:execution(ai, cfg, self)
local str = 'goal_taken_' .. wesnoth.current.turn .. '_' .. closest_hex[1] .. '_' .. closest_hex[2]
local tmp_table = {}
tmp_table[str] = true
MAISD.insert_mai_self_data(self.data, cfg.ai_id, tmp_table)
MAISD.insert_mai_self_data(data, cfg.ai_id, tmp_table)
end
-- If any of the non-standard path finding options were used,
@ -227,12 +227,12 @@ function ca_goto:execution(ai, cfg, self)
end
if cfg.release_all_units_at_goal then
MAISD.insert_mai_self_data(self.data, cfg.ai_id, { release_all = true })
MAISD.insert_mai_self_data(data, cfg.ai_id, { release_all = true })
end
end
end
self.data.GO_units, self.data.GO_locs = nil, nil
GO_units, GO_locs = nil, nil
end
return ca_goto

View file

@ -7,27 +7,28 @@ local MAISD = wesnoth.require "ai/micro_ais/micro_ai_self_data.lua"
local function get_hang_out_units(cfg)
local units = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter }
{ "and", H.get_child(cfg, "filter") }
}
return units
end
local ca_hang_out = {}
function ca_hang_out:evaluation(ai, cfg, self)
function ca_hang_out:evaluation(cfg, data)
-- Return 0 if the mobilize condition has previously been met
if MAISD.get_mai_self_data(self.data, cfg.ai_id, "mobilize_units") then
if MAISD.get_mai_self_data(data, cfg.ai_id, "mobilize_units") then
return 0
end
-- Otherwise check if any of the mobilize conditions are now met
if (cfg.mobilize_condition and wesnoth.eval_conditional(cfg.mobilize_condition))
local mobilize_condition = H.get_child(cfg, "mobilize_condition")
if (mobilize_condition and wesnoth.eval_conditional(mobilize_condition))
or (cfg.mobilize_on_gold_less_than and (wesnoth.sides[wesnoth.current.side].gold < cfg.mobilize_on_gold_less_than))
then
MAISD.insert_mai_self_data(self.data, cfg.ai_id, { mobilize_units = true })
MAISD.insert_mai_self_data(data, cfg.ai_id, { mobilize_units = true })
-- Need to unmark all units also (all units, with and without moves)
local units = wesnoth.get_units { side = wesnoth.current.side, { "and", cfg.filter } }
local units = wesnoth.get_units { side = wesnoth.current.side, { "and", H.get_child(cfg, "filter") } }
for _,unit in ipairs(units) do
MAIUV.delete_mai_unit_variables(unit, cfg.ai_id)
end
@ -39,12 +40,12 @@ function ca_hang_out:evaluation(ai, cfg, self)
return 0
end
function ca_hang_out:execution(ai, cfg, self)
function ca_hang_out:execution(cfg)
local units = get_hang_out_units(cfg)
-- 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 {
local filter_location = H.get_child(cfg, "filter_location") or {
{ "filter", { side = wesnoth.current.side, canrecruit = "yes" } }
}
@ -58,25 +59,29 @@ function ca_hang_out:execution(ai, cfg, self)
-- Get map of locations to be avoided.
-- Use [micro_ai][avoid] tag with priority over [ai][avoid].
-- If neither is given, default to all castle terrain.
-- ai.get_avoid() cannot be used as it always returns an array, even when the aspect is not set,
-- and an empty array could also mean that no hexes match the filter
local avoid_tag
if cfg.avoid then
avoid_tag = cfg.avoid
local avoid_tag = H.get_child(cfg, "avoid")
local avoid_map
if avoid_tag then
avoid_map = LS.of_pairs(wesnoth.get_locations(avoid_tag))
else
local ai_tag = H.get_child(wesnoth.sides[wesnoth.current.side].__cfg, 'ai')
for aspect in H.child_range(ai_tag, 'aspect') do
if (aspect.id == 'avoid') then
local facet = H.get_child(aspect, 'facet')
if facet then
avoid_tag = H.get_child(facet, 'value')
else
avoid_tag = { terrain = 'C*,C*^*,*^C*' }
if facet or aspect.name ~= "composite_aspect" then
-- If there's a [facet] child, it's set as a composite aspect,
-- with at least one facet.
-- But it might not be a composite aspect; it could be
-- a Lua aspect or a standard aspect.
avoid_map = LS.of_pairs(ai.aspects.avoid)
break
end
end
end
end
local avoid_map = LS.of_pairs(wesnoth.get_locations(avoid_tag))
if avoid_map == nil then
avoid_map = LS.of_pairs(wesnoth.get_locations { terrain = 'C*,C*^*,*^C*' })
end
local max_rating, best_hex, best_unit = -9e99
for _,unit in ipairs(units) do

View file

@ -3,7 +3,7 @@ local W = H.set_wml_action_metatable {}
local ca_healer_initialize = {}
function ca_healer_initialize:evaluation(ai)
function ca_healer_initialize:evaluation()
-- Set variables and aspects so that healers are excluded from attacks at beginning of turn
-- This will be blacklisted after first execution each turn
@ -11,7 +11,7 @@ function ca_healer_initialize:evaluation(ai)
return score
end
function ca_healer_initialize:execution(ai, cfg, self)
function ca_healer_initialize:execution(cfg, data)
W.modify_ai {
side = wesnoth.current.side,
action = "try_delete",
@ -27,14 +27,14 @@ function ca_healer_initialize:execution(ai, cfg, self)
id = "no_healers_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_own", {
{ "not", { ability = "healing", { "and", cfg.filter } } }
{ "not", { ability = "healing", { "and", H.get_child(cfg, "filter") } } }
} }
} }
}
-- We also need to set the score of healer moves to happen after
-- combat (of other units) at beginning of turn
self.data.HS_healer_move_score = 95000
data.HS_healer_move_score = 95000
end
return ca_healer_initialize

View file

@ -3,7 +3,7 @@ local W = H.set_wml_action_metatable {}
local ca_healer_may_attack = {}
function ca_healer_may_attack:evaluation(ai)
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
@ -11,7 +11,7 @@ function ca_healer_may_attack:evaluation(ai)
return score
end
function ca_healer_may_attack:execution(ai, cfg, self)
function ca_healer_may_attack:execution(cfg, data)
W.modify_ai {
side = wesnoth.current.side,
action = "try_delete",
@ -20,7 +20,7 @@ function ca_healer_may_attack:execution(ai, cfg, self)
-- Once combat (by other units) is done, set the healer move score so that it
-- now happens before combat (of the healers which were so far excluded from combat)
self.data.HS_healer_move_score = nil
data.HS_healer_move_score = nil
end
return ca_healer_may_attack

View file

@ -3,19 +3,19 @@ local LS = wesnoth.require "lua/location_set.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
local ca_healer_move = {}
local ca_healer_move, best_healer, best_hex = {}
function ca_healer_move:evaluation(ai, cfg, self)
function ca_healer_move:evaluation(cfg, data)
-- Should happen with higher priority than attacks, except at beginning of turn,
-- when we want attacks (by other units) done first
-- This is done so that it is possible for healers to attack, if they do not
-- find an appropriate hex to back up other units
local score = self.data.HS_healer_move_score or 105000
local score = data.HS_healer_move_score or 105000
local all_healers = wesnoth.get_units {
side = wesnoth.current.side,
ability = "healing",
{ "and", cfg.filter }
{ "and", H.get_child(cfg, "filter") }
}
local healers, healers_noMP = {}, {}
@ -30,7 +30,7 @@ function ca_healer_move:evaluation(ai, cfg, self)
local all_healees = wesnoth.get_units {
side = wesnoth.current.side,
{ "and", cfg.filter_second }
{ "and", H.get_child(cfg, "filter_second") }
}
local healees, healees_MP = {}, {}
@ -65,7 +65,7 @@ function ca_healer_move:evaluation(ai, cfg, self)
local avoid_map = LS.of_pairs(ai.get_avoid())
local max_rating, best_healer, best_hex = -9e99
local max_rating = -9e99
for _,healer in ipairs(healers) do
local reach = wesnoth.find_reach(healer)
@ -119,16 +119,15 @@ function ca_healer_move:evaluation(ai, cfg, self)
end
if best_healer then
self.data.HS_unit, self.data.HS_hex = best_healer, best_hex
return score
end
return 0
end
function ca_healer_move:execution(ai, cfg, self)
AH.movefull_outofway_stopunit(ai, self.data.HS_unit, self.data.HS_hex)
self.data.HS_unit, self.data.HS_hex = nil, nil
function ca_healer_move:execution(cfg)
AH.movefull_outofway_stopunit(ai, best_healer, best_hex)
best_healer, best_hex = nil, nil
end
return ca_healer_move

View file

@ -4,7 +4,7 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_sheep(cfg)
local sheep = wesnoth.get_units {
side = wesnoth.current.side,
{ "and", cfg.filter_second }
{ "and", H.get_child(cfg, "filter_second") }
}
return sheep
end
@ -12,7 +12,7 @@ end
local function get_dogs(cfg)
local dogs = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter }
{ "and", H.get_child(cfg, "filter") }
}
return dogs
end
@ -22,7 +22,7 @@ local function get_enemies(cfg, radius)
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "filter_location",
{ radius = radius,
{ "filter", { side = wesnoth.current.side, { "and", cfg.filter_second } } } }
{ "filter", { side = wesnoth.current.side, { "and", H.get_child(cfg, "filter_second") } } } }
}
}
return enemies
@ -30,7 +30,7 @@ end
local ca_herding_attack_close_enemy = {}
function ca_herding_attack_close_enemy:evaluation(ai, cfg)
function ca_herding_attack_close_enemy:evaluation(cfg)
-- Any enemy within attention_distance (default = 8) hexes of a sheep will get the dogs' attention
-- with enemies within attack_distance (default: 4) being attacked
if not get_sheep(cfg)[1] then return 0 end
@ -41,7 +41,7 @@ function ca_herding_attack_close_enemy:evaluation(ai, cfg)
return 0
end
function ca_herding_attack_close_enemy:execution(ai, cfg)
function ca_herding_attack_close_enemy:execution(cfg)
local sheep = get_sheep(cfg)
local dogs = get_dogs(cfg)

View file

@ -5,24 +5,24 @@ local LS = wesnoth.require "lua/location_set.lua"
local function get_dog(cfg)
local dogs = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter },
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, { "and", cfg.filter_second } } } } }
{ "and", H.get_child(cfg, "filter") },
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, { "and", H.get_child(cfg, "filter_second") } } } } }
}
return dogs[1]
end
local ca_herding_dog_move = {}
function ca_herding_dog_move:evaluation(ai, cfg)
function ca_herding_dog_move:evaluation(cfg)
-- As a final step, any dog not adjacent to a sheep moves within herding_perimeter
if get_dog(cfg) then return cfg.ca_score end
return 0
end
function ca_herding_dog_move:execution(ai, cfg)
function ca_herding_dog_move:execution(cfg)
-- We simply move the first dog first, order does not matter
local dog = get_dog(cfg)
local herding_perimeter = LS.of_pairs(wesnoth.get_locations(cfg.filter_location))
local herding_perimeter = LS.of_pairs(wesnoth.get_locations(H.get_child(cfg, "filter_location")))
-- Find average distance of herding_perimeter from center
local av_dist = 0

View file

@ -1,9 +1,10 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_dog(cfg)
local dogs = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter },
{ "and", H.get_child(cfg, "filter") },
}
return dogs[1]
end
@ -14,12 +15,12 @@ end
local ca_herding_dog_stopmove = {}
function ca_herding_dog_stopmove:evaluation(ai, cfg)
function ca_herding_dog_stopmove:evaluation(cfg)
if get_dog(cfg) then return cfg.ca_score end
return 0
end
function ca_herding_dog_stopmove:execution(ai, cfg)
function ca_herding_dog_stopmove:execution(cfg)
local dog = get_dog(cfg)
AH.checked_stopunit_moves(ai, dog)

View file

@ -4,17 +4,18 @@ local LS = wesnoth.require "lua/location_set.lua"
return function(cfg)
-- Find the area that the sheep can occupy
-- First, find all contiguous hexes around center hex that are inside herding_perimeter
local location_filter = H.get_child(cfg, "filter_location")
local herding_area = LS.of_pairs(wesnoth.get_locations {
x = cfg.herd_x,
y = cfg.herd_y,
radius = 999,
{ "filter_radius", { { "not", cfg.filter_location } } }
{ "filter_radius", { { "not", location_filter } } }
})
-- Then, also exclude hexes next to herding_perimeter; some of the functions work better like that
herding_area:iter( function(x, y, v)
for xa, ya in H.adjacent_tiles(x, y) do
if (wesnoth.match_location(xa, ya, cfg.filter_location) ) then
if (wesnoth.match_location(xa, ya, location_filter) ) then
herding_area:remove(x, y)
end
end

View file

@ -6,7 +6,7 @@ local herding_area = wesnoth.require "ai/micro_ais/cas/ca_herding_f_herding_area
local function get_dogs(cfg)
local dogs = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter }
{ "and", H.get_child(cfg, "filter") }
}
return dogs
end
@ -14,8 +14,8 @@ end
local function get_sheep_to_herd(cfg)
local all_sheep = wesnoth.get_units {
side = wesnoth.current.side,
{ "and", cfg.filter_second },
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, { "and", cfg.filter } } } } }
{ "and", H.get_child(cfg, "filter_second") },
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, { "and", H.get_child(cfg, "filter") } } } } }
}
local sheep_to_herd = {}
@ -30,7 +30,7 @@ end
local ca_herding_herd_sheep = {}
function ca_herding_herd_sheep:evaluation(ai, cfg)
function ca_herding_herd_sheep:evaluation(cfg)
-- If dogs have moves left, and there is a sheep with moves left outside the
-- herding area, chase it back
if (not get_dogs(cfg)[1]) then return 0 end
@ -38,7 +38,7 @@ function ca_herding_herd_sheep:evaluation(ai, cfg)
return cfg.ca_score
end
function ca_herding_herd_sheep:execution(ai, cfg)
function ca_herding_herd_sheep:execution(cfg)
local dogs = get_dogs(cfg)
local sheep_to_herd = get_sheep_to_herd(cfg)
@ -62,7 +62,7 @@ function ca_herding_herd_sheep:execution(ai, cfg)
-- And the closer dog goes first (so that it might be able to chase another sheep afterward)
rating = rating - H.distance_between(x, y, dog.x, dog.y) / 100.
-- Finally, prefer to stay on path, if possible
if (wesnoth.match_location(x, y, cfg.filter_location) ) then rating = rating + 0.001 end
if (wesnoth.match_location(x, y, H.get_child(cfg, "filter_location")) ) then rating = rating + 0.001 end
reach_map:insert(x, y, rating)

View file

@ -6,29 +6,30 @@ local herding_area = wesnoth.require "ai/micro_ais/cas/ca_herding_f_herding_area
local function get_next_sheep(cfg)
local sheep = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter_second }
{ "and", H.get_child(cfg, "filter_second") }
}
return sheep[1]
end
local ca_herding_sheep_move = {}
function ca_herding_sheep_move:evaluation(ai, cfg)
function ca_herding_sheep_move:evaluation(cfg)
-- If nothing else is to be done, the sheep do a random move
if get_next_sheep(cfg) then return cfg.ca_score end
return 0
end
function ca_herding_sheep_move:execution(ai, cfg)
function ca_herding_sheep_move:execution(cfg)
-- We simply move the first sheep first, the order does not matter
local sheep = get_next_sheep(cfg)
local reach_map = AH.get_reachable_unocc(sheep)
local dogs_filter = H.get_child(cfg, "filter")
-- Exclude those that are next to a dog
reach_map:iter( function(x, y, v)
for xa, ya in H.adjacent_tiles(x, y) do
local dog = wesnoth.get_unit(xa, ya)
if dog and (wesnoth.match_unit(dog, cfg.filter)) then
if dog and (wesnoth.match_unit(dog, dogs_filter)) then
reach_map:remove(x, y)
end
end
@ -45,7 +46,7 @@ function ca_herding_sheep_move:execution(ai, cfg)
local herding_area = herding_area(cfg)
local dogs = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter }
{ "and", dogs_filter }
}
if herding_area:get(x, y) or (not dogs[1]) or ((x == sheep.x) and (y == sheep.y)) then
AH.movefull_stopunit(ai, sheep, x, y)

View file

@ -4,26 +4,26 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_next_sheep(cfg)
local sheep = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter_second },
{ "filter_adjacent", { side = wesnoth.current.side, { "and", cfg.filter } } }
{ "and", H.get_child(cfg, "filter_second") },
{ "filter_adjacent", { side = wesnoth.current.side, { "and", H.get_child(cfg, "filter") } } }
}
return sheep[1]
end
local ca_herding_sheep_runs_dog = {}
function ca_herding_sheep_runs_dog:evaluation(ai, cfg)
function ca_herding_sheep_runs_dog:evaluation(cfg)
-- Any sheep with moves left next to a dog runs away
if get_next_sheep(cfg) then return cfg.ca_score end
return 0
end
function ca_herding_sheep_runs_dog:execution(ai, cfg)
function ca_herding_sheep_runs_dog:execution(cfg)
-- Simply get the first sheep, order does not matter
local sheep = get_next_sheep(cfg)
-- Get the first dog that the sheep is adjacent to
local dog = wesnoth.get_units { side = wesnoth.current.side, { "and", cfg.filter },
local dog = wesnoth.get_units { side = wesnoth.current.side, { "and", H.get_child(cfg, "filter") },
{ "filter_adjacent", { x = sheep.x, y = sheep.y } }
}[1]

View file

@ -4,7 +4,7 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_next_sheep(cfg)
local sheep = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter_second },
{ "and", H.get_child(cfg, "filter_second") },
{ "filter_location",
{
{ "filter", { { "filter_side", { { "enemy_of", {side = wesnoth.current.side} } } } }
@ -18,13 +18,13 @@ end
local ca_herding_sheep_runs_enemy = {}
function ca_herding_sheep_runs_enemy:evaluation(ai, cfg)
function ca_herding_sheep_runs_enemy:evaluation(cfg)
-- Sheep runs from any enemy within attention_distance hexes (after the dogs have moved in)
if get_next_sheep(cfg) then return cfg.ca_score end
return 0
end
function ca_herding_sheep_runs_enemy:execution(ai, cfg)
function ca_herding_sheep_runs_enemy:execution(cfg)
-- Simply start with the first sheep, order does not matter
local sheep = get_next_sheep(cfg)

View file

@ -35,7 +35,7 @@ local function hunter_attack_weakest_adj_enemy(ai, hunter)
end
local function get_hunter(cfg)
local filter = cfg.filter or { id = cfg.id }
local filter = H.get_child(cfg, "filter") or { id = cfg.id }
local hunter = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", filter }
@ -45,12 +45,12 @@ end
local ca_hunter = {}
function ca_hunter:evaluation(ai, cfg)
function ca_hunter:evaluation(cfg)
if get_hunter(cfg) then return cfg.ca_score end
return 0
end
function ca_hunter:execution(ai, cfg)
function ca_hunter:execution(cfg)
-- Hunter does a random wander in area given by @cfg.hunting_ground until it finds
-- and kills an enemy unit, then retreats to position given by @cfg.home_x/y
-- for @cfg.rest_turns turns, or until fully healed
@ -64,7 +64,7 @@ function ca_hunter:execution(ai, cfg)
local rand = math.random(10)
if (not hunter_vars.goal_x) or (rand == 1) then
-- 'locs' includes border hexes, but that does not matter here
locs = AH.get_passable_locations((cfg.filter_location or {}), hunter)
locs = AH.get_passable_locations((H.get_child(cfg, "filter_location") or {}), hunter)
local rand = math.random(#locs)
hunter_vars.goal_x, hunter_vars.goal_y = locs[rand][1], locs[rand][2]

View file

@ -6,19 +6,19 @@ local function get_lurker(cfg)
-- We simply pick the first of the lurkers, they have no strategy
local lurker = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter }
{ "and", H.get_child(cfg, "filter") }
}[1]
return lurker
end
local ca_lurkers = {}
function ca_lurkers:evaluation(ai, cfg)
function ca_lurkers:evaluation(cfg)
if get_lurker(cfg) then return cfg.ca_score end
return 0
end
function ca_lurkers:execution(ai, cfg)
function ca_lurkers:execution(cfg)
local lurker = get_lurker(cfg)
local targets = AH.get_live_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
@ -28,10 +28,11 @@ function ca_lurkers:execution(ai, cfg)
table.sort(targets, function(a, b) return (a.hitpoints < b.hitpoints) end)
local reach = LS.of_pairs(wesnoth.find_reach(lurker.x, lurker.y))
local lurk_area = H.get_child(cfg, "filter_location")
local reachable_attack_terrain =
LS.of_pairs(wesnoth.get_locations {
{ "and", { x = lurker.x, y = lurker.y, radius = lurker.moves } },
{ "and", cfg.filter_location }
{ "and", lurk_area }
})
reachable_attack_terrain:inter(reach)
@ -72,7 +73,7 @@ function ca_lurkers:execution(ai, cfg)
local reachable_wander_terrain =
LS.of_pairs( wesnoth.get_locations {
{ "and", { x = lurker.x, y = lurker.y, radius = lurker.moves } },
{ "and", (cfg.filter_location_wander or cfg.filter_location) }
{ "and", H.get_child(cfg, "filter_location_wander") or lurk_area }
})
reachable_wander_terrain:inter(reach)

View file

@ -2,6 +2,7 @@ local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f_next_waypoint.lua"
local best_attack
local function messenger_find_enemies_in_way(messenger, goal_x, goal_y)
-- Returns the first unit on or next to the path of the messenger
@ -49,17 +50,17 @@ local function messenger_find_clearing_attack(messenger, goal_x, goal_y, cfg)
if (not enemy_in_way) then return end
local filter = cfg.filter or { id = cfg.id }
local filter = H.get_child(cfg, "filter") or { id = cfg.id }
local units = AH.get_units_with_attacks {
side = wesnoth.current.side,
{ "not", filter },
{ "and", cfg.filter_second }
{ "and", H.get_child(cfg, "filter_second") }
}
if (not units[1]) then return end
local attacks = AH.get_attacks(units, { simulate_combat = true })
local max_rating, best_attack = -9e99
local max_rating = -9e99
for _,attack in ipairs(attacks) do
if (attack.target.x == enemy_in_way.x) and (attack.target.y == enemy_in_way.y) then
@ -96,7 +97,7 @@ end
local ca_messenger_attack = {}
function ca_messenger_attack:evaluation(ai, cfg, self)
function ca_messenger_attack:evaluation(cfg)
-- Attack units in the path of the messengers
local messenger, x, y = messenger_next_waypoint(cfg)
@ -105,23 +106,22 @@ function ca_messenger_attack:evaluation(ai, cfg, self)
local attack = messenger_find_clearing_attack(messenger, x, y, cfg)
if attack then
self.data.ME_best_attack = attack
return cfg.ca_score
end
return 0
end
function ca_messenger_attack:execution(ai, cfg, self)
local attacker = wesnoth.get_unit(self.data.ME_best_attack.src.x, self.data.ME_best_attack.src.y)
local defender = wesnoth.get_unit(self.data.ME_best_attack.target.x, self.data.ME_best_attack.target.y)
function ca_messenger_attack:execution(cfg)
local attacker = wesnoth.get_unit(best_attack.src.x, best_attack.src.y)
local defender = wesnoth.get_unit(best_attack.target.x, best_attack.target.y)
AH.movefull_stopunit(ai, attacker, self.data.ME_best_attack.dst.x, self.data.ME_best_attack.dst.y)
AH.movefull_stopunit(ai, attacker, best_attack.dst.x, best_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)
self.data.ME_best_attack = nil
best_attack = nil
end
return ca_messenger_attack

View file

@ -8,14 +8,14 @@ local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f
local function get_escorts(cfg)
local escorts = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter_second }
{ "and", H.get_child(cfg, "filter_second") }
}
return escorts
end
local ca_messenger_escort_move = {}
function ca_messenger_escort_move:evaluation(ai, cfg)
function ca_messenger_escort_move:evaluation(cfg)
-- Move escort units close to messengers, and in between messengers and enemies
-- The messengers have moved at this time, so we don't need to exclude them,
-- but we check that there are messengers left
@ -28,7 +28,7 @@ function ca_messenger_escort_move:evaluation(ai, cfg)
return cfg.ca_score
end
function ca_messenger_escort_move:execution(ai, cfg)
function ca_messenger_escort_move:execution(cfg)
local escorts = get_escorts(cfg)
local _, _, _, messengers = messenger_next_waypoint(cfg)

View file

@ -10,7 +10,7 @@ return function(cfg)
-- Returns nil for first 3 arguments if no messenger has moves left
-- Returns nil for all arguments if there are no messengers on the map
local filter = cfg.filter or { id = cfg.id }
local filter = H.get_child(cfg, "filter") or { id = cfg.id }
local messengers = wesnoth.get_units { side = wesnoth.current.side, { "and", filter } }
if (not messengers[1]) then return end

View file

@ -5,7 +5,7 @@ local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f
local ca_messenger_move = {}
function ca_messenger_move:evaluation(ai, cfg)
function ca_messenger_move:evaluation(cfg)
-- Move the messenger toward goal, potentially attack adjacent unit
local messenger = messenger_next_waypoint(cfg)
@ -14,7 +14,7 @@ function ca_messenger_move:evaluation(ai, cfg)
return 0
end
function ca_messenger_move:execution(ai, cfg)
function ca_messenger_move:execution(cfg)
local messenger, x, y = messenger_next_waypoint(cfg)
if (messenger.x ~= x) or (messenger.y ~= y) then
@ -57,17 +57,17 @@ function ca_messenger_move:execution(ai, cfg)
if (unit_in_way == messenger) then unit_in_way = nil end
if unit_in_way then wesnoth.extract_unit(unit_in_way) end
wesnoth.put_unit(next_hop[1], next_hop[2], messenger)
wesnoth.put_unit(messenger, next_hop[1], next_hop[2])
local _, cost1 = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })
local unit_in_way2 = wesnoth.get_unit(optimum_hop[1], optimum_hop[2])
if (unit_in_way2 == messenger) then unit_in_way2 = nil end
if unit_in_way2 then wesnoth.extract_unit(unit_in_way2) end
wesnoth.put_unit(optimum_hop[1], optimum_hop[2], messenger)
wesnoth.put_unit(messenger, optimum_hop[1], optimum_hop[2])
local _, cost2 = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })
wesnoth.put_unit(x_current, y_current, messenger)
wesnoth.put_unit(messenger, x_current, y_current)
if unit_in_way then wesnoth.put_unit(unit_in_way) end
if unit_in_way2 then wesnoth.put_unit(unit_in_way2) end

View file

@ -1,8 +1,9 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
local function get_patrol(cfg)
local filter = cfg.filter or { id = cfg.id }
local filter = H.get_child(cfg, "filter") or { id = cfg.id }
local patrol = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", filter }
@ -12,12 +13,12 @@ end
local ca_patrol = {}
function ca_patrol:evaluation(ai, cfg)
function ca_patrol:evaluation(cfg)
if get_patrol(cfg) then return cfg.ca_score end
return 0
end
function ca_patrol:execution(ai, cfg)
function ca_patrol:execution(cfg)
local patrol = get_patrol(cfg)
local patrol_vars = MAIUV.get_mai_unit_variables(patrol, cfg.ai_id)

View file

@ -2,16 +2,16 @@ 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 ca_protect_unit_attack = {}
local ca_protect_unit_attack, best_attack = {}
function ca_protect_unit_attack:evaluation(ai, cfg, self)
function ca_protect_unit_attack:evaluation(cfg)
-- Find possible attacks for the units
-- This is set up very conservatively: if a unit can die in the attack
-- or the counter attack on the enemy turn, it does not attack, even if that's really unlikely
local units = {}
for _,id in ipairs(cfg.id) do
table.insert(units, AH.get_units_with_attacks { id = id }[1])
for u in H.child_range(cfg, "unit") do
table.insert(units, AH.get_units_with_attacks { id = u.id }[1])
end
if (not units[1]) then return 0 end
@ -32,7 +32,7 @@ function ca_protect_unit_attack:evaluation(ai, cfg, self)
-- Set up a counter attack damage table, as many pairs of attacks will be the same
local counter_damage_table = {}
local max_rating, best_attack = -9e99
local max_rating = -9e99
for _,attack in pairs(attacks) do
-- Only consider attack if there is no chance to die or to be poisoned or slowed
if (attack.att_stats.hp_chance[0] == 0)
@ -96,22 +96,21 @@ function ca_protect_unit_attack:evaluation(ai, cfg, self)
end
if best_attack then
self.data.PU_best_attack = best_attack
return 95000
end
return 0
end
function ca_protect_unit_attack:execution(ai, cfg, self)
local attacker = wesnoth.get_unit(self.data.PU_best_attack.src.x, self.data.PU_best_attack.src.y)
local defender = wesnoth.get_unit(self.data.PU_best_attack.target.x, self.data.PU_best_attack.target.y)
function ca_protect_unit_attack:execution(cfg)
local attacker = wesnoth.get_unit(best_attack.src.x, best_attack.src.y)
local defender = wesnoth.get_unit(best_attack.target.x, best_attack.target.y)
AH.movefull_stopunit(ai, attacker, self.data.PU_best_attack.dst.x, self.data.PU_best_attack.dst.y)
AH.movefull_stopunit(ai, attacker, best_attack.dst.x, best_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)
self.data.PU_best_attack = nil
best_attack = nil
end
return ca_protect_unit_attack

View file

@ -1,16 +1,17 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_protect_unit_finish = {}
local ca_protect_unit_finish, PU_unit, PU_goal = {}
function ca_protect_unit_finish:evaluation(ai, cfg, self)
function ca_protect_unit_finish:evaluation(cfg)
-- If a unit can make it to the goal, this is the first thing that happens
for i,id in ipairs(cfg.id) do
local unit = AH.get_units_with_moves { id = id }[1]
for u in H.child_range(cfg, "unit") do
local unit = AH.get_units_with_moves { id = u.id }[1]
if unit then
local path, cost = wesnoth.find_path(unit, cfg.goal_x[i], cfg.goal_y[i])
if (cost <= unit.moves) and ((unit.x ~= cfg.goal_x[i]) or (unit.y ~= cfg.goal_y[i])) then
self.data.PU_unit = unit
self.data.PU_goal = { cfg.goal_x[i], cfg.goal_y[i] }
local path, cost = wesnoth.find_path(unit, u.goal_x, u.goal_y)
if (cost <= unit.moves) and ((unit.x ~= u.goal_x) or (unit.y ~= u.goal_y)) then
PU_unit = unit
PU_goal = { u.goal_x, u.goal_y }
return 300000
end
end
@ -18,9 +19,9 @@ function ca_protect_unit_finish:evaluation(ai, cfg, self)
return 0
end
function ca_protect_unit_finish:execution(ai, cfg, self)
AH.movefull_stopunit(ai, self.data.PU_unit, self.data.PU_goal)
self.data.PU_unit, self.data.PU_goal = nil, nil
function ca_protect_unit_finish:execution(cfg)
AH.movefull_stopunit(ai, PU_unit, PU_goal)
PU_unit, PU_goal = nil, nil
end
return ca_protect_unit_finish

View file

@ -5,20 +5,20 @@ local BC = wesnoth.require "ai/lua/battle_calcs.lua"
local function get_protected_units(cfg)
local units = {}
for _,id in ipairs(cfg.id) do
table.insert(units, AH.get_units_with_moves { id = id }[1])
for u in H.child_range(cfg, "unit") do
table.insert(units, AH.get_units_with_moves { id = u.id }[1])
end
return units
end
local ca_protect_unit_move = {}
function ca_protect_unit_move:evaluation(ai, cfg, self)
function ca_protect_unit_move:evaluation(cfg)
if get_protected_units(cfg)[1] then return 94999 end
return 0
end
function ca_protect_unit_move:execution(ai, cfg, self)
function ca_protect_unit_move:execution(cfg, data)
-- Find and execute best (safest) move toward goal
local protected_units = get_protected_units(cfg)
@ -40,8 +40,8 @@ function ca_protect_unit_move:execution(ai, cfg, self)
-- We move the weakest (fewest HP unit) first
local unit = AH.choose(protected_units, function(u) return - u.hitpoints end)
local goal = {}
for i,id in ipairs(cfg.id) do
if (unit.id == id) then goal = { cfg.goal_x[i], cfg.goal_y[i] } end
for u in H.child_range(cfg, "unit") do
if (unit.id == u.id) then goal = { u.goal_x, u.goal_y } end
end
local reach_map = AH.get_reachable_unocc(unit)
@ -58,10 +58,10 @@ function ca_protect_unit_move:execution(ai, cfg, self)
end)
-- Configuration parameters (no option to change these enabled at the moment)
local enemy_weight = self.data.PU_enemy_weight or 100.
local my_unit_weight = self.data.PU_my_unit_weight or 1.
local distance_weight = self.data.PU_distance_weight or 3.
local terrain_weight = self.data.PU_terrain_weight or 0.1
local enemy_weight = data.PU_enemy_weight or 100.
local my_unit_weight = data.PU_my_unit_weight or 1.
local distance_weight = data.PU_distance_weight or 3.
local terrain_weight = data.PU_terrain_weight or 0.1
-- If there are no enemies left, only distance to goal matters
-- This is to avoid rare situations where moving toward goal rating is canceled by rating for moving away from own units

View file

@ -1,12 +1,12 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require("ai/lua/ai_helper.lua")
local LS = wesnoth.dofile "lua/location_set.lua"
local LS = wesnoth.require "lua/location_set.lua"
local recruit_type
local ca_recruit_random = {}
function ca_recruit_random:evaluation(ai, cfg)
function ca_recruit_random:evaluation(cfg)
-- Random recruiting from all the units the side has
-- Check if leader is on keep
@ -59,15 +59,14 @@ function ca_recruit_random:evaluation(ai, cfg)
local probabilities, probability_sum = {}, 0
-- Go through all the types listed in [probability] tags (which can be comma-separated lists)
-- Types and probabilities are put into cfg.type and cfg.prob arrays by micro_ai_wml_tag.lua
for ind,types in ipairs(cfg.type) do
types = AH.split(types, ",")
for prob in H.child_range(cfg, "probability") do
types = AH.split(prob.type, ",")
for _,typ in ipairs(types) do -- 'type' is a reserved keyword in Lua
-- If this type is in the recruit list, add it
for _,recruit in ipairs(wesnoth.sides[wesnoth.current.side].recruit) do
if (recruit == typ) then
probabilities[typ] = { value = cfg.prob[ind] }
probability_sum = probability_sum + cfg.prob[ind]
probabilities[typ] = { value = prob.probability }
probability_sum = probability_sum + prob.probability
break
end
end
@ -119,7 +118,7 @@ function ca_recruit_random:evaluation(ai, cfg)
return cfg.ca_score
end
function ca_recruit_random:execution(ai, cfg)
function ca_recruit_random:execution(cfg)
-- Let this function blacklist itself if the chosen recruit is too expensive
if wesnoth.unit_types[recruit_type].cost <= wesnoth.sides[wesnoth.current.side].gold then
AH.checked_recruit(ai, recruit_type)

View file

@ -1,22 +1,21 @@
local internal_recruit_cas = {}
local internal_params = {}
local ai
-- The following external engine creates the CA functions recruit_rushers_eval and recruit_rushers_exec
-- It also exposes find_best_recruit and find_best_recruit_hex for use by other recruit engines
-- 'ai' is nil here (not defined), so we pass it directly in the execution function below
wesnoth.require("ai/lua/generic_recruit_engine.lua").init(ai, internal_recruit_cas, internal_params)
local ca_recruit_rushers = {}
function ca_recruit_rushers:evaluation(ai, cfg)
function ca_recruit_rushers:evaluation(cfg)
internal_params.randomness = cfg.randomness
internal_params.score_function = function() return cfg.ca_score end
return internal_recruit_cas:recruit_rushers_eval()
end
function ca_recruit_rushers:execution(ai)
return internal_recruit_cas:recruit_rushers_exec(ai)
function ca_recruit_rushers:execution()
return internal_recruit_cas:recruit_rushers_exec()
end
return ca_recruit_rushers

View file

@ -1,7 +1,8 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_guardian(cfg)
local filter = cfg.filter or { id = cfg.id }
local filter = H.get_child(cfg, "filter") or { id = cfg.id }
local guardian = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", filter }
@ -12,7 +13,7 @@ end
local ca_return_guardian = {}
function ca_return_guardian:evaluation(ai, cfg)
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
@ -25,7 +26,7 @@ function ca_return_guardian:evaluation(ai, cfg)
return 0
end
function ca_return_guardian:execution(ai, cfg)
function ca_return_guardian:execution(cfg)
local guardian = get_guardian(cfg)
-- In case the return hex is occupied:

View file

@ -1,23 +1,25 @@
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 = {}
local ca_simple_attack, best_attack = {}
function ca_simple_attack:evaluation(ai, cfg, self)
function ca_simple_attack:evaluation(cfg)
local units = AH.get_units_with_attacks {
side = wesnoth.current.side,
{ "and", cfg.filter }
{ "and", H.get_child(cfg, "filter") }
}
if (not units[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_filter = H.get_child(cfg, "filter_second")
local enemy_map
if cfg.filter_second then
if enemy_filter then
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "and", cfg.filter_second }
{ "and", enemy_filter }
}
if (not enemies[1]) then return 0 end
@ -29,10 +31,10 @@ function ca_simple_attack:evaluation(ai, cfg, self)
local attacks = AH.get_attacks(units, { include_occupied = true })
if (not attacks[1]) then return 0 end
local max_rating, best_attack = -9e99
local max_rating = -9e99
for _, 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
if enemy_filter and (not enemy_map:get(att.target.x, att.target.y)) then
valid_target = false
end
@ -50,23 +52,22 @@ function ca_simple_attack:evaluation(ai, cfg, self)
end
if best_attack then
self.data.SA_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.SA_attack.src.x, self.data.SA_attack.src.y)
local defender = wesnoth.get_unit(self.data.SA_attack.target.x, self.data.SA_attack.target.y)
function ca_simple_attack:execution(cfg)
local attacker = wesnoth.get_unit(best_attack.src.x, best_attack.src.y)
local defender = wesnoth.get_unit(best_attack.target.x, best_attack.target.y)
AH.movefull_outofway_stopunit(ai, attacker, self.data.SA_attack.dst.x, self.data.SA_attack.dst.y)
AH.movefull_outofway_stopunit(ai, attacker, best_attack.dst.x, best_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, (cfg.weapon or -1))
self.data.SA_attack = nil
best_attack = nil
end
return ca_simple_attack

View file

@ -2,7 +2,7 @@ local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_guardian(cfg)
local filter = cfg.filter or { id = cfg.id }
local filter = H.get_child(cfg, "filter") or { id = cfg.id }
local guardian = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", filter }
@ -12,12 +12,12 @@ end
local ca_stationed_guardian = {}
function ca_stationed_guardian:evaluation(ai, cfg)
function ca_stationed_guardian:evaluation(cfg)
if get_guardian(cfg) then return cfg.ca_score end
return 0
end
function ca_stationed_guardian:execution(ai, cfg)
function ca_stationed_guardian:execution(cfg)
-- (s_x, s_y): coordinates where guardian is stationed; tries to move here if there is nobody to attack
-- (g_x, g_y): location that the guardian guards

View file

@ -3,7 +3,7 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_swarm_move = {}
function ca_swarm_move:evaluation(ai, cfg)
function ca_swarm_move:evaluation(cfg)
local units = wesnoth.get_units { side = wesnoth.current.side }
for _,unit in ipairs(units) do
if (unit.moves > 0) then return cfg.ca_score end
@ -12,7 +12,7 @@ function ca_swarm_move:evaluation(ai, cfg)
return 0
end
function ca_swarm_move:execution(ai, cfg)
function ca_swarm_move:execution(cfg)
local enemy_distance = cfg.enemy_distance or 5
local vision_distance = cfg.vision_distance or 12

View file

@ -18,13 +18,13 @@ end
local ca_swarm_scatter = {}
function ca_swarm_scatter:evaluation(ai, cfg)
function ca_swarm_scatter:evaluation(cfg)
if (not get_enemies(cfg)[1]) then return 0 end
if (not get_swarm_units(cfg)[1]) then return 0 end
return cfg.ca_score
end
function ca_swarm_scatter:execution(ai, cfg)
function ca_swarm_scatter:execution(cfg)
local enemies = get_enemies(cfg)
local units = get_swarm_units(cfg)
local vision_distance = cfg.vision_distance or 12

View file

@ -5,7 +5,7 @@ local BC = wesnoth.require "ai/lua/battle_calcs.lua"
local function get_wolves(cfg)
local wolves = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter }
{ "and", H.get_child(cfg, "filter") }
}
return wolves
end
@ -13,20 +13,20 @@ end
local function get_prey(cfg)
local prey = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "and", cfg.filter_second }
{ "and", H.get_child(cfg, "filter_second") }
}
return prey
end
local ca_wolves_move = {}
function ca_wolves_move:evaluation(ai, cfg)
function ca_wolves_move:evaluation(cfg)
if (not get_wolves(cfg)[1]) then return 0 end
if (not get_prey(cfg)[1]) then return 0 end
return cfg.ca_score
end
function ca_wolves_move:execution(ai, cfg)
function ca_wolves_move:execution(cfg)
local wolves = get_wolves(cfg)
local prey = get_prey(cfg)

View file

@ -5,7 +5,7 @@ local WMPF = wesnoth.require "ai/micro_ais/cas/ca_wolves_multipacks_functions.lu
local ca_wolves_multipacks_attack = {}
function ca_wolves_multipacks_attack:evaluation(ai, cfg)
function ca_wolves_multipacks_attack:evaluation(cfg)
-- If wolves have attacks left, call this CA
-- It will be disabled by being black-listed, so as to avoid
-- having to do the full attack evaluation for every single move evaluation
@ -19,7 +19,7 @@ function ca_wolves_multipacks_attack:evaluation(ai, cfg)
return 0
end
function ca_wolves_multipacks_attack:execution(ai, cfg)
function ca_wolves_multipacks_attack:execution(cfg)
local packs = WMPF.assign_packs(cfg)
-- Attacks are dealt with on a pack by pack basis

View file

@ -6,7 +6,7 @@ local WMPF = wesnoth.require "ai/micro_ais/cas/ca_wolves_multipacks_functions.lu
local ca_wolves_multipacks_wander = {}
function ca_wolves_multipacks_wander:evaluation(ai, cfg)
function ca_wolves_multipacks_wander:evaluation(cfg)
-- When there's nothing to attack, the wolves wander and regroup into their packs
local wolves = AH.get_units_with_moves {
@ -18,7 +18,7 @@ function ca_wolves_multipacks_wander:evaluation(ai, cfg)
return 0
end
function ca_wolves_multipacks_wander:execution(ai, cfg)
function ca_wolves_multipacks_wander:execution(cfg)
local packs = WMPF.assign_packs(cfg)
for pack_number,pack in pairs(packs) do

View file

@ -6,20 +6,20 @@ local LS = wesnoth.require "lua/location_set.lua"
local function get_wolves(cfg)
local wolves = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", cfg.filter }
{ "and", H.get_child(cfg, "filter") }
}
return wolves
end
local ca_wolves_wander = {}
function ca_wolves_wander:evaluation(ai, cfg)
function ca_wolves_wander:evaluation(cfg)
-- When there's no prey left, the wolves wander and regroup
if get_wolves(cfg)[1] then return cfg.ca_score end
return 0
end
function ca_wolves_wander:execution(ai, cfg)
function ca_wolves_wander:execution(cfg)
local wolves = get_wolves(cfg)
-- Number of wolves that can reach each hex

View file

@ -3,7 +3,7 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local LS = wesnoth.require "lua/location_set.lua"
local function get_guardian(cfg)
local filter = cfg.filter or { id = cfg.id }
local filter = H.get_child(cfg, "filter") or { id = cfg.id }
local guardian = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", filter }
@ -13,16 +13,17 @@ end
local ca_zone_guardian = {}
function ca_zone_guardian:evaluation(ai, cfg)
function ca_zone_guardian:evaluation(cfg)
if get_guardian(cfg) then return cfg.ca_score end
return 0
end
function ca_zone_guardian:execution(ai, cfg)
function ca_zone_guardian:execution(cfg)
local guardian = get_guardian(cfg)
local reach = wesnoth.find_reach(guardian)
local zone_enemy = cfg.filter_location_enemy or cfg.filter_location
local zone = H.get_child(cfg, "filter_location")
local zone_enemy = H.get_child(cfg, "filter_location_enemy") or zone
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "filter_location", zone_enemy }
@ -95,7 +96,7 @@ function ca_zone_guardian:execution(ai, cfg)
local locs_map = LS.of_pairs(wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", cfg.filter_location }
{ "and", zone }
})
-- Check out which of those hexes the guardian can reach

View file

@ -6,7 +6,7 @@ def is_swamp(map, xx, yy)
map(
filter( map.terrain, (x=xx) and (y=yy)),
self.id
)[0] = 'swamp_water';
)[0] = 'swamp_water_reed';
def reachable_swamp(unit_loc,map)
# get all reachable swamp locs for unit at unit_loc #
@ -36,9 +36,9 @@ def weakest_defender(attacks)
# Attack if possible, otherwise random move #
if( size(possible_attacks) != 0,
weakest_defender(possible_attacks),
if( size(swamp_in_reach) != 0,
move(me.loc,random(swamp_in_reach)),
end)
if( size(swamp_in_reach) != 0,
move(me.loc,random(swamp_in_reach)),
end)
)
where possible_attacks = reachable_enemies_next_to_swamp( me.loc, my_attacks,map)

View file

@ -1,5 +1,5 @@
return {
init = function(ai)
init = function()
local priority_target = {}
local H = wesnoth.require "lua/helper.lua"

View file

@ -1,5 +1,5 @@
return {
init = function(ai)
init = function()
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local urudin = {}

View file

@ -0,0 +1,133 @@
local H = wesnoth.require "lua/helper.lua"
local MAIH = wesnoth.require("ai/micro_ais/micro_ai_helper.lua")
function wesnoth.micro_ais.big_animals(cfg)
local required_keys = { "[filter]"}
local optional_keys = { "[avoid_unit]", "[filter_location]", "[filter_location_wander]" }
local CA_parms = {
ai_id = 'mai_big_animals',
{ ca_id = "move", location = 'ca_big_animals.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end
function wesnoth.micro_ais.wolves(cfg)
local required_keys = { "[filter]", "[filter_second]" }
local optional_keys = { "attack_only_prey", "avoid_type" }
local score = cfg.ca_score or 90000
local CA_parms = {
ai_id = 'mai_wolves',
{ ca_id = "move", location = 'ca_wolves_move.lua', score = score },
{ ca_id = "wander", location = 'ca_wolves_wander.lua', score = score - 1 }
}
if cfg.attack_only_prey then
local wolves_aspects = {
{
aspect = "attacks",
facet = {
name = "ai_default_rca::aspect_attacks",
id = "dont_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_enemy", {
{ "and", H.get_child(cfg, "filter_second") }
} }
}
}
}
if (cfg.action == "delete") then
MAIH.delete_aspects(cfg.side, wolves_aspects)
else
MAIH.add_aspects(cfg.side, wolves_aspects)
end
elseif cfg.avoid_type then
local wolves_aspects = {
{
aspect = "attacks",
facet = {
name = "ai_default_rca::aspect_attacks",
id = "dont_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_enemy", {
{ "not", {
type=cfg.avoid_type
} }
} }
}
}
}
if (cfg.action == "delete") then
MAIH.delete_aspects(cfg.side, wolves_aspects)
else
MAIH.add_aspects(cfg.side, wolves_aspects)
end
end
return required_keys, optional_keys, CA_parms
end
function wesnoth.micro_ais.herding(cfg)
local required_keys = { "[filter_location]", "[filter]", "[filter_second]", "herd_x", "herd_y" }
local optional_keys = { "attention_distance", "attack_distance" }
local score = cfg.ca_score or 300000
local CA_parms = {
ai_id = 'mai_herding',
{ ca_id = "attack_close_enemy", location = 'ca_herding_attack_close_enemy.lua', score = score },
{ ca_id = "sheep_runs_enemy", location = 'ca_herding_sheep_runs_enemy.lua', score = score - 1 },
{ ca_id = "sheep_runs_dog", location = 'ca_herding_sheep_runs_dog.lua', score = score - 2 },
{ ca_id = "herd_sheep", location = 'ca_herding_herd_sheep.lua', score = score - 3 },
{ ca_id = "sheep_move", location = 'ca_herding_sheep_move.lua', score = score - 4 },
{ ca_id = "dog_move", location = 'ca_herding_dog_move.lua', score = score - 5 },
{ ca_id = "dog_stopmove", location = 'ca_herding_dog_stopmove.lua', score = score - 6 }
}
return required_keys, optional_keys, CA_parms
end
function wesnoth.micro_ais.forest_animals(cfg)
local optional_keys = { "rabbit_type", "rabbit_number", "rabbit_enemy_distance", "rabbit_hole_img",
"tusker_type", "tusklet_type", "deer_type", "[filter_location]"
}
local score = cfg.ca_score or 300000
local CA_parms = {
ai_id = 'mai_forest_animals',
{ ca_id = "new_rabbit", location = 'ca_forest_animals_new_rabbit.lua', score = score },
{ ca_id = "tusker_attack", location = 'ca_forest_animals_tusker_attack.lua', score = score - 1 },
{ ca_id = "move", location = 'ca_forest_animals_move.lua', score = score - 2 },
{ ca_id = "tusklet_move", location = 'ca_forest_animals_tusklet_move.lua', score = score - 3 }
}
return {}, optional_keys, CA_parms
end
function wesnoth.micro_ais.swarm(cfg)
local optional_keys = { "scatter_distance", "vision_distance", "enemy_distance" }
local score = cfg.ca_score or 300000
local CA_parms = {
ai_id = 'mai_swarm',
{ ca_id = "scatter", location = 'ca_swarm_scatter.lua', score = score },
{ ca_id = "move", location = 'ca_swarm_move.lua', score = score - 1 }
}
return {}, optional_keys, CA_parms
end
function wesnoth.micro_ais.wolves_multipacks(cfg)
local optional_keys = { "type", "pack_size", "show_pack_number" }
local score = cfg.ca_score or 300000
local CA_parms = {
ai_id = 'mai_wolves_multipacks',
{ ca_id = "attack", location = 'ca_wolves_multipacks_attack.lua', score = score },
{ ca_id = "wander", location = 'ca_wolves_multipacks_wander.lua', score = score - 1 }
}
return {}, optional_keys, CA_parms
end
function wesnoth.micro_ais.hunter(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Hunter [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "home_x", "home_y" }
local optional_keys = { "id", "[filter]", "[filter_location]", "rest_turns", "show_messages" }
local CA_parms = {
ai_id = 'mai_hunter',
{ ca_id = "move", location = 'ca_hunter.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end

View file

@ -0,0 +1,12 @@
function wesnoth.micro_ais.bottleneck_defense(cfg)
local required_keys = { "x", "y", "enemy_x", "enemy_y" }
local optional_keys = { "healer_x", "healer_y", "leadership_x", "leadership_y", "active_side_leader" }
local score = cfg.ca_score or 300000
local CA_parms = {
ai_id = 'mai_bottleneck',
{ ca_id = 'move', location = 'ca_bottleneck_move.lua', score = score },
{ ca_id = 'attack', location = 'ca_bottleneck_attack.lua', score = score - 1 }
}
return required_keys, optional_keys, CA_parms
end

View file

@ -0,0 +1,17 @@
local H = wesnoth.require "lua/helper.lua"
function wesnoth.micro_ais.messenger_escort(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Messenger [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "waypoint_x", "waypoint_y" }
local optional_keys = { "id", "enemy_death_chance", "[filter]", "[filter_second]", "invert_order", "messenger_death_chance" }
local score = cfg.ca_score or 300000
local CA_parms = {
ai_id = 'mai_messenger',
{ ca_id = 'attack', location = 'ca_messenger_attack.lua', score = score },
{ ca_id = 'move', location = 'ca_messenger_move.lua', score = score - 1 },
{ ca_id = 'escort_move', location = 'ca_messenger_escort_move.lua', score = score - 2 }
}
return required_keys, optional_keys, CA_parms
end

View file

@ -0,0 +1,115 @@
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
function wesnoth.micro_ais.fast_ai(cfg)
local optional_keys = {
"attack_hidden_enemies", "[avoid]", "dungeon_mode",
"[filter]", "[filter_second]", "include_occupied_attack_hexes",
"leader_additional_threat", "leader_attack_max_units", "leader_weight", "move_cost_factor",
"weak_units_first", "skip_combat_ca", "skip_move_ca", "threatened_leader_fights"
}
local CA_parms = {
ai_id = 'mai_fast',
{ ca_id = 'combat', location = 'ca_fast_combat.lua', score = 100000 },
{ ca_id = 'move', location = 'ca_fast_move.lua', score = 20000 },
{ ca_id = 'combat_leader', location = 'ca_fast_combat_leader.lua', score = 19900 }
}
-- Also need to delete/add some default CAs
if (cfg.action == 'delete') then
-- This can be done independently of whether these were removed earlier
W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="combat",
engine="cpp",
name="ai_default_rca::combat_phase",
max_score=100000,
score=100000
} }
}
W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="villages",
engine="cpp",
name="ai_default_rca::get_villages_phase",
max_score=60000,
score=60000
} }
}
W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="retreat",
engine="cpp",
name="ai_default_rca::retreat_phase",
max_score=40000,
score=40000
} }
}
W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="move_to_targets",
engine="cpp",
name="ai_default_rca::move_to_targets_phase",
max_score=20000,
score=20000
} }
}
else
if (not cfg.skip_combat_ca) then
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[combat]"
}
else
for i,parm in ipairs(CA_parms) do
if (parm.ca_id == 'combat') or (parm.ca_id == 'combat_leader') then
table.remove(CA_parms, i)
end
end
end
if (not cfg.skip_move_ca) then
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[villages]"
}
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[retreat]"
}
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[move_to_targets]"
}
else
for i,parm in ipairs(CA_parms) do
if (parm.ca_id == 'move') then
table.remove(CA_parms, i)
break
end
end
end
end
return {}, optional_keys, CA_parms
end

View file

@ -0,0 +1,53 @@
local H = wesnoth.require "lua/helper.lua"
function wesnoth.micro_ais.stationed_guardian(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Stationed Guardian [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "distance", "station_x", "station_y" }
local optional_keys = { "id", "[filter]", "guard_x", "guard_y" }
local CA_parms = {
ai_id = 'mai_stationed_guardian',
{ ca_id = 'move', location = 'ca_stationed_guardian.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end
function wesnoth.micro_ais.zone_guardian(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Zone Guardian [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "[filter_location]" }
local optional_keys = { "id", "[filter]", "[filter_location_enemy]", "station_x", "station_y" }
local CA_parms = {
ai_id = 'mai_zone_guardian',
{ ca_id = 'move', location = 'ca_zone_guardian.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end
function wesnoth.micro_ais.return_guardian(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Return Guardian [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "return_x", "return_y" }
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 }
}
return required_keys, optional_keys, CA_parms
end
function wesnoth.micro_ais.coward(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Coward [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "distance" }
local optional_keys = { "attack_if_trapped", "id", "[filter]", "[filter_second]", "seek_x", "seek_y","avoid_x","avoid_y" }
local CA_parms = {
ai_id = 'mai_coward',
{ ca_id = 'move', location = 'ca_coward.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end

View file

@ -0,0 +1,17 @@
function wesnoth.micro_ais.healer_support(cfg)
local optional_keys = { "aggression", "injured_units_only", "max_threats", "[filter]", "[filter_second]" }
-- Scores for this AI need to be hard-coded, it does not work otherwise
local CA_parms = {
ai_id = 'mai_healer',
{ ca_id = 'initialize', location = 'ca_healer_initialize.lua', score = 999990 },
{ ca_id = 'move', location = 'ca_healer_move.lua', score = 105000 },
}
-- 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 })
end
return {}, optional_keys, CA_parms
end

View file

@ -0,0 +1,42 @@
function wesnoth.micro_ais.lurkers(cfg)
local required_keys = { "[filter]", "[filter_location]" }
local optional_keys = { "stationary", "[filter_location_wander]" }
local CA_parms = {
ai_id = 'mai_lurkers',
{ ca_id = 'move', location = 'ca_lurkers.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end
-- goto is a keyword, so need to use index operator directly
wesnoth.micro_ais["goto"] = function(cfg)
local required_keys = { "[filter_location]" }
local optional_keys = {
"avoid_enemies", "[filter]", "ignore_units", "ignore_enemy_at_goal",
"release_all_units_at_goal", "release_unit_at_goal", "unique_goals", "use_straight_line"
}
local CA_parms = {
ai_id = 'mai_goto',
{ ca_id = 'move', location = 'ca_goto.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end
function wesnoth.micro_ais.hang_out(cfg)
local optional_keys = { "[filter]", "[filter_location]", "[avoid]", "[mobilize_condition]", "mobilize_on_gold_less_than" }
local CA_parms = {
ai_id = 'mai_hang_out',
{ ca_id = 'move', location = 'ca_hang_out.lua', score = cfg.ca_score or 170000 }
}
return {}, optional_keys, CA_parms
end
function wesnoth.micro_ais.simple_attack(cfg)
local optional_keys = { "[filter]", "[filter_second]", "weapon" }
local CA_parms = {
ai_id = 'mai_simple_attack',
{ ca_id = 'move', location = 'ca_simple_attack.lua', score = cfg.ca_score or 110000 }
}
return {}, optional_keys, CA_parms
end

View file

@ -0,0 +1,14 @@
local H = wesnoth.require "lua/helper.lua"
function wesnoth.micro_ais.patrol(cfg)
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Patrol [micro_ai] tag requires either id= key or [filter] tag")
end
local required_keys = { "waypoint_x", "waypoint_y" }
local optional_keys = { "id", "[filter]", "attack", "one_time_only", "out_and_back" }
local CA_parms = {
ai_id = 'mai_patrol',
{ ca_id = "move", location = 'ca_patrol.lua', score = cfg.ca_score or 300000 }
}
return required_keys, optional_keys, CA_parms
end

View file

@ -0,0 +1,75 @@
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local MAIH = wesnoth.require("ai/micro_ais/micro_ai_helper.lua")
function wesnoth.micro_ais.protect_unit(cfg)
-- Scores for this AI need to be hard-coded, it does not work otherwise
local CA_parms = {
ai_id = 'mai_protect_unit',
{ ca_id = 'finish', location = 'ca_protect_unit_finish.lua', score = 300000 },
{ ca_id = 'attack', location = 'ca_protect_unit_attack.lua', score = 95000 },
{ ca_id = 'move', location = 'ca_protect_unit_move.lua', score = 94999 }
}
local unit_ids = {}
for u in H.child_range(cfg, "unit") do
if not u.id then
H.wml_error("Protect Unit Micro AI missing id key in [unit] tag")
end
if not u.goal_x then
H.wml_error("Protect Unit Micro AI missing goal_x key in [unit] tag")
end
if not u.goal_y then
H.wml_error("Protect Unit Micro AI missing goal_y key in [unit] tag")
end
table.insert(unit_ids, u.id)
end
-- Optional key disable_move_leader_to_keep: needs to be dealt with
-- separately as it affects a default CA
if cfg.disable_move_leader_to_keep then
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[move_leader_to_keep]"
}
end
-- attacks aspects also needs to be set separately
local aspect_parms = {
{
aspect = "attacks",
facet = {
name = "ai_default_rca::aspect_attacks",
ca_id = "dont_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_own", {
{ "not", {
id = unit_ids
} }
} }
}
}
}
if (cfg.action == "delete") then
MAIH.delete_aspects(cfg.side, aspect_parms)
-- We also need to add the move_leader_to_keep CA back in
-- This works even if it was not removed, it simply overwrites the existing CA
W.modify_ai {
side = side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="move_leader_to_keep",
engine="cpp",
name="ai_default_rca::move_leader_to_keep_phase",
max_score=160000,
score=160000
} }
}
else
MAIH.add_aspects(cfg.side, aspect_parms)
end
return {"[unit]"}, {}, CA_parms
end

View file

@ -0,0 +1,50 @@
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local function handle_default_recruitment(cfg)
-- Also need to delete/add the default recruitment CA
if cfg.action == 'add' then
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[recruitment]"
}
elseif cfg.action == 'delete' then
-- We need to add the recruitment CA back in
-- This works even if it was not removed, it simply overwrites the existing CA
W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="recruitment",
engine="cpp",
name="ai_default_rca::aspect_recruitment_phase",
max_score=180000,
score=180000
} }
}
end
end
function wesnoth.micro_ais.recruit_rushers(cfg)
local optional_keys = { "randomness" }
local CA_parms = {
ai_id = 'mai_rusher_recruit',
{ ca_id = "move", location = 'ca_recruit_rushers.lua', score = cfg.ca_score or 180000 }
}
handle_default_recruitment(cfg)
return {}, optional_keys, CA_parms
end
function wesnoth.micro_ais.recruit_random(cfg)
local optional_keys = { "skip_low_gold_recruiting", "[probability]" }
local CA_parms = {
ai_id = 'mai_random_recruit',
{ ca_id = "move", location = 'ca_recruit_random.lua', score = cfg.ca_score or 180000 }
}
handle_default_recruitment(cfg)
return {}, optional_keys, CA_parms
end

View file

@ -1,24 +1,38 @@
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local T = H.set_wml_tag_metatable {}
local AH = wesnoth.require("ai/lua/ai_helper.lua")
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
local micro_ai_helper = {}
function micro_ai_helper.add_CAs(side, CA_parms, CA_cfg)
function micro_ai_helper.add_CAs(side, ca_id_core, CA_parms, CA_cfg)
-- Add the candidate actions defined in @CA_parms to the AI of @side
-- @CA_parms is an array of tables, one for each CA to be added (CA setup parameters)
-- and also contains one key: ai_id
-- @CA_cfg is a table with the parameters passed to the eval/exec functions
-- @ca_id_core: ca_id= key from the [micro_ai] tag
-- @CA_parms: array of tables, one for each CA to be added (CA setup parameters)
-- Also contains one key: ai_id
-- @CA_cfg: table with the parameters passed to the eval/exec functions
--
-- Required keys for each table of @CA_parms:
-- - ca_id: is used for CA id/name
-- - location: the path+file name for the external CA file
-- - score: the evaluation score
-- We need to make sure that the id/name of each CA are unique.
-- We do this by checking if CAs starting with ai_id exist already
-- If yes, we add numbers to the end of ai_id until we find an id that does not exist yet
-- About ai_id, ca_id_core and ca_id:
-- ai_id: If the AI stores information in the [data] variable, we need to
-- ensure that it is uniquely attributed to this AI, and not to a separate
-- AI of the same type. ai_id is used for this and must therefore be unique.
-- We ensure this by checking if CAs or [data][micro_ai] tags using the
-- default ai_id value exist already and if so, by adding numbers to the end
-- until we find an id that is not used yet.
-- ca_id_core: This is used as base for the id= and name= keys of the
-- [candidate_action] tags. If [micro_ai]ca_id= is given, we use it as is
-- without checking if an AI with this id already exists. This is required in
-- order to ensure that removal with action=delete is possible and it is the
-- responsibility of the user to ensure uniqueness. If [micro_ai]ca_id= is not
-- given, use ai_id for ca_id_core, which also makes ids unique for this case.
-- ca_id: This is specific to the individual CAs of an AI and is added to
-- ca_id_core for the names and ids of each CA.
local ai_id, id_found = CA_parms.ai_id, true
@ -38,7 +52,7 @@ function micro_ai_helper.add_CAs(side, CA_parms, CA_cfg)
end
-- Ideally, we would also delete previous occurrences of [micro_ai] tags in the
-- AI's self.data variable. However, the MAI can be changed while it is not
-- AI's data variable. However, the MAI can be changed while it is not
-- the AI's turn, when this is not possible. So instead, we check for the
-- existence of such tags and make sure we are using a different ai_id.
for ai_tag in H.child_range(wesnoth.sides[side].__cfg, 'ai') do
@ -58,9 +72,12 @@ function micro_ai_helper.add_CAs(side, CA_parms, CA_cfg)
n = n + 1
end
-- For CA ids and names, use value of [micro_ai]ca_id= if given, ai_id otherwise
ca_id_core = ca_id_core or ai_id
-- Now add the CAs
for _,parms in ipairs(CA_parms) do
local ca_id = ai_id .. '_' .. parms.ca_id
local ca_id = ca_id_core .. '_' .. parms.ca_id
-- Always pass the ai_id and ca_score to the eval/exec functions
CA_cfg.ai_id = ai_id
@ -74,27 +91,29 @@ function micro_ai_helper.add_CAs(side, CA_parms, CA_cfg)
}
CA.location = parms.location
local cfg = string.sub(AH.serialize(CA_cfg), 2, -2) -- need to strip surrounding {}
CA.eval_parms = cfg
CA.exec_parms = cfg
table.insert(CA, T.args(CA_cfg))
W.modify_ai {
side = side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", CA }
T.candidate_action(CA)
}
end
end
function micro_ai_helper.delete_CAs(side, CA_parms)
function micro_ai_helper.delete_CAs(side, ca_id_core, CA_parms)
-- Delete the candidate actions defined in @CA_parms from the AI of @side
-- @CA_parms is an array of tables, one for each CA to be removed
-- We can simply pass the one used for add_CAs(), although only the
-- CA_parms.ca_id field is needed
-- @ca_id_core: ca_id= key from the [micro_ai] tag
-- @CA_parms: array of tables, one for each CA to be removed
-- We can simply pass the one used for add_CAs(), although only the
-- CA_parms.ca_id field is needed
-- For CA ids, use value of [micro_ai]ca_id= if given, ai_id otherwise
ca_id_core = ca_id_core or CA_parms.ai_id
for _,parms in ipairs(CA_parms) do
local ca_id = CA_parms.ai_id .. '_' .. parms.ca_id
local ca_id = ca_id_core .. '_' .. parms.ca_id
W.modify_ai {
side = side,
@ -136,7 +155,7 @@ function micro_ai_helper.add_aspects(side, aspect_parms)
side = side,
action = "add",
path = "aspect[" .. parms.aspect .. "].facet",
{ "facet", parms.facet }
T.facet(parms.facet)
}
end
end
@ -157,16 +176,8 @@ function micro_ai_helper.delete_aspects(side, aspect_parms)
end
function micro_ai_helper.micro_ai_setup(cfg, CA_parms, required_keys, optional_keys)
-- If cfg.ca_id is set, it gets used as the ai_id= key
-- This allows for selective removal of CAs
-- Note: the ca_id key of the [micro_ai] tag should really be renamed to ai_id,
-- but that would mean breaking backward compatibility, so we'll just deal with it internally instead
CA_parms.ai_id = cfg.ca_id or CA_parms.ai_id
-- If action=delete, we do that and are done
if (cfg.action == 'delete') then
micro_ai_helper.delete_CAs(cfg.side, CA_parms)
micro_ai_helper.delete_CAs(cfg.side, cfg.ca_id, CA_parms)
return
end
@ -175,26 +186,39 @@ function micro_ai_helper.micro_ai_setup(cfg, CA_parms, required_keys, optional_k
-- Required keys
for _,v in pairs(required_keys) do
local child = H.get_child(cfg, v)
if (not cfg[v]) and (not child) then
H.wml_error("[micro_ai] tag (" .. cfg.ai_type .. ") is missing required parameter: " .. v)
if v:match('%[[a-zA-Z0-9_]+%]') then
v = v:sub(2,-2)
if not H.get_child(cfg, v) then
H.wml_error("[micro_ai] tag (" .. cfg.ai_type .. ") is missing required parameter: [" .. v .. "]")
end
for child in H.child_range(cfg, v) do
table.insert(CA_cfg, T[v](child))
end
else
if not cfg[v] then
H.wml_error("[micro_ai] tag (" .. cfg.ai_type .. ") is missing required parameter: " .. v .."=")
end
CA_cfg[v] = cfg[v]
end
CA_cfg[v] = cfg[v]
if child then CA_cfg[v] = child end
end
-- Optional keys
for _,v in pairs(optional_keys) do
CA_cfg[v] = cfg[v]
local child = H.get_child(cfg, v)
if child then CA_cfg[v] = child end
if v:match('%[[a-zA-Z0-9_]+%]') then
v = v:sub(2,-2)
for child in H.child_range(cfg, v) do
table.insert(CA_cfg, T[v](child))
end
else
CA_cfg[v] = cfg[v]
end
end
-- Finally, set up the candidate actions themselves
if (cfg.action == 'add') then micro_ai_helper.add_CAs(cfg.side, CA_parms, CA_cfg) end
if (cfg.action == 'add') then micro_ai_helper.add_CAs(cfg.side, cfg.ca_id, CA_parms, CA_cfg) end
if (cfg.action == 'change') then
micro_ai_helper.delete_CAs(cfg.side, CA_parms, cfg.id)
micro_ai_helper.add_CAs(cfg.side, CA_parms, CA_cfg)
micro_ai_helper.delete_CAs(cfg.side, cfg.ca_id, CA_parms)
micro_ai_helper.add_CAs(cfg.side, cfg.ca_id, CA_parms, CA_cfg)
end
end

View file

@ -1,12 +1,12 @@
-- This set of functions provides a consistent way of storing Micro AI variables
-- in the AI's persistent self.data variable. These need to be stored inside
-- in the AI's persistent data variable. These need to be stored inside
-- a [micro_ai] tag, so that they are bundled together for a given Micro AI
-- together with an ai_id= key. Their existence can then be checked when setting
-- up another MAI. Otherwise other Micro AIs used in the same scenario might
-- work incorrectly or not at all.
-- Note that, ideally, we would delete these [micro_ai] tags when a Micro AI is
-- deleted, but that that is not always possible as deletion can happen on
-- another side's turn, while the self.data variable is only accessible during
-- another side's turn, while the data variable is only accessible during
-- the AI turn.
-- Note that, with this method, there can only ever be one of these tags for each
-- Micro AI (but of course several when there are several Micro AIs for the
@ -18,7 +18,7 @@ local H = wesnoth.require "lua/helper.lua"
local micro_ai_self_data = {}
function micro_ai_self_data.modify_mai_self_data(self_data, ai_id, action, vars_table)
-- Modify self.data [micro_ai] tags
-- Modify data [micro_ai] tags
-- @ai_id (string): the id of the Micro AI
-- @action (string): "delete", "set" or "insert"
-- @vars_table: table of key=value pairs with the variables to be set or inserted
@ -73,7 +73,7 @@ function micro_ai_self_data.set_mai_self_data(self_data, ai_id, vars_table)
end
function micro_ai_self_data.get_mai_self_data(self_data, ai_id, key)
-- Get the content of the self.data [micro_ai] tag for the given @ai_id
-- Get the content of the data [micro_ai] tag for the given @ai_id
-- Return value:
-- - If tag is found: value of key if @key parameter is given, otherwise
-- table of key=value pairs (including the ai_id key)

View file

@ -2,51 +2,25 @@ local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local MAIH = wesnoth.require("ai/micro_ais/micro_ai_helper.lua")
wesnoth.micro_ais = {}
-- Load all default MicroAIs
wesnoth.require("ai/micro_ais/mai-defs/animals.lua")
wesnoth.require("ai/micro_ais/mai-defs/bottleneck.lua")
wesnoth.require("ai/micro_ais/mai-defs/escort.lua")
wesnoth.require("ai/micro_ais/mai-defs/fast.lua")
wesnoth.require("ai/micro_ais/mai-defs/guardian.lua")
wesnoth.require("ai/micro_ais/mai-defs/healers.lua")
wesnoth.require("ai/micro_ais/mai-defs/misc.lua")
wesnoth.require("ai/micro_ais/mai-defs/patrol.lua")
wesnoth.require("ai/micro_ais/mai-defs/protect.lua")
wesnoth.require("ai/micro_ais/mai-defs/recruiting.lua")
function wesnoth.wml_actions.micro_ai(cfg)
local CA_path = 'ai/micro_ais/cas/'
cfg = cfg.__parsed
-- Add translation for old-syntax animal MAIs to new syntax plus deprecation message
if (cfg.ai_type == 'animals') and (cfg.animal_type) then
wesnoth.message("The syntax 'ai_type=animals animal_type=" .. cfg.animal_type .. "' is deprecated. Use 'ai_type=" .. cfg.animal_type .. "' instead.")
cfg.ai_type = cfg.animal_type
cfg.animal_type = nil
end
-- Add translation for old-syntax guardian MAIs to new syntax plus deprecation message
if (cfg.ai_type == 'guardian_unit') and (cfg.guardian_type) then
wesnoth.message("The syntax 'ai_type=guardian_unit guardian_type=" .. cfg.guardian_type .. "' is deprecated. Use 'ai_type=" .. cfg.guardian_type .. "' instead.")
cfg.ai_type = cfg.guardian_type
cfg.guardian_type = nil
end
-- Add translation for old-syntax hunter_unit MAI to new syntax plus deprecation message
if (cfg.ai_type == 'hunter_unit') then
wesnoth.message("'ai_type=hunter_unit' is deprecated. Use 'ai_type=hunter' instead.")
cfg.ai_type = 'hunter'
end
-- Add translation for old-syntax patrol_unit MAI to new syntax plus deprecation message
if (cfg.ai_type == 'patrol_unit') then
wesnoth.message("'ai_type=patrol_unit' is deprecated. Use 'ai_type=patrol' instead.")
cfg.ai_type = 'patrol'
end
-- Add translation for old-syntax recruiting MAI to new syntax plus deprecation message
if (cfg.ai_type == 'recruiting') and (cfg.recruiting_type) then
local new_type = 'recruit_random'
if (cfg.recruiting_type == 'rushers') then new_type = 'recruit_rushers' end
wesnoth.message("The syntax 'ai_type=recruiting recruiting_type=" .. cfg.recruiting_type .. "' is deprecated. Use 'ai_type=" .. new_type .. "' instead.")
cfg.ai_type = new_type
cfg.recruiting_type = nil
end
-- Check that the required common keys are all present and set correctly
if (not cfg.ai_type) then H.wml_error("[micro_ai] is missing required ai_type= key") end
if (not cfg.side) then H.wml_error("[micro_ai] is missing required side= key") end
@ -61,518 +35,18 @@ function wesnoth.wml_actions.micro_ai(cfg)
end
-- Set up the configuration tables for the different Micro AIs
local required_keys, optional_keys, CA_parms = {}, {}, {}
--------- Healer Support Micro AI ------------------------------------
if (cfg.ai_type == 'healer_support') then
optional_keys = { "aggression", "injured_units_only", "max_threats", "filter", "filter_second" }
-- Scores for this AI need to be hard-coded, it does not work otherwise
CA_parms = {
ai_id = 'mai_healer',
{ ca_id = 'initialize', location = CA_path .. 'ca_healer_initialize.lua', score = 999990 },
{ ca_id = 'move', location = CA_path .. 'ca_healer_move.lua', score = 105000 },
}
-- 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_path .. 'ca_healer_may_attack.lua', score = 99990 })
end
--------- Bottleneck Defense Micro AI -----------------------------------
elseif (cfg.ai_type == 'bottleneck_defense') then
required_keys = { "x", "y", "enemy_x", "enemy_y" }
optional_keys = { "healer_x", "healer_y", "leadership_x", "leadership_y", "active_side_leader" }
local score = cfg.ca_score or 300000
CA_parms = {
ai_id = 'mai_bottleneck',
{ ca_id = 'move', location = CA_path .. 'ca_bottleneck_move.lua', score = score },
{ ca_id = 'attack', location = CA_path .. 'ca_bottleneck_attack.lua', score = score - 1 }
}
--------- Messenger Escort Micro AI ------------------------------------
elseif (cfg.ai_type == 'messenger_escort') then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Messenger [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "waypoint_x", "waypoint_y" }
optional_keys = { "id", "enemy_death_chance", "filter", "filter_second", "invert_order", "messenger_death_chance" }
local score = cfg.ca_score or 300000
CA_parms = {
ai_id = 'mai_messenger',
{ ca_id = 'attack', location = CA_path .. 'ca_messenger_attack.lua', score = score },
{ ca_id = 'move', location = CA_path .. 'ca_messenger_move.lua', score = score - 1 },
{ ca_id = 'escort_move', location = CA_path .. 'ca_messenger_escort_move.lua', score = score - 2 }
}
--------- Lurkers Micro AI ------------------------------------
elseif (cfg.ai_type == 'lurkers') then
required_keys = { "filter", "filter_location" }
optional_keys = { "stationary", "filter_location_wander" }
CA_parms = {
ai_id = 'mai_lurkers',
{ ca_id = 'move', location = CA_path .. 'ca_lurkers.lua', score = cfg.ca_score or 300000 }
}
--------- Protect Unit Micro AI ------------------------------------
elseif (cfg.ai_type == 'protect_unit') then
required_keys = { "id", "goal_x", "goal_y" }
-- Scores for this AI need to be hard-coded, it does not work otherwise
CA_parms = {
ai_id = 'mai_protect_unit',
{ ca_id = 'finish', location = CA_path .. 'ca_protect_unit_finish.lua', score = 300000 },
{ ca_id = 'attack', location = CA_path .. 'ca_protect_unit_attack.lua', score = 95000 },
{ ca_id = 'move', location = CA_path .. 'ca_protect_unit_move.lua', score = 94999 }
}
-- [unit] tags need to be dealt with separately
cfg.id, cfg.goal_x, cfg.goal_y = {}, {}, {}
if (cfg.action ~= 'delete') then
for unit in H.child_range(cfg, "unit") do
if (not unit.id) then
H.wml_error("Protect Unit Micro AI [unit] tag is missing required id= key")
end
if (not unit.goal_x) then
H.wml_error("Protect Unit Micro AI [unit] tag is missing required goal_x= key")
end
if (not unit.goal_y) then
H.wml_error("Protect Unit Micro AI [unit] tag is missing required goal_y= key")
end
table.insert(cfg.id, unit.id)
table.insert(cfg.goal_x, unit.goal_x)
table.insert(cfg.goal_y, unit.goal_y)
end
if (not cfg.id[1]) then
H.wml_error("Protect Unit Micro AI is missing required [unit] tag")
end
end
-- Optional key disable_move_leader_to_keep: needs to be dealt with
-- separately as it affects a default CA
if cfg.disable_move_leader_to_keep then
W.modify_ai {
side = side,
action = "try_delete",
path = "stage[main_loop].candidate_action[move_leader_to_keep]"
}
end
-- attacks aspects also needs to be set separately
local unit_ids_str = 'dummy'
for _,id in ipairs(cfg.id) do
unit_ids_str = unit_ids_str .. ',' .. id
end
local aspect_parms = {
{
aspect = "attacks",
facet = {
name = "ai_default_rca::aspect_attacks",
ca_id = "dont_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_own", {
{ "not", {
id = unit_ids_str
} }
} }
}
}
}
if (cfg.action == "delete") then
MAIH.delete_aspects(cfg.side, aspect_parms)
-- We also need to add the move_leader_to_keep CA back in
-- This works even if it was not removed, it simply overwrites the existing CA
W.modify_ai {
side = side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="move_leader_to_keep",
engine="cpp",
name="ai_default_rca::move_leader_to_keep_phase",
max_score=160000,
score=160000
} }
}
else
MAIH.add_aspects(cfg.side, aspect_parms)
end
--------- Micro AI Guardian -----------------------------------
elseif (cfg.ai_type == 'stationed_guardian') then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Stationed Guardian [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "distance", "station_x", "station_y" }
optional_keys = { "id", "filter", "guard_x", "guard_y" }
CA_parms = {
ai_id = 'mai_stationed_guardian',
{ ca_id = 'move', location = CA_path .. 'ca_stationed_guardian.lua', score = cfg.ca_score or 300000 }
}
elseif (cfg.ai_type == 'zone_guardian') then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Zone Guardian [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "filter_location" }
optional_keys = { "id", "filter", "filter_location_enemy", "station_x", "station_y" }
CA_parms = {
ai_id = 'mai_zone_guardian',
{ ca_id = 'move', location = CA_path .. 'ca_zone_guardian.lua', score = cfg.ca_score or 300000 }
}
elseif (cfg.ai_type == 'return_guardian') then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Return Guardian [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "return_x", "return_y" }
optional_keys = { "id", "filter" }
CA_parms = {
ai_id = 'mai_return_guardian',
{ ca_id = 'move', location = CA_path .. 'ca_return_guardian.lua', score = cfg.ca_score or 100010 }
}
elseif (cfg.ai_type == 'coward') then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Coward [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "distance" }
optional_keys = { "attack_if_trapped", "id", "filter", "filter_second", "seek_x", "seek_y","avoid_x","avoid_y" }
CA_parms = {
ai_id = 'mai_coward',
{ ca_id = 'move', location = CA_path .. 'ca_coward.lua', score = cfg.ca_score or 300000 }
}
--------- Micro AI Animals ------------------------------------
elseif (cfg.ai_type == 'big_animals') then
required_keys = { "filter"}
optional_keys = { "avoid_unit", "filter_location", "filter_location_wander" }
CA_parms = {
ai_id = 'mai_big_animals',
{ ca_id = "move", location = CA_path .. 'ca_big_animals.lua', score = cfg.ca_score or 300000 }
}
elseif (cfg.ai_type == 'wolves') then
required_keys = { "filter", "filter_second" }
optional_keys = { "attack_only_prey", "avoid_type" }
local score = cfg.ca_score or 90000
CA_parms = {
ai_id = 'mai_wolves',
{ ca_id = "move", location = CA_path .. 'ca_wolves_move.lua', score = score },
{ ca_id = "wander", location = CA_path .. 'ca_wolves_wander.lua', score = score - 1 }
}
if cfg.attack_only_prey then
local wolves_aspects = {
{
aspect = "attacks",
facet = {
name = "ai_default_rca::aspect_attacks",
id = "dont_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_enemy", {
{ "and", H.get_child(cfg, "filter_second") }
} }
}
}
}
if (cfg.action == "delete") then
MAIH.delete_aspects(cfg.side, wolves_aspects)
else
MAIH.add_aspects(cfg.side, wolves_aspects)
end
elseif cfg.avoid_type then
local wolves_aspects = {
{
aspect = "attacks",
facet = {
name = "ai_default_rca::aspect_attacks",
id = "dont_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_enemy", {
{ "not", {
type=cfg.avoid_type
} }
} }
}
}
}
if (cfg.action == "delete") then
MAIH.delete_aspects(cfg.side, wolves_aspects)
else
MAIH.add_aspects(cfg.side, wolves_aspects)
end
end
elseif (cfg.ai_type == 'herding') then
required_keys = { "filter_location", "filter", "filter_second", "herd_x", "herd_y" }
optional_keys = { "attention_distance", "attack_distance" }
local score = cfg.ca_score or 300000
CA_parms = {
ai_id = 'mai_herding',
{ ca_id = "attack_close_enemy", location = CA_path .. 'ca_herding_attack_close_enemy.lua', score = score },
{ ca_id = "sheep_runs_enemy", location = CA_path .. 'ca_herding_sheep_runs_enemy.lua', score = score - 1 },
{ ca_id = "sheep_runs_dog", location = CA_path .. 'ca_herding_sheep_runs_dog.lua', score = score - 2 },
{ ca_id = "herd_sheep", location = CA_path .. 'ca_herding_herd_sheep.lua', score = score - 3 },
{ ca_id = "sheep_move", location = CA_path .. 'ca_herding_sheep_move.lua', score = score - 4 },
{ ca_id = "dog_move", location = CA_path .. 'ca_herding_dog_move.lua', score = score - 5 },
{ ca_id = "dog_stopmove", location = CA_path .. 'ca_herding_dog_stopmove.lua', score = score - 6 }
}
elseif (cfg.ai_type == 'forest_animals') then
optional_keys = { "rabbit_type", "rabbit_number", "rabbit_enemy_distance", "rabbit_hole_img",
"tusker_type", "tusklet_type", "deer_type", "filter_location"
}
local score = cfg.ca_score or 300000
CA_parms = {
ai_id = 'mai_forest_animals',
{ ca_id = "new_rabbit", location = CA_path .. 'ca_forest_animals_new_rabbit.lua', score = score },
{ ca_id = "tusker_attack", location = CA_path .. 'ca_forest_animals_tusker_attack.lua', score = score - 1 },
{ ca_id = "move", location = CA_path .. 'ca_forest_animals_move.lua', score = score - 2 },
{ ca_id = "tusklet_move", location = CA_path .. 'ca_forest_animals_tusklet_move.lua', score = score - 3 }
}
elseif (cfg.ai_type == 'swarm') then
optional_keys = { "scatter_distance", "vision_distance", "enemy_distance" }
local score = cfg.ca_score or 300000
CA_parms = {
ai_id = 'mai_swarm',
{ ca_id = "scatter", location = CA_path .. 'ca_swarm_scatter.lua', score = score },
{ ca_id = "move", location = CA_path .. 'ca_swarm_move.lua', score = score - 1 }
}
elseif (cfg.ai_type == 'wolves_multipacks') then
optional_keys = { "type", "pack_size", "show_pack_number" }
local score = cfg.ca_score or 300000
CA_parms = {
ai_id = 'mai_wolves_multipacks',
{ ca_id = "attack", location = CA_path .. 'ca_wolves_multipacks_attack.lua', score = score },
{ ca_id = "wander", location = CA_path .. 'ca_wolves_multipacks_wander.lua', score = score - 1 }
}
elseif (cfg.ai_type == 'hunter') then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Hunter [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "home_x", "home_y" }
optional_keys = { "id", "filter", "filter_location", "rest_turns", "show_messages" }
CA_parms = {
ai_id = 'mai_hunter',
{ ca_id = "move", location = CA_path .. 'ca_hunter.lua', score = cfg.ca_score or 300000 }
}
--------- Patrol Micro AI ------------------------------------
elseif (cfg.ai_type == 'patrol') then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Patrol [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "waypoint_x", "waypoint_y" }
optional_keys = { "id", "filter", "attack", "one_time_only", "out_and_back" }
CA_parms = {
ai_id = 'mai_patrol',
{ ca_id = "move", location = CA_path .. 'ca_patrol.lua', score = cfg.ca_score or 300000 }
}
--------- Recruiting Micro AI ------------------------------------
elseif (cfg.ai_type == 'recruit_rushers') or (cfg.ai_type == 'recruit_random')then
if (cfg.ai_type == 'recruit_rushers') then
optional_keys = { "randomness" }
CA_parms = {
ai_id = 'mai_rusher_recruit',
{ ca_id = "move", location = CA_path .. 'ca_recruit_rushers.lua', score = cfg.ca_score or 180000 }
}
else
optional_keys = { "skip_low_gold_recruiting", "type", "prob" }
CA_parms = {
ai_id = 'mai_random_recruit',
{ ca_id = "move", location = CA_path .. 'ca_recruit_random.lua', score = cfg.ca_score or 180000 }
}
if (cfg.action ~= 'delete') then
-- The 'probability' tags need to be handled separately here
cfg.type, cfg.prob = {}, {}
for probability in H.child_range(cfg, "probability") do
if (not probability.type) then
H.wml_error("Random Recruiting Micro AI [probability] tag is missing required type= key")
end
if (not probability.probability) then
H.wml_error("Random Recruiting Micro AI [probability] tag is missing required probability= key")
end
table.insert(cfg.type, probability.type)
table.insert(cfg.prob, probability.probability)
end
end
end
-- Also need to delete/add the default recruitment CA
if cfg.action == 'add' then
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[recruitment]"
}
elseif cfg.action == 'delete' then
-- We need to add the recruitment CA back in
-- This works even if it was not removed, it simply overwrites the existing CA
W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="recruitment",
engine="cpp",
name="ai_default_rca::aspect_recruitment_phase",
max_score=180000,
score=180000
} }
}
end
--------- Goto Micro AI ------------------------------------
elseif (cfg.ai_type == 'goto') then
required_keys = { "filter_location" }
optional_keys = {
"avoid_enemies", "filter", "ignore_units", "ignore_enemy_at_goal",
"release_all_units_at_goal", "release_unit_at_goal", "unique_goals", "use_straight_line"
}
CA_parms = {
ai_id = 'mai_goto',
{ ca_id = 'move', location = CA_path .. 'ca_goto.lua', score = cfg.ca_score or 300000 }
}
--------- Hang Out Micro AI ------------------------------------
elseif (cfg.ai_type == 'hang_out') then
optional_keys = { "filter", "filter_location", "avoid", "mobilize_condition", "mobilize_on_gold_less_than" }
CA_parms = {
ai_id = 'mai_hang_out',
{ ca_id = 'move', location = CA_path .. 'ca_hang_out.lua', score = cfg.ca_score or 170000 }
}
--------- Simple Attack Micro AI ---------------------------
elseif (cfg.ai_type == 'simple_attack') then
optional_keys = { "filter", "filter_second", "weapon" }
CA_parms = {
ai_id = 'mai_simple_attack',
{ ca_id = 'move', location = CA_path .. 'ca_simple_attack.lua', score = cfg.ca_score or 110000 }
}
elseif (cfg.ai_type == 'fast_ai') then
optional_keys = {
"attack_hidden_enemies", "avoid", "dungeon_mode",
"filter", "filter_second", "include_occupied_attack_hexes",
"leader_additional_threat", "leader_attack_max_units", "leader_weight", "move_cost_factor",
"weak_units_first", "skip_combat_ca", "skip_move_ca", "threatened_leader_fights"
}
CA_parms = {
ai_id = 'mai_fast',
{ ca_id = 'combat', location = CA_path .. 'ca_fast_combat.lua', score = 100000 },
{ ca_id = 'move', location = CA_path .. 'ca_fast_move.lua', score = 20000 },
{ ca_id = 'combat_leader', location = CA_path .. 'ca_fast_combat_leader.lua', score = 19900 }
}
-- Also need to delete/add some default CAs
if (cfg.action == 'delete') then
-- This can be done independently of whether these were removed earlier
W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="combat",
engine="cpp",
name="ai_default_rca::combat_phase",
max_score=100000,
score=100000
} }
}
W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="villages",
engine="cpp",
name="ai_default_rca::get_villages_phase",
max_score=60000,
score=60000
} }
}
W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="retreat",
engine="cpp",
name="ai_default_rca::retreat_phase",
max_score=40000,
score=40000
} }
}
W.modify_ai {
side = cfg.side,
action = "add",
path = "stage[main_loop].candidate_action",
{ "candidate_action", {
id="move_to_targets",
engine="cpp",
name="ai_default_rca::move_to_targets_phase",
max_score=20000,
score=20000
} }
}
else
if (not cfg.skip_combat_ca) then
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[combat]"
}
else
for i,parm in ipairs(CA_parms) do
if (parm.ca_id == 'combat') or (parm.ca_id == 'combat_leader') then
table.remove(CA_parms, i)
end
end
end
if (not cfg.skip_move_ca) then
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[villages]"
}
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[retreat]"
}
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[move_to_targets]"
}
else
for i,parm in ipairs(CA_parms) do
if (parm.ca_id == 'move') then
table.remove(CA_parms, i)
break
end
end
end
end
-- If we got here, none of the valid ai_types was specified
else
if wesnoth.micro_ais[cfg.ai_type] == nil then
H.wml_error("unknown value for ai_type= in [micro_ai]")
end
local required_keys, optional_keys, CA_parms = wesnoth.micro_ais[cfg.ai_type](cfg)
-- Fixup any relative CA paths
for i,v in ipairs(CA_parms) do
if v.location and v.location:find('~') ~= 1 then
v.location = CA_path .. v.location
end
end
MAIH.micro_ai_setup(cfg, CA_parms, required_keys, optional_keys)
end

View file

@ -76,8 +76,6 @@
#against elves, and just do it
#define EBESIEGED_RECRUITMENT
[ai]
recruitment_ignore_bad_movement=yes
recruitment_ignore_bad_combat=yes
simple_targeting=yes
[/ai]
#enddef
@ -166,7 +164,7 @@
recruit=Elvish Shaman,Elvish Archer,Elvish Fighter
[ai]
passive_leader="yes"
leader_shares_keep="yes"
passive_leader_shares_keep="yes"
recruitment_pattern=archer,archer,fighter,fighter,healer
[/ai]
gold=170

View file

@ -36,6 +36,7 @@
[side]
side=1
controller=human
suppress_end_turn_confirmation=yes
hidden=no
name= _ "Rutburt"
id=Rutburt
@ -357,20 +358,25 @@ Also note: The Animal AIs are coded as Micro AIs. A Micro AI can be added and ad
# wmlindent: stop ignoring
# wmllint: unbalanced-off
[option]
message= _ "<span font='16'>I'll just watch the animals.</span>"
label= _ "<span font='16'>I'll just watch the animals.</span>"
[command]
[modify_side]
side=1
controller=null
hidden=yes
[/modify_side]
[kill]
side=1
[/kill]
[end_turn]
[/end_turn]
[event]
name=side 2 turn
[modify_side]
side=1
controller=null
hidden=yes
[/modify_side]
[kill]
side=1
[/kill]
[/event]
[/command]
[/option]
[option]
message= _ "<span font='16'>I want to have control of Side 1.</span>"
label= _ "<span font='16'>I want to have control of Side 1.</span>"
[/option]
[/message]

View file

@ -32,6 +32,7 @@
[side]
side=2
controller=ai
suppress_end_turn_confirmation=yes
type=Orcish Leader
id=Big Bad Orc
name= _ "Big Bad Orc"
@ -98,10 +99,10 @@ Note: The Bottleneck Defense AI is coded as a Micro AI. A Micro AI can be added
image=wesnoth-icon.png
message= _ "In this scenario, the AI playing the humans in the east is instructed to form a defensive line at the pass and hold off the orcs for as long as possible. Do you want to play the orc side or let the default (RCA) AI do that?"
[option]
message= _ "<span font='16'>I'll watch the two AIs fight it out.</span>"
label= _ "<span font='16'>I'll watch the two AIs fight it out.</span>"
[/option]
[option]
message= _ "<span font='16'>I'll play the orcs.</span>"
label= _ "<span font='16'>I'll play the orcs.</span>"
[command]
[modify_side]
side=2

View file

@ -28,6 +28,7 @@
side=2
type=Dread Bat
controller=human
suppress_end_turn_confirmation=yes
id=Dreadful Bat
name= _ "Dreadful Bat"

View file

@ -16,6 +16,7 @@
[side]
side=1
controller=human
suppress_end_turn_confirmation=yes
hidden=no
name=Vaddan
id=Vaddan
@ -297,6 +298,7 @@
side=7
ai_type=goto
action=add
ca_id=bats
[filter_location]
x=1,1,44,44
@ -364,12 +366,15 @@
{VARIABLE_CONDITIONAL scenario_name equals goto}
[/show_if]
[command]
[modify_side]
side=1
controller=null
[/modify_side]
[end_turn]
[/end_turn]
[event]
name=side turn
[modify_side]
side=1
controller=null
[/modify_side]
[/event]
[/command]
[/set_menu_item]
[set_menu_item]
@ -715,6 +720,7 @@ Note: The messengers are controlled by Goto Micro AI definitions that differ by
side=7
ai_type=goto
action=change
ca_id=bats
[filter_location]
x,y=35,13
@ -755,6 +761,7 @@ Note: The messengers are controlled by Goto Micro AI definitions that differ by
side=7
ai_type=goto
action=change
ca_id=bats
[filter_location]
x=1,1,44,44

Some files were not shown because too many files have changed in this diff Show more