Merge branch 'master' into boost_filesystem
Conflicts: .travis.yml SConstruct src/addon/manager.cpp src/campaign_server/campaign_server.cpp src/editor/map/context_manager.cpp src/editor/map/map_context.cpp src/filesystem.cpp src/filesystem.hpp src/game.cpp src/game_config_manager.cpp src/gui/dialogs/editor/custom_tod.cpp src/gui/dialogs/lobby/lobby_data.cpp src/gui/dialogs/mp_create_game.cpp src/gui/widgets/settings.cpp src/hotkeys.cpp src/multiplayer_create_engine.cpp src/multiplayer_lobby.cpp src/network.cpp src/savegame.cpp src/serialization/preprocessor.cpp src/widgets/button.cpp src/windows_tray_notification.hpp
7
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
|||
.settings
|
||||
.project
|
||||
.cproject
|
||||
.pydevproject
|
||||
revision.h
|
||||
*.swp
|
||||
.DS_Store
|
||||
|
@ -23,7 +24,7 @@ configure
|
|||
.sconf_temp
|
||||
CMakeFiles
|
||||
CMakeLists.txt.user
|
||||
CMakeLists.txt.user.1.3
|
||||
CMakeLists.txt.user.*
|
||||
cmake_install.cmake
|
||||
CPackConfig.cmake
|
||||
CPackSourceConfig.cmake
|
||||
|
@ -67,5 +68,7 @@ uninstall.vcproj
|
|||
Project.ncb
|
||||
Project.suo
|
||||
*.vcproj.*.user
|
||||
projectfiles/VC10
|
||||
projectfiles/VC1*
|
||||
projectfiles/VC201*
|
||||
*Neuer Ordner*
|
||||
Thumbs.db
|
||||
|
|
|
@ -4,8 +4,9 @@ compiler:
|
|||
- gcc
|
||||
install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq libboost-filesystem-dev libboost-iostreams-dev libboost-program-options-dev libboost-regex-dev libboost-system-dev libcairo2-dev libfribidi-dev libpango1.0-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-net1.2-dev libsdl-ttf2.0-dev
|
||||
script: scons cxxtool=$CXX wesnoth wesnothd campaignd
|
||||
- sudo apt-get install -qq libboost-filesystem-dev libboost-iostreams-dev libboost-program-options-dev libboost-regex-dev libboost-system-dev libboost-test-dev libcairo2-dev libfribidi-dev libpango1.0-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-net1.2-dev libsdl-ttf2.0-dev
|
||||
script: scons cxxtool=$CXX strict=True wesnoth wesnothd campaignd test
|
||||
after_script: ./test
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
|
|
|
@ -82,6 +82,7 @@ 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)
|
||||
option(ENABLE_OMP "Enables OpenMP, and has additional dependencies" OFF)
|
||||
option(ENABLE_PANDORA "Add support for the OpenPandora by deactivating libvorbis support" OFF)
|
||||
|
||||
if(NOT DEFINED ENABLE_DISPLAY_REVISION)
|
||||
# Test whether the code is used in a repository if not autorevision will
|
||||
|
@ -293,7 +294,7 @@ check_compiler_has_flag(
|
|||
|
||||
if(NOT CMAKE_COMPILER_IS_GNUCXX)
|
||||
# Silences warnings about overloaded virtuals.
|
||||
# (GCC doesn't complain Clang does.)
|
||||
# (GCC doesn't complain Clang 3.2 does. Clang 3.4 no longer does.)
|
||||
check_compiler_has_flag(
|
||||
CXX_FLAGS_STRICT_COMPILATION
|
||||
"-Woverloaded-virtual"
|
||||
|
@ -302,6 +303,12 @@ if(NOT CMAKE_COMPILER_IS_GNUCXX)
|
|||
)
|
||||
endif(NOT CMAKE_COMPILER_IS_GNUCXX)
|
||||
|
||||
check_compiler_has_flag(
|
||||
CXX_FLAGS_STRICT_COMPILATION
|
||||
"-Wold-style-cast"
|
||||
HAS_COMPILER_FLAG_WOLD_STYLE_CAST
|
||||
)
|
||||
|
||||
check_compiler_has_flag(
|
||||
CXX_FLAGS_STRICT_COMPILATION
|
||||
"-Wdeprecated-register"
|
||||
|
@ -391,6 +398,24 @@ check_compiler_has_flag(
|
|||
HAS_COMPILER_FLAG_WDOCUMENTATION
|
||||
)
|
||||
|
||||
check_compiler_has_flag(
|
||||
CXX_FLAGS_PEDANTIC_COMPILATION
|
||||
"-Wdocumentation-deprecated-sync"
|
||||
HAS_COMPILER_FLAG_WDOCUMENTATION
|
||||
"-Wno-documentation-deprecated-sync"
|
||||
)
|
||||
|
||||
check_compiler_has_flag(
|
||||
CXX_FLAGS_PEDANTIC_COMPILATION
|
||||
"-Wextra-semi"
|
||||
HAS_COMPILER_FLAG_WEXTRA_SEMI
|
||||
)
|
||||
|
||||
check_compiler_has_flag(
|
||||
CXX_FLAGS_STRICT_COMPILATION
|
||||
"-Wconditional-uninitialized"
|
||||
HAS_COMPILER_FLAG_WCONDITIONAL_INITIALIZED
|
||||
)
|
||||
|
||||
### Set the final compiler flags.
|
||||
|
||||
|
@ -435,6 +460,40 @@ endif(X11_FOUND)
|
|||
|
||||
add_definitions(-DLOCALEDIR=\\\"${LOCALEDIR}\\\")
|
||||
|
||||
# -NDEBUG is automatically added to all release build types, so manually remove
|
||||
# this define from the related variables
|
||||
MESSAGE ("removing NDEBUG flag from CMAKE_CXX_FLAGS_RELEASE")
|
||||
separate_arguments(CMAKE_CXX_FLAGS_RELEASE)
|
||||
list(REMOVE_ITEM CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG")
|
||||
string(REPLACE ";" " " CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}" CACHE STRING "removed NDEBUG flag" FORCE)
|
||||
MESSAGE ("removing NDEBUG flag from CMAKE_C_FLAGS_RELEASE")
|
||||
separate_arguments(CMAKE_C_FLAGS_RELEASE)
|
||||
list(REMOVE_ITEM CMAKE_C_FLAGS_RELEASE "-DNDEBUG")
|
||||
string(REPLACE ";" " " CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "removed NDEBUG flag" FORCE)
|
||||
MESSAGE ("removing NDEBUG flag from CMAKE_CXX_FLAGS_RELWITHDEBINFO")
|
||||
separate_arguments(CMAKE_CXX_FLAGS_RELWITHDEBINFO)
|
||||
list(REMOVE_ITEM CMAKE_CXX_FLAGS_RELWITHDEBINFO "-DNDEBUG")
|
||||
string(REPLACE ";" " " CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}" CACHE STRING "removed NDEBUG flag" FORCE)
|
||||
MESSAGE ("removing NDEBUG flag from CMAKE_C_FLAGS_RELWITHDEBINFO")
|
||||
separate_arguments(CMAKE_C_FLAGS_RELWITHDEBINFO)
|
||||
list(REMOVE_ITEM CMAKE_C_FLAGS_RELWITHDEBINFO "-DNDEBUG")
|
||||
string(REPLACE ";" " " CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
|
||||
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}" CACHE STRING "removed NDEBUG flag" FORCE)
|
||||
MESSAGE ("removing NDEBUG flag from CMAKE_CXX_FLAGS_MINSIZEREL")
|
||||
separate_arguments(CMAKE_CXX_FLAGS_MINSIZEREL)
|
||||
list(REMOVE_ITEM CMAKE_CXX_FLAGS_MINSIZEREL "-DNDEBUG")
|
||||
string(REPLACE ";" " " CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL}")
|
||||
set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL}" CACHE STRING "removed NDEBUG flag" FORCE)
|
||||
MESSAGE ("removing NDEBUG flag from CMAKE_C_FLAGS_MINSIZEREL")
|
||||
separate_arguments(CMAKE_C_FLAGS_MINSIZEREL)
|
||||
list(REMOVE_ITEM CMAKE_C_FLAGS_MINSIZEREL "-DNDEBUG")
|
||||
string(REPLACE ";" " " CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}")
|
||||
set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}" CACHE STRING "removed NDEBUG flag" FORCE)
|
||||
|
||||
|
||||
# When the path starts with a / on a Unix system it's an absolute path.
|
||||
# This means that on Windows the path used is always relative.
|
||||
if(LOCALEDIR MATCHES "^/")
|
||||
|
@ -464,6 +523,10 @@ if(ENABLE_LOW_MEM)
|
|||
add_definitions(-DLOW_MEM)
|
||||
endif(ENABLE_LOW_MEM)
|
||||
|
||||
if(ENABLE_PANDORA)
|
||||
add_definitions(-DPANDORA)
|
||||
endif(ENABLE_PANDORA)
|
||||
|
||||
if(ENABLE_OMP)
|
||||
find_package(OpenMP REQUIRED)
|
||||
set(CMAKE_C_FLAGS "${OpenMP_C_FLAGS} ${CMAKE_C_FLAGS}")
|
||||
|
|
2
Doxyfile
|
@ -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.11.7+dev
|
||||
PROJECT_NUMBER = 1.13.0-dev
|
||||
|
||||
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
|
||||
# base path where the generated documentation will be put.
|
||||
|
|
|
@ -18,6 +18,10 @@ CHANGES
|
|||
Example contents.
|
||||
[/section]
|
||||
|
||||
[section="Updated OS X user data directory"]
|
||||
The OS X user data directory is now ~/Library/Application Support/Wesnoth_1.13, in preparation for the upcoming development release, Wesnoth 1.13.0. This means that preferences and add-ons need to be either reset/reloaded or be copied/linked from ~/Library/Application Support/Wesnoth_1.11.
|
||||
[/section]
|
||||
|
||||
==========
|
||||
KNOWN BUGS
|
||||
==========
|
||||
|
|
65
SConstruct
|
@ -31,7 +31,7 @@ try:
|
|||
version = build_config["VERSION"]
|
||||
print "Building Wesnoth version %s" % version
|
||||
except KeyError:
|
||||
print "Couldn't determin the Wesnoth version number, bailing out!"
|
||||
print "Couldn't determine the Wesnoth version number, bailing out!"
|
||||
sys.exit(1)
|
||||
|
||||
#
|
||||
|
@ -103,15 +103,17 @@ opts.AddVariables(
|
|||
BoolVariable('cxx0x', 'Use C++0x features.', False),
|
||||
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("lockfile", "Create a lockfile to prevent multiple instances of scons from being run at the same time on this working copy.", False),
|
||||
BoolVariable("sdl2", "Build with SDL2 support (experimental!)", False)
|
||||
)
|
||||
|
||||
#
|
||||
# Setup
|
||||
#
|
||||
|
||||
sys.path.insert(0, "./scons")
|
||||
env = Environment(tools=["tar", "gettext", "install", "python_devel", "scanreplace"], options = opts, toolpath = ["scons"])
|
||||
toolpath = ["scons"] + map(lambda x : x.abspath + "/scons", Dir(".").repositories)
|
||||
sys.path = toolpath + sys.path
|
||||
env = Environment(tools=["tar", "gettext", "install", "python_devel", "scanreplace"], options = opts, toolpath = toolpath)
|
||||
|
||||
if env["lockfile"]:
|
||||
print "Creating lockfile"
|
||||
|
@ -124,8 +126,8 @@ opts.Save(GetOption("option_cache"), env)
|
|||
env.SConsignFile("$build_dir/sconsign.dblite")
|
||||
|
||||
# Make sure the user's environment is always available
|
||||
env['ENV']['PATH'] = os.environ["PATH"]
|
||||
env['ENV']['TERM'] = os.environ["TERM"]
|
||||
env['ENV']['PATH'] = os.environ.get("PATH")
|
||||
env['ENV']['TERM'] = os.environ.get("TERM")
|
||||
if env["PLATFORM"] == "win32":
|
||||
env.Tool("mingw")
|
||||
elif env["PLATFORM"] == "sunos":
|
||||
|
@ -269,7 +271,7 @@ def Warning(message):
|
|||
return False
|
||||
|
||||
from metasconf import init_metasconf
|
||||
configure_args = dict(custom_tests = init_metasconf(env, ["cplusplus", "python_devel", "sdl", "boost", "pango", "pkgconfig", "gettext", "lua"]), config_h = "config.h",
|
||||
configure_args = dict(custom_tests = init_metasconf(env, ["cplusplus", "python_devel", "sdl", "boost", "pango", "pkgconfig", "gettext", "lua"]), config_h = "$build_dir/config.h",
|
||||
log_file="$build_dir/config.log", conf_dir="$build_dir/sconf_temp")
|
||||
|
||||
env.MergeFlags(env["extra_flags_config"])
|
||||
|
@ -320,31 +322,46 @@ if env["prereqs"]:
|
|||
conf.CheckLib("vorbis")
|
||||
conf.CheckLib("mikmod")
|
||||
|
||||
have_server_prereqs = \
|
||||
if env['sdl2']:
|
||||
have_sdl_net = \
|
||||
conf.CheckSDL(require_version = '2.0.0') and \
|
||||
conf.CheckSDL("SDL2_net", header_file = "SDL_net")
|
||||
|
||||
have_sdl_other = \
|
||||
conf.CheckSDL("SDL2_ttf", header_file = "SDL_ttf") and \
|
||||
conf.CheckSDL("SDL2_mixer", header_file = "SDL_mixer") and \
|
||||
conf.CheckSDL("SDL2_image", header_file = "SDL_image")
|
||||
|
||||
else:
|
||||
have_sdl_net = \
|
||||
conf.CheckSDL(require_version = '1.2.0') and \
|
||||
conf.CheckSDL('SDL_net')
|
||||
|
||||
have_sdl_other = \
|
||||
conf.CheckSDL("SDL_ttf", require_version = "2.0.8") and \
|
||||
conf.CheckSDL("SDL_mixer", require_version = '1.2.0') and \
|
||||
conf.CheckSDL("SDL_image", require_version = '1.2.0')
|
||||
|
||||
have_server_prereqs = have_sdl_net and \
|
||||
conf.CheckCPlusPlus(gcc_version = "3.3") and \
|
||||
conf.CheckGettextLibintl() and \
|
||||
conf.CheckBoost("iostreams", require_version = "1.34.1") and \
|
||||
conf.CheckBoostIostreamsGZip() and \
|
||||
conf.CheckBoostIostreamsBZip2() and \
|
||||
conf.CheckBoost("smart_ptr", header_only = True) and \
|
||||
conf.CheckSDL(require_version = '1.2.7') and \
|
||||
conf.CheckSDL('SDL_net') and \
|
||||
conf.CheckBoost("system") and \
|
||||
((not env["boostfilesystem"]) or (conf.CheckBoost("filesystem", require_version = "1.44.0"))) or Warning("Base prerequisites are not met.")
|
||||
|
||||
env = conf.Finish()
|
||||
client_env = env.Clone()
|
||||
conf = client_env.Configure(**configure_args)
|
||||
have_client_prereqs = have_server_prereqs and \
|
||||
have_client_prereqs = have_server_prereqs and have_sdl_other and \
|
||||
CheckAsio(conf) and \
|
||||
conf.CheckPango("cairo", require_version = "1.21.3") and \
|
||||
conf.CheckPKG("fontconfig") and \
|
||||
conf.CheckBoost("program_options", require_version="1.35.0") and \
|
||||
conf.CheckBoost("regex", require_version = "1.35.0") and \
|
||||
conf.CheckSDL("SDL_ttf", require_version = "2.0.8") and \
|
||||
conf.CheckSDL("SDL_mixer", require_version = '1.2.0') and \
|
||||
conf.CheckLib("vorbisfile") and \
|
||||
conf.CheckSDL("SDL_image", require_version = '1.2.0') and \
|
||||
conf.CheckOgg() or Warning("Client prerequisites are not met. wesnoth, cutter and exploder cannot be built.")
|
||||
|
||||
have_X = False
|
||||
|
@ -360,8 +377,13 @@ if env["prereqs"]:
|
|||
client_env['fribidi'] = conf.CheckPKG('fribidi >= 0.10.9') or Warning("Can't find libfribidi, disabling freebidi support.")
|
||||
|
||||
if env["forum_user_handler"]:
|
||||
env.ParseConfig("mysql_config --libs --cflags")
|
||||
flags = env.ParseFlags("!mysql_config --libs --cflags")
|
||||
try: # Some versions of mysql_config add -DNDEBUG but we don't want it
|
||||
flags["CPPDEFINES"].remove("NDEBUG")
|
||||
except ValueError:
|
||||
pass
|
||||
env.Append(CPPDEFINES = ["HAVE_MYSQLPP"])
|
||||
env.MergeFlags(flags)
|
||||
|
||||
client_env = conf.Finish()
|
||||
|
||||
|
@ -404,7 +426,10 @@ if not env['nls']:
|
|||
#
|
||||
|
||||
for env in [test_env, client_env, env]:
|
||||
env.Prepend(CPPPATH = ["#/", "#/src"])
|
||||
build_root="#/"
|
||||
if os.path.isabs(env["build_dir"]):
|
||||
build_root = ""
|
||||
env.Prepend(CPPPATH = [build_root + "$build_dir", "#/src"])
|
||||
|
||||
env.Append(CPPDEFINES = ["HAVE_CONFIG_H"])
|
||||
|
||||
|
@ -426,6 +451,10 @@ for env in [test_env, client_env, env]:
|
|||
env["OPT_FLAGS"] = "-O2"
|
||||
env["DEBUG_FLAGS"] = Split("-O0 -DDEBUG -ggdb3")
|
||||
|
||||
if "clang" in env["CXX"]:
|
||||
# Silence warnings about unused -I options and unknown warning switches.
|
||||
env.AppendUnique(CCFLAGS = Split("-Qunused-arguments -Wno-unknown-warning-option"))
|
||||
|
||||
if "suncc" in env["TOOLS"]:
|
||||
env["OPT_FLAGS"] = "-g0"
|
||||
env["DEBUG_FLAGS"] = "-g"
|
||||
|
@ -453,7 +482,7 @@ if not env['static_test']:
|
|||
test_env.Append(CPPDEFINES = "BOOST_TEST_DYN_LINK")
|
||||
|
||||
try:
|
||||
if call("utils/autorevision -t h > revision.h", shell=True) == 0:
|
||||
if call(env.subst("utils/autorevision -t h > $build_dir/revision.h"), shell=True) == 0:
|
||||
env["have_autorevision"] = True
|
||||
except:
|
||||
pass
|
||||
|
@ -529,7 +558,7 @@ def CopyFilter(fn):
|
|||
|
||||
env["copy_filter"] = CopyFilter
|
||||
|
||||
linguas = Split(open("po/LINGUAS").read())
|
||||
linguas = Split(File("po/LINGUAS").get_contents())
|
||||
|
||||
def InstallManpages(env, component):
|
||||
env.InstallData("mandir", component, os.path.join("doc", "man", component + ".6"), "man6")
|
||||
|
|
455
changelog
|
@ -1,23 +1,472 @@
|
|||
Version 1.11.7+dev:
|
||||
Version 1.13.0-dev:
|
||||
* AI:
|
||||
* Fixed a bug that made it impossible to change or delete Micro AIs after a
|
||||
game had been reloaded (bug #21750). This was a general bug in the RCA AI
|
||||
mechanism and applied to other CAs as well, but it was most visible in the
|
||||
Micro AIs.
|
||||
* Messenger Micro AI: fix rare bug in attacks by messenger
|
||||
* Patrol Micro AI: fixed bug in the attack part of this MAI
|
||||
* Campaigns:
|
||||
* The Hammer of Thursagan:
|
||||
* Fixed missing objective in 'The Court of Karrag'.
|
||||
* Rebalanced scenarios 'Invaders', Mages and Drakes' and 'Fear'.
|
||||
* C++ Engine:
|
||||
* Purge "human_ai" controller type. This is a fixup of bugfix #18829 below.
|
||||
* Language and i18n:
|
||||
* Updated translations: German, Greek, Hungarian, Italian, Scottish Gaelic,
|
||||
Slovak
|
||||
* Lua API:
|
||||
* Fix bug #21761: wesnoth.synchronize_choice will now give a warning when
|
||||
the table returned by the user-specified function is not completely valid,
|
||||
when wesnoth is running in debug mode (--debug command line flag).
|
||||
* Units:
|
||||
* Increased the experience requirement for the Rami from 32 to 39
|
||||
* Increased the experience requirement for the Saree from 56 to 64
|
||||
* Changed sound timings for Khalifate melee attacks
|
||||
* New chill tempest animation for Lich/Ancient Lich
|
||||
* User interface:
|
||||
* Made orb and minmap colors configurable by the game preferences.
|
||||
* WML engine:
|
||||
* Added customizable recall costs for unit types and individual units,
|
||||
using the new recall_cost attribute in [unit_type] and [unit].
|
||||
* Added support for [elseif] tags inside [if]
|
||||
* Implemented checks for missing [do] inside [while] and missing [case],
|
||||
variable= and value= inside [switch]
|
||||
* Miscellaneous and bug fixes:
|
||||
* Fix Fisher-Yates implemenation of Lua helper.shuffle (bug #21706)
|
||||
* Fixed issues with the ~BG() image path function not always working with
|
||||
cached images.
|
||||
* Idle controlled sides now serialize as human controlled.
|
||||
* Fixed bug #20876: A segfault in cut_surface.
|
||||
* Changed: Dropped support for AmigaOS, BeOS, and OS/2.
|
||||
* Increased the sound mixer channel allocation from 16 to 32, thereby
|
||||
raising the limit for simultaneous sound effects from 5 to 21.
|
||||
* Fix bug #21758: "Ready not blocked while player pick faction."
|
||||
* Fix bug #18829: "ai sides show up as "controller=network" on remote clients".
|
||||
This issue is the source of some difficulties with mp campaigns which occur
|
||||
when the campaign is reloaded from a non-host side, or after a player rejoins
|
||||
from observer status. Hopefully, reloading campaigns is easier after this.
|
||||
* Fix bug #21797: "Mandatory WML child missing" when leaving a reloaded game.
|
||||
* Fix bug #21808: Cannot join a reloaded game as an observer.
|
||||
* Fixed halos glitching through locations that become shrouded after the
|
||||
halo is rendered for the first time.
|
||||
* OS X user data directory is now ~/Library/Application Support/Wesnoth_1.13
|
||||
* Fix bug #21257: Lagging animations with skip AI animations and fog/shroud.
|
||||
* Improved unicode handling on windows for characters outside the Basic
|
||||
Multilingual Plane.
|
||||
* Fix bug #3856: The turn dialog used in hotseat MP play now applies
|
||||
a blindfold for the duration of the dialog.
|
||||
* Petrified units are no longer displayed in the "Damage versus" tooltip.
|
||||
* Fix bug #21759: "timer refreshed too often when time runs out"
|
||||
* Use one combo box instead of check boxes for replay options "skip replay"
|
||||
and "enter blindfold". This fixes the mp lobby in width <= 800 resolutions.
|
||||
Fixes bug #21888.
|
||||
* MP server now commits controller changes to the replay rather than updating
|
||||
as we go along. Among other things these means that players that join an
|
||||
scenario with ai sides which has already started won't have corrupted
|
||||
controller types which would prevent them from successfully saving and
|
||||
reloading in the future.
|
||||
We have also moved all "controller tweaks" associated to the start of MP
|
||||
games to server-side rather than having a mix of client and server code.
|
||||
* Fixup user-displayed strings associated to replay options, idle state
|
||||
|
||||
Version 1.11.11:
|
||||
* Add-ons server:
|
||||
* Filenames with whitespace in them are no longer allowed.
|
||||
* AI:
|
||||
* Non-default multiplayer and Micro AIs:
|
||||
* Fixed bugs which could lead to the AIs being disabled for the rest of the
|
||||
turn if WML events removed or changed units during the AI turn
|
||||
* Improved error reporting of invalid AI actions
|
||||
* Campaigns:
|
||||
* Eastern Invasion:
|
||||
* Updated maps for scenarios 12, 14, 16 and 17a.
|
||||
* Fixed a bug in 'Captured' which can cause the beginning to make no
|
||||
sense.
|
||||
* Under the Burning Suns:
|
||||
* Fixed broken Divine Incarnation unit type in The Final Confrontation.
|
||||
* Language and i18n:
|
||||
* Updated translations: German, Italian, Slovak
|
||||
* WML engine:
|
||||
* Bug #21643: Removing fog from a single hex no longer makes the hex ugly.
|
||||
* WML files whose names contain whitespace trigger preprocessor errors.
|
||||
* Bug #21722: Event handlers with multiple names never fired.
|
||||
* User interface:
|
||||
* Corrected most of the issues left with the new default theme.
|
||||
* Reintroduced the alignment, race and side being shown in the sidebar.
|
||||
* Adjusted the theme to the size and shape of the new minimap frame images.
|
||||
* Certain changes to the used text colors, sizes and alignment.
|
||||
* Non-team labels no longer remove team labels that were present in the
|
||||
same hex.
|
||||
* New colors for the Light Red and Dark Red minimap markers.
|
||||
* Bug #21724: 'none' is now a special case for [unit_type] ellipse
|
||||
* Miscellaneous and bug fixes:
|
||||
* Units can no longer be moved in linger mode (bug #21450).
|
||||
* Changed: Updated valgrind suppression file.
|
||||
* Fixed color issues with font_rgb in unit status labels in themes.
|
||||
* Labels are now removed when shroud/fog is removed, rather than waiting
|
||||
for a new turn (bug #21434).
|
||||
* Percent signs show when describing traits that increase damage or attacks
|
||||
by a percentage (bug #21577).
|
||||
* Linux dbus notifications: Only last 5 messages are remembered, and they
|
||||
are displayed with the most recent ones first.
|
||||
* Fixed bug #21736: MP create screen always defaulting to top entry.
|
||||
|
||||
Version 1.11.10:
|
||||
* Add-ons client:
|
||||
* Fixed faulty add-on _info.cfg files causing the game to display obscure
|
||||
error messages or crash to desktop.
|
||||
* Generated _info.cfg files now contain the list of dependencies for an
|
||||
add-on as well (needed by wesnoth_addon_manager).
|
||||
* Python wesnoth_addon_manager client:
|
||||
* The dependencies= attribute and the [feedback] tag in .pbl files are
|
||||
now properly supported (bug #21189).
|
||||
* Generated _info.cfg files now contain the same information as the
|
||||
game's built-in client (type and title were missing).
|
||||
* AI:
|
||||
* Hang Out Micro AI: default AI [avoid] aspect is now taken into account
|
||||
* Fixed problems with several Micro AIs that sometimes produced OOS errors
|
||||
* Campaigns:
|
||||
* Eastern Invasion:
|
||||
* New world map.
|
||||
* Rewrote scenario 'Training the Ogres' and renamed it to
|
||||
'Capturing the Ogres'.
|
||||
* Rewrote scenario 'Captured'.
|
||||
* Heir to the Throne:
|
||||
* Fixed Delfador clobbering whichever unit happens to be standing on
|
||||
31,11 at the end of The Bay of Pearls, causing it to disappear
|
||||
forever.
|
||||
* S15 (The Lost General): fix bug of sighted events firing too early
|
||||
* Legend of Wesmere:
|
||||
* Fixed missing journey map background in story screens.
|
||||
* Liberty:
|
||||
* Updated sprites for Shadow Mage line.
|
||||
* Son of the Black Eye:
|
||||
* Rebalancing of the campaign is now complete
|
||||
* Under the Burning Suns:
|
||||
* Updated animation WML of all campaign specific units
|
||||
* Editor:
|
||||
* Fixed: Drawing the offmap area for small resolutions.
|
||||
* GUI2:
|
||||
* Added: FAI-function handling in GUI2 widgets.
|
||||
* Added: A new tooltip window.
|
||||
* Language and i18n:
|
||||
* Updated translations: Scottish Gaelic
|
||||
* Lua API:
|
||||
* Config of current era is now available in a Lua table in MP games
|
||||
* Config of any era can be requested by id, also a list valid era ids
|
||||
* Multiplayer:
|
||||
* Fix for bug #21405, in a series of features:
|
||||
* The abort option presented to the host when a player disconnects
|
||||
from a networked game is now a "save and abort" option.
|
||||
* New Idle controller status: Sides may now be set in an "idle" state
|
||||
by the host when a player disconnects from a network game. This does
|
||||
not give any player control or vision. To proceed with the game, the
|
||||
host must reassign the side's controller using :control, :droid, or
|
||||
:give_control as usual. (give_control existed but was not documented)
|
||||
Related to this, there are new commands :controller which query the
|
||||
controller status, and :idle which toggles the idle status.
|
||||
* New "Blindfold Replays" option: Observers may check a box in the
|
||||
lobby so that if they join a game, they will be "blindfolded" and see
|
||||
only a black screen until they are given control of a side.
|
||||
* Units:
|
||||
* New baseframes for Jundi, Muharib, Batal, Qatif-al-nar, Qudafi, Rasikh.
|
||||
* User interface:
|
||||
* New UI for displaying errors detected during the core and add-on WML
|
||||
loading process (parser and preprocessor errors), including the
|
||||
ability to copy the report to clipboard.
|
||||
* New UI for displaying the notification that a screenshot or map
|
||||
screenshot was successfully saved to disk, including options to open it
|
||||
in an external application, copy the path to clipboard, or browse the
|
||||
screenshots folder.
|
||||
* Force grayscale antialiasing for text rendered using Cairo/Pango (e.g. by
|
||||
GUI2) on Windows to work around ClearType-induced glitches (bug #21648).
|
||||
* Fixed bug #21584: Properly redraw the minimap when the minimap is
|
||||
resized.
|
||||
* Fixed: Enable blurring in the title screen.
|
||||
* Added descriptions to the options in Preferences -> Display -> Themes.
|
||||
* New sound played to signal the start of an MP game.
|
||||
* WML engine:
|
||||
* WML loading phase errors are reported to stderr in a new indented format.
|
||||
* Implemented [true] and [false] ConditionalWML tags, which describe a
|
||||
condition that always yields true or false, respectively.
|
||||
* Fixed: Disallow change and remove sections without an id in the ThemeWML.
|
||||
* Added [theme] description attribute for including a description of the
|
||||
theme that will be displayed in Preferences.
|
||||
* [theme] name attribute is now expected to be translatable and used only
|
||||
for the theme selection UI. Existing [theme]s need to be converted to
|
||||
have a separate 'id' attribute.
|
||||
* [endlevel] now has two optional subtags [next_scenario_settings],
|
||||
[next_scenario_append], which can be used to reconfigure next scenario.
|
||||
* Optimizations made to the game events engine. Slower machines may notice
|
||||
an improvement during movement (when enter/exit_hex events are triggered).
|
||||
The optimization is more effective when relatively few events have variables
|
||||
in their names.
|
||||
* Miscellaneous and bug fixes:
|
||||
* Fixed: A compilation warning with DEBUG_WINDOW_LAYOUT_GRAPHS.
|
||||
* Added -Wold-style-cast to the CMake strict flags.
|
||||
* Made sure that cmake does not add -NDEBUG for release builds since this
|
||||
flag breaks building.
|
||||
* Updated screenshots used inside the ingame help and fixed description of
|
||||
orbs.
|
||||
* Fixed bug #21659: lua location_set:empty now works as described
|
||||
* Users now get a warning if they start a multiplayer scenario through the
|
||||
title screen load button, as this may cause eras and modifications not
|
||||
to work correctly in subsequent scenarios of an mp campaign.
|
||||
* wmllint can now update base terrain aliases in UMC after the changes in
|
||||
versions 1.11.8 and 1.11.9. This conversion is applied to the aliasof,
|
||||
mvt_alias, and def_alias attributes under the [terrain_type] tag.
|
||||
* Parser warnings when skipping over Unicode BOMs are now printed in stderr
|
||||
with the file location and substitution trail when available.
|
||||
|
||||
Version 1.11.9:
|
||||
* Add-ons client:
|
||||
* Display the first and last upload dates in the Description dialog.
|
||||
* Add-ons server:
|
||||
* Record the first upload date and time for new add-ons.
|
||||
* Removed ancient compatibility code used only for add-ons stored by some
|
||||
1.5.x versions.
|
||||
* AI:
|
||||
* Default AI: Gold saving is turned off by default again
|
||||
* New macros AI_SAVE_GOLD and AI_SAVE_GOLD_DEFAULT for easy enabling of
|
||||
recruitment gold saving in specific scenarios
|
||||
* Wolves Micro AI: new optional parameter attack_only_prey=
|
||||
* Campaigns:
|
||||
* Heir to the Throne:
|
||||
* Gave Li'sar a new ability "initiative" (grants adjacent allies first
|
||||
strike in melee).
|
||||
* Son of the Black Eye:
|
||||
* Rebalancing of the campaign continues and is done for Scenarios 1 (End
|
||||
of Peace) through 12 (Giving Some Back). In addition, the following
|
||||
not directly balance related changes have also been made:
|
||||
* S3: remove AI controller right-click menu option
|
||||
* S4 & S9: make AI attack enemies 1 XP from leveling so that it is not
|
||||
possible to block key locations with such units
|
||||
* S10 & S12: don't give huge unannounced gold bonus to AIs
|
||||
* Editor:
|
||||
* Added Impassable Overlay and Unwalkable Overlay terrains to the obstacle
|
||||
group.
|
||||
* Added Snowy Human City village terrain to the frozen group.
|
||||
* Added Cave Path terrain to the flat group.
|
||||
* Added Dry Hills terrain to the fall group.
|
||||
* Language and i18n:
|
||||
* Updated translations: Dutch, Portuguese, Scottish Gaelic
|
||||
* Lua API:
|
||||
* Added wesnoth.set_dialog_markup function (patch #2759).
|
||||
* Multiplayer
|
||||
* Updated map: Ruins of Terra-Dwelve.
|
||||
* Terrains:
|
||||
* Made Snowy Encampment, Snowy Orcish Castle, Snowy Encampment Keep and Snowy
|
||||
Orcish Keep aliases of both castle and frozen terrains
|
||||
* Units:
|
||||
* Decreased the strikes of the Dwarvish Lord's hatchet attack from 2 to 1.
|
||||
* Fixed subtle magenta TC for the Giant Mudcrawler sprites not being
|
||||
enabled in-game.
|
||||
* User interface:
|
||||
* Restored the old control scheme as the default
|
||||
* Fixed hidden variations of unit types (hide_help=yes) being listed in the
|
||||
help browser when they shouldn't.
|
||||
* Gray-out GUI1 scrollbar upwards scrolling button by default when starting
|
||||
with the view scrolled to the top.
|
||||
* Truncate long Advanced Preferences entries with ellipses to avoid
|
||||
situations where the listbox is wider than the Preferences dialog frame
|
||||
(bug #19482).
|
||||
* Team color is now applied on the Unknown unit icon in the game Status
|
||||
Table regardless of whether the side's leader unit supports team color.
|
||||
* Miscellaneous and bug fixes:
|
||||
* Added -Wno-documentation-deprecated-sync to the CMake pedantic flags.
|
||||
* Fixed several Doxygen issues found by Clang 3.4.
|
||||
* Fixed possible invalid memory access issue in the MP sides configuration
|
||||
code causing crashes for some users (bug #21449).
|
||||
* Fixed broken image references in the Gameplay -> Time of Day help topic.
|
||||
* The internal variables used by the LIMIT_RECRUITS WML macro are now
|
||||
cleared on victory.
|
||||
* Fixed missing log error message for invalid music tracks set with
|
||||
play_once=yes (bug #21479).
|
||||
* Don't force the .gz suffix on every entry of the save_index (bug #20849).
|
||||
* Fixed a bug in [filter_vision] in SUFs that caused a hidden unit under
|
||||
fog/shroud to produce a false positive.
|
||||
* A lack of ToD schedule no longer causes segfaults (bug #21489).
|
||||
* SLF work again when x XOR y is specified (bug #21488).
|
||||
* Selecting off-map hexes, then hovering over a unit no longer causes
|
||||
the game to crash (bug #21351).
|
||||
* Changed: Added -Wextra-semi to pedantic compilation.
|
||||
* Changed: Added -Wconditional-uninitialized to pedantic compilation.
|
||||
* Fixed NULL pointer dereference when viewing units in the Recall Unit
|
||||
dialog including nonexistent/unreadable images in their overlays, while
|
||||
not in debug mode.
|
||||
|
||||
Version 1.11.8:
|
||||
* Add-ons client:
|
||||
* Introduced new add-on type "SP/MP Campaign" for campaigns with
|
||||
"type=hybrid."
|
||||
* Fixed invalid file size data from the server crashing the client on the
|
||||
network transfer progress dialog (bug #20893).
|
||||
* Added support for specifying a feedback page URL in the .pbl file when
|
||||
publishing an add-on, currently intended for associating add-ons in the
|
||||
official add-ons server with topics from forums.wesnoth.org; this is
|
||||
achieved by including a [feedback] block with a topic_id=<number> key in
|
||||
it.
|
||||
* Redesigned Add-ons Description dialog, including support for displaying
|
||||
add-on feedback page URLs.
|
||||
* Add-ons server:
|
||||
* Fixed mishandling of inaccessible add-on packs resulting in multiple data
|
||||
conversion errors and stalling clients (bug #20893).
|
||||
* Added support for managing and emitting add-on feedback page URLs to
|
||||
clients ([server_info] feedback_url_format option in the server
|
||||
configuration file).
|
||||
* AI:
|
||||
* RCA AI: fix bug #21334: surrounded units don't attack
|
||||
* Coward Micro AI: new optional parameter [filter_second]
|
||||
* Simple Attack Micro AI: new optional parameter weapon=
|
||||
* Wolves Micro AI: fix bug that sometimes kept predators from attacking
|
||||
* Lua AI: new replay-safe action ai.synced_command()
|
||||
* ai.cfg: fix MEDIUM to NORMAL in attack_depth macro
|
||||
* Campaigns:
|
||||
* all: convert many wmllint magic comments from "recognize" to "who" and
|
||||
"whofield",
|
||||
* Dead Water:
|
||||
* New world map.
|
||||
* Delfador's Memoirs:
|
||||
* Updated sprite and animations for the Wose Shaman.
|
||||
* Descent into Darkness:
|
||||
* S3: set aggression=1 for Side 4 to avoid wrong choice of attack
|
||||
* Eastern Invasion:
|
||||
* Made Dacyn use teal TC and Mal-Ravanal blue TC, to make them fit the
|
||||
portraits more.
|
||||
* Updated maps for scenario 1-7.
|
||||
* Heir To The Throne:
|
||||
* Increased Li'sar's lvl3 hitpoints from 52 to 62.
|
||||
* Implemented the portrait variations for Delfador and Asheviere.
|
||||
* Changed Kaylan's portrait and gave him teal team coloring.
|
||||
* Changed the flaming sword so it's now a 25% increase to damage, instead
|
||||
of changing the damage to 15-4
|
||||
* Added a new mechanic to Sceptre of Fire. By standing still for a turn,
|
||||
Delfador can now tell the player the shortest path to the Sceptre.
|
||||
* Fixed Konrad's level 1's attack animation giving an 'image not found'
|
||||
error.
|
||||
* Fixed Konrad's dying words event.
|
||||
* Legend of Wesmere:
|
||||
* S9: set aggression=1 for Side 4 to avoid wrong choice of attack
|
||||
* Liberty:
|
||||
* New world map.
|
||||
* S5: set aggression=1 for Side 3 to avoid wrong choice of attack
|
||||
* Northern Rebirth:
|
||||
* S5a: dialogue tweaks
|
||||
* The Rise of Wesnoth:
|
||||
* New world maps.
|
||||
* Redesigned scenario 'A New Land'.
|
||||
* The South Guard:
|
||||
* S6a: fix ogre's last words event
|
||||
* S6b: set aggression=1 for Side 2 to avoid wrong choice of attack
|
||||
* Son of the Black Eye:
|
||||
* Rebalancing of the campaign continues and is mostly done for Scenarios 1
|
||||
(End of Peace) through 8 (Silent Forest). In addition, the following
|
||||
not directly balance related changes have also been made:
|
||||
* S1: the AI enemy can now also recruit bowmen
|
||||
* S4: give the player control of the Side 3 orcs in the center castle
|
||||
* S6: unload units from transport galleons preferentially onto land hexes
|
||||
* S7: use Simple Attack Micro AI to have scorpions spread poison
|
||||
* S8: use Healer Support Micro AI for elvish healers
|
||||
* S16: Kapou'e gets his own castle at the start of the scenario to
|
||||
eliminate dependence on luck during the first turn
|
||||
* S17: add a warning that the AI will receive reinforcements
|
||||
* S18: no linger mode at the end of the last scenario
|
||||
* Minor updates to messages (grammar and prose) and objectives.
|
||||
* Updated sprites and animations for the Orcish Shamans.
|
||||
* Graphics:
|
||||
* New and updated animations for the Loyalist Horseman.
|
||||
* Language and i18n:
|
||||
* Updated translations: Chinese (Traditional)
|
||||
* Updated translations: Catalan, Chinese (Traditional), Dutch, Galician,
|
||||
Japanese, Latin
|
||||
* Lua API:
|
||||
* Added flag, flag_icon, and village_support fields to wesnoth.sides table
|
||||
elements.
|
||||
* Made wesnoth.sides[n].hidden a read-write field.
|
||||
* New lua proxy table "wesnoth.game_config.mp_settings" for access to
|
||||
MP specific settings, such as era, scenario name, and timer
|
||||
* Multiplayer:
|
||||
* Unit names and genders are synced in MP games.
|
||||
* Added new CampaignWML attribute "require_campaign". If set to "yes",
|
||||
players not having campaign installed won't be able to join the game.
|
||||
* New eras: the Default+Khalifate and Age of Heroes+Khalifate eras are now
|
||||
available.
|
||||
* Replays:
|
||||
* Replays include the prestart and start events again.
|
||||
* Unit names and genders are synced between games and replays.
|
||||
* Play/stop buttons are disabled again at the end of a replay.
|
||||
* The 'reset replay' button works correctly and does not cause OOS
|
||||
errors any more.
|
||||
* User interface:
|
||||
* Removed the possibility to undo unit recruits because it caused oos.
|
||||
* Added a party full bell to the MP game configuration screen, played once
|
||||
all human player slots have been taken.
|
||||
* Change layout for advertized games in the MP lobby and add map icon.
|
||||
* Moved color cursors option to Advanced Preferences.
|
||||
* Always hide and disable color cursors option on Mac OS X since it's known
|
||||
to cause severe lags that render the cursor unusable.
|
||||
* Unit overlays are now displayed in the Recall dialog, both on the list
|
||||
and the description panel.
|
||||
* Made filtering controls on the MP create screen functional.
|
||||
* Removed the MP custom options dialog; all options are now shown directly
|
||||
on the configuration screen.
|
||||
* Removed the MP modifications dialog; modifications are now displayed
|
||||
directly on the creation screen.
|
||||
* The "Compressed saves" and "Compress savegames using bzip2" options in
|
||||
Preferences -> Advanced have been replaced by a single option,
|
||||
"Compressed saved games", that lets the user pick between gzip (default),
|
||||
bzip2, and no compression. Users who previously enabled bzip2 compression
|
||||
will need to do so again.
|
||||
* Hide eras menu in MP Create for campaigns which have
|
||||
"allow_era_choice=no".
|
||||
* Introduced side's name in MP Connect.
|
||||
* Middle click scrolling is now based on distance from initial click instead
|
||||
of the centre of the screen.
|
||||
* Make sliders able to be scrolled with the mouse wheel
|
||||
* Allow advanced preference booleans and mp modifications to be toggled
|
||||
via double click
|
||||
* Fixed slight scrolling glitches with credits sections with multi-line
|
||||
headers (e.g. those generated for campaigns with multi-line titles).
|
||||
* WML engine:
|
||||
* WML variable turn_number is set correctly (to 1) in prestart and start
|
||||
events. Previously, it retained its last value from the previous scenario
|
||||
until after the start event.
|
||||
* [scroll_to] and [scroll_to_unit] now take an optional side filter.
|
||||
* [trait] now accepts a "generate_description=" attribute, allowing the
|
||||
auto-generated effect descriptions to be turned off.
|
||||
* [modify_side] can now change a side's flags and status bar icon using the
|
||||
"flag" and "flag_icon" attributes also accepted in [side] definitions
|
||||
(bug #18454).
|
||||
* [store_side] now stores the "flag", "flag_icon", and "village_support"
|
||||
attributes from sides.
|
||||
* New macros RECALL and RECALL_XY
|
||||
* Miscellaneous and bug fixes:
|
||||
* Pango markup is applied correctly and consistently in button tooltips.
|
||||
* Fixed mishandling of invalid Pango markup resulting in previous messages
|
||||
being displayed instead in e.g. [message] (bug #20996).
|
||||
* Added wmllint code for recognizing unit id fields in macros, added
|
||||
non-attribute lines to local_sanity_check, added unknown speaker check.
|
||||
* Refactored code in wmltools to create a macro-parsing function.
|
||||
* Added era descriptions.
|
||||
* Fixed file chooser dialog (used in the map editor and for locating the
|
||||
wesnothd executable) interpreting special markup at the beginning of file
|
||||
names such as "#foo.map".
|
||||
* Fixed bug with modifications dependency check dialogs (bug #21365)
|
||||
* Fixed bug with scrollbar overlaying mp description text (bug #21364)
|
||||
* Fixed bug with help units not making links (bug #21339)
|
||||
* Split command line option --config-dir into --userconfig-dir and
|
||||
--userdata-dir, with --userconfig-dir defaulting to --userdata-dir's
|
||||
value on some platforms.
|
||||
* The color_adjust_blue_ attribute in [display] tags of saved games has
|
||||
been renamed to color_adjust_blue. Since it is only non-zero following a
|
||||
[color_adjust] action in a WML event, only mid-scenario saved games
|
||||
created with previous versions may present minor color issues after this
|
||||
change.
|
||||
* Fixed sound sources removed while the sound effects volume is zero
|
||||
(either in Preferences -> Sound or through the [volume] WML action)
|
||||
persisting and escaping the sound source management code (bug #21426).
|
||||
* The negative sign is no longer dropped when formula AI prints numbers
|
||||
between 0 and -1.
|
||||
|
||||
Version 1.11.7:
|
||||
* Add-ons client:
|
||||
|
@ -98,6 +547,8 @@ Version 1.11.7:
|
|||
pierce resistance from 30% to 60% and lowered its impact resistance
|
||||
from -10% to -20%.
|
||||
* Greatly decreased the Death Squire's HP from 66 to 44.
|
||||
* Northern Rebirth:
|
||||
* S5a: added dialog for dungeon signpost moveto
|
||||
* Sceptre of Fire:
|
||||
* Converted animation WML to the new syntax
|
||||
* Son of the Black Eye:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2003 - 2013 by David White <dave@whitevine.net>
|
||||
Copyright (C) 2003 - 2014 by David White <dave@whitevine.net>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
|
|
|
@ -1,9 +1,30 @@
|
|||
#textdomain wesnoth
|
||||
[advanced_preference]
|
||||
field=compress_saves
|
||||
name= _ "Compressed saves"
|
||||
type=boolean
|
||||
default=yes
|
||||
# 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]
|
||||
|
||||
[advanced_preference]
|
||||
|
@ -146,7 +167,7 @@
|
|||
|
||||
[advanced_preference]
|
||||
field=confirm_load_save_from_different_version
|
||||
name= _ "Confirm loading of saves from a different version"
|
||||
name= _ "Confirm loading saves from a different version"
|
||||
type=boolean
|
||||
default=yes
|
||||
[/advanced_preference]
|
||||
|
@ -159,13 +180,6 @@
|
|||
default=no
|
||||
[/advanced_preference]
|
||||
|
||||
[advanced_preference]
|
||||
field=bzip2_savegame_compression
|
||||
name= _ "Compress savegames using bzip2"
|
||||
type=boolean
|
||||
default=no
|
||||
[/advanced_preference]
|
||||
|
||||
[advanced_preference]
|
||||
field=new_lobby
|
||||
name= _ "Experimental multiplayer lobby"
|
||||
|
@ -174,6 +188,16 @@
|
|||
default=no
|
||||
[/advanced_preference]
|
||||
|
||||
#ifndef APPLE
|
||||
[advanced_preference]
|
||||
field=color_cursors
|
||||
name= _ "Show color cursors"
|
||||
description= _ "Use colored mouse cursors, which may be slower or break the game (use at your own risk)"
|
||||
type=boolean
|
||||
default=no
|
||||
[/advanced_preference]
|
||||
#endif
|
||||
|
||||
#ifdef __UNUSED__
|
||||
[advanced_preference]
|
||||
field=joystick_support_enabled
|
||||
|
|
|
@ -82,8 +82,13 @@ function ai_helper.put_labels(map, cfg)
|
|||
if cfg.keys then
|
||||
for i,k in ipairs(cfg.keys) do data = data[k] end
|
||||
end
|
||||
out = tonumber(data) or 'nan'
|
||||
if (type(data) == 'string') then
|
||||
out = data
|
||||
else
|
||||
out = tonumber(data) or 'nan'
|
||||
end
|
||||
end
|
||||
|
||||
if (type(out) == 'number') then out = out * factor end
|
||||
W.label { x = x, y = y, text = out }
|
||||
end)
|
||||
|
@ -121,6 +126,97 @@ function ai_helper.print_ts_delta(start_time, ...)
|
|||
return ts, delta
|
||||
end
|
||||
|
||||
----- AI execution helper functions ------
|
||||
|
||||
function ai_helper.checked_action_error(action, error_code)
|
||||
wesnoth.message('Lua AI error', 'If you see this message, something has gone wrong. Please report this on the Wesnoth forums, ideally with a replay and/or savegame.')
|
||||
error(action .. ' could not be executed. Error code: ' .. error_code)
|
||||
end
|
||||
|
||||
function ai_helper.checked_attack(ai, attacker, defender, weapon)
|
||||
local check = ai.check_attack(attacker, defender, weapon)
|
||||
|
||||
if (not check.ok) then
|
||||
ai_helper.checked_action_error('ai.attack', check.status)
|
||||
return
|
||||
end
|
||||
|
||||
ai.attack(attacker, defender, weapon)
|
||||
end
|
||||
|
||||
function ai_helper.checked_move_core(ai, unit, x, y, move_type)
|
||||
local check = ai.check_move(unit, x, y)
|
||||
|
||||
if (not check.ok) then
|
||||
-- The following errors are not fatal:
|
||||
-- E_EMPTY_MOVE = 2001
|
||||
-- E_AMBUSHED = 2005
|
||||
-- E_NOT_REACHED_DESTINATION = 2007
|
||||
if (check.status ~= 2001) and (check.status ~= 2005) and (check.status ~= 2007) then
|
||||
ai_helper.checked_action_error(move_type, check.status)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if (move_type == 'ai.move_full') then
|
||||
ai.move_full(unit, x, y)
|
||||
else
|
||||
ai.move(unit, x, y)
|
||||
end
|
||||
end
|
||||
|
||||
function ai_helper.checked_move_full(ai, unit, x, y)
|
||||
ai_helper.checked_move_core(ai, unit, x, y, 'ai.move_full')
|
||||
end
|
||||
|
||||
function ai_helper.checked_move(ai, unit, x, y)
|
||||
ai_helper.checked_move_core(ai, unit, x, y, 'ai.move')
|
||||
end
|
||||
|
||||
function ai_helper.checked_recruit(ai, unit_type, x, y)
|
||||
local check = ai.check_recruit(unit_type, x, y)
|
||||
|
||||
if (not check.ok) then
|
||||
ai_helper.checked_action_error('ai.recruit', check.status)
|
||||
return
|
||||
end
|
||||
|
||||
ai.recruit(unit_type, x, y)
|
||||
end
|
||||
|
||||
function ai_helper.checked_stopunit_all(ai, unit)
|
||||
local check = ai.check_stopunit(unit)
|
||||
|
||||
if (not check.ok) then
|
||||
ai_helper.checked_action_error('ai.stopunit_all', check.status)
|
||||
return
|
||||
end
|
||||
|
||||
ai.stopunit_all(unit)
|
||||
end
|
||||
|
||||
function ai_helper.checked_stopunit_attacks(ai, unit)
|
||||
local check = ai.check_stopunit(unit)
|
||||
|
||||
if (not check.ok) then
|
||||
ai_helper.checked_action_error('ai.stopunit_attacks', check.status)
|
||||
return
|
||||
end
|
||||
|
||||
ai.stopunit_attacks(unit)
|
||||
end
|
||||
|
||||
function ai_helper.checked_stopunit_moves(ai, unit)
|
||||
local check = ai.check_stopunit(unit)
|
||||
|
||||
if (not check.ok) then
|
||||
ai_helper.checked_action_error('ai.stopunit_moves', check.status)
|
||||
return
|
||||
end
|
||||
|
||||
ai.stopunit_moves(unit)
|
||||
end
|
||||
|
||||
----- General functionality and maths helper functions ------
|
||||
|
||||
function ai_helper.filter(input, condition)
|
||||
|
@ -159,16 +255,6 @@ function ai_helper.choose(input, value)
|
|||
return best_input, max_value, best_key
|
||||
end
|
||||
|
||||
function ai_helper.random(min, max)
|
||||
-- Use this function as Lua's 'math.random' is not replay or MP safe
|
||||
|
||||
if not max then min, max = 1, min end
|
||||
wesnoth.fire("set_variable", { name = "LUA_random", rand = string.format("%d..%d", min, max) })
|
||||
local res = wesnoth.get_variable "LUA_random"
|
||||
wesnoth.set_variable "LUA_random"
|
||||
return res
|
||||
end
|
||||
|
||||
function ai_helper.table_copy(t)
|
||||
-- Make a copy of a table (rather than just another pointer to the same table)
|
||||
local copy = {}
|
||||
|
@ -253,7 +339,7 @@ function ai_helper.LS_random_hex(set)
|
|||
-- This seems "inelegant", but I can't come up with another way without creating an extra array
|
||||
-- Return -1, -1 if set is empty
|
||||
|
||||
local r = ai_helper.random(set:size())
|
||||
local r = math.random(set:size())
|
||||
local i, xr, yr = 1, -1, -1
|
||||
set:iter( function(x, y, v)
|
||||
if (i == r) then xr, yr = x, y end
|
||||
|
@ -265,6 +351,63 @@ end
|
|||
|
||||
--------- Location, position or hex related helper functions ----------
|
||||
|
||||
function ai_helper.cartesian_coords(x, y)
|
||||
-- Converts coordinates from hex geometry to cartesian coordinates,
|
||||
-- meaning that y coordinates are offset by 0.5 every other hex
|
||||
-- Example: (1,1) stays (1,1) and (3,1) remains (3,1), but (2,1) -> (2,1.5) etc.
|
||||
return x, y + ((x + 1) % 2) / 2.
|
||||
end
|
||||
|
||||
function ai_helper.get_angle(from_hex, to_hex)
|
||||
-- Returns the angle of the direction from 'from_hex' to 'to_hex'
|
||||
-- Angle is in radians and goes from -pi to pi. 0 is toward east.
|
||||
-- Input hex tables can be of form { x, y } or { x = x, y = y }, which
|
||||
-- means that it is also possible to pass a unit table
|
||||
local x1, y1 = from_hex.x or from_hex[1], from_hex.y or from_hex[2]
|
||||
local x2, y2 = to_hex.x or to_hex[1], to_hex.y or to_hex[2]
|
||||
|
||||
local _, y1cart = ai_helper.cartesian_coords(x1, y1)
|
||||
local _, y2cart = ai_helper.cartesian_coords(x2, y2)
|
||||
|
||||
return math.atan2(y2cart - y1cart, x2 - x1)
|
||||
end
|
||||
|
||||
function ai_helper.get_direction_index(from_hex, to_hex, n, center_on_east)
|
||||
-- Returns an integer index for the direction from 'from_hex' to 'to_hex'
|
||||
-- with the full circle divided into 'n' slices
|
||||
-- 1 is always to the east, with indices increasing clockwise
|
||||
-- Input hex tables can be of form { x, y } or { x = x, y = y }, which
|
||||
-- means that it is also possible to pass a unit table
|
||||
--
|
||||
-- Optional input:
|
||||
-- center_on_east (false): boolean. By default, the eastern direction is the
|
||||
-- northern border of the first slice. If this parameter is set, east will
|
||||
-- instead be the center direction of the first slice
|
||||
|
||||
local d_east = 0
|
||||
if center_on_east then d_east = 0.5 end
|
||||
|
||||
local angle = ai_helper.get_angle(from_hex, to_hex)
|
||||
local index = math.floor((angle / math.pi * n/2 + d_east) % n ) + 1
|
||||
|
||||
return index
|
||||
end
|
||||
|
||||
function ai_helper.get_cardinal_directions(from_hex, to_hex)
|
||||
local dirs = { "E", "S", "W", "N" }
|
||||
return dirs[ai_helper.get_direction_index(from_hex, to_hex, 4, true)]
|
||||
end
|
||||
|
||||
function ai_helper.get_intercardinal_directions(from_hex, to_hex)
|
||||
local dirs = { "E", "SE", "S", "SW", "W", "NW", "N", "NE" }
|
||||
return dirs[ai_helper.get_direction_index(from_hex, to_hex, 8, true)]
|
||||
end
|
||||
|
||||
function ai_helper.get_hex_facing(from_hex, to_hex)
|
||||
local dirs = { "se", "s", "sw", "nw", "n", "ne" }
|
||||
return dirs[ai_helper.get_direction_index(from_hex, to_hex, 6)]
|
||||
end
|
||||
|
||||
function ai_helper.find_opposite_hex_adjacent(hex, center_hex)
|
||||
-- Find the hex that is opposite of 'hex' w.r.t. 'center_hex'
|
||||
-- Both input hexes are of format { x, y }
|
||||
|
@ -342,14 +485,15 @@ function ai_helper.get_closest_location(hex, location_filter, unit)
|
|||
if (radius == 0) then
|
||||
loc_filter = {
|
||||
{ "and", { x = hex[1], y = hex[2], radius = radius } },
|
||||
{ "and", location_filter }
|
||||
}
|
||||
else
|
||||
loc_filter = {
|
||||
{ "and", { x = hex[1], y = hex[2], radius = radius } },
|
||||
{ "not", { x = hex[1], y = hex[2], radius = radius - 1 } },
|
||||
{ "and", location_filter }
|
||||
}
|
||||
end
|
||||
for k,v in pairs(location_filter) do loc_filter[k] = v end
|
||||
|
||||
local locs = wesnoth.get_locations(loc_filter)
|
||||
|
||||
|
@ -908,7 +1052,7 @@ function ai_helper.find_best_move(units, rating_function, cfg)
|
|||
local rating = rating_function(x, y)
|
||||
|
||||
-- If cfg.random is set, add some randomness (on 0.0001 - 0.0099 level)
|
||||
if (not cfg.no_random) then rating = rating + ai_helper.random(99) / 10000. end
|
||||
if (not cfg.no_random) then rating = rating + math.random(99) / 10000. end
|
||||
-- If cfg.labels is set: insert values for label map
|
||||
if cfg.labels then reach_map:insert(x, y, rating) end
|
||||
|
||||
|
@ -954,7 +1098,7 @@ function ai_helper.move_unit_out_of_way(ai, unit, cfg)
|
|||
|
||||
if (max_rating > -9e99) then
|
||||
--W.message { speaker = unit.id, message = 'Moving out of way' }
|
||||
ai.move(unit, best_hex[1], best_hex[2])
|
||||
ai_helper.checked_move(ai, unit, best_hex[1], best_hex[2])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -972,9 +1116,9 @@ function ai_helper.movefull_stopunit(ai, unit, x, y)
|
|||
|
||||
local next_hop = ai_helper.next_hop(unit, x, y)
|
||||
if next_hop and ((next_hop[1] ~= unit.x) or (next_hop[2] ~= unit.y)) then
|
||||
ai.move_full(unit, next_hop[1], next_hop[2])
|
||||
ai_helper.checked_move_full(ai, unit, next_hop[1], next_hop[2])
|
||||
else
|
||||
ai.stopunit_moves(unit)
|
||||
ai_helper.checked_stopunit_moves(ai, unit)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1001,9 +1145,9 @@ function ai_helper.movefull_outofway_stopunit(ai, unit, x, y, cfg)
|
|||
|
||||
local next_hop = ai_helper.next_hop(unit, x, y)
|
||||
if next_hop and ((next_hop[1] ~= unit.x) or (next_hop[2] ~= unit.y)) then
|
||||
ai.move_full(unit, next_hop[1], next_hop[2])
|
||||
ai_helper.checked_move_full(ai, unit, next_hop[1], next_hop[2])
|
||||
else
|
||||
ai.stopunit_moves(unit)
|
||||
ai_helper.checked_stopunit_moves(ai, unit)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -559,7 +559,7 @@ return {
|
|||
until recruit_type ~= nil
|
||||
|
||||
if wesnoth.unit_types[recruit_type].cost <= wesnoth.sides[wesnoth.current.side].gold then
|
||||
ai.recruit(recruit_type, recruit_data.recruit.best_hex[1], recruit_data.recruit.best_hex[2])
|
||||
AH.checked_recruit(ai, recruit_type, recruit_data.recruit.best_hex[1], recruit_data.recruit.best_hex[2])
|
||||
|
||||
-- If the recruited unit cannot reach the target hex, return it to the pool of targets
|
||||
if recruit_data.recruit.target_hex ~= nil and recruit_data.recruit.target_hex[1] ~= nil then
|
||||
|
|
|
@ -254,7 +254,7 @@ return {
|
|||
if AH.print_exec() then print_time(' Executing castle_switch CA') end
|
||||
if AH.show_messages() then W.message { speaker = leader.id, message = 'Switching castles' } end
|
||||
|
||||
ai.move(leader, self.data.leader_target[1], self.data.leader_target[2])
|
||||
AH.checked_move(ai, leader, self.data.leader_target[1], self.data.leader_target[2])
|
||||
self.data.leader_target = nil
|
||||
end
|
||||
|
||||
|
@ -493,12 +493,14 @@ return {
|
|||
local defender = wesnoth.get_unit(self.data.attack.target.x, self.data.attack.target.y)
|
||||
|
||||
AH.movefull_stopunit(ai, attacker, self.data.attack.dst.x, self.data.attack.dst.y)
|
||||
if (not attacker) or (not attacker.valid) then return end
|
||||
if (not defender) or (not defender.valid) then return end
|
||||
|
||||
-- Find the poison weapon
|
||||
-- If several attacks have poison, this will always find the last one
|
||||
local is_poisoner, poison_weapon = AH.has_weapon_special(attacker, "poison")
|
||||
|
||||
ai.attack(attacker, defender, poison_weapon)
|
||||
AH.checked_attack(ai, attacker, defender, poison_weapon)
|
||||
|
||||
self.data.attack = nil
|
||||
end
|
||||
|
@ -604,7 +606,7 @@ return {
|
|||
if target then
|
||||
local x, y = wesnoth.find_vacant_tile(target[1], target[2], unit)
|
||||
local dest = AH.next_hop(unit, x, y)
|
||||
ai.move(unit, dest[1], dest[2])
|
||||
AH.checked_move(ai, unit, dest[1], dest[2])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ return {
|
|||
end
|
||||
|
||||
function move_to_any_target:move_to_enemy_exec()
|
||||
ai.move(self.data.unit, self.data.destination[1], self.data.destination[2])
|
||||
AH.checked_move(ai, self.data.unit, self.data.destination[1], self.data.destination[2])
|
||||
end
|
||||
|
||||
return move_to_any_target
|
||||
|
|
|
@ -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 MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
|
||||
local ca_big_animals = {}
|
||||
|
||||
|
@ -33,15 +34,18 @@ function ca_big_animals:execution(ai, cfg)
|
|||
--AH.put_labels(avoid)
|
||||
|
||||
for i,unit in ipairs(units) do
|
||||
local goal = MAIUV.get_mai_unit_variables(unit, cfg.ai_id)
|
||||
|
||||
-- Unit gets a new goal if none exist or on any move with 10% random chance
|
||||
local r = AH.random(10)
|
||||
if (not unit.variables.goal_x) or (r == 1) then
|
||||
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 rand = AH.random(#locs)
|
||||
local rand = math.random(#locs)
|
||||
--print(type, ': #locs', #locs, rand)
|
||||
unit.variables.goal_x, unit.variables.goal_y = locs[rand][1], locs[rand][2]
|
||||
goal.goal_x, goal.goal_y = locs[rand][1], locs[rand][2]
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, goal)
|
||||
end
|
||||
--print('Big animal goto: ', type, unit.variables.goal_x, unit.variables.goal_y, r)
|
||||
--print('Big animal goto: ', unit.id, goal.goal_x, goal.goal_y, r)
|
||||
|
||||
-- hexes the unit can reach
|
||||
local reach_map = AH.get_reachable_unocc(unit)
|
||||
|
@ -57,7 +61,7 @@ function ca_big_animals:execution(ai, cfg)
|
|||
local max_rating, best_hex = -9e99, {}
|
||||
reach_map:iter( function(x, y, v)
|
||||
-- Distance from goal is first rating
|
||||
local rating = - H.distance_between(x, y, unit.variables.goal_x, unit.variables.goal_y)
|
||||
local rating = - H.distance_between(x, y, goal.goal_x, goal.goal_y)
|
||||
|
||||
-- Proximity to an enemy unit is a plus
|
||||
local enemy_hp = 500
|
||||
|
@ -82,17 +86,17 @@ function ca_big_animals:execution(ai, cfg)
|
|||
--AH.put_labels(reach_map)
|
||||
|
||||
if (best_hex[1] ~= unit.x) or (best_hex[2] ~= unit.y) then
|
||||
ai.move(unit, best_hex[1], best_hex[2]) -- partial move only
|
||||
AH.checked_move(ai, unit, best_hex[1], best_hex[2]) -- partial move only
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
else -- If animal did not move, we need to stop it (also delete the goal)
|
||||
ai.stopunit_moves(unit)
|
||||
unit.variables.goal_x = nil
|
||||
unit.variables.goal_y = nil
|
||||
AH.checked_stopunit_moves(ai, unit)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
MAIUV.delete_mai_unit_variables(unit, cfg.ai_id)
|
||||
end
|
||||
|
||||
-- Or if this gets the unit to the goal, we also delete the goal
|
||||
if (unit.x == unit.variables.goal_x) and (unit.y == unit.variables.goal_y) then
|
||||
unit.variables.goal_x = nil
|
||||
unit.variables.goal_y = nil
|
||||
if (unit.x == goal.goal_x) and (unit.y == goal.goal_y) then
|
||||
MAIUV.delete_mai_unit_variables(unit, cfg.ai_id)
|
||||
end
|
||||
|
||||
-- Finally, if the unit ended up next to enemies, attack the weakest of those
|
||||
|
@ -106,9 +110,8 @@ function ca_big_animals:execution(ai, cfg)
|
|||
end
|
||||
end
|
||||
if target.id then
|
||||
ai.attack(unit, target)
|
||||
AH.checked_attack(ai, unit, target)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local H = wesnoth.require "lua/helper.lua"
|
||||
|
||||
local ca_bottleneck_attack = {}
|
||||
|
@ -71,10 +72,10 @@ function ca_bottleneck_attack:execution(ai, cfg, self)
|
|||
if self.data.bottleneck_attacks_done then
|
||||
local units = wesnoth.get_units { side = wesnoth.current.side, formula = '$this_unit.attacks_left > 0' }
|
||||
for i,u in ipairs(units) do
|
||||
ai.stopunit_attacks(u)
|
||||
AH.checked_stopunit_attacks(ai, u)
|
||||
end
|
||||
else
|
||||
ai.attack(self.data.attacker, self.data.target, self.data.weapon)
|
||||
AH.checked_attack(ai, self.data.attacker, self.data.target, self.data.weapon)
|
||||
end
|
||||
|
||||
self.data.attacker, self.data.target, self.data.weapon = nil, nil, nil
|
||||
|
|
|
@ -2,6 +2,7 @@ local H = wesnoth.require "lua/helper.lua"
|
|||
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 MAISD = wesnoth.dofile "ai/micro_ais/micro_ai_self_data.lua"
|
||||
|
||||
local ca_bottleneck_move = {}
|
||||
|
||||
|
@ -213,7 +214,9 @@ end
|
|||
|
||||
function ca_bottleneck_move:evaluation(ai, cfg, self)
|
||||
-- Check whether the side leader should be included or not
|
||||
if cfg.active_side_leader and (not self.data.side_leader_activated) then
|
||||
if cfg.active_side_leader and
|
||||
(not MAISD.get_mai_self_data(self.data, cfg.ai_id, "side_leader_activated"))
|
||||
then
|
||||
local can_still_recruit = false -- enough gold left for another recruit?
|
||||
local recruit_list = wesnoth.sides[wesnoth.current.side].recruit
|
||||
for i,recruit_type in ipairs(recruit_list) do
|
||||
|
@ -224,12 +227,14 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
|
|||
break
|
||||
end
|
||||
end
|
||||
if (not can_still_recruit) then self.data.side_leader_activated = true end
|
||||
if (not can_still_recruit) then
|
||||
MAISD.set_mai_self_data(self.data, cfg.ai_id, { side_leader_activated = true })
|
||||
end
|
||||
end
|
||||
|
||||
-- Now find all units, including the leader or not, depending on situation and settings
|
||||
local units = {}
|
||||
if self.data.side_leader_activated then
|
||||
if MAISD.get_mai_self_data(self.data, cfg.ai_id, "side_leader_activated") then
|
||||
units = wesnoth.get_units { side = wesnoth.current.side,
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
|
@ -481,7 +486,7 @@ end
|
|||
function ca_bottleneck_move:execution(ai, cfg, self)
|
||||
if self.data.bottleneck_moves_done then
|
||||
local units = {}
|
||||
if self.data.side_leader_activated then
|
||||
if MAISD.get_mai_self_data(self.data, cfg.ai_id, "side_leader_activated") then
|
||||
units = wesnoth.get_units { side = wesnoth.current.side,
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
|
@ -491,25 +496,26 @@ function ca_bottleneck_move:execution(ai, cfg, self)
|
|||
}
|
||||
end
|
||||
for i,u in ipairs(units) do
|
||||
ai.stopunit_moves(u)
|
||||
AH.checked_stopunit_moves(ai, u)
|
||||
end
|
||||
else
|
||||
--print("Moving unit:",self.data.unit.id, self.data.unit.x, self.data.unit.y, " ->", best_hex[1], best_hex[2], " -- turn:", wesnoth.current.turn)
|
||||
|
||||
if (self.data.unit.x ~= self.data.hex[1]) or (self.data.unit.y ~= self.data.hex[2]) then -- test needed for level-up move
|
||||
ai.move(self.data.unit, self.data.hex[1], self.data.hex[2]) -- don't want full move, as this might be stepping out of the way
|
||||
AH.checked_move(ai, self.data.unit, self.data.hex[1], self.data.hex[2]) -- don't want full move, as this might be stepping out of the way
|
||||
end
|
||||
if (not self.data.unit) or (not self.data.unit.valid) then return end
|
||||
|
||||
-- If this is a move for a level-up attack, do the attack also
|
||||
if self.data.lu_defender then
|
||||
--print("Level-up attack",self.data.unit.id, self.data.lu_defender.id, self.data.lu_weapon)
|
||||
|
||||
ai.attack(self.data.unit, self.data.lu_defender, self.data.lu_weapon)
|
||||
AH.checked_attack(ai, self.data.unit, self.data.lu_defender, self.data.lu_weapon)
|
||||
end
|
||||
end
|
||||
|
||||
-- Now delete almost everything
|
||||
-- Keep: self.data.is_my_territory, self.data.side_leader_activated
|
||||
-- Keep: self.data.is_my_territory, [micro_ai]side_leader_activated=
|
||||
self.data.unit, self.data.hex = nil, nil
|
||||
self.data.lu_defender, self.data.lu_weapon = nil, nil
|
||||
self.data.bottleneck_moves_done = nil
|
||||
|
|
|
@ -4,46 +4,38 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
|||
local ca_coward = {}
|
||||
|
||||
function ca_coward:evaluation(ai, cfg)
|
||||
local unit
|
||||
if cfg.filter then
|
||||
unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
else
|
||||
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
|
||||
end
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
-- Check if unit exists as sticky BCAs are not always removed successfully
|
||||
if unit then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
-- cfg parameters: id, distance, seek_x, seek_y, avoid_x, avoid_y
|
||||
function ca_coward:execution(ai, cfg)
|
||||
--print("Coward exec " .. cfg.id)
|
||||
local unit
|
||||
if cfg.filter then
|
||||
unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
else
|
||||
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
|
||||
end
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
local reach = wesnoth.find_reach(unit)
|
||||
|
||||
-- enemy units within reach
|
||||
local filter_second = cfg.filter_second or { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
|
||||
local enemies = wesnoth.get_units {
|
||||
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
|
||||
{ "and", filter_second },
|
||||
{ "filter_location", {x = unit.x, y = unit.y, radius = cfg.distance} }
|
||||
}
|
||||
|
||||
-- if no enemies are within reach: keep unit from doing anything and exit
|
||||
if not enemies[1] then
|
||||
ai.stopunit_all(unit)
|
||||
AH.checked_stopunit_all(ai, unit)
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -110,14 +102,10 @@ function ca_coward:execution(ai, cfg)
|
|||
end
|
||||
--items.place_image(mx, my, "items/ring-gold.png")
|
||||
|
||||
-- (mx,my) is the position to move to
|
||||
if (mx ~= unit.x or my ~= unit.y) then
|
||||
AH.movefull_stopunit(ai, unit, mx, my)
|
||||
end
|
||||
AH.movefull_stopunit(ai, unit, mx, my)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
|
||||
-- Get unit again, just in case it was killed by a moveto event
|
||||
local unit = wesnoth.get_units{ id = cfg.id }[1]
|
||||
if unit then ai.stopunit_all(unit) end
|
||||
AH.checked_stopunit_all(ai, unit)
|
||||
end
|
||||
|
||||
return ca_coward
|
||||
|
|
|
@ -95,10 +95,10 @@ function ca_forest_animals_move:execution(ai, cfg)
|
|||
|
||||
-- Choose one of the possible locations at random
|
||||
if reachable_terrain[1] then
|
||||
local rand = AH.random(#reachable_terrain)
|
||||
local rand = math.random(#reachable_terrain)
|
||||
-- This is not a full move, as running away might happen next
|
||||
if (unit.x ~= reachable_terrain[rand][1]) or (unit.y ~= reachable_terrain[rand][2]) then
|
||||
ai.move(unit, reachable_terrain[rand][1], reachable_terrain[rand][2])
|
||||
AH.checked_move(ai, unit, reachable_terrain[rand][1], reachable_terrain[rand][2])
|
||||
end
|
||||
else -- or if no close reachable terrain was found, move toward the closest
|
||||
local locs = wesnoth.get_locations(wander_terrain)
|
||||
|
@ -114,7 +114,7 @@ function ca_forest_animals_move:execution(ai, cfg)
|
|||
local next_hop = AH.next_hop(unit, x, y)
|
||||
--print(next_hop[1], next_hop[2])
|
||||
if (unit.x ~= next_hop[1]) or (unit.y ~= next_hop[2]) then
|
||||
ai.move(unit, next_hop[1], next_hop[2])
|
||||
AH.checked_move(ai, unit, next_hop[1], next_hop[2])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -122,9 +122,14 @@ function ca_forest_animals_move:execution(ai, cfg)
|
|||
|
||||
-- Now we check for close enemies again, as we might just have moved within reach of some
|
||||
local close_enemies = {}
|
||||
for j,e in ipairs(enemies) do
|
||||
if (H.distance_between(unit.x, unit.y, e.x, e.y) <= unit.max_moves+1) then
|
||||
table.insert(close_enemies, e)
|
||||
|
||||
-- We use a trick here to exclude the case when the unit might have been
|
||||
-- removed in an event above
|
||||
if unit and unit.valid then
|
||||
for j,e in ipairs(enemies) do
|
||||
if (H.distance_between(unit.x, unit.y, e.x, e.y) <= unit.max_moves+1) then
|
||||
table.insert(close_enemies, e)
|
||||
end
|
||||
end
|
||||
end
|
||||
--print(' #close_enemies after move', #close_enemies, #enemies, unit.id)
|
||||
|
@ -155,14 +160,14 @@ function ca_forest_animals_move:execution(ai, cfg)
|
|||
AH.movefull_stopunit(ai, unit, farthest_hex)
|
||||
-- If this is a rabbit ending on a hole -> disappears
|
||||
if (unit.type == rabbit_type) and hole_map:get(farthest_hex[1], farthest_hex[2]) then
|
||||
wesnoth.put_unit(farthest_hex[1], farthest_hex[2])
|
||||
local command = "wesnoth.put_unit(x1, y1)"
|
||||
ai.synced_command(command, farthest_hex[1], farthest_hex[2])
|
||||
end
|
||||
end
|
||||
|
||||
-- Finally, take moves away, as only partial move might have been done
|
||||
-- Also attacks, as these units never attack
|
||||
if unit and unit.valid then ai.stopunit_all(unit) end
|
||||
-- Need this ^ test here because bunnies might have disappeared
|
||||
if unit and unit.valid then AH.checked_stopunit_all(ai, unit) end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -38,10 +38,10 @@ function ca_forest_animals_new_rabbit:execution(ai, cfg)
|
|||
if (holes[i].image ~= cfg.rabbit_hole_img) and (holes[i].halo ~= cfg.rabbit_hole_img) then
|
||||
table.remove(holes, i)
|
||||
else
|
||||
holes[i].random = AH.random(100)
|
||||
holes[i].random = math.random(100)
|
||||
end
|
||||
else
|
||||
holes[i].random = AH.random(100)
|
||||
holes[i].random = math.random(100)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -64,7 +64,13 @@ function ca_forest_animals_new_rabbit:execution(ai, cfg)
|
|||
else
|
||||
x, y = wesnoth.find_vacant_tile(holes[i].x, holes[i].y)
|
||||
end
|
||||
wesnoth.put_unit(x, y, { side = wesnoth.current.side, type = cfg.rabbit_type } )
|
||||
|
||||
local command = "wesnoth.put_unit(x1, y1, { side = "
|
||||
.. wesnoth.current.side
|
||||
.. ", type = '"
|
||||
.. cfg.rabbit_type
|
||||
.. "' } )"
|
||||
ai.synced_command(command, x, y)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -55,13 +55,15 @@ function ca_forest_animals_tusker_attack:execution(ai, cfg)
|
|||
end)
|
||||
--print('attacker', attacker.x, attacker.y, ' -> ', best_hex[1], best_hex[2])
|
||||
AH.movefull_stopunit(ai, attacker, best_hex)
|
||||
if (not attacker) or (not attacker.valid) then return end
|
||||
if (not target) or (not target.valid) then return end
|
||||
|
||||
-- If adjacent, attack
|
||||
local dist = H.distance_between(attacker.x, attacker.y, target.x, target.y)
|
||||
if (dist == 1) then
|
||||
ai.attack(attacker, target)
|
||||
AH.checked_attack(ai, attacker, target)
|
||||
else
|
||||
ai.stopunit_attacks(attacker)
|
||||
AH.checked_stopunit_attacks(ai, attacker)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ function ca_forest_animals_tusklet_move:execution(ai, cfg)
|
|||
AH.movefull_stopunit(ai, tusklet, best_hex)
|
||||
|
||||
-- Also make sure tusklets never attack
|
||||
ai.stopunit_all(tusklet)
|
||||
if tusklet and tusklet.valid then AH.checked_stopunit_all(ai, tusklet) end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ 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 MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
local MAISD = wesnoth.dofile "ai/micro_ais/micro_ai_self_data.lua"
|
||||
|
||||
local ca_goto = {}
|
||||
|
||||
|
@ -20,12 +22,8 @@ function ca_goto:evaluation(ai, cfg, self)
|
|||
-- 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 cfg.release_all_units_at_goal then
|
||||
for rel in H.child_range(self.data, "goto_release_all") do
|
||||
if (rel.id == cfg.ca_id) then
|
||||
return 0
|
||||
end
|
||||
end
|
||||
if MAISD.get_mai_self_data(self.data, cfg.ai_id, "release_all") then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- For convenience, we check for locations here, and just pass that to the exec function
|
||||
|
@ -39,7 +37,8 @@ function ca_goto:evaluation(ai, cfg, self)
|
|||
--print('#locs org', #locs)
|
||||
if (#locs == 0) then return 0 end
|
||||
|
||||
-- If 'unique_goals' is set, check whether there are locations left to go to
|
||||
-- If 'unique_goals' is set, check whether there are locations left to go to.
|
||||
-- This does not have to be a persistent variable
|
||||
if cfg.unique_goals then
|
||||
-- First, some cleanup of previous turn data
|
||||
local str = 'goals_taken_' .. (wesnoth.current.turn - 1)
|
||||
|
@ -64,11 +63,8 @@ function ca_goto:evaluation(ai, cfg, self)
|
|||
-- Exclude released units
|
||||
if cfg.release_unit_at_goal then
|
||||
for i_unit=#units,1,-1 do
|
||||
for rel in H.child_range(self.data, "goto_release_unit") do
|
||||
if (rel.id == cfg.ca_id .. '_' .. units[i_unit].id) then
|
||||
table.remove(units, i_unit)
|
||||
break
|
||||
end
|
||||
if MAIUV.get_mai_unit_variables(units[i_unit], cfg.ai_id, "release") then
|
||||
table.remove(units, i_unit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -157,7 +153,7 @@ function ca_goto:execution(ai, cfg, self)
|
|||
-- Add a small penalty for occupied hexes
|
||||
-- (this mean occupied by an allied unit, as enemies make the hex unreachable)
|
||||
local unit_in_way = wesnoth.get_unit(l[1], l[2])
|
||||
if unit_in_way and ((unit_in_way.x ~= u.x) or (unit_in_way.y ~= u.y)) then
|
||||
if unit_in_way and (unit_in_way ~= u) then
|
||||
rating = rating - 0.01
|
||||
end
|
||||
|
||||
|
@ -202,10 +198,11 @@ function ca_goto:execution(ai, cfg, self)
|
|||
end
|
||||
|
||||
if closest_hex then
|
||||
ai.move_full(best_unit, closest_hex[1], closest_hex[2])
|
||||
AH.checked_move_full(ai, best_unit, closest_hex[1], closest_hex[2])
|
||||
else
|
||||
ai.stopunit_moves(best_unit)
|
||||
AH.checked_stopunit_moves(ai, best_unit)
|
||||
end
|
||||
if (not best_unit) or (not best_unit.valid) then return end
|
||||
|
||||
-- If release_unit_at_goal= or release_all_units_at_goal= key is set:
|
||||
-- Check if the unit made it to one of the goal hexes
|
||||
|
@ -226,12 +223,12 @@ function ca_goto:execution(ai, cfg, self)
|
|||
-- 2. Keys cannot contain certain characters -> everything potentially user-defined needs to be in values
|
||||
if unit_at_goal then
|
||||
if cfg.release_unit_at_goal then
|
||||
table.insert(self.data, { "goto_release_unit" , { id = cfg.ca_id .. '_' .. best_unit.id } } )
|
||||
MAIUV.set_mai_unit_variables(best_unit, cfg.ai_id, { release = true })
|
||||
end
|
||||
|
||||
if cfg.release_all_units_at_goal then
|
||||
--print("Releasing all units")
|
||||
table.insert(self.data, { "goto_release_all", { id = cfg.ca_id } } )
|
||||
MAISD.insert_mai_self_data(self.data, cfg.ai_id, { release_all = true })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
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 MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
local MAISD = wesnoth.dofile "ai/micro_ais/micro_ai_self_data.lua"
|
||||
|
||||
local ca_hang_out = {}
|
||||
|
||||
|
@ -8,22 +10,20 @@ function ca_hang_out:evaluation(ai, cfg, self)
|
|||
cfg = cfg or {}
|
||||
|
||||
-- Return 0 if the mobilize condition has previously been met
|
||||
for mobilze in H.child_range(self.data, "hangout_mobilize_units") do
|
||||
if (mobilze.id == cfg.ca_id) then
|
||||
return 0
|
||||
end
|
||||
if MAISD.get_mai_self_data(self.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))
|
||||
or (cfg.mobilize_on_gold_less_than and (wesnoth.sides[wesnoth.current.side].gold < cfg.mobilize_on_gold_less_than))
|
||||
then
|
||||
table.insert(self.data, { "hangout_mobilize_units" , { id = cfg.ca_id } } )
|
||||
MAISD.insert_mai_self_data(self.data, cfg.ai_id, { mobilize_units = true })
|
||||
|
||||
-- Need to unmark all units also
|
||||
local units = wesnoth.get_units { side = wesnoth.current.side, { "and", cfg.filter } }
|
||||
for i,u in ipairs(units) do
|
||||
u.variables.mai_hangout_moved = nil
|
||||
MAIUV.delete_mai_unit_variables(u, cfg.ai_id)
|
||||
end
|
||||
|
||||
return 0
|
||||
|
@ -59,14 +59,33 @@ function ca_hang_out:execution(ai, cfg, self)
|
|||
}
|
||||
--print('#locs', #locs)
|
||||
|
||||
-- Get map for locations to be avoided (defaults to all castle terrain)
|
||||
local avoid = cfg.avoid or { terrain = 'C*,C*^*,*^C*' }
|
||||
local avoid_map = LS.of_pairs(wesnoth.get_locations(avoid))
|
||||
-- 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
|
||||
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*' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local avoid_map = LS.of_pairs(wesnoth.get_locations(avoid_tag))
|
||||
|
||||
local best_hex, best_unit, max_rating = {}, {}, -9e99
|
||||
for i,u in ipairs(units) do
|
||||
-- Only consider units that have not been marked yet
|
||||
if (not u.variables.mai_hangout_moved) then
|
||||
if (not MAIUV.get_mai_unit_variables(u, cfg.ai_id, "moved")) then
|
||||
local best_hex_unit, max_rating_unit = {}, -9e99
|
||||
|
||||
-- Check out all unoccupied hexes the unit can reach
|
||||
|
@ -107,14 +126,16 @@ function ca_hang_out:execution(ai, cfg, self)
|
|||
-- respective best locations already, we take moves away from all units
|
||||
if (max_rating == -9e99) then
|
||||
for i,u in ipairs(units) do
|
||||
ai.stopunit_moves(u)
|
||||
AH.checked_stopunit_moves(ai, u)
|
||||
-- Also remove the markers
|
||||
u.variables.mai_hangout_moved = nil
|
||||
if u and u.valid then MAIUV.delete_mai_unit_variables(u, cfg.ai_id) end
|
||||
end
|
||||
else
|
||||
-- Otherwise move unit and mark as having been used
|
||||
ai.move(best_unit, best_hex[1], best_hex[2])
|
||||
best_unit.variables.mai_hangout_moved = true
|
||||
AH.checked_move(ai, best_unit, best_hex[1], best_hex[2])
|
||||
if best_unit and best_unit.valid then
|
||||
MAIUV.set_mai_unit_variables(best_unit, cfg.ai_id, { moved = true })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ function ca_healer_move:evaluation(ai, cfg, self)
|
|||
-- Also, hex must be unoccupied by another unit, of course
|
||||
local unit_in_way = wesnoth.get_unit(r[1], r[2])
|
||||
if (not avoid_map:get(r[1], r[2])) then
|
||||
if (not unit_in_way) or ((unit_in_way.x == h.x) and (unit_in_way.y == h.y)) then
|
||||
if (not unit_in_way) or (unit_in_way == h) then
|
||||
for k,u in ipairs(healees) do
|
||||
if (H.distance_between(u.x, u.y, r[1], r[2]) == 1) then
|
||||
-- !!!!!!! These ratings have to be positive or the method doesn't work !!!!!!!!!
|
||||
|
|
|
@ -67,7 +67,7 @@ function ca_herding_attack_close_enemy:execution(ai, cfg)
|
|||
--print('Dog moving in to attack')
|
||||
AH.movefull_stopunit(ai, best_dog, best_hex)
|
||||
if H.distance_between(best_dog.x, best_dog.y, best_enemy.x, best_enemy.y) == 1 then
|
||||
ai.attack(best_dog, best_enemy)
|
||||
AH.checked_attack(ai, best_dog, best_enemy)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
|
|
@ -37,9 +37,9 @@ function ca_herding_dog_move:execution(ai, cfg)
|
|||
-- Or, if dog cannot get there, prefer to be av_dist from the center
|
||||
local rating = 0
|
||||
if herding_perimeter:get(x, y) then
|
||||
rating = rating + 1000 + AH.random(99) / 100.
|
||||
rating = rating + 1000 + math.random(99) / 100.
|
||||
else
|
||||
rating = rating - math.abs(H.distance_between(x, y, cfg.herd_x, cfg.herd_y) - av_dist) + AH.random(99) / 100.
|
||||
rating = rating - math.abs(H.distance_between(x, y, cfg.herd_x, cfg.herd_y) - av_dist) + math.random(99) / 100.
|
||||
end
|
||||
|
||||
return rating
|
||||
|
|
|
@ -83,10 +83,10 @@ function ca_herding_herd_sheep:execution(ai, cfg)
|
|||
-- If it's already in the best position, we just take moves away from it
|
||||
-- (to avoid black-listing of CA, in the worst case)
|
||||
if (best_hex[1] == best_dog.x) and (best_hex[2] == best_dog.y) then
|
||||
ai.stopunit_moves(best_dog)
|
||||
AH.checked_stopunit_moves(ai, best_dog)
|
||||
else
|
||||
--print('Dog moving to herd sheep')
|
||||
ai.move(best_dog, best_hex[1], best_hex[2]) -- partial move only
|
||||
AH.checked_move(ai, best_dog, best_hex[1], best_hex[2]) -- partial move only
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ function ca_herding_sheep_move:execution(ai, cfg)
|
|||
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)
|
||||
else
|
||||
ai.move(sheep, x, y)
|
||||
AH.checked_move(ai, sheep, x, y)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local W = H.set_wml_action_metatable {}
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
|
||||
local ca_hunter = {}
|
||||
|
||||
|
@ -27,7 +28,7 @@ local function hunter_attack_weakest_adj_enemy(ai, unit)
|
|||
|
||||
if target.id then
|
||||
--W.message { speaker = unit.id, message = 'Attacking weakest adjacent enemy' }
|
||||
ai.attack(unit, target)
|
||||
AH.checked_attack(ai, unit, target)
|
||||
if target.valid then
|
||||
return 'attacked'
|
||||
else
|
||||
|
@ -39,16 +40,12 @@ local function hunter_attack_weakest_adj_enemy(ai, unit)
|
|||
end
|
||||
|
||||
function ca_hunter:evaluation(ai, cfg)
|
||||
local unit
|
||||
if cfg.filter then
|
||||
unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
else
|
||||
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
|
||||
end
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
if unit then return cfg.ca_score end
|
||||
return 0
|
||||
|
@ -60,30 +57,27 @@ function ca_hunter:execution(ai, cfg)
|
|||
-- hunting_ground, then retreats to
|
||||
-- position given by 'home_x,home_y' for 'rest_turns' turns, or until fully healed
|
||||
|
||||
local unit
|
||||
if cfg.filter then
|
||||
unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
else
|
||||
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
|
||||
end
|
||||
--print('Hunter: ', unit.id)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
-- If hunting_status is not set for the unit -> default behavior -> hunting
|
||||
if (not unit.variables.hunting_status) then
|
||||
local hunter_vars = MAIUV.get_mai_unit_variables(unit, cfg.ai_id)
|
||||
if (not hunter_vars.hunting_status) then
|
||||
-- Unit gets a new goal if none exist or on any move with 10% random chance
|
||||
local r = AH.random(10)
|
||||
if (not unit.variables.goal_x) or (r <= 1) then
|
||||
local r = math.random(10)
|
||||
if (not hunter_vars.goal_x) or (r <= 1) then
|
||||
-- 'locs' includes border hexes, but that does not matter here
|
||||
locs = AH.get_passable_locations((cfg.filter_location or {}), unit)
|
||||
local rand = AH.random(#locs)
|
||||
local rand = math.random(#locs)
|
||||
--print('#locs', #locs, rand)
|
||||
unit.variables.goal_x, unit.variables.goal_y = locs[rand][1], locs[rand][2]
|
||||
hunter_vars.goal_x, hunter_vars.goal_y = locs[rand][1], locs[rand][2]
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
end
|
||||
--print('Hunter goto: ', unit.variables.goal_x, unit.variables.goal_y, r)
|
||||
--print('Hunter goto: ', hunter_vars.goal_x, hunter_vars.goal_y, r)
|
||||
|
||||
-- Hexes the unit can reach
|
||||
local reach_map = AH.get_reachable_unocc(unit)
|
||||
|
@ -92,7 +86,7 @@ function ca_hunter:execution(ai, cfg)
|
|||
local max_rating, best_hex = -9e99, {}
|
||||
reach_map:iter( function(x, y, v)
|
||||
-- Distance from goal is first rating
|
||||
local rating = - H.distance_between(x, y, unit.variables.goal_x, unit.variables.goal_y)
|
||||
local rating = - H.distance_between(x, y, hunter_vars.goal_x, hunter_vars.goal_y)
|
||||
|
||||
-- Proximity to an enemy unit is a plus
|
||||
local enemy_hp = 500
|
||||
|
@ -113,15 +107,19 @@ function ca_hunter:execution(ai, cfg)
|
|||
--AH.put_labels(reach_map)
|
||||
|
||||
if (best_hex[1] ~= unit.x) or (best_hex[2] ~= unit.y) then
|
||||
ai.move(unit, best_hex[1], best_hex[2]) -- partial move only
|
||||
AH.checked_move(ai, unit, best_hex[1], best_hex[2]) -- partial move only
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
else -- If hunter did not move, we need to stop it (also delete the goal)
|
||||
ai.stopunit_moves(unit)
|
||||
unit.variables.goal_x, unit.variables.goal_y = nil, nil
|
||||
AH.checked_stopunit_moves(ai, unit)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
hunter_vars.goal_x, hunter_vars.goal_y = nil, nil
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
end
|
||||
|
||||
-- Or if this gets the unit to the goal, we also delete the goal
|
||||
if (unit.x == unit.variables.goal_x) and (unit.y == unit.variables.goal_y) then
|
||||
unit.variables.goal_x, unit.variables.goal_y = nil, nil
|
||||
if (unit.x == hunter_vars.goal_x) and (unit.y == hunter_vars.goal_y) then
|
||||
hunter_vars.goal_x, hunter_vars.goal_y = nil, nil
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
end
|
||||
|
||||
-- Finally, if the unit ended up next to enemies, attack the weakest of those
|
||||
|
@ -129,8 +127,9 @@ function ca_hunter:execution(ai, cfg)
|
|||
|
||||
-- If the enemy was killed, hunter returns home
|
||||
if unit.valid and (attack_status == 'killed') then
|
||||
unit.variables.goal_x, unit.variables.goal_y = nil, nil
|
||||
unit.variables.hunting_status = 'return'
|
||||
hunter_vars.goal_x, hunter_vars.goal_y = nil, nil
|
||||
hunter_vars.hunting_status = 'return'
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
if cfg.show_messages then
|
||||
W.message { speaker = unit.id, message = 'Now that I have eaten, I will go back home.' }
|
||||
end
|
||||
|
@ -141,7 +140,7 @@ function ca_hunter:execution(ai, cfg)
|
|||
end
|
||||
|
||||
-- If we got here, this means the unit is either returning, or resting
|
||||
if (unit.variables.hunting_status == 'return') then
|
||||
if (hunter_vars.hunting_status == 'return') then
|
||||
goto_x, goto_y = wesnoth.find_vacant_tile(cfg.home_x, cfg.home_y)
|
||||
--print('Go home:', home_x, home_y, goto_x, goto_y)
|
||||
|
||||
|
@ -149,6 +148,7 @@ function ca_hunter:execution(ai, cfg)
|
|||
if next_hop then
|
||||
--print(next_hop[1], next_hop[2])
|
||||
AH.movefull_stopunit(ai, unit, next_hop)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
|
||||
-- If there's an enemy on the 'home' hex and we got right next to it, attack that enemy
|
||||
if (H.distance_between(cfg.home_x, cfg.home_y, next_hop[1], next_hop[2]) == 1) then
|
||||
|
@ -157,20 +157,23 @@ function ca_hunter:execution(ai, cfg)
|
|||
if cfg.show_messages then
|
||||
W.message { speaker = unit.id, message = 'Get out of my home!' }
|
||||
end
|
||||
ai.attack(unit, enemy)
|
||||
AH.checked_attack(ai, unit, enemy)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- We also attack the weakest adjacent enemy, if still possible
|
||||
hunter_attack_weakest_adj_enemy(ai, unit)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
|
||||
-- If the unit got home, start the resting counter
|
||||
if unit.valid and (unit.x == cfg.home_x) and (unit.y == cfg.home_y) then
|
||||
unit.variables.hunting_status = 'resting'
|
||||
unit.variables.resting_until = wesnoth.current.turn + (cfg.rest_turns or 3)
|
||||
if (unit.x == cfg.home_x) and (unit.y == cfg.home_y) then
|
||||
hunter_vars.hunting_status = 'resting'
|
||||
hunter_vars.resting_until = wesnoth.current.turn + (cfg.rest_turns or 3)
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
if cfg.show_messages then
|
||||
W.message { speaker = unit.id, message = 'I made it home - resting now until the end of Turn ' .. unit.variables.resting_until .. ' or until fully healed.' }
|
||||
W.message { speaker = unit.id, message = 'I made it home - resting now until the end of Turn ' .. hunter_vars.resting_until .. ' or until fully healed.' }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -179,17 +182,20 @@ function ca_hunter:execution(ai, cfg)
|
|||
end
|
||||
|
||||
-- If we got here, the only remaining action is resting
|
||||
if (unit.variables.hunting_status == 'resting') then
|
||||
if (hunter_vars.hunting_status == 'resting') then
|
||||
-- So all we need to do is take moves away from the unit
|
||||
ai.stopunit_moves(unit)
|
||||
AH.checked_stopunit_moves(ai, unit)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
|
||||
-- However, we do also attack the weakest adjacent enemy, if still possible
|
||||
hunter_attack_weakest_adj_enemy(ai, unit)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
|
||||
-- If this is the last turn of resting, we also remove the status and turn variable
|
||||
if unit.valid and (unit.hitpoints >= unit.max_hitpoints) and (unit.variables.resting_until <= wesnoth.current.turn) then
|
||||
unit.variables.hunting_status = nil
|
||||
unit.variables.resting_until = nil
|
||||
if (unit.hitpoints >= unit.max_hitpoints) and (hunter_vars.resting_until <= wesnoth.current.turn) then
|
||||
hunter_vars.hunting_status = nil
|
||||
hunter_vars.resting_until = nil
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
if cfg.show_messages then
|
||||
W.message { speaker = unit.id, message = 'I am done resting. It is time to go hunting again next turn.' }
|
||||
end
|
||||
|
@ -198,7 +204,8 @@ function ca_hunter:execution(ai, cfg)
|
|||
end
|
||||
|
||||
-- In principle we should never get here, but just in case: reset variable, so that unit goes hunting on next turn
|
||||
unit.variables.hunting_status = nil
|
||||
hunter_vars.hunting_status = nil
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
end
|
||||
|
||||
return ca_hunter
|
||||
|
|
|
@ -60,14 +60,16 @@ function ca_lurkers:execution(ai, cfg)
|
|||
if rattack_nt_target:size() > 0 then
|
||||
|
||||
-- Choose one of the possible attack locations at random
|
||||
local rand = AH.random(1, rattack_nt_target:size())
|
||||
local rand = math.random(1, rattack_nt_target:size())
|
||||
local dst = rattack_nt_target:to_stable_pairs()
|
||||
AH.movefull_stopunit(ai, me, dst[rand])
|
||||
ai.attack(dst[rand][1], dst[rand][2], target.x, target.y)
|
||||
if (not me) or (not me.valid) then return end
|
||||
AH.checked_attack(ai, me, target)
|
||||
attacked = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if (not me) or (not me.valid) then return end
|
||||
|
||||
-- If unit has moves left (that is, it didn't attack), go to random wander terrain hex
|
||||
-- Check first that unit wasn't killed in the attack
|
||||
|
@ -81,7 +83,7 @@ function ca_lurkers:execution(ai, cfg)
|
|||
reachable_wander_terrain:inter(reach)
|
||||
|
||||
-- get one of the reachable wander terrain hexes randomly
|
||||
local rand = AH.random(1, reachable_wander_terrain:size())
|
||||
local rand = math.random(1, reachable_wander_terrain:size())
|
||||
--print(" reach_wander no allies: " .. reachable_wander_terrain:size() .. " rand #: " .. rand)
|
||||
local dst = reachable_wander_terrain:to_stable_pairs()
|
||||
if dst[1] then
|
||||
|
@ -91,9 +93,10 @@ function ca_lurkers:execution(ai, cfg)
|
|||
end
|
||||
AH.movefull_stopunit(ai, me, dst)
|
||||
end
|
||||
if (not me) or (not me.valid) then return end
|
||||
|
||||
-- If the unit has moves or attacks left at this point, take them away
|
||||
if me and me.valid then ai.stopunit_all(me) end
|
||||
AH.checked_stopunit_all(ai, me)
|
||||
end
|
||||
|
||||
return ca_lurkers
|
||||
|
|
|
@ -53,7 +53,7 @@ local function messenger_find_enemies_in_way(unit, goal_x, goal_y)
|
|||
return
|
||||
end
|
||||
|
||||
local function messenger_find_clearing_attack(unit, goal_x, goal_y)
|
||||
local function messenger_find_clearing_attack(unit, goal_x, goal_y, cfg)
|
||||
-- Check if an enemy is in the way of the messenger
|
||||
-- If so, find attack that would "clear" that enemy out of the way
|
||||
-- unit: proxy table for the messenger unit
|
||||
|
@ -68,8 +68,12 @@ local function messenger_find_clearing_attack(unit, goal_x, goal_y)
|
|||
--print('Finding attacks on',enemy_in_way.name,enemy_in_way.id)
|
||||
|
||||
-- Find all units that can attack this enemy
|
||||
local my_units = wesnoth.get_units{ side = wesnoth.current.side, formula = '$this_unit.attacks_left > 0',
|
||||
{ "not", { id = unit.id } }
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local my_units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.attacks_left > 0',
|
||||
{ "not", filter },
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
|
||||
-- Eliminate units without attacks
|
||||
|
@ -131,17 +135,14 @@ local function messenger_find_clearing_attack(unit, goal_x, goal_y)
|
|||
end
|
||||
|
||||
function ca_messenger_attack:evaluation(ai, cfg, self)
|
||||
-- Attack units in the path of the messenger
|
||||
-- id: id of the messenger unit
|
||||
-- Attack units in the path of the messengers
|
||||
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
|
||||
|
||||
local messenger = wesnoth.get_units{ side = wesnoth.current.side, id = cfg.id }[1]
|
||||
local messenger, x, y = messenger_next_waypoint(cfg)
|
||||
if (not messenger) then return 0 end
|
||||
|
||||
local x, y = messenger_next_waypoint(messenger, cfg, self)
|
||||
|
||||
-- See if there's an enemy in the way that should be attacked
|
||||
local attack = messenger_find_clearing_attack(messenger, x, y)
|
||||
local attack = messenger_find_clearing_attack(messenger, x, y, cfg)
|
||||
|
||||
if attack then
|
||||
self.data.best_attack = attack
|
||||
|
@ -156,7 +157,10 @@ function ca_messenger_attack:execution(ai, cfg, self)
|
|||
local defender = wesnoth.get_unit(self.data.best_attack.target.x, self.data.best_attack.target.y)
|
||||
|
||||
AH.movefull_stopunit(ai, attacker, self.data.best_attack.dst.x, self.data.best_attack.dst.y)
|
||||
ai.attack(attacker, defender)
|
||||
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.best_attack = nil
|
||||
end
|
||||
|
||||
|
|
|
@ -1,61 +1,94 @@
|
|||
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 MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
|
||||
local ca_messenger_escort_move = {}
|
||||
|
||||
local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f_next_waypoint.lua"
|
||||
|
||||
function ca_messenger_escort_move:evaluation(ai, cfg)
|
||||
-- Move escort units close to messenger, and in between messenger and enemies
|
||||
-- The messenger has moved at this time, so we don't need to exclude him
|
||||
-- But we check that he exist (not for this scenario, but for others)
|
||||
local messenger = wesnoth.get_units{ side = wesnoth.current.side, id = cfg.id }[1]
|
||||
if (not messenger) then return 0 end
|
||||
-- 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
|
||||
|
||||
local my_units = wesnoth.get_units{ side = wesnoth.current.side, formula = '$this_unit.moves > 0' }
|
||||
local my_units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.moves > 0',
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
if (not my_units[1]) then return 0 end
|
||||
|
||||
if my_units[1] then
|
||||
return cfg.ca_score
|
||||
end
|
||||
return 0
|
||||
local _, _, _, messengers = messenger_next_waypoint(cfg)
|
||||
if (not messengers) or (not messengers[1]) then return 0 end
|
||||
|
||||
return cfg.ca_score
|
||||
end
|
||||
|
||||
function ca_messenger_escort_move:execution(ai, cfg)
|
||||
local messenger = wesnoth.get_units{ id = cfg.id }[1]
|
||||
local my_units = wesnoth.get_units{ side = wesnoth.current.side, formula = '$this_unit.moves > 0' }
|
||||
local my_units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.moves > 0',
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
|
||||
-- Simply move units one at a time
|
||||
local next_unit = my_units[1]
|
||||
local reach = LS.of_pairs(wesnoth.find_reach(next_unit))
|
||||
|
||||
-- Distance from messenger for each hex the unit can reach
|
||||
local dist_messenger = AH.distance_map({messenger}, reach)
|
||||
local _, _, _, messengers = messenger_next_waypoint(cfg)
|
||||
|
||||
local enemies = wesnoth.get_units {
|
||||
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
|
||||
}
|
||||
|
||||
-- Rating (in the end, we pick the _minimum _rating):
|
||||
-- 1. Minimize distance from enemies
|
||||
local rating = AH.distance_map(enemies, reach)
|
||||
-- 2. This one favors hexes in between messenger and enemies
|
||||
rating:union_merge(dist_messenger, function(x, y, v1, v2)
|
||||
return v1 + v2*#enemies
|
||||
end)
|
||||
-- 3. Strongly prefer hexes close to the messenger
|
||||
rating:union_merge(dist_messenger, function(x, y, v1, v2)
|
||||
return v1 + v2^2
|
||||
end)
|
||||
--AH.put_labels(rating)
|
||||
local base_rating_map = LS.create()
|
||||
local max_rating, best_unit, best_hex = -9e99
|
||||
for _,unit in ipairs(my_units) do
|
||||
-- Only considering hexes unoccupied by other units is good enough for this
|
||||
local reach_map = AH.get_reachable_unocc(unit)
|
||||
|
||||
-- Now find hex with minimum value that is unoccupied
|
||||
min_rating, best_hex = 9e99, {}
|
||||
rating:iter(function(x, y, r)
|
||||
local unit_in_way = wesnoth.get_units{ x = x, y = y, { "not", { id = next_unit.id } } }[1]
|
||||
if (not unit_in_way) and (r < min_rating) then
|
||||
min_rating, best_hex = r, { x, y }
|
||||
end
|
||||
end)
|
||||
-- and move the unit there
|
||||
AH.movefull_stopunit(ai, next_unit, best_hex)
|
||||
-- Minor rating for the fastest and strongest unit to go first
|
||||
local unit_rating = unit.max_moves / 100. + unit.hitpoints / 1000.
|
||||
|
||||
reach_map:iter( function(x, y, v)
|
||||
-- base rating only needs to be calculated once for each hex
|
||||
local base_rating = base_rating_map:get(x, y)
|
||||
|
||||
if (not base_rating) then
|
||||
base_rating = 0
|
||||
|
||||
-- Distance from messenger is most important; only closest messenger counts for this
|
||||
-- Give somewhat of a bonus for the messenger that has moved the most through the waypoints
|
||||
local max_messenger_rating = -9e99
|
||||
for _,m in ipairs(messengers) do
|
||||
local messenger_rating = 1. / (H.distance_between(x, y, m.x, m.y) + 2.)
|
||||
local wp_rating = MAIUV.get_mai_unit_variables(m, cfg.ai_id, "wp_rating")
|
||||
messenger_rating = messenger_rating * 10. * (1. + wp_rating * 2.)
|
||||
|
||||
if (messenger_rating > max_messenger_rating) then
|
||||
max_messenger_rating = messenger_rating
|
||||
end
|
||||
end
|
||||
base_rating = base_rating + max_messenger_rating
|
||||
|
||||
-- Distance from (sum of) enemies is important too
|
||||
-- This favors placing escort units between the messenger and close enemies
|
||||
for _,e in ipairs(enemies) do
|
||||
base_rating = base_rating + 1. / (H.distance_between(x, y, e.x, e.y) + 2.)
|
||||
end
|
||||
|
||||
base_rating_map:insert(x, y, base_rating)
|
||||
end
|
||||
|
||||
local rating = base_rating + unit_rating
|
||||
|
||||
if (rating > max_rating) then
|
||||
max_rating = rating
|
||||
best_unit, best_hex = unit, { x, y }
|
||||
end
|
||||
end)
|
||||
end
|
||||
--AH.put_labels(base_rating_map)
|
||||
|
||||
-- This will always find at least the hex the unit is on -> no check necessary
|
||||
AH.movefull_stopunit(ai, best_unit, best_hex)
|
||||
end
|
||||
|
||||
return ca_messenger_escort_move
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
|
||||
return function(messenger, cfg, self)
|
||||
-- Variable to store which waypoint to go to next (persistent)
|
||||
if (not self.data.next_waypoint) then self.data.next_waypoint = 1 end
|
||||
return function(cfg)
|
||||
-- Calculate next waypoint and rating for all messengers
|
||||
-- Return next messenger to move and waypoint toward which it should move
|
||||
-- Also return the array of all messengers (for escort move evaluation,
|
||||
-- so that it only needs to be done in one place, in case the syntax is changed some more)
|
||||
-- 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 messengers = wesnoth.get_units { side = wesnoth.current.side, { "and", filter } }
|
||||
if (not messengers[1]) then return end
|
||||
|
||||
local waypoint_x = AH.split(cfg.waypoint_x, ",")
|
||||
local waypoint_y = AH.split(cfg.waypoint_y, ",")
|
||||
|
@ -12,14 +21,39 @@ return function(messenger, cfg, self)
|
|||
waypoint_y[i] = tonumber(waypoint_y[i])
|
||||
end
|
||||
|
||||
-- If we're within 3 hexes of the next waypoint, we go on to the one after that
|
||||
-- except if that one's the last one already
|
||||
local dist_wp = H.distance_between(messenger.x, messenger.y,
|
||||
waypoint_x[self.data.next_waypoint], waypoint_y[self.data.next_waypoint]
|
||||
)
|
||||
if (dist_wp <= 3) and (self.data.next_waypoint < #waypoint_x) then
|
||||
self.data.next_waypoint = self.data.next_waypoint + 1
|
||||
-- Set the next waypoint for all messengers
|
||||
-- Also find those with MP left and return the one to next move, together with the WP to move toward
|
||||
local max_rating, best_messenger, x, y = -9e99
|
||||
for i, m in ipairs(messengers) do
|
||||
-- To avoid code duplication and ensure consistency, we store some pieces of
|
||||
-- information in the messenger units, even though it could be calculated each time it is needed
|
||||
local wp_i = MAIUV.get_mai_unit_variables(m, cfg.ai_id, "wp_i") or 1
|
||||
local wp_x, wp_y = waypoint_x[wp_i], waypoint_y[wp_i]
|
||||
|
||||
-- If this messenger is within 3 hexes of the next waypoint, we go on to the one after that
|
||||
-- except if that one's the last one already
|
||||
local dist_wp = H.distance_between(m.x, m.y, wp_x, wp_y)
|
||||
if (dist_wp <= 3) and (wp_i < #waypoint_x) then wp_i = wp_i + 1 end
|
||||
|
||||
-- Also store the rating for each messenger
|
||||
-- For now, this is simply a "forward rating"
|
||||
local rating = wp_i - dist_wp / 1000.
|
||||
|
||||
-- If invert_order= key is set, we want to move the rearmost messenger first.
|
||||
-- We still want to keep the rating value positive (mostly, this is not strict)
|
||||
-- and of the same order of magnitude.
|
||||
if cfg.invert_order then
|
||||
rating = #waypoint_x - rating
|
||||
end
|
||||
|
||||
MAIUV.set_mai_unit_variables(m, cfg.ai_id, { wp_i = wp_i, wp_x = wp_x, wp_y = wp_y, wp_rating = rating })
|
||||
|
||||
-- Find the messenger with the highest rating that has MP left
|
||||
if (m.moves > 0) and (rating > max_rating) then
|
||||
best_messenger, max_rating = m, rating
|
||||
x, y = wp_x, wp_y
|
||||
end
|
||||
end
|
||||
|
||||
return waypoint_x[self.data.next_waypoint], waypoint_y[self.data.next_waypoint]
|
||||
return best_messenger, x, y, messengers
|
||||
end
|
||||
|
|
|
@ -6,10 +6,10 @@ local ca_messenger_move = {}
|
|||
local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f_next_waypoint.lua"
|
||||
|
||||
function ca_messenger_move:evaluation(ai, cfg)
|
||||
-- Move the messenger (unit with passed id) toward goal, attack adjacent unit if possible
|
||||
-- Move the messenger toward goal, attack adjacent unit if possible
|
||||
-- without retaliation or little expected damage with high chance of killing the enemy
|
||||
|
||||
local messenger = wesnoth.get_units{ id = cfg.id, formula = '$this_unit.moves > 0' }[1]
|
||||
local messenger = messenger_next_waypoint(cfg)
|
||||
|
||||
if messenger then
|
||||
return cfg.ca_score
|
||||
|
@ -17,25 +17,36 @@ function ca_messenger_move:evaluation(ai, cfg)
|
|||
return 0
|
||||
end
|
||||
|
||||
function ca_messenger_move:execution(ai, cfg, self)
|
||||
local messenger = wesnoth.get_units{ id = cfg.id, formula = '$this_unit.moves > 0' }[1]
|
||||
function ca_messenger_move:execution(ai, cfg)
|
||||
local messenger, x, y = messenger_next_waypoint(cfg)
|
||||
|
||||
local x, y = messenger_next_waypoint(messenger, cfg, self)
|
||||
if (messenger.x ~= x) or (messenger.y ~= y) then
|
||||
x, y = wesnoth.find_vacant_tile( x, y, messenger)
|
||||
local wp = AH.get_closest_location(
|
||||
{ x, y },
|
||||
{ { "not", { { "filter", { { "not", { side = wesnoth.current.side } } } } } } },
|
||||
messenger
|
||||
)
|
||||
x, y = wp[1], wp[2]
|
||||
end
|
||||
local next_hop = AH.next_hop(messenger, x, y)
|
||||
|
||||
local next_hop = AH.next_hop(messenger, x, y, { ignore_own_units = true } )
|
||||
if (not next_hop) then next_hop = { messenger.x, messenger.y } end
|
||||
|
||||
-- Compare this to the "ideal path"
|
||||
local path, cost = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })
|
||||
local opt_hop, opt_cost = {messenger.x, messenger.y}, 0
|
||||
local opt_hop, opt_cost = { messenger.x, messenger.y }, 0
|
||||
for i, p in ipairs(path) do
|
||||
local sub_path, sub_cost = wesnoth.find_path(messenger, p[1], p[2])
|
||||
if sub_cost > messenger.moves then
|
||||
if sub_cost > messenger.moves then
|
||||
break
|
||||
else
|
||||
local unit_in_way = wesnoth.get_unit(p[1], p[2])
|
||||
|
||||
if unit_in_way and (unit_in_way.side == messenger.side) then
|
||||
local reach = AH.get_reachable_unocc(unit_in_way)
|
||||
if (reach:size() > 1) then unit_in_way = nil end
|
||||
end
|
||||
|
||||
if not unit_in_way then
|
||||
opt_hop, nh_cost = p, sub_cost
|
||||
end
|
||||
|
@ -45,11 +56,24 @@ function ca_messenger_move:execution(ai, cfg, self)
|
|||
--print(next_hop[1], next_hop[2], opt_hop[1], opt_hop[2])
|
||||
-- Now compare how long it would take from the end of both of these options
|
||||
local x1, y1 = messenger.x, messenger.y
|
||||
|
||||
local unit_in_way = wesnoth.get_unit(next_hop[1], next_hop[2])
|
||||
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)
|
||||
local tmp, cost1 = wesnoth.find_path(messenger, x, y, {ignore_units = 'yes'})
|
||||
|
||||
local unit_in_way2 = wesnoth.get_unit(opt_hop[1], opt_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(opt_hop[1], opt_hop[2], messenger)
|
||||
local tmp, cost2 = wesnoth.find_path(messenger, x, y, {ignore_units = 'yes'})
|
||||
|
||||
wesnoth.put_unit(x1, y1, messenger)
|
||||
if unit_in_way then wesnoth.put_unit(unit_in_way) end
|
||||
if unit_in_way2 then wesnoth.put_unit(unit_in_way2) end
|
||||
--print(cost1, cost2)
|
||||
|
||||
-- If cost2 is significantly less, that means that the other path might overall be faster
|
||||
|
@ -58,17 +82,23 @@ function ca_messenger_move:execution(ai, cfg, self)
|
|||
--print(next_hop[1], next_hop[2])
|
||||
|
||||
if next_hop and ((next_hop[1] ~= messenger.x) or (next_hop[2] ~= messenger.y)) then
|
||||
ai.move(messenger, next_hop[1], next_hop[2])
|
||||
local unit_in_way = wesnoth.get_unit(next_hop[1], next_hop[2])
|
||||
if unit_in_way then AH.move_unit_out_of_way(ai, unit_in_way) end
|
||||
if (not messenger) or (not messenger.valid) then return end
|
||||
|
||||
AH.checked_move(ai, messenger, next_hop[1], next_hop[2])
|
||||
else
|
||||
ai.stopunit_moves(messenger)
|
||||
AH.checked_stopunit_moves(ai, messenger)
|
||||
end
|
||||
if (not messenger) or (not messenger.valid) then return end
|
||||
|
||||
-- We also test whether an attack without retaliation or with little damage is possible
|
||||
if (messenger.attacks_left <= 0) then return end
|
||||
if (not H.get_child(messenger.__cfg, 'attack')) then return end
|
||||
|
||||
local targets = wesnoth.get_units {
|
||||
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
|
||||
{ "filter_adjacent", { id = cfg.id } }
|
||||
{ "filter_adjacent", { id = messenger.id } }
|
||||
}
|
||||
|
||||
local max_rating, best_tar, best_weapon = -9e99, {}, -1
|
||||
|
@ -100,7 +130,7 @@ function ca_messenger_move:execution(ai, cfg, self)
|
|||
end
|
||||
|
||||
if max_rating > -9e99 then
|
||||
ai.attack(messenger, best_tar, best_weapon)
|
||||
AH.checked_attack(ai, messenger, best_tar, best_weapon)
|
||||
else
|
||||
-- Otherwise, always attack enemy on last waypoint
|
||||
local waypoint_x = AH.split(cfg.waypoint_x, ",")
|
||||
|
@ -109,16 +139,17 @@ function ca_messenger_move:execution(ai, cfg, self)
|
|||
x = tonumber(waypoint_x[#waypoint_x]),
|
||||
y = tonumber(waypoint_y[#waypoint_y]),
|
||||
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
|
||||
{ "filter_adjacent", { id = cfg.id } }
|
||||
{ "filter_adjacent", { id = messenger.id } }
|
||||
}[1]
|
||||
|
||||
if target then
|
||||
ai.attack(messenger, target)
|
||||
AH.checked_attack(ai, messenger, target)
|
||||
end
|
||||
end
|
||||
if (not messenger) or (not messenger.valid) then return end
|
||||
|
||||
-- Finally, make sure unit is really done after this
|
||||
ai.stopunit_attacks(messenger)
|
||||
AH.checked_stopunit_attacks(ai, messenger)
|
||||
end
|
||||
|
||||
return ca_messenger_move
|
||||
|
|
|
@ -1,46 +1,40 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
|
||||
local ca_patrol = {}
|
||||
|
||||
function ca_patrol:evaluation(ai, cfg)
|
||||
local patrol
|
||||
if cfg.filter then
|
||||
patrol = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
else
|
||||
patrol = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
|
||||
end
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local patrol = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
-- Check if unit exists as sticky BCAs are not always removed successfully
|
||||
if patrol then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
function ca_patrol:execution(ai, cfg, self)
|
||||
local patrol
|
||||
if cfg.filter then
|
||||
patrol = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
else
|
||||
patrol = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
|
||||
end
|
||||
function ca_patrol:execution(ai, cfg)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local patrol = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
cfg.waypoint_x = AH.split(cfg.waypoint_x, ",")
|
||||
cfg.waypoint_y = AH.split(cfg.waypoint_y, ",")
|
||||
|
||||
local n_wp = #cfg.waypoint_x -- just for convenience
|
||||
|
||||
local patrol_vars = MAIUV.get_mai_unit_variables(patrol, cfg.ai_id)
|
||||
|
||||
-- Set up waypoints, taking into account whether 'reverse' is set
|
||||
-- This works even the first time, when self.data.id_reverse is not set yet
|
||||
-- This works even the first time, when patrol_vars.patrol_reverse is not set yet
|
||||
local waypoints = {}
|
||||
if self.data[patrol.id..'_reverse'] then
|
||||
if patrol_vars.patrol_reverse then
|
||||
for i = 1,n_wp do
|
||||
waypoints[i] = { tonumber(cfg.waypoint_x[n_wp-i+1]), tonumber(cfg.waypoint_y[n_wp-i+1]) }
|
||||
end
|
||||
|
@ -50,12 +44,13 @@ function ca_patrol:execution(ai, cfg, self)
|
|||
end
|
||||
end
|
||||
|
||||
-- if not set, set next location (first move)
|
||||
-- If not set, set next location (first move)
|
||||
-- This needs to be in WML format, so that it persists over save/load cycles
|
||||
if (not self.data[patrol.id..'_x']) then
|
||||
self.data[patrol.id..'_x'] = waypoints[1][1]
|
||||
self.data[patrol.id..'_y'] = waypoints[1][2]
|
||||
self.data[patrol.id..'_reverse'] = false
|
||||
if (not patrol_vars.patrol_x) then
|
||||
patrol_vars.patrol_x = waypoints[1][1]
|
||||
patrol_vars.patrol_y = waypoints[1][2]
|
||||
patrol_vars.patrol_reverse = false
|
||||
MAIUV.set_mai_unit_variables(patrol, cfg.ai_id, patrol_vars)
|
||||
end
|
||||
|
||||
while patrol.moves > 0 do
|
||||
|
@ -63,16 +58,16 @@ function ca_patrol:execution(ai, cfg, self)
|
|||
-- If so, don't move, but attack that enemy
|
||||
local enemies = wesnoth.get_units {
|
||||
id = cfg.attack,
|
||||
{ "filter_adjacent", { id = cfg.id } },
|
||||
{ "filter_adjacent", { id = patrol.id } },
|
||||
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
|
||||
}
|
||||
if next(enemies) then break end
|
||||
|
||||
-- Also check whether we're next to any unit (enemy or ally) which is on the next waypoint
|
||||
local unit_on_wp = wesnoth.get_units {
|
||||
x = self.data[patrol.id..'_x'],
|
||||
y = self.data[patrol.id..'_y'],
|
||||
{ "filter_adjacent", { id = cfg.id } }
|
||||
x = patrol_vars.patrol_x,
|
||||
y = patrol_vars.patrol_y,
|
||||
{ "filter_adjacent", { id = patrol.id } }
|
||||
}[1]
|
||||
|
||||
for i,wp in ipairs(waypoints) do
|
||||
|
@ -84,28 +79,32 @@ function ca_patrol:execution(ai, cfg, self)
|
|||
-- Move him to the first one (or reverse route), if he's on the last waypoint
|
||||
-- Unless cfg.one_time_only is set
|
||||
if cfg.one_time_only then
|
||||
self.data[patrol.id..'_x'] = waypoints[n_wp][1]
|
||||
self.data[patrol.id..'_y'] = waypoints[n_wp][2]
|
||||
patrol_vars.patrol_x = waypoints[n_wp][1]
|
||||
patrol_vars.patrol_y = waypoints[n_wp][2]
|
||||
MAIUV.set_mai_unit_variables(patrol, cfg.ai_id, patrol_vars)
|
||||
else
|
||||
-- Go back to first WP or reverse direction
|
||||
if cfg.out_and_back then
|
||||
self.data[patrol.id..'_x'] = waypoints[n_wp-1][1]
|
||||
self.data[patrol.id..'_y'] = waypoints[n_wp-1][2]
|
||||
|
||||
patrol_vars.patrol_x = waypoints[n_wp-1][1]
|
||||
patrol_vars.patrol_y = waypoints[n_wp-1][2]
|
||||
-- We also need to reverse the waypoints right here, as this might not be the end of the move
|
||||
self.data[patrol.id..'_reverse'] = not self.data[patrol.id..'_reverse']
|
||||
patrol_vars.patrol_reverse = not patrol_vars.patrol_reverse
|
||||
MAIUV.set_mai_unit_variables(patrol, cfg.ai_id, patrol_vars)
|
||||
|
||||
local tmp_wp = {}
|
||||
for i,wp in ipairs(waypoints) do tmp_wp[n_wp-i+1] = wp end
|
||||
waypoints = tmp_wp
|
||||
else
|
||||
self.data[patrol.id..'_x'] = waypoints[1][1]
|
||||
self.data[patrol.id..'_y'] = waypoints[1][2]
|
||||
patrol_vars.patrol_x = waypoints[1][1]
|
||||
patrol_vars.patrol_y = waypoints[1][2]
|
||||
MAIUV.set_mai_unit_variables(patrol, cfg.ai_id, patrol_vars)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- ... else move him on the next waypoint
|
||||
self.data[patrol.id..'_x'] = waypoints[i+1][1]
|
||||
self.data[patrol.id..'_y'] = waypoints[i+1][2]
|
||||
patrol_vars.patrol_x = waypoints[i+1][1]
|
||||
patrol_vars.patrol_y = waypoints[i+1][2]
|
||||
MAIUV.set_mai_unit_variables(patrol, cfg.ai_id, patrol_vars)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -114,16 +113,17 @@ function ca_patrol:execution(ai, cfg, self)
|
|||
if cfg.one_time_only and
|
||||
(patrol.x == waypoints[n_wp][1]) and (patrol.y == waypoints[n_wp][2])
|
||||
then
|
||||
ai.stopunit_moves(patrol)
|
||||
AH.checked_stopunit_moves(ai, patrol)
|
||||
else -- otherwise move toward next WP
|
||||
local x, y = wesnoth.find_vacant_tile(self.data[patrol.id..'_x'], self.data[patrol.id..'_y'], patrol)
|
||||
local x, y = wesnoth.find_vacant_tile(patrol_vars.patrol_x, patrol_vars.patrol_y, patrol)
|
||||
local nh = AH.next_hop(patrol, x, y)
|
||||
if nh and ((nh[1] ~= patrol.x) or (nh[2] ~= patrol.y)) then
|
||||
ai.move(patrol, nh[1], nh[2])
|
||||
AH.checked_move(ai, patrol, nh[1], nh[2])
|
||||
else
|
||||
ai.stopunit_moves(patrol)
|
||||
AH.checked_stopunit_moves(ai, patrol)
|
||||
end
|
||||
end
|
||||
if (not patrol) or (not patrol.valid) then return end
|
||||
end
|
||||
|
||||
-- Attack unit on the last waypoint under all circumstances if cfg.one_time_only is set
|
||||
|
@ -132,7 +132,7 @@ function ca_patrol:execution(ai, cfg, self)
|
|||
enemies = wesnoth.get_units{
|
||||
x = waypoints[n_wp][1],
|
||||
y = waypoints[n_wp][2],
|
||||
{ "filter_adjacent", { id = cfg.id } },
|
||||
{ "filter_adjacent", { id = patrol.id } },
|
||||
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
|
||||
}
|
||||
end
|
||||
|
@ -141,20 +141,20 @@ function ca_patrol:execution(ai, cfg, self)
|
|||
if (not next(enemies)) then
|
||||
enemies = wesnoth.get_units{
|
||||
id = cfg.attack,
|
||||
{ "filter_adjacent", { id = cfg.id } },
|
||||
{ "filter_adjacent", { id = patrol.id } },
|
||||
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
|
||||
}
|
||||
end
|
||||
|
||||
if next(enemies) then
|
||||
for i,v in ipairs(enemies) do
|
||||
ai.attack(patrol, v)
|
||||
AH.checked_attack(ai, patrol, v)
|
||||
break
|
||||
end
|
||||
end
|
||||
if (not patrol) or (not patrol.valid) then return end
|
||||
|
||||
-- Check that patrol is not killed
|
||||
if patrol and patrol.valid then ai.stopunit_all(patrol) end
|
||||
AH.checked_stopunit_all(ai, patrol)
|
||||
end
|
||||
|
||||
return ca_patrol
|
||||
|
|
|
@ -127,7 +127,10 @@ function ca_protect_unit_attack:execution(ai, cfg, self)
|
|||
local defender = wesnoth.get_unit(self.data.best_attack.target.x, self.data.best_attack.target.y)
|
||||
|
||||
AH.movefull_stopunit(ai, attacker, self.data.best_attack.dst.x, self.data.best_attack.dst.y)
|
||||
ai.attack(attacker, defender)
|
||||
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.best_attack = nil
|
||||
end
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ function ca_protect_unit_move:execution(ai, cfg, self)
|
|||
end)
|
||||
--AH.put_labels(GDM)
|
||||
|
||||
-- Configuration parameters (no option to change these enable at the moment)
|
||||
-- Configuration parameters (no option to change these enabled at the moment)
|
||||
local enemy_weight = self.data.enemy_weight or 100.
|
||||
local my_unit_weight = self.data.my_unit_weight or 1.
|
||||
local distance_weight = self.data.distance_weight or 3.
|
||||
|
|
|
@ -86,7 +86,7 @@ function ca_recruit_random:evaluation(ai, cfg)
|
|||
-- The point is that this will blacklist the CA if an unaffordable recruit was
|
||||
-- chosen -> no cheaper recruits will be selected in subsequent calls
|
||||
if (n_recruits > 0) then
|
||||
local rand_prob = AH.random(1e6)
|
||||
local rand_prob = math.random(1e6)
|
||||
for typ,pr in pairs(prob) do
|
||||
if (pr.p_i <= rand_prob) and (rand_prob < pr.p_f) then
|
||||
recruit = typ
|
||||
|
@ -103,7 +103,7 @@ end
|
|||
function ca_recruit_random:execution(ai, cfg)
|
||||
-- Let this function blacklist itself if the chosen recruit is too expensive
|
||||
if wesnoth.unit_types[recruit].cost <= wesnoth.sides[wesnoth.current.side].gold then
|
||||
ai.recruit(recruit)
|
||||
AH.checked_recruit(ai, recruit)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,18 +3,13 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
|||
local ca_return_guardian = {}
|
||||
|
||||
function ca_return_guardian:evaluation(ai, cfg)
|
||||
local unit
|
||||
if cfg.filter then
|
||||
unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
else
|
||||
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
|
||||
end
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
-- Check if unit exists as sticky BCAs are not always removed successfully
|
||||
if unit then
|
||||
if ((unit.x ~= cfg.return_x) or (unit.y ~= cfg.return_y)) then
|
||||
return cfg.ca_score
|
||||
|
@ -26,17 +21,12 @@ function ca_return_guardian:evaluation(ai, cfg)
|
|||
end
|
||||
|
||||
function ca_return_guardian:execution(ai, cfg)
|
||||
local unit
|
||||
if cfg.filter then
|
||||
unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
else
|
||||
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
|
||||
end
|
||||
--print("Exec guardian move",unit.id)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
-- In case the return hex is occupied:
|
||||
local x, y = cfg.return_x, cfg.return_y
|
||||
|
@ -45,9 +35,7 @@ function ca_return_guardian:execution(ai, cfg)
|
|||
end
|
||||
|
||||
local nh = AH.next_hop(unit, x, y)
|
||||
if unit.moves~=0 then
|
||||
AH.movefull_stopunit(ai, unit, nh)
|
||||
end
|
||||
AH.movefull_stopunit(ai, unit, nh)
|
||||
end
|
||||
|
||||
return ca_return_guardian
|
||||
|
|
|
@ -79,7 +79,10 @@ function ca_simple_attack:execution(ai, cfg, self)
|
|||
local defender = wesnoth.get_unit(self.data.attack.target.x, self.data.attack.target.y)
|
||||
|
||||
AH.movefull_outofway_stopunit(ai, attacker, self.data.attack.dst.x, self.data.attack.dst.y)
|
||||
ai.attack(attacker, defender, (cfg.weapon or -1))
|
||||
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.attack = nil
|
||||
end
|
||||
|
||||
|
|
|
@ -4,18 +4,13 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
|||
local ca_stationed_guardian = {}
|
||||
|
||||
function ca_stationed_guardian:evaluation(ai, cfg)
|
||||
local unit
|
||||
if cfg.filter then
|
||||
unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
else
|
||||
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
|
||||
end
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
-- Check if unit exists as sticky BCAs are not always removed successfully
|
||||
if unit then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
@ -24,16 +19,12 @@ function ca_stationed_guardian:execution(ai, cfg)
|
|||
-- (s_x,s_y): coordinates where unit is stationed; tries to move here if there is nobody to attack
|
||||
-- (g_x,g_y): location that the unit guards
|
||||
|
||||
local unit
|
||||
if cfg.filter then
|
||||
unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
else
|
||||
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
|
||||
end
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
-- find if there are enemies within 'distance'
|
||||
local enemies = wesnoth.get_units {
|
||||
|
@ -44,7 +35,7 @@ function ca_stationed_guardian:execution(ai, cfg)
|
|||
-- if no enemies are within 'distance': keep unit from doing anything and exit
|
||||
if not enemies[1] then
|
||||
--print("No enemies close -> sleeping:",unit.id)
|
||||
ai.stopunit_moves(unit)
|
||||
AH.checked_stopunit_moves(ai, unit)
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -92,9 +83,9 @@ function ca_stationed_guardian:execution(ai, cfg)
|
|||
if (best_defense ~= -9e99) then
|
||||
--print("Attack at:",attack_loc[1],attack_loc[2],best_defense)
|
||||
AH.movefull_stopunit(ai, unit, attack_loc)
|
||||
-- There should be an ai.check_attack_action() here in case something weird is
|
||||
-- done in a 'moveto' event.
|
||||
ai.attack(unit, target)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
if (not target) or (not target.valid) then return end
|
||||
AH.checked_attack(ai, unit, target)
|
||||
else -- otherwise move toward that enemy
|
||||
--print("Cannot reach target, moving toward it")
|
||||
local reach = wesnoth.find_reach(unit)
|
||||
|
@ -125,9 +116,9 @@ function ca_stationed_guardian:execution(ai, cfg)
|
|||
AH.movefull_stopunit(ai, unit, nh)
|
||||
end
|
||||
|
||||
-- Get unit again, just in case something was done to it in a 'moveto' or 'attack' event
|
||||
local unit = wesnoth.get_units{ id = cfg.id }[1]
|
||||
if unit then ai.stopunit_moves(unit) end
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
|
||||
AH.checked_stopunit_moves(ai, unit)
|
||||
-- If there are attacks left and unit ended up next to an enemy, we'll leave this to RCA AI
|
||||
end
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ function swarm_move:execution(ai, cfg)
|
|||
--print('#units, #units_no_moves, #enemies', #units, #units_no_moves, #enemies)
|
||||
|
||||
-- pick a random unit and remove it from 'units'
|
||||
local rand = AH.random(#units)
|
||||
local rand = math.random(#units)
|
||||
local unit = units[rand]
|
||||
table.remove(units, rand)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local W = H.set_wml_action_metatable {}
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
local WMPF = wesnoth.require "ai/micro_ais/cas/ca_wolves_multipacks_functions.lua"
|
||||
|
||||
|
@ -8,7 +9,6 @@ local ca_wolves_multipacks_attack = {}
|
|||
|
||||
function ca_wolves_multipacks_attack:evaluation(ai, cfg)
|
||||
local unit_type = cfg.type or "Wolf"
|
||||
|
||||
-- If wolves have attacks left, call this CA
|
||||
-- It will generally be disabled by being black-listed, so as to avoid
|
||||
-- having to do the full attack evaluation for every single move
|
||||
|
@ -26,7 +26,6 @@ function ca_wolves_multipacks_attack:execution(ai, cfg)
|
|||
-- and I want all wolves in a pack to move first, before going on to the next pack
|
||||
-- which makes this slightly more complicated than it would be otherwise
|
||||
for pack_number,pack in pairs(packs) do
|
||||
|
||||
local keep_attacking_this_pack = true -- whether there might be attacks left
|
||||
local pack_attacked = false -- whether an attack by the pack has happened
|
||||
|
||||
|
@ -97,10 +96,13 @@ function ca_wolves_multipacks_attack:execution(ai, cfg)
|
|||
-- the same target for all wolves of the pack)
|
||||
for x, y in H.adjacent_tiles(target.x, target.y) do
|
||||
local adj_unit = wesnoth.get_unit(x, y)
|
||||
if adj_unit and (adj_unit.variables.pack == pack_number)
|
||||
and (adj_unit.side == wesnoth.current.side) and (adj_unit.attacks_left == 0)
|
||||
then
|
||||
rating = rating + 10 -- very strongly favors this target
|
||||
if adj_unit then
|
||||
local pack = MAIUV.get_mai_unit_variables(adj_unit, cfg.ai_id, "pack")
|
||||
if (pack == pack_number) and (adj_unit.side == wesnoth.current.side)
|
||||
and (adj_unit.attacks_left == 0)
|
||||
then
|
||||
rating = rating + 10 -- very strongly favors this target
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -136,7 +138,7 @@ function ca_wolves_multipacks_attack:execution(ai, cfg)
|
|||
end
|
||||
|
||||
local a_x, a_y, d_x, d_y = attacker.x, attacker.y, defender.x, defender.y
|
||||
ai.attack(attacker, defender)
|
||||
AH.checked_attack(ai, attacker, defender)
|
||||
-- Remove the labels, if one of the units died
|
||||
if cfg.show_pack_number then
|
||||
if (not attacker.valid) then W.label { x = a_x, y = a_y, text = "" } end
|
||||
|
@ -182,7 +184,7 @@ function ca_wolves_multipacks_attack:execution(ai, cfg)
|
|||
W.label { x = w.x, y = w.y, text = "" }
|
||||
end
|
||||
AH.movefull_stopunit(ai, w, best_hex)
|
||||
if cfg.show_pack_number then
|
||||
if cfg.show_pack_number and w and w.valid then
|
||||
WMPF.color_label(w.x, w.y, pack_number)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local W = H.set_wml_action_metatable {}
|
||||
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
|
||||
local wolves_multipacks_functions = {}
|
||||
|
||||
|
@ -25,9 +26,10 @@ function wolves_multipacks_functions.assign_packs(cfg)
|
|||
local packs = {}
|
||||
-- Find wolves that already have a pack number assigned
|
||||
for i,w in ipairs(wolves) do
|
||||
if w.variables.pack then
|
||||
if (not packs[w.variables.pack]) then packs[w.variables.pack] = {} end
|
||||
table.insert(packs[w.variables.pack], { x = w.x, y = w.y, id = w.id })
|
||||
local pack = MAIUV.get_mai_unit_variables(w, cfg.ai_id, "pack")
|
||||
if pack then
|
||||
if (not packs[pack]) then packs[pack] = {} end
|
||||
table.insert(packs[pack], { x = w.x, y = w.y, id = w.id })
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -37,7 +39,7 @@ function wolves_multipacks_functions.assign_packs(cfg)
|
|||
--print(' have pack:', k, ' #members:', #p)
|
||||
if (#p == 1) then
|
||||
local wolf = wesnoth.get_unit(p[1].x, p[1].y)
|
||||
wolf.variables.pack, wolf.variables.goal_x, wolf.variables.goal_y = nil, nil, nil
|
||||
MAIUV.delete_mai_unit_variables(wolf, cfg.ai_id)
|
||||
packs[k] = nil
|
||||
end
|
||||
end
|
||||
|
@ -47,10 +49,11 @@ function wolves_multipacks_functions.assign_packs(cfg)
|
|||
-- Wolves that are not in a pack (new ones or those removed above)
|
||||
local nopack_wolves = {}
|
||||
for i,w in ipairs(wolves) do
|
||||
if (not w.variables.pack) then
|
||||
local pack = MAIUV.get_mai_unit_variables(w, cfg.ai_id, "pack")
|
||||
if (not pack) then
|
||||
table.insert(nopack_wolves, w)
|
||||
-- Also erase any goal one of these might have
|
||||
w.variables.pack, w.variables.goal_x, w.variables.goal_y = nil, nil, nil
|
||||
MAIUV.delete_mai_unit_variables(w, cfg.ai_id)
|
||||
end
|
||||
end
|
||||
--print('#nopack_wolves:', #nopack_wolves)
|
||||
|
@ -70,7 +73,7 @@ function wolves_multipacks_functions.assign_packs(cfg)
|
|||
end
|
||||
if (min_dist < 9e99) then
|
||||
table.insert(packs[k], { x = best_wolf.x, y = best_wolf.y, id = best_wolf.id })
|
||||
best_wolf.variables.pack = k
|
||||
MAIUV.set_mai_unit_variables(best_wolf, cfg.ai_id, { pack = k })
|
||||
table.remove(nopack_wolves, best_ind)
|
||||
end
|
||||
end
|
||||
|
@ -93,7 +96,7 @@ function wolves_multipacks_functions.assign_packs(cfg)
|
|||
packs[new_pack] = {}
|
||||
for i,w in ipairs(nopack_wolves) do
|
||||
table.insert(packs[new_pack], { x = w.x, y = w.y, id = w.id })
|
||||
w.variables.pack = new_pack
|
||||
MAIUV.set_mai_unit_variables(w, cfg.ai_id, { pack = new_pack })
|
||||
end
|
||||
break
|
||||
end
|
||||
|
@ -121,7 +124,7 @@ function wolves_multipacks_functions.assign_packs(cfg)
|
|||
-- Need to count down for table.remove to work correctly
|
||||
for i = pack_size,1,-1 do
|
||||
table.insert(packs[new_pack], { x = best_wolves[i].x, y = best_wolves[i].y, id = best_wolves[i].id })
|
||||
best_wolves[i].variables.pack = new_pack
|
||||
MAIUV.set_mai_unit_variables(best_wolves[i], cfg.ai_id, { pack = new_pack })
|
||||
end
|
||||
end
|
||||
--print('After grouping remaining single wolves')
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local W = H.set_wml_action_metatable {}
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
local WMPF = wesnoth.require "ai/micro_ais/cas/ca_wolves_multipacks_functions.lua"
|
||||
|
||||
|
@ -27,9 +28,11 @@ function ca_wolves_multipacks_wander:execution(ai, cfg)
|
|||
local wolf = wesnoth.get_unit(loc.x, loc.y)
|
||||
--print(k, i, wolf.id)
|
||||
table.insert(wolves, wolf)
|
||||
|
||||
-- If any of the wolves in the pack has a goal set, we use that one
|
||||
if wolf.variables.goal_x then
|
||||
goal = { wolf.variables.goal_x, wolf.variables.goal_y }
|
||||
local wolf_goal = MAIUV.get_mai_unit_variables(wolf, cfg.ai_id)
|
||||
if wolf_goal.goal_x then
|
||||
goal = { wolf_goal.goal_x, wolf_goal.goal_y }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -39,7 +42,7 @@ function ca_wolves_multipacks_wander:execution(ai, cfg)
|
|||
end
|
||||
|
||||
-- Pack gets a new goal if none exist or on any move with 10% random chance
|
||||
local r = AH.random(10)
|
||||
local r = math.random(10)
|
||||
if (not goal[1]) or (r == 1) then
|
||||
local w,h,b = wesnoth.get_map_size()
|
||||
local locs = {}
|
||||
|
@ -49,7 +52,7 @@ function ca_wolves_multipacks_wander:execution(ai, cfg)
|
|||
-- We only check whether the first wolf can get there
|
||||
local unreachable = true
|
||||
while unreachable do
|
||||
local rand = AH.random(#locs)
|
||||
local rand = math.random(#locs)
|
||||
local next_hop = AH.next_hop(wolves[1], locs[rand][1], locs[rand][2])
|
||||
if next_hop then
|
||||
goal = { locs[rand][1], locs[rand][2] }
|
||||
|
@ -61,7 +64,7 @@ function ca_wolves_multipacks_wander:execution(ai, cfg)
|
|||
|
||||
-- This goal is saved with every wolf of the pack
|
||||
for i,w in ipairs(wolves) do
|
||||
w.variables.goal_x, w.variables.goal_y = goal[1], goal[2]
|
||||
MAIUV.insert_mai_unit_variables(w, cfg.ai_id, { goal_x = goal[1], goal_y = goal[2] })
|
||||
end
|
||||
|
||||
-- The pack wanders with only 2 considerations
|
||||
|
@ -132,7 +135,7 @@ function ca_wolves_multipacks_wander:execution(ai, cfg)
|
|||
W.label { x = w.x, y = w.y, text = "" }
|
||||
end
|
||||
AH.movefull_stopunit(ai, w, best_hex)
|
||||
if cfg.show_pack_number then
|
||||
if cfg.show_pack_number and w and w.valid then
|
||||
WMPF.color_label(w.x, w.y, k)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,7 +37,7 @@ function ca_wolves_wander:execution(ai, cfg)
|
|||
|
||||
local max_rating, goal_hex = -9e99, {}
|
||||
reach_map:iter( function (x, y, v)
|
||||
local rating = v + AH.random(99)/100.
|
||||
local rating = v + math.random(99)/100.
|
||||
if avoid:get(x, y) then rating = rating - 1000 end
|
||||
|
||||
if (rating > max_rating) then
|
||||
|
|
|
@ -5,33 +5,24 @@ local LS = wesnoth.require "lua/location_set.lua"
|
|||
local ca_zone_guardian = {}
|
||||
|
||||
function ca_zone_guardian:evaluation(ai, cfg)
|
||||
local unit
|
||||
if cfg.filter then
|
||||
unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
else
|
||||
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
|
||||
end
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
-- Check if unit exists as sticky BCAs are not always removed successfully
|
||||
if unit then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
function ca_zone_guardian:execution(ai, cfg)
|
||||
local unit
|
||||
if cfg.filter then
|
||||
unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
else
|
||||
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
|
||||
end
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
local reach = wesnoth.find_reach(unit)
|
||||
local zone_enemy = cfg.filter_location_enemy or cfg.filter_location
|
||||
|
@ -84,7 +75,8 @@ function ca_zone_guardian:execution(ai, cfg)
|
|||
if (best_defense ~= -9e99) then
|
||||
--print("Attack at:",attack_loc[1],attack_loc[2],best_defense)
|
||||
AH.movefull_stopunit(ai, unit, attack_loc)
|
||||
ai.attack(unit, target)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
AH.checked_attack(ai, unit, target)
|
||||
else -- otherwise move toward that enemy
|
||||
--print("Cannot reach target, moving toward it")
|
||||
local reach = wesnoth.find_reach(unit)
|
||||
|
@ -153,10 +145,9 @@ function ca_zone_guardian:execution(ai, cfg)
|
|||
AH.movefull_stopunit(ai, unit, nh)
|
||||
end
|
||||
end
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
|
||||
-- Get unit again, just in case something was done to it in a 'moveto' or 'attack' event
|
||||
local unit = wesnoth.get_units{ id = cfg.id }[1]
|
||||
if unit then ai.stopunit_moves(unit) end
|
||||
AH.checked_stopunit_moves(ai, unit)
|
||||
-- If there are attacks left and unit ended up next to an enemy, we'll leave this to RCA AI
|
||||
end
|
||||
|
||||
|
|
203
data/ai/micro_ais/micro_ai_helper.lua
Normal file
|
@ -0,0 +1,203 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local W = H.set_wml_action_metatable {}
|
||||
local AH = wesnoth.require("ai/lua/ai_helper.lua")
|
||||
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
|
||||
local micro_ai_helper = {}
|
||||
|
||||
function micro_ai_helper.add_CAs(side, 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
|
||||
--
|
||||
-- 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
|
||||
|
||||
local ai_id, id_found = CA_parms.ai_id, true
|
||||
|
||||
local n = 1
|
||||
while id_found do -- This is really just a precaution
|
||||
id_found = false
|
||||
|
||||
for ai_tag in H.child_range(wesnoth.sides[side].__cfg, 'ai') do
|
||||
for stage in H.child_range(ai_tag, 'stage') do
|
||||
for ca in H.child_range(stage, 'candidate_action') do
|
||||
if string.find(ca.name, ai_id .. '_') then
|
||||
id_found = true
|
||||
--print('---> found CA:', ca.name, ai_id, id_found, string.find(ca.name, ai_id))
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
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
|
||||
-- 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
|
||||
for engine in H.child_range(ai_tag, 'engine') do
|
||||
for data in H.child_range(engine, 'data') do
|
||||
for mai in H.child_range(data, 'micro_ai') do
|
||||
if (mai.ai_id == ai_id) then
|
||||
id_found = true
|
||||
print('---> found [micro_ai] tag with ai_id =', ai_id)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (id_found) then ai_id = CA_parms.ai_id .. n end
|
||||
n = n + 1
|
||||
end
|
||||
|
||||
-- Now add the CAs
|
||||
for i,parms in ipairs(CA_parms) do
|
||||
local ca_id = ai_id .. '_' .. parms.ca_id
|
||||
|
||||
-- Always pass the ai_id and ca_score to the eval/exec functions
|
||||
CA_cfg.ai_id = ai_id
|
||||
CA_cfg.ca_score = parms.score
|
||||
|
||||
local CA = {
|
||||
engine = "lua",
|
||||
id = ca_id,
|
||||
name = ca_id,
|
||||
max_score = parms.score
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
W.modify_ai {
|
||||
side = side,
|
||||
action = "add",
|
||||
path = "stage[main_loop].candidate_action",
|
||||
{ "candidate_action", CA }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function micro_ai_helper.delete_CAs(side, 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
|
||||
|
||||
for i,parms in ipairs(CA_parms) do
|
||||
local ca_id = CA_parms.ai_id .. '_' .. parms.ca_id
|
||||
|
||||
W.modify_ai {
|
||||
side = side,
|
||||
action = "try_delete",
|
||||
path = "stage[main_loop].candidate_action[" .. ca_id .. "]"
|
||||
}
|
||||
|
||||
-- Also need to delete variable stored in all units of the side, so that later MAIs can use these units
|
||||
local units = wesnoth.get_units { side = side }
|
||||
for i,u in ipairs(units) do
|
||||
MAIUV.delete_mai_unit_variables(u, CA_parms.ai_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function micro_ai_helper.add_aspects(side, aspect_parms)
|
||||
-- Add the aspects defined in @aspect_parms to the AI of @side
|
||||
-- @aspect_parms is an array of tables, one for each aspect to be added
|
||||
--
|
||||
-- Required keys for @aspect_parms:
|
||||
-- - aspect: the aspect name (e.g. 'attacks' or 'aggression')
|
||||
-- - facet: A table describing the facet to be added
|
||||
--
|
||||
-- Examples of facets:
|
||||
-- 1. Simple aspect, e.g. aggression
|
||||
-- { value = 0.99 }
|
||||
--
|
||||
-- 2. Composite aspect, e.g. attacks
|
||||
-- { name = "ai_default_rca::aspect_attacks",
|
||||
-- id = "dont_attack",
|
||||
-- invalidate_on_gamestate_change = "yes",
|
||||
-- { "filter_own", {
|
||||
-- type = "Dark Sorcerer"
|
||||
-- } }
|
||||
-- }
|
||||
|
||||
for i,parms in ipairs(aspect_parms) do
|
||||
W.modify_ai {
|
||||
side = side,
|
||||
action = "add",
|
||||
path = "aspect[" .. parms.aspect .. "].facet",
|
||||
{ "facet", parms.facet }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function micro_ai_helper.delete_aspects(side, aspect_parms)
|
||||
-- Delete the aspects defined in @aspect_parms from the AI of @side
|
||||
-- @aspect_parms is an array of tables, one for each aspect to be removed
|
||||
-- We can simply pass the one used for add_aspects(), although only the
|
||||
-- aspect_parms.aspect_id field is needed
|
||||
|
||||
for i,parms in ipairs(aspect_parms) do
|
||||
W.modify_ai {
|
||||
side = side,
|
||||
action = "try_delete",
|
||||
path = "aspect[attacks].facet[" .. parms.aspect_id .. "]"
|
||||
}
|
||||
end
|
||||
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)
|
||||
return
|
||||
end
|
||||
|
||||
-- Otherwise, set up the cfg table to be passed to the CA eval/exec functions
|
||||
local CA_cfg = {}
|
||||
|
||||
-- Required keys
|
||||
for k, 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)
|
||||
end
|
||||
CA_cfg[v] = cfg[v]
|
||||
if child then CA_cfg[v] = child end
|
||||
end
|
||||
|
||||
-- Optional keys
|
||||
for k, 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
|
||||
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 == 'change') then
|
||||
micro_ai_helper.delete_CAs(cfg.side, CA_parms, cfg.id)
|
||||
micro_ai_helper.add_CAs(cfg.side, CA_parms, CA_cfg)
|
||||
end
|
||||
end
|
||||
|
||||
return micro_ai_helper
|
97
data/ai/micro_ais/micro_ai_self_data.lua
Normal file
|
@ -0,0 +1,97 @@
|
|||
-- 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
|
||||
-- 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
|
||||
-- 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
|
||||
-- same side).
|
||||
-- For the time being, we only allow key=value style variables.
|
||||
|
||||
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
|
||||
-- @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
|
||||
-- if this is set for @action="delete", then only the keys in @vars_table are deleted,
|
||||
-- otherwise the entire [micro_ai] tag is deleted
|
||||
|
||||
-- Always delete the respective [micro_ai] tag, if it exists
|
||||
local existing_table
|
||||
for i,mai in ipairs(self_data, "micro_ai") do
|
||||
if (mai[1] == "micro_ai") and (mai[2].ai_id == ai_id) then
|
||||
if (action == "delete") and vars_table then
|
||||
for k,_ in pairs(vars_table) do
|
||||
mai[2][k] = nil
|
||||
end
|
||||
return
|
||||
end
|
||||
existing_table = mai[2]
|
||||
table.remove(self_data, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Then replace it, if the "set" action is selected
|
||||
-- or add the new keys to it, overwriting old ones with the same name, if action == "insert"
|
||||
if (action == "set") or (action == "insert") then
|
||||
local tag = { "micro_ai" }
|
||||
|
||||
if (not existing_table) or (action == "set") then
|
||||
-- Important: we need to make a copy of the input table, not use it!
|
||||
tag[2] = {}
|
||||
for k,v in pairs(vars_table) do tag[2][k] = v end
|
||||
tag[2].ai_id = ai_id
|
||||
else
|
||||
for k,v in pairs(vars_table) do existing_table[k] = v end
|
||||
tag[2] = existing_table
|
||||
end
|
||||
|
||||
table.insert(self_data, tag)
|
||||
end
|
||||
end
|
||||
|
||||
function micro_ai_self_data.delete_mai_self_data(self_data, ai_id, vars_table)
|
||||
micro_ai_self_data.modify_mai_self_data(self_data, ai_id, "delete", vars_table)
|
||||
end
|
||||
|
||||
function micro_ai_self_data.insert_mai_self_data(self_data, ai_id, vars_table)
|
||||
micro_ai_self_data.modify_mai_self_data(self_data, ai_id, "insert", vars_table)
|
||||
end
|
||||
|
||||
function micro_ai_self_data.set_mai_self_data(self_data, ai_id, vars_table)
|
||||
micro_ai_self_data.modify_mai_self_data(self_data, ai_id, "set", 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
|
||||
-- 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)
|
||||
-- - If no such tag is found: nil (if @key is set), otherwise empty table
|
||||
|
||||
for mai in H.child_range(self_data, "micro_ai") do
|
||||
if (mai.ai_id == ai_id) then
|
||||
if key then
|
||||
return mai[key]
|
||||
else
|
||||
return mai
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If we got here, no corresponding tag was found
|
||||
-- Return empty table; or nil if @key was set
|
||||
if (not key) then return {} end
|
||||
end
|
||||
|
||||
return micro_ai_self_data
|
89
data/ai/micro_ais/micro_ai_unit_variables.lua
Normal file
|
@ -0,0 +1,89 @@
|
|||
-- This set of functions provides a consistent way of storing Micro AI
|
||||
-- variables in units. They need to be stored inside a [micro_ai] tag in a
|
||||
-- unit's [variables] tag together with an ai_id= key, so that they can be
|
||||
-- removed when the Micro AI gets deleted. Otherwise subsequent Micro AIs used
|
||||
-- in the same scenario (or using the same units in later scenarios) might work
|
||||
-- incorrectly or not at all.
|
||||
-- Note that, with this method, there can only ever be one of these tags for each
|
||||
-- ai_ca in each unit (but of course several when there are several Micro AIs
|
||||
-- with different ai_CA values affecting the same unit)
|
||||
-- For the time being, we only allow key=value style variables.
|
||||
|
||||
local H = wesnoth.require "lua/helper.lua"
|
||||
|
||||
local micro_ai_unit_variables = {}
|
||||
|
||||
function micro_ai_unit_variables.modify_mai_unit_variables(unit, ai_id, action, vars_table)
|
||||
-- Modify [unit][variables][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 (not needed for @action="delete")
|
||||
|
||||
local unit_cfg = unit.__cfg
|
||||
local variables, ind = H.get_child(unit_cfg, "variables")
|
||||
|
||||
-- Always delete the respective [variables][micro_ai] tag, if it exists
|
||||
local existing_table
|
||||
for i,mai in ipairs(variables, "micro_ai") do
|
||||
if (mai[1] == "micro_ai") and (mai[2].ai_id == ai_id) then
|
||||
existing_table = mai[2]
|
||||
table.remove(variables, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Then replace it, if the "set" action is selected
|
||||
-- or add the new keys to it, overwriting old ones with the same name, if action == "insert"
|
||||
if (action == "set") or (action == "insert") then
|
||||
local tag = { "micro_ai" }
|
||||
|
||||
if (not existing_table) or (action == "set") then
|
||||
tag[2] = vars_table
|
||||
tag[2].ai_id = ai_id
|
||||
else
|
||||
for k,v in pairs(vars_table) do existing_table[k] = v end
|
||||
tag[2] = existing_table
|
||||
end
|
||||
|
||||
table.insert(unit_cfg[ind][2], tag)
|
||||
end
|
||||
|
||||
-- All of this so far was only on the table dump -> apply to unit
|
||||
wesnoth.put_unit(unit_cfg)
|
||||
end
|
||||
|
||||
function micro_ai_unit_variables.delete_mai_unit_variables(unit, ai_id)
|
||||
micro_ai_unit_variables.modify_mai_unit_variables(unit, ai_id, "delete")
|
||||
end
|
||||
|
||||
function micro_ai_unit_variables.insert_mai_unit_variables(unit, ai_id, vars_table)
|
||||
micro_ai_unit_variables.modify_mai_unit_variables(unit, ai_id, "insert", vars_table)
|
||||
end
|
||||
|
||||
function micro_ai_unit_variables.set_mai_unit_variables(unit, ai_id, vars_table)
|
||||
micro_ai_unit_variables.modify_mai_unit_variables(unit, ai_id, "set", vars_table)
|
||||
end
|
||||
|
||||
function micro_ai_unit_variables.get_mai_unit_variables(unit, ai_id, key)
|
||||
-- Get the content of [unit][variables][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)
|
||||
-- - If no such tag is found: nil (if @key is set), otherwise empty table
|
||||
|
||||
for mai in H.child_range(unit.variables.__cfg, "micro_ai") do
|
||||
if (mai.ai_id == ai_id) then
|
||||
if key then
|
||||
return mai[key]
|
||||
else
|
||||
return mai
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If we got here, no corresponding tag was found
|
||||
-- Return empty table; or nil if @key was set
|
||||
if (not key) then return {} end
|
||||
end
|
||||
|
||||
return micro_ai_unit_variables
|
|
@ -1,130 +1,10 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local W = H.set_wml_action_metatable {}
|
||||
local AH = wesnoth.require("ai/lua/ai_helper.lua")
|
||||
|
||||
local function add_CAs(side, 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)
|
||||
-- CA_cfg is a table with the parameters passed to the eval/exec functions
|
||||
--
|
||||
-- Required keys for CA_parms:
|
||||
-- - ca_id: is used for CA id/name and the eval/exec function names
|
||||
-- - score: the evaluation score
|
||||
|
||||
for i,parms in ipairs(CA_parms) do
|
||||
-- Make sure the id/name of each CA are unique.
|
||||
-- We do this by seeing if a CA by that name exists already.
|
||||
-- If not, we use the passed id in parms.ca_id
|
||||
-- If yes, we add a number to the end of parms.ca_id until we find an id that does not exist yet
|
||||
local ca_id, id_found = parms.ca_id, true
|
||||
|
||||
local n = 1
|
||||
while id_found do -- This is really just a precaution
|
||||
id_found = false
|
||||
|
||||
for ai_tag in H.child_range(wesnoth.sides[side].__cfg, 'ai') do
|
||||
for stage in H.child_range(ai_tag, 'stage') do
|
||||
for ca in H.child_range(stage, 'candidate_action') do
|
||||
if (ca.name == ca_id) then id_found = true end
|
||||
--print('---> found CA:', ca.name, id_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (id_found) then ca_id = parms.ca_id .. n end
|
||||
n = n+1
|
||||
end
|
||||
|
||||
-- Always pass the ca_id and ca_score to the eval/exec functions
|
||||
CA_cfg.ca_id = ca_id
|
||||
CA_cfg.ca_score = parms.score
|
||||
|
||||
local CA = {
|
||||
engine = "lua",
|
||||
id = ca_id,
|
||||
name = ca_id,
|
||||
max_score = parms.score
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
W.modify_ai {
|
||||
side = side,
|
||||
action = "add",
|
||||
path = "stage[main_loop].candidate_action",
|
||||
{ "candidate_action", CA }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function delete_CAs(side, 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
|
||||
|
||||
for i,parms in ipairs(CA_parms) do
|
||||
local ca_id = parms.ca_id
|
||||
|
||||
W.modify_ai {
|
||||
side = side,
|
||||
action = "try_delete",
|
||||
path = "stage[main_loop].candidate_action[" .. ca_id .. "]"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function add_aspects(side, aspect_parms)
|
||||
-- Add the aspects defined in 'aspect_parms' to the AI of 'side'
|
||||
-- aspect_parms is an array of tables, one for each aspect to be added
|
||||
--
|
||||
-- Required keys for aspect_parms:
|
||||
-- - aspect: the aspect name (e.g. 'attacks' or 'aggression')
|
||||
-- - facet: A table describing the facet to be added
|
||||
--
|
||||
-- Examples of facets:
|
||||
-- 1. Simple aspect, e.g. aggression
|
||||
-- { value = 0.99 }
|
||||
--
|
||||
-- 2. Composite aspect, e.g. attacks
|
||||
-- { name = "ai_default_rca::aspect_attacks",
|
||||
-- id = "dont_attack",
|
||||
-- invalidate_on_gamestate_change = "yes",
|
||||
-- { "filter_own", {
|
||||
-- type = "Dark Sorcerer"
|
||||
-- } }
|
||||
-- }
|
||||
|
||||
for i,parms in ipairs(aspect_parms) do
|
||||
W.modify_ai {
|
||||
side = side,
|
||||
action = "add",
|
||||
path = "aspect[" .. parms.aspect .. "].facet",
|
||||
{ "facet", parms.facet }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function delete_aspects(side, aspect_parms)
|
||||
-- Delete the aspects defined in 'aspect_parms' from the AI of 'side'
|
||||
-- aspect_parms is an array of tables, one for each CA to be removed
|
||||
-- We can simply pass the one used for add_aspects(), although only the
|
||||
-- aspect_parms.aspect_id field is needed
|
||||
|
||||
for i,parms in ipairs(aspect_parms) do
|
||||
W.modify_ai {
|
||||
side = side,
|
||||
action = "try_delete",
|
||||
path = "aspect[attacks].facet[" .. parms.aspect_id .. "]"
|
||||
}
|
||||
end
|
||||
end
|
||||
local MAIH = wesnoth.require("ai/micro_ais/micro_ai_helper.lua")
|
||||
|
||||
function wesnoth.wml_actions.micro_ai(cfg)
|
||||
-- Set up the [micro_ai] tag functionality for each Micro AI
|
||||
local CA_path = 'ai/micro_ais/cas/'
|
||||
|
||||
cfg = cfg.__parsed
|
||||
|
||||
-- Add translation for old-syntax animal MAIs to new syntax plus deprecation message
|
||||
|
@ -184,14 +64,15 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
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 = {
|
||||
{ ca_id = 'mai_healer_initialize', location = 'ai/micro_ais/cas/ca_healer_initialize.lua', score = 999990 },
|
||||
{ ca_id = 'mai_healer_move', location = 'ai/micro_ais/cas/ca_healer_move.lua', score = 105000 },
|
||||
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 = 'mai_healer_may_attack', location = 'ai/micro_ais/cas/ca_healer_may_attack.lua', score = 99990 })
|
||||
table.insert(CA_parms, { ca_id = 'may_attack', location = CA_path .. 'ca_healer_may_attack.lua', score = 99990 })
|
||||
end
|
||||
|
||||
--------- Bottleneck Defense Micro AI -----------------------------------
|
||||
|
@ -200,35 +81,44 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
optional_keys = { "healer_x", "healer_y", "leadership_x", "leadership_y", "active_side_leader" }
|
||||
local score = cfg.ca_score or 300000
|
||||
CA_parms = {
|
||||
{ ca_id = 'mai_bottleneck_move', location = 'ai/micro_ais/cas/ca_bottleneck_move.lua', score = score },
|
||||
{ ca_id = 'mai_bottleneck_attack', location = 'ai/micro_ais/cas/ca_bottleneck_attack.lua', score = score - 1 }
|
||||
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
|
||||
required_keys = { "id", "waypoint_x", "waypoint_y" }
|
||||
optional_keys = { "enemy_death_chance", "messenger_death_chance" }
|
||||
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 = {
|
||||
{ ca_id = 'mai_messenger_attack', location = 'ai/micro_ais/cas/ca_messenger_attack.lua', score = score },
|
||||
{ ca_id = 'mai_messenger_move', location = 'ai/micro_ais/cas/ca_messenger_move.lua', score = score - 1 },
|
||||
{ ca_id = 'mai_messenger_escort_move', location = 'ai/micro_ais/cas/ca_messenger_escort_move.lua', score = score - 2 }
|
||||
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 = { { ca_id = 'mai_lurkers', location = 'ai/micro_ais/cas/ca_lurkers.lua', score = cfg.ca_score or 300000 } }
|
||||
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 = {
|
||||
{ ca_id = 'mai_protect_unit_finish', location = 'ai/micro_ais/cas/ca_protect_unit_finish.lua', score = 300000 },
|
||||
{ ca_id = 'mai_protect_unit_attack', location = 'ai/micro_ais/cas/ca_protect_unit_attack.lua', score = 95000 },
|
||||
{ ca_id = 'mai_protect_unit_move', location = 'ai/micro_ais/cas/ca_protect_unit_move.lua', score = 94999 }
|
||||
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
|
||||
|
@ -286,7 +176,7 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
}
|
||||
|
||||
if (cfg.action == "delete") then
|
||||
delete_aspects(cfg.side, aspect_parms)
|
||||
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 {
|
||||
|
@ -302,76 +192,113 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
} }
|
||||
}
|
||||
else
|
||||
add_aspects(cfg.side, aspect_parms)
|
||||
MAIH.add_aspects(cfg.side, aspect_parms)
|
||||
end
|
||||
|
||||
--------- Micro AI Guardian -----------------------------------
|
||||
elseif (cfg.ai_type == 'stationed_guardian') then
|
||||
if (not cfg.id) and (not H.get_child(cfg, "filter")) 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", "guard_x", "guard_y" }
|
||||
optional_keys = { "id", "filter" }
|
||||
CA_parms = { { ca_id = 'mai_stationed_guardian', location = 'ai/micro_ais/cas/ca_stationed_guardian.lua', score = cfg.ca_score or 300000 } }
|
||||
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 (not cfg.id) and (not H.get_child(cfg, "filter")) 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 = { { ca_id = 'mai_zone_guardian', location = 'ai/micro_ais/cas/ca_zone_guardian.lua', score = cfg.ca_score or 300000 } }
|
||||
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 (not cfg.id) and (not H.get_child(cfg, "filter")) 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 = { { ca_id = 'mai_return_guardian', location = 'ai/micro_ais/cas/ca_return_guardian.lua', score = cfg.ca_score or 100010 } }
|
||||
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 (not cfg.id) and (not H.get_child(cfg, "filter")) 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 = { "id", "filter", "seek_x", "seek_y","avoid_x","avoid_y" }
|
||||
CA_parms = { { ca_id = 'mai_coward', location = 'ai/micro_ais/cas/ca_coward.lua', score = cfg.ca_score or 300000 } }
|
||||
optional_keys = { "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 = { { ca_id = "mai_big_animals", location = 'ai/micro_ais/cas/ca_big_animals.lua', score = cfg.ca_score or 300000 } }
|
||||
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 = { "avoid_type" }
|
||||
optional_keys = { "attack_only_prey", "avoid_type" }
|
||||
local score = cfg.ca_score or 90000
|
||||
CA_parms = {
|
||||
{ ca_id = "mai_wolves_move", location = 'ai/micro_ais/cas/ca_wolves_move.lua', score = score },
|
||||
{ ca_id = "mai_wolves_wander", location = 'ai/micro_ais/cas/ca_wolves_wander.lua', score = score - 1 }
|
||||
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 }
|
||||
}
|
||||
|
||||
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.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
|
||||
delete_aspects(cfg.side, wolves_aspects)
|
||||
else
|
||||
add_aspects(cfg.side, wolves_aspects)
|
||||
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
|
||||
|
@ -379,12 +306,13 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
optional_keys = { "attention_distance", "attack_distance" }
|
||||
local score = cfg.ca_score or 300000
|
||||
CA_parms = {
|
||||
{ ca_id = "mai_herding_attack_close_enemy", location = 'ai/micro_ais/cas/ca_herding_attack_close_enemy.lua', score = score },
|
||||
{ ca_id = "mai_herding_sheep_runs_enemy", location = 'ai/micro_ais/cas/ca_herding_sheep_runs_enemy.lua', score = score - 1 },
|
||||
{ ca_id = "mai_herding_sheep_runs_dog", location = 'ai/micro_ais/cas/ca_herding_sheep_runs_dog.lua', score = score - 2 },
|
||||
{ ca_id = "mai_herding_herd_sheep", location = 'ai/micro_ais/cas/ca_herding_herd_sheep.lua', score = score - 3 },
|
||||
{ ca_id = "mai_herding_sheep_move", location = 'ai/micro_ais/cas/ca_herding_sheep_move.lua', score = score - 4 },
|
||||
{ ca_id = "mai_herding_dog_move", location = 'ai/micro_ais/cas/ca_herding_dog_move.lua', score = score - 5 }
|
||||
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 }
|
||||
}
|
||||
|
||||
elseif (cfg.ai_type == 'forest_animals') then
|
||||
|
@ -393,66 +321,83 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
}
|
||||
local score = cfg.ca_score or 300000
|
||||
CA_parms = {
|
||||
{ ca_id = "mai_forest_animals_new_rabbit", location = 'ai/micro_ais/cas/ca_forest_animals_new_rabbit.lua', score = score },
|
||||
{ ca_id = "mai_forest_animals_tusker_attack", location = 'ai/micro_ais/cas/ca_forest_animals_tusker_attack.lua', score = score - 1 },
|
||||
{ ca_id = "mai_forest_animals_move", location = 'ai/micro_ais/cas/ca_forest_animals_move.lua', score = score - 2 },
|
||||
{ ca_id = "mai_forest_animals_tusklet_move", location = 'ai/micro_ais/cas/ca_forest_animals_tusklet_move.lua', score = score - 3 }
|
||||
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 = {
|
||||
{ ca_id = "mai_swarm_scatter", location = 'ai/micro_ais/cas/ca_swarm_scatter.lua', score = score },
|
||||
{ ca_id = "mai_swarm_move", location = 'ai/micro_ais/cas/ca_swarm_move.lua', score = score - 1 }
|
||||
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 = {
|
||||
{ ca_id = "mai_wolves_multipacks_attack", location = 'ai/micro_ais/cas/ca_wolves_multipacks_attack.lua', score = score },
|
||||
{ ca_id = "mai_wolves_multipacks_wander", location = 'ai/micro_ais/cas/ca_wolves_multipacks_wander.lua', score = score - 1 }
|
||||
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 (not cfg.id) and (not H.get_child(cfg, "filter")) 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 = { { ca_id = "mai_hunter", location = 'ai/micro_ais/cas/ca_hunter.lua', score = cfg.ca_score or 300000 } }
|
||||
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 (not cfg.id) and (not H.get_child(cfg, "filter")) 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 = { { ca_id = "mai_patrol", location = 'ai/micro_ais/cas/ca_patrol.lua', score = cfg.ca_score or 300000 } }
|
||||
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 = { { ca_id = "mai_rusher_recruit", location = 'ai/micro_ais/cas/ca_recruit_rushers.lua', score = cfg.ca_score or 180000 } }
|
||||
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 = { { ca_id = "mai_random_recruit", location = 'ai/micro_ais/cas/ca_recruit_random.lua', score = cfg.ca_score or 180000 } }
|
||||
CA_parms = {
|
||||
ai_id = 'mai_random_recruit',
|
||||
{ ca_id = "move", location = CA_path .. 'ca_recruit_random.lua', score = cfg.ca_score or 180000 }
|
||||
}
|
||||
|
||||
-- The 'probability' tags need to be handled separately here
|
||||
cfg.type, cfg.prob = {}, {}
|
||||
for p in H.child_range(cfg, "probability") do
|
||||
if (not p.type) then
|
||||
H.wml_error("Random Recruiting Micro AI [probability] tag is missing required type= key")
|
||||
if (cfg.action ~= 'delete') then
|
||||
-- The 'probability' tags need to be handled separately here
|
||||
cfg.type, cfg.prob = {}, {}
|
||||
for p in H.child_range(cfg, "probability") do
|
||||
if (not p.type) then
|
||||
H.wml_error("Random Recruiting Micro AI [probability] tag is missing required type= key")
|
||||
end
|
||||
if (not p.probability) then
|
||||
H.wml_error("Random Recruiting Micro AI [probability] tag is missing required probability= key")
|
||||
end
|
||||
table.insert(cfg.type, p.type)
|
||||
table.insert(cfg.prob, p.probability)
|
||||
end
|
||||
if (not p.probability) then
|
||||
H.wml_error("Random Recruiting Micro AI [probability] tag is missing required probability= key")
|
||||
end
|
||||
table.insert(cfg.type, p.type)
|
||||
table.insert(cfg.prob, p.probability)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -487,64 +432,31 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
"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 = { { ca_id = 'mai_goto', location = 'ai/micro_ais/cas/ca_goto.lua', score = cfg.ca_score or 300000 } }
|
||||
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 = { { ca_id = 'mai_hang_out', location = 'ai/micro_ais/cas/ca_hang_out.lua', score = cfg.ca_score or 170000 } }
|
||||
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 = { { ca_id = 'mai_simple_attack', location = 'ai/micro_ais/cas/ca_simple_attack.lua', score = cfg.ca_score or 110000 } }
|
||||
CA_parms = {
|
||||
ai_id = 'mai_simple_attack',
|
||||
{ ca_id = 'move', location = CA_path .. 'ca_simple_attack.lua', score = cfg.ca_score or 110000 }
|
||||
}
|
||||
|
||||
-- If we got here, none of the valid ai_types was specified
|
||||
else
|
||||
H.wml_error("unknown value for ai_type= in [micro_ai]")
|
||||
end
|
||||
|
||||
--------- Now go on to setting up the CAs ---------------------------------
|
||||
-- If cfg.ca_id is set, it gets added to the ca_id= key of all CAs
|
||||
-- This allows for selective removal of CAs
|
||||
if cfg.ca_id then
|
||||
for i,parms in ipairs(CA_parms) do
|
||||
-- Need to save eval_id first though
|
||||
parms.eval_id = parms.ca_id
|
||||
parms.ca_id = parms.ca_id .. '_' .. cfg.ca_id
|
||||
end
|
||||
end
|
||||
|
||||
-- If action=delete, we do that and are done
|
||||
if (cfg.action == 'delete') then
|
||||
delete_CAs(cfg.side, CA_parms)
|
||||
return
|
||||
end
|
||||
|
||||
-- Otherwise, set up the cfg table to be passed to the CA eval/exec functions
|
||||
local CA_cfg = {}
|
||||
|
||||
-- Required keys
|
||||
for k, 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)
|
||||
end
|
||||
CA_cfg[v] = cfg[v]
|
||||
if child then CA_cfg[v] = child end
|
||||
end
|
||||
|
||||
-- Optional keys
|
||||
for k, 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
|
||||
end
|
||||
|
||||
-- Finally, set up the candidate actions themselves
|
||||
if (cfg.action == 'add') then add_CAs(cfg.side, CA_parms, CA_cfg) end
|
||||
if (cfg.action == 'change') then
|
||||
delete_CAs(cfg.side, CA_parms, cfg.id)
|
||||
add_CAs(cfg.side, CA_parms, CA_cfg)
|
||||
end
|
||||
MAIH.micro_ai_setup(cfg, CA_parms, required_keys, optional_keys)
|
||||
end
|
||||
|
|
|
@ -600,7 +600,7 @@ Note: The messengers are controlled by Goto Micro AI definitions that differ by
|
|||
|
||||
[message]
|
||||
speaker=unit
|
||||
message= _ "$unit.type $unit.name from Lieutenant Gadoc's squadron reporting for duty, sir."
|
||||
message= _ "$unit.language_name $unit.name from Lieutenant Gadoc's squadron reporting for duty, sir."
|
||||
[/message]
|
||||
|
||||
[fire_event]
|
||||
|
@ -628,7 +628,7 @@ Note: The messengers are controlled by Goto Micro AI definitions that differ by
|
|||
|
||||
[message]
|
||||
speaker=fort_commander
|
||||
message= _ "Very good, $unit.type. Now go help your comrade get rid of that saurian infestation in the swamps."
|
||||
message= _ "Very good, $unit.language_name. Now go help your comrade get rid of that saurian infestation in the swamps."
|
||||
[/message]
|
||||
|
||||
[event]
|
||||
|
|
|
@ -86,7 +86,7 @@ Note: The Healer Support AI is coded as a Micro AI. A Micro AI can be added and
|
|||
|
||||
# Stop if the leader of one side died
|
||||
[event]
|
||||
name=die
|
||||
name=last breath
|
||||
first_time_only=no
|
||||
[filter]
|
||||
canrecruit=yes
|
||||
|
|
|
@ -46,6 +46,22 @@
|
|||
hidden=yes
|
||||
[/side]
|
||||
|
||||
[event]
|
||||
name=preload
|
||||
first_time_only=no
|
||||
[lua]
|
||||
code=<<
|
||||
function close_to_advancing(unit)
|
||||
if (unit.experience >= unit.max_experience-1) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
>>
|
||||
[/lua]
|
||||
[/event]
|
||||
|
||||
[event]
|
||||
name=prestart
|
||||
|
||||
|
@ -64,18 +80,6 @@
|
|||
{SCATTER_UNITS 12 "Soulless" 1 (x,y=5-9,8-22) (side=2)}
|
||||
{SCATTER_UNITS 6 "Skeleton,Skeleton Archer" 1 (x,y=5-9,8-22) (side=2)}
|
||||
|
||||
[lua]
|
||||
code=<<
|
||||
function close_to_advancing(unit)
|
||||
if (unit.experience >= unit.max_experience-1) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
>>
|
||||
[/lua]
|
||||
|
||||
[micro_ai]
|
||||
side=2
|
||||
ai_type=simple_attack
|
||||
|
@ -118,7 +122,7 @@
|
|||
{MESSAGE sergeant "" "" _"General Grospur, what do we do? These undead will surely wipe us out."}
|
||||
{MESSAGE Grospur "" "" _"Don't be such a chicken, Sergeant! I have placed units with lots of experience around the perimeter. The undead will not dare to attack them. And those few that sneak through... we can easily dispose of them once they make it inside.
|
||||
|
||||
<i>In other words, the Wesnoth AI does generally not attack units one XP from leveling if there is no chance of killing the unit with a single attack. However, some of the attacks by the undead are handle by the Simple Attack Micro AI in this scenario. General Grospur might be in for a surprise.</i>"}
|
||||
<i>In other words, the Wesnoth AI does generally not attack units one XP from leveling if there is no chance of killing the unit with a single attack. However, some of the attacks by the undead are handled by the Simple Attack Micro AI in this scenario. General Grospur might be in for a surprise.</i>"}
|
||||
|
||||
[objectives]
|
||||
summary= _ "Watch the undead take care of business"
|
||||
|
|
|
@ -105,11 +105,7 @@
|
|||
engine=cpp
|
||||
name=standard_aspect
|
||||
[value]
|
||||
active=2
|
||||
begin=1.5
|
||||
end=1.1
|
||||
spend_all_gold=-1
|
||||
save_on_negative_income=no
|
||||
active=0
|
||||
[/value]
|
||||
[/default]
|
||||
[/aspect]
|
||||
|
|
After Width: | Height: | Size: 117 KiB |
|
@ -113,7 +113,7 @@ Elves can move quickly and safely among the trees. Pick off the enemy grunts wit
|
|||
[/gold_carryover]
|
||||
[/objectives]
|
||||
|
||||
{NAMED_LOYAL_UNIT 1 "Elvish Rider" 15 18 "Lomarfel" (_ "Lomarfel")} # wmllint: recognize Lomarfel
|
||||
{NAMED_LOYAL_UNIT 1 "Elvish Rider" 15 18 "Lomarfel" (_ "Lomarfel")}
|
||||
[+unit]
|
||||
profile=portraits/lomarfel.png
|
||||
[/unit]
|
||||
|
|
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 51 KiB |
BIN
data/campaigns/Dead_Water/images/maps/dw.png
Normal file
After Width: | Height: | Size: 2.1 MiB |
BIN
data/campaigns/Dead_Water/images/maps/l10n/gl/dw--overlay.png
Normal file
After Width: | Height: | Size: 119 KiB |
BIN
data/campaigns/Dead_Water/images/maps/l10n/pt/dw--overlay.png
Normal file
After Width: | Height: | Size: 120 KiB |
|
@ -41,38 +41,38 @@
|
|||
|
||||
[story]
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
{DW_BIGMAP}
|
||||
story= _ "In the Far North, where the Mountains of Dorth approach the coast, lies a bay with a narrow mouth. In the calm waters of this bay lies the merfolk city of Jotha. The natural harbor and the prosperity of the dwellers within drew many envious eyes, but none could match the prowess of the merfolk in their aquatic domain. In most years, orcs from the port city of Tirigaz, further north, would raid against Jotha as predictably as the spring rains; always, they were driven back to dry land with heavy losses, the salt water of the bay stinging their wounds."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "In 626 YW, Jotha was attacked by a larger raiding party than ever before. The king Kai Laudiss led his troops to repel the raiders. They were successful, but merfolk casualties were greater than usual. And numbered with the slain was the wife of Kai Laudiss. The kai’s grief was great, but his wrath was also fearsome to behold. He readied his army to follow the orcs. His stated purpose was not revenge, but the desire to secure the safety of Jotha with a demonstration of force that would teach the orcs to steer well clear of the merfolk. The merfolk army caught up with the orc band as it reached Tirigaz."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "Under the cover of darkness, the mermen slid into the bay, intending to launch a surprise attack at dawn. But the orcs were prepared for this, and had hastily devised a surprise of their own. Ships at the mouth of the bay that had seemed but derelict hulks disgorged hordes of orcs. More of them poured forth from the city. The merfolk were trapped, and found themselves vastly outnumbered."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "Kai Laudiss’s army fought fiercely, slaying five orcs for every warrior they lost. The kai himself fought with reckless abandon; foes fell with each swing of his great mace. The orcs discovered that trapped mermen were tougher than they had imagined, and found themselves fleeing into the forest to escape their vengeful spears. As the kai had intended, it was a defeat the orcs would not soon forget; but the cost was heavy. Kai Laudiss was felled by a poisoned orcish dart, and the greater part of his army was destroyed. When the remaining soldiers returned to Jotha, Krellis, the young son of Kai Laudiss, learned that he had become kai."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "Kai Krellis felt too young for the responsibilities of a kingdom, so he relied on the wisdom of a priestess named Cylanna. She had been a friend of his father, and he had known her as long as he could remember. Cylanna mourned the former kai, but believed that his sacrifice would allow Krellis to preside over an age of peace. Unfortunately, that was not to be. A new enemy appeared from under the mountains."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
show_title=yes
|
||||
{NEW_BATTLE 746 673}
|
||||
[/part]
|
||||
[/story]
|
||||
|
||||
{BIGMAP_01}
|
||||
|
||||
{DEFAULT_SCHEDULE}
|
||||
{TURNS4 30 28 26 26}
|
||||
|
||||
[side]
|
||||
# wmllint: recognize Kai Krellis
|
||||
# wmllint: who SIDE_1 is Kai Krellis
|
||||
{SIDE_1}
|
||||
{GOLD4 110 110 130 130}
|
||||
[/side]
|
||||
|
|
|
@ -32,31 +32,28 @@
|
|||
|
||||
[story]
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "Kai Krellis was concerned that the undead might return, so he determined to learn as much as possible about them. His only clue was the name the necromancer had spoken: <i>“Lord Ravanal”</i>. He sent his swiftest scouts north and south along the coast to learn what they might about this Ravanal. The news they brought him was not good."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "Mal-Ravanal, it seemed, was a great human wizard who had transformed himself into a lich. He had attacked the edges of the kingdom of Wesnoth, and was building a fearsome army of undead humans there. He had also sent necromancers to attack the orcs, and even the elves, in a quest for undead soldiers of different races. It seemed that he also lusted for merfolk slaves. Krellis’ scouts reported that more dark armies were heading north."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "As the weeks went by, the veterans of the last battle helped train new recruits. Krellis’ army did not yet match his father’s, but he now had many competent soldiers. As he had expected, their adversaries soon arrived."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
show_title=yes
|
||||
{OLD_BATTLE 746 673}
|
||||
{NEW_BATTLE 746 673}
|
||||
[/part]
|
||||
[/story]
|
||||
|
||||
{BIGMAP_02}
|
||||
|
||||
{DEFAULT_SCHEDULE}
|
||||
{TURNS4 21 22 23 24}
|
||||
victory_when_enemies_defeated=no
|
||||
|
||||
[side]
|
||||
# wmllint: recognize Kai Krellis
|
||||
{SIDE_1}
|
||||
{GOLD4 120 120 120 120}
|
||||
[/side]
|
||||
|
@ -74,8 +71,8 @@
|
|||
append=yes
|
||||
[/music]
|
||||
|
||||
# wmllint: who RECALL_LOYAL_UNITS is Cylanna, Gwabbo
|
||||
{RECALL_LOYAL_UNITS}
|
||||
# wmllint: recognize Gwabbo
|
||||
|
||||
[objectives]
|
||||
side=1
|
||||
|
|
|
@ -17,23 +17,18 @@
|
|||
next_scenario=04_Slavers
|
||||
[story]
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "Distrustful of the open ocean, Kai Krellis led his people up the coast."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "They had only just begun their trek when they encountered wolves who had gotten wind of the battle. Many of the wolves were ridden by goblins. The orcs of Tirigaz, chastened by Kai Laudiss’s victory, might have decided not to bother the merfolk, but these goblins were bandits and outcasts even to their own kind."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
show_title=yes
|
||||
{OLD_BATTLE 746 673}
|
||||
{NEW_JOURNEY 729 657}
|
||||
{NEW_JOURNEY 734 635}
|
||||
{NEW_BATTLE 732 615}
|
||||
[/part]
|
||||
[/story]
|
||||
|
||||
{BIGMAP_03}
|
||||
|
||||
{DEFAULT_SCHEDULE}
|
||||
{TURNS4 18 17 16 16}
|
||||
|
||||
|
@ -61,10 +56,8 @@
|
|||
append=yes
|
||||
[/music]
|
||||
|
||||
# wmllint: who RECALL_LOYAL_UNITS is Friendly Bat, Undead Bat, Fearsome Bat
|
||||
{RECALL_LOYAL_UNITS}
|
||||
# wmllint: recognize Friendly Bat
|
||||
# wmllint: recognize Undead Bat
|
||||
# wmllint: recognize Fearsome Bat
|
||||
|
||||
[objectives]
|
||||
side=1
|
||||
|
|
|
@ -38,32 +38,14 @@
|
|||
|
||||
[story]
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "Now, they were farther away from home than most merfolk ever went. Only Cylanna and a few soldiers had been farther."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
show_title=yes
|
||||
{OLD_BATTLE 746 673}
|
||||
{OLD_JOURNEY 729 657}
|
||||
{OLD_JOURNEY 734 635}
|
||||
{OLD_BATTLE 732 615}
|
||||
{NEW_JOURNEY 708 612}
|
||||
{NEW_JOURNEY 681 607}
|
||||
{NEW_JOURNEY 654 600}
|
||||
{NEW_JOURNEY 647 573}
|
||||
{NEW_JOURNEY 640 546}
|
||||
{NEW_BATTLE 650 522}
|
||||
[/part]
|
||||
[/story]
|
||||
|
||||
{DUSK}
|
||||
{FIRST_WATCH}
|
||||
{SECOND_WATCH}
|
||||
{DAWN}
|
||||
{MORNING}
|
||||
{AFTERNOON}
|
||||
{BIGMAP_04}
|
||||
|
||||
{DEFAULT_SCHEDULE_DUSK}
|
||||
turns=10
|
||||
{TURNS4 30 28 26 24}
|
||||
|
||||
|
@ -81,7 +63,6 @@
|
|||
[/music]
|
||||
|
||||
{RECALL_LOYAL_UNITS}
|
||||
# wmllint: recognize Gwabbo
|
||||
|
||||
[objectives]
|
||||
side=1
|
||||
|
|
|
@ -28,40 +28,14 @@
|
|||
|
||||
[story]
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "Farther up the coast was the port city of Tirigaz where Kai Laudiss had so soundly beaten the orcs. Bilheld was due west from there. The merfolk felt uneasy, so they took shelter in an abandoned orcish encampment as night fell."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
show_title=yes
|
||||
{OLD_BATTLE 746 673}
|
||||
{OLD_JOURNEY 729 657}
|
||||
{OLD_JOURNEY 734 635}
|
||||
{OLD_BATTLE 732 615}
|
||||
{OLD_JOURNEY 708 612}
|
||||
{OLD_JOURNEY 681 607}
|
||||
{OLD_JOURNEY 654 600}
|
||||
{OLD_JOURNEY 647 573}
|
||||
{OLD_JOURNEY 640 546}
|
||||
{OLD_BATTLE 650 522}
|
||||
{NEW_JOURNEY 637 497}
|
||||
{NEW_JOURNEY 628 473}
|
||||
{NEW_JOURNEY 610 454}
|
||||
{NEW_JOURNEY 589 437}
|
||||
{NEW_JOURNEY 576 412}
|
||||
{NEW_JOURNEY 575 388}
|
||||
{NEW_JOURNEY 552 381}
|
||||
{NEW_BATTLE 542 361}
|
||||
[/part]
|
||||
[/story]
|
||||
|
||||
{DUSK}
|
||||
{FIRST_WATCH}
|
||||
{SECOND_WATCH}
|
||||
{DAWN}
|
||||
{MORNING}
|
||||
{AFTERNOON}
|
||||
{BIGMAP_05}
|
||||
|
||||
{DEFAULT_SCHEDULE_DUSK}
|
||||
{TURNS4 18 17 16 16}
|
||||
victory_when_enemies_defeated=no
|
||||
|
||||
|
@ -82,6 +56,7 @@
|
|||
append=yes
|
||||
[/music]
|
||||
|
||||
# wmllint: who RECALL_LOYAL_UNITS is Teeloa, Keshan
|
||||
{RECALL_LOYAL_UNITS}
|
||||
|
||||
[objectives]
|
||||
|
|
|
@ -31,44 +31,14 @@
|
|||
|
||||
[story]
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "Now the merfolk had no choice but to travel through the open ocean, which they knew was more dangerous than shallow water. After a few days, they found some islands in their path. The infirm and young among the refugees needed a break from travel; Cylanna only vaguely remembered them, but thought they might be safe."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
show_title=yes
|
||||
{OLD_BATTLE 746 673}
|
||||
{OLD_JOURNEY 729 657}
|
||||
{OLD_JOURNEY 734 635}
|
||||
{OLD_BATTLE 732 615}
|
||||
{OLD_JOURNEY 708 612}
|
||||
{OLD_JOURNEY 681 607}
|
||||
{OLD_JOURNEY 654 600}
|
||||
{OLD_JOURNEY 647 573}
|
||||
{OLD_JOURNEY 640 546}
|
||||
{OLD_BATTLE 650 522}
|
||||
{OLD_JOURNEY 637 497}
|
||||
{OLD_JOURNEY 628 473}
|
||||
{OLD_JOURNEY 610 454}
|
||||
{OLD_JOURNEY 589 437}
|
||||
{OLD_JOURNEY 576 412}
|
||||
{OLD_JOURNEY 575 388}
|
||||
{OLD_JOURNEY 552 381}
|
||||
{OLD_BATTLE 542 361}
|
||||
{NEW_JOURNEY 523 370}
|
||||
{NEW_JOURNEY 501 381}
|
||||
{NEW_JOURNEY 477 391}
|
||||
{NEW_BATTLE 455 401}
|
||||
[/part]
|
||||
[/story]
|
||||
|
||||
{DAWN}
|
||||
{MORNING}
|
||||
{AFTERNOON}
|
||||
{DUSK}
|
||||
{FIRST_WATCH}
|
||||
{SECOND_WATCH}
|
||||
{BIGMAP_06}
|
||||
|
||||
{DEFAULT_SCHEDULE_DAWN}
|
||||
{TURNS4 25 24 23 22}
|
||||
|
||||
[side]
|
||||
|
|
|
@ -29,47 +29,14 @@
|
|||
|
||||
[story]
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "Finally, the merfolk arrived at Bilheld. Their destination, and help, was just behind it. The island was inhabited, however, and the residents didn’t look happy to see them."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
show_title=yes
|
||||
{OLD_BATTLE 746 673}
|
||||
{OLD_JOURNEY 729 657}
|
||||
{OLD_JOURNEY 734 635}
|
||||
{OLD_BATTLE 732 615}
|
||||
{OLD_JOURNEY 708 612}
|
||||
{OLD_JOURNEY 681 607}
|
||||
{OLD_JOURNEY 654 600}
|
||||
{OLD_JOURNEY 647 573}
|
||||
{OLD_JOURNEY 640 546}
|
||||
{OLD_BATTLE 650 522}
|
||||
{OLD_JOURNEY 637 497}
|
||||
{OLD_JOURNEY 628 473}
|
||||
{OLD_JOURNEY 610 454}
|
||||
{OLD_JOURNEY 589 437}
|
||||
{OLD_JOURNEY 576 412}
|
||||
{OLD_JOURNEY 575 388}
|
||||
{OLD_JOURNEY 552 381}
|
||||
{OLD_BATTLE 542 361}
|
||||
{OLD_JOURNEY 523 370}
|
||||
{OLD_JOURNEY 501 381}
|
||||
{OLD_JOURNEY 477 391}
|
||||
{OLD_BATTLE 455 401}
|
||||
{NEW_JOURNEY 434 414}
|
||||
{NEW_JOURNEY 414 428}
|
||||
{NEW_BATTLE 395 446}
|
||||
[/part]
|
||||
[/story]
|
||||
|
||||
{DAWN}
|
||||
{MORNING}
|
||||
{AFTERNOON}
|
||||
{DUSK}
|
||||
{FIRST_WATCH}
|
||||
{SECOND_WATCH}
|
||||
{BIGMAP_07}
|
||||
|
||||
{DEFAULT_SCHEDULE_DAWN}
|
||||
{TURNS4 24 22 20 20}
|
||||
|
||||
[side]
|
||||
|
@ -131,8 +98,8 @@
|
|||
name=knalgan_theme.ogg
|
||||
[/music]
|
||||
|
||||
# wmllint: who RECALL_LOYAL_UNITS is Inky
|
||||
{RECALL_LOYAL_UNITS}
|
||||
# wmllint: recognize Keshan
|
||||
|
||||
[objectives]
|
||||
side=1
|
||||
|
|
|
@ -12,55 +12,14 @@
|
|||
|
||||
[story]
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "It had been more of an adventure than the Kai Krellis had planned, but they were finally at their destination. The small island where Tyegëa and her students lived was before them."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
show_title=yes
|
||||
{OLD_BATTLE 746 673}
|
||||
{OLD_JOURNEY 729 657}
|
||||
{OLD_JOURNEY 734 635}
|
||||
{OLD_BATTLE 732 615}
|
||||
{OLD_JOURNEY 708 612}
|
||||
{OLD_JOURNEY 681 607}
|
||||
{OLD_JOURNEY 654 600}
|
||||
{OLD_JOURNEY 647 573}
|
||||
{OLD_JOURNEY 640 546}
|
||||
{OLD_BATTLE 650 522}
|
||||
{OLD_JOURNEY 637 497}
|
||||
{OLD_JOURNEY 628 473}
|
||||
{OLD_JOURNEY 610 454}
|
||||
{OLD_JOURNEY 589 437}
|
||||
{OLD_JOURNEY 576 412}
|
||||
{OLD_JOURNEY 575 388}
|
||||
{OLD_JOURNEY 552 381}
|
||||
{OLD_BATTLE 542 361}
|
||||
{OLD_JOURNEY 523 370}
|
||||
{OLD_JOURNEY 501 381}
|
||||
{OLD_JOURNEY 477 391}
|
||||
{OLD_BATTLE 455 401}
|
||||
{OLD_JOURNEY 434 414}
|
||||
{OLD_JOURNEY 414 428}
|
||||
{OLD_BATTLE 395 446}
|
||||
{NEW_JOURNEY 402 471}
|
||||
{NEW_JOURNEY 396 495}
|
||||
{NEW_JOURNEY 387 518}
|
||||
{NEW_JOURNEY 369 537}
|
||||
{NEW_JOURNEY 344 548}
|
||||
{NEW_JOURNEY 319 542}
|
||||
{NEW_JOURNEY 307 523}
|
||||
{NEW_REST 306 497}
|
||||
[/part]
|
||||
[/story]
|
||||
|
||||
{DAWN}
|
||||
{MORNING}
|
||||
{AFTERNOON}
|
||||
{DUSK}
|
||||
{FIRST_WATCH}
|
||||
{SECOND_WATCH}
|
||||
{BIGMAP_08}
|
||||
|
||||
{DEFAULT_SCHEDULE_DAWN}
|
||||
turns=-1
|
||||
victory_when_enemies_defeated=no
|
||||
|
||||
|
@ -79,7 +38,6 @@
|
|||
team_name=good guys
|
||||
|
||||
id=Tyegea
|
||||
# wmllint: recognize Tyegea
|
||||
name= _ "Tyegëa"
|
||||
canrecruit=yes
|
||||
type=Mermaid Diviner
|
||||
|
|
|
@ -17,81 +17,26 @@
|
|||
|
||||
[story]
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "The party traveled back to the mainland and followed the shore further north. Kai Krellis had much to think about. First, what Tyegëa had revealed about his ancestry gave him questions he could not answer. Second, he was nervous about the task ahead. He felt confidence in his people but feared paying too great a price in dead and wounded to pass Tyegëa’s test. He felt sometimes angry at Tyegëa for sending him on this extra journey, and sometimes grateful that she was willing to help at all."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "After several days, the smell of the swamp was just becoming noticeable when they found a small ruined castle in the right place to be the one Tyegëa had mentioned. Night was falling, and it was very dark beneath the trees, but Krellis was in a hurry, now that the end of their journey was in sight. He decided to go ashore and try to find the mage immediately."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
show_title=yes
|
||||
{OLD_BATTLE 746 673}
|
||||
{OLD_JOURNEY 729 657}
|
||||
{OLD_JOURNEY 734 635}
|
||||
{OLD_BATTLE 732 615}
|
||||
{OLD_JOURNEY 708 612}
|
||||
{OLD_JOURNEY 681 607}
|
||||
{OLD_JOURNEY 654 600}
|
||||
{OLD_JOURNEY 647 573}
|
||||
{OLD_JOURNEY 640 546}
|
||||
{OLD_BATTLE 650 522}
|
||||
{OLD_JOURNEY 637 497}
|
||||
{OLD_JOURNEY 628 473}
|
||||
{OLD_JOURNEY 610 454}
|
||||
{OLD_JOURNEY 589 437}
|
||||
{OLD_JOURNEY 576 412}
|
||||
{OLD_JOURNEY 575 388}
|
||||
{OLD_JOURNEY 552 381}
|
||||
{OLD_BATTLE 542 361}
|
||||
{OLD_JOURNEY 523 370}
|
||||
{OLD_JOURNEY 501 381}
|
||||
{OLD_JOURNEY 477 391}
|
||||
{OLD_BATTLE 455 401}
|
||||
{OLD_JOURNEY 434 414}
|
||||
{OLD_JOURNEY 414 428}
|
||||
{OLD_BATTLE 395 446}
|
||||
{OLD_JOURNEY 402 471}
|
||||
{OLD_JOURNEY 396 495}
|
||||
{OLD_JOURNEY 387 518}
|
||||
{OLD_JOURNEY 369 537}
|
||||
{OLD_JOURNEY 344 548}
|
||||
{OLD_JOURNEY 319 542}
|
||||
{OLD_JOURNEY 307 523}
|
||||
{OLD_REST 306 497}
|
||||
{NEW_JOURNEY 286 479}
|
||||
{NEW_JOURNEY 265 467}
|
||||
{NEW_JOURNEY 266 444}
|
||||
{NEW_JOURNEY 279 424}
|
||||
{NEW_JOURNEY 301 413}
|
||||
{NEW_JOURNEY 321 399}
|
||||
{NEW_JOURNEY 339 378}
|
||||
{NEW_JOURNEY 354 356}
|
||||
{NEW_JOURNEY 368 334}
|
||||
{NEW_JOURNEY 384 315}
|
||||
{NEW_JOURNEY 395 293}
|
||||
{NEW_JOURNEY 375 278}
|
||||
{NEW_JOURNEY 357 264}
|
||||
{NEW_BATTLE 355 242}
|
||||
[/part]
|
||||
[/story]
|
||||
|
||||
{BIGMAP_09}
|
||||
|
||||
# Don't change this order without modifying the turns on which the
|
||||
# bats appear and disappear.
|
||||
{DUSK}
|
||||
{FIRST_WATCH}
|
||||
{SECOND_WATCH}
|
||||
{DAWN}
|
||||
{MORNING}
|
||||
{AFTERNOON}
|
||||
|
||||
{DEFAULT_SCHEDULE_DUSK}
|
||||
{TURNS4 16 17 18 19}
|
||||
victory_when_enemies_defeated=no
|
||||
|
||||
# wmllint: validate-off
|
||||
[side]
|
||||
# wmllint: recognize Kai Krellis
|
||||
{SIDE_1}
|
||||
{GOLD4 120 110 100 90}
|
||||
shroud=yes
|
||||
|
@ -118,9 +63,6 @@
|
|||
[/music]
|
||||
|
||||
{RECALL_LOYAL_UNITS}
|
||||
# wmllint: recognize Gwabbo
|
||||
# wmllint: recognize Cylanna
|
||||
# wmllint: recognize Friendly Bat
|
||||
|
||||
[objectives]
|
||||
side=1
|
||||
|
|
|
@ -19,75 +19,19 @@
|
|||
|
||||
[story]
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "Caladon led them just a little way up the shore. Despite its name, the swamp was not actually desolate on the outskirts. A hardy population of humans coaxed a living out of the damp soil."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
show_title=yes
|
||||
{OLD_BATTLE 746 673}
|
||||
{OLD_JOURNEY 729 657}
|
||||
{OLD_JOURNEY 734 635}
|
||||
{OLD_BATTLE 732 615}
|
||||
{OLD_JOURNEY 708 612}
|
||||
{OLD_JOURNEY 681 607}
|
||||
{OLD_JOURNEY 654 600}
|
||||
{OLD_JOURNEY 647 573}
|
||||
{OLD_JOURNEY 640 546}
|
||||
{OLD_BATTLE 650 522}
|
||||
{OLD_JOURNEY 637 497}
|
||||
{OLD_JOURNEY 628 473}
|
||||
{OLD_JOURNEY 610 454}
|
||||
{OLD_JOURNEY 589 437}
|
||||
{OLD_JOURNEY 576 412}
|
||||
{OLD_JOURNEY 575 388}
|
||||
{OLD_JOURNEY 552 381}
|
||||
{OLD_BATTLE 542 361}
|
||||
{OLD_JOURNEY 523 370}
|
||||
{OLD_JOURNEY 501 381}
|
||||
{OLD_JOURNEY 477 391}
|
||||
{OLD_BATTLE 455 401}
|
||||
{OLD_JOURNEY 434 414}
|
||||
{OLD_JOURNEY 414 428}
|
||||
{OLD_BATTLE 395 446}
|
||||
{OLD_JOURNEY 402 471}
|
||||
{OLD_JOURNEY 396 495}
|
||||
{OLD_JOURNEY 387 518}
|
||||
{OLD_JOURNEY 369 537}
|
||||
{OLD_JOURNEY 344 548}
|
||||
{OLD_JOURNEY 319 542}
|
||||
{OLD_JOURNEY 307 523}
|
||||
{OLD_REST 306 497}
|
||||
{OLD_JOURNEY 286 479}
|
||||
{OLD_JOURNEY 265 467}
|
||||
{OLD_JOURNEY 266 444}
|
||||
{OLD_JOURNEY 279 424}
|
||||
{OLD_JOURNEY 301 413}
|
||||
{OLD_JOURNEY 321 399}
|
||||
{OLD_JOURNEY 339 378}
|
||||
{OLD_JOURNEY 354 356}
|
||||
{OLD_JOURNEY 368 334}
|
||||
{OLD_JOURNEY 384 315}
|
||||
{OLD_JOURNEY 395 293}
|
||||
{OLD_JOURNEY 375 278}
|
||||
{OLD_JOURNEY 357 264}
|
||||
{OLD_BATTLE 355 242}
|
||||
{NEW_JOURNEY 346 222}
|
||||
{NEW_JOURNEY 357 203}
|
||||
{NEW_JOURNEY 374 191}
|
||||
{NEW_JOURNEY 386 175}
|
||||
{NEW_BATTLE 373 160}
|
||||
[/part]
|
||||
[/story]
|
||||
|
||||
{DEFAULT_SCHEDULE}
|
||||
{BIGMAP_10}
|
||||
|
||||
{DEFAULT_SCHEDULE}
|
||||
{TURNS4 30 28 28 28}
|
||||
victory_when_enemies_defeated=no
|
||||
|
||||
# wmllint: validate-off
|
||||
[side]
|
||||
# wmllint: recognize Kai Krellis
|
||||
{SIDE_1}
|
||||
fog=yes
|
||||
{GOLD4 120 120 120 120}
|
||||
|
@ -236,7 +180,6 @@
|
|||
[/music]
|
||||
|
||||
{RECALL_LOYAL_UNITS}
|
||||
# wmllint: recognize Cylanna
|
||||
|
||||
[objectives]
|
||||
side=1
|
||||
|
|
|
@ -9,90 +9,14 @@
|
|||
|
||||
[story]
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "Kai Krellis and his people no longer feared the open ocean, and they made straight for Tyegëa’s island to show her the flaming sword."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
show_title=yes
|
||||
{OLD_BATTLE 746 673}
|
||||
{OLD_JOURNEY 729 657}
|
||||
{OLD_JOURNEY 734 635}
|
||||
{OLD_BATTLE 732 615}
|
||||
{OLD_JOURNEY 708 612}
|
||||
{OLD_JOURNEY 681 607}
|
||||
{OLD_JOURNEY 654 600}
|
||||
{OLD_JOURNEY 647 573}
|
||||
{OLD_JOURNEY 640 546}
|
||||
{OLD_BATTLE 650 522}
|
||||
{OLD_JOURNEY 637 497}
|
||||
{OLD_JOURNEY 628 473}
|
||||
{OLD_JOURNEY 610 454}
|
||||
{OLD_JOURNEY 589 437}
|
||||
{OLD_JOURNEY 576 412}
|
||||
{OLD_JOURNEY 575 388}
|
||||
{OLD_JOURNEY 552 381}
|
||||
{OLD_BATTLE 542 361}
|
||||
{OLD_JOURNEY 523 370}
|
||||
{OLD_JOURNEY 501 381}
|
||||
{OLD_JOURNEY 477 391}
|
||||
{OLD_BATTLE 455 401}
|
||||
{OLD_JOURNEY 434 414}
|
||||
{OLD_JOURNEY 414 428}
|
||||
{OLD_BATTLE 395 446}
|
||||
{OLD_JOURNEY 402 471}
|
||||
{OLD_JOURNEY 396 495}
|
||||
{OLD_JOURNEY 387 518}
|
||||
{OLD_JOURNEY 369 537}
|
||||
{OLD_JOURNEY 344 548}
|
||||
{OLD_JOURNEY 319 542}
|
||||
{OLD_JOURNEY 307 523}
|
||||
{OLD_REST 306 497}
|
||||
{OLD_JOURNEY 286 479}
|
||||
{OLD_JOURNEY 265 467}
|
||||
{OLD_JOURNEY 266 444}
|
||||
{OLD_JOURNEY 279 424}
|
||||
{OLD_JOURNEY 301 413}
|
||||
{OLD_JOURNEY 321 399}
|
||||
{OLD_JOURNEY 339 378}
|
||||
{OLD_JOURNEY 354 356}
|
||||
{OLD_JOURNEY 368 334}
|
||||
{OLD_JOURNEY 384 315}
|
||||
{OLD_JOURNEY 395 293}
|
||||
{OLD_JOURNEY 375 278}
|
||||
{OLD_JOURNEY 357 264}
|
||||
{OLD_BATTLE 355 242}
|
||||
{OLD_JOURNEY 346 222}
|
||||
{OLD_JOURNEY 357 203}
|
||||
{OLD_JOURNEY 374 191}
|
||||
{OLD_JOURNEY 386 175}
|
||||
{OLD_BATTLE 373 160}
|
||||
{NEW_JOURNEY 367 180}
|
||||
{NEW_JOURNEY 355 199}
|
||||
{NEW_JOURNEY 345 221}
|
||||
{NEW_JOURNEY 339 243}
|
||||
{NEW_JOURNEY 331 265}
|
||||
{NEW_JOURNEY 322 289}
|
||||
{NEW_JOURNEY 314 312}
|
||||
{NEW_JOURNEY 305 335}
|
||||
{NEW_JOURNEY 297 358}
|
||||
{NEW_JOURNEY 289 380}
|
||||
{NEW_JOURNEY 280 402}
|
||||
{NEW_JOURNEY 272 425}
|
||||
{NEW_JOURNEY 264 447}
|
||||
{NEW_JOURNEY 265 471}
|
||||
{NEW_JOURNEY 287 480}
|
||||
{NEW_REST 306 497}
|
||||
[/part]
|
||||
[/story]
|
||||
|
||||
{DAWN}
|
||||
{MORNING}
|
||||
{AFTERNOON}
|
||||
{DUSK}
|
||||
{FIRST_WATCH}
|
||||
{SECOND_WATCH}
|
||||
{BIGMAP_11}
|
||||
|
||||
{DEFAULT_SCHEDULE_DAWN}
|
||||
turns=-1
|
||||
victory_when_enemies_defeated=no
|
||||
|
||||
|
@ -137,7 +61,6 @@
|
|||
[/music]
|
||||
|
||||
{RECALL_LOYAL_UNITS}
|
||||
# wmllint: recognize Cylanna
|
||||
|
||||
[unit]
|
||||
type=Mermaid Priestess
|
||||
|
|
|
@ -9,116 +9,21 @@
|
|||
|
||||
[story]
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "The mission had been more difficult than Kai Krellis would have believed when he started. Finally though, the end was in sight. He had seen how priestesses could carve through masses of undead, and now he traveled with a whole contingent of them. Tyegëa was even more powerful, though as unpredictable as Cylanna had said."
|
||||
[/part]
|
||||
[part]
|
||||
{DW_BIGMAP}
|
||||
story= _ "The tribe that had fled Jotha unable to fight returned as an army stiffened by veteran fighters and led by a warrior king. Though they were tired from their journey, weariness fell from them as they neared home. The mermen arrived during the night, and found the mouth of their bay guarded, so they headed south along the coast to some outlying villages to gather news of the invaders."
|
||||
[/part]
|
||||
|
||||
[part]
|
||||
background=dead-water-map.png
|
||||
show_title=yes
|
||||
{OLD_BATTLE 746 673}
|
||||
{OLD_JOURNEY 729 657}
|
||||
{OLD_JOURNEY 734 635}
|
||||
{OLD_BATTLE 732 615}
|
||||
{OLD_JOURNEY 708 612}
|
||||
{OLD_JOURNEY 681 607}
|
||||
{OLD_JOURNEY 654 600}
|
||||
{OLD_JOURNEY 647 573}
|
||||
{OLD_JOURNEY 640 546}
|
||||
{OLD_BATTLE 650 522}
|
||||
{OLD_JOURNEY 637 497}
|
||||
{OLD_JOURNEY 628 473}
|
||||
{OLD_JOURNEY 610 454}
|
||||
{OLD_JOURNEY 589 437}
|
||||
{OLD_JOURNEY 576 412}
|
||||
{OLD_JOURNEY 575 388}
|
||||
{OLD_JOURNEY 552 381}
|
||||
{OLD_BATTLE 542 361}
|
||||
{OLD_JOURNEY 523 370}
|
||||
{OLD_JOURNEY 501 381}
|
||||
{OLD_JOURNEY 477 391}
|
||||
{OLD_BATTLE 455 401}
|
||||
{OLD_JOURNEY 434 414}
|
||||
{OLD_JOURNEY 414 428}
|
||||
{OLD_BATTLE 395 446}
|
||||
{OLD_JOURNEY 402 471}
|
||||
{OLD_JOURNEY 396 495}
|
||||
{OLD_JOURNEY 387 518}
|
||||
{OLD_JOURNEY 369 537}
|
||||
{OLD_JOURNEY 344 548}
|
||||
{OLD_JOURNEY 319 542}
|
||||
{OLD_JOURNEY 307 523}
|
||||
{OLD_REST 306 497}
|
||||
{OLD_JOURNEY 286 479}
|
||||
{OLD_JOURNEY 265 467}
|
||||
{OLD_JOURNEY 266 444}
|
||||
{OLD_JOURNEY 279 424}
|
||||
{OLD_JOURNEY 301 413}
|
||||
{OLD_JOURNEY 321 399}
|
||||
{OLD_JOURNEY 339 378}
|
||||
{OLD_JOURNEY 354 356}
|
||||
{OLD_JOURNEY 368 334}
|
||||
{OLD_JOURNEY 384 315}
|
||||
{OLD_JOURNEY 395 293}
|
||||
{OLD_JOURNEY 375 278}
|
||||
{OLD_JOURNEY 357 264}
|
||||
{OLD_BATTLE 355 242}
|
||||
{OLD_JOURNEY 346 222}
|
||||
{OLD_JOURNEY 357 203}
|
||||
{OLD_JOURNEY 374 191}
|
||||
{OLD_JOURNEY 386 175}
|
||||
{OLD_BATTLE 373 160}
|
||||
{OLD_JOURNEY 367 180}
|
||||
{OLD_JOURNEY 355 199}
|
||||
{OLD_JOURNEY 345 221}
|
||||
{OLD_JOURNEY 339 243}
|
||||
{OLD_JOURNEY 331 265}
|
||||
{OLD_JOURNEY 322 289}
|
||||
{OLD_JOURNEY 314 312}
|
||||
{OLD_JOURNEY 305 335}
|
||||
{OLD_JOURNEY 297 358}
|
||||
{OLD_JOURNEY 289 380}
|
||||
{OLD_JOURNEY 280 402}
|
||||
{OLD_JOURNEY 272 425}
|
||||
{OLD_JOURNEY 264 447}
|
||||
{OLD_JOURNEY 265 471}
|
||||
{OLD_JOURNEY 287 480}
|
||||
{OLD_REST 306 497}
|
||||
{NEW_JOURNEY 306 523}
|
||||
{NEW_JOURNEY 318 543}
|
||||
{NEW_JOURNEY 339 555}
|
||||
{NEW_JOURNEY 362 564}
|
||||
{NEW_JOURNEY 385 573}
|
||||
{NEW_JOURNEY 409 583}
|
||||
{NEW_JOURNEY 433 590}
|
||||
{NEW_JOURNEY 458 598}
|
||||
{NEW_JOURNEY 484 606}
|
||||
{NEW_JOURNEY 508 613}
|
||||
{NEW_JOURNEY 533 620}
|
||||
{NEW_JOURNEY 557 625}
|
||||
{NEW_JOURNEY 581 631}
|
||||
{NEW_JOURNEY 606 638}
|
||||
{NEW_JOURNEY 630 645}
|
||||
{NEW_JOURNEY 655 651}
|
||||
{NEW_JOURNEY 678 657}
|
||||
{NEW_JOURNEY 702 662}
|
||||
{NEW_JOURNEY 725 667}
|
||||
{NEW_BATTLE 746 673}
|
||||
[/part]
|
||||
[/story]
|
||||
|
||||
{SECOND_WATCH}
|
||||
{DAWN}
|
||||
{MORNING}
|
||||
{AFTERNOON}
|
||||
{DUSK}
|
||||
{FIRST_WATCH}
|
||||
{BIGMAP_12}
|
||||
|
||||
{DEFAULT_SCHEDULE_SECOND_WATCH}
|
||||
turns=-1
|
||||
|
||||
[side]
|
||||
# wmllint: recognize Kai Krellis
|
||||
{SIDE_1}
|
||||
{GOLD4 300 300 300 300} # I doubt this is enough to win, but there was a lot of gold on The Flaming Sword, so the player should have around 200 plus this (on normal).
|
||||
[/side]
|
||||
|
@ -571,9 +476,8 @@
|
|||
|
||||
# ...and:
|
||||
[unhide_unit][/unhide_unit]
|
||||
# wmllint: who RECALL_LOYAL_UNITS is Tyegea
|
||||
{RECALL_LOYAL_UNITS}
|
||||
# wmllint: recognize Cylanna
|
||||
# wmllint: recognize Tyegea
|
||||
|
||||
[clear_variable]
|
||||
name=KK_stored
|
||||
|
|
|
@ -22,12 +22,7 @@
|
|||
[/music]
|
||||
|
||||
{RECALL_LOYAL_UNITS}
|
||||
# wmllint: recognize Gwabbo
|
||||
# wmllint: recognize Cylanna
|
||||
# wmllint: recognize Keshan
|
||||
# wmllint: recognize Tyegea
|
||||
# wmllint: recognize Teeloa
|
||||
# wmllint: recognize Inky
|
||||
# wmllint: unwho ALL
|
||||
[/event]
|
||||
|
||||
[event]
|
||||
|
|
465
data/campaigns/Dead_Water/utils/bigmap.cfg
Normal file
|
@ -0,0 +1,465 @@
|
|||
#textdomain wesnoth-dw
|
||||
|
||||
#define DW_BIGMAP
|
||||
[background_layer]
|
||||
image=maps/background.jpg
|
||||
scale_vertically=yes
|
||||
scale_horizontally=no
|
||||
keep_aspect_ratio=yes
|
||||
[/background_layer]
|
||||
[background_layer]
|
||||
image=maps/dw.png
|
||||
scale_vertically=yes
|
||||
scale_horizontally=no
|
||||
keep_aspect_ratio=yes
|
||||
base_layer=yes
|
||||
[/background_layer]
|
||||
#enddef
|
||||
|
||||
#undef NEW_JOURNEY
|
||||
|
||||
#define NEW_JOURNEY X Y
|
||||
[image]
|
||||
x,y={X},{Y}
|
||||
file=misc/new-journey.png
|
||||
delay=300
|
||||
centered=yes
|
||||
[/image]
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_01_NEW
|
||||
{NEW_BATTLE 593 740}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_02_NEW
|
||||
{NEW_BATTLE 593 740}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_02_OLD
|
||||
{OLD_BATTLE 593 740}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_03_NEW
|
||||
{JOURNEY_02_OLD}
|
||||
{NEW_JOURNEY 580 724}
|
||||
{NEW_JOURNEY 569 707}
|
||||
{NEW_JOURNEY 564 687}
|
||||
{NEW_JOURNEY 568 667}
|
||||
{NEW_JOURNEY 581 652}
|
||||
{NEW_JOURNEY 600 643}
|
||||
{NEW_JOURNEY 620 641}
|
||||
{NEW_JOURNEY 639 646}
|
||||
{NEW_JOURNEY 659 653}
|
||||
{NEW_JOURNEY 680 652}
|
||||
{NEW_JOURNEY 698 640}
|
||||
{NEW_JOURNEY 710 623}
|
||||
{NEW_BATTLE 714 602}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_03_OLD
|
||||
{JOURNEY_02_OLD}
|
||||
{OLD_JOURNEY 580 724}
|
||||
{OLD_JOURNEY 569 707}
|
||||
{OLD_JOURNEY 564 687}
|
||||
{OLD_JOURNEY 568 667}
|
||||
{OLD_JOURNEY 581 652}
|
||||
{OLD_JOURNEY 600 643}
|
||||
{OLD_JOURNEY 620 641}
|
||||
{OLD_JOURNEY 639 646}
|
||||
{OLD_JOURNEY 659 653}
|
||||
{OLD_JOURNEY 680 652}
|
||||
{OLD_JOURNEY 698 640}
|
||||
{OLD_JOURNEY 710 623}
|
||||
{OLD_BATTLE 714 602}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_04_NEW
|
||||
{JOURNEY_03_OLD}
|
||||
{NEW_JOURNEY 711 581}
|
||||
{NEW_JOURNEY 703 562}
|
||||
{NEW_JOURNEY 693 544}
|
||||
{NEW_JOURNEY 682 526}
|
||||
{NEW_JOURNEY 673 507}
|
||||
{NEW_JOURNEY 673 487}
|
||||
{NEW_JOURNEY 676 462}
|
||||
{NEW_JOURNEY 668 438}
|
||||
{NEW_BATTLE 655 417}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_04_OLD
|
||||
{JOURNEY_03_OLD}
|
||||
{OLD_JOURNEY 711 581}
|
||||
{OLD_JOURNEY 703 562}
|
||||
{OLD_JOURNEY 693 544}
|
||||
{OLD_JOURNEY 682 526}
|
||||
{OLD_JOURNEY 673 507}
|
||||
{OLD_JOURNEY 673 487}
|
||||
{OLD_JOURNEY 676 462}
|
||||
{OLD_JOURNEY 668 438}
|
||||
{OLD_BATTLE 655 417}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_05_NEW
|
||||
{JOURNEY_04_OLD}
|
||||
{NEW_JOURNEY 641 401}
|
||||
{NEW_JOURNEY 628 386}
|
||||
{NEW_JOURNEY 615 369}
|
||||
{NEW_JOURNEY 603 353}
|
||||
{NEW_JOURNEY 592 335}
|
||||
{NEW_JOURNEY 584 316}
|
||||
{NEW_BATTLE 585 295}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_05_OLD
|
||||
{JOURNEY_04_OLD}
|
||||
{OLD_JOURNEY 641 401}
|
||||
{OLD_JOURNEY 628 386}
|
||||
{OLD_JOURNEY 615 369}
|
||||
{OLD_JOURNEY 603 353}
|
||||
{OLD_JOURNEY 592 335}
|
||||
{OLD_JOURNEY 584 316}
|
||||
{OLD_BATTLE 585 295}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_06_NEW
|
||||
{JOURNEY_05_OLD}
|
||||
{NEW_JOURNEY 565 292}
|
||||
{NEW_JOURNEY 545 293}
|
||||
{NEW_JOURNEY 525 294}
|
||||
{NEW_JOURNEY 505 296}
|
||||
{NEW_JOURNEY 486 298}
|
||||
{NEW_JOURNEY 466 300}
|
||||
{NEW_JOURNEY 446 301}
|
||||
{NEW_JOURNEY 426 301}
|
||||
{NEW_BATTLE 406 298}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_06_OLD
|
||||
{JOURNEY_05_OLD}
|
||||
{OLD_JOURNEY 565 292}
|
||||
{OLD_JOURNEY 545 293}
|
||||
{OLD_JOURNEY 525 294}
|
||||
{OLD_JOURNEY 505 296}
|
||||
{OLD_JOURNEY 486 298}
|
||||
{OLD_JOURNEY 466 300}
|
||||
{OLD_JOURNEY 446 301}
|
||||
{OLD_JOURNEY 426 301}
|
||||
{OLD_BATTLE 406 298}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_07_NEW
|
||||
{JOURNEY_06_OLD}
|
||||
{NEW_JOURNEY 386 295}
|
||||
{NEW_JOURNEY 366 296}
|
||||
{NEW_JOURNEY 347 298}
|
||||
{NEW_JOURNEY 327 303}
|
||||
{NEW_JOURNEY 309 310}
|
||||
{NEW_BATTLE 291 320}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_07_OLD
|
||||
{JOURNEY_06_OLD}
|
||||
{OLD_JOURNEY 386 295}
|
||||
{OLD_JOURNEY 365 294}
|
||||
{OLD_JOURNEY 347 298}
|
||||
{OLD_JOURNEY 327 303}
|
||||
{OLD_JOURNEY 309 310}
|
||||
{OLD_BATTLE 291 320}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_08_NEW
|
||||
{JOURNEY_07_OLD}
|
||||
{NEW_JOURNEY 291 340}
|
||||
{NEW_JOURNEY 287 360}
|
||||
{NEW_JOURNEY 276 377}
|
||||
{NEW_JOURNEY 257 386}
|
||||
{NEW_JOURNEY 237 386}
|
||||
{NEW_JOURNEY 217 382}
|
||||
{NEW_JOURNEY 197 376}
|
||||
{NEW_JOURNEY 178 368}
|
||||
{NEW_REST 159 359}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_08_OLD
|
||||
{JOURNEY_07_OLD}
|
||||
{OLD_JOURNEY 291 340}
|
||||
{OLD_JOURNEY 287 360}
|
||||
{OLD_JOURNEY 276 377}
|
||||
{OLD_JOURNEY 257 386}
|
||||
{OLD_JOURNEY 237 386}
|
||||
{OLD_JOURNEY 217 382}
|
||||
{OLD_JOURNEY 197 376}
|
||||
{OLD_JOURNEY 178 368}
|
||||
{OLD_REST 159 359}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_09_NEW
|
||||
{JOURNEY_08_OLD}
|
||||
{NEW_JOURNEY 167 342}
|
||||
{NEW_JOURNEY 180 327}
|
||||
{NEW_JOURNEY 193 312}
|
||||
{NEW_JOURNEY 210 303}
|
||||
{NEW_JOURNEY 228 295}
|
||||
{NEW_JOURNEY 246 290}
|
||||
{NEW_JOURNEY 265 287}
|
||||
{NEW_JOURNEY 285 286}
|
||||
{NEW_JOURNEY 304 286}
|
||||
{NEW_JOURNEY 323 287}
|
||||
{NEW_JOURNEY 345 290}
|
||||
{NEW_JOURNEY 365 294}
|
||||
{NEW_JOURNEY 386 295}
|
||||
{NEW_JOURNEY 404 297}
|
||||
{NEW_JOURNEY 426 301}
|
||||
{NEW_JOURNEY 446 301}
|
||||
{NEW_JOURNEY 466 300}
|
||||
{NEW_JOURNEY 486 298}
|
||||
{NEW_JOURNEY 488 279}
|
||||
{NEW_JOURNEY 475 261}
|
||||
{NEW_JOURNEY 455 251}
|
||||
{NEW_JOURNEY 435 239}
|
||||
{NEW_BATTLE 418 223}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_09_OLD
|
||||
{JOURNEY_08_OLD}
|
||||
{OLD_JOURNEY 167 342}
|
||||
{OLD_JOURNEY 180 327}
|
||||
{OLD_JOURNEY 193 312}
|
||||
{OLD_JOURNEY 210 303}
|
||||
{OLD_JOURNEY 228 295}
|
||||
{OLD_JOURNEY 246 290}
|
||||
{OLD_JOURNEY 265 287}
|
||||
{OLD_JOURNEY 285 286}
|
||||
{OLD_JOURNEY 304 286}
|
||||
{OLD_JOURNEY 323 287}
|
||||
{OLD_JOURNEY 345 290}
|
||||
{OLD_JOURNEY 365 294}
|
||||
{OLD_JOURNEY 386 295}
|
||||
{OLD_JOURNEY 404 297}
|
||||
{OLD_JOURNEY 426 301}
|
||||
{OLD_JOURNEY 446 301}
|
||||
{OLD_JOURNEY 466 300}
|
||||
{OLD_JOURNEY 486 298}
|
||||
{OLD_JOURNEY 488 279}
|
||||
{OLD_JOURNEY 475 261}
|
||||
{OLD_JOURNEY 455 251}
|
||||
{OLD_JOURNEY 435 239}
|
||||
{OLD_BATTLE 418 223}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_10_NEW
|
||||
{JOURNEY_09_OLD}
|
||||
{NEW_JOURNEY 409 204}
|
||||
{NEW_JOURNEY 411 183}
|
||||
{NEW_JOURNEY 420 165}
|
||||
{NEW_JOURNEY 422 144}
|
||||
{NEW_BATTLE 417 124}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_10_OLD
|
||||
{JOURNEY_09_OLD}
|
||||
{OLD_JOURNEY 409 204}
|
||||
{OLD_JOURNEY 411 183}
|
||||
{OLD_JOURNEY 420 165}
|
||||
{OLD_JOURNEY 422 144}
|
||||
{OLD_BATTLE 417 124}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_11_NEW
|
||||
{JOURNEY_10_OLD}
|
||||
{NEW_JOURNEY 404 138}
|
||||
{NEW_JOURNEY 391 153}
|
||||
{NEW_JOURNEY 376 166}
|
||||
{NEW_JOURNEY 361 178}
|
||||
{NEW_JOURNEY 346 191}
|
||||
{NEW_JOURNEY 330 203}
|
||||
{NEW_JOURNEY 315 214}
|
||||
{NEW_JOURNEY 299 226}
|
||||
{NEW_JOURNEY 283 237}
|
||||
{NEW_JOURNEY 267 249}
|
||||
{NEW_JOURNEY 251 261}
|
||||
{NEW_JOURNEY 236 273}
|
||||
{NEW_JOURNEY 221 285}
|
||||
{NEW_JOURNEY 206 298}
|
||||
{NEW_JOURNEY 193 312}
|
||||
{NEW_JOURNEY 180 327}
|
||||
{NEW_JOURNEY 167 342}
|
||||
{NEW_REST 159 359}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_11_OLD
|
||||
{JOURNEY_10_OLD}
|
||||
{OLD_JOURNEY 404 138}
|
||||
{OLD_JOURNEY 391 153}
|
||||
{OLD_JOURNEY 376 166}
|
||||
{OLD_JOURNEY 361 178}
|
||||
{OLD_JOURNEY 346 191}
|
||||
{OLD_JOURNEY 330 203}
|
||||
{OLD_JOURNEY 315 214}
|
||||
{OLD_JOURNEY 299 226}
|
||||
{OLD_JOURNEY 283 237}
|
||||
{OLD_JOURNEY 267 249}
|
||||
{OLD_JOURNEY 251 261}
|
||||
{OLD_JOURNEY 236 273}
|
||||
{OLD_JOURNEY 221 285}
|
||||
{OLD_JOURNEY 206 298}
|
||||
{OLD_JOURNEY 193 312}
|
||||
{OLD_JOURNEY 180 327}
|
||||
{OLD_JOURNEY 167 342}
|
||||
{OLD_REST 159 359}
|
||||
#enddef
|
||||
|
||||
#define JOURNEY_12_NEW
|
||||
{JOURNEY_11_OLD}
|
||||
{NEW_JOURNEY 163 378}
|
||||
{NEW_JOURNEY 168 397}
|
||||
{NEW_JOURNEY 174 416}
|
||||
{NEW_JOURNEY 180 435}
|
||||
{NEW_JOURNEY 187 453}
|
||||
{NEW_JOURNEY 195 471}
|
||||
{NEW_JOURNEY 204 488}
|
||||
{NEW_JOURNEY 213 505}
|
||||
{NEW_JOURNEY 224 522}
|
||||
{NEW_JOURNEY 236 537}
|
||||
{NEW_JOURNEY 250 551}
|
||||
{NEW_JOURNEY 264 564}
|
||||
{NEW_JOURNEY 280 576}
|
||||
{NEW_JOURNEY 296 587}
|
||||
{NEW_JOURNEY 314 596}
|
||||
{NEW_JOURNEY 331 604}
|
||||
{NEW_JOURNEY 349 612}
|
||||
{NEW_JOURNEY 368 619}
|
||||
{NEW_JOURNEY 386 625}
|
||||
{NEW_JOURNEY 405 632}
|
||||
{NEW_JOURNEY 423 638}
|
||||
{NEW_JOURNEY 441 645}
|
||||
{NEW_JOURNEY 459 653}
|
||||
{NEW_JOURNEY 478 661}
|
||||
{NEW_JOURNEY 497 669}
|
||||
{NEW_JOURNEY 515 677}
|
||||
{NEW_JOURNEY 532 685}
|
||||
{NEW_JOURNEY 542 698}
|
||||
{NEW_JOURNEY 521 701}
|
||||
{NEW_JOURNEY 505 713}
|
||||
{NEW_BATTLE 507 731}
|
||||
#enddef
|
||||
|
||||
#define BIGMAP_01
|
||||
[story]
|
||||
[part]
|
||||
show_title=yes
|
||||
{DW_BIGMAP}
|
||||
{JOURNEY_01_NEW}
|
||||
[/part]
|
||||
[/story]
|
||||
#enddef
|
||||
|
||||
#define BIGMAP_02
|
||||
[story]
|
||||
[part]
|
||||
show_title=yes
|
||||
{DW_BIGMAP}
|
||||
{JOURNEY_02_NEW}
|
||||
[/part]
|
||||
[/story]
|
||||
#enddef
|
||||
|
||||
#define BIGMAP_03
|
||||
[story]
|
||||
[part]
|
||||
show_title=yes
|
||||
{DW_BIGMAP}
|
||||
{JOURNEY_03_NEW}
|
||||
[/part]
|
||||
[/story]
|
||||
#enddef
|
||||
|
||||
#define BIGMAP_04
|
||||
[story]
|
||||
[part]
|
||||
show_title=yes
|
||||
{DW_BIGMAP}
|
||||
{JOURNEY_04_NEW}
|
||||
[/part]
|
||||
[/story]
|
||||
#enddef
|
||||
|
||||
#define BIGMAP_05
|
||||
[story]
|
||||
[part]
|
||||
show_title=yes
|
||||
{DW_BIGMAP}
|
||||
{JOURNEY_05_NEW}
|
||||
[/part]
|
||||
[/story]
|
||||
#enddef
|
||||
|
||||
#define BIGMAP_06
|
||||
[story]
|
||||
[part]
|
||||
show_title=yes
|
||||
{DW_BIGMAP}
|
||||
{JOURNEY_06_NEW}
|
||||
[/part]
|
||||
[/story]
|
||||
#enddef
|
||||
|
||||
#define BIGMAP_07
|
||||
[story]
|
||||
[part]
|
||||
show_title=yes
|
||||
{DW_BIGMAP}
|
||||
{JOURNEY_07_NEW}
|
||||
[/part]
|
||||
[/story]
|
||||
#enddef
|
||||
|
||||
#define BIGMAP_08
|
||||
[story]
|
||||
[part]
|
||||
show_title=yes
|
||||
{DW_BIGMAP}
|
||||
{JOURNEY_08_NEW}
|
||||
[/part]
|
||||
[/story]
|
||||
#enddef
|
||||
|
||||
#define BIGMAP_09
|
||||
[story]
|
||||
[part]
|
||||
show_title=yes
|
||||
{DW_BIGMAP}
|
||||
{JOURNEY_09_NEW}
|
||||
[/part]
|
||||
[/story]
|
||||
#enddef
|
||||
|
||||
#define BIGMAP_10
|
||||
[story]
|
||||
[part]
|
||||
show_title=yes
|
||||
{DW_BIGMAP}
|
||||
{JOURNEY_10_NEW}
|
||||
[/part]
|
||||
[/story]
|
||||
#enddef
|
||||
|
||||
#define BIGMAP_11
|
||||
[story]
|
||||
[part]
|
||||
show_title=yes
|
||||
{DW_BIGMAP}
|
||||
{JOURNEY_11_NEW}
|
||||
[/part]
|
||||
[/story]
|
||||
#enddef
|
||||
|
||||
#define BIGMAP_12
|
||||
[story]
|
||||
[part]
|
||||
show_title=yes
|
||||
{DW_BIGMAP}
|
||||
{JOURNEY_12_NEW}
|
||||
[/part]
|
||||
[/story]
|
||||
#enddef
|
Before Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 2.8 KiB |
|
@ -12,6 +12,7 @@
|
|||
{INTRO_AND_SCENARIO_MUSIC revelation.ogg heroes_rite.ogg}
|
||||
|
||||
[side]
|
||||
# wmllint: who YOUNG_DELFADOR is Delfador
|
||||
{YOUNG_DELFADOR}
|
||||
profile="portraits/younger_delfador.png"
|
||||
type=Journeyman Mage
|
||||
|
@ -46,10 +47,6 @@
|
|||
[+unit]
|
||||
profile=portraits/oracle4.png
|
||||
[/unit]
|
||||
# wmllint: recognize First Oracle
|
||||
# wmllint: recognize Second Oracle
|
||||
# wmllint: recognize Third Oracle
|
||||
# wmllint: recognize Fourth Oracle
|
||||
[/side]
|
||||
|
||||
[story]
|
||||
|
@ -212,6 +209,7 @@
|
|||
time=500
|
||||
[/delay]
|
||||
|
||||
# wmllint: recognize Methor
|
||||
[message]
|
||||
speaker=Methor
|
||||
message = _ "But I will not see you to its end, for that is yours alone."
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
[side]
|
||||
{YOUNG_DELFADOR}
|
||||
profile="portraits/younger_delfador.png"
|
||||
# wmllint: recognize Delfador
|
||||
type=Journeyman Mage
|
||||
canrecruit=yes
|
||||
recruit=Mage,Spearman,Horseman
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
[side]
|
||||
{YOUNG_DELFADOR}
|
||||
profile="portraits/younger_delfador.png"
|
||||
# wmllint: recognize Delfador
|
||||
type=Journeyman Mage
|
||||
recruit=Mage,Spearman,Horseman,Bowman,Cavalryman,Heavy Infantryman
|
||||
team_name=goodies
|
||||
|
@ -87,6 +86,7 @@
|
|||
side=2
|
||||
[/unit]
|
||||
{MOVE_UNIT (id=Lionel) 26 19}
|
||||
# wmllint: recognize Lionel
|
||||
|
||||
[message]
|
||||
speaker=Garard
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
[event]
|
||||
name=prestart
|
||||
|
||||
# wmllint: recognize Lionel
|
||||
[recall]
|
||||
id=Lionel
|
||||
[/recall]
|
||||
|
|
|
@ -119,7 +119,6 @@
|
|||
|
||||
[event]
|
||||
name=start
|
||||
# wmllint: recognize Lionel
|
||||
[recall]
|
||||
id=Lionel
|
||||
[/recall]
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
[/side]
|
||||
[event]
|
||||
name=prestart
|
||||
# wmllint: recognize Lionel
|
||||
[recall]
|
||||
id=Lionel
|
||||
[/recall]
|
||||
|
|
|
@ -340,8 +340,8 @@
|
|||
{MEMOIRS_SURPRISE_3 6 3 Skeleton}
|
||||
{MEMOIRS_WARNING 9 4 6}
|
||||
|
||||
# wmllint: whofield MEMOIRS_DEAD_HOUSE 4
|
||||
{MEMOIRS_DEAD_HOUSE 36 18 Ghoul Penella ( _ "Penella")}
|
||||
# wmllint: recognize Penella
|
||||
[message]
|
||||
speaker=Penella
|
||||
message=_"Do not fear me, Delfador. It is my doom to appear as you see me. I was a serf of a cruel lord. He demanded great taxes to fight many wars... then plague came... my family were starving, and we were forced to eat the flesh of those who had died."
|
||||
|
@ -353,20 +353,17 @@
|
|||
{MEMOIRS_DEAD_HOUSE_END}
|
||||
{MEMOIRS_DEAD_HOUSE 16 13 Ghost Roddry ( _ "Roddry")}
|
||||
[message]
|
||||
# wmllint: recognize Roddry
|
||||
speaker=Roddry
|
||||
message=_"Have you encountered the skeletons? Their castle lies north of here. It is difficult to attack, but there is a secret entrance in the valley beyond Sythan’s village."
|
||||
[/message]
|
||||
{MEMOIRS_DEAD_HOUSE_END}
|
||||
{MEMOIRS_DEAD_HOUSE 22 12 Shadow Nameless ( _ "Nameless")}
|
||||
# wmllint: recognize Nameless
|
||||
[message]
|
||||
speaker=Nameless
|
||||
message=_"I died a long time ago. I have forgotten my name."
|
||||
[/message]
|
||||
{MEMOIRS_DEAD_HOUSE_END}
|
||||
{MEMOIRS_DEAD_HOUSE 23 3 Ghost Melinna ( _ "Melinna")}
|
||||
# wmllint: recognize Melinna
|
||||
[message]
|
||||
speaker=Melinna
|
||||
message=_"Iliah-Malal says that he can open a portal to the world of the living. Those who follow him will walk the earth as they did in life."
|
||||
|
@ -381,14 +378,12 @@
|
|||
[/message]
|
||||
{MEMOIRS_DEAD_HOUSE_END}
|
||||
{MEMOIRS_DEAD_HOUSE 34 11 Ghost Sythan ( _ "Sythan")}
|
||||
# wmllint: recognize Sythan
|
||||
[message]
|
||||
speaker=Sythan
|
||||
message=_"I was once a great lord... I commanded armies! Iliah-Malal promised I would lead again."
|
||||
[/message]
|
||||
{MEMOIRS_DEAD_HOUSE_END}
|
||||
{MEMOIRS_DEAD_HOUSE 22 16 Ghost Hereld ( _ "Hereld")}
|
||||
# wmllint: recognize Hereld
|
||||
[message]
|
||||
speaker=Hereld
|
||||
message=_"Beware the skeletons! They are not the spirits of dead men, but the creations of evil magic."
|
||||
|
@ -401,6 +396,7 @@
|
|||
speaker=Hereld
|
||||
message=_"It is said that they guard a powerful magical artifact. Roddry knows more than I do; he lives to the west."
|
||||
[/message]
|
||||
#wmllint: whofield clear MEMOIRS_DEAD_HOUSE
|
||||
{MEMOIRS_DEAD_HOUSE_END}
|
||||
|
||||
[event]
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
[side]
|
||||
{YOUNG_DELFADOR}
|
||||
profile="portraits/young_delfador.png"
|
||||
# wmllint: recognize Delfador
|
||||
type=Journeyman Mage
|
||||
canrecruit=yes
|
||||
recruit=Elvish Scout,Elvish Archer,Elvish Fighter,Elvish Shaman
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
[objectives]
|
||||
side=1
|
||||
[objective]
|
||||
description= _ "Defeat all enemies"
|
||||
description= _ "Defeat all enemy leaders"
|
||||
condition=win
|
||||
[/objective]
|
||||
[objective]
|
||||
|
@ -81,7 +81,6 @@
|
|||
{PLACE_IMAGE scenery/village-human-burned2.png 24 7}
|
||||
{PLACE_IMAGE scenery/village-human-burned3.png 32 23} # has moveto
|
||||
|
||||
#wmllint: recognize Delfador
|
||||
[side]
|
||||
{YOUNG_DELFADOR}
|
||||
profile="portraits/young_delfador.png"
|
||||
|
|
|
@ -213,7 +213,6 @@
|
|||
[/message]
|
||||
[/event]
|
||||
|
||||
# wmllint: recognize Chantal
|
||||
[event]
|
||||
name=victory
|
||||
[switch]
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
|
||||
#wmllint: validate-off
|
||||
[side]
|
||||
# wmllint: recognize Delfador
|
||||
{YOUNG_DELFADOR}
|
||||
profile="portraits/young_delfador.png"
|
||||
type=Journeyman Mage
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
{TO_A_NEW_ALLY}
|
||||
[/part]
|
||||
[/story]
|
||||
# wmllint: recognize Delfador
|
||||
[side]
|
||||
{YOUNG_DELFADOR}
|
||||
profile="portraits/young_delfador.png"
|
||||
|
@ -40,7 +39,6 @@
|
|||
|
||||
{RESTORE_WESNOTHIAN_VETERANS}
|
||||
|
||||
# wmllint: recognize Relgorn
|
||||
{NAMED_UNIT 2 (Dwarvish Fighter) 5 10 "Relgorn" (_"Relgorn") (ai_special=guardian)}
|
||||
{MAKE_HERO Relgorn}
|
||||
{NAMED_UNIT 2 (Dwarvish Fighter) 9 7 "Udrin" (_"Udrin") (ai_special=guardian)}
|
||||
|
@ -48,6 +46,7 @@
|
|||
|
||||
[side]
|
||||
side=2
|
||||
# wmllint: who ULREK is Ulrek
|
||||
{ULREK}
|
||||
type=Dwarvish Lord
|
||||
|
||||
|
@ -233,7 +232,6 @@
|
|||
###############################################
|
||||
# defeat player if Ulrek or Relgorn is killed #
|
||||
###############################################
|
||||
#wmllint: recognize Ulrek
|
||||
[event]
|
||||
name=die
|
||||
[filter]
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
|
||||
[unit]
|
||||
{ULREK}
|
||||
# wmllint: unwho ULREK
|
||||
placement=leader
|
||||
[/unit]
|
||||
{DELFADOR_GETS_DWARVES}
|
||||
|
@ -144,7 +145,6 @@
|
|||
|
||||
#define MATERIALIZE_ILIAH_MALAL
|
||||
# Materialize the bad guy on his keep
|
||||
#wmllint: recognize Iliah-Malal
|
||||
[unit]
|
||||
id=Iliah-Malal
|
||||
name=_ "Iliah-Malal"
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
profile="portraits/young_delfador.png"
|
||||
type=Journeyman Mage
|
||||
canrecruit=yes
|
||||
# wmllint: recognize Delfador
|
||||
recruit={LOYALISTS},White Mage,Red Mage
|
||||
shroud=yes
|
||||
team_name=goodies
|
||||
|
@ -217,6 +216,7 @@
|
|||
y=$start_loc.y
|
||||
[/unstore_unit]
|
||||
{CLEAR_VARIABLE start_loc,Lionel}
|
||||
# wmllint: recognize Lionel
|
||||
[/event]
|
||||
|
||||
[event]
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
{YOUNG_DELFADOR}
|
||||
profile="portraits/young_delfador.png"
|
||||
type=Journeyman Mage
|
||||
# wmllint: recognize Delfador
|
||||
canrecruit=yes
|
||||
recruit={ELVES}
|
||||
fog=yes
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
{YOUNG_DELFADOR}
|
||||
profile="portraits/young_delfador.png"
|
||||
type=Journeyman Mage
|
||||
# wmllint: recognize Delfador
|
||||
# wmllint: unwho all
|
||||
recruit={ELVES}
|
||||
fog=yes
|
||||
{GOLD 130 100 100}
|
||||
|
|
|
@ -54,9 +54,9 @@
|
|||
[/filter_attack]
|
||||
start_time=-500
|
||||
{SOUND:HIT_AND_MISS wose-attack.ogg wose-miss.ogg -500}
|
||||
[frame]
|
||||
image=units/wose-shaman-attack-[1~2].png:[400,225]
|
||||
[/frame]
|
||||
# [frame]
|
||||
# image=units/wose-shaman-attack-[1~2].png:[400,225]
|
||||
# [/frame]
|
||||
[/attack_anim]
|
||||
[attack_anim]
|
||||
[filter_attack]
|
||||
|
@ -64,14 +64,22 @@
|
|||
[/filter_attack]
|
||||
missile_start_time=-250
|
||||
[missile_frame]
|
||||
offset=1.0
|
||||
duration=250
|
||||
image=projectiles/entangle.png
|
||||
image_diagonal=projectiles/entangle.png
|
||||
[/missile_frame]
|
||||
start_time=-300
|
||||
attack_sound_start_time=-100
|
||||
|
||||
[attack_sound_frame]
|
||||
sound=entangle.wav
|
||||
[/attack_sound_frame]
|
||||
{SOUND:SLOW}
|
||||
|
||||
[frame]
|
||||
image=units/wose-shaman.png:150,units/wose-shaman-attack-[1~2].png:150,units/wose-shaman.png:150
|
||||
halo=halo/wose-ranged-halo[1~4].png:150
|
||||
image="units/wose-shaman.png"
|
||||
halo="halo/elven/nature-halo[1~8].png:75"
|
||||
[/frame]
|
||||
[/attack_anim]
|
||||
[/unit_type]
|
||||
|
|
|
@ -7,12 +7,7 @@
|
|||
map_data="{campaigns/Descent_Into_Darkness/maps/02_Peaceful_Valley.map}"
|
||||
victory_when_enemies_defeated=no
|
||||
{TURNS 29 26 23}
|
||||
{MORNING}
|
||||
{AFTERNOON}
|
||||
{DUSK}
|
||||
{FIRST_WATCH}
|
||||
{SECOND_WATCH}
|
||||
{DAWN}
|
||||
{DEFAULT_SCHEDULE_MORNING}
|
||||
|
||||
{INTRO_AND_SCENARIO_MUSIC wanderer.ogg elvish-theme.ogg}
|
||||
{EXTRA_SCENARIO_MUSIC knolls.ogg}
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
fog=no
|
||||
shroud=no
|
||||
[ai]
|
||||
aggression=4.0
|
||||
aggression=1.0
|
||||
caution=0.0
|
||||
grouping=no
|
||||
simple_targeting=yes
|
||||
|
|
|
@ -7,12 +7,8 @@
|
|||
map_data="{campaigns/Descent_Into_Darkness/maps/04_Beginning_of_the_Revenge.map}"
|
||||
turns=36
|
||||
victory_when_enemies_defeated=yes
|
||||
{DUSK}
|
||||
{FIRST_WATCH}
|
||||
{SECOND_WATCH}
|
||||
{DAWN}
|
||||
{MORNING}
|
||||
{AFTERNOON}
|
||||
|
||||
{DEFAULT_SCHEDULE_DUSK}
|
||||
|
||||
{INTRO_AND_SCENARIO_MUSIC revelation.ogg vengeful.ogg}
|
||||
{EXTRA_SCENARIO_MUSIC nunc_dimittis.ogg}
|
||||
|
|
|
@ -7,12 +7,7 @@
|
|||
map_data="{campaigns/Descent_Into_Darkness/maps/05_Orc_War.map}"
|
||||
victory_when_enemies_defeated=yes
|
||||
turns=30
|
||||
{MORNING}
|
||||
{AFTERNOON}
|
||||
{DUSK}
|
||||
{FIRST_WATCH}
|
||||
{SECOND_WATCH}
|
||||
{DAWN}
|
||||
{DEFAULT_SCHEDULE_MORNING}
|
||||
|
||||
{INTRO_AND_SCENARIO_MUSIC northerners.ogg battle.ogg}
|
||||
{EXTRA_SCENARIO_MUSIC the_city_falls.ogg}
|
||||
|
|