Merge branch 'master' into campaignd_asio
This commit is contained in:
commit
1c90221527
3031 changed files with 208407 additions and 179567 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
2
INSTALL
2
INSTALL
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
216
changelog
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
1623
data/ai/formula/engine.lua
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
300
data/ai/lua/ca_high_xp_attack.lua
Normal file
300
data/ai/lua/ca_high_xp_attack.lua
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
167
data/ai/micro_ais/cas/ca_assassin_move.lua
Normal file
167
data/ai/micro_ais/cas/ca_assassin_move.lua
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 } } } },
|
||||
|
|
|
@ -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 = {}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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", {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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]" }
|
||||
|
|
|
@ -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", {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
174
data/ai/scenarios/scenario-high_xp_attack.cfg
Normal file
174
data/ai/scenarios/scenario-high_xp_attack.cfg
Normal 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]
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 can’t get through, my Lord. These whelps are not individually very dangerous, but there are huge numbers of them."
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
|
||||
{campaigns/Dead_Water/utils}
|
||||
|
||||
[+units]
|
||||
[units]
|
||||
{campaigns/Dead_Water/units}
|
||||
[/units]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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! I’ll take it."
|
||||
label= _ "That sounds great! I’ll 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= _ "I’ll just leave that trident where it is."
|
||||
label= _ "I’ll just leave that trident where it is."
|
||||
[/option]
|
||||
[/message]
|
||||
[/then]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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= _ "I’ll carry this sword and destroy undead with blasts of flame."
|
||||
label= _ "I’ll 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= _ "I’ll carry the sword."
|
||||
label= _ "I’ll 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]
|
||||
|
|
|
@ -826,7 +826,7 @@
|
|||
[message]
|
||||
speaker=$unit.id
|
||||
[option]
|
||||
message= _ "That sounds great! I’ll take it."
|
||||
label= _ "That sounds great! I’ll take it."
|
||||
[command]
|
||||
[set_variable]
|
||||
name=get_necklace
|
||||
|
@ -835,7 +835,7 @@
|
|||
[/command]
|
||||
[/option]
|
||||
[option]
|
||||
message= _ "It doesn’t seem to have helped its previous owner. I don’t want it."
|
||||
label= _ "It doesn’t seem to have helped its previous owner. I don’t 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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
movement=6
|
||||
experience=25
|
||||
level=0
|
||||
{MAGENTA_IS_THE_TEAM_COLOR}
|
||||
alignment=lawful
|
||||
advances_to=Merman Young King
|
||||
cost=8
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
movement=7
|
||||
experience=76
|
||||
level=2
|
||||
{MAGENTA_IS_THE_TEAM_COLOR}
|
||||
alignment=lawful
|
||||
advances_to=Merman Warrior King
|
||||
cost=36
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
experience=150
|
||||
level=3
|
||||
{AMLA_DEFAULT}
|
||||
{MAGENTA_IS_THE_TEAM_COLOR}
|
||||
alignment=lawful
|
||||
cost=60
|
||||
usage=fighter
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
movement=6
|
||||
experience=38
|
||||
level=1
|
||||
{MAGENTA_IS_THE_TEAM_COLOR}
|
||||
alignment=lawful
|
||||
advances_to=Merman Soldier King
|
||||
cost=15
|
||||
|
|
|
@ -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= _ "I’ll take this ring, and you can rely on my strength."
|
||||
label= _ "I’ll 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]
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
[binary_path]
|
||||
path=data/campaigns/Delfadors_Memoirs
|
||||
[/binary_path]
|
||||
[+units]
|
||||
[units]
|
||||
{campaigns/Delfadors_Memoirs/units}
|
||||
[/units]
|
||||
{campaigns/Delfadors_Memoirs/utils}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -132,7 +132,7 @@
|
|||
speaker=Garrath
|
||||
message=_"Greetings, strangers! This swamp is dangerous... You wanna cross it, you’ll need protection — cost you only $fee gold!"
|
||||
[option]
|
||||
message=_"Thanks very much. Here’s the gold..."
|
||||
label=_"Thanks very much. Here’s the gold..."
|
||||
[command]
|
||||
{VARIABLE_OP fee multiply -1}
|
||||
[gold]
|
||||
|
@ -180,7 +180,7 @@
|
|||
[/command]
|
||||
[/option]
|
||||
[option]
|
||||
message=_"No thanks — we’ll manage by ourselves..."
|
||||
label=_"No thanks — we’ll manage by ourselves..."
|
||||
[command]
|
||||
[message]
|
||||
speaker=Garrath
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -494,7 +494,7 @@
|
|||
[if]
|
||||
[variable]
|
||||
name=undead_veterans
|
||||
equals=true
|
||||
boolean_equals=true
|
||||
[/variable]
|
||||
#wmllint: local spelling un-life
|
||||
[then]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
[/campaign]
|
||||
|
||||
#ifdef CAMPAIGN_DESCENT
|
||||
[+units]
|
||||
[units]
|
||||
{campaigns/Descent_Into_Darkness/units}
|
||||
[/units]
|
||||
[binary_path]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 lake’s 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 lake’s 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]
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
[/lua]
|
||||
|
||||
{campaigns/Eastern_Invasion/utils}
|
||||
[+units]
|
||||
[units]
|
||||
{campaigns/Eastern_Invasion/units}
|
||||
[/units]
|
||||
{campaigns/Eastern_Invasion/scenarios}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue