Merge branch 'master' into campaignd_asio

This commit is contained in:
loonycyborg 2016-09-13 16:30:37 +03:00
commit 1c90221527
3031 changed files with 208407 additions and 179567 deletions

2
.gitignore vendored
View file

@ -52,6 +52,7 @@ projectfiles/VC*/*.vc.*db
projectfiles/VC*/*.sdf
projectfiles/VC*/*.suo
projectfiles/VC*/*.aps
projectfiles/VC*/*.user
ALL_BUILD.vcproj
INSTALL.vcproj
PACKAGE.vcproj
@ -61,6 +62,7 @@ uninstall.vcproj
src/**/*.vcproj
/WindowsTimeout.ilk
/WindowsTimeout.pdb
.vscode
# eclipse
.settings

View file

@ -53,9 +53,7 @@ install:
- if [ "$CXXSTD" == "1y" ]; then
sudo apt-get install -qq g++-5;
export CXX=g++-5;
elif [ "$CXX" == "g++" ]; then
sudo apt-get install -qq g++-4.7;
export CXX=g++-4.7;
export CC=gcc-5;
fi
script:
@ -65,7 +63,7 @@ script:
- if [ "$USE_CMAKE" = true ]; then
cmake . -DENABLE_STRICT_COMPILATION=$STRICT_COMPILATION -DENABLE_NLS=$NLS -DENABLE_TESTS=$CPP_TESTS && make VERBOSE=1 -j2;
else
scons cxxtool=$CXX --debug=time build=release extra_flags_config=-pipe extra_flags_release="$EXTRA_FLAGS_RELEASE" strict=$STRICT_COMPILATION $TARGETS cxx_std=$CXXSTD nls=$NLS jobs=2;
scons cxxtool=$CXX ctool=$CC --debug=time build=release extra_flags_config=-pipe extra_flags_release="$EXTRA_FLAGS_RELEASE" strict=$STRICT_COMPILATION $TARGETS cxx_std=$CXXSTD nls=$NLS jobs=2;
fi
- "export DISPLAY=:99.0"
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1024x768x24"

2690
Doxyfile

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@ Contents
1. Prerequisites
================
Wesnoth requires a compiler with sufficient C++11 support such as GCC 4.7 and
Wesnoth requires a compiler with sufficient C++11 support such as GCC 4.8 and
later, or Clang 3.3 and later.
You'll need to have these libraries and their development headers installed in

View file

@ -18,57 +18,18 @@ CHANGES
Example contents.
[/rasection]
[rasection="Support for SDL 1.2 has been dropped"]
SDL version 1.2.x has been removed in favour of SDL version 2.0.x. The build systems have been and all code has been removed that utilised verions 1.2.x.
[/rasection]
[rasection="WML/Lua compatibility changes and deprecations"]
[rasection="Additions and changes to the AI"]
[list]
[*]A new candidate action (CA), called [wiki=RCA_AI#The_Candidate_Actions_.28CAs.29_of_the_main_loop_Stage_in_the_RCA_AI]high XP attack[/wiki], has been added to all general Wesnoth AIs in mainline, fixing a short-coming in the default AI's combat CA. Previously, the AI would only attack units 1 experience point (XP) from leveling under very specific and rarely occurring circumstances. It was possible to exploit this, for example, for blocking vital passages or keeping key units alive without these units ever being attacked, making scenarios much easier than they were intended.
The new CA fixes this hole and will generally, but not blindly, attack such units now. The attack logic is explained [url=https://github.com/wesnoth/wesnoth/blob/master/data/ai/lua/ca_high_xp_attack.lua#L5]here[/url]. Since the new behavior is accomplished not by changing the existing attack code, but by adding a new candidate action, the previous behavior can be restored by removing the high_xp_attack CA, in case this is desired in specific scenarios.
[*]For content creators, a [wiki=Micro_AIs#Assassin_Squad_Micro_AI_.28ai_type.3Dassassin.29]new Micro AI[/wiki], controlling individual assassins or an entire assassin squad has been added. It is already being used in Heir to the Throne scenario 8, 'The Princess of Wesnoth'.
[*]A long standing bug which caused units with zero maximum moves not to attack adjacent enemies under certain circumstances has been fixed. This also (sometimes) affected units which could not move due to other circumstances (such as terrain).
[*]Two instances of the AI ignoring the [disable] weapon special have been fixed.
[/list]
[/rasection]
[rasection="Wesnoth Formula Language update"]
The Wesnoth formula language used by $(...) substitution, the formula= key in standard unit filters, and a few other places has received some significant updates. The changlog has the full list, but some highlights are:
[list]
[*]New operators for string concatenation, ranges, and testing containment
[*]New string interpolation syntax
[*]Dot operator can access identifier-friendly string keys in maps
[*]Function definitions work outside FormulaAI and GUI2
[*]Many new functions for math and string manipulation
[*]Exponentiation operator is now right-associative (meaning 2^3^2 now produces 512 rather than 64)
[*]Dice operator is now synced
[*]Improved formula debugger
[/list]
The "fai" and "faiend" keywords are deprecated in favour of "wfl" and "wflend" respectively. This is part of a move to more clearly distinguish [wiki]Wesnoth Formula Language[/wiki], the language itself, from [wiki]FormulaAI[/wiki], which is just one use of the language in Wesnoth.
The attributes leader, total_movement, movement_left, and states in WFL unit variables were renamed to canrecruit, max_moves, moves, and status respectively; this was done to make them match the Lua and WML names for the same things. Also, the special attribute in WFL weapon variables is now specials. The old names will continue to work for now, but they may be removed in a future version.
[/rasection]
[rasection="Changes to the AI"]
After several development cycles of adding new and/or experimental features to the AI engine, a long overdue AI code cleanup effort was undertaken in order to facilitate future AI maintenance and development. This included a refactoring and reorganization of the code of the current AI as well as the removal of non-functional, outdated or deprecated code.
As a result, the format for configuring some AI parameters has been simplified. The AI functionality has also been extended in several ways. For example, it now allows Lua facets and the [modify_ai] tag has been extended to allow editing the internal jobs of the recruitment_instructions aspect.
While making these changes, we tried to retain backward compatibility as much as feasible. Thus, most of the changes are invisible to the WML and custom AI developer and the vast majority of existing UMC code will continue to function as is. It is, however, unavoidable that some old AIs or AI configurations will require updating. A summary of those changes is given below. For more details, see the full changelog and the Wesnoth wiki. In addition, we have set up a separate [forum thread; create first post and add link] in which you can post any questions and problems you might encounter while updating your code.
[/rasection]
[rawarn="AI compatibility breaking changes"]
Several old, deprecated AI components were removed:
[list]
[*]Several aspects that no longer did anything anyway: number_of_possible_recruits_to_force_recruit, recruitment_ignore_bad_combat, recruitment_ignore_bad_movement
[*]The recruitment aspect; however, this is automatically translated to a recruitment_instructions aspect, which means it will now be honored where it was previously ignored
[*]Stages that were either old and deprecated or experimental and unmaintained/broken: old recruitment, fallback, Strategy Formulation with RCA
[*]Candidate actions that were either old and deprecated or experimental and unmaintained/broken: old recruitment, simple move-to-targets, global fallback, Akihara recruitment, experimental recruitment
[*]The protect_my_unit [goal] type
[/list]
A number of changes were made to the way Lua AI components are declared in the WML and how the code is called by the engine. The wiki documents the new method in detail, but unfortunately these changes are likely to break some AIs. However, the fixes will be simple in most cases:
[list=1]
[*]If you used old-style Lua candidate actions (with execute= and evaluate= keys) together with an explicit [engine]name=lua tag, they will no longer work as written. In many cases, the fix will be simple - the code within [engine] frequently begins with a line similar to [c]local ai = ...[/c]. Simply removing this line will be sufficient to fix most such cases; if you accessed a "data" variable in the AI, see the Experimental AI definition (data/ai/ais/ai_generic_rush.cfg) for an example of how to fix it.
[*]If you used old-style Lua candidate actions without an explicit engine tag, you may need to remove a line similar to [c]local ai = ...[/c] from some of your code. In general, anything of the form [c]ai = <something>[/c] needs to be changed.
[*]If you used new-style (external) Lua candidate actions (with a location key), they will still work for the time being (provided that they use the exec_parms= and/or eval_parms= keys), but we recommend updating them nevertheless. The exec_parms= and eval_parms= keys should be combined into an [args] tag, and the argument list of your execution and evaluation functions should be changed from [c](ai, cfg, self)[/c] to [c](cfg, data)[/c]. The data parameter is the same as self.data was before; however, provided you do not have an explicit [engine] tag, self.data will continue to work.
[/list]
[/rawarn]
==========
KNOWN BUGS
@ -78,11 +39,11 @@ KNOWN BUGS
[b]General bugs:[/b]
[list]
[*]Crashes may occur during the loading screen. If those crashes happen to you, you can disable the loading screen animation by addding disable_loading screen_animation=yes in your preferences file.
[*]The MP server has trouble with Local player types in campaigns (bug [bug]21965[/bug]). We have decided to postpone dealing with this. In the meantime, you might try assigning such sides to the host, or running multiple instances of Wesnoth.
[*]Wesnoth does not exist on the panel (bug [bug]24202[/bug]).
[*]"Name of Game" bleeds onto "Player/Type" label when leaving/entering the app (bug [bug]24437[/bug]).
[*]Inconsistent transparency on credits screen (bug [bug]24428[/bug]).
[*]options become black on hover (bug [bug]24478[/bug]).
[*]Dialog In Replay Clearing Issue (bug [bug]24491[/bug]).
[/list]
[/raissue]
@ -99,22 +60,12 @@ KNOWN BUGS
[list]
[*]ClearType font rendering is disabled as it causes glitches (bug [bug]21648[/bug]).
This is likely caused by outdated libraries in the packaging process.
[*]Consecutive line breaks (paragraph breaks) are not rendered as expected (bug [bug]21649[/bug]).
This is likely caused by outdated libraries in the packaging process. There is no built-in workaround available yet.
[/list]
[b]Bugs specific to Apple OS X:[/b]
The following issues affecting Wesnoth on Apple OS X are known and they are pending fixes. Many of them require significant re-engineering that can only be done in 1.13.x development releases later, or cannot be properly addressed due to a lack of experienced OS X coders in our team. Thus, unless someone can contribute patches to address them, it is unlikely that these bugs will be fixed.
[b]Bugs specific to macOS:[/b]
[list]
[*]The default variable-width font is used instead of a monospace font even where a monospace font is required (bug [bug]23628[/bug]).
[*]Helvetica is used as the default variable-width font instead of DejaVu Sans (bug [bug]23560[/bug]).
[*]Fullscreen mode does not fill the entire screen when maximum resolution is selected in Preferences → Display, and user interface elements are scaled and distorted.
[*]System commands do not work while Wesnoth is running in fullscreen mode (bug [bug]21943[/bug]).
[*]Trackpad tap clicking is sometimes not recognized ([url=http://forums.wesnoth.org/viewtopic.php?f=4&t=39788]forum post[/url]).
[*]Unofficial builds with OpenMP support enabled randomly freeze (bug [bug]18144[/bug]).
[*]Consecutive line breaks (paragraph breaks) are not rendered as expected (bug [bug]21649[/bug]).
This is likely caused by outdated libraries in the packaging process. There is no built-in workaround available yet.
[*]Unofficial builds with OpenMP support enabled randomly freeze (bug [bug]18144[/bug]).
[/list]
[/raissue]

View file

@ -507,7 +507,8 @@ for env in [test_env, client_env, env]:
env.AppendUnique(CXXFLAGS = ["-fopenmp"], LIBS = ["gomp"])
if env['strict']:
env.AppendUnique(CCFLAGS = Split("-Werror -Wold-style-cast $(-Wno-unused-local-typedefs$)"))
env.AppendUnique(CCFLAGS = Split("-Werror $(-Wno-unsused-local-typedefs$)"))
env.AppendUnique(CXXFLAGS = Split("-Wold-style-cast"))
if env['sanitize']:
env.AppendUnique(CCFLAGS = ["-fsanitize=" + env["sanitize"]], LINKFLAGS = ["-fsanitize=" + env["sanitize"]])

216
changelog
View file

@ -1,36 +1,197 @@
Version 1.13.4+dev:
Version 1.13.5+dev:
* AI:
* Added new high_xp_attack candidate action to default AI. This CA performs
attacks on enemy units so close to leveling that the default AI's combat CA
would not attack them.
* New Micro AI: Assassin Squad AI
* Fix bug #23720, AI units with max_moves=0 do not attack.
* Fix bug #22179: [disable] weapon special is ignored by AI. A second
instance of the AI also ignoring this special under different circumstances
has also been fixed.
* Campaigns:
* Eastern Invasion:
* Fixed broken village encounters.
* Delfador's Memoirs:
* S9: Resolved inability to end level even when Delfador has the Staff
(bug #24951)
* S17: Resolved Wesnoth units returning to recall list not being healed
properly (bug #24952)
* S19: Resolved undead veterans victory condition not working properly.
* Music and sound effects:
* Added a preference to pause the music when the game loses focus.
* Now the music fades out between scenarios.
* Graphics:
* Improved or new terrain graphics: New wooden floor variation and new
transitions.
* New sprite for Tentacle of the Deep.
* Units:
* Changed the sound for the melee attack of the
Loyalist Bowman, Orcish Crossbowman and Orcish Slurbow.
* User Interface:
* Trait descriptions in the help are now generated. (This makes user-defined
traits show up in the help as well.)
* Fix game map sometimes showing and buttons sometimes not rendered properly
in story screen (bug #24553)
* Improved font rendering on Windows.
* Redesigned gamestate inspector window.
* Recall dialog no longer shows units that no leader on the map can recall
(due to the [filter_recall] not matching)
* Weapon specials only gained through AMLAs now get a help topic
* The "Cores" button on the title screen is now hidden if no cores other
than the default are installed
* Redesigned game dropdown/context menu appearance
* New categories bar in hotkey preferences allows you to filter hotkeys
* Fix issue with the title screen not redrawing when the window size or
fullscreen setting changes with a dialog open over it.
* WML Engine:
* Added {HAS_NO_TURN_LIMIT} macro for objectives
* New attributes for [message] with [option]
* Added variable= to [message]: if specified, gives variable name to
receive the [option] index (1..n) selected
only used if any [option] appear
* Added value= to [option]: if specified, gives value to store in variable
instead of index number, only used if variable= appears in [message]
* New attributes for [role]:
* search_recall_list=yes|no|only(default yes) controls where to look
* reassign=yes|no(default yes) if no, check for a unit and do not assign to
another
* [auto_recall] sub-tag, if assigned to recall list, gives [recall]
attributes (no SUF)
* [else] sub-tag, WML to execute if no unit found for the role
* New help_text= key for [trait] to set the description displayed in the
help.
* Added tag id= [fire_event], which allows raising events by id
* [modify_unit] now understands [effect] tags, which it applies directly.
This replaces the use of [object] with no_write=yes (which will be removed
in the next release).
* Add [object]take_only_once=yes|no (default yes) - if set to no, automatic
tracking is disabled for this object (allowing it to be taken multiple
times even if it has an id).
* New [remove_object] tag which removes applied [object]s from matched units;
it can filter on the entire [object] WML. The most efficient use is to
remove all objects with a specific duration value.
* Renamed [foreach] variable= to array=
* Renamed [foreach] item_var= to variable=
* Fixed several bugs in the name generation of the map generator
* Fixed issues with using [endlevel] in victory/defeat events
* The {MAGENTA_IS_THE_TEAM_COLOR} macro is no longer needed in [unit_type]
It is now the default behaviour unless overridden with the flag_rgb key.
* New key type_tree in unit filters - similar to type, but also matches any
possible advancements of the specified unit types.
* [options][combo] in [scenario], [modifications], etc has been renamed
to [choice] (which is more accurate). The old name still works for now.
* Lua API:
* Added new functions wesnoth.fire_event_by_id and fire_event_by_name. The
old function wesnoth.fire_event is now an alias for wesnoth.fire_event_by_name
* Added new function wesnoth.remove_modifications, which removes applied
modifications of the chosen type from a unit. The most efficient use is to
remove all modifications with a specific duration value.
Also callable as u:remove_modifications.
* The built-in conditionals have_unit, have_location, and variable are now
present in the wesnoth.wml_conditionals table. This means they can be
directly called, extended with new features, or even overridden with custom
implementations.
* New recall_filter field in unit proxy returns the [filter_recall] config
* New variations field in unit_type proxy returns a list of unit variations
Each member is a full unit_type describing that variation.
The table is iterable with pairs().
* Lua side, unit_type, and unit attack proxies can now be compared with ==
with identity semantics. (Previously, each time such a proxy was obtained,
it would produce a new object that did not compare equal to any others.)
* Lua dialog functions now support the stacked widget and the unit preview pane
* New wesnoth.show_menu function shows a dropdown menu at the mouse location
* Lua attack proxy has new read_only field which is true for unit_type attacks
If true, attempts to change the attack will result in an error.
* The name field in Lua attack proxy is now writable
* The attacks field in the Lua unit proxy is now writable
Specifically, attacks may be replaced, appended (by assigning a new ID or
the next valid index), or removed (by setting a field to nil).
* wesnoth.show_message_dialog supports second_portrait and second_mirror keys
in its first argument, which produces a dialog with two portraits.
* The callable userdata returned by wesnoth.textdomain can now be called with
an additional two parameters (a plural string and a count) in order to support
gettext plurals.
* New matches function in team and unit attack metatables, which test if the
side or weapon matches a filter.
* Performance:
* When a heuristic determines that it's probably faster, the game predicts
battle
outcome by simulating a few thousand fights instead of calculating exact
probabilities. This method is inexact, but in very complex battles
(extremely high HP, drain, slow, berserk, etc.) it's significantly faster than the
default damage calculation method.
* Miscellaneous and bug fixes:
* A new way to make units invulnerable for debugging: select the unit and type
";unit invulnerable=yes". This method operates by decreasing the opponent's hit
chance to zero: as a result, it doesn't slow down damage prediction unlike the
"increase HP to ridiculous levels" method.
* The ;choose_level command now works in the tutorial and in [test] scenarios
* Fixed a stray ; character appearing pre-entered in the command console.
* Fixed bug in wesnothd that was causing server crashes if game with
multiple network and local players was ran.
* Added a tab to run the wmlxgettext tool to GUI.pyw
* Fixed problem with Spectre's hitpoint bar positioning.
* Show correct number of attacks in case of swarm weapon special (bug #24978)
* Fixed bug that icons of buttons under the minimap disappeared when the
player opened and closed a menu.
Version 1.13.5:
* Campaigns:
* An Orcish Incursion:
* Linaera can recruit Mage, and cannot recruit Elves
* Heir to the Throne:
* Add journey tracks for 19c/20b path.
* New sprites for Li'sar.
* S10: Clarify objectives and change egg image on capture.
* S19c: Removed the undead and the swamps.
* Tutorial
* Improve translatability for languages with gender-dependent pronouns
* S1: Fix unit being deselected after the select message
* S2: Highlight (outline) talked-about locations
* Legend of Wesmere
* S3: fix bug which silently disabled Urudin retreat AI
* S3: fix bug which silently disabled Urudin retreat AI
* Graphics:
* Updated generic portrait of Mermaid Initiate.
* Added generic portrait for Giant Spider.
* Language and i18n:
* Updated translations: British English, Galician, German, Italian, Japanese,
Portuguese, RACV, Russian, Scottish Gaelic, Spanish
Polish, Portuguese, RACV, Russian, Scottish Gaelic, Spanish
* Networking:
* Reworked the multiplayer server to use asio functions for networking
operations instead of SDL_net, thus it no longer depends on SDL_net and SDL.
* The client now uses boost::asio for communication with wesnothd too.
* Removed support for SDL 1.2. SDL 2 is now the only supported version.
* Terrains:
* Changed terrain code of Desert Mountains from Mdy to Mdd.
* Editor:
* Allow to set special locations in the editor which can then be read by wml.
* User Interface:
* Fix flickering caused by tooltips, closing windows and tabbing into the game (bug #24532)
* Various design improvements to GUI2 widgets
* New simpler GUI2 loading screen
* New colored cursor graphics
* Fixed Mage of Light halo appearing in the top-left corner of the screen
while the mage is moving (bug #23712).
* Fixed Observers icon appearing behind other top bar items in MP games on
horizontal UI resolutions < 1024 (bug #24455).
* Fixed ToD schedule progress indicator appearing behind other top bar items
on vertical UI resolutions < 600.
* Improved the dialog for choosing what to do when a player leaves in
multiplayer.
* The side overview now also shows allied human sides in sp even if
they aren't discovered yet
* Added an option to disable the loadingscreen animation since it caused
bugs in some configurations.
* Added a gui method to activate loggers (Preferences -> Advanced -> Logging)
loggers activated in the gui print just like loggers activated in the
command line (i.e. messages appear in the console)
* The Lua console screen now has a clear button
* Fix bug #24762: Editor actions are out of sync after resizing.
* Increased the font size for text in buttons.
* Changed unit help topics to use smaller images on smaller monitors.
* WML engine:
* Add color= attribute to [message].
* Add [else], search_recall_list=, auto_recall= to [role]
* Fix some issues with [foreach]
* Fix some issues with backstab-like weapon specials
* Support [effect]times=<integer>
@ -59,7 +220,13 @@ Version 1.13.4+dev:
are derived
* Modification tags in [modify_unit] now support delayed_variable_substitution
(This means [advancement], [object], and [trait] tags.)
* All looping tags now give an error if they contain no [do] tag.
(They may contain multiple [do] tags, however.)
* Add [message]image_pos=left|right, which mostly supercedes ~RIGHT()
* For [core] authors: New keys for game logo (game_logo, game_logo_background)
* AiWML:
* Filters within [micro_ai] can now use $this_unit, which was previously
impossible due to the config being prematurely parsed.
* Simplified aspect syntax which works for all aspects, present and future:
* All aspects with simple values can be specified as key=value
* Except attacks, all aspects with complex values have a simple tag form
@ -139,6 +306,13 @@ Version 1.13.4+dev:
aspect[recruitment_instructions].facet[facet_id].recruit[recruit_id].
(For a limit, replace "recruit" with "limit".)
The [recruit] and [limit] tags now support id keys for this.
* Added side_name= attribute in [side], it's now no longer possible to
specify the current_player attribute in the [side] wml.
* unit filters, specially in wesnoth.get_units now have a limit= attribute
to limit the number or matched units.
* Added new attribute "registered_users_only" to MultiplayerServerWML which indicates
that only registered users should be allowed to join the game
* wesnoth now does a stricter check for unit type ids.
* Lua API:
* wesnoth.match_unit can now take a location (rather than a unit) as
the optional third parameter. This will cause the filter to consider
@ -147,7 +321,7 @@ Version 1.13.4+dev:
* wesnoth.highlight_hex is no longer deprecated, but its effect is
slightly different from the old one. It outlines a hex, nothing more.
* wesnoth.select_hex is now deprecated in favour of the new
wesnoth.select_unit (or u:select_unit). The effect is almost the same
wesnoth.select_unit (or u:select). The effect is almost the same
(with the exception that it does not outline the hex if true is
passed as the second argument), but this name change was done to
emphasize that it acts on a unit, more than a location.
@ -165,7 +339,11 @@ Version 1.13.4+dev:
and [deprecated_message] tags now use this.
* New wesnoth.name_generator function builds a name generator and returns
it as a callable userdata. Both the original Markov chain generator
and the new context free gramamr generator are supported
and the new context free grammar generator are supported
* New wesnoth.get_end_level_data() function which can be called in a
victory, defeat, or scenario_end event to access (or change) how
the scenario ends.
* Fix wesnoth.erase_unit failing if the unit was on a recall list.
* WML tables defined in Lua now accept string keys with array values
(where "array" is a table whose keys are all integers). This joins
the elements of the array with commas and produces a single string
@ -217,8 +395,17 @@ Version 1.13.4+dev:
should return a table with keys "own" and "enemy", each of which may
be either a unit filter table or a function which takes a unit as a
parameter and returns true or false.
* Added wesnoth.game_events.on_mouse_move/on_mouse_actions callbacks
(fr #22635)
* Added wesnoth.special_locations
* [lua] tags now also support the [args] subtag outside events.
* added lua_function= attribute in location filters
* Added on_event.lua which is an eaiser to use wrapper for
wesnoth.game_events.on_event
* Multiplayer:
* Hornshark Island: simplified multiplayer faction determination
* Added "Registered users only" checkbox to multiplayer configuration dialog which
when checked, only allows registered users to join the game
* Wesnoth formula engine:
* Formulas in unit filters can now access nearly all unit attributes
The following attributes were renamed (old names still work, for now):
@ -269,8 +456,8 @@ Version 1.13.4+dev:
This counts backwards from the specified offset
A size of -1 is the same as 1.
* if() can take two arguments; returns null if the condition is false
* tolist() will now invert the effect of tomap()
* debug_print() now shows in console if debug mode is on
* tomap() will now invert the effect of tolist()
* debug_print() now shows in chat area if debug mode is on
* New core functions:
* Trig functions tan, acos, asin, atan have been added. (Sin and cos
existed since at least 1.9 but were undocumented until very recently.)
@ -290,6 +477,9 @@ Version 1.13.4+dev:
passing to tomap() - this also means key-value pairs are now
serializable (relevant in FormulaAI)
* sgn(), trunc() and frac() functions for decimal numbers
* Map generator engine:
* makes now use of the new context free grammar name generator
* ported name generation from english.cfg to [naming]
* Bugfixes:
* Dice operator is now synced (where possible)
* Modulus (%) operator now works on decimal numbers
@ -324,6 +514,9 @@ Version 1.13.4+dev:
* Debug commands that create units now do so for the currently controlled
side instead of always side 1.
* Fixed bug #24696 (Nightstalk ability not working)
* Ported the following scripts to Python 3: TeamColorizer, about_cfg_to_wiki,
campaign2wiki, terrain2wiki
* Wesnoth now ignores unknown arguments that XCode may pass when testing.
Version 1.13.4:
* Language and i18n:
@ -407,6 +600,13 @@ Version 1.13.3:
* Added name= and write_name= attributes in [item]
* Added description_alignment= key to [campaign]
* Fixed [put_to_recall_list] not working correctly (bug #24390)
* Wesnoth now does a stricter check in variablenames and doesn't allow
empty indexes like "a[].b" anymore.
* Lua API:
* Added wesnoth.read_file
* wesnoth.dofile/require now allow .. in relative paths.
* unit.level and unit.upkeep can now be changed via lua unit proxies.
* wesnoth.find_path is now available in lua map generators and allows border=yes/no parameter.
* Miscellaneous and bug fixes:
* Fix the new log code on Windows to actually use Unicode-aware functions
in a couple of places so Wesnoth does not quit on startup when trying to
@ -3443,6 +3643,8 @@ Version 1.9.7:
* Fixed bug #18000, #18099: Show a wrongly entered MP password and crash
upon editing this text.
* WML engine:
* added mode=replace to [modify_unit] to replace rather than merge unit subtags
(does not apply to object, trait, effect, or advancement)
* new attribute team_name= in SSFs
* added [event][filter_side]<SSF keys> support
* added support for inline SSF to [chat]

View file

@ -21,7 +21,6 @@
id=Custom
name= _"Custom"
image="units/unknown-unit.png"
{MAGENTA_IS_THE_TEAM_COLOR}
[/multiplayer_side]
#ifdef MULTIPLAYER
@ -31,11 +30,11 @@
{test/multiplayer/}
#endif
#else
# using different default eras in sp and mp forcesus to a config reload which we dont want.
{multiplayer/eras.cfg}
#endif
# Using different default eras in SP and MP forces a config reload which we don't want.
{multiplayer/eras.cfg}
{campaigns/}
[ais]

View file

@ -109,18 +109,6 @@
step=1
[/advanced_preference]
[advanced_preference]
field=max_wml_menu_items
# TODO: It would be better to eliminate this preference and have it instead determined by the gui layout algorithm.
name=_ "Maximum WML menu items"
description= _ "Maximum number of WML-defined menu items displayed at once"
type=int
default=7
min=3
max=32
step=1
[/advanced_preference]
[advanced_preference]
field=use_twelve_hour_clock_format
name= _ "Use 12-hour clock format"
@ -167,7 +155,7 @@
name= _ "Show color cursors"
description= _ "Use colored mouse cursors"
type=boolean
default=no
default=yes
[/advanced_preference]
[advanced_preference]
@ -188,6 +176,14 @@
type=custom
[/advanced_preference]
[advanced_preference]
field=damage_prediction_allow_monte_carlo_simulation
name= _ "Allow damage calculation with Monte Carlo simulation"
description= _ "Allow the damage calculation window to simulate fights instead of using exact probability calculations"
type=boolean
default=yes
[/advanced_preference]
#ifdef __UNUSED__
[advanced_preference]
field=joystick_support_enabled

View file

@ -15,6 +15,7 @@
{AI_CA_RECRUITMENT}
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_HIGH_XP_ATTACK}
{AI_CA_COMBAT}
{AI_CA_HEALING}
{AI_CA_VILLAGES}

View file

@ -16,6 +16,7 @@
{AI_CA_RECRUITMENT}
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_HIGH_XP_ATTACK}
{AI_CA_COMBAT}
{AI_CA_HEALING}
{AI_CA_VILLAGES}

View file

@ -22,6 +22,7 @@
#{AI_CA_RECRUITMENT}
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_HIGH_XP_ATTACK}
{AI_CA_COMBAT}
{AI_CA_HEALING}
{AI_CA_VILLAGES}

View file

@ -14,6 +14,7 @@
{AI_CA_RECRUITMENT}
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_HIGH_XP_ATTACK}
{AI_CA_COMBAT}
{AI_CA_HEALING}
{AI_CA_VILLAGES}

1623
data/ai/formula/engine.lua Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
fai 'level_up_attack_move.fai'
wfl 'level_up_attack_move.fai'
# Score will be the probability * 100 of attacker unit (me) killing target unit #
# (target) if attacker can level up and if the highest probable outcome #
@ -25,4 +25,4 @@ if(calc_exp(target.level) > (me.max_experience - me.experience),
-5)
where battle_outcome = calculate_outcome(me.loc, get_best_defense_loc(my_moves.moves, me, target), target.loc)
faiend
wflend

View file

@ -1,4 +1,4 @@
fai 'level_up_attack_move.fat'
wfl 'level_up_attack_move.fat'
# If move is chosen, attacker (me) will attack enemy unit (target) from the hex #
# with highest defensive value. This move will only be chosen if the attacker #
@ -7,4 +7,4 @@ fai 'level_up_attack_move.fat'
attack(me.loc, get_best_defense_loc(my_moves.moves, me, target), target.loc)
faiend
wflend

View file

@ -1,4 +1,4 @@
fai 'ai/formula/lib/map_evaluation.fai' #===== Evaluation how good unit is on a map ===== #
wfl 'ai/formula/lib/map_evaluation.fai' #===== Evaluation how good unit is on a map ===== #
# filter out important locations #
@ -104,4 +104,4 @@ def units_average_defense(ai*, units_list, terrain_min_percent)
units_list
);
faiend
wflend

View file

@ -1,4 +1,4 @@
fai 'ai/formula/lib/recruitment.fai'
wfl 'ai/formula/lib/recruitment.fai'
def recruit_army(ai*,unit_map)
choose(
@ -29,4 +29,4 @@ def recruit_army(ai*,unit_map)
faiend
wflend

View file

@ -1,4 +1,4 @@
fai 'ai/formula/lib/util.fai'
wfl 'ai/formula/lib/util.fai'
def sumarize_values( input_map )
sum(
@ -49,4 +49,4 @@ def sumarize_maps_values( map_A, map_B )
value + map_A[key]
);
faiend
wflend

View file

@ -1,4 +1,4 @@
fai 'new_recruitment.fai'
wfl 'new_recruitment.fai'
{ai/formula/lib/map_evaluation.fai}
{ai/formula/lib/util.fai}
@ -365,4 +365,4 @@ if( vars.side_terrain,
)
faiend
wflend

View file

@ -1,4 +1,4 @@
fai 'patrol.fai'
wfl 'patrol.fai'
def closest_unit(ai*, me)
choose(
@ -70,4 +70,4 @@ where path_to = if( enemy_units,
shortest_path( me.loc, closest_unit(self, me).loc ),
[]
)
faiend
wflend

View file

@ -1,4 +1,4 @@
fai 'poisoner_attack.fai'
wfl 'poisoner_attack.fai'
def get_best_defense_loc(moves, attacker, enemy)
choose(filter(map(filter(moves, src=attacker.loc), dst),
@ -10,4 +10,4 @@ def get_best_defense_loc(moves, attacker, enemy)
where att_weap = index_of(['poison'],map(me.attacks,specials))
faiend
wflend

View file

@ -1,4 +1,4 @@
fai 'poisoner_eval.fai'
wfl 'poisoner_eval.fai'
{AI_CA_COMBAT_SCORE} + 100 +
#rate units with regeneration as worse targets #
@ -26,5 +26,5 @@ if( target.abilities,
0
)
faiend
wflend

View file

@ -1,4 +1,4 @@
fai 'recruitment.fai'
wfl 'recruitment.fai'
{ai/formula/lib/map_evaluation.fai}
{ai/formula/lib/util.fai}
@ -332,4 +332,4 @@ if( vars.side_terrain,
)
faiend
wflend

View file

@ -1,4 +1,4 @@
fai 'scouting_eval.fai'
wfl 'scouting_eval.fai'
if(me.loc = my_leader.loc,
-5,
@ -12,4 +12,4 @@ if( me.max_moves > 5,
where enemies_in_range = filter( enemy_units, 'enemy', distance_between( me.loc, enemy.loc ) < me.max_moves),
shroud = find_shroud()
faiend
wflend

View file

@ -1,4 +1,4 @@
fai 'scouting_move.fai'
wfl 'scouting_move.fai'
if(size(villages) != 0,
move(me.loc, nearest_loc(nearest_loc(me.loc,villages),unit_moves(me.loc))),
@ -6,4 +6,4 @@ if(size(villages) != 0,
where villages = filter(unit_moves(me.loc),is_unowned_village(map,x,y)),
shroud = find_shroud()
faiend
wflend

View file

@ -736,12 +736,11 @@ end
function ai_helper.get_units_with_attacks(filter)
-- Using formula = '$this_unit.attacks_left > 0' is slow, this method is much faster
-- Also need to check that units actually have attacks (as attacks_left > 0 with no attacks is possible)
-- The latter has to go through unit.__cfg which is slow, but there is no way around that, as far as I know
local all_units = wesnoth.get_units(filter)
local units = {}
for _,unit in ipairs(all_units) do
if (unit.attacks_left > 0) and (H.get_child(unit.__cfg, 'attack')) then
if (unit.attacks_left > 0) and (#unit.attacks > 0) then
table.insert(units, unit)
end
end

View file

@ -0,0 +1,300 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local LS = wesnoth.require "lua/location_set.lua"
-- Evaluation process:
--
-- Find all enemy units that could be caused to level up by an attack
-- - If only units that would cause them to level up can attack, CA score = 100,010.
-- This means the attack will be done before the default AI attacks, so that AI
-- units do not get used otherwise by the default AI.
-- - If units that would not cause a leveling can also attack, CA score = 99,990,
-- meaning we see whether the default AI attacks that unit with one of those first.
-- We also check whether it is possible to move an own unit out of the way
--
-- Attack rating:
-- 0. If the CTD (chance to die) of the AI unit is larger than the value of
-- aggression for the side, do not do the attack
-- 1. Otherwise, if the attack might result in a kill, do that preferentially:
-- rating = CTD of defender - CTD of attacker
-- 2. Otherwise, if the enemy is poisoned, do not attack (wait for it
-- weaken and attack on a later turn)
-- 3. Otherwise, calculate damage done to enemy (as if it were not leveling) and
-- own unit, expressed in partial loss of unit value (gold) and minimize both.
-- Damage to enemy is minimized because we want to level it with the weakest AI unit,
-- so that we can follow up with stronger units. In addition, use of poison or
-- slow attacks is strongly discouraged. See code for exact equations.
local ca_attack_highxp = {}
function ca_attack_highxp:evaluation(cfg, data)
-- Note: (most of) the code below is set up to maximize speed. Do not
-- "simplify" this unless you understand exactly what that means
-- E.g., getting all units, plus looping over them, is much faster than using a filter
local all_units = wesnoth.get_units()
local max_unit_level = 0
local units = {}
for _,unit in ipairs(all_units) do
if (unit.side == wesnoth.current.side) and (unit.attacks_left > 0) and (#unit.attacks > 0) then
table.insert(units, unit)
local level = unit.level
if (level > max_unit_level) then
max_unit_level = level
end
end
end
if (not units[1]) then return 0 end
-- Mark enemies as potential targets if they are close enough to an AI unit
-- that could trigger them leveling up; this is not a sufficient criterion,
-- but it is much faster than path finding, so it is done for preselection.
local target_infos = {}
for i_t,enemy in ipairs(all_units) do
if wesnoth.is_enemy(wesnoth.current.side, enemy.side) then
local XP_to_levelup = enemy.max_experience - enemy.experience
if (max_unit_level >= XP_to_levelup) then
local potential_target = false
local ind_attackers, ind_other_units = {}, {}
for i_u,unit in ipairs(units) do
if (H.distance_between(enemy.x, enemy.y, unit.x, unit.y) <= unit.moves + 1) then
if (unit.level >= XP_to_levelup) then
potential_target = true
table.insert(ind_attackers, i_u)
else
table.insert(ind_other_units, i_u)
end
end
end
if potential_target then
local target_info = {
ind_target = i_t,
ind_attackers = ind_attackers,
ind_other_units = ind_other_units
}
table.insert(target_infos, target_info)
end
end
end
end
if (not target_infos[1]) then return 0 end
-- The following location sets are used so that we at most need to call
-- find_reach() and wesnoth.copy_unit() once per unit
local reaches = LS.create()
local attacker_copies = LS.create()
local aggression = ai.get_aggression()
local max_ca_score, max_rating, best_attack = 0, 0
for _,target_info in ipairs(target_infos) do
local target = all_units[target_info.ind_target]
local can_force_level = {}
local attack_hexes = LS.create()
for xa,ya in H.adjacent_tiles(target.x, target.y) do
local unit_in_way = wesnoth.get_unit(xa, ya)
if unit_in_way then
if (unit_in_way.side == wesnoth.current.side) then
local uiw_reach
if reaches:get(unit_in_way.x, unit_in_way.y) then
uiw_reach = reaches:get(unit_in_way.x, unit_in_way.y)
else
uiw_reach = wesnoth.find_reach(unit_in_way)
reaches:insert(unit_in_way.x, unit_in_way.y, uiw_reach)
end
-- Check whether the unit to move out of the way has an unoccupied hex to move to.
-- We do not deal with cases where a unit can move out of the way for a
-- unit that is moving out of the way of the initial unit (etc.).
local can_move = false
for _,uiw_loc in ipairs(uiw_reach) do
-- Unit in the way of the unit in the way
local uiw_uiw = wesnoth.get_unit(uiw_loc[1], uiw_loc[2])
if (not uiw_uiw) then
can_move = true
break
end
end
if (not can_move) then
-- Keep this case as the unit in the way might be a potential attacker
attack_hexes:insert(xa, ya, unit_in_way.id)
else
attack_hexes:insert(xa, ya, 'can_move_away')
end
end
else
attack_hexes:insert(xa, ya, 'empty')
end
end
attack_hexes:iter(function(xa, ya, occupied)
for _,i_a in ipairs(target_info.ind_attackers) do
local attacker = units[i_a]
if (occupied == 'empty') or (occupied == 'can_move_away') then
-- If the hex is not blocked, check all potential attackers
local reach
if reaches:get(attacker.x, attacker.y) then
reach = reaches:get(attacker.x, attacker.y)
else
reach = wesnoth.find_reach(attacker)
reaches:insert(attacker.x, attacker.y, reach)
end
for _,loc in ipairs(reach) do
if (loc[1] == xa) and (loc[2] == ya) then
local tmp = {
ind_attacker = i_a,
dst = { x = xa, y = ya },
src = { x = attacker.x, y = attacker.y }
}
table.insert(can_force_level, tmp)
break
end
end
else
-- If hex is blocked by own units, check whether this unit
-- is one of the potential attackers
if (attacker.id == occupied) then
local tmp = {
ind_attacker = i_a,
dst = { x = xa, y = ya },
src = { x = attacker.x, y = attacker.y }
}
table.insert(can_force_level, tmp)
end
end
end
end)
-- If a leveling attack is possible, check whether any of the other
-- units (those with too low a level to force leveling up) can get there
local ca_score = 100010
attack_hexes:iter(function(xa, ya, occupied)
if (ca_score == 100010) then -- cannot break out of the iteration with goto
for _,i_u in ipairs(target_info.ind_other_units) do
local unit = units[i_u]
if (occupied == 'empty') or (occupied == 'can_move_away') then
-- If the hex is not blocked, check if unit can get there
local reach
if reaches:get(unit.x, unit.y) then
reach = reaches:get(unit.x, unit.y)
else
reach = wesnoth.find_reach(unit)
reaches:insert(unit.x, unit.y, reach)
end
for _,loc in ipairs(reach) do
if (loc[1] == xa) and (loc[2] == ya) then
ca_score = 99990
goto found_unit
end
end
else
-- If hex is blocked by own units, check whether this unit
-- is one of the potential attackers
if (unit.id == occupied) then
ca_score = 99990
goto found_unit
end
end
end
-- It is sufficient to find one unit that can get to any attack hex
::found_unit::
end
end)
if (ca_score >= max_ca_score) then
for _,attack_info in ipairs(can_force_level) do
local attacker = units[attack_info.ind_attacker]
local attacker_copy
if attacker_copies:get(attacker.x, attacker.y) then
attacker_copy = attacker_copies:get(attacker.x, attacker.y)
else
attacker_copy = wesnoth.copy_unit(attacker)
attacker_copies:insert(attacker.x, attacker.y, attacker_copy)
end
attacker_copy.x = attack_info.dst.x
attacker_copy.y = attack_info.dst.y
-- Choose the attacker that would do the *least* damage.
-- We want the damage distribution here as if the target were not to level up
-- the chance to die is the same in either case
local old_experience = target.experience
target.experience = 0
local att_stats, def_stats, att_weapon = wesnoth.simulate_combat(attacker_copy, target)
target.experience = old_experience
local rating = -1000
if (att_stats.hp_chance[0] <= aggression) then
if (def_stats.hp_chance[0] > 0) then
rating = 5000 + def_stats.hp_chance[0] - att_stats.hp_chance[0]
elseif target.status.poisoned then
rating = -1002
else
rating = 1000
local enemy_value_loss = (target.hitpoints - def_stats.average_hp) / target.max_hitpoints
enemy_value_loss = enemy_value_loss * wesnoth.unit_types[target.type].cost
-- We want the _least_ damage to the enemy, so the minus sign is no typo!
rating = rating - enemy_value_loss
local own_value_loss = (attacker_copy.hitpoints - att_stats.average_hp) / attacker_copy.max_hitpoints
own_value_loss = own_value_loss + att_stats.hp_chance[0]
own_value_loss = own_value_loss * wesnoth.unit_types[attacker_copy.type].cost
rating = rating - own_value_loss
-- Strongly discourage poison or slow attacks
if att_weapon.poisons or att_weapon.slows then
rating = rating - 100
end
-- Minor penalty if the attack hex is occupied
if (attack_hexes:get(attack_info.dst.x, attack_info.dst.y) == 'can_move_away') then
rating = rating - 0.001
end
end
end
if (rating > max_rating)
or ((rating > 0) and (ca_score > max_ca_score))
then
max_rating = rating
max_ca_score = ca_score
best_attack = attack_info
best_attack.target = { x = target.x, y = target.y }
best_attack.ca_score = ca_score
end
end
end
end
if best_attack then
data.XP_attack = best_attack
end
return max_ca_score
end
function ca_attack_highxp:execution(cfg, data)
local attacker = wesnoth.get_unit(data.XP_attack.src.x, data.XP_attack.src.y)
local defender = wesnoth.get_unit(data.XP_attack.target.x, data.XP_attack.target.y)
AH.movefull_outofway_stopunit(ai, attacker, data.XP_attack.dst.x, data.XP_attack.dst.y)
if (not attacker) or (not attacker.valid) then return end
if (not defender) or (not defender.valid) then return end
AH.checked_attack(ai, attacker, defender)
data.XP_attack = nil
end
return ca_attack_highxp

View file

@ -1,7 +1,5 @@
return {
-- init parameters:
-- ai: a reference to the ai engine so recruit has access to ai functions
-- It is also possible to pass an ai table directly to the execution function, which will then override the value passed here
-- ai_cas: an object reference to store the CAs and associated data
-- the CA will use the function names ai_cas:recruit_rushers_eval/exec, so should be referenced by the object name used by the calling AI
-- ai_cas also has the functions find_best_recruit, find_best_recruit_hex and analyze_enemy_unit added to it
@ -16,7 +14,7 @@ return {
-- (default always returns false)
-- leader_takes_village: function that returns true if and only if the leader is going to move to capture a village this turn
-- (default always returns true)
init = function(ai, ai_cas, params)
init = function(ai_cas, params)
if not params then
params = {}
end

View file

@ -63,7 +63,7 @@ return {
end
)
}
wesnoth.require("ai/lua/generic_recruit_engine.lua").init(ai, generic_rush, params)
wesnoth.require("ai/lua/generic_recruit_engine.lua").init(generic_rush, params)
-------- Castle Switch CA --------------
local function get_reachable_enemy_leaders(unit)

View file

@ -13,7 +13,7 @@ function retreat_functions.min_hp(unit)
-- The minimum hp to retreat is a function of level and terrain defense
-- We want to stay longer on good terrain and leave early on very bad terrain
local hp_per_level = wesnoth.unit_defense(unit, wesnoth.get_terrain(unit.x, unit.y))/15
local level = wesnoth.unit_types[unit.type].level
local level = unit.level
-- Leaders are considered to be higher level because of their value
if unit.canrecruit then level = level+2 end

View file

@ -0,0 +1,167 @@
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 function get_units_target(cfg)
local units = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", H.get_child(cfg, "filter") }
}
local target = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "and", H.get_child(cfg, "filter_second") }
}[1]
return units, target
end
local function custom_cost(x, y, unit, enemy_rating_map, prefer_map)
-- Custom cost function for assassin path finding consisting of:
-- 1. The standard movecost of the units
-- 2. A penalty for hexes that can be attacked or are blocked by enemies (stored in rating map)
-- 3. A penalty for non-preferred hexes (prefer_map). This has to be a penalty for
-- non-preferred hexes rather than a bonus for preferred hexes as the cost function
-- must return values >=1 for the a* search to work.
local terrain = wesnoth.get_terrain(x, y)
local move_cost = wesnoth.unit_movement_cost(unit, terrain)
move_cost = move_cost + (enemy_rating_map:get(x, y) or 0)
if prefer_map then
if (not prefer_map:get(x, y)) then
move_cost = move_cost + 5
end
end
return move_cost
end
local ca_assassin_move = {}
function ca_assassin_move:evaluation(cfg, data)
local units, target = get_units_target(cfg)
if (not units[1]) then return 0 end
if (not target) then return 0 end
return cfg.ca_score
end
function ca_assassin_move:execution(cfg, data)
-- We simply move the assassins one at a time
local units, target = get_units_target(cfg)
local unit = units[1]
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "not", H.get_child(cfg, "filter_second") }
}
-- Maximum damage the enemies can theoretically do for all hexes they can attack
local enemy_damage_map = LS.create()
for _,enemy in ipairs(enemies) do
-- Need to "move" enemy next to unit for attack calculation
-- Do this with a unit copy, so that no actual unit has to be moved
local enemy_copy = wesnoth.copy_unit(enemy)
-- First get the reach of the enemy with full moves though
enemy_copy.moves = enemy_copy.max_moves
local reach = wesnoth.find_reach(enemy_copy, { ignore_units = true })
enemy_copy.x = unit.x
enemy_copy.y = unit.y + 1 -- this even works at map border
local _, _, att_weapon, _ = wesnoth.simulate_combat(enemy_copy, unit)
local max_damage = att_weapon.damage * att_weapon.num_blows
local unit_damage_map = LS.create()
for _,loc in ipairs(reach) do
unit_damage_map:insert(loc[1], loc[2], max_damage)
for xa,ya in H.adjacent_tiles(loc[1], loc[2]) do
unit_damage_map:insert(xa, ya, max_damage)
end
end
enemy_damage_map:union_merge(unit_damage_map, function(x, y, v1, v2)
return (v1 or 0) + v2
end)
end
-- Penalties for damage by enemies
local enemy_rating_map = LS.create()
enemy_damage_map:iter(function(x, y, enemy_damage)
local hit_chance = (wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y))) / 100.
local rating = hit_chance * enemy_damage
rating = rating / unit.max_hitpoints
rating = rating * 5
enemy_rating_map:insert(x, y, rating)
end)
-- Penalties for blocked hexes and ZOC
local is_skirmisher = wesnoth.unit_ability(unit, "skirmisher")
for _,enemy in ipairs(enemies) do
-- Hexes an enemy is on get a very large penalty
enemy_rating_map:insert(enemy.x, enemy.y, (enemy_rating_map:get(enemy.x, enemy.y) or 0) + 100)
-- Hexes adjacent to enemies get max_moves penalty
-- except if AI unit is skirmisher or enemy is level 0
local zoc_active = (not is_skirmisher)
if zoc_active then
local level = enemy.level
if (level == 0) then zoc_active = false end
end
if zoc_active then
for xa,ya in H.adjacent_tiles(enemy.x, enemy.y) do
enemy_rating_map:insert(xa, ya, (enemy_rating_map:get(xa, ya) or 0) + unit.max_moves)
end
end
end
-- Preferred hexes (do this here once for all hexes, so that it does not need
-- to get done for every step of the a* search.
-- We only need to know whether a hex is preferred or not, there's no additional rating.
local prefer_slf = H.get_child(cfg, "prefer")
local prefer_map -- want this to be nil, not empty LS if [prefer] tag not given
if prefer_slf then
local preferred_hexes = wesnoth.get_locations(prefer_slf)
prefer_map = LS.create()
for _,hex in ipairs(preferred_hexes) do
prefer_map:insert(hex[1], hex[2], true)
end
end
local path, cost = wesnoth.find_path(unit, target.x, target.y,
function(x, y, current_cost)
return custom_cost(x, y, unit, enemy_rating_map, prefer_map)
end
)
local path_map = LS.of_pairs(path)
-- We need to pick the farthest reachable hex along that path
local farthest_hex = path[1]
for i = 2,#path do
local sub_path, sub_cost = wesnoth.find_path(unit, path[i][1], path[i][2], cfg)
if sub_cost <= unit.moves then
local unit_in_way = wesnoth.get_unit(path[i][1], path[i][2])
if not unit_in_way then
farthest_hex = path[i]
end
else
break
end
end
if farthest_hex then
AH.checked_move_full(ai, unit, farthest_hex[1], farthest_hex[2])
else
AH.checked_stopunit_moves(ai, unit)
end
end
return ca_assassin_move

View file

@ -319,7 +319,7 @@ function ca_bottleneck_move:evaluation(cfg, data)
local unit_in_way = wesnoth.get_unit(xa, ya)
local data = { x = xa, y = ya,
defender = enemy,
defender_level = wesnoth.unit_types[enemy.type].level,
defender_level = enemy.level,
unit_in_way = unit_in_way
}
table.insert(attacks, data)

View file

@ -77,7 +77,7 @@ function ca_fast_combat:evaluation(cfg, data)
local unit_info = FAU.get_unit_info(unit, data.gamedata)
local unit_copy = FAU.get_unit_copy(unit.id, data.gamedata)
if (unit.attacks_left > 0) and (H.get_child(unit.__cfg, 'attack')) then
if (unit.attacks_left > 0) and (#unit.attacks > 0) then
local attacks = AH.get_attacks({ unit }, { include_occupied = cfg.include_occupied_attack_hexes })
if (#attacks > 0) then

View file

@ -37,7 +37,7 @@ function ca_fast_combat_leader:evaluation(cfg, data)
end
end
if (leader.attacks_left == 0) or (not H.get_child(leader.__cfg, 'attack')) then return 0 end
if (leader.attacks_left == 0) or (#leader.attacks == 0) then return 0 end
local excluded_enemies_map = LS.create()

View file

@ -7,7 +7,7 @@ function ca_healer_may_attack:evaluation()
-- After attacks by all other units are done, reset things so that healers can attack, if desired
-- This will be blacklisted after first execution each turn
local score = 99990
local score = 99900
return score
end

View file

@ -88,7 +88,7 @@ function ca_messenger_move:execution(cfg)
-- 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
if (#messenger.attacks == 0) then return end
local targets = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },

View file

@ -4,7 +4,7 @@ local internal_params = {}
-- The following external engine creates the CA functions recruit_rushers_eval and recruit_rushers_exec
-- It also exposes find_best_recruit and find_best_recruit_hex for use by other recruit engines
wesnoth.require("ai/lua/generic_recruit_engine.lua").init(ai, internal_recruit_cas, internal_params)
wesnoth.require("ai/lua/generic_recruit_engine.lua").init(internal_recruit_cas, internal_params)
local ca_recruit_rushers = {}

View file

@ -17,7 +17,7 @@ function ca_return_guardian:evaluation(cfg)
local guardian = get_guardian(cfg)
if guardian then
if (guardian.x == cfg.return_x) and (guardian.y == cfg.return_y) then
return cfg.ca_score - 20
return cfg.ca_score - 200
else
return cfg.ca_score
end

View file

@ -1,4 +1,4 @@
fai 'lurker_moves.fai'
wfl 'lurker_moves.fai'
# run_file('ai/micro_ais/engines/lurker_moves.fai') #
def is_swamp(map, xx, yy)
@ -45,4 +45,4 @@ where possible_attacks = reachable_enemies_next_to_swamp( me.loc, my_attacks,map
where swamp_in_reach = reachable_swamp(me.loc,map)
faiend
wflend

View file

@ -46,7 +46,7 @@ function wesnoth.micro_ais.wolves(cfg)
aspect = "attacks",
facet = {
name = "ai_default_rca::aspect_attacks",
id = "dont_attack",
id = "mai_wolves_" .. cfg.ca_id .. "_dont_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_enemy", {
{ "not", {

View file

@ -71,6 +71,11 @@ function wesnoth.micro_ais.fast_ai(cfg)
}
else
if (not cfg.skip_combat_ca) then
W.modify_ai {
side = cfg.side,
action = "try_delete",
path = "stage[main_loop].candidate_action[high_xp_attack]"
}
W.modify_ai {
side = cfg.side,
action = "try_delete",

View file

@ -34,7 +34,7 @@ function wesnoth.micro_ais.return_guardian(cfg)
local optional_keys = { "id", "[filter]" }
local CA_parms = {
ai_id = 'mai_return_guardian',
{ ca_id = 'move', location = 'ca_return_guardian.lua', score = cfg.ca_score or 100010 }
{ ca_id = 'move', location = 'ca_return_guardian.lua', score = cfg.ca_score or 100100 }
}
return required_keys, optional_keys, CA_parms
end

View file

@ -11,7 +11,7 @@ function wesnoth.micro_ais.healer_support(cfg)
-- The healers_can_attack CA is only added to the table if aggression ~= 0
-- But: make sure we always try removal
if (cfg.action == 'delete') or (tonumber(cfg.aggression) ~= 0) then
table.insert(CA_parms, { ca_id = 'may_attack', location = 'ca_healer_may_attack.lua', score = 99990 })
table.insert(CA_parms, { ca_id = 'may_attack', location = 'ca_healer_may_attack.lua', score = 99900 })
end
return {}, optional_keys, CA_parms
end

View file

@ -1,3 +1,13 @@
function wesnoth.micro_ais.assassin(cfg)
local required_keys = { "[filter]", "[filter_second]" }
local optional_keys = { "[prefer]" }
local CA_parms = {
ai_id = 'mai_assassin',
{ ca_id = 'attack', location = 'ca_simple_attack.lua', score = 110001 },
{ ca_id = 'move', location = 'ca_assassin_move.lua', score = 110000 }
}
return required_keys, optional_keys, CA_parms
end
function wesnoth.micro_ais.lurkers(cfg)
local required_keys = { "[filter]", "[filter_location]" }

View file

@ -41,7 +41,7 @@ function wesnoth.micro_ais.protect_unit(cfg)
aspect = "attacks",
facet = {
name = "ai_default_rca::aspect_attacks",
ca_id = "dont_attack",
id = "mai_protect_" .. cfg.ca_id .. "_dont_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_own", {
{ "not", {

View file

@ -170,7 +170,7 @@ function micro_ai_helper.delete_aspects(side, aspect_parms)
W.modify_ai {
side = side,
action = "try_delete",
path = "aspect[attacks].facet[" .. parms.aspect_id .. "]"
path = "aspect[attacks].facet[" .. parms.facet.id .. "]"
}
end
end

View file

@ -19,7 +19,7 @@ wesnoth.require("ai/micro_ais/mai-defs/recruiting.lua")
function wesnoth.wml_actions.micro_ai(cfg)
local CA_path = 'ai/micro_ais/cas/'
cfg = cfg.__parsed
cfg = cfg.__shallow_parsed
-- Check that the required common keys are all present and set correctly
if (not cfg.ai_type) then H.wml_error("[micro_ai] is missing required ai_type= key") end

View file

@ -510,11 +510,14 @@ Note: This is a demonstration of how the Goto Micro AI can be used to code guard
variable=stored_ghosts
[/store_unit]
{FOREACH stored_ghosts i_g}
[kill]
id=$stored_ghosts[$i_g].id
[/kill]
{NEXT i_g}
[foreach]
array=stored_ghosts
[do]
[kill]
id=$this_item.id
[/kill]
[/do]
[/foreach]
{CLEAR_VARIABLE stored_ghosts}
[/event]

View file

@ -411,9 +411,12 @@ separate attack Zone"
variable=tmp_units
kill=no
[/store_unit]
{FOREACH tmp_units i}
{SET_LABEL $tmp_units[$i].x $tmp_units[$i].y $tmp_units[$i].variables.label}
{NEXT i}
[foreach]
array=tmp_units
[do]
{SET_LABEL $this_item.x $this_item.y $this_item.variables.label}
[/do]
[/foreach]
{CLEAR_VARIABLE tmp_units}
# The right-click menu items

View file

@ -10,15 +10,19 @@
{VARIABLE minimum 99999}
# Go through container variable
{FOREACH {CONTAINER} i_min}
{IF_VAR {CONTAINER}[$i_min].{VAR} less_than $minimum (
[then]
{VARIABLE minimum ${CONTAINER}[$i_min].{VAR}}
{VARIABLE min_index $i_min}
[/then]
)}
#{DEBUG "$i_min: $minimum (${CONTAINER}[$i_min].{VAR})"}
{NEXT i_min}
[for]
array={CONTAINER}
variable=i_min
[do]
{IF_VAR {CONTAINER}[$i_min].{VAR} less_than $minimum (
[then]
{VARIABLE minimum ${CONTAINER}[$i_min].{VAR}}
{VARIABLE min_index $i_min}
[/then]
)}
#{DEBUG "$i_min: $minimum (${CONTAINER}[$i_min].{VAR})"}
[/do]
[/for]
#enddef
#define LURKER_MOVES SIDE ENEMY_SIDES
@ -44,123 +48,131 @@
[/store_unit]
# For each Lurker, we do:
{FOREACH stored_lurkers i_l}
#{DEBUG "Lurker $i_l"}
[for]
array=stored_lurkers
variable=i_l
[do]
#{DEBUG "Lurker $i_l"}
# Store reachable swamp locations next to an enemy
# This should include the current location
[store_reachable_locations]
[filter]
id=$stored_lurkers[$i_l].id
[/filter]
[filter_location]
terrain=S* # swamp
[not] # unoccupied by other unit
[filter]
[not]
id=$stored_lurkers[$i_l].id
[/not]
[/filter]
[/not]
[filter_adjacent_location] # next to enemy
[filter]
side={ENEMY_SIDES}
[/filter]
[/filter_adjacent_location]
[/filter_location]
moves=max
variable=stored_locs
[/store_reachable_locations]
#{DEBUG " reachable with enemy adjacent: $stored_locs.length"}
# Now find all those enemies and store
# Doesn't matter if some enemies are stored several times
{FOREACH stored_locs i_e}
[store_unit]
# Store reachable swamp locations next to an enemy
# This should include the current location
[store_reachable_locations]
[filter]
side={ENEMY_SIDES}
[filter_location]
[filter_adjacent_location]
x,y=$stored_locs[$i_e].x,$stored_locs[$i_e].y
[/filter_adjacent_location]
[/filter_location]
id=$stored_lurkers[$i_l].id
[/filter]
variable=adj_enemies
mode=append
[/store_unit]
#{DEBUG " $i_e: enemies adjacent to current location: $adj_enemies.length"}
{NEXT i_e}
[filter_location]
terrain=S* # swamp
[not] # unoccupied by other unit
[filter]
[not]
id=$stored_lurkers[$i_l].id
[/not]
[/filter]
[/not]
[filter_adjacent_location] # next to enemy
[filter]
side={ENEMY_SIDES}
[/filter]
[/filter_adjacent_location]
[/filter_location]
moves=max
variable=stored_locs
[/store_reachable_locations]
#{DEBUG " reachable with enemy adjacent: $stored_locs.length"}
{IF_VAR adj_enemies.length equals 0 (
[then] # if there is no reachable enemy
# Simply pick a random reachable swamp hex (Lurkers are pretty stupid)
# (there is always at least the hex the unit is on)
[store_reachable_locations]
[filter]
id=$stored_lurkers[$i_l].id
[/filter]
[filter_location]
terrain=S* # swamp
[not] # unoccupied by other unit
[filter]
[not]
id=$stored_lurkers[$i_l].id
[/not]
[/filter]
[/not]
[/filter_location]
moves=max
variable=stored_locs
[/store_reachable_locations]
#{DEBUG "Reachable hexes: $stored_locs.length"}
[/then]
# Now find all those enemies and store
# Doesn't matter if some enemies are stored several times
[for]
array=stored_locs
variable=i_e
[do]
[store_unit]
[filter]
side={ENEMY_SIDES}
[filter_location]
[filter_adjacent_location]
x,y=$stored_locs[$i_e].x,$stored_locs[$i_e].y
[/filter_adjacent_location]
[/filter_location]
[/filter]
variable=adj_enemies
mode=append
[/store_unit]
#{DEBUG " $i_e: enemies adjacent to current location: $adj_enemies.length"}
[/do]
[/for]
[else] # if there are reachable enemies
# We simply find the enemies with minimum hitpoints (Lurkers are pretty stupid)
{MINIMUM adj_enemies hitpoints}
# Unfortunately, now we need to store the reachable location again
[store_reachable_locations]
[filter]
id=$stored_lurkers[$i_l].id
[/filter]
[filter_location]
terrain=S* # swamp
[not] # unoccupied by other unit
[filter]
[not]
id=$stored_lurkers[$i_l].id
[/not]
[/filter]
[/not]
[filter_adjacent_location]
[filter]
x,y=$adj_enemies[$min_index].x,$adj_enemies[$min_index].y
[/filter]
[/filter_adjacent_location]
[/filter_location]
moves=max
variable=stored_locs
[/store_reachable_locations]
#{DEBUG " final possible reachable locations: $stored_locs.length"}
[/else]
)}
{IF_VAR adj_enemies.length equals 0 (
[then] # if there is no reachable enemy
# Simply pick a random reachable swamp hex (Lurkers are pretty stupid)
# (there is always at least the hex the unit is on)
[store_reachable_locations]
[filter]
id=$stored_lurkers[$i_l].id
[/filter]
[filter_location]
terrain=S* # swamp
[not] # unoccupied by other unit
[filter]
[not]
id=$stored_lurkers[$i_l].id
[/not]
[/filter]
[/not]
[/filter_location]
moves=max
variable=stored_locs
[/store_reachable_locations]
#{DEBUG "Reachable hexes: $stored_locs.length"}
[/then]
# Move unit to one of those at random
{RANDOM "0..$($stored_locs.length-1)"}
#{DEBUG "$stored_lurkers[$i_l].x $stored_lurkers[$i_l].y : $random"}
[if] # only if different from current position
{VARIABLE_CONDITIONAL stored_locs[$random].x not_equals $stored_lurkers[$i_l].x}
[or]
{VARIABLE_CONDITIONAL stored_locs[$random].y not_equals $stored_lurkers[$i_l].y}
[/or]
[then]
{MOVE_UNIT id=$stored_lurkers[$i_l].id $stored_locs[$random].x $stored_locs[$random].y}
[/then]
[/if]
[else] # if there are reachable enemies
# We simply find the enemies with minimum hitpoints (Lurkers are pretty stupid)
{MINIMUM adj_enemies hitpoints}
# Unfortunately, now we need to store the reachable location again
[store_reachable_locations]
[filter]
id=$stored_lurkers[$i_l].id
[/filter]
[filter_location]
terrain=S* # swamp
[not] # unoccupied by other unit
[filter]
[not]
id=$stored_lurkers[$i_l].id
[/not]
[/filter]
[/not]
[filter_adjacent_location]
[filter]
x,y=$adj_enemies[$min_index].x,$adj_enemies[$min_index].y
[/filter]
[/filter_adjacent_location]
[/filter_location]
moves=max
variable=stored_locs
[/store_reachable_locations]
#{DEBUG " final possible reachable locations: $stored_locs.length"}
[/else]
)}
# Need to clear adj_enemies inside the loop, as mode=append
{CLEAR_VARIABLE adj_enemies,minimum,min_index,random,stored_locs}
{NEXT i_l}
# Move unit to one of those at random
{RANDOM "0..$($stored_locs.length-1)"}
#{DEBUG "$stored_lurkers[$i_l].x $stored_lurkers[$i_l].y : $random"}
[if] # only if different from current position
{VARIABLE_CONDITIONAL stored_locs[$random].x not_equals $stored_lurkers[$i_l].x}
[or]
{VARIABLE_CONDITIONAL stored_locs[$random].y not_equals $stored_lurkers[$i_l].y}
[/or]
[then]
{MOVE_UNIT id=$stored_lurkers[$i_l].id $stored_locs[$random].x $stored_locs[$random].y}
[/then]
[/if]
# Need to clear adj_enemies inside the loop, as mode=append
{CLEAR_VARIABLE adj_enemies,minimum,min_index,random,stored_locs}
[/do]
[/for]
{CLEAR_VARIABLE stored_lurkers}
# Attack is then left to the AI

View file

@ -71,6 +71,7 @@ Gs^Fp , Gs^Fp , Wwf , Wwf , Mm , Rd
{AI_CA_GOTO}
{AI_CA_MOVE_LEADER_TO_GOALS}
{AI_CA_MOVE_LEADER_TO_KEEP}
{AI_CA_HIGH_XP_ATTACK}
{AI_CA_COMBAT}
{AI_CA_HEALING}
{AI_CA_VILLAGES}

View file

@ -0,0 +1,174 @@
#textdomain wesnoth-ai
[test]
id=high_xp_attack
name="High XP Attack"
map_data="{campaigns/The_Hammer_of_Thursagan/maps/12_The_Underlevels.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=yes
[side]
side=1
controller=human
id=player
name="Sly Player"
type=Necromancer
x,y=17,58
persistent=no
facing=sw
team_name=player
user_team_name="Sly Player"
recruit=Skeleton,Skeleton Archer
gold=0
[/side]
[side]
type=Dwarvish Steelclad
id=dwarf
name="Fearless AI Leader"
side=2
x,y=8,53
facing=se
team_name=dwarves
user_team_name="Fearless AI"
recruit=Dwarvish Fighter,Dwarvish Scout,Dwarvish Thunderer
gold=0
[/side]
# Prestart actions
[event]
name=prestart
[terrain]
x,y=21,55
terrain=Rd
[/terrain]
{UNIT 1 (Skeleton) 6 58 (random_traits,experience=no,33)}
{UNIT 2 (Dwarvish Scout) 6 56 (random_traits=no)}
{UNIT 2 (Dwarvish Scout) 4 58 (random_traits=no)}
{UNIT 2 (Dwarvish Fighter) 8 59 (random_traits=no)}
{UNIT 1 (Skeleton) 23 55 (random_traits,experience,hitpoints=no,34,10)}
{UNIT 2 (Dwarvish Scout) 27 54 (random_traits=no)}
# Groups of units with enemy 2 XP from leveling
{UNIT 1 (Revenant) 8 29 (random_traits,experience=no,83)}
{UNIT 2 (Dwarvish Scout) 5 29 (random_traits=no)}
{UNIT 2 (Dwarvish Scout) 5 30 (random_traits=no)}
{UNIT 2 (Dwarvish Steelclad) 6 29 (random_traits=no)}
{UNIT 1 (Revenant) 18 29 (random_traits,experience=no,83)}
{UNIT 2 (Dwarvish Fighter) 21 29 (random_traits,hitpoints=no,2)}
{UNIT 2 (Dwarvish Fighter) 21 30 (random_traits,hitpoints=no,2)}
{UNIT 2 (Dwarvish Runesmith) 20 29 (random_traits=no)}
# Move-out-of-way triple
{UNIT 1 (Skeleton) 20 54 (random_traits,experience=no,34)}
{UNIT 2 (Dwarvish Runesmith) 19 54 (random_traits=no)}
{UNIT 2 (Dwarvish Scout) 14 51 (random_traits=no)}
# Revenant - Fighter pairs
{UNIT 1 (Revenant) 32 41 (random_traits,experience=no,84)}
{UNIT 1 (Revenant) 46 41 (random_traits,experience,hitpoints=no,84,25)}
{UNIT 2 (Dwarvish Fighter) 35 40 (random_traits,hitpoints=no,7)}
{UNIT 2 (Dwarvish Fighter) 43 40 (random_traits,hitpoints=no,18)}
# Poisoned enemies
{UNIT 1 (Ghoul) 52 55 (random_traits,experience,hitpoints=no,34,16)}
[+unit]
[status]
poisoned=yes
[/status]
[/unit]
{UNIT 1 (Ghoul) 56 55 (random_traits,experience,hitpoints=no,34,24)}
[+unit]
[status]
poisoned=yes
[/status]
[/unit]
{UNIT 2 (Dwarvish Fighter) 54 54 (random_traits=no)}
{UNIT 2 (Dwarvish Fighter) 54 55 (random_traits=no)}
[/event]
[event]
name=start
[message]
id=player
message="Hahaha! I have placed my units at strategic choke points and given them XP close to leveling. I am safe from the stupid Wesnoth AI."
[/message]
[message]
id=dwarf
message="You're in for a nasty surprise ..."
[/message]
[message]
speaker=narrator
image="wesnoth-icon.png"
caption="Note"
message="This is a test scenario for a new AI algorithm that attacks units close to leveling. A few test cases are already set up on the map, but it is really expected that you add more units and/or change hitpoints and experience using debug commands to try out other situations."
[/message]
[message]
x,y=52,55
message="Poisoned units are only attacked if there is a chance to kill them. Otherwise we simply wait and let the poison do its work."
[/message]
[message]
x,y=19,54
message="The scout in the northwest is the better choice for forcing the skeleton to level up, so I'll move out of the way for him."
[/message]
[message]
x,y=8,29
message="Here we have a unit 2 XP from leveling with both L1 and L2 AI units in reach. In this case, we wait to see what the default AI does. After the default AI attacks with one of the L1 units and the enemy is 1 XP from leveling, we execute a level-up attack."
[/message]
[message]
x,y=18,29
message="This is an equivalent setup, except that the default AI chooses not to attack with the (weakened) L1 units. In this case, we execute the level-up attack with the L2 unit after the default AI is done."
[/message]
[message]
x,y=35,40
message="There's a high chance that I will die in attacking that revenant, so the AI will not attack with aggression=0.4 (the default). By contrast, with aggression=1 (which you can set in a moment), it does attack."
[/message]
[message]
x,y=43,40
message="I have a much lower chance to die and will attack even with the default setting for aggression."
[/message]
[message]
speaker=narrator
image=wesnoth-icon.png
message="What value should we use for aggression for the AI side?"
[option]
message="aggression 0.4 (the default)"
[/option]
[option]
message="aggression 1.0"
[command]
[modify_side]
side=2
[ai]
aggression=1
[/ai]
[/modify_side]
[/command]
[/option]
[/message]
[objectives]
[note]
description="Modify the units on the map as desired, then end the turn"
[/note]
[/objectives]
[/event]
[/test]

View file

@ -50,7 +50,6 @@
[side]
side=1
controller=human
recruit="Elvish Archer,Elvish Fighter,Elvish Scout,Elvish Shaman"
{GOLD 200 150 100}
income=0
team_name=Elves

View file

@ -36,7 +36,6 @@
[side]
side=1
controller=human
recruit="Elvish Archer,Elvish Fighter,Elvish Scout,Elvish Shaman"
type=Elvish Lord
team_name=Elves
user_team_name= _ "Elves"

View file

@ -44,7 +44,6 @@
[side]
side=1
controller=human
recruit="Elvish Archer,Elvish Fighter,Elvish Scout,Elvish Shaman"
{INCOME 12 9 9}
team_name=Elves
user_team_name= _ "Elves"
@ -233,13 +232,7 @@
[event]
name=victory
[role]
race=elf
[not]
canrecruit=yes
[/not]
role=advisor
[/role]
{RECALL_ADVISOR}
[message]
role=advisor
@ -320,13 +313,7 @@
[event]
name=time over
[role]
race=elf
[not]
canrecruit=yes
[/not]
role=advisor
[/role]
{PROMOTE_ADVISOR}
[message]
role=advisor

View file

@ -40,7 +40,6 @@
[side]
side=1
controller=human
recruit="Elvish Archer,Elvish Fighter,Elvish Scout,Elvish Shaman"
team_name=Elves
user_team_name= _ "Elves"
{FLAG_VARIANT wood-elvish}
@ -229,13 +228,7 @@
[event]
name=enemies defeated
[role]
race=elf
[not]
canrecruit=yes
[/not]
role=advisor
[/role]
{RECALL_ADVISOR}
[message]
role=advisor
@ -283,13 +276,7 @@
id=Erlornas
[/filter]
[role]
race=elf
[not]
canrecruit=yes
[/not]
role=advisor
[/role]
{PROMOTE_ADVISOR}
[message]
speaker=second_unit
@ -318,6 +305,7 @@
[event]
name=time over
{RECALL_ADVISOR}
[message]
role=advisor
message= _ "We cant get through, my Lord. These whelps are not individually very dangerous, but there are huge numbers of them."

View file

@ -41,7 +41,6 @@
[side]
side=1
controller=human
recruit="Elvish Archer,Elvish Fighter,Elvish Scout,Elvish Shaman"
{GOLD 100 100 100}
{INCOME 0 0 0}
team_name=Elves

View file

@ -24,7 +24,6 @@
[side]
side=1
controller=human
recruit="Elvish Archer,Elvish Fighter,Elvish Scout,Elvish Shaman"
team_name=Elves
user_team_name= _ "Elves"
{FLAG_VARIANT wood-elvish}
@ -94,17 +93,7 @@
id=Linaera
[/recall]
[role]
type="Red Mage,White Mage,Mage,Arch Mage,Silver Mage,Mage of Light,Great Mage"
[not]
canrecruit=yes
[/not]
role=mage
[/role]
[recall]
role=mage
[/recall]
{RECALL_MAGE}
{MODIFY_UNIT (side=1) facing se}
[/event]
@ -121,13 +110,7 @@
[event]
name=enemies defeated
[role]
type="Red Mage,White Mage,Mage,Arch Mage,Silver Mage,Mage of Light,Great Mage"
[not]
canrecruit=yes
[/not]
role=mage
[/role]
{RECALL_MAGE}
[message]
speaker=Linaera

View file

@ -23,7 +23,6 @@
[side]
side=1
controller=human
recruit="Elvish Archer,Elvish Fighter,Elvish Scout,Elvish Shaman"
gold=100
income=0
team_name=Elves
@ -247,13 +246,7 @@
side=2
[/kill]
[role]
race=elf
[not]
canrecruit=yes
[/not]
role=advisor
[/role]
{RECALL_ADVISOR}
[message]
speaker=Erlornas

View file

@ -6,6 +6,7 @@
name= _ "Erlornas"
profile=portraits/erlornas.png
canrecruit=yes
extra_recruit="Elvish Archer,Elvish Fighter,Elvish Scout,Elvish Shaman"
unrenamable=yes
#enddef
@ -16,6 +17,7 @@
profile=portraits/linaera.png
gender=female
canrecruit=yes
extra_recruit="Mage"
unrenamable=yes
[modifications]
{TRAIT_LOYAL}

View file

@ -9,44 +9,18 @@
#enddef
#define RECALL_ADVISOR
[if]
[have_unit]
side=1
role=advisor
search_recall_list=yes
[/have_unit]
[then]
# Recall an advisor if we have one to do so
[recall]
role=advisor
[/recall]
[/then]
[elseif]
# Else, make a new advisor from an elf unit
[have_unit]
side=1
race=elf
[not]
canrecruit=yes
[/not]
search_recall_list=yes
[/have_unit]
[then]
[role]
race=elf
[not]
canrecruit=yes
[/not]
role=advisor
[/role]
[role]
role=advisor
[auto_recall][/auto_recall]
search_recall_list=yes
side=1
race=elf
[not]
id=Erlornas
[/not]
[recall]
role=advisor
[/recall]
[/then]
[/elseif]
[else]
# If that fails too, make a brand new advisor
[unit]
type=Elvish Fighter
side=1
@ -55,5 +29,42 @@
placement=leader
[/unit]
[/else]
[/if]
[/role]
#enddef
#define PROMOTE_ADVISOR
[role]
role=advisor
search_recall_list=no
side=1
race=elf
[not]
id=Erlornas
[/not]
[/role]
#enddef
#define RECALL_MAGE
[role]
role=mage
[auto_recall][/auto_recall]
search_recall_list=yes
side=1
type="Great Mage,Mage of Light,Arch Mage,Silver Mage,White Mage,Red Mage,Mage"
[not]
id=Linaera
[/not]
[else]
[unit]
type=Mage
side=1
role=mage
animate=yes
placement=leader
[/unit]
[/else]
[/role]
#enddef

View file

@ -65,7 +65,7 @@
{campaigns/Dead_Water/utils}
[+units]
[units]
{campaigns/Dead_Water/units}
[/units]

View file

@ -484,21 +484,24 @@
# Loop through the zombified villages to see if that
# location has already been stored:
{FOREACH zombified_villages loop}
[if]
[variable]
name=village_location
equals=$zombified_villages[$loop].location
[/variable]
[foreach]
array=zombified_villages
[do]
[if]
[variable]
name=village_location
equals=$this_item.location
[/variable]
[then]
[set_variable]
name=already_zombified
value=yes
[/set_variable]
[/then]
[/if]
{NEXT loop}
[then]
[set_variable]
name=already_zombified
value=yes
[/set_variable]
[/then]
[/if]
[/do]
[/foreach]
# If the village's location was not found in the
# array, it hasn't been taken over yet, so zombify

View file

@ -296,14 +296,14 @@
[message]
speaker=narrator
message= _ "The trident is 14-2, magical, ranged, with <i>fire</i> damage."
message= _ "The trident is 14×2, magical, ranged, with <i>fire</i> damage."
image=wesnoth-icon.png
[/message]
[message]
speaker=$unit.id
[option]
message= _ "That sounds great! Ill take it."
label= _ "That sounds great! Ill take it."
[command]
[set_variable]
name=get_trident
@ -312,7 +312,7 @@
[/command]
[/option]
[option]
message= _ "That sounds frightening. Let someone else have it."
label= _ "That sounds frightening. Let someone else have it."
[/option]
[/message]
@ -350,7 +350,7 @@
[message]
speaker=$unit.id
[option]
message= _ "Let me have that trident. I want to control lightning!"
label= _ "Let me have that trident. I want to control lightning!"
[command]
[set_variable]
name=get_trident
@ -359,7 +359,7 @@
[/command]
[/option]
[option]
message= _ "Ill just leave that trident where it is."
label= _ "Ill just leave that trident where it is."
[/option]
[/message]
[/then]

View file

@ -334,12 +334,15 @@
[/filter]
variable=undead
[/store_unit]
{FOREACH undead loop}
[set_variable]
name=number_of_undead
add=1
[/set_variable]
{NEXT loop}
[foreach]
array=undead
[do]
[set_variable]
name=number_of_undead
add=1
[/set_variable]
[/do]
[/foreach]
[clear_variable]
name=undead
[/clear_variable]
@ -602,15 +605,18 @@
kill=yes
variable=orcs
[/store_unit]
{FOREACH orcs orc}
[set_variable]
name=orcs[$orc].moves
value=0
[/set_variable]
[unstore_unit]
variable=orcs[$orc]
[/unstore_unit]
{NEXT orc}
[foreach]
array=orcs
[do]
[set_variable]
name=this_item.moves
value=0
[/set_variable]
[unstore_unit]
variable=this_item
[/unstore_unit]
[/do]
[/foreach]
[clear_variable]
name=orcs
[/clear_variable]
@ -664,7 +670,7 @@
speaker=Kai Krellis
message= _ "We have destroyed the undead."
[option]
message= _ "Now maybe we can head west undisturbed."
label= _ "Now maybe we can head west undisturbed."
[command]
[endlevel]
result=victory
@ -673,7 +679,7 @@
[/command]
[/option]
[option]
message= _ "This orc leader has not learned that threatening merfolk is a bad idea. We shall defeat him before we go."
label= _ "This orc leader has not learned that threatening merfolk is a bad idea. We shall defeat him before we go."
[/option]
[/message]
[/else]

View file

@ -178,19 +178,21 @@
[/store_locations]
# Put a bat into each chasm hex.
{FOREACH chasm_hexes hex}
{RANDOM ({BAT_TYPES_VALUE})}
[unit]
type=$random
x=$chasm_hexes[$hex].x
y=$chasm_hexes[$hex].y
side=2
animate=yes # The animation fades in, which looks good. If the animation is ever changed to something else, this might have to be changed.
[/unit]
{NEXT hex}
[foreach]
array=chasm_hexes
[do]
{RANDOM ({BAT_TYPES_VALUE})}
[unit]
type=$random
x=$this_item.x
y=$this_item.y
side=2
animate=yes # The animation fades in, which looks good. If the animation is ever changed to something else, this might have to be changed.
[/unit]
[/do]
[/foreach]
{CLEAR_VARIABLE chasm_hexes}
{CLEAR_VARIABLE hex}
#enddef
#define BATS_ENTER_CAVE
@ -227,19 +229,22 @@
variable=bats
[/store_unit]
{FOREACH bats bat}
{NEAREST_HEX $bats[$bat].x $bats[$bat].y 99 (terrain=Qx*) chasm_hex}
[kill]
id=$bats[$bat].id
animate=no
[/kill]
[move_unit_fake]
type=$bats[$bat].type
x=$bats[$bat].x, $chasm_hex.x
y=$bats[$bat].y, $chasm_hex.y
side=2
[/move_unit_fake]
{NEXT bat}
[foreach]
array=bats
[do]
{NEAREST_HEX $this_item.x $this_item.y 99 (terrain=Qx*) chasm_hex}
[kill]
id=$this_item.id
animate=no
[/kill]
[move_unit_fake]
type=$this_item.type
x=$this_item.x, $chasm_hex.x
y=$this_item.y, $chasm_hex.y
side=2
[/move_unit_fake]
[/do]
[/foreach]
{CLEAR_VARIABLE bats}
{CLEAR_VARIABLE chasm_hex}

View file

@ -952,14 +952,14 @@
[message]
speaker=narrator
message= _ "This sword is 8-4, magical, with <i>fire</i> damage."
message= _ "This sword is 8×4, magical, with <i>fire</i> damage."
image=wesnoth-icon.png
[/message]
[message]
speaker=unit
[option]
message= _ "Ill carry this sword and destroy undead with blasts of flame."
label= _ "Ill carry this sword and destroy undead with blasts of flame."
[command]
[set_variable]
name=get_sword
@ -968,7 +968,7 @@
[/command]
[/option]
[option]
message= _ "This sword is not right for me. Let someone else have it."
label= _ "This sword is not right for me. Let someone else have it."
[/option]
[/message]
@ -1003,7 +1003,7 @@
[message]
speaker=unit
[option]
message= _ "Ill carry the sword."
label= _ "Ill carry the sword."
[command]
[set_variable]
name=get_sword
@ -1012,7 +1012,7 @@
[/command]
[/option]
[option]
message= _ "Let someone else have it."
label= _ "Let someone else have it."
[/option]
[/message]
[if]

View file

@ -826,7 +826,7 @@
[message]
speaker=$unit.id
[option]
message= _ "That sounds great! Ill take it."
label= _ "That sounds great! Ill take it."
[command]
[set_variable]
name=get_necklace
@ -835,7 +835,7 @@
[/command]
[/option]
[option]
message= _ "It doesnt seem to have helped its previous owner. I dont want it."
label= _ "It doesnt seem to have helped its previous owner. I dont want it."
[/option]
[/message]
@ -872,7 +872,7 @@
[message]
speaker=$unit.id
[option]
message= _ "I would like my life force protected."
label= _ "I would like my life force protected."
[command]
[set_variable]
name=get_necklace
@ -881,7 +881,7 @@
[/command]
[/option]
[option]
message= _ "My life force feels fine as it is."
label= _ "My life force feels fine as it is."
[/option]
[/message]
[/then]

View file

@ -5,7 +5,6 @@
profile="portraits/brawler.png"
race=merman
image="units/merfolk/brawler.png"
{MAGENTA_IS_THE_TEAM_COLOR}
hitpoints=38
movement_type=swimmer
movement=6
@ -211,13 +210,16 @@
variable=stunned
[/store_unit]
{FOREACH stunned i}
{CLEAR_VARIABLE stunned[$i].status.stunned}
[foreach]
array=stunned
[do]
{CLEAR_VARIABLE this_item.status.stunned}
[unstore_unit]
variable=stunned[$i]
[/unstore_unit]
{NEXT i}
[unstore_unit]
variable=this_item
[/unstore_unit]
[/do]
[/foreach]
{CLEAR_VARIABLE stunned}
[/event]

View file

@ -10,7 +10,6 @@
movement=6
experience=25
level=0
{MAGENTA_IS_THE_TEAM_COLOR}
alignment=lawful
advances_to=Merman Young King
cost=8

View file

@ -5,7 +5,6 @@
profile="portraits/brawler.png"
race=merman
image="units/merfolk/citizen.png"
{MAGENTA_IS_THE_TEAM_COLOR}
hitpoints=20
movement_type=swimmer
movement=6

View file

@ -16,7 +16,6 @@
movement=7
experience=76
level=2
{MAGENTA_IS_THE_TEAM_COLOR}
alignment=lawful
advances_to=Merman Warrior King
cost=36

View file

@ -17,7 +17,6 @@
experience=150
level=3
{AMLA_DEFAULT}
{MAGENTA_IS_THE_TEAM_COLOR}
alignment=lawful
cost=60
usage=fighter

View file

@ -16,7 +16,6 @@
movement=6
experience=38
level=1
{MAGENTA_IS_THE_TEAM_COLOR}
alignment=lawful
advances_to=Merman Soldier King
cost=15

View file

@ -207,7 +207,7 @@
[message]
speaker=Kai Krellis
[option]
message= _ "We will let the bat have the ring. It will make him more helpful."
label= _ "We will let the bat have the ring. It will make him more helpful."
[command]
[set_variable]
name=get_ring
@ -216,7 +216,7 @@
[/command]
[/option]
[option]
message= _ "Someone take that off him before he hurts himself."
label= _ "Someone take that off him before he hurts himself."
[/option]
[/message]
[/then]
@ -253,7 +253,7 @@
[message]
speaker=unit
[option]
message= _ "Ill take this ring, and you can rely on my strength."
label= _ "Ill take this ring, and you can rely on my strength."
[command]
[set_variable]
name=get_ring
@ -262,7 +262,7 @@
[/command]
[/option]
[option]
message= _ "This thing makes me dizzy. Someone else can have it."
label= _ "This thing makes me dizzy. Someone else can have it."
[/option]
[/message]
[/else]

View file

@ -9,7 +9,7 @@
[binary_path]
path=data/campaigns/Delfadors_Memoirs
[/binary_path]
[+units]
[units]
{campaigns/Delfadors_Memoirs/units}
[/units]
{campaigns/Delfadors_Memoirs/utils}

View file

@ -31,22 +31,10 @@
canrecruit=no
team_name=player
controller=ai
{NAMED_UNIT 2 (Great Mage) 8 9 "First Oracle" (_"First Oracle") (ai_special,facing=guardian,se)}
[+unit]
profile=portraits/oracle1.png
[/unit]
{NAMED_UNIT 2 (Great Mage) 10 11 "Second Oracle" (_"Second Oracle") (ai_special,facing=guardian,sw)}
[+unit]
profile=portraits/oracle2.png
[/unit]
{NAMED_UNIT 2 (Great Mage) 8 13 "Third Oracle" (_"Third Oracle") (ai_special,facing=guardian,nw)}
[+unit]
profile=portraits/oracle3.png
[/unit]
{NAMED_UNIT 2 (Great Mage) 6 11 "Fourth Oracle" (_"Fourth Oracle") (ai_special,facing=guardian,ne)}
[+unit]
profile=portraits/oracle4.png
[/unit]
{NAMED_UNIT 2 (Great Mage) 8 9 "First Oracle" (_"First Oracle") (ai_special,facing,profile=guardian,se,portraits/oracle1.png)}
{NAMED_UNIT 2 (Great Mage) 10 11 "Second Oracle" (_"Second Oracle") (ai_special,facing,profile=guardian,sw,portraits/oracle2.png)}
{NAMED_UNIT 2 (Great Mage) 8 13 "Third Oracle" (_"Third Oracle") (ai_special,facing,profile=guardian,nw,portraits/oracle3.png)}
{NAMED_UNIT 2 (Great Mage) 6 11 "Fourth Oracle" (_"Fourth Oracle") (ai_special,facing,profile=guardian,ne,portraits/oracle4.png)}
[/side]
[story]

View file

@ -132,7 +132,7 @@
speaker=Garrath
message=_"Greetings, strangers! This swamp is dangerous... You wanna cross it, youll need protection — cost you only $fee gold!"
[option]
message=_"Thanks very much. Heres the gold..."
label=_"Thanks very much. Heres the gold..."
[command]
{VARIABLE_OP fee multiply -1}
[gold]
@ -180,7 +180,7 @@
[/command]
[/option]
[option]
message=_"No thanks — well manage by ourselves..."
label=_"No thanks — well manage by ourselves..."
[command]
[message]
speaker=Garrath

View file

@ -58,7 +58,7 @@
speaker={STRING}
message=_"Do you serve Iliah-Malal, living man?"
[option]
message=_"Yes, I serve him."
label=_"Yes, I serve him."
[command]
[message]
speaker={STRING}
@ -67,7 +67,7 @@
[/command]
[/option]
[option]
message=_"No, I do not."
label=_"No, I do not."
[command]
[message]
speaker={STRING}
@ -169,7 +169,7 @@
[if]
[variable]
name=delf_has_staff
equals=true
boolean_equals=true
[/variable]
[then]
{CLEAR_VARIABLE delf_has_staff}

View file

@ -494,7 +494,7 @@
[if]
[variable]
name=undead_veterans
equals=true
boolean_equals=true
[/variable]
#wmllint: local spelling un-life
[then]

View file

@ -4,7 +4,6 @@
name=_ "Wose Shaman"
race=wose
image=units/wose-shaman.png
{MAGENTA_IS_THE_TEAM_COLOR}
[standing_anim]
start_time=0
[frame]

View file

@ -31,23 +31,26 @@
variable=monoliths
[/store_locations]
{FOREACH monoliths one}
[scroll_to]
x=$monoliths[$one].x
y=$monoliths[$one].y
[/scroll_to]
[sound]
name=heal.wav
[/sound]
{RANDOM (Skeleton,Skeleton Archer)}
[unit]
type=$random
side=2
x=$monoliths[$one].x
y=$monoliths[$one].y
animate=yes
[/unit]
{NEXT one}
[foreach]
array=monoliths
[do]
[scroll_to]
x=$this_item.x
y=$this_item.y
[/scroll_to]
[sound]
name=heal.wav
[/sound]
{RANDOM (Skeleton,Skeleton Archer)}
[unit]
type=$random
side=2
x=$this_item.x
y=$this_item.y
animate=yes
[/unit]
[/do]
[/foreach]
[/event]
# if someone capable of destroying the generator moves there

View file

@ -32,20 +32,23 @@
#define MEMOIRS_UNSTORE_UNITS VAR
# Unstore units from an array back into existence
{FOREACH {VAR} i}
[unstore_unit]
variable={VAR}[$i]
find_vacant=yes
x=recall
y=recall
[/unstore_unit]
[heal_unit]
[filter]
id={VAR}[$i].id
[/filter]
amount=999
[/heal_unit]
{NEXT i}
[foreach]
array={VAR}
[do]
[unstore_unit]
variable=this_item
find_vacant=yes
x=recall
y=recall
[/unstore_unit]
[heal_unit]
[filter]
id=$this_item.id
[/filter]
amount=999
[/heal_unit]
[/do]
[/foreach]
[clear_variable]
name={VAR}
@ -59,12 +62,15 @@
variable=side_villages
[/store_villages]
{FOREACH side_villages i}
[capture_village]
side=1
x,y=$side_villages[$i].x,$side_villages[$i].y
[/capture_village]
{NEXT i}
[foreach]
array=side_villages
[do]
[capture_village]
side=1
x,y=$this_item.x,$this_item.y
[/capture_village]
[/do]
[/foreach]
{CLEAR_VARIABLE side_villages}
#next, turn off canrecruit for the leader and change all allegiances.
@ -84,7 +90,7 @@
#define SAVE_WESNOTH_VETERANS
[heal_unit]
[filter]
id=Lionel
side=1
[/filter]
amount=full
animate=no

View file

@ -59,7 +59,7 @@
[/campaign]
#ifdef CAMPAIGN_DESCENT
[+units]
[units]
{campaigns/Descent_Into_Darkness/units}
[/units]
[binary_path]

View file

@ -379,11 +379,14 @@
variable=villages
[/store_villages]
{FOREACH villages i}
{VARIABLE_OP village_var value "village_$villages[$i].x|_$villages[$i].y|_cleared"}
[foreach]
array=villages
[do]
{VARIABLE_OP village_var value "village_$this_item.x|_$this_item.y|_cleared"}
{CLEAR_VARIABLE $village_var}
{NEXT i}
{CLEAR_VARIABLE $village_var}
[/do]
[/foreach]
{CLEAR_VARIABLE villages,village_var,done_shaun}
[/event]

View file

@ -218,54 +218,57 @@
variable=breaking_ice
[/store_locations]
{FOREACH breaking_ice i}
[scroll_to]
x,y=$breaking_ice[$i].x,$breaking_ice[$i].y
[/scroll_to]
[foreach]
array=breaking_ice
[do]
[scroll_to]
x,y=$this_item.x,$this_item.y
[/scroll_to]
[terrain]
x,y=$breaking_ice[$i].x,$breaking_ice[$i].y
terrain=Wo
[/terrain]
[terrain]
x,y=$this_item.x,$this_item.y
terrain=Wo
[/terrain]
[remove_item]
x,y=$breaking_ice[$i].x,$breaking_ice[$i].y
image=misc/weakened-ice.png
[/remove_item]
[remove_item]
x,y=$this_item.x,$this_item.y
image=misc/weakened-ice.png
[/remove_item]
[redraw][/redraw]
[redraw][/redraw]
[if]
[have_unit]
x,y=$breaking_ice[$i].x,$breaking_ice[$i].y
race=undead
[/have_unit]
[then]
[message]
speaker=narrator
image=wesnoth-icon.png
message= _ "The weak ice gives way beneath the undead creature, who becomes mired in the thick mud at the lakes bottom."
[/message]
[/then]
[else]
[message]
speaker=narrator
image=wesnoth-icon.png
message=_ "The weak ice gives way beneath the heavy warrior, who drowns in the frigid mountain waters."
[/message]
[/else]
[/if]
[if]
[have_unit]
x,y=$this_item.x,$this_item.y
race=undead
[/have_unit]
[then]
[message]
speaker=narrator
image=wesnoth-icon.png
message= _ "The weak ice gives way beneath the undead creature, who becomes mired in the thick mud at the lakes bottom."
[/message]
[/then]
[else]
[message]
speaker=narrator
image=wesnoth-icon.png
message=_ "The weak ice gives way beneath the heavy warrior, who drowns in the frigid mountain waters."
[/message]
[/else]
[/if]
[sound]
name=water-blast.wav
[/sound]
[sound]
name=water-blast.wav
[/sound]
[kill]
x,y=$breaking_ice[$i].x,$breaking_ice[$i].y
animate=yes
fire_event=yes
[/kill]
{NEXT i}
[kill]
x,y=$this_item.x,$this_item.y
animate=yes
fire_event=yes
[/kill]
[/do]
[/foreach]
[store_locations]
terrain=Ai
@ -284,12 +287,15 @@
variable=weakened_ice
[/store_locations]
{FOREACH weakened_ice i}
[item]
x,y=$weakened_ice[$i].x,$weakened_ice[$i].y
image=misc/weakened-ice.png
[/item]
{NEXT i}
[foreach]
array=weakened_ice
[do]
[item]
x,y=$this_item.x,$this_item.y
image=misc/weakened-ice.png
[/item]
[/do]
[/foreach]
{CLEAR_VARIABLE breaking_ice}
[/event]

View file

@ -335,18 +335,21 @@
variable=units_inside_manor
[/store_unit]
{FOREACH units_inside_manor i}
{VARIABLE units_inside_manor[$i].upkeep loyal}
[foreach]
array=units_inside_manor
[do]
{VARIABLE this_item.upkeep loyal}
[unstore_unit]
variable=units_inside_manor[$i]
find_vacant=no
[/unstore_unit]
[unstore_unit]
variable=this_item
find_vacant=no
[/unstore_unit]
[recall]
id=$units_inside_manor[$i].id
[/recall]
{NEXT i}
[recall]
id=$this_item.id
[/recall]
[/do]
[/foreach]
{CLEAR_VARIABLE units_inside_manor}

View file

@ -198,18 +198,21 @@
variable=units_inside_manor
[/store_unit]
{FOREACH units_inside_manor i}
{VARIABLE units_inside_manor[$i].upkeep loyal}
[foreach]
array=units_inside_manor
[do]
{VARIABLE this_item.upkeep loyal}
[unstore_unit]
variable=units_inside_manor[$i]
find_vacant=no
[/unstore_unit]
[unstore_unit]
variable=this_item
find_vacant=no
[/unstore_unit]
[recall]
id=$units_inside_manor[$i].id
[/recall]
{NEXT i}
[recall]
id=$this_item.id
[/recall]
[/do]
[/foreach]
{CLEAR_VARIABLE units_inside_manor}

View file

@ -186,14 +186,17 @@
variable=units_inside_manor
[/store_unit]
{FOREACH units_inside_manor i}
{VARIABLE units_inside_manor[$i].upkeep full}
[foreach]
array=units_inside_manor
[do]
{VARIABLE this_item.upkeep full}
[unstore_unit]
variable=units_inside_manor[$i]
find_vacant=no
[/unstore_unit]
{NEXT i}
[unstore_unit]
variable=this_item
find_vacant=no
[/unstore_unit]
[/do]
[/foreach]
{CLEAR_VARIABLE units_inside_manor}

View file

@ -721,6 +721,7 @@
{VARIABLE wose_awakened yes}
[/event]
#ifdef __UNUSED__
# Have a nice trip, see you in the fall. . .
[event]
name=moveto
@ -745,6 +746,7 @@
fire_event=yes
[/kill]
[/event]
#endif
# Victory Condition
[event]

View file

@ -5,7 +5,6 @@
race=human
gender=male
image="units/apprentice-mage.png"
{MAGENTA_IS_THE_TEAM_COLOR}
hitpoints=38
movement_type=smallfoot
movement=6

View file

@ -4,7 +4,6 @@
name=_ "Apprentice Necromancer"
race=human
image="units/apprentice-necromancer.png"
{MAGENTA_IS_THE_TEAM_COLOR}
hitpoints=52
movement_type=smallfoot
movement=6

View file

@ -4,7 +4,6 @@
name= _ "Dark Mage"
race=human
image="units/dark-mage.png"
{MAGENTA_IS_THE_TEAM_COLOR}
hitpoints=67
movement_type=smallfoot
movement=6

View file

@ -82,34 +82,37 @@
variable=doors
[/store_locations]
{FOREACH doors i}
[if]
[have_location]
x,y=$doors[$i].x,$doors[$i].y
terrain=*^Bw\
[/have_location]
[foreach]
variable=doors
[do]
[if]
[have_location]
x,y=$this_item.x,$this_item.y
terrain=*^Bw\
[/have_location]
[then]
[item]
x,y=$doors[$i].x,$doors[$i].y
image=scenery/gate-rusty-sw.png
[/item]
[/then]
[then]
[item]
x,y=$this_item.x,$this_item.y
image=scenery/gate-rusty-sw.png
[/item]
[/then]
[else]
[item]
x,y=$doors[$i].x,$doors[$i].y
image=scenery/gate-rusty-se.png
[/item]
[/else]
[/if]
[else]
[item]
x,y=$this_item.x,$this_item.y
image=scenery/gate-rusty-se.png
[/item]
[/else]
[/if]
[terrain]
x,y=$doors[$i].x,$doors[$i].y
terrain=^Xo
layer=overlay
[/terrain]
{NEXT i}
[terrain]
x,y=$this_item.x,$this_item.y
terrain=^Xo
layer=overlay
[/terrain]
[/do]
[/foreach]
{CLEAR_VARIABLE doors}
[/event]

View file

@ -83,7 +83,7 @@
[/lua]
{campaigns/Eastern_Invasion/utils}
[+units]
[units]
{campaigns/Eastern_Invasion/units}
[/units]
{campaigns/Eastern_Invasion/scenarios}

View file

@ -38,7 +38,7 @@ local function bandits_found(x,y)
local bandit_villages = helper.get_variable_array("bandit_villages")
local boss_found = wesnoth.get_variable("boss_found")
local visited = wesnoth.get_variable("villages_visited")
local rand1 = helper.rand(3,4)
local rand1 = helper.rand("3,4")
local rand2 = helper.rand("2.."..rand1)
for i=1,rand2 do

View file

@ -197,7 +197,7 @@
speaker=Gweddry
message= _ "Hmm..."
[option]
message= _ "I wish to destroy the evil before it can spread. East we go!"
label= _ "I wish to destroy the evil before it can spread. East we go!"
[command]
[message]
speaker=Dacyn
@ -220,7 +220,7 @@
[/command]
[/option]
[option]
message= _ "You are right. It is foolish to go onward — we will turn back."
label= _ "You are right. It is foolish to go onward — we will turn back."
[command]
[message]
speaker=Mal-Skraat

View file

@ -181,71 +181,74 @@
{VARIABLE_OP paladin_i rand "0..$($prison_locations.length - 1)"}
{FOREACH prison_locations i}
[item]
x,y=$prison_locations[$i].x,$prison_locations[$i].y
halo=items/horse-cage.png
[/item]
[store_locations]
[not]
terrain=S*,D*
[/not]
[filter_adjacent_location]
[for]
array=prison_locations
[do]
[item]
x,y=$prison_locations[$i].x,$prison_locations[$i].y
[/filter_adjacent_location]
halo=items/horse-cage.png
[/item]
include_borders=no
[store_locations]
[not]
terrain=S*,D*
[/not]
variable=guard_location
[/store_locations]
[filter_adjacent_location]
x,y=$prison_locations[$i].x,$prison_locations[$i].y
[/filter_adjacent_location]
{VARIABLE_OP guard_i rand "0..$($guard_location.length - 1)"}
include_borders=no
{LOYAL_UNIT 4 Revenant $guard_location[$guard_i].x $guard_location[$guard_i].y}
[+unit]
max_moves=0
ai_special=guardian
role=prison guard
[variables]
prison_x=$prison_locations[$i].x
prison_y=$prison_locations[$i].y
[/variables]
[/unit]
variable=guard_location
[/store_locations]
[if]
{VARIABLE_CONDITIONAL i numerical_equals $paladin_i}
{VARIABLE_OP guard_i rand "0..$($guard_location.length - 1)"}
[then]
[unit]
side=6
type=Paladin
id=Terraent
name= _ "Terraent"
unrenamable=yes
x,y=$prison_locations[$i].x,$prison_locations[$i].y
random_traits=no
facing=sw
{IS_LOYAL}
[modifications]
{TRAIT_LOYAL}
{TEAM_COLOR_OVERRIDE () white}
[/modifications]
[/unit]
[/then]
{LOYAL_UNIT 4 Revenant $guard_location[$guard_i].x $guard_location[$guard_i].y}
[+unit]
max_moves=0
ai_special=guardian
role=prison guard
[variables]
prison_x=$prison_locations[$i].x
prison_y=$prison_locations[$i].y
[/variables]
[/unit]
[else]
{GENERIC_UNIT 6 Knight $prison_locations[$i].x $prison_locations[$i].y}
[+unit]
facing=sw
[modifications]
{TEAM_COLOR_OVERRIDE () white}
[/modifications]
[/unit]
[/else]
[/if]
{NEXT i}
[if]
{VARIABLE_CONDITIONAL i numerical_equals $paladin_i}
[then]
[unit]
side=6
type=Paladin
id=Terraent
name= _ "Terraent"
unrenamable=yes
x,y=$prison_locations[$i].x,$prison_locations[$i].y
random_traits=no
facing=sw
{IS_LOYAL}
[modifications]
{TRAIT_LOYAL}
{TEAM_COLOR_OVERRIDE () white}
[/modifications]
[/unit]
[/then]
[else]
{GENERIC_UNIT 6 Knight $prison_locations[$i].x $prison_locations[$i].y}
[+unit]
facing=sw
[modifications]
{TEAM_COLOR_OVERRIDE () white}
[/modifications]
[/unit]
[/else]
[/if]
[/do]
[/for]
{CLEAR_VARIABLE prison_locations,guard_location,paladin_i,guard_i}

View file

@ -218,7 +218,7 @@
{GENERIC_UNIT 2 (Bone Shooter) 34 31}
#endif
[unit]
type=Ancient Lich
type=Lich
id=Mal-Hakralan
name= _ "Mal-Hakralan"
side=2

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