Initial code import.
[[The Wesnoth repository started off as CVS in September 2003 on SourceForge. In September 2005 it was converted to Subversion using cvs2svn and hosted at Gna!; the last CVS commit corresponded to Subversion r8374. In March 2013 it was converted to git by ESR using reposurgeon 2.30; the last Subversion commit was r56594. In the process, several small, abandoned experimental branches were removed. For all branches known to have been merged to trunk merge points were found and patched in. Comments have been massaged into git summary + body form; revision references have been lifted to action stamps. Conversion comments are, like this one, enclosed in double square brackets. Typos in change comments have often been quietly fixed. Some abbreviations (notably for mainline campaign names) have been made more uniform than they were in the Subversion comments. Infix "::" to mark a campaign-scenario pair (as in "HttT::12" has sometimes been inserted for clarity and to shorten summary lines. Two branches, website/ and resources/, have been merged to trunk, where their history now appears as that of those two top-level directories rather than as separate branches. Subversion property settings, and the commits in the Subversion history that manipulated them, are almost all gone. A few have been translated to .gitignore files and setting of executable bits. There are a few committers that we have been unable to identify. These are: uso zas uid65860 uid66289 uid67456 uid68698 uid68803 uid68842 uid68850 uid68852 uid69097 uid69206 The uid names seem to have been mechanically generated from Wesnoth forum postings. Committer lines for all of these have been left without a domain name in the email address.]]
This commit is contained in:
parent
1e4bf1d2bb
commit
e461d803c9
77 changed files with 15914 additions and 0 deletions
340
COPYING
Normal file
340
COPYING
Normal file
|
@ -0,0 +1,340 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
52
GAME_RULES
Normal file
52
GAME_RULES
Normal file
|
@ -0,0 +1,52 @@
|
|||
In the Battle for Wesnoth, you take command of an army in a fantasy world, and have to maneuver the army to accomplish one or more goals. The game is divided into scenarios. Once you complete one scenario, you move onto the next. A story sequence during which the key players in the game discuss the situation occurs at the beginning and end of each scenario.
|
||||
|
||||
Basic controls:
|
||||
|
||||
* Escape: Exit game
|
||||
* z: Zoom in
|
||||
* x: Zoom out
|
||||
* d: Default zoom level
|
||||
* Arrow key: Scroll
|
||||
* Left click: Select unit/move unit
|
||||
* Right click: Main menu, cancel action
|
||||
* u: undo last move (only moves which have a deterministic outcome may be undone)
|
||||
* r: redo move
|
||||
|
||||
When you complete a scenario, your game is automatically saved, so that it can be loaded later. A game may not be saved during a scenario at this time.
|
||||
|
||||
The map is made up of hexagons. Each army is divided up into units. Up to 1 unit can be in each hexagon.
|
||||
|
||||
Every scenario will have your army fighting against one or more enemy armies. The scenario will be divided up into turns. You move your units and decide who to attack on your turn, and your enemies do the same on your turn.
|
||||
|
||||
At the start of each scenario, you will start with one unit, your commander, and perhaps a few other key units. You will usually start on castle terrain. While you're on a castle, you can recruit units into your army. Each unit has a cost associated with it, which is deducted from your gold balance displayed on the right. You recruit units by right-clicking and select the 'recruit' option from the menu that is displayed.
|
||||
|
||||
If you have successfully completed previous scenarios, and have surviving units in those scenarios, you may re-recruit them into your army with the 'recall' option. The recall option has a cost of 20 gold pieces for each time it is used.
|
||||
|
||||
Scattered around the map are villages. If you move a unit onto a village, it will turn to your army's color (red). For each village you control, you earn revenue of 2 gold every turn. For each unit your army has fielded, you pay expenses of 1 gold every turn.
|
||||
|
||||
Each unit can move a certain number of hexagons every turn. Typically foot soldiers will be able to move 5 hexagons every turn, but this will vary depending upon the unit. However, some terrain types are more difficult for some units to traverse than others, and units may move at half, a third, or a quarter of their normal speed over some types of terrain. Additionally, some types of terrain cannot be traversed at all by certain units.
|
||||
|
||||
If you click on a unit, all the hexagons it can move to this turn are colored, while all the hexagons it cannot move to are displayed in black and white. You may then click on the hexagon you want the unit to move to, to move it there.
|
||||
|
||||
If a unit moves next to an enemy unit, it must stop moving immediately. A unit that moves next to an enemy unit may attack the enemy unit. You can do this by clicking on the unit, and then clicking on the adjacent enemy you want to attack.
|
||||
|
||||
Every unit has one or more attacks it can use. Every attack has a rating for how much damage it does, and how many times the unit can strike with that attack in a turn. When you attack an enemy, they will be able to strike back at you.
|
||||
|
||||
Some attacks are short range, and some attacks are long range. If you attack with a short range attack, then the enemy you are attacking will strike back at short range. If you attack with a long range attack, they will strike back with a long range attack, if they have one. If you attack a unit that has no long range attack with a long range attack, it will not be able to strike back at all.
|
||||
|
||||
When a unit attacks another unit, it will be given a % chance that each attack will hit its target. This chance to hit is based on the terrain the defending unit is standing in. Different units have better defense in different types of terrain.
|
||||
|
||||
When a unit is hit, it loses some hitpoints. If a unit runs out of hitpoints, it is killed and removed from the game. Units are rewarded experience for each battle they are in, and especially for each unit that they kill. If a unit reaches its experience needed, it will become a more powerful unit.
|
||||
|
||||
If a unit is injured, it may move onto a village to heal itself. It will receive 8 hitpoints back every turn it remains on the village.
|
||||
|
||||
Every attack has a type. For instance, swords are 'blade' attacks. Bows are 'piercing' attacks. Each unit has different vulnerability to different kinds of attacks. For instance, horsemen are especially vulnerable to piercing attacks. The more vulnerable a unit is to an attack, the more damage it will suffer against it.
|
||||
|
||||
On the right of the screen, an image is displayed which shows what time of day it is. It may be dawn, day, dusk, or night. Every unit is Lawful, Neutral, or Chaotic. It is possible for evil units to be lawful and good units to be chaotic. For instance, a soldier might concentrate on how they can lawfully do as much evil as they can.
|
||||
|
||||
Lawful units fight better at day, and Chaotic units fight better at night. Neutral units are unaffected by day and night.
|
||||
|
||||
Some units may have special skills or abilities that allow them to operate in ways which distort the normal game rules. If you right-click on a unit and select 'Description', the abilities of that unit will be described (This is not yet fully implemented for all units).
|
||||
|
||||
David White
|
||||
davidnwhite@optusnet.com.au
|
10
INSTALL
Normal file
10
INSTALL
Normal file
|
@ -0,0 +1,10 @@
|
|||
To compile the game, for the source version, type 'make' from the game's root directory.
|
||||
|
||||
To run the game, type ./wesnoth from the game's root directory. The current directory must be the game's root directory when running the game. This is a limitation that will be addressed in later versions.
|
||||
|
||||
If you want to run the game in a window, use the option -windowed at the command line. The game may have trouble running on X-windows on a display set to anything other than 16 bits per pixel.
|
||||
|
||||
Please report any problems with installing or running the game to me.
|
||||
|
||||
David White
|
||||
davidnwhite@optusnet.com.au
|
26
Makefile
Normal file
26
Makefile
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
CC=g++
|
||||
SDL_CFLAGS = `sdl-config --cflags` `freetype-config --cflags`
|
||||
SDL_LIBS = `sdl-config --libs` `freetype-config --libs` -lSDL_mixer -lSDL_ttf -lSDL_image
|
||||
|
||||
OBJS=actions.o ai.o ai_attack.o ai_move.o config.o dialogs.o display.o filesystem.o font.o game.o game_config.o game_events.o gamestatus.o hotkeys.o intro.o key.o language.o log.o map.o menu.o multiplayer.o pathfind.o playlevel.o playturn.o preferences.o replay.o sdl_utils.o sound.o team.o terrain.o unit.o unit_types.o video.o widgets/button.o widgets/slider.o widgets/textbox.o
|
||||
|
||||
MAKE_TRANS_OBJS=make_translation.o config.o filesystem.o log.o
|
||||
MERGE_TRANS_OBJS=merge_translations.o config.o filesystem.o log.o
|
||||
|
||||
wesnoth: $(OBJS)
|
||||
${CC} ${CXXFLAGS} ${SDL_CFLAGS} -o $@ ${OBJS} ${SDL_LIBS} -lstdc++
|
||||
|
||||
make_translation: $(MAKE_TRANS_OBJS)
|
||||
${CC} ${CXXFLAGS} -o $@ ${MAKE_TRANS_OBJS} ${SDL_LIBS} -lstdc++
|
||||
|
||||
merge_translations: $(MERGE_TRANS_OBJS)
|
||||
${CC} ${CXXFLAGS} -o $@ ${MERGE_TRANS_OBJS} ${SDL_LIBS} -lstdc++
|
||||
|
||||
.cpp.o:
|
||||
${CC} ${CXXFLAGS} ${SDL_CFLAGS} -c $< -o $*.o
|
||||
|
||||
clean:
|
||||
-rm -f *.o wesnoth make_translation merge_translations widgets/*.o
|
||||
|
||||
|
12
README
Normal file
12
README
Normal file
|
@ -0,0 +1,12 @@
|
|||
The games license is described in COPYING
|
||||
|
||||
Installation instructions can be found in INSTALL
|
||||
|
||||
A description of how to play the game can be found in GAME_RULES.
|
||||
|
||||
Things on the game's "to-do" list are found in TODO
|
||||
|
||||
The game currently takes one command-line option: -windowed will make it run ina window, instead of full-screen.
|
||||
|
||||
David White
|
||||
davidnwhite@optusnet.com.au
|
6
TODO
Normal file
6
TODO
Normal file
|
@ -0,0 +1,6 @@
|
|||
- The AI is currently a little slow, and a little dumb in some situations.
|
||||
- The game could generally be a little faster
|
||||
- More scenarios are needed
|
||||
- The terrain graphics could be made to look a little more interesting
|
||||
- The introduction sequence needs some work
|
||||
- The hard-coded text strings could be placed in a configuration file, for easier internationalization
|
768
actions.cpp
Normal file
768
actions.cpp
Normal file
|
@ -0,0 +1,768 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "actions.hpp"
|
||||
#include "display.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "game_events.hpp"
|
||||
#include "key.hpp"
|
||||
#include "language.hpp"
|
||||
#include "map.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "sound.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
struct castle_cost_calculator
|
||||
{
|
||||
castle_cost_calculator(const gamemap& map) : map_(map)
|
||||
{}
|
||||
|
||||
double cost(const gamemap::location& loc) const
|
||||
{
|
||||
if(!map_.on_board(loc) || map_[loc.x][loc.y] != gamemap::CASTLE)
|
||||
return 10000;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private:
|
||||
const gamemap& map_;
|
||||
};
|
||||
|
||||
std::string recruit_unit(const gamemap& map, int side,
|
||||
std::map<gamemap::location,unit>& units, unit& new_unit,
|
||||
gamemap::location recruit_location, display* disp)
|
||||
{
|
||||
typedef std::map<gamemap::location,unit> units_map;
|
||||
|
||||
//find the unit that can recruit
|
||||
units_map::const_iterator u;
|
||||
|
||||
for(u = units.begin(); u != units.end(); ++u) {
|
||||
if(u->second.can_recruit() && u->second.side() == side) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(u == units.end())
|
||||
return string_table["no_leader_to_recruit"];
|
||||
|
||||
if(!map.is_starting_position(u->first))
|
||||
return string_table["leader_not_on_start"];
|
||||
|
||||
if(map.on_board(recruit_location)) {
|
||||
const paths::route& rt = a_star_search(u->first,recruit_location,
|
||||
100.0,castle_cost_calculator(map));
|
||||
if(rt.steps.empty() || units.find(recruit_location) != units.end() ||
|
||||
map[recruit_location.x][recruit_location.y] != gamemap::CASTLE)
|
||||
recruit_location = gamemap::location();
|
||||
}
|
||||
|
||||
if(!map.on_board(recruit_location)) {
|
||||
recruit_location = find_vacant_tile(map,units,u->first,gamemap::CASTLE);
|
||||
}
|
||||
|
||||
if(!map.on_board(recruit_location)) {
|
||||
return string_table["no_recruit_location"];
|
||||
}
|
||||
|
||||
new_unit.set_movement(0);
|
||||
new_unit.set_attacked();
|
||||
units.insert(std::pair<gamemap::location,unit>(
|
||||
recruit_location,new_unit));
|
||||
|
||||
if(disp != NULL && !disp->turbo()) {
|
||||
disp->draw(true,true);
|
||||
|
||||
for(double alpha = 0.0; alpha <= 1.0; alpha += 0.1) {
|
||||
disp->draw_tile(recruit_location.x,recruit_location.y,NULL,alpha);
|
||||
disp->update_display();
|
||||
SDL_Delay(20);
|
||||
}
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
bool under_leadership(const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc)
|
||||
{
|
||||
gamemap::location adjacent[6];
|
||||
get_adjacent_tiles(loc,adjacent);
|
||||
const std::map<gamemap::location,unit>::const_iterator un =
|
||||
units.find(loc);
|
||||
if(un == units.end())
|
||||
return false;
|
||||
|
||||
const int side = un->second.side();
|
||||
const int level = un->second.type().level();
|
||||
|
||||
for(int i = 0; i != 6; ++i) {
|
||||
const std::map<gamemap::location,unit>::const_iterator it =
|
||||
units.find(adjacent[i]);
|
||||
if(it != units.end() && it->second.side() == side &&
|
||||
it->second.type().is_leader() && it->second.type().level() > level)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
battle_stats evaluate_battle_stats(
|
||||
const gamemap& map,
|
||||
const gamemap::location& attacker,
|
||||
const gamemap::location& defender,
|
||||
int attack_with,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamestatus& state,
|
||||
const game_data& info,
|
||||
gamemap::TERRAIN attacker_terrain_override,
|
||||
bool include_strings)
|
||||
{
|
||||
battle_stats res;
|
||||
|
||||
res.attack_with = attack_with;
|
||||
|
||||
if(include_strings)
|
||||
res.defend_name = "none";
|
||||
|
||||
const std::map<gamemap::location,unit>::iterator a = units.find(attacker);
|
||||
const std::map<gamemap::location,unit>::iterator d = units.find(defender);
|
||||
|
||||
assert(a != units.end());
|
||||
assert(d != units.end());
|
||||
|
||||
const gamemap::TERRAIN attacker_terrain = attacker_terrain_override ?
|
||||
attacker_terrain_override : map[attacker.x][attacker.y];
|
||||
const gamemap::TERRAIN defender_terrain = map[defender.x][defender.y];
|
||||
|
||||
res.chance_to_hit_attacker =
|
||||
a->second.defense_modifier(map,attacker_terrain);
|
||||
|
||||
res.chance_to_hit_defender =
|
||||
d->second.defense_modifier(map,defender_terrain);
|
||||
|
||||
const std::vector<attack_type>& attacker_attacks =
|
||||
a->second.attacks();
|
||||
const std::vector<attack_type>& defender_attacks =
|
||||
d->second.attacks();
|
||||
|
||||
assert(attack_with >= 0 && attack_with < int(attacker_attacks.size()));
|
||||
const attack_type& attack = attacker_attacks[attack_with];
|
||||
|
||||
static const std::string charge_string("charge");
|
||||
const bool charge = (attack.special() == charge_string);
|
||||
|
||||
bool backstab = false;
|
||||
|
||||
static const std::string backstab_string("backstab");
|
||||
if(attack.special() == backstab_string) {
|
||||
gamemap::location adj[6];
|
||||
get_adjacent_tiles(defender,adj);
|
||||
int i;
|
||||
for(i = 0; i != 6; ++i) {
|
||||
if(adj[i] == attacker)
|
||||
break;
|
||||
}
|
||||
|
||||
if(i != 6) {
|
||||
const std::map<gamemap::location,unit>::const_iterator u =
|
||||
units.find(adj[(i+3)%6]);
|
||||
if(u != units.end() && u->second.side() == a->second.side()) {
|
||||
backstab = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const std::string plague_string("plague");
|
||||
res.attacker_plague = (attack.special() == plague_string);
|
||||
res.defender_plague = false;
|
||||
|
||||
res.attack_name = attack.name();
|
||||
res.attack_type = attack.type();
|
||||
|
||||
if(include_strings) {
|
||||
res.attack_special = attack.special();
|
||||
|
||||
//don't show backstabbing unless it's actually happening
|
||||
if(res.attack_special == "backstab" && !backstab)
|
||||
res.attack_special = "";
|
||||
|
||||
res.range = (attack.range() == attack_type::SHORT_RANGE ?
|
||||
"Melee" : "Ranged");
|
||||
}
|
||||
|
||||
res.nattacks = attack.num_attacks();
|
||||
int defend;
|
||||
res.ndefends = 0;
|
||||
for(defend = 0; defend != int(defender_attacks.size()); ++defend) {
|
||||
if(defender_attacks[defend].range() == attack.range())
|
||||
break;
|
||||
}
|
||||
|
||||
res.defend_with = defend != int(defender_attacks.size()) ? defend : -1;
|
||||
|
||||
const bool counterattack = defend != int(defender_attacks.size());
|
||||
|
||||
static const std::string drain_string("drain");
|
||||
static const std::string magical_string("magical");
|
||||
|
||||
res.damage_attacker_takes = 0;
|
||||
if(counterattack) {
|
||||
//magical attacks always have a 70% chance to hit
|
||||
if(defender_attacks[defend].special() == magical_string)
|
||||
res.chance_to_hit_attacker = 0.7;
|
||||
|
||||
res.damage_attacker_takes = int(double(
|
||||
a->second.damage_against(defender_attacks[defend]))
|
||||
* combat_modifier(state,units,d->first,d->second.type().alignment()));
|
||||
|
||||
if(charge)
|
||||
res.damage_attacker_takes *= 2;
|
||||
|
||||
if(under_leadership(units,defender))
|
||||
res.damage_attacker_takes += res.damage_attacker_takes/8 + 1;
|
||||
|
||||
if(res.damage_attacker_takes < 1)
|
||||
res.damage_attacker_takes = 1;
|
||||
|
||||
res.ndefends = defender_attacks[defend].num_attacks();
|
||||
|
||||
res.defend_name = defender_attacks[defend].name();
|
||||
res.defend_type = defender_attacks[defend].type();
|
||||
|
||||
if(include_strings) {
|
||||
res.defend_special = defender_attacks[defend].special();
|
||||
}
|
||||
|
||||
if(defender_attacks[defend].special() == drain_string) {
|
||||
res.amount_defender_drains = res.damage_attacker_takes/2;
|
||||
} else {
|
||||
res.amount_defender_drains = 0;
|
||||
}
|
||||
|
||||
res.defender_plague =
|
||||
(defender_attacks[defend].special() == plague_string);
|
||||
}
|
||||
|
||||
if(attack.special() == magical_string)
|
||||
res.chance_to_hit_defender = 0.7;
|
||||
|
||||
static const std::string marksman_string("marksman");
|
||||
|
||||
//offensive marksman attacks always have at least 60% chance to hit
|
||||
if(res.chance_to_hit_defender < 0.6 && attack.special() == marksman_string)
|
||||
res.chance_to_hit_defender = 0.6;
|
||||
|
||||
res.damage_defender_takes = int(
|
||||
double(d->second.damage_against(attack))
|
||||
* combat_modifier(state,units,a->first,a->second.type().alignment()))
|
||||
* (charge ? 2 : 1) * (backstab ? 2 : 1);
|
||||
|
||||
if(under_leadership(units,attacker))
|
||||
res.damage_defender_takes += res.damage_defender_takes/8 + 1;
|
||||
|
||||
if(attack.special() == drain_string) {
|
||||
res.amount_attacker_drains = res.damage_defender_takes/2;
|
||||
} else {
|
||||
res.amount_attacker_drains = 0;
|
||||
}
|
||||
|
||||
static const std::string slowed_string("slowed");
|
||||
if(a->second.has_flag(slowed_string) && res.nattacks > 1)
|
||||
--res.nattacks;
|
||||
|
||||
if(d->second.has_flag(slowed_string) && res.ndefends > 1)
|
||||
--res.ndefends;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void attack(display& gui, const gamemap& map,
|
||||
const gamemap::location& attacker,
|
||||
const gamemap::location& defender,
|
||||
int attack_with,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamestatus& state,
|
||||
const game_data& info, bool player_is_attacker)
|
||||
{
|
||||
std::map<gamemap::location,unit>::iterator a = units.find(attacker);
|
||||
std::map<gamemap::location,unit>::iterator d = units.find(defender);
|
||||
|
||||
assert(a != units.end());
|
||||
assert(d != units.end());
|
||||
|
||||
int attackerxp = d->second.type().level();
|
||||
int defenderxp = a->second.type().level();
|
||||
|
||||
a->second.set_attacked();
|
||||
|
||||
//if the attacker was invisible, she isn't anymore!
|
||||
static const std::string forest_invisible("ambush");
|
||||
a->second.remove_flag(forest_invisible);
|
||||
|
||||
battle_stats stats = evaluate_battle_stats(map,attacker,defender,
|
||||
attack_with,units,state,info);
|
||||
|
||||
while(stats.nattacks > 0 || stats.ndefends > 0) {
|
||||
if(stats.nattacks > 0) {
|
||||
const double roll = double(recorder.get_random()%1000)/1000.0;
|
||||
const bool hits = roll < stats.chance_to_hit_defender;
|
||||
const bool dies = gui.unit_attack(attacker,defender,
|
||||
hits ? stats.damage_defender_takes : 0,
|
||||
a->second.attacks()[attack_with]);
|
||||
if(dies) {
|
||||
attackerxp = 10*d->second.type().level();
|
||||
if(d->second.type().level() == 0)
|
||||
attackerxp = 5;
|
||||
|
||||
defenderxp = 0;
|
||||
|
||||
gamemap::location loc = d->first;
|
||||
gamemap::location attacker_loc = a->first;
|
||||
game_events::fire("die",loc,a->first);
|
||||
|
||||
//the handling of the event may have removed the object
|
||||
//so we have to find it again
|
||||
units.erase(loc);
|
||||
|
||||
//plague units make clones of themselves on the target hex
|
||||
//units on villages that die cannot be plagued
|
||||
if(stats.attacker_plague && map[loc.x][loc.y]!=gamemap::TOWER) {
|
||||
a = units.find(attacker_loc);
|
||||
if(a != units.end()) {
|
||||
units.insert(std::pair<gamemap::location,unit>(
|
||||
loc,a->second));
|
||||
gui.draw_tile(loc.x,loc.y);
|
||||
gui.update_display();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
} else if(hits) {
|
||||
static const std::string poison_string("poison");
|
||||
if(stats.attack_special == poison_string &&
|
||||
d->second.has_flag("poisoned") == false) {
|
||||
d->second.set_flag("poisoned");
|
||||
}
|
||||
|
||||
static const std::string slow_string("slow");
|
||||
if(stats.attack_special == slow_string &&
|
||||
d->second.has_flag("slowed") == false) {
|
||||
d->second.set_flag("slowed");
|
||||
if(stats.ndefends > 1)
|
||||
--stats.ndefends;
|
||||
}
|
||||
|
||||
if(stats.amount_attacker_drains > 0) {
|
||||
a->second.gets_hit(-stats.amount_attacker_drains);
|
||||
}
|
||||
}
|
||||
|
||||
--stats.nattacks;
|
||||
}
|
||||
|
||||
if(stats.ndefends > 0) {
|
||||
const double roll = double(recorder.get_random()%1000)/1000.0;
|
||||
const bool hits = roll < stats.chance_to_hit_attacker;
|
||||
const bool dies = gui.unit_attack(defender,attacker,
|
||||
hits ? stats.damage_attacker_takes : 0,
|
||||
d->second.attacks()[stats.defend_with]);
|
||||
|
||||
if(dies) {
|
||||
defenderxp = 10*a->second.type().level();
|
||||
if(a->second.type().level() == 0)
|
||||
defenderxp = 5;
|
||||
|
||||
attackerxp = 0;
|
||||
|
||||
gamemap::location loc = a->first;
|
||||
gamemap::location defender_loc = d->first;
|
||||
game_events::fire("die",loc,d->first);
|
||||
|
||||
//the handling of the event may have removed the object
|
||||
//so we have to find it again
|
||||
units.erase(loc);
|
||||
|
||||
//plague units make clones of themselves on the target hex.
|
||||
//units on villages that die cannot be plagued
|
||||
if(stats.defender_plague && map[loc.x][loc.y]!=gamemap::TOWER) {
|
||||
d = units.find(defender_loc);
|
||||
if(d != units.end()) {
|
||||
units.insert(std::pair<gamemap::location,unit>(
|
||||
loc,d->second));
|
||||
gui.draw_tile(loc.x,loc.y);
|
||||
gui.update_display();
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else if(hits) {
|
||||
if(stats.defend_special == "poison" &&
|
||||
a->second.has_flag("poisoned") == false) {
|
||||
a->second.set_flag("poisoned");
|
||||
}
|
||||
|
||||
static const std::string slow_string("slow");
|
||||
if(stats.defend_special == slow_string &&
|
||||
a->second.has_flag("slowed") == false) {
|
||||
a->second.set_flag("slowed");
|
||||
if(stats.nattacks > 1)
|
||||
--stats.nattacks;
|
||||
}
|
||||
|
||||
if(stats.amount_defender_drains > 0) {
|
||||
d->second.gets_hit(-stats.amount_defender_drains);
|
||||
}
|
||||
}
|
||||
|
||||
--stats.ndefends;
|
||||
}
|
||||
}
|
||||
|
||||
if(attackerxp) {
|
||||
a->second.get_experience(attackerxp);
|
||||
}
|
||||
|
||||
if(defenderxp) {
|
||||
d->second.get_experience(defenderxp);
|
||||
}
|
||||
}
|
||||
|
||||
int tower_owner(const gamemap::location& loc, std::vector<team>& teams)
|
||||
{
|
||||
for(int i = 0; i != teams.size(); ++i) {
|
||||
if(teams[i].owns_tower(loc))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void get_tower(const gamemap::location& loc, std::vector<team>& teams,
|
||||
int team_num)
|
||||
{
|
||||
for(int i = 0; i != teams.size(); ++i) {
|
||||
if(i != team_num && teams[i].owns_tower(loc)) {
|
||||
teams[i].lose_tower(loc);
|
||||
}
|
||||
}
|
||||
|
||||
if(team_num >= 0 && team_num < teams.size())
|
||||
teams[team_num].get_tower(loc);
|
||||
}
|
||||
|
||||
std::map<gamemap::location,unit>::iterator
|
||||
find_leader(std::map<gamemap::location,unit>& units, int side)
|
||||
{
|
||||
for(std::map<gamemap::location,unit>::iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
if(i->second.side() == side && i->second.can_recruit())
|
||||
return i;
|
||||
}
|
||||
|
||||
return units.end();
|
||||
}
|
||||
|
||||
void calculate_healing(display& disp, const gamemap& map,
|
||||
std::map<gamemap::location,unit>& units, int side)
|
||||
{
|
||||
std::map<gamemap::location,int> healed_units;
|
||||
|
||||
std::map<gamemap::location,unit>::iterator i;
|
||||
for(i = units.begin(); i != units.end(); ++i) {
|
||||
if(i->second.side() != side)
|
||||
continue;
|
||||
|
||||
bool heals = false;
|
||||
if(map[i->first.x][i->first.y] == gamemap::TOWER) {
|
||||
heals = true;
|
||||
}
|
||||
|
||||
if(i->second.type().regenerates()) {
|
||||
heals = true;
|
||||
}
|
||||
|
||||
gamemap::location adjacent[6];
|
||||
get_adjacent_tiles(i->first,adjacent);
|
||||
for(int j = 0; j != 6; ++j) {
|
||||
const std::map<gamemap::location,unit>::const_iterator adj_unit =
|
||||
units.find(adjacent[j]);
|
||||
if(adj_unit != units.end() && adj_unit->second.type().heals() &&
|
||||
adj_unit->second.side() == side) {
|
||||
heals = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(heals) {
|
||||
healed_units.insert(std::pair<gamemap::location,int>(
|
||||
i->first, game_config::heal_amount));
|
||||
}
|
||||
}
|
||||
|
||||
//now see about units that can heal other units
|
||||
for(i = units.begin(); i != units.end(); ++i) {
|
||||
if(i->second.side() != side)
|
||||
continue;
|
||||
|
||||
if(i->second.type().heals()) {
|
||||
gamemap::location adjacent[6];
|
||||
get_adjacent_tiles(i->first,adjacent);
|
||||
|
||||
int nhealed = 0;
|
||||
int j;
|
||||
for(j = 0; j != 6; ++j) {
|
||||
const std::map<gamemap::location,unit>::const_iterator adj =
|
||||
units.find(adjacent[j]);
|
||||
if(adj != units.end() &&
|
||||
adj->second.hitpoints() < adj->second.max_hitpoints() &&
|
||||
adj->second.side() == i->second.side() &&
|
||||
healed_units.count(adj->first) == 0) {
|
||||
++nhealed;
|
||||
}
|
||||
}
|
||||
|
||||
if(nhealed == 0)
|
||||
continue;
|
||||
|
||||
const int healing_per_unit = game_config::healer_heals_per_turn/
|
||||
nhealed;
|
||||
|
||||
for(j = 0; j != 6; ++j) {
|
||||
const std::map<gamemap::location,unit>::const_iterator adj =
|
||||
units.find(adjacent[j]);
|
||||
if(adj != units.end() &&
|
||||
adj->second.hitpoints() < adj->second.max_hitpoints() &&
|
||||
adj->second.side() == i->second.side() &&
|
||||
healed_units.count(adj->first) == 0) {
|
||||
healed_units.insert(std::pair<gamemap::location,int>(
|
||||
i->first,healing_per_unit));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//poisoned units will take the same amount of damage per turn, as
|
||||
//healing heals until they are reduced to 1 hitpoint. If they are
|
||||
//healed on a turn, they recover 0 hitpoints that turn, but they
|
||||
//are no longer poisoned
|
||||
for(i = units.begin(); i != units.end(); ++i) {
|
||||
if(i->second.side() != side)
|
||||
continue;
|
||||
|
||||
if(i->second.has_flag("poisoned")) {
|
||||
const int damage = minimum<int>(game_config::heal_amount,
|
||||
i->second.hitpoints()-1);
|
||||
|
||||
if(damage > 0) {
|
||||
healed_units.insert(std::pair<gamemap::location,int>(
|
||||
i->first,-damage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(std::map<gamemap::location,int>::iterator h = healed_units.begin();
|
||||
h != healed_units.end(); ++h) {
|
||||
|
||||
const gamemap::location& loc = h->first;
|
||||
|
||||
const bool show_healing = !disp.turbo() && !recorder.skipping();
|
||||
|
||||
assert(units.count(loc) == 1);
|
||||
|
||||
unit& u = units.find(loc)->second;
|
||||
|
||||
if(h->second > 0 && h->second > u.max_hitpoints()-u.hitpoints()) {
|
||||
h->second = u.max_hitpoints()-u.hitpoints();
|
||||
if(h->second <= 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(show_healing) {
|
||||
disp.scroll_to_tile(loc.x,loc.y,display::WARP);
|
||||
disp.select_hex(loc);
|
||||
disp.update_display();
|
||||
}
|
||||
|
||||
const int DelayAmount = 50;
|
||||
|
||||
if(u.has_flag("poisoned")) {
|
||||
|
||||
u.remove_flag("poisoned");
|
||||
h->second = 0;
|
||||
|
||||
if(show_healing) {
|
||||
sound::play_sound("heal.wav");
|
||||
SDL_Delay(DelayAmount);
|
||||
disp.invalidate_unit();
|
||||
disp.update_display();
|
||||
}
|
||||
} else if(h->second < 0) {
|
||||
if(show_healing)
|
||||
sound::play_sound("groan.wav");
|
||||
} else if(h->second > 0) {
|
||||
if(show_healing)
|
||||
sound::play_sound("heal.wav");
|
||||
}
|
||||
|
||||
while(h->second > 0) {
|
||||
const display::Pixel heal_colour = disp.rgb(0,0,200);
|
||||
u.heal(1);
|
||||
|
||||
if(show_healing) {
|
||||
if((h->second%2) == 1)
|
||||
disp.draw_tile(loc.x,loc.y,NULL,0.5,heal_colour);
|
||||
else
|
||||
disp.draw_tile(loc.x,loc.y);
|
||||
SDL_Delay(DelayAmount);
|
||||
disp.update_display();
|
||||
}
|
||||
|
||||
--h->second;
|
||||
}
|
||||
|
||||
while(h->second < 0) {
|
||||
const display::Pixel damage_colour = disp.rgb(200,0,0);
|
||||
u.gets_hit(1);
|
||||
|
||||
if(show_healing) {
|
||||
if((h->second%2) == 1)
|
||||
disp.draw_tile(loc.x,loc.y,NULL,0.5,damage_colour);
|
||||
else
|
||||
disp.draw_tile(loc.x,loc.y);
|
||||
|
||||
SDL_Delay(DelayAmount);
|
||||
disp.update_display();
|
||||
}
|
||||
|
||||
++h->second;
|
||||
}
|
||||
|
||||
if(show_healing) {
|
||||
disp.draw_tile(loc.x,loc.y);
|
||||
disp.update_display();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unit get_advanced_unit(const game_data& info,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, const std::string& advance_to)
|
||||
{
|
||||
const std::map<std::string,unit_type>::const_iterator new_type =
|
||||
info.unit_types.find(advance_to);
|
||||
std::map<gamemap::location,unit>::iterator un = units.find(loc);
|
||||
if(new_type != info.unit_types.end() && un != units.end()) {
|
||||
const int side = un->second.side();
|
||||
|
||||
unit new_unit(&(new_type->second),un->second);
|
||||
|
||||
return new_unit;
|
||||
} else {
|
||||
throw gamestatus::game_error("Could not find the unit being advanced"
|
||||
" to: " + advance_to);
|
||||
}
|
||||
}
|
||||
|
||||
void advance_unit(const game_data& info,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, const std::string& advance_to)
|
||||
{
|
||||
const unit& new_unit = get_advanced_unit(info,units,loc,advance_to);
|
||||
units.erase(loc);
|
||||
units.insert(std::pair<gamemap::location,unit>(loc,new_unit));
|
||||
}
|
||||
|
||||
int check_victory(std::map<gamemap::location,unit>& units)
|
||||
{
|
||||
std::set<int> seen_leaders;
|
||||
for(std::map<gamemap::location,unit>::const_iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
if(i->second.can_recruit())
|
||||
seen_leaders.insert(i->second.side());
|
||||
}
|
||||
|
||||
//if only one leader remains standing, his team has won
|
||||
if(seen_leaders.size() == 1)
|
||||
return *seen_leaders.begin();
|
||||
|
||||
//if the player (team 1) isn't here, the player has lost
|
||||
if(seen_leaders.count(1) == 0)
|
||||
return 2;
|
||||
|
||||
//remove any units which are leaderless
|
||||
for(std::map<gamemap::location,unit>::iterator j = units.begin();
|
||||
j != units.end(); ++j) {
|
||||
if(seen_leaders.count(j->second.side()) == 0) {
|
||||
units.erase(j);
|
||||
j = units.begin();
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
gamestatus::TIME timeofday_at(const gamestatus& status,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc)
|
||||
{
|
||||
gamemap::location locs[7];
|
||||
locs[0] = loc;
|
||||
get_adjacent_tiles(loc,locs+1);
|
||||
|
||||
bool lighten = false;
|
||||
for(int i = 0; i != 7; ++i) {
|
||||
const std::map<gamemap::location,unit>::const_iterator itor =
|
||||
units.find(locs[i]);
|
||||
if(itor != units.end() &&
|
||||
itor->second.type().is_lightbringer()) {
|
||||
lighten = true;
|
||||
}
|
||||
}
|
||||
|
||||
gamestatus::TIME res = status.timeofday();
|
||||
if(lighten) {
|
||||
if(res == gamestatus::DUSK)
|
||||
res = gamestatus::DAY2;
|
||||
else if(res > gamestatus::DUSK)
|
||||
res = gamestatus::DUSK;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
double combat_modifier(const gamestatus& status,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
unit_type::ALIGNMENT alignment)
|
||||
{
|
||||
const gamestatus::TIME timeofday = timeofday_at(status,units,loc);
|
||||
if(alignment == unit_type::LAWFUL) {
|
||||
if(timeofday > gamestatus::DAWN && timeofday < gamestatus::DUSK)
|
||||
return 1.25;
|
||||
else if(timeofday > gamestatus::DUSK &&
|
||||
units.find(loc)->second.type().nightvision() == false)
|
||||
return 0.75;
|
||||
} else if(alignment == unit_type::CHAOTIC) {
|
||||
if(timeofday > gamestatus::DAWN && timeofday < gamestatus::DUSK)
|
||||
return 0.75;
|
||||
else if(timeofday > gamestatus::DUSK)
|
||||
return 1.25;
|
||||
}
|
||||
|
||||
return 1.0;
|
||||
}
|
97
actions.hpp
Normal file
97
actions.hpp
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef ACTIONS_H_INCLUDED
|
||||
#define ACTIONS_H_INCLUDED
|
||||
|
||||
#include "display.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "map.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
std::string recruit_unit(const gamemap& map, int team,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
unit& unit, gamemap::location preferred_location,
|
||||
display *disp=NULL);
|
||||
|
||||
struct battle_stats
|
||||
{
|
||||
std::string attack_name, defend_name;
|
||||
std::string attack_type, defend_type;
|
||||
std::string attack_special, defend_special;
|
||||
std::string range;
|
||||
double chance_to_hit_attacker, chance_to_hit_defender;
|
||||
int damage_attacker_takes, damage_defender_takes;
|
||||
int amount_attacker_drains, amount_defender_drains;
|
||||
int ndefends, nattacks;
|
||||
int attack_with, defend_with;
|
||||
bool attacker_plague, defender_plague;
|
||||
};
|
||||
|
||||
battle_stats evaluate_battle_stats(
|
||||
const gamemap& map,
|
||||
const gamemap::location& attacker,
|
||||
const gamemap::location& defender,
|
||||
int attack_with,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamestatus& state,
|
||||
const game_data& info,
|
||||
gamemap::TERRAIN attacker_terrain_override=0,
|
||||
bool include_strings=true);
|
||||
|
||||
void attack(display& gui, const gamemap& map,
|
||||
const gamemap::location& attacker,
|
||||
const gamemap::location& defender,
|
||||
int attack_with,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamestatus& state,
|
||||
const game_data& info, bool player_is_attacker);
|
||||
|
||||
int tower_owner(const gamemap::location& loc, std::vector<team>& teams);
|
||||
|
||||
void get_tower(const gamemap::location& loc, std::vector<team>& teams,
|
||||
int team_num);
|
||||
|
||||
std::map<gamemap::location,unit>::iterator
|
||||
find_leader(std::map<gamemap::location,unit>& units, int side);
|
||||
|
||||
void calculate_healing(display& disp, const gamemap& map,
|
||||
std::map<gamemap::location,unit>& units, int side);
|
||||
|
||||
unit get_advanced_unit(const game_data& info,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, const std::string& advance_to);
|
||||
|
||||
void advance_unit(const game_data& info,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, const std::string& advance_to);
|
||||
|
||||
bool under_leadership(const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc);
|
||||
|
||||
int check_victory(std::map<gamemap::location,unit>& units);
|
||||
|
||||
//gets the time of day at a certain tile. Certain tiles may have a time of
|
||||
//day that differs from 'the' time of day, if a unit that brings light is there
|
||||
gamestatus::TIME timeofday_at(const gamestatus& status,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc);
|
||||
|
||||
double combat_modifier(const gamestatus& status,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
unit_type::ALIGNMENT alignment);
|
||||
|
||||
#endif
|
503
ai.cpp
Normal file
503
ai.cpp
Normal file
|
@ -0,0 +1,503 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "actions.hpp"
|
||||
#include "ai.hpp"
|
||||
#include "ai_attack.hpp"
|
||||
#include "ai_move.hpp"
|
||||
#include "dialogs.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "log.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "playturn.hpp"
|
||||
#include "replay.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
|
||||
bool recruit(const gamemap& map, const gamemap::location& leader,
|
||||
const std::string& usage, const game_data& gameinfo,
|
||||
int team_num, team& tm, int min_gold,
|
||||
std::map<gamemap::location,unit>& units, display& disp)
|
||||
{
|
||||
log_scope("recruiting troops");
|
||||
std::cerr << "recruiting " << usage << "\n";
|
||||
|
||||
std::vector<std::map<std::string,unit_type>::const_iterator> options;
|
||||
|
||||
//record the number of the recruit for replay recording
|
||||
std::vector<int> option_numbers;
|
||||
|
||||
//find an available unit that can be recruited, matches the desired
|
||||
//usage type, and comes in under budget
|
||||
const std::set<std::string>& recruits = tm.recruits();
|
||||
for(std::map<std::string,unit_type>::const_iterator i =
|
||||
gameinfo.unit_types.begin(); i != gameinfo.unit_types.end(); ++i) {
|
||||
|
||||
if(i->second.usage() == usage && recruits.count(i->second.name())
|
||||
&& tm.gold() - i->second.cost() > min_gold) {
|
||||
|
||||
options.push_back(i);
|
||||
option_numbers.push_back(std::distance(recruits.begin(),
|
||||
recruits.find(i->first)));
|
||||
}
|
||||
}
|
||||
|
||||
//from the available options, choose one at random
|
||||
if(options.empty() == false) {
|
||||
const int option = rand()%options.size();
|
||||
recorder.add_recruit(option_numbers[option],gamemap::location());
|
||||
const unit_type& u = options[option]->second;
|
||||
tm.spend_gold(u.cost());
|
||||
unit new_unit(&u,team_num,true);
|
||||
|
||||
std::cerr << "recruiting a " << u.name() << " for " << u.cost() << " have " << tm.gold() << " left\n";
|
||||
const gamemap::location loc;
|
||||
return recruit_unit(map,team_num,units,new_unit,loc,&disp).empty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ai {
|
||||
|
||||
void move_unit(const game_data& gameinfo, display& disp,
|
||||
const gamemap& map,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const location& from, const location& to,
|
||||
std::map<location,paths>& possible_moves,
|
||||
std::vector<team>& teams, int team_num)
|
||||
{
|
||||
assert(units.find(to) == units.end() || from == to);
|
||||
|
||||
disp.select_hex(from);
|
||||
disp.update_display();
|
||||
|
||||
log_scope("move_unit");
|
||||
std::map<location,unit>::iterator u_it = units.find(from);
|
||||
if(u_it == units.end()) {
|
||||
std::cout << "Could not find unit at " << from.x << ", "
|
||||
<< from.y << "\n";
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
recorder.add_movement(from,to);
|
||||
|
||||
const bool ignore_zocs = u_it->second.type().is_skirmisher();
|
||||
const bool teleport = u_it->second.type().teleports();
|
||||
paths current_paths = paths(map,gameinfo,units,from,teams,
|
||||
ignore_zocs,teleport);
|
||||
paths_wiper wiper(disp);
|
||||
disp.set_paths(¤t_paths);
|
||||
|
||||
disp.scroll_to_tiles(from.x,from.y,to.x,to.y);
|
||||
|
||||
unit current_unit = u_it->second;
|
||||
units.erase(u_it);
|
||||
|
||||
const std::map<location,paths>::iterator p_it = possible_moves.find(from);
|
||||
|
||||
if(p_it != possible_moves.end()) {
|
||||
paths& p = p_it->second;
|
||||
std::map<location,paths::route>::iterator rt = p.routes.begin();
|
||||
for(; rt != p.routes.end(); ++rt) {
|
||||
if(rt->first == to) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(rt != p.routes.end()) {
|
||||
std::vector<location> steps = rt->second.steps;
|
||||
steps.push_back(to); //add the destination to the steps
|
||||
disp.move_unit(steps,current_unit);
|
||||
}
|
||||
}
|
||||
|
||||
current_unit.set_movement(0);
|
||||
units.insert(std::pair<location,unit>(to,current_unit));
|
||||
if(map[to.x][to.y] == gamemap::TOWER)
|
||||
get_tower(to,teams,team_num-1);
|
||||
|
||||
disp.draw_tile(to.x,to.y);
|
||||
disp.draw();
|
||||
|
||||
game_events::fire("moveto",to);
|
||||
}
|
||||
|
||||
void do_move(display& disp, const gamemap& map, const game_data& gameinfo,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
std::vector<team>& teams, int team_num, const gamestatus& state,
|
||||
bool consider_combat, std::vector<target>* additional_targets)
|
||||
{
|
||||
std::vector<target> tgts;
|
||||
if(additional_targets == NULL)
|
||||
additional_targets = &tgts;
|
||||
|
||||
log_scope("doing ai move");
|
||||
|
||||
team& current_team = teams[team_num-1];
|
||||
|
||||
typedef paths::route route;
|
||||
|
||||
std::multimap<location,location> srcdst;
|
||||
std::multimap<location,location> dstsrc;
|
||||
|
||||
std::vector<gamemap::location> leader_locations;
|
||||
|
||||
typedef std::map<location,paths> moves_map;
|
||||
moves_map possible_moves;
|
||||
for(std::map<gamemap::location,unit>::const_iterator un_it = units.begin();
|
||||
un_it != units.end(); ++un_it) {
|
||||
|
||||
if(un_it->second.side() != team_num) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//insert the trivial moves of staying on the same location
|
||||
if(un_it->second.movement_left() == un_it->second.total_movement()) {
|
||||
std::pair<location,location> trivial_mv(un_it->first,un_it->first);
|
||||
srcdst.insert(trivial_mv);
|
||||
dstsrc.insert(trivial_mv);
|
||||
}
|
||||
|
||||
if(un_it->second.can_recruit()) {
|
||||
//save so we can remove from possible moves later
|
||||
leader_locations.push_back(un_it->first);
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool ignore_zocs = un_it->second.type().is_skirmisher();
|
||||
const bool teleports = un_it->second.type().teleports();
|
||||
possible_moves.insert(std::pair<gamemap::location,paths>(
|
||||
un_it->first,paths(map,gameinfo,units,
|
||||
un_it->first,teams,ignore_zocs,teleports)));
|
||||
}
|
||||
|
||||
|
||||
for(moves_map::iterator m = possible_moves.begin();
|
||||
m != possible_moves.end(); ++m) {
|
||||
for(std::map<location,route>::iterator rtit =
|
||||
m->second.routes.begin(); rtit != m->second.routes.end();
|
||||
++rtit) {
|
||||
const location& src = m->first;
|
||||
const location& dst = rtit->first;
|
||||
|
||||
if(src != dst && units.find(dst) == units.end()) {
|
||||
srcdst.insert(std::pair<location,location>(src,dst));
|
||||
dstsrc.insert(std::pair<location,location>(dst,src));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//no moves left, recruitment phase
|
||||
//take stock of our current set of units
|
||||
if(srcdst.empty()) {
|
||||
std::cout << "recruitment......\n";
|
||||
location leader;
|
||||
int num_units = 0;
|
||||
std::map<std::string,int> unit_types;
|
||||
for(std::map<location,unit>::const_iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
if(i->second.side() != team_num)
|
||||
continue;
|
||||
|
||||
if(i->second.can_recruit()) {
|
||||
leader = i->first;
|
||||
continue;
|
||||
}
|
||||
|
||||
unit_types[i->second.type().usage()]++;
|
||||
++num_units;
|
||||
}
|
||||
|
||||
const int cash_flow = current_team.towers()*game_config::tower_income +
|
||||
game_config::base_income - num_units;
|
||||
|
||||
const int min_gold = 10 + (cash_flow < 0 ? -cash_flow*10 : 0);
|
||||
|
||||
//count the number of scouts we have currently
|
||||
|
||||
const int towers = map.towers().size();
|
||||
int taken_towers = 0;
|
||||
for(int j = 0; j != teams.size(); ++j) {
|
||||
taken_towers += teams[j].towers();
|
||||
}
|
||||
|
||||
const int neutral_towers = towers - taken_towers;
|
||||
|
||||
//we want at least one scout for every eight neutral towers
|
||||
int scouts_wanted = neutral_towers/8;
|
||||
if(scouts_wanted < 1)
|
||||
scouts_wanted = 1;
|
||||
while(unit_types["scout"] < scouts_wanted) {
|
||||
if(recruit(map,leader,"scout",gameinfo,team_num,current_team,
|
||||
min_gold,units,disp) == false)
|
||||
break;
|
||||
|
||||
++unit_types["scout"];
|
||||
}
|
||||
|
||||
const std::vector<std::string>& options =
|
||||
current_team.recruitment_pattern();
|
||||
|
||||
if(options.empty()) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
//buy fighters as long as we have room and can afford it
|
||||
while(recruit(map,leader,options[rand()%options.size()].c_str(),
|
||||
gameinfo,team_num,current_team,min_gold,units,disp)) {
|
||||
|
||||
}
|
||||
|
||||
recorder.end_turn();
|
||||
return;
|
||||
}
|
||||
|
||||
int ticks = SDL_GetTicks();
|
||||
//look for targets of opportunity that we are hoping to kill this turn
|
||||
std::vector<attack_analysis> analysis;
|
||||
|
||||
if(consider_combat)
|
||||
analysis = analyze_targets(map,srcdst,dstsrc,units,
|
||||
current_team,team_num,state,gameinfo);
|
||||
|
||||
int time_taken = SDL_GetTicks() - ticks;
|
||||
std::cout << "took " << time_taken << " ticks for " << analysis.size() << " positions. Analyzing...\n";
|
||||
|
||||
ticks = SDL_GetTicks();
|
||||
|
||||
const int max_sims = 30000;
|
||||
int num_sims = analysis.empty() ? 0 : max_sims/analysis.size();
|
||||
if(num_sims < 8)
|
||||
num_sims = 8;
|
||||
if(num_sims > 20)
|
||||
num_sims = 20;
|
||||
|
||||
std::cout << "simulations: " << num_sims << "\n";
|
||||
|
||||
const int max_positions = 20000;
|
||||
const int skip_num = analysis.size()/max_positions;
|
||||
|
||||
std::vector<attack_analysis>::iterator choice_it = analysis.end();
|
||||
double choice_rating = -1000.0;
|
||||
for(std::vector<attack_analysis>::iterator it = analysis.begin();
|
||||
it != analysis.end(); ++it) {
|
||||
if(skip_num > 0 && ((it - analysis.begin())%skip_num) &&
|
||||
it->movements.size() > 1)
|
||||
continue;
|
||||
|
||||
const double rating = it->rating(current_team.aggression());
|
||||
std::cout << "attack option rated at " << rating << " (" << current_team.aggression() << ")\n";
|
||||
if(rating > choice_rating) {
|
||||
choice_it = it;
|
||||
choice_rating = rating;
|
||||
}
|
||||
}
|
||||
|
||||
time_taken = SDL_GetTicks() - ticks;
|
||||
std::cout << "analysis took " << time_taken << " ticks\n";
|
||||
|
||||
if(choice_rating > 0.0) {
|
||||
const location& from = choice_it->movements[0].first;
|
||||
const location& to = choice_it->movements[0].second;
|
||||
const location& target_loc = choice_it->target;
|
||||
const int weapon = choice_it->weapons[0];
|
||||
|
||||
const std::map<gamemap::location,unit>::const_iterator tgt =
|
||||
units.find(target_loc);
|
||||
|
||||
const bool defender_human = (tgt != units.end()) ?
|
||||
teams[tgt->second.side()-1].is_human() : false;
|
||||
|
||||
move_unit(gameinfo,disp,map,units,from,to,
|
||||
possible_moves,teams,team_num);
|
||||
|
||||
std::cerr << "attacking...\n";
|
||||
recorder.add_attack(to,target_loc,weapon);
|
||||
|
||||
game_events::fire("attack",to,target_loc);
|
||||
if(units.count(to) && units.count(target_loc)) {
|
||||
attack(disp,map,to,target_loc,weapon,units,state,gameinfo,false);
|
||||
const int res = check_victory(units);
|
||||
if(res == 1)
|
||||
throw end_level_exception(VICTORY);
|
||||
else if(res > 1)
|
||||
throw end_level_exception(DEFEAT);
|
||||
}
|
||||
std::cerr << "done attacking...\n";
|
||||
|
||||
//if this is the only unit in the planned attack, and the target
|
||||
//is still alive, then also summon reinforcements
|
||||
if(choice_it->movements.size() == 1 && units.count(target_loc)) {
|
||||
additional_targets->push_back(target(target_loc,3.0));
|
||||
}
|
||||
|
||||
dialogs::advance_unit(gameinfo,units,to,disp,true);
|
||||
dialogs::advance_unit(gameinfo,units,target_loc,disp,!defender_human);
|
||||
|
||||
do_move(disp,map,gameinfo,units,teams,team_num,state,consider_combat,
|
||||
additional_targets);
|
||||
return;
|
||||
|
||||
} else {
|
||||
log_scope("summoning reinforcements...\n");
|
||||
consider_combat = false;
|
||||
|
||||
std::set<gamemap::location> already_done;
|
||||
|
||||
for(std::vector<attack_analysis>::iterator it = analysis.begin();
|
||||
it != analysis.end(); ++it) {
|
||||
assert(it->movements.empty() == false);
|
||||
const gamemap::location& loc = it->movements.front().first;
|
||||
if(already_done.count(loc) > 0)
|
||||
continue;
|
||||
|
||||
additional_targets->push_back(target(loc,3.0));
|
||||
already_done.insert(loc);
|
||||
}
|
||||
}
|
||||
|
||||
//mark the leader as having moved and attacked by now
|
||||
for(std::vector<location>::const_iterator lead = leader_locations.begin();
|
||||
lead != leader_locations.end(); ++lead) {
|
||||
const std::map<location,unit>::iterator leader = units.find(*lead);
|
||||
if(leader != units.end()) {
|
||||
leader->second.set_movement(0);
|
||||
leader->second.set_attacked();
|
||||
}
|
||||
|
||||
//remove leader from further consideration
|
||||
srcdst.erase(*lead);
|
||||
dstsrc.erase(*lead);
|
||||
}
|
||||
|
||||
//try to acquire towers
|
||||
for(std::multimap<location,location>::const_iterator i = dstsrc.begin();
|
||||
i != dstsrc.end(); ++i) {
|
||||
if(map[i->first.x][i->first.y] != gamemap::TOWER)
|
||||
continue;
|
||||
|
||||
bool want_tower = true;
|
||||
for(int j = 0; j != teams.size(); ++j) {
|
||||
if(!current_team.is_enemy(j+1) && teams[j].owns_tower(i->first)) {
|
||||
want_tower = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(want_tower) {
|
||||
const std::map<location,unit>::iterator un = units.find(i->second);
|
||||
if(un == units.end()) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if(un->second.is_guardian())
|
||||
continue;
|
||||
|
||||
move_unit(gameinfo,disp,map,units,i->second,i->first,
|
||||
possible_moves,teams,team_num);
|
||||
|
||||
do_move(disp,map,gameinfo,units,teams,team_num,
|
||||
state,consider_combat,additional_targets);
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::cout << "b\n";
|
||||
|
||||
//find units in need of healing
|
||||
std::map<location,unit>::iterator u_it = units.begin();
|
||||
for(; u_it != units.end(); ++u_it) {
|
||||
unit& u = u_it->second;
|
||||
|
||||
//if the unit is on our side, has lost as many or more than 1/2 round
|
||||
//worth of healing, and doesn't regenerate itself, then try to
|
||||
//find a vacant village for it to rest in
|
||||
if(u.side() == team_num &&
|
||||
u.type().hitpoints() - u.hitpoints() >= game_config::heal_amount/2 &&
|
||||
!u.type().regenerates()) {
|
||||
typedef std::multimap<location,location>::iterator Itor;
|
||||
std::pair<Itor,Itor> it = srcdst.equal_range(u_it->first);
|
||||
while(it.first != it.second) {
|
||||
const location& dst = it.first->second;
|
||||
if(map[dst.x][dst.y] == gamemap::TOWER &&
|
||||
units.find(dst) == units.end()) {
|
||||
const location& src = it.first->first;
|
||||
|
||||
move_unit(gameinfo,disp,map,units,src,dst,
|
||||
possible_moves,teams,team_num);
|
||||
do_move(disp,map,gameinfo,units,teams,team_num,state,
|
||||
consider_combat,additional_targets);
|
||||
return;
|
||||
}
|
||||
|
||||
++it.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(dstsrc.empty()) {
|
||||
do_move(disp,map,gameinfo,units,teams,team_num,state,
|
||||
consider_combat,additional_targets);
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "finding targets...\n";
|
||||
std::vector<target> targets = find_targets(map,units,teams,team_num);
|
||||
targets.insert(targets.end(),additional_targets->begin(),
|
||||
additional_targets->end());
|
||||
for(;;) {
|
||||
if(targets.empty()) {
|
||||
recorder.end_turn();
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "choosing move...\n";
|
||||
std::pair<location,location> move = choose_move(targets,dstsrc,units,
|
||||
map,teams,team_num,
|
||||
gameinfo);
|
||||
for(std::vector<target>::const_iterator ittg = targets.begin();
|
||||
ittg != targets.end(); ++ittg) {
|
||||
assert(map.on_board(ittg->loc));
|
||||
}
|
||||
|
||||
|
||||
if(move.first.valid() == false)
|
||||
break;
|
||||
|
||||
std::cout << "move: " << move.first.x << ", " << move.first.y << " - " << move.second.x << ", " << move.second.y << "\n";
|
||||
|
||||
std::cout << "calling move_unit\n";
|
||||
move_unit(gameinfo,disp,map,units,move.first,move.second,
|
||||
possible_moves,teams,team_num);
|
||||
std::cout << "end move_unit\n";
|
||||
|
||||
//don't allow any other units to move onto the tile our unit
|
||||
//just moved onto
|
||||
typedef std::multimap<location,location>::iterator Itor;
|
||||
std::pair<Itor,Itor> del = dstsrc.equal_range(move.second);
|
||||
dstsrc.erase(del.first,del.second);
|
||||
}
|
||||
|
||||
do_move(disp,map,gameinfo,units,teams,team_num,state,
|
||||
consider_combat,additional_targets);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
33
ai.hpp
Normal file
33
ai.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef AI_HPP_INCLUDED
|
||||
#define AI_HPP_INCLUDED
|
||||
|
||||
#include "ai_move.hpp"
|
||||
#include "display.hpp"
|
||||
#include "map.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace ai {
|
||||
typedef gamemap::location location;
|
||||
|
||||
void do_move(display& disp, const gamemap& map, const game_data& gameinfo,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
std::vector<team>& teams, int team_num, const gamestatus& state,
|
||||
bool consider_combat=true,
|
||||
std::vector<target>* additional_targets=NULL);
|
||||
}
|
||||
|
||||
#endif
|
440
ai_attack.cpp
Normal file
440
ai_attack.cpp
Normal file
|
@ -0,0 +1,440 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "actions.hpp"
|
||||
#include "ai_attack.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "log.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
|
||||
namespace {
|
||||
|
||||
const int max_positions = 10000;
|
||||
|
||||
using namespace ai;
|
||||
|
||||
void do_analysis(
|
||||
const gamemap& map,
|
||||
const location& loc,
|
||||
const std::multimap<location,location>& srcdst,
|
||||
const std::multimap<location,location>& dstsrc,
|
||||
const location* tiles, bool* used_locations,
|
||||
std::vector<location>& units,
|
||||
std::map<gamemap::location,unit>& units_map,
|
||||
std::vector<attack_analysis>& result,
|
||||
const game_data& data, const gamestatus& status,
|
||||
attack_analysis& cur_analysis
|
||||
)
|
||||
{
|
||||
if(cur_analysis.movements.size() >= 6)
|
||||
return;
|
||||
|
||||
static double best_results[6];
|
||||
if(result.empty()) {
|
||||
for(int i = 0; i != 6; ++i) {
|
||||
best_results[i] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// if(result.size() > max_positions && !cur_analysis.movements.empty())
|
||||
// return;
|
||||
|
||||
const double cur_rating = cur_analysis.movements.empty() ? 0 :
|
||||
cur_analysis.rating(0.0);
|
||||
|
||||
double rating_to_beat = cur_rating;
|
||||
|
||||
if(!cur_analysis.movements.empty()) {
|
||||
assert(cur_analysis.movements.size() < 6);
|
||||
double& best_res = best_results[cur_analysis.movements.size()-1];
|
||||
rating_to_beat = best_res = maximum(best_res,cur_rating);
|
||||
}
|
||||
|
||||
for(int i = 0; i != units.size(); ++i) {
|
||||
const location current_unit = units[i];
|
||||
units.erase(units.begin() + i);
|
||||
|
||||
for(int j = 0; j != 6; ++j) {
|
||||
if(used_locations[j])
|
||||
continue;
|
||||
|
||||
typedef std::multimap<location,location>::const_iterator Itor;
|
||||
std::pair<Itor,Itor> its = dstsrc.equal_range(tiles[j]);
|
||||
while(its.first != its.second) {
|
||||
if(its.first->second == current_unit)
|
||||
break;
|
||||
++its.first;
|
||||
}
|
||||
|
||||
if(its.first == its.second)
|
||||
continue;
|
||||
|
||||
cur_analysis.movements.push_back(
|
||||
std::pair<location,location>(current_unit,tiles[j]));
|
||||
|
||||
cur_analysis.analyze(map,units_map,status,data,50);
|
||||
|
||||
if(cur_analysis.rating(0.0) > rating_to_beat) {
|
||||
|
||||
result.push_back(cur_analysis);
|
||||
used_locations[j] = true;
|
||||
do_analysis(map,loc,srcdst,dstsrc,tiles,used_locations,
|
||||
units,units_map,result,data,status,cur_analysis);
|
||||
used_locations[j] = false;
|
||||
}
|
||||
|
||||
cur_analysis.movements.pop_back();
|
||||
}
|
||||
|
||||
units.insert(units.begin() + i, current_unit);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ai {
|
||||
|
||||
struct battle_type {
|
||||
battle_type(const gamemap::location& a, const gamemap::location& d,
|
||||
gamemap::TERRAIN t)
|
||||
: attacker(a),defender(d),terrain(t),weapon(-1)
|
||||
{}
|
||||
|
||||
const gamemap::location attacker;
|
||||
const gamemap::location defender;
|
||||
const gamemap::TERRAIN terrain;
|
||||
int weapon;
|
||||
battle_stats stats;
|
||||
};
|
||||
|
||||
bool operator<(const battle_type& a, const battle_type& b)
|
||||
{
|
||||
return a.attacker < b.attacker ||
|
||||
a.attacker == b.attacker && a.defender < b.defender ||
|
||||
a.attacker == b.attacker && a.defender == b.defender &&
|
||||
a.terrain < b.terrain;
|
||||
}
|
||||
|
||||
bool operator==(const battle_type& a, const battle_type& b)
|
||||
{
|
||||
return a.attacker == b.attacker && a.defender == b.defender &&
|
||||
a.terrain == b.terrain;
|
||||
}
|
||||
|
||||
std::set<battle_type> weapon_choice_cache;
|
||||
|
||||
int choose_weapon(const gamemap& map, std::map<location,unit>& units,
|
||||
const gamestatus& status, const game_data& info,
|
||||
const location& att, const location& def,
|
||||
battle_stats& cur_stats, gamemap::TERRAIN terrain)
|
||||
{
|
||||
const std::map<location,unit>::const_iterator itor = units.find(att);
|
||||
if(itor == units.end())
|
||||
return -1;
|
||||
|
||||
static int cache_hits = 0;
|
||||
static int cache_misses = 0;
|
||||
|
||||
battle_type battle(att,def,terrain);
|
||||
const std::set<battle_type>::const_iterator cache_itor
|
||||
= weapon_choice_cache.find(battle);
|
||||
|
||||
if(cache_itor != weapon_choice_cache.end()) {
|
||||
assert(*cache_itor == battle);
|
||||
|
||||
++cache_hits;
|
||||
cur_stats = cache_itor->stats;
|
||||
|
||||
if(!(cache_itor->weapon >= 0 &&
|
||||
cache_itor->weapon < itor->second.attacks().size())) {
|
||||
}
|
||||
|
||||
assert(cache_itor->weapon >= 0 &&
|
||||
cache_itor->weapon < itor->second.attacks().size());
|
||||
return cache_itor->weapon;
|
||||
}
|
||||
|
||||
++cache_misses;
|
||||
|
||||
if((cache_misses%100) == 0) {
|
||||
std::cerr << "cache_stats: " << cache_hits << ":" << cache_misses << " " << weapon_choice_cache.size() << "\n";
|
||||
}
|
||||
|
||||
int current_choice = -1;
|
||||
double current_rating = 0.0;
|
||||
const std::vector<attack_type>& attacks = itor->second.attacks();
|
||||
assert(!attacks.empty());
|
||||
|
||||
for(int a = 0; a != attacks.size(); ++a) {
|
||||
const battle_stats stats = evaluate_battle_stats(map,att,def,a,units,
|
||||
status,info,terrain,false);
|
||||
|
||||
//TODO: improve this rating formula!
|
||||
const double rating =
|
||||
stats.chance_to_hit_defender*stats.damage_defender_takes*
|
||||
stats.nattacks -
|
||||
stats.chance_to_hit_attacker*stats.damage_attacker_takes*
|
||||
stats.ndefends;
|
||||
if(rating > current_rating || current_choice == -1) {
|
||||
current_choice = a;
|
||||
current_rating = rating;
|
||||
cur_stats = stats;
|
||||
}
|
||||
}
|
||||
|
||||
assert(current_choice >= 0 && current_choice < attacks.size());
|
||||
|
||||
battle.stats = cur_stats;
|
||||
battle.weapon = current_choice;
|
||||
weapon_choice_cache.insert(battle);
|
||||
|
||||
return current_choice;
|
||||
}
|
||||
|
||||
void attack_analysis::analyze(const gamemap& map,
|
||||
std::map<location,unit>& units,
|
||||
const gamestatus& status,
|
||||
const game_data& info, int num_sims)
|
||||
{
|
||||
const std::map<location,unit>::const_iterator defend_it =units.find(target);
|
||||
assert(defend_it != units.end());
|
||||
|
||||
target_value = defend_it->second.type().cost();
|
||||
target_value += (double(defend_it->second.experience())/
|
||||
double(defend_it->second.max_experience()))*target_value;
|
||||
target_starting_damage = defend_it->second.max_hitpoints() -
|
||||
defend_it->second.hitpoints();
|
||||
chance_to_kill = 0.0;
|
||||
avg_damage_inflicted = 0.0;
|
||||
avg_damage_taken = 0.0;
|
||||
resources_used = 0.0;
|
||||
terrain_quality = 0.0;
|
||||
counter_strength_ratio = 0.0;
|
||||
avg_losses = 0.0;
|
||||
|
||||
const int target_max_hp = defend_it->second.max_hitpoints();
|
||||
const int target_hp = defend_it->second.hitpoints();
|
||||
static std::vector<int> hitpoints;
|
||||
static std::vector<battle_stats> stats;
|
||||
|
||||
hitpoints.clear();
|
||||
stats.clear();
|
||||
weapons.clear();
|
||||
|
||||
std::vector<std::pair<location,location> >::const_iterator m;
|
||||
for(m = movements.begin(); m != movements.end(); ++m) {
|
||||
battle_stats bat_stats;
|
||||
const int weapon = choose_weapon(map,units,status,info,
|
||||
m->first,target, bat_stats,
|
||||
map[m->second.x][m->second.y]);
|
||||
|
||||
assert(weapon != -1);
|
||||
weapons.push_back(weapon);
|
||||
|
||||
stats.push_back(bat_stats);
|
||||
hitpoints.push_back(units.find(m->first)->second.hitpoints());
|
||||
}
|
||||
|
||||
for(int j = 0; j != num_sims; ++j) {
|
||||
|
||||
int defenderxp = 0;
|
||||
|
||||
int defhp = target_hp;
|
||||
for(int i = 0; i != movements.size() && defhp; ++i) {
|
||||
const battle_stats& stat = stats[i];
|
||||
int atthp = hitpoints[i];
|
||||
|
||||
int attacks = stat.nattacks;
|
||||
int defends = stat.ndefends;
|
||||
|
||||
std::map<location,unit>::const_iterator att
|
||||
= units.find(movements[i].first);
|
||||
double cost = att->second.type().cost();
|
||||
|
||||
//up to double the value of a unit based on experience
|
||||
cost += (double(att->second.experience())/
|
||||
double(att->second.max_experience()))*cost;
|
||||
|
||||
terrain_quality += stat.chance_to_hit_attacker*cost;
|
||||
resources_used += cost;
|
||||
|
||||
while(attacks || defends) {
|
||||
if(attacks) {
|
||||
const double roll = double(rand()%1000)/1000.0;
|
||||
if(roll < stat.chance_to_hit_defender) {
|
||||
defhp -= stat.damage_defender_takes;
|
||||
if(defhp <= 0) {
|
||||
|
||||
//the reward for advancing a unit is to
|
||||
//get a 'negative' loss of that unit
|
||||
const int xp = defend_it->second.type().level()*10;
|
||||
if(xp >= att->second.max_experience() -
|
||||
att->second.experience()) {
|
||||
avg_losses -= att->second.type().cost();
|
||||
}
|
||||
|
||||
//the reward for killing with a unit that
|
||||
//plagues is to get a 'negative' loss of that unit
|
||||
if(stat.attacker_plague) {
|
||||
avg_losses -= att->second.type().cost();
|
||||
}
|
||||
|
||||
defhp = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
atthp += stat.amount_attacker_drains;
|
||||
if(atthp > hitpoints[i])
|
||||
atthp = hitpoints[i];
|
||||
}
|
||||
|
||||
--attacks;
|
||||
}
|
||||
|
||||
if(defends) {
|
||||
const double roll = double(rand()%1000)/1000.0;
|
||||
if(roll < stat.chance_to_hit_attacker) {
|
||||
atthp -= stat.damage_attacker_takes;
|
||||
if(atthp <= 0) {
|
||||
atthp = 0;
|
||||
|
||||
//penalty for allowing plague is a 'negative' kill
|
||||
if(stat.defender_plague) {
|
||||
chance_to_kill -= 1.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
defhp += stat.amount_defender_drains;
|
||||
if(defhp > target_max_hp)
|
||||
defhp = target_max_hp;
|
||||
}
|
||||
|
||||
--defends;
|
||||
}
|
||||
}
|
||||
|
||||
if(defhp <= 0) {
|
||||
break;
|
||||
} else if(atthp == 0) {
|
||||
avg_losses += cost;
|
||||
} else if(map[movements[i].second.x][movements[i].second.y] ==
|
||||
gamemap::TOWER) {
|
||||
atthp += game_config::heal_amount;
|
||||
if(atthp > hitpoints[i])
|
||||
atthp = hitpoints[i];
|
||||
}
|
||||
|
||||
defenderxp += (atthp == 0 ? 10:1)*att->second.type().level();
|
||||
|
||||
avg_damage_taken += hitpoints[i] - atthp;
|
||||
}
|
||||
|
||||
//penalty for allowing advancement is a 'negative' kill
|
||||
if(defend_it->second.experience() < defend_it->second.max_experience()&&
|
||||
defend_it->second.experience() + defenderxp >=
|
||||
defend_it->second.max_experience()) {
|
||||
chance_to_kill -= 1.0;
|
||||
} else if(defhp == 0) {
|
||||
chance_to_kill += 1.0;
|
||||
} else if(map[defend_it->first.x][defend_it->first.y]==gamemap::TOWER) {
|
||||
defhp += game_config::heal_amount;
|
||||
if(defhp > target_hp)
|
||||
defhp = target_hp;
|
||||
}
|
||||
|
||||
avg_damage_inflicted += target_hp - defhp;
|
||||
}
|
||||
|
||||
chance_to_kill /= num_sims;
|
||||
avg_damage_inflicted /= num_sims;
|
||||
avg_damage_taken /= num_sims;
|
||||
terrain_quality /= resources_used;
|
||||
resources_used /= num_sims;
|
||||
avg_losses /= num_sims;
|
||||
}
|
||||
|
||||
double attack_analysis::rating(double aggression) const
|
||||
{
|
||||
double value = chance_to_kill*target_value - avg_losses;
|
||||
|
||||
//prefer to attack already damaged targets
|
||||
value += ((target_starting_damage/3 + avg_damage_inflicted)*
|
||||
(target_value/resources_used) -
|
||||
(1.0-aggression)*avg_damage_taken*(resources_used/target_value))/10.0;
|
||||
value /= ((resources_used/2) + (resources_used/2)*terrain_quality);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
std::vector<attack_analysis> analyze_targets(
|
||||
const gamemap& map,
|
||||
const std::multimap<location,location>& srcdst,
|
||||
const std::multimap<location,location>& dstsrc,
|
||||
std::map<location,unit>& units,
|
||||
const team& current_team, int team_num,
|
||||
const gamestatus& status, const game_data& data
|
||||
)
|
||||
{
|
||||
log_scope("analyzing targets...");
|
||||
|
||||
weapon_choice_cache.clear();
|
||||
|
||||
std::vector<attack_analysis> res;
|
||||
|
||||
std::vector<location> unit_locs;
|
||||
for(std::map<location,unit>::const_iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
if(i->second.side() == team_num) {
|
||||
unit_locs.push_back(i->first);
|
||||
}
|
||||
}
|
||||
|
||||
bool used_locations[6];
|
||||
std::fill(used_locations,used_locations+6,false);
|
||||
|
||||
for(std::map<location,unit>::const_iterator j = units.begin();
|
||||
j != units.end(); ++j) {
|
||||
|
||||
//attack anyone who is on the enemy side, and who is not invisible
|
||||
if(current_team.is_enemy(j->second.side()) &&
|
||||
j->second.invisible(map.underlying_terrain(
|
||||
map[j->first.x][j->first.y])) == false) {
|
||||
location adjacent[6];
|
||||
get_adjacent_tiles(j->first,adjacent);
|
||||
attack_analysis analysis;
|
||||
analysis.target = j->first;
|
||||
|
||||
const int ticks = SDL_GetTicks();
|
||||
|
||||
do_analysis(map,j->first,srcdst,dstsrc,adjacent,used_locations,
|
||||
unit_locs,units,res,data,status,analysis);
|
||||
|
||||
const int time_taken = SDL_GetTicks() - ticks;
|
||||
static int max_time = 0;
|
||||
if(time_taken > max_time)
|
||||
max_time = time_taken;
|
||||
|
||||
std::cerr << "do_analysis took " << time_taken << " (" << max_time << ")\n";
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
76
ai_attack.hpp
Normal file
76
ai_attack.hpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef AI_ATTACK_INCLUDED
|
||||
#define AI_ATTACK_INCLUDED
|
||||
|
||||
#include "actions.hpp"
|
||||
#include "ai.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "map.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace ai {
|
||||
|
||||
struct attack_analysis
|
||||
{
|
||||
void analyze(const gamemap& map, std::map<location,unit>& units,
|
||||
const gamestatus& status, const game_data& info, int sims);
|
||||
|
||||
double rating(double aggression) const;
|
||||
|
||||
gamemap::location target;
|
||||
std::vector<std::pair<gamemap::location,gamemap::location> > movements;
|
||||
std::vector<int> weapons;
|
||||
|
||||
//the value of the unit being targeted
|
||||
double target_value;
|
||||
|
||||
//the value on average, of units lost in the combat
|
||||
double avg_losses;
|
||||
|
||||
//estimated % chance to kill the unit
|
||||
double chance_to_kill;
|
||||
|
||||
//the average hitpoints damage inflicted
|
||||
double avg_damage_inflicted;
|
||||
|
||||
int target_starting_damage;
|
||||
|
||||
//the average hitpoints damage taken
|
||||
double avg_damage_taken;
|
||||
|
||||
//the sum of the values of units used in the attack
|
||||
double resources_used;
|
||||
|
||||
//the weighted average of the % chance to hit each attacking unit
|
||||
double terrain_quality;
|
||||
|
||||
//the ratio of the attacks the unit being attacked will get to
|
||||
//the strength of its most powerful attack
|
||||
double counter_strength_ratio;
|
||||
};
|
||||
|
||||
std::vector<attack_analysis> analyze_targets(
|
||||
const gamemap& map,
|
||||
const std::multimap<location,location>& srcdst,
|
||||
const std::multimap<location,location>& dstsrc,
|
||||
std::map<location,unit>& units,
|
||||
const team& current_team, int team_num,
|
||||
const gamestatus& status, const game_data& data
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
291
ai_move.cpp
Normal file
291
ai_move.cpp
Normal file
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "ai_move.hpp"
|
||||
#include "display.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "log.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace ai {
|
||||
|
||||
struct move_cost_calculator
|
||||
{
|
||||
move_cost_calculator(const unit& u, const gamemap& map,
|
||||
const game_data& data,
|
||||
const std::map<location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
const std::multimap<location,location>& dstsrc)
|
||||
: unit_(u), map_(map), data_(data), units_(units),
|
||||
move_type_(u.type().movement_type()), loc_(loc), dstsrc_(dstsrc)
|
||||
{}
|
||||
|
||||
double cost(const location& loc) const
|
||||
{
|
||||
if(!map_.on_board(loc))
|
||||
return 1000.0;
|
||||
|
||||
//if this unit can move to that location this turn, it has a very
|
||||
//very low cost
|
||||
typedef std::multimap<location,location>::const_iterator Itor;
|
||||
std::pair<Itor,Itor> range = dstsrc_.equal_range(loc);
|
||||
while(range.first != range.second) {
|
||||
if(range.first->first == loc)
|
||||
return 0.01;
|
||||
++range.first;
|
||||
}
|
||||
|
||||
const gamemap::TERRAIN terrain =
|
||||
map_.underlying_terrain(map_[loc.x][loc.y]);
|
||||
|
||||
const double modifier = 1.0;//move_type_.defense_modifier(map_,terrain);
|
||||
const double move_cost = move_type_.movement_cost(map_,terrain);
|
||||
|
||||
double enemies = 0;
|
||||
/* //is this stuff responsible for making it take a long time?
|
||||
location adj[7];
|
||||
adj[0] = loc;
|
||||
get_adjacent_tiles(loc,adj+1);
|
||||
for(int i = 0; i != 7; ++i) {
|
||||
const std::map<location,unit>::const_iterator en=units_.find(adj[i]);
|
||||
//at the moment, don't allow any units to be in the path
|
||||
if(i == 0 && en != units_.end()) {
|
||||
return 1000.0;
|
||||
}
|
||||
|
||||
if(en != units_.end() && en->second.side() == enemy_) {
|
||||
enemies += 1.0;
|
||||
}
|
||||
}
|
||||
*/
|
||||
const double res = modifier*move_cost + enemies*2.0;
|
||||
assert(res > 0);
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
const unit& unit_;
|
||||
const gamemap& map_;
|
||||
const game_data& data_;
|
||||
const std::map<location,unit>& units_;
|
||||
const unit_movement_type& move_type_;
|
||||
const gamemap::location loc_;
|
||||
const std::multimap<location,location> dstsrc_;
|
||||
|
||||
};
|
||||
|
||||
std::vector<target> find_targets(
|
||||
const gamemap& map, std::map<location,unit>& units,
|
||||
std::vector<team>& teams, int current_team
|
||||
)
|
||||
{
|
||||
log_scope("finding targets...");
|
||||
|
||||
team& tm = teams[current_team-1];
|
||||
|
||||
std::vector<target> targets;
|
||||
|
||||
if(tm.village_value() > 0.0) {
|
||||
const std::vector<location>& towers = map.towers();
|
||||
for(std::vector<location>::const_iterator t = towers.begin();
|
||||
t != towers.end(); ++t) {
|
||||
assert(map.on_board(*t));
|
||||
bool get_tower = true;
|
||||
for(int i = 0; i != teams.size(); ++i) {
|
||||
if(!tm.is_enemy(i+1) && teams[i].owns_tower(*t)) {
|
||||
get_tower = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(get_tower) {
|
||||
targets.push_back(target(*t,tm.village_value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<team::target>& team_targets = tm.targets();
|
||||
|
||||
//find the enemy leaders and explicit targets
|
||||
std::map<location,unit>::const_iterator u;
|
||||
for(u = units.begin(); u != units.end(); ++u) {
|
||||
|
||||
//is an enemy leader
|
||||
if(u->second.can_recruit() && tm.is_enemy(u->second.side())) {
|
||||
assert(map.on_board(u->first));
|
||||
targets.push_back(target(u->first,tm.leader_value()));
|
||||
}
|
||||
|
||||
//explicit targets for this team
|
||||
for(std::vector<team::target>::iterator j = team_targets.begin();
|
||||
j != team_targets.end(); ++j) {
|
||||
if(u->second.matches_filter(j->criteria)) {
|
||||
std::cerr << "found explicit target..." << j->value << "\n";
|
||||
targets.push_back(target(u->first,j->value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> new_values;
|
||||
|
||||
for(std::vector<target>::iterator i = targets.begin();
|
||||
i != targets.end(); ++i) {
|
||||
|
||||
new_values.push_back(i->value);
|
||||
|
||||
for(std::vector<target>::const_iterator j = targets.begin();
|
||||
j != targets.end(); ++j) {
|
||||
if(i->loc == j->loc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const double distance = abs(j->loc.x - i->loc.x) +
|
||||
abs(j->loc.y - i->loc.y);
|
||||
new_values.back() += j->value/(distance*distance);
|
||||
}
|
||||
}
|
||||
|
||||
assert(new_values.size() == targets.size());
|
||||
for(int n = 0; n != new_values.size(); ++n) {
|
||||
std::cerr << "target value: " << targets[n].value << " -> " << new_values[n] << "\n";
|
||||
targets[n].value = new_values[n];
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
std::pair<location,location> choose_move(
|
||||
std::vector<target>& targets,
|
||||
const std::multimap<location,location>& dstsrc,
|
||||
std::map<location,unit>& units,
|
||||
const gamemap& map, const std::vector<team>& teams,
|
||||
int current_team,
|
||||
const game_data& data
|
||||
)
|
||||
{
|
||||
log_scope("choosing move");
|
||||
|
||||
std::vector<target>::const_iterator ittg;
|
||||
for(ittg = targets.begin(); ittg != targets.end(); ++ittg) {
|
||||
assert(map.on_board(ittg->loc));
|
||||
}
|
||||
|
||||
paths::route best_route;
|
||||
std::map<location,unit>::iterator best = units.end();
|
||||
double best_rating = 0.1;
|
||||
std::vector<target>::iterator best_target = targets.end();
|
||||
|
||||
std::map<location,unit>::iterator u;
|
||||
|
||||
//find the first eligible unit
|
||||
for(u = units.begin(); u != units.end(); ++u) {
|
||||
if(!(u->second.side() != current_team || u->second.can_recruit() ||
|
||||
u->second.movement_left() <= 0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(u == units.end()) {
|
||||
std::cout << "no eligible units found\n";
|
||||
return std::pair<location,location>();
|
||||
}
|
||||
|
||||
//guardian units stay put
|
||||
if(u->second.is_guardian()) {
|
||||
std::cerr << u->second.type().name() << " is guardian, staying still\n";
|
||||
return std::pair<location,location>(u->first,u->first);
|
||||
}
|
||||
|
||||
const move_cost_calculator cost_calc(u->second,map,data,units,
|
||||
u->first,dstsrc);
|
||||
|
||||
//choose the best target for that unit
|
||||
for(std::vector<target>::iterator tg = targets.begin();
|
||||
tg != targets.end(); ++tg) {
|
||||
assert(map.on_board(tg->loc));
|
||||
const paths::route cur_route = a_star_search(u->first,tg->loc,
|
||||
minimum(tg->value/best_rating,100.0),cost_calc);
|
||||
const double rating = tg->value/cur_route.move_left;
|
||||
std::cerr << tg->value << "/" << cur_route.move_left << " = " << rating << "\n";
|
||||
if(best_target == targets.end() || rating > best_rating) {
|
||||
best_rating = rating;
|
||||
best_target = tg;
|
||||
best = u;
|
||||
best_route = cur_route;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(best_target == targets.end()) {
|
||||
std::cout << "no eligible targets found\n";
|
||||
return std::pair<location,location>();
|
||||
}
|
||||
|
||||
//now see if any other unit can put a better bid forward
|
||||
for(++u; u != units.end(); ++u) {
|
||||
if(u->second.side() != current_team || u->second.can_recruit() ||
|
||||
u->second.movement_left() <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const move_cost_calculator calc(u->second,map,data,units,
|
||||
u->first,dstsrc);
|
||||
const paths::route cur_route = a_star_search(u->first,best_target->loc,
|
||||
minimum(best_target->value/best_rating,100.0),calc);
|
||||
const double rating = best_target->value/cur_route.move_left;
|
||||
if(best == units.end() || rating > best_rating) {
|
||||
best_rating = rating;
|
||||
best = u;
|
||||
best_route = cur_route;
|
||||
}
|
||||
}
|
||||
|
||||
assert(best_target >= targets.begin() && best_target < targets.end());
|
||||
best_target->value -= best->second.type().cost()/20.0;
|
||||
if(best_target->value <= 0.0)
|
||||
targets.erase(best_target);
|
||||
|
||||
for(ittg = targets.begin();
|
||||
ittg != targets.end(); ++ittg) {
|
||||
assert(map.on_board(ittg->loc));
|
||||
}
|
||||
|
||||
for(std::vector<location>::reverse_iterator ri =
|
||||
best_route.steps.rbegin(); ri != best_route.steps.rend(); ++ri) {
|
||||
|
||||
if(game_config::debug) {
|
||||
display::debug_highlight(*ri,0.2);
|
||||
}
|
||||
|
||||
typedef std::multimap<location,location>::const_iterator Itor;
|
||||
std::pair<Itor,Itor> its = dstsrc.equal_range(*ri);
|
||||
while(its.first != its.second) {
|
||||
if(its.first->second == best->first) {
|
||||
return std::pair<location,location>(its.first->second,
|
||||
its.first->first);
|
||||
}
|
||||
|
||||
++its.first;
|
||||
}
|
||||
}
|
||||
|
||||
if(best != units.end()) {
|
||||
std::cout << "Could not make good move, staying still\n";
|
||||
return std::pair<location,location>(best->first,best->first);
|
||||
}
|
||||
|
||||
std::cout << "Could not find anywhere to move!\n";
|
||||
return std::pair<location,location>();
|
||||
}
|
||||
|
||||
}
|
49
ai_move.hpp
Normal file
49
ai_move.hpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef AI_MOVE_H_INCLUDED
|
||||
#define AI_MOVE_H_INCLUDED
|
||||
|
||||
#include "map.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace ai {
|
||||
|
||||
typedef gamemap::location location;
|
||||
|
||||
struct target {
|
||||
target(const location& pos, double val) : loc(pos), value(val)
|
||||
{}
|
||||
location loc;
|
||||
double value;
|
||||
};
|
||||
|
||||
std::vector<target> find_targets(
|
||||
const gamemap& map, std::map<location,unit>& units,
|
||||
std::vector<team>& teams, int current_team
|
||||
);
|
||||
|
||||
std::pair<location,location> choose_move(
|
||||
std::vector<target>& targets,
|
||||
const std::multimap<location,location>& dstsrc,
|
||||
std::map<location,unit>& units,
|
||||
const gamemap& map, const std::vector<team>& teams,
|
||||
int current_team,
|
||||
const game_data& data
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
156
changelog
Normal file
156
changelog
Normal file
|
@ -0,0 +1,156 @@
|
|||
Version 0.4.8:
|
||||
- added lohari's new terrain tiles
|
||||
- adjusted the way AI will summon reinforcements to a combat area by making it still move after summoning reinforcements
|
||||
|
||||
Version 0.4.7:
|
||||
- patched the units config files with miyo's patch to clean up the structure
|
||||
- made it so the game will dump core if it segfaults
|
||||
- fixed crash reported by zas when a unit attacks
|
||||
- you can now zoom in and out when the AI is moving
|
||||
- added improved keyboard control that allows keys to be set in the locale settings. Keyboard shortcuts (in the English locale) are now,
|
||||
|
||||
u: undo
|
||||
r: redo
|
||||
n: cycle units (including only partially moved units)
|
||||
l: goto leader
|
||||
z: zoom in
|
||||
x: zoom out
|
||||
c: default zoom
|
||||
control-f: toggle full screen
|
||||
control-a: toggle accelerated mode
|
||||
control-d: view unit resistance (defense) table
|
||||
control-t: view unit terrain table
|
||||
|
||||
(some keyboard shortcuts are still not implemented, and there is currently no way to configure keyboard shortcuts in-game)
|
||||
|
||||
- added ability to make different game-paths - e.g. the scenario you play could be determined by how you won last scenario. Dialogs can now be popped up which ask the player to make a selection, and the selection they make determines how the game progresses. (But no part of the game actually uses this feature yet).
|
||||
- added scenario objectives in scenario 10
|
||||
- now when the AI attacks an enemy and doesn't kill it, it will move other nearby units toward the place where the combat took place
|
||||
- if an AI sees only combats that result in loss nearby, it will request reinforcements from nearby.
|
||||
- made it so that if the macro WESNOTH_PATH is defined, the game will look for its data files at that path. You can thus compile with e.g. -DWESNOTH_PATH=\"/usr/local/games/wesnoth\"
|
||||
- added benchmarking of times to perform various operations in the game
|
||||
- made leaders start in a 'keep'. (But need a better image for the keep :)
|
||||
- added difficulty level settings for scenarios up to scenario 9
|
||||
- added (partially done) Italian translation
|
||||
- added 'skip turn' option, accessible by pressing space - will end the selected unit's turn, and go to the next unit that has moves left
|
||||
|
||||
Version 0.4.6:
|
||||
- converted over to using png images instead of bmp
|
||||
- fixed up difficulty levels for 'normal' on scenarios 3 and 4 as reported by miyo
|
||||
- made it so healing animations and sounds don't play during replays
|
||||
- removed name generation code, since it wasn't fast enough
|
||||
- added sounds to a number of Elvish units
|
||||
- made it so that when the attack-selection dialog is displayed, the attacking unit is displayed in the right side bar, so that one can easily compare the attacking and defending units
|
||||
- fixed bug where after a scenario is loaded, on new turn it wouldn't scroll to the leader
|
||||
- made the display area for units bigger when recalling, to fit in units like the Druid
|
||||
- reduced Mage's hitpoints from 25 -> 18
|
||||
- added in plague ability and gave it to walking corpse and wraith. A unit with plague will create a new unit of their own type whenever they kill an enemy unit.
|
||||
- tweaked recruitment pattern on scenario 6
|
||||
- made marksman and sharpshooter both very bad at close range
|
||||
- increased power of Mage of Light's attack
|
||||
- made it so you get a gold bonus when you complete scenario 9
|
||||
- fixed scenario 7 to say 'survive for 2 days'
|
||||
- changed Swordman -> Swordsman and Beserker -> Berserker
|
||||
- made some speed changes which will hopefully help for people who are finding it slow on startup and after selecting difficulty levels
|
||||
- fixed display problem where background of menus that had scroll arrows looked displaced
|
||||
- corrected facing of dwarvern units
|
||||
- added transition hexes at the edge of the scenario to make the edges of the map look nicer
|
||||
- added a 'show grid' option
|
||||
|
||||
Version 0.4.5:
|
||||
- added mine image for scenario 11 provided by fmunoz
|
||||
- added some missing headers to source files, problem pointed out by zas
|
||||
- made the animation for healing take a little longer
|
||||
- added new shortcut: control-F alternates between full screen and windowed mode
|
||||
- added buttons 'next' and 'skip' to introduction sequence
|
||||
- added two new images from fmunoz to introduction sequence
|
||||
- added new preferences dialog, which contains volume controls for music and sound effects
|
||||
- got rid of flicker on title screen when you cancel selection of a dialog box
|
||||
- fixed a number of drawing bugs which caused strange lines to appear across the screen sometimes, and caused the game to display badly when zoomed out alot. Zooming out should now work perfectly
|
||||
|
||||
Version 0.4.4:
|
||||
- fixed assertion failure if you try to start a campaign and then cancel on the difficulty level settings
|
||||
- added 'merge_translations' tool which will merge an old version of a foreign language translation to the current English translation, making a new translation that has foreign language strings where they are available, and English strings otherwise
|
||||
- fixed crash if you opened a menu that had some empty strings in it (for instance clicking preferences in the French version)
|
||||
- changed recruitment so that now a leader can only recruit if they are on a starting location. They can recruit units on any vacant castle tile connected to the starting location they are on. The player can choose a location to recruit onto by selecting it when accessing the recruit menu. TODO: Need a graphic to distinguish the starting hex from other castle hexes
|
||||
- changed maximum items displayed in a menu before up/down buttons appear from 10 to 18
|
||||
- fixed bug in scenario 8 where objectives would not be displayed
|
||||
- fixed bug in scenario 9 where major characters could die without loss
|
||||
- changed it so that when writing a configuration file, [/element-name] will be used to end an element instead of [end]
|
||||
- fixed corrupted save file bug reported by miyo
|
||||
- made the White Mages that join you in level 7 leave you at the end of the level, as they are meant to.
|
||||
- changed advancement animation colour to black for chaotic units
|
||||
- made it so that when an AI-controlled unit moves, its details are displayed on the sidebar, as suggested by miyo
|
||||
- implemented algorithm so that if an AI is moving and the map has to be scrolled, frames will be skipped if necessary to make the AI moving at a decent speed
|
||||
- added Danish translation
|
||||
- added improved French translation
|
||||
- made it so that if you mouse-over a unit, it will be displayed in the unit details on the sidebar
|
||||
- changed healing so that a healer can only heal up to 12 hitpoints per turn. Added animations and sound effects to healing.
|
||||
- added in AI type 'guardian', which is a unit that will stay in position until enemies come in range at which point it attacks. Now in scenario 3 there is a cage with many mermen in it that has 2 naga guardians around it.
|
||||
- added scenario 10 provided by Shroud and scenario 11 provided by fmunoz
|
||||
|
||||
Version 0.4.3:
|
||||
- made it so that when a directory is scanned for files, only files ending in .cfg will be used. This is mainly to stop vim swap files from being used
|
||||
- changed AI's movement routines to make it slightly more intelligent
|
||||
- added in difficulty levels - easy, medium, and hard
|
||||
- scenarios 1 and 2 now have easy/medium/hard difficulty levels implemented for them
|
||||
- when an enemy dies, its energy bar now fades out with it
|
||||
- added 'turbo' mode in preferences area. In turbo mode, the operation of the shift key is inverted. Turbo mode and full screen mode settings are now saved to the preferences file.
|
||||
- made the time of day go dawn - day - day - dusk - night - night as suggested by miyo
|
||||
- fixed up bugs in the AI's pathfinding, the AI should now be substantially smarter
|
||||
- allowed setting of custom target units for the AI
|
||||
- added debug mode with -debug option
|
||||
|
||||
Version 0.4.2:
|
||||
- the recall facility is now sorted and in a table
|
||||
- If you hold shift, the game won't scroll at all, it'll jump between locations
|
||||
- Added attack animations for necromancer and mage
|
||||
- Added in Lohari's images with corrected shadows
|
||||
- Added in Lohari's crossed daggers for battles, instead of the cross
|
||||
- Added in Paladin's patch to highlight the hex of the unit that is currently selected
|
||||
- Used new Makefile provided by zas
|
||||
- Added setting of window title as suggested by zas
|
||||
- Changed so that holding shift skips fading in recruiting units
|
||||
- Increased cost of Naga from 8 -> 11 gold to make scenario 3 easier
|
||||
- Fixed bug with recalling in mid-level saved games - should save alot of game corruption issues
|
||||
- Made it so attack sounds do not play while loading game
|
||||
- Added animations for Goblin Knight, Wolf Rider, and Troll Whelp
|
||||
- Made it so Glordorf in Scenario 2 doesn't join the player
|
||||
- Added in new missile images done by fmunoz
|
||||
- Fixed bug where merman's storm trident would run out when the merman advances, or at the end of the level
|
||||
- Page up and page down can be used to maneuver through menus
|
||||
- Fixed bug reported by Jaramir, where exiting a multiplayer game would cause the save state to be remembered
|
||||
- Fixed bug reported by Jaramir, where recruiting or recalling a unit wouldn't update your gold immediately
|
||||
- Added better guarantees that when an AI attacks a unit, the unit being attacked won't be mostly off the screen
|
||||
- Changed Necromancer to level 2
|
||||
- Fixed bug where quitting the game by pressing escape during opening dialog sequence would cause the game to crash
|
||||
- Added facility suggested by miyo where an entire directory can be scanned to look for configuration files. Re-arranged files in data/ to utilize this.
|
||||
- Changed configuration files to allow [/tagname] to end a tag instead of [end]. Added better error handling for bad configuration files
|
||||
- Added utility make_translation which when run, will construct a sample translation with all the strings that can be translated in it
|
||||
- Removed Outrider's spear/charge attack and replaced it with a sword
|
||||
- Saved games are now stored in a .wesnoth/saves directory which is located under $HOME (or under the directory the program is run from is $HOME is not defined). There is now a user preferences file in .wesnoth/preferences
|
||||
- Added facility to change languages in-game. The language used by default will be obtained from the $LANG environment variable
|
||||
|
||||
Version 0.4.1:
|
||||
- added music and sound support. The game has one song, provided by ZhayTee. Added some sample sounds for Elvish Fighter attacking.
|
||||
- fixed up bug where if you loaded a mid-level game, and then saved at the end of level, the save would be corrupt
|
||||
- fixed bug where the display mode would always be displayed as 'windowed'
|
||||
- make the game report an error message if switching between windowed/full screen fails
|
||||
- make the game handle switches between windowed and full screen even if exact colour depth can't be matched
|
||||
- arrow keys can now be used to maneuver menus, and enter can be used for 'ok and yes' in dialogs
|
||||
- fixed bug that caused a crash at the end of the tutorial
|
||||
- fixed bug where moving a unit along the border edges would go very slowly
|
||||
|
||||
Version 0.4:
|
||||
- fixed missiles up to point in the correct direction
|
||||
- fixed naming of Shallow Water and Deep Water (used to be 'ocean' and 'coast' and not internationalized properly)
|
||||
- removed bug where a dialog could overwrite part of the sidebar on the right
|
||||
- added resistance tables and terrain movement and defense tables to 'unit description'
|
||||
- added unit descriptions to many more units
|
||||
- changed rectangle on the map of Wesnoth to cross as suggested by ettin
|
||||
- completed scenario 9
|
||||
- added gryphon rider unit and put them in scenario 9
|
||||
- now you have to 'kill' Li'sar in scenario 6 before she'll surrender
|
||||
- Wraith's damage taken up from 7 to 8.
|
||||
- when a unit is recruited, it now fades in
|
||||
- added more cleaning in Makefile as per miyo's suggestions
|
460
config.cpp
Normal file
460
config.cpp
Normal file
|
@ -0,0 +1,460 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stack>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
std::string read_file(const std::string& fname)
|
||||
{
|
||||
//if we have a path to the data
|
||||
#ifdef WESNOTH_PATH
|
||||
|
||||
//convert any filepath which is relative
|
||||
if(!fname.empty() && fname[0] != '/' && WESNOTH_PATH[0] == '/') {
|
||||
std::cerr << "trying to read file: '" <<
|
||||
(WESNOTH_PATH + std::string("/") + fname) << "'\n";
|
||||
const std::string& res =
|
||||
read_file(WESNOTH_PATH + std::string("/") + fname);
|
||||
if(!res.empty()) {
|
||||
std::cerr << "success\n";
|
||||
return res;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::ifstream file(fname.c_str());
|
||||
std::string res;
|
||||
char c;
|
||||
while(file.get(c)) {
|
||||
res.resize(res.size()+1);
|
||||
res[res.size()-1] = c;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void write_file(const std::string& fname, const std::string& data)
|
||||
{
|
||||
std::ofstream file(fname.c_str());
|
||||
for(std::string::const_iterator i = data.begin(); i != data.end(); ++i) {
|
||||
file << *i;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void internal_preprocess_file(const std::string& fname,
|
||||
std::map<std::string,std::string> defines_map,
|
||||
int depth, std::vector<char>& res)
|
||||
{
|
||||
//if it's a directory, we process all files in the directory
|
||||
//that end in .cfg
|
||||
if(is_directory(fname)) {
|
||||
|
||||
std::vector<std::string> files;
|
||||
get_files_in_dir(fname,&files,NULL,ENTIRE_FILE_PATH);
|
||||
|
||||
for(std::vector<std::string>::const_iterator f = files.begin();
|
||||
f != files.end(); ++f) {
|
||||
if(f->size() > 4 && std::equal(f->end()-4,f->end(),".cfg")) {
|
||||
internal_preprocess_file(*f,defines_map,depth,res);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string data = read_file(fname);
|
||||
|
||||
bool in_quotes = false;
|
||||
|
||||
for(std::string::const_iterator i = data.begin(); i != data.end(); ++i) {
|
||||
const char c = *i;
|
||||
if(c == '"') {
|
||||
in_quotes = !in_quotes;
|
||||
}
|
||||
|
||||
if(c == '{') {
|
||||
std::stringstream newfile;
|
||||
for(++i; i != data.end() && *i != '}'; ++i) {
|
||||
newfile << *i;
|
||||
}
|
||||
|
||||
if(i == data.end())
|
||||
break;
|
||||
|
||||
const std::string fname = newfile.str();
|
||||
|
||||
//if this is a known pre-processing symbol, then we insert
|
||||
//it, otherwise we assume it's a file name to load
|
||||
if(defines_map.count(fname) != 0) {
|
||||
const std::string& val = defines_map[fname];
|
||||
res.insert(res.end(),val.begin(),val.end());
|
||||
} else if(depth < 20) {
|
||||
internal_preprocess_file("data/" + newfile.str(),
|
||||
defines_map, depth+1,res);
|
||||
} else {
|
||||
const std::string& str = read_file(newfile.str());
|
||||
res.insert(res.end(),str.begin(),str.end());
|
||||
}
|
||||
} else if(c == '#' && !in_quotes) {
|
||||
//if this is the beginning of a pre-processing definition
|
||||
static const std::string hash_define("#define");
|
||||
if(data.end() - i > hash_define.size() &&
|
||||
std::equal(hash_define.begin(),hash_define.end(),i)) {
|
||||
i += hash_define.size();
|
||||
while(i != data.end() && isspace(*i))
|
||||
++i;
|
||||
|
||||
const std::string::const_iterator end =
|
||||
std::find_if(i,data.end(),isspace);
|
||||
|
||||
if(end == data.end())
|
||||
break;
|
||||
|
||||
const std::string symbol(i,end);
|
||||
std::stringstream value;
|
||||
for(i = end+1; i != data.end(); ++i) {
|
||||
static const std::string hash_enddef("#enddef");
|
||||
if(data.end() - i > hash_enddef.size() &&
|
||||
std::equal(hash_enddef.begin(),hash_enddef.end(),i)) {
|
||||
i += hash_enddef.size();
|
||||
break;
|
||||
}
|
||||
|
||||
value << *i;
|
||||
}
|
||||
|
||||
defines_map.insert(std::pair<std::string,std::string>(
|
||||
symbol,value.str()));
|
||||
}
|
||||
|
||||
//if this is a pre-processing conditional
|
||||
static const std::string hash_ifdef("#ifdef");
|
||||
static const std::string hash_else("#else");
|
||||
static const std::string hash_endif("#endif");
|
||||
|
||||
if(data.end() - i > hash_ifdef.size() &&
|
||||
std::equal(hash_ifdef.begin(),hash_ifdef.end(),i)) {
|
||||
i += hash_ifdef.size();
|
||||
while(i != data.end() && isspace(*i))
|
||||
++i;
|
||||
|
||||
const std::string::const_iterator end =
|
||||
std::find_if(i,data.end(),isspace);
|
||||
|
||||
if(end == data.end())
|
||||
break;
|
||||
|
||||
//if the symbol is not defined, then we want to skip
|
||||
//to the #endif or #else . Otherwise, continue processing
|
||||
//as normal. The #endif will just be treated as a comment
|
||||
//anyway.
|
||||
const std::string symbol(i,end);
|
||||
if(defines_map.count(symbol) == 0) {
|
||||
while(data.end() - i > hash_endif.size() &&
|
||||
!std::equal(hash_endif.begin(),hash_endif.end(),i) &&
|
||||
!std::equal(hash_else.begin(),hash_else.end(),i)) {
|
||||
++i;
|
||||
}
|
||||
|
||||
i = std::find(i,data.end(),'\n');
|
||||
if(i == data.end())
|
||||
break;
|
||||
} else {
|
||||
i = end;
|
||||
}
|
||||
}
|
||||
|
||||
//if we come across a #else, it must mean that we found a #ifdef
|
||||
//earlier, and we should ignore until #endif
|
||||
if(data.end() - i > hash_else.size() &&
|
||||
std::equal(hash_else.begin(),hash_else.end(),i)) {
|
||||
while(data.end() - i > hash_endif.size() &&
|
||||
!std::equal(hash_endif.begin(),hash_endif.end(),i)) {
|
||||
++i;
|
||||
}
|
||||
|
||||
i = std::find(i,data.end(),'\n');
|
||||
if(i == data.end())
|
||||
break;
|
||||
}
|
||||
|
||||
for(; i != data.end() && *i != '\n'; ++i) {
|
||||
}
|
||||
|
||||
if(i == data.end())
|
||||
break;
|
||||
|
||||
res.push_back('\n');
|
||||
} else {
|
||||
res.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
} //end anonymous namespace
|
||||
|
||||
std::string preprocess_file(const std::string& fname,
|
||||
const std::map<std::string,std::string>* defines)
|
||||
{
|
||||
log_scope("preprocessing file...");
|
||||
static const std::map<std::string,std::string> default_defines;
|
||||
if(defines == NULL)
|
||||
defines = &default_defines;
|
||||
|
||||
std::vector<char> res;
|
||||
internal_preprocess_file(fname,*defines,0,res);
|
||||
return std::string(res.begin(),res.end());
|
||||
}
|
||||
|
||||
config::config(const std::string& data)
|
||||
{
|
||||
log_scope("parsing config...");
|
||||
read(data);
|
||||
}
|
||||
|
||||
config::config(const config& cfg) : values(cfg.values)
|
||||
{
|
||||
for(std::map<std::string,std::vector<config*> >::const_iterator i =
|
||||
cfg.children.begin(); i != cfg.children.end(); ++i) {
|
||||
std::vector<config*> v;
|
||||
for(std::vector<config*>::const_iterator j = i->second.begin();
|
||||
j != i->second.end(); ++j) {
|
||||
v.push_back(new config(**j));
|
||||
}
|
||||
|
||||
children[i->first].swap(v);
|
||||
}
|
||||
}
|
||||
|
||||
config::~config()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
config& config::operator=(const config& cfg)
|
||||
{
|
||||
clear();
|
||||
|
||||
values = cfg.values;
|
||||
|
||||
for(std::map<std::string,std::vector<config*> >::const_iterator i =
|
||||
cfg.children.begin(); i != cfg.children.end(); ++i) {
|
||||
std::vector<config*> v;
|
||||
for(std::vector<config*>::const_iterator j = i->second.begin();
|
||||
j != i->second.end(); ++j) {
|
||||
v.push_back(new config(**j));
|
||||
}
|
||||
|
||||
children[i->first].swap(v);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void config::read(const std::string& data)
|
||||
{
|
||||
clear();
|
||||
|
||||
std::stack<std::string> element_names;
|
||||
std::stack<config*> elements;
|
||||
elements.push(this);
|
||||
element_names.push("");
|
||||
|
||||
enum { ELEMENT_NAME, IN_ELEMENT, VARIABLE_NAME, VALUE }
|
||||
state = IN_ELEMENT;
|
||||
std::string var;
|
||||
std::string value;
|
||||
|
||||
bool in_quotes = false;
|
||||
|
||||
for(std::string::const_iterator i = data.begin(); i != data.end(); ++i) {
|
||||
const char c = *i;
|
||||
switch(state) {
|
||||
case ELEMENT_NAME:
|
||||
if(c == ']') {
|
||||
if(value == "end" || !value.empty() && value[0] == '/') {
|
||||
assert(!elements.empty());
|
||||
|
||||
if(value[0] == '/' &&
|
||||
std::string("/" + element_names.top()) != value) {
|
||||
throw error("Found illegal end tag: '" +
|
||||
value + "', at end of '" +
|
||||
element_names.top() + "'");
|
||||
}
|
||||
|
||||
elements.pop();
|
||||
element_names.pop();
|
||||
|
||||
if(elements.empty()) {
|
||||
throw error("Unexpected terminating tag\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
state = IN_ELEMENT;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
config* const new_config = new config();
|
||||
elements.top()->children[value].push_back(new_config);
|
||||
elements.push(new_config);
|
||||
element_names.push(value);
|
||||
state = IN_ELEMENT;
|
||||
value = "";
|
||||
} else {
|
||||
value.resize(value.size()+1);
|
||||
value[value.size()-1] = c;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case IN_ELEMENT:
|
||||
if(c == '[') {
|
||||
state = ELEMENT_NAME;
|
||||
value = "";
|
||||
} else if(!isspace(c)) {
|
||||
value.resize(1);
|
||||
value[0] = c;
|
||||
state = VARIABLE_NAME;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case VARIABLE_NAME:
|
||||
if(c == '=') {
|
||||
state = VALUE;
|
||||
var = value;
|
||||
value = "";
|
||||
} else {
|
||||
value.resize(value.size()+1);
|
||||
value[value.size()-1] = c;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case VALUE:
|
||||
if(c == '"') {
|
||||
in_quotes = !in_quotes;
|
||||
} else if(c == '\n' && !in_quotes) {
|
||||
state = IN_ELEMENT;
|
||||
elements.top()->values.insert(
|
||||
std::pair<std::string,std::string>(var,value));
|
||||
var = "";
|
||||
value = "";
|
||||
} else {
|
||||
value.resize(value.size()+1);
|
||||
value[value.size()-1] = c;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string config::write() const
|
||||
{
|
||||
std::string res;
|
||||
for(std::map<std::string,std::string>::const_iterator i = values.begin();
|
||||
i != values.end(); ++i) {
|
||||
if(i->second.empty() == false) {
|
||||
res += i->first + "=" + i->second + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
for(std::map<std::string,std::vector<config*> >::const_iterator j =
|
||||
children.begin(); j != children.end(); ++j) {
|
||||
const std::vector<config*>& v = j->second;
|
||||
for(std::vector<config*>::const_iterator it = v.begin();
|
||||
it != v.end(); ++it) {
|
||||
res += "[" + j->first + "]\n";
|
||||
res += (*it)->write();
|
||||
res += "[/" + j->first + "]\n";
|
||||
}
|
||||
}
|
||||
|
||||
res += "\n";
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::string> config::split(const std::string& val)
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
|
||||
std::string::const_iterator i1 = val.begin();
|
||||
std::string::const_iterator i2 = val.begin();
|
||||
|
||||
while(i2 != val.end()) {
|
||||
if(*i2 == ',') {
|
||||
std::string new_val(i1,i2);
|
||||
if(!new_val.empty())
|
||||
res.push_back(new_val);
|
||||
++i2;
|
||||
while(i2 != val.end() && *i2 == ' ')
|
||||
++i2;
|
||||
|
||||
i1 = i2;
|
||||
} else {
|
||||
++i2;
|
||||
}
|
||||
}
|
||||
|
||||
std::string new_val(i1,i2);
|
||||
if(!new_val.empty())
|
||||
res.push_back(new_val);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool config::has_value(const std::string& values, const std::string& val)
|
||||
{
|
||||
const std::vector<std::string>& vals = split(values);
|
||||
return std::count(vals.begin(),vals.end(),val) > 0;
|
||||
}
|
||||
|
||||
void config::clear()
|
||||
{
|
||||
for(std::map<std::string,std::vector<config*> >::iterator i =
|
||||
children.begin(); i != children.end(); ++i) {
|
||||
std::vector<config*>& v = i->second;
|
||||
for(std::vector<config*>::iterator j = v.begin(); j != v.end(); ++j)
|
||||
delete *j;
|
||||
}
|
||||
|
||||
children.clear();
|
||||
values.clear();
|
||||
}
|
||||
|
||||
//#define TEST_CONFIG
|
||||
|
||||
#ifdef TEST_CONFIG
|
||||
|
||||
int main()
|
||||
{
|
||||
config cfg(read_file("testconfig"));
|
||||
std::cout << cfg.write() << std::endl;
|
||||
}
|
||||
|
||||
#endif
|
52
config.hpp
Normal file
52
config.hpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef CONFIG_HPP_INCLUDED
|
||||
#define CONFIG_HPP_INCLUDED
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
std::string read_file(const std::string& fname);
|
||||
void write_file(const std::string& fname, const std::string& data);
|
||||
std::string preprocess_file(const std::string& fname,
|
||||
const std::map<std::string,std::string>* defines=0);
|
||||
|
||||
typedef std::map<std::string,std::string> string_map;
|
||||
|
||||
struct config
|
||||
{
|
||||
config() {}
|
||||
config(const std::string& data); //throws config::error
|
||||
config(const config& cfg);
|
||||
~config();
|
||||
|
||||
config& operator=(const config& cfg);
|
||||
|
||||
void read(const std::string& data); //throws config::error
|
||||
std::string write() const;
|
||||
|
||||
std::map<std::string,std::string> values;
|
||||
std::map<std::string,std::vector<config*> > children;
|
||||
|
||||
static std::vector<std::string> split(const std::string& val);
|
||||
static bool has_value(const std::string& values, const std::string& val);
|
||||
|
||||
void clear();
|
||||
|
||||
struct error {
|
||||
error(const std::string& msg) : message(msg) {}
|
||||
std::string message;
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
12
copyright
Normal file
12
copyright
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
82
dialogs.cpp
Normal file
82
dialogs.cpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "dialogs.hpp"
|
||||
#include "language.hpp"
|
||||
#include "replay.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace dialogs
|
||||
{
|
||||
|
||||
void advance_unit(const game_data& info,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
display& gui, bool random_choice)
|
||||
{
|
||||
std::map<gamemap::location,unit>::iterator u = units.find(loc);
|
||||
if(u == units.end() || u->second.advances() == false)
|
||||
return;
|
||||
|
||||
const std::vector<std::string>& options = u->second.type().advances_to();
|
||||
|
||||
std::vector<unit> sample_units;
|
||||
for(std::vector<std::string>::const_iterator op = options.begin();
|
||||
op != options.end(); ++op) {
|
||||
sample_units.push_back(::get_advanced_unit(info,units,loc,*op));
|
||||
}
|
||||
|
||||
int res = 0;
|
||||
|
||||
if(options.empty()) {
|
||||
return;
|
||||
} else if(random_choice) {
|
||||
res = rand()%options.size();
|
||||
} else if(options.size() > 1) {
|
||||
|
||||
res = gui::show_dialog(gui,NULL,string_table["advance_unit_heading"],
|
||||
string_table["advance_unit_message"],
|
||||
gui::OK_ONLY, &options, &sample_units);
|
||||
}
|
||||
|
||||
//when the unit advances, it fades to white, and then switches to the
|
||||
//new unit, then fades back to the normal colour
|
||||
double intensity;
|
||||
for(intensity = 1.0; intensity >= 0.0; intensity -= 0.05) {
|
||||
gui.set_advancing_unit(loc,intensity);
|
||||
gui.draw(false);
|
||||
gui.update_display();
|
||||
SDL_Delay(30);
|
||||
}
|
||||
|
||||
recorder.choose_option(res);
|
||||
|
||||
::advance_unit(info,units,loc,options[res]);
|
||||
|
||||
gui.invalidate_unit();
|
||||
|
||||
for(intensity = 0.0; intensity <= 1.0; intensity += 0.05) {
|
||||
gui.set_advancing_unit(loc,intensity);
|
||||
gui.draw(false);
|
||||
gui.update_display();
|
||||
SDL_Delay(30);
|
||||
}
|
||||
|
||||
gui.set_advancing_unit(gamemap::location(),0.0);
|
||||
|
||||
gui.invalidate_all();
|
||||
gui.draw();
|
||||
}
|
||||
|
||||
} //end namespace dialogs
|
27
dialogs.hpp
Normal file
27
dialogs.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef DIALOGS_H_INCLUDED
|
||||
#define DIALOGS_H_INCLUDED
|
||||
|
||||
#include "actions.hpp"
|
||||
#include "display.hpp"
|
||||
#include "menu.hpp"
|
||||
|
||||
namespace dialogs
|
||||
{
|
||||
void advance_unit(const game_data& info,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
display& gui, bool random_choice=false);
|
||||
}
|
||||
|
||||
#endif
|
2268
display.cpp
Normal file
2268
display.cpp
Normal file
File diff suppressed because it is too large
Load diff
251
display.hpp
Normal file
251
display.hpp
Normal file
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef DISPLAY_H_INCLUDED
|
||||
#define DISPLAY_H_INCLUDED
|
||||
|
||||
#include "gamestatus.hpp"
|
||||
#include "key.hpp"
|
||||
#include "map.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "team.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "video.hpp"
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
class display
|
||||
{
|
||||
public:
|
||||
typedef std::map<gamemap::location,unit> unit_map;
|
||||
typedef short Pixel;
|
||||
|
||||
display(unit_map& units, CVideo& video,
|
||||
const gamemap& map, const gamestatus& status,
|
||||
const std::vector<team>& t);
|
||||
~display();
|
||||
|
||||
Pixel rgb(int r, int g, int b) const;
|
||||
|
||||
void scroll(double xmov, double ymov);
|
||||
void zoom(double amount);
|
||||
void default_zoom();
|
||||
|
||||
enum SCROLL_TYPE { SCROLL, WARP };
|
||||
void scroll_to_tile(int x, int y, SCROLL_TYPE scroll_type=SCROLL);
|
||||
void scroll_to_tiles(int x1, int y1, int x2, int y2,
|
||||
SCROLL_TYPE scroll_type=SCROLL);
|
||||
|
||||
void redraw_everything();
|
||||
void draw(bool update=true,bool force=false);
|
||||
|
||||
int x() const;
|
||||
int mapx() const;
|
||||
int y() const;
|
||||
|
||||
void select_hex(gamemap::location hex);
|
||||
void highlight_hex(gamemap::location hex);
|
||||
gamemap::location hex_clicked_on(int x, int y);
|
||||
gamemap::location minimap_location_on(int x, int y);
|
||||
|
||||
void set_paths(const paths* paths_list);
|
||||
|
||||
double get_location_x(const gamemap::location& loc) const;
|
||||
double get_location_y(const gamemap::location& loc) const;
|
||||
|
||||
void move_unit(const std::vector<gamemap::location>& path, unit& u);
|
||||
bool unit_attack(const gamemap::location& a, const gamemap::location& b,
|
||||
int damage, const attack_type& attack);
|
||||
void draw_tile(int x, int y, SDL_Surface* unit_image=NULL,
|
||||
double alpha=1.0, short blend_to=0);
|
||||
|
||||
CVideo& video() { return screen_; }
|
||||
|
||||
enum IMAGE_TYPE { UNSCALED, SCALED, GREYED, BRIGHTENED };
|
||||
SDL_Surface* getImage(const std::string& filename,IMAGE_TYPE type=SCALED);
|
||||
|
||||
//blits a surface with black as alpha
|
||||
void blit_surface(int x, int y, SDL_Surface* surface);
|
||||
|
||||
void invalidate_all();
|
||||
void invalidate_game_status();
|
||||
void invalidate_unit();
|
||||
|
||||
void recalculate_minimap();
|
||||
|
||||
void add_overlay(const gamemap::location& loc, const std::string& image);
|
||||
void remove_overlay(const gamemap::location& loc);
|
||||
|
||||
void draw_unit_details(int x, int y, const gamemap::location& loc,
|
||||
const unit& u, SDL_Rect& description_rect, SDL_Rect& profile_rect);
|
||||
|
||||
void update_display();
|
||||
void update_rect(const SDL_Rect& rect);
|
||||
|
||||
void draw_terrain_palette(int x, int y, gamemap::TERRAIN selected);
|
||||
gamemap::TERRAIN get_terrain_on(int palx, int paly, int x, int y);
|
||||
|
||||
void set_team(int team);
|
||||
|
||||
void set_advancing_unit(const gamemap::location& loc, double amount);
|
||||
|
||||
void lock_updates(bool value);
|
||||
bool update_locked() const;
|
||||
|
||||
bool turbo() const;
|
||||
void set_turbo(bool turbo);
|
||||
|
||||
void set_grid(bool grid);
|
||||
|
||||
static void debug_highlight(const gamemap::location& loc, double amount);
|
||||
static void clear_debug_highlights();
|
||||
|
||||
private:
|
||||
display(const display&);
|
||||
void operator=(const display&);
|
||||
|
||||
void move_unit_between(const gamemap::location& a,
|
||||
const gamemap::location& b,
|
||||
const unit& u);
|
||||
|
||||
void draw_unit(int x, int y, const SDL_Surface* image,
|
||||
bool reverse, bool upside_down=false,
|
||||
double alpha=1.0, short blendto=0);
|
||||
|
||||
void unit_die(const gamemap::location& loc, SDL_Surface* image=NULL);
|
||||
|
||||
bool unit_attack_ranged(const gamemap::location& a,
|
||||
const gamemap::location& b,
|
||||
int damage, const attack_type& attack);
|
||||
|
||||
void draw_sidebar();
|
||||
SDL_Rect get_minimap_location(int x, int y, int w, int h);
|
||||
void draw_minimap(int x, int y, int w, int h);
|
||||
void draw_game_status(int x, int y);
|
||||
|
||||
SDL_Rect gameStatusRect_;
|
||||
SDL_Rect unitDescriptionRect_;
|
||||
SDL_Rect unitProfileRect_;
|
||||
|
||||
int lastTimeOfDay_;
|
||||
|
||||
void bounds_check_position();
|
||||
|
||||
std::vector<SDL_Surface*> getAdjacentTerrain(int x, int y, IMAGE_TYPE type);
|
||||
SDL_Surface* getTerrain(gamemap::TERRAIN, IMAGE_TYPE type,
|
||||
int x, int y, const std::string& dir="");
|
||||
|
||||
enum TINT { GREY_IMAGE, BRIGHTEN_IMAGE };
|
||||
SDL_Surface* getImageTinted(const std::string& filename, TINT tint);
|
||||
SDL_Surface* getMinimap(int w, int h);
|
||||
|
||||
void clearImageCache();
|
||||
|
||||
CVideo& screen_;
|
||||
mutable CKey keys_;
|
||||
double xpos_, ypos_, zoom_;
|
||||
const gamemap& map_;
|
||||
|
||||
gamemap::location selectedHex_;
|
||||
gamemap::location mouseoverHex_;
|
||||
|
||||
unit_map& units_;
|
||||
|
||||
std::map<std::string,SDL_Surface*> images_, scaledImages_,
|
||||
greyedImages_, brightenedImages_;
|
||||
|
||||
//function which finds the start and end rows on the energy bar image
|
||||
//where white pixels are substituted for the colour of the energy
|
||||
const std::pair<int,int>& calculate_energy_bar();
|
||||
std::pair<int,int> energy_bar_count_;
|
||||
|
||||
SDL_Surface* minimap_;
|
||||
bool minimapDecorationsDrawn_;
|
||||
|
||||
const paths* pathsList_;
|
||||
|
||||
const gamestatus& status_;
|
||||
|
||||
const std::vector<team>& teams_;
|
||||
|
||||
int lastDraw_;
|
||||
int drawSkips_;
|
||||
|
||||
void invalidate(const gamemap::location& loc);
|
||||
|
||||
std::set<gamemap::location> invalidated_;
|
||||
bool invalidateAll_;
|
||||
bool invalidateUnit_;
|
||||
bool invalidateGameStatus_;
|
||||
|
||||
std::multimap<gamemap::location,std::string> overlays_;
|
||||
|
||||
void update_whole_screen();
|
||||
void update_map_area();
|
||||
void update_side_bar();
|
||||
std::vector<SDL_Rect> updateRects_;
|
||||
|
||||
bool sideBarBgDrawn_;
|
||||
|
||||
int currentTeam_;
|
||||
|
||||
//used to store a unit that is not drawn, because it's currently
|
||||
//being moved or otherwise changed
|
||||
gamemap::location hiddenUnit_;
|
||||
|
||||
//used to store any unit that is currently being hit
|
||||
gamemap::location hitUnit_;
|
||||
|
||||
//used to store any unit that is dying
|
||||
gamemap::location deadUnit_;
|
||||
double deadAmount_;
|
||||
|
||||
//used to store any unit that is advancing
|
||||
gamemap::location advancingUnit_;
|
||||
double advancingAmount_;
|
||||
|
||||
int updatesLocked_;
|
||||
|
||||
bool turbo_, grid_;
|
||||
|
||||
//for debug mode
|
||||
static std::map<gamemap::location,double> debugHighlights_;
|
||||
};
|
||||
|
||||
struct update_locker
|
||||
{
|
||||
update_locker(display& d, bool lock=true) : disp(d), unlock(lock) {
|
||||
if(lock) {
|
||||
disp.lock_updates(true);
|
||||
}
|
||||
}
|
||||
|
||||
~update_locker() {
|
||||
unlock_update();
|
||||
}
|
||||
|
||||
void unlock_update() {
|
||||
if(unlock) {
|
||||
disp.lock_updates(false);
|
||||
unlock = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
display& disp;
|
||||
bool unlock;
|
||||
};
|
||||
|
||||
#endif
|
242
filesystem.cpp
Normal file
242
filesystem.cpp
Normal file
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
//include files for opendir(3), readdir(3), etc. These files may vary
|
||||
//from platform to platform, since these functions are NOT ANSI-conforming
|
||||
//functions. They may have to be altered to port to new platforms
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
//for mkdir
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <direct.h>
|
||||
|
||||
#include <io.h>
|
||||
|
||||
#define mkdir(a,b) (_mkdir(a))
|
||||
|
||||
#define mode_t int
|
||||
|
||||
#endif
|
||||
|
||||
//for getenv
|
||||
#include <cstdlib>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include "filesystem.hpp"
|
||||
|
||||
namespace {
|
||||
const mode_t AccessMode = 00770;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DIR_INVALID(d) (d == -1)
|
||||
#else
|
||||
#define DIR_INVALID(d) (d == NULL)
|
||||
#endif
|
||||
|
||||
void get_files_in_dir(const std::string& directory,
|
||||
std::vector<std::string>* files,
|
||||
std::vector<std::string>* dirs,
|
||||
FILE_NAME_MODE mode)
|
||||
{
|
||||
|
||||
//if we have a path to find directories in, then convert relative
|
||||
//pathnames to be rooted on the wesnoth path
|
||||
#ifdef WESNOTH_PATH
|
||||
if(!directory.empty() && directory[0] != '/' && WESNOTH_PATH[0] == '/') {
|
||||
const std::string& dir = WESNOTH_PATH + std::string("/") + directory;
|
||||
if(is_directory(dir)) {
|
||||
get_files_in_dir(dir,files,dirs,mode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
_finddata_t fileinfo;
|
||||
long dir = _findfirst((directory + "/*.*").c_str(),&fileinfo);
|
||||
#else
|
||||
|
||||
DIR* dir = opendir(directory.c_str());
|
||||
#endif
|
||||
|
||||
if(DIR_INVALID(dir)) {
|
||||
//try to make the directory
|
||||
const int res = mkdir(directory.c_str(),AccessMode);
|
||||
if(res == 0) {
|
||||
#ifdef _WIN32
|
||||
dir = _findfirst((directory + "/*.*").c_str(),&fileinfo);
|
||||
#else
|
||||
dir = opendir(directory.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
if(DIR_INVALID(dir))
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
int res = dir;
|
||||
do {
|
||||
const std::string path = (mode == ENTIRE_FILE_PATH ?
|
||||
directory + "/" : std::string("")) + fileinfo.name;
|
||||
|
||||
if(fileinfo.attrib&_A_SUBDIR) {
|
||||
if(dirs != NULL)
|
||||
dirs->push_back(path);
|
||||
} else {
|
||||
if(files != NULL)
|
||||
files->push_back(path);
|
||||
}
|
||||
|
||||
res = _findnext(dir,&fileinfo);
|
||||
} while(!DIR_INVALID(res));
|
||||
|
||||
_findclose(dir);
|
||||
|
||||
#else
|
||||
struct dirent* entry;
|
||||
while((entry = readdir(dir)) != NULL) {
|
||||
const std::string name((directory + "/") + entry->d_name);
|
||||
|
||||
//try to open it as a directory to test if it is a directory
|
||||
DIR* const temp_dir = opendir(name.c_str());
|
||||
if(temp_dir != NULL) {
|
||||
closedir(temp_dir);
|
||||
if(dirs != NULL) {
|
||||
if(mode == ENTIRE_FILE_PATH)
|
||||
dirs->push_back(name);
|
||||
else
|
||||
dirs->push_back(entry->d_name);
|
||||
}
|
||||
} else if(files != NULL) {
|
||||
if(mode == ENTIRE_FILE_PATH)
|
||||
files->push_back(name);
|
||||
else
|
||||
files->push_back(entry->d_name);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
#endif
|
||||
|
||||
if(files != NULL)
|
||||
std::sort(files->begin(),files->end());
|
||||
|
||||
if(dirs != NULL)
|
||||
std::sort(dirs->begin(),dirs->end());
|
||||
}
|
||||
|
||||
std::string get_prefs_file()
|
||||
{
|
||||
return get_user_data_dir() + "/preferences";
|
||||
}
|
||||
|
||||
std::string get_saves_dir()
|
||||
{
|
||||
const std::string dir_path = get_user_data_dir() + "/saves";
|
||||
|
||||
#ifdef _WIN32
|
||||
_mkdir(dir_path.c_str());
|
||||
#else
|
||||
|
||||
DIR* dir = opendir(dir_path.c_str());
|
||||
if(dir == NULL) {
|
||||
const int res = mkdir(dir_path.c_str(),AccessMode);
|
||||
if(res == 0) {
|
||||
dir = opendir(dir_path.c_str());
|
||||
} else {
|
||||
std::cerr << "Could not open or create directory: '" << dir_path
|
||||
<< "'\n";
|
||||
}
|
||||
}
|
||||
|
||||
if(dir == NULL)
|
||||
return "";
|
||||
#endif
|
||||
|
||||
return dir_path;
|
||||
}
|
||||
|
||||
std::string get_user_data_dir()
|
||||
{
|
||||
static const char* const current_dir = ".";
|
||||
|
||||
#ifdef _WIN32
|
||||
_mkdir("userdata");
|
||||
return "userdata";
|
||||
#else
|
||||
|
||||
const char* home_str = getenv("HOME");
|
||||
if(home_str == NULL)
|
||||
home_str = current_dir;
|
||||
|
||||
const std::string home(home_str);
|
||||
|
||||
const std::string dir_path = home + "/.wesnoth";
|
||||
DIR* dir = opendir(dir_path.c_str());
|
||||
if(dir == NULL) {
|
||||
const int res = mkdir(dir_path.c_str(),AccessMode);
|
||||
if(res == 0) {
|
||||
dir = opendir(dir_path.c_str());
|
||||
} else {
|
||||
std::cerr << "Could not open or create directory: '" << dir_path
|
||||
<< "'\n";
|
||||
}
|
||||
}
|
||||
|
||||
if(dir == NULL)
|
||||
return "";
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return dir_path;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool is_directory(const std::string& fname)
|
||||
{
|
||||
|
||||
#ifdef WESNOTH_PATH
|
||||
if(!fname.empty() && fname[0] != '/' && WESNOTH_PATH[0] == '/') {
|
||||
if(is_directory(WESNOTH_PATH + std::string("/") + fname))
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
_finddata_t info;
|
||||
const long handle = _findfirst((fname + "/*").c_str(),&info);
|
||||
if(handle >= 0) {
|
||||
_findclose(handle);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
#else
|
||||
DIR* const dir = opendir(fname.c_str());
|
||||
if(dir != NULL) {
|
||||
closedir(dir);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
31
filesystem.hpp
Normal file
31
filesystem.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef FILESYSTEM_HPP_INCLUDED
|
||||
#define FILESYSTEM_HPP_INCLUDED
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum FILE_NAME_MODE { ENTIRE_FILE_PATH, FILE_NAME_ONLY };
|
||||
|
||||
void get_files_in_dir(const std::string& dir,
|
||||
std::vector<std::string>* files,
|
||||
std::vector<std::string>* dirs=NULL,
|
||||
FILE_NAME_MODE mode=FILE_NAME_ONLY);
|
||||
|
||||
std::string get_prefs_file();
|
||||
std::string get_saves_dir();
|
||||
std::string get_user_data_dir();
|
||||
|
||||
bool is_directory(const std::string& fname);
|
||||
|
||||
#endif
|
198
font.cpp
Normal file
198
font.cpp
Normal file
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "config.hpp"
|
||||
#include "font.hpp"
|
||||
#include "SDL_ttf.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
std::map<int,TTF_Font*> font_table;
|
||||
|
||||
//SDL_ttf seems to have a problem where TTF_OpenFont will seg fault if
|
||||
//the font file doesn't exist, so make sure it exists first.
|
||||
TTF_Font* open_font(const std::string& fname, int size)
|
||||
{
|
||||
if(read_file(fname).empty())
|
||||
return NULL;
|
||||
|
||||
return TTF_OpenFont(fname.c_str(),size);
|
||||
}
|
||||
|
||||
TTF_Font* get_font(int size)
|
||||
{
|
||||
const std::map<int,TTF_Font*>::iterator it = font_table.find(size);
|
||||
if(it != font_table.end())
|
||||
return it->second;
|
||||
|
||||
TTF_Font* font = NULL;
|
||||
std::cerr << "opening font file...\n";
|
||||
|
||||
#ifdef WESNOTH_PATH
|
||||
font = open_font(std::string(WESNOTH_PATH) + "/images/misc/Vera.ttf",size);
|
||||
#endif
|
||||
|
||||
if(font == NULL) {
|
||||
font = open_font("images/misc/Vera.ttf",size);
|
||||
}
|
||||
|
||||
if(font == NULL) {
|
||||
std::cerr << "Could not open font file\n";
|
||||
return font;
|
||||
}
|
||||
|
||||
TTF_SetFontStyle(font,TTF_STYLE_NORMAL);
|
||||
|
||||
std::cerr << "inserting font...\n";
|
||||
font_table.insert(std::pair<int,TTF_Font*>(size,font));
|
||||
return font;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace font {
|
||||
|
||||
manager::manager()
|
||||
{
|
||||
const int res = TTF_Init();
|
||||
if(res < 0) {
|
||||
std::cerr << "Could not initialize true type fonts\n";
|
||||
} else {
|
||||
std::cerr << "Initialized true type fonts\n";
|
||||
}
|
||||
}
|
||||
|
||||
manager::~manager()
|
||||
{
|
||||
for(std::map<int,TTF_Font*>::iterator i = font_table.begin();
|
||||
i != font_table.end(); ++i) {
|
||||
TTF_CloseFont(i->second);
|
||||
}
|
||||
|
||||
TTF_Quit();
|
||||
}
|
||||
|
||||
SDL_Rect draw_text_line(display* gui, const SDL_Rect& area, int size,
|
||||
COLOUR colour, const std::string& text, int x, int y,
|
||||
SDL_Surface* bg)
|
||||
{
|
||||
static const SDL_Color colours[] =
|
||||
// neutral good bad
|
||||
{ {0xFF,0xFF,0,0}, {0,0xFF,0,0}, {0xFF,0,0,0} };
|
||||
|
||||
const SDL_Color& col = colours[colour];
|
||||
TTF_Font* const font = get_font(size);
|
||||
if(font == NULL) {
|
||||
std::cerr << "Could not get font\n";
|
||||
SDL_Rect res;
|
||||
res.x = 0; res.y = 0; res.w = 0; res.h = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
SDL_Surface* const surface = TTF_RenderText_Blended(font,text.c_str(),col);
|
||||
if(surface == NULL) {
|
||||
std::cerr << "Could not render ttf: '" << text << "'\n";
|
||||
SDL_Rect res;
|
||||
res.x = 0; res.y = 0; res.w = 0; res.h = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
SDL_Rect dest;
|
||||
dest.x = x;
|
||||
dest.y = y;
|
||||
dest.w = surface->w;
|
||||
dest.h = surface->h;
|
||||
|
||||
if(dest.x + dest.w > area.x + area.w) {
|
||||
dest.w = area.x + area.w - dest.x;
|
||||
}
|
||||
|
||||
if(dest.y + dest.h > area.y + area.h) {
|
||||
dest.h = area.y + area.h - dest.y;
|
||||
}
|
||||
|
||||
if(gui != NULL) {
|
||||
SDL_Rect src = dest;
|
||||
src.x = 0;
|
||||
src.y = 0;
|
||||
SDL_BlitSurface(surface,&src,gui->video().getSurface(),&dest);
|
||||
}
|
||||
|
||||
SDL_FreeSurface(surface);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
|
||||
SDL_Rect draw_text(display* gui, const SDL_Rect& area, int size,
|
||||
COLOUR colour, const std::string& txt, int x, int y,
|
||||
SDL_Surface* bg)
|
||||
{
|
||||
//make sure there's always at least a space, so we can ensure
|
||||
//that we can return a rectangle for height
|
||||
static const std::string blank_text(" ");
|
||||
const std::string& text = txt.empty() ? blank_text : txt;
|
||||
|
||||
SDL_Rect res;
|
||||
res.x = x;
|
||||
res.y = y;
|
||||
res.w = 0;
|
||||
res.h = 0;
|
||||
|
||||
std::string::const_iterator i1 = text.begin();
|
||||
std::string::const_iterator i2 = std::find(i1,text.end(),'\n');
|
||||
for(;;) {
|
||||
if(i1 != i2) {
|
||||
COLOUR col = NORMAL_COLOUR;
|
||||
int sz = size;
|
||||
if(*i1 == '#') {
|
||||
col = BAD_COLOUR;
|
||||
++i1;
|
||||
} else if(*i1 == '@') {
|
||||
col = GOOD_COLOUR;
|
||||
++i1;
|
||||
} else if(*i1 == '+') {
|
||||
sz += 2;
|
||||
++i1;
|
||||
} else if(*i1 == '-') {
|
||||
sz -= 2;
|
||||
++i1;
|
||||
}
|
||||
|
||||
if(i1 != i2) {
|
||||
const std::string new_string(i1,i2);
|
||||
const SDL_Rect rect =
|
||||
draw_text_line(gui,area,sz,col,new_string,x,y,bg);
|
||||
if(rect.w > res.w)
|
||||
res.w = rect.w;
|
||||
res.h += rect.h;
|
||||
y += rect.h;
|
||||
}
|
||||
}
|
||||
|
||||
if(i2 == text.end()) {
|
||||
break;
|
||||
}
|
||||
|
||||
i1 = i2+1;
|
||||
i2 = std::find(i1,text.end(),'\n');
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
36
font.hpp
Normal file
36
font.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef FONT_HPP_INCLUDED
|
||||
#define FONT_HPP_INCLUDED
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
#include "display.hpp"
|
||||
#include "video.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace font {
|
||||
|
||||
struct manager {
|
||||
manager();
|
||||
~manager();
|
||||
};
|
||||
|
||||
enum COLOUR { NORMAL_COLOUR, GOOD_COLOUR, BAD_COLOUR };
|
||||
|
||||
SDL_Rect draw_text(display* gui, const SDL_Rect& area, int size, COLOUR colour,
|
||||
const std::string& text, int x, int y, SDL_Surface* bg=NULL);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
390
game.cpp
Normal file
390
game.cpp
Normal file
|
@ -0,0 +1,390 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "SDL.h"
|
||||
|
||||
#include "actions.hpp"
|
||||
#include "ai.hpp"
|
||||
#include "config.hpp"
|
||||
#include "dialogs.hpp"
|
||||
#include "display.hpp"
|
||||
#include "font.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "game_events.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "key.hpp"
|
||||
#include "language.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "multiplayer.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "preferences.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "sound.hpp"
|
||||
#include "team.hpp"
|
||||
#include "unit_types.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "video.hpp"
|
||||
#include "widgets/button.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
LEVEL_RESULT play_game(display& disp, game_state& state, config& game_config,
|
||||
game_data& units_data, CVideo& video)
|
||||
{
|
||||
CKey key;
|
||||
|
||||
std::string type = state.campaign_type;
|
||||
if(type.empty())
|
||||
type = "scenario";
|
||||
const std::vector<config*>& scenarios = game_config.children[type];
|
||||
|
||||
for(int i = state.scenario; i < scenarios.size(); ++i) {
|
||||
std::vector<config*>& story = scenarios[i]->children["story"];
|
||||
|
||||
try {
|
||||
LEVEL_RESULT res = REPLAY;
|
||||
|
||||
state.label = scenarios[i]->values["name"];
|
||||
|
||||
recorder.set_save_info(state);
|
||||
|
||||
while(res == REPLAY) {
|
||||
state = recorder.get_save_info();
|
||||
if(!recorder.empty()) {
|
||||
recorder.start_replay();
|
||||
}
|
||||
|
||||
res = play_level(units_data,game_config,scenarios[i],
|
||||
video,state,story);
|
||||
}
|
||||
|
||||
recorder.clear();
|
||||
state.replay_data.clear();
|
||||
|
||||
if(res != VICTORY) {
|
||||
return res;
|
||||
}
|
||||
} catch(gamestatus::load_game_failed& e) {
|
||||
std::cerr << "The game could not be loaded: " << e.message << "\n";
|
||||
return QUIT;
|
||||
} catch(gamestatus::game_error& e) {
|
||||
std::cerr << "An error occurred while playing the game: "
|
||||
<< e.message << "\n";
|
||||
return QUIT;
|
||||
} catch(gamemap::incorrect_format_exception& e) {
|
||||
std::cerr << "The game map could not be loaded: " << e.msg_ << "\n";
|
||||
return QUIT;
|
||||
}
|
||||
|
||||
//skip over any scenarios which should be skipped, because their
|
||||
//conditions aren't met
|
||||
while(i+1 < scenarios.size()) {
|
||||
bool skip = false;
|
||||
|
||||
std::vector<config*>& conditions =
|
||||
scenarios[i+1]->children["condition"];
|
||||
for(std::vector<config*>::iterator cond = conditions.begin();
|
||||
cond != conditions.end(); ++cond) {
|
||||
if(game_events::conditional_passed(state,NULL,**cond) == false)
|
||||
skip = true;
|
||||
}
|
||||
|
||||
if(!skip)
|
||||
break;
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
//if this isn't the last scenario, then save the game
|
||||
if(i+1 < scenarios.size()) {
|
||||
state.label = scenarios[i+1]->values["name"];
|
||||
state.scenario = i+1;
|
||||
|
||||
const int should_save = gui::show_dialog(disp,NULL,"",
|
||||
string_table["save_game_message"],
|
||||
gui::YES_NO,NULL,NULL,
|
||||
string_table["save_game_label"],&state.label);
|
||||
|
||||
if(should_save == 0) {
|
||||
save_game(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return VICTORY;
|
||||
}
|
||||
|
||||
int play_game(int argc, char** argv)
|
||||
{
|
||||
std::string text_chr = read_file("data/text.chr");
|
||||
text_chr.resize(256*8);
|
||||
|
||||
CVideo video(text_chr.c_str());
|
||||
const font::manager font_manager;
|
||||
const sound::manager sound_manager;
|
||||
const preferences::manager prefs_manager;
|
||||
|
||||
std::map<std::string,std::string> defines_map;
|
||||
defines_map["NORMAL"] = "";
|
||||
config game_config(preprocess_file("data/game.cfg", &defines_map));
|
||||
|
||||
const std::vector<config*>& units = game_config.children["units"];
|
||||
if(units.empty()) {
|
||||
std::cerr << "Could not find units configuration\n";
|
||||
std::cerr << game_config.write();
|
||||
return 0;
|
||||
}
|
||||
|
||||
game_data units_data(*units[0]);
|
||||
|
||||
const bool lang_res = set_language(get_locale(), game_config);
|
||||
if(!lang_res) {
|
||||
std::cerr << "No translation for locale '" << get_locale()
|
||||
<< "', default to locale 'en'\n";
|
||||
|
||||
const bool lang_res = set_language("en", game_config);
|
||||
if(!lang_res) {
|
||||
std::cerr << "Language data not found\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool test_mode = false;
|
||||
int video_flags = preferences::fullscreen() ? FULL_SCREEN : 0;
|
||||
|
||||
for(int arg = 1; arg != argc; ++arg) {
|
||||
const std::string val(argv[arg]);
|
||||
if(val == "-windowed") {
|
||||
video_flags = 0;
|
||||
} else if(val == "-test") {
|
||||
test_mode = true;
|
||||
} else if(val == "-debug") {
|
||||
game_config::debug = true;
|
||||
}
|
||||
}
|
||||
|
||||
const int bpp = video.modePossible(1024,768,16,video_flags);
|
||||
|
||||
if(bpp == 0) {
|
||||
std::cerr << "The required video mode, 1024x768x16 "
|
||||
<< "is not supported\n";
|
||||
|
||||
if(video_flags == FULL_SCREEN && argc == 0)
|
||||
std::cerr << "Try running the program with the -windowed option "
|
||||
<< "using a 16bpp X windows setting\n";
|
||||
|
||||
if(video_flags == 0 && argc == 0)
|
||||
std::cerr << "Try running with the -fullscreen option\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(bpp != 16) {
|
||||
std::cerr << "Video mode must be emulated; the game may run slowly. "
|
||||
<< "For best results, run the program on a 16 bpp display\n";
|
||||
}
|
||||
|
||||
const int res = video.setMode(1024,768,16,video_flags);
|
||||
if(res != 16) {
|
||||
std::cerr << "required video mode, 1024x768x16 is not supported\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_WM_SetCaption(string_table["game_title"].c_str(), NULL);
|
||||
|
||||
for(;;) {
|
||||
sound::play_music("wesnoth-1.ogg");
|
||||
|
||||
game_state state;
|
||||
|
||||
display::unit_map u_map;
|
||||
config dummy_cfg("");
|
||||
display disp(u_map,video,gamemap(dummy_cfg,"1"),gamestatus(0),
|
||||
std::vector<team>());
|
||||
|
||||
if(test_mode) {
|
||||
state.campaign_type = "test";
|
||||
state.scenario = 0;
|
||||
|
||||
play_game(disp,state,game_config,units_data,video);
|
||||
return 0;
|
||||
}
|
||||
|
||||
gui::TITLE_RESULT res = gui::show_title(disp);
|
||||
if(res == gui::QUIT_GAME) {
|
||||
return 0;
|
||||
} else if(res == gui::LOAD_GAME) {
|
||||
srand(SDL_GetTicks());
|
||||
|
||||
const std::vector<std::string>& games = get_saves_list();
|
||||
|
||||
if(games.empty()) {
|
||||
gui::show_dialog(disp,NULL,
|
||||
string_table["no_saves_heading"],
|
||||
string_table["no_saves_message"],
|
||||
gui::OK_ONLY);
|
||||
continue;
|
||||
}
|
||||
|
||||
const int res = gui::show_dialog(disp,NULL,
|
||||
string_table["load_game_heading"],
|
||||
string_table["load_game_message"],
|
||||
gui::OK_CANCEL, &games);
|
||||
if(res == -1)
|
||||
continue;
|
||||
|
||||
try {
|
||||
load_game(units_data,games[res],state);
|
||||
defines_map.clear();
|
||||
defines_map[state.difficulty] = "";
|
||||
} catch(gamestatus::load_game_failed& e) {
|
||||
gui::show_dialog(disp,NULL,string_table["bad_save_heading"],
|
||||
string_table["bad_save_message"],gui::OK_ONLY);
|
||||
continue;
|
||||
}
|
||||
|
||||
recorder = replay(state.replay_data);
|
||||
if(!recorder.empty()) {
|
||||
const int res = gui::show_dialog(disp,NULL,
|
||||
"", string_table["replay_game_message"],
|
||||
gui::YES_NO);
|
||||
//if yes, then show the replay, otherwise
|
||||
//skip showing the replay
|
||||
if(res == 0) {
|
||||
recorder.set_skip(0);
|
||||
} else {
|
||||
std::cerr << "skipping...\n";
|
||||
recorder.set_skip(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if(state.campaign_type == "multiplayer") {
|
||||
recorder.set_save_info(state);
|
||||
std::vector<config*> story;
|
||||
|
||||
try {
|
||||
play_level(units_data,game_config,&state.starting_pos,
|
||||
video,state,story);
|
||||
recorder.clear();
|
||||
} catch(gamestatus::load_game_failed& e) {
|
||||
std::cerr << "error loading the game: " << e.message
|
||||
<< "\n";
|
||||
return 0;
|
||||
} catch(gamestatus::game_error& e) {
|
||||
std::cerr << "error while playing the game: "
|
||||
<< e.message << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
} else if(res == gui::TUTORIAL) {
|
||||
state.campaign_type = "tutorial";
|
||||
state.scenario = 0;
|
||||
} else if(res == gui::NEW_CAMPAIGN) {
|
||||
state.campaign_type = "scenario";
|
||||
state.scenario = 0;
|
||||
|
||||
static const std::string difficulties[] = {"EASY","NORMAL","HARD"};
|
||||
const int ndiff = sizeof(difficulties)/sizeof(*difficulties);
|
||||
std::vector<std::string> options;
|
||||
|
||||
for(int i = 0; i != ndiff; ++i) {
|
||||
options.push_back(string_table[difficulties[i]]);
|
||||
if(options.back().empty())
|
||||
options.back() = difficulties[i];
|
||||
}
|
||||
|
||||
const int res = gui::show_dialog(disp,NULL,"",
|
||||
string_table["difficulty_level"],
|
||||
gui::OK_CANCEL,&options);
|
||||
if(res == -1)
|
||||
continue;
|
||||
|
||||
assert(res >= 0 && res < options.size());
|
||||
|
||||
state.difficulty = difficulties[res];
|
||||
defines_map.clear();
|
||||
defines_map[difficulties[res]] = "";
|
||||
} else if(res == gui::MULTIPLAYER) {
|
||||
state.campaign_type = "multiplayer";
|
||||
state.scenario = 0;
|
||||
|
||||
try {
|
||||
play_multiplayer(disp,units_data,game_config,state);
|
||||
} catch(gamestatus::load_game_failed& e) {
|
||||
std::cerr << "error loading the game: " << e.message
|
||||
<< "\n";
|
||||
return 0;
|
||||
} catch(gamestatus::game_error& e) {
|
||||
std::cerr << "error while playing the game: "
|
||||
<< e.message << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
continue;
|
||||
} else if(res == gui::CHANGE_LANGUAGE) {
|
||||
|
||||
const std::vector<std::string>& langs = get_languages(game_config);
|
||||
const int res = gui::show_dialog(disp,NULL,"",
|
||||
string_table["language_button"] + ":",
|
||||
gui::OK_CANCEL,&langs);
|
||||
if(res >= 0 && res < langs.size()) {
|
||||
set_language(langs[res], game_config);
|
||||
preferences::set_locale(langs[res]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//make a new game config item based on the difficulty level
|
||||
config game_config(preprocess_file("data/game.cfg", &defines_map));
|
||||
|
||||
const std::vector<config*>& units = game_config.children["units"];
|
||||
if(units.empty()) {
|
||||
std::cerr << "Could not find units configuration\n";
|
||||
std::cerr << game_config.write();
|
||||
return 0;
|
||||
}
|
||||
|
||||
game_data units_data(*units[0]);
|
||||
|
||||
const LEVEL_RESULT result = play_game(disp,state,game_config,
|
||||
units_data,video);
|
||||
if(result == VICTORY) {
|
||||
gui::show_dialog(disp,NULL,
|
||||
string_table["end_game_heading"],
|
||||
string_table["end_game_message"],
|
||||
gui::OK_ONLY);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
try {
|
||||
return play_game(argc,argv);
|
||||
} catch(CVideo::error&) {
|
||||
std::cerr << "Could not initialize video\n";
|
||||
} catch(config::error& e) {
|
||||
std::cerr << e.message << "\n";
|
||||
} catch(gui::button::error&) {
|
||||
std::cerr << "Could not create button: Image could not be found\n";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
24
game_config.cpp
Normal file
24
game_config.cpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "game_config.hpp"
|
||||
|
||||
namespace game_config
|
||||
{
|
||||
const int unit_cost = 1;
|
||||
const int base_income = 2;
|
||||
const int tower_income = 2;
|
||||
const int heal_amount = 8;
|
||||
const int healer_heals_per_turn = 12;
|
||||
const int recall_cost = 20;
|
||||
const std::string version = "0.4.4";
|
||||
bool debug = false;
|
||||
}
|
30
game_config.hpp
Normal file
30
game_config.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef GAME_CONFIG_H_INCLUDED
|
||||
#define GAME_CONFIG_H_INCLUDED
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace game_config
|
||||
{
|
||||
extern const int unit_cost;
|
||||
extern const int base_income;
|
||||
extern const int tower_income;
|
||||
extern const int heal_amount;
|
||||
extern const int healer_heals_per_turn;
|
||||
extern const int recall_cost;
|
||||
extern const std::string version;
|
||||
|
||||
extern bool debug;
|
||||
}
|
||||
|
||||
#endif
|
739
game_events.cpp
Normal file
739
game_events.cpp
Normal file
|
@ -0,0 +1,739 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "game_events.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "language.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "replay.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace game_events {
|
||||
|
||||
bool conditional_passed(game_state& state_of_game,
|
||||
const std::map<gamemap::location,unit>* units,
|
||||
config& cond)
|
||||
{
|
||||
//if the if statement requires we have a certain unit, then
|
||||
//check for that.
|
||||
std::vector<config*>& have_unit = cond.children["have_unit"];
|
||||
|
||||
for(std::vector<config*>::iterator u = have_unit.begin();
|
||||
u != have_unit.end(); ++u) {
|
||||
|
||||
if(units == NULL)
|
||||
return false;
|
||||
|
||||
std::map<gamemap::location,unit>::const_iterator itor;
|
||||
for(itor = units->begin(); itor != units->end(); ++itor) {
|
||||
if(itor->second.matches_filter(**u)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(itor == units->end()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//check against each variable statement to see if the variable
|
||||
//matches the conditions or not
|
||||
std::vector<config*>& variables = cond.children["variable"];
|
||||
for(std::vector<config*>::iterator var = variables.begin();
|
||||
var != variables.end(); ++var) {
|
||||
std::map<std::string,std::string>& values = (*var)->values;
|
||||
std::map<std::string,std::string>& vars = state_of_game.variables;
|
||||
const std::string& name = values["name"];
|
||||
|
||||
//if we don't have a record of the variable, then the statement
|
||||
//is not true, unless it's a not equals statement, in which it's
|
||||
//automatically true
|
||||
if(vars.find(name) == vars.end()) {
|
||||
if(values.find("not_equals") == values.end() &&
|
||||
values.find("numerical_not_equals") == values.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& value = vars[name];
|
||||
const double num_value = atof(value.c_str());
|
||||
|
||||
std::map<std::string,std::string>::iterator itor;
|
||||
|
||||
itor = values.find("equals");
|
||||
if(itor != values.end() && itor->second != value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("numerical_equals");
|
||||
if(itor != values.end() && atof(itor->second.c_str()) != num_value){
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("not_equals");
|
||||
if(itor != values.end() && itor->second == value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("numerical_not_equals");
|
||||
if(itor != values.end() && atof(itor->second.c_str()) == num_value){
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("greater_than");
|
||||
if(itor != values.end() && atof(itor->second.c_str()) >= num_value){
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("less_than");
|
||||
if(itor != values.end() && atof(itor->second.c_str()) <= num_value){
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("greater_than_equal_to");
|
||||
if(itor != values.end() && atof(itor->second.c_str()) > num_value){
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("less_than_equal_to");
|
||||
if(itor != values.end() && atof(itor->second.c_str()) < num_value){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} //end namespace game_events
|
||||
|
||||
namespace {
|
||||
|
||||
display* screen = NULL;
|
||||
gamemap* game_map = NULL;
|
||||
std::map<gamemap::location,unit>* units = NULL;
|
||||
game_state* state_of_game = NULL;
|
||||
game_data* game_data_ptr = NULL;
|
||||
|
||||
bool events_init() { return screen != NULL; }
|
||||
|
||||
struct queued_event {
|
||||
queued_event(const std::string& name, const gamemap::location& loc1,
|
||||
const gamemap::location& loc2)
|
||||
: name(name), loc1(loc1), loc2(loc2) {}
|
||||
|
||||
std::string name;
|
||||
gamemap::location loc1;
|
||||
gamemap::location loc2;
|
||||
};
|
||||
|
||||
std::deque<queued_event> events_queue;
|
||||
|
||||
class event_handler
|
||||
{
|
||||
public:
|
||||
event_handler(config* cfg) : name_(cfg->values["name"]),
|
||||
first_time_only_(cfg->values["first_time_only"] != "no"),
|
||||
disabled_(false),
|
||||
cfg_(cfg)
|
||||
{}
|
||||
|
||||
const std::string& name() const { return name_; }
|
||||
|
||||
bool first_time_only() const { return first_time_only_; }
|
||||
|
||||
void disable() { disabled_ = true; }
|
||||
bool disabled() const { return disabled_; }
|
||||
|
||||
std::vector<config*>& first_arg_filters() {
|
||||
return cfg_->children["filter"];
|
||||
}
|
||||
|
||||
std::vector<config*>& second_arg_filters() {
|
||||
return cfg_->children["filter_second"];
|
||||
}
|
||||
|
||||
void handle_event(const queued_event& event_info, config* cfg=NULL);
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
bool first_time_only_;
|
||||
bool disabled_;
|
||||
config* cfg_;
|
||||
};
|
||||
|
||||
std::multimap<std::string,event_handler> events_map;
|
||||
|
||||
void event_handler::handle_event(const queued_event& event_info, config* cfg)
|
||||
{
|
||||
if(cfg == NULL)
|
||||
cfg = cfg_;
|
||||
|
||||
//sub commands that need to be handled in a guaranteed ordering
|
||||
std::vector<config*>& commands = cfg->children["command"];
|
||||
for(std::vector<config*>::iterator cmd = commands.begin();
|
||||
cmd != commands.end(); ++cmd) {
|
||||
handle_event(event_info,*cmd);
|
||||
}
|
||||
|
||||
//setting a variable
|
||||
std::vector<config*>& set_vars = cfg->children["set_variable"];
|
||||
for(std::vector<config*>::iterator var = set_vars.begin();
|
||||
var != set_vars.end(); ++var) {
|
||||
std::map<std::string,std::string>& vals = (*var)->values;
|
||||
const std::string& name = vals["name"];
|
||||
const std::string& value = vals["value"];
|
||||
if(value.empty() == false) {
|
||||
state_of_game->variables[name] = value;
|
||||
}
|
||||
|
||||
const std::string& add = vals["add"];
|
||||
if(add.empty() == false) {
|
||||
double value = atof(state_of_game->variables[name].c_str());
|
||||
value += atof(add.c_str());
|
||||
char buf[50];
|
||||
sprintf(buf,"%f",value);
|
||||
state_of_game->variables[name] = buf;
|
||||
}
|
||||
|
||||
const std::string& multiply = vals["multiply"];
|
||||
if(multiply.empty() == false) {
|
||||
double value = atof(state_of_game->variables[name].c_str());
|
||||
value *= atof(multiply.c_str());
|
||||
char buf[50];
|
||||
sprintf(buf,"%f",value);
|
||||
state_of_game->variables[name] = buf;
|
||||
}
|
||||
}
|
||||
|
||||
//conditional statements
|
||||
std::vector<config*>& conditionals = cfg->children["if"];
|
||||
for(std::vector<config*>::iterator cond = conditionals.begin();
|
||||
cond != conditionals.end(); ++cond) {
|
||||
const std::string type = game_events::conditional_passed(
|
||||
*state_of_game,units,**cond) ? "then":"else";
|
||||
|
||||
//if the if statement passed, then execute all 'then' statements,
|
||||
//otherwise execute 'else' statements
|
||||
std::vector<config*>& commands = cfg->children[type];
|
||||
for(std::vector<config*>::iterator cmd = commands.begin();
|
||||
cmd != commands.end(); ++cmd) {
|
||||
handle_event(event_info,*cmd);
|
||||
}
|
||||
}
|
||||
|
||||
//if we are assigning a role to a unit from the available units list
|
||||
std::vector<config*>& assign_role = cfg->children["role"];
|
||||
for(std::vector<config*>::iterator rl = assign_role.begin();
|
||||
rl != assign_role.end(); ++rl) {
|
||||
|
||||
//get a list of the types this unit can be
|
||||
std::vector<std::string> types = config::split((*rl)->values["type"]);
|
||||
|
||||
//iterate over all the types, and for each type, try to find
|
||||
//a unit that matches
|
||||
std::vector<std::string>::iterator ti;
|
||||
for(ti = types.begin(); ti != types.end(); ++ti) {
|
||||
config cfg;
|
||||
cfg.values["type"] = *ti;
|
||||
|
||||
std::map<gamemap::location,unit>::iterator itor;
|
||||
for(itor = units->begin(); itor != units->end(); ++itor) {
|
||||
if(itor->second.matches_filter(cfg)) {
|
||||
itor->second.assign_role((*rl)->values["role"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(itor != units->end())
|
||||
break;
|
||||
|
||||
std::vector<unit>::iterator ui;
|
||||
//iterate over the units, and try to find one that matches
|
||||
for(ui = state_of_game->available_units.begin();
|
||||
ui != state_of_game->available_units.end(); ++ui) {
|
||||
if(ui->matches_filter(cfg)) {
|
||||
ui->assign_role((*rl)->values["role"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//if we found a unit, we don't have to keep going.
|
||||
if(ui != state_of_game->available_units.end())
|
||||
break;
|
||||
}
|
||||
|
||||
//if we found a unit in the current type, we don't have to
|
||||
//look through any more types
|
||||
if(ti != types.end())
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<config*>& remove_overlays = cfg->children["removeitem"];
|
||||
for(std::vector<config*>::iterator rm = remove_overlays.begin();
|
||||
rm != remove_overlays.end(); ++rm) {
|
||||
gamemap::location loc(**rm);
|
||||
if(!loc.valid()) {
|
||||
loc = event_info.loc1;
|
||||
}
|
||||
|
||||
screen->remove_overlay(loc);
|
||||
}
|
||||
|
||||
//adding new items
|
||||
std::vector<config*>& add_overlays = cfg->children["item"];
|
||||
for(std::vector<config*>::iterator ni = add_overlays.begin();
|
||||
ni != add_overlays.end(); ++ni) {
|
||||
gamemap::location loc(**ni);
|
||||
const std::string& img = (*ni)->values["image"];
|
||||
if(!img.empty())
|
||||
screen->add_overlay(loc,img);
|
||||
}
|
||||
|
||||
//changing the terrain
|
||||
std::vector<config*>& terrain_changes = cfg->children["terrain"];
|
||||
for(std::vector<config*>::iterator tc = terrain_changes.begin();
|
||||
tc != terrain_changes.end(); ++tc) {
|
||||
gamemap::location loc(**tc);
|
||||
const std::string& terrain_type = (*tc)->values["letter"];
|
||||
if(terrain_type.size() > 0) {
|
||||
game_map->set_terrain(loc,terrain_type[0]);
|
||||
screen->recalculate_minimap();
|
||||
screen->invalidate_all();
|
||||
}
|
||||
}
|
||||
|
||||
//if we should spawn a new unit on the map somewhere
|
||||
std::vector<config*>& new_units = cfg->children["unit"];
|
||||
for(std::vector<config*>::iterator ui = new_units.begin();
|
||||
ui != new_units.end(); ++ui) {
|
||||
unit new_unit(*game_data_ptr,**ui);
|
||||
gamemap::location loc(**ui);
|
||||
|
||||
if(game_map->on_board(loc)) {
|
||||
loc = find_vacant_tile(*game_map,*units,loc);
|
||||
units->insert(std::pair<gamemap::location,unit>(loc,new_unit));
|
||||
} else {
|
||||
state_of_game->available_units.push_back(new_unit);
|
||||
}
|
||||
}
|
||||
|
||||
//if we should recall units that match a certain description
|
||||
std::vector<config*>& recalls = cfg->children["recall"];
|
||||
for(std::vector<config*>::iterator ir = recalls.begin();
|
||||
ir != recalls.end(); ++ir) {
|
||||
std::vector<unit>& avail = state_of_game->available_units;
|
||||
for(std::vector<unit>::iterator u = avail.begin();
|
||||
u != avail.end(); ++u) {
|
||||
if(u->matches_filter(**ir)) {
|
||||
recruit_unit(*game_map,1,*units,*u,gamemap::location(),screen);
|
||||
u = avail.erase(u);
|
||||
if(u == avail.end())
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<config*>& objects = cfg->children["object"];
|
||||
for(std::vector<config*>::iterator obj = objects.begin();
|
||||
obj != objects.end(); ++obj) {
|
||||
std::map<std::string,std::string>& values = (*obj)->values;
|
||||
|
||||
//if this item has already been used
|
||||
if(values["used"].empty() == false)
|
||||
continue;
|
||||
|
||||
const std::string& id = values["id"];
|
||||
|
||||
const std::string image = values["image"];
|
||||
std::string caption = values["name"];
|
||||
|
||||
const std::string& caption_lang = string_table[id + "_name"];
|
||||
if(caption_lang.empty() == false)
|
||||
caption = caption_lang;
|
||||
|
||||
const std::map<gamemap::location,unit>::iterator u =
|
||||
units->find(event_info.loc1);
|
||||
|
||||
if(u == units->end())
|
||||
continue;
|
||||
|
||||
std::string text;
|
||||
|
||||
std::vector<config*>& filters = (*obj)->children["filter"];
|
||||
|
||||
if(filters.empty() || u->second.matches_filter(*filters[0])) {
|
||||
const std::string& lang = string_table[id];
|
||||
if(!lang.empty())
|
||||
text = lang;
|
||||
else
|
||||
text = values["description"];
|
||||
|
||||
u->second.add_modification("object",**obj);
|
||||
screen->remove_overlay(event_info.loc1);
|
||||
screen->select_hex(event_info.loc1);
|
||||
screen->invalidate_unit();
|
||||
|
||||
//mark that this item won't be used again
|
||||
values["used"] = "true";
|
||||
} else {
|
||||
const std::string& lang = string_table[id + "_cannot_use"];
|
||||
if(!lang.empty())
|
||||
text = lang;
|
||||
else
|
||||
text = values["cannot_use_message"];
|
||||
}
|
||||
|
||||
|
||||
SDL_Surface* surface = NULL;
|
||||
if(image.empty() == false) {
|
||||
surface = screen->getImage(image,display::UNSCALED);
|
||||
}
|
||||
|
||||
gui::show_dialog(*screen,surface,caption,text);
|
||||
|
||||
//this will redraw the unit, with its new stats
|
||||
screen->draw();
|
||||
}
|
||||
|
||||
std::vector<config*>& messages = cfg->children["message"];
|
||||
for(std::vector<config*>::iterator msg = messages.begin();
|
||||
msg != messages.end(); ++msg) {
|
||||
std::map<std::string,std::string>& values = (*msg)->values;
|
||||
std::map<gamemap::location,unit>::iterator speaker = units->end();
|
||||
if(values["speaker"] == "unit") {
|
||||
speaker = units->find(event_info.loc1);
|
||||
} else if(values["speaker"] == "second_unit") {
|
||||
speaker = units->find(event_info.loc2);
|
||||
} else if(values["speaker"] != "narrator") {
|
||||
for(speaker = units->begin(); speaker != units->end();
|
||||
++speaker){
|
||||
if(speaker->second.matches_filter(**msg))
|
||||
break;
|
||||
}
|
||||
|
||||
if(speaker == units->end()) {
|
||||
//no matching unit found, so the dialog can't come up
|
||||
//continue onto the next message
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& id = values["id"];
|
||||
|
||||
std::string image = (*msg)->values["image"];
|
||||
std::string caption;
|
||||
|
||||
const std::string& lang_caption = string_table[id + "_caption"];
|
||||
if(!lang_caption.empty())
|
||||
caption = lang_caption;
|
||||
else
|
||||
caption = (*msg)->values["caption"];
|
||||
|
||||
if(speaker != units->end()) {
|
||||
screen->highlight_hex(speaker->first);
|
||||
screen->scroll_to_tile(speaker->first.x,speaker->first.y);
|
||||
|
||||
if(image.empty()) {
|
||||
image = speaker->second.type().image_profile();
|
||||
}
|
||||
|
||||
if(caption.empty()) {
|
||||
caption = speaker->second.description();
|
||||
if(caption.empty()) {
|
||||
caption = speaker->second.type().language_name();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> options;
|
||||
std::vector<std::vector<config*>*> option_events;
|
||||
|
||||
std::vector<config*>& menu_items = (*msg)->children["option"];
|
||||
for(std::vector<config*>::iterator mi = menu_items.begin();
|
||||
mi != menu_items.end(); ++mi) {
|
||||
const std::string& lang_msg = string_table[(*mi)->values["id"]];
|
||||
const std::string& msg = lang_msg.empty() ?
|
||||
(*mi)->values["message"] : lang_msg;
|
||||
options.push_back(msg);
|
||||
option_events.push_back(&(*mi)->children["command"]);
|
||||
}
|
||||
|
||||
SDL_Surface* surface = NULL;
|
||||
if(image.empty() == false) {
|
||||
surface = screen->getImage(image,display::UNSCALED);
|
||||
}
|
||||
|
||||
const std::string& lang_message = string_table[id];
|
||||
int option_chosen = gui::show_dialog(*screen,surface,caption,
|
||||
lang_message.empty() ? values["message"] : lang_message,
|
||||
options.empty() ? gui::MESSAGE : gui::OK_ONLY,
|
||||
options.empty() ? NULL : &options);
|
||||
|
||||
if(screen->update_locked() && options.empty() == false) {
|
||||
config* const cfg = recorder.get_next_action();
|
||||
if(cfg == NULL || cfg->children["choose"].empty()) {
|
||||
std::cerr << "choice expected but none found\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
const std::string& val =
|
||||
cfg->children["choose"].front()->values["value"];
|
||||
option_chosen = atol(val.c_str());
|
||||
|
||||
} else if(options.empty() == false) {
|
||||
recorder.choose_option(option_chosen);
|
||||
}
|
||||
|
||||
if(options.empty() == false) {
|
||||
assert(option_chosen >= 0 && option_chosen < menu_items.size());
|
||||
std::vector<config*>& events = *option_events[option_chosen];
|
||||
for(std::vector<config*>::iterator ev = events.begin();
|
||||
ev != events.end(); ++ev) {
|
||||
handle_event(event_info,*ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<config*>& dead_units = cfg->children["kill"];
|
||||
for(std::vector<config*>::iterator du = dead_units.begin();
|
||||
du != dead_units.end(); ++du) {
|
||||
|
||||
for(std::map<gamemap::location,unit>::iterator i = units->begin();
|
||||
i != units->end(); ++i) {
|
||||
while(i->second.matches_filter(**du) && i != units->end()) {
|
||||
units->erase(i);
|
||||
i = units->begin();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unit>& avail_units = state_of_game->available_units;
|
||||
for(std::vector<unit>::iterator j = avail_units.begin();
|
||||
j != avail_units.end(); ++j) {
|
||||
while(j->matches_filter(**du) && j != avail_units.end()) {
|
||||
j = avail_units.erase(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//adding of new events
|
||||
std::vector<config*>& new_events = cfg->children["event"];
|
||||
for(std::vector<config*>::iterator ne = new_events.begin();
|
||||
ne != new_events.end(); ++ne) {
|
||||
event_handler new_handler(*ne);
|
||||
events_map.insert(std::pair<std::string,event_handler>(
|
||||
new_handler.name(),new_handler));
|
||||
}
|
||||
|
||||
std::vector<config*>& end_level = cfg->children["endlevel"];
|
||||
if(end_level.empty() == false) {
|
||||
config* const end_info = end_level[0];
|
||||
const std::string& result = end_info->values["result"];
|
||||
if(result.size() == 0 || result == "victory") {
|
||||
const bool bonus = (end_info->values["bonus"] == "yes");
|
||||
throw end_level_exception(VICTORY,bonus);
|
||||
} else {
|
||||
throw end_level_exception(DEFEAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool filter_loc_impl(const gamemap::location& loc, const std::string& xloc,
|
||||
const std::string& yloc)
|
||||
{
|
||||
if(std::find(xloc.begin(),xloc.end(),',') != xloc.end()) {
|
||||
std::vector<std::string> xlocs = config::split(xloc);
|
||||
std::vector<std::string> ylocs = config::split(yloc);
|
||||
|
||||
const int size = xlocs.size() < ylocs.size()?xlocs.size():ylocs.size();
|
||||
for(int i = 0; i != size; ++i) {
|
||||
if(filter_loc_impl(loc,xlocs[i],ylocs[i]))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!xloc.empty()) {
|
||||
const std::string::const_iterator dash =
|
||||
std::find(xloc.begin(),xloc.end(),'-');
|
||||
|
||||
if(dash != xloc.end()) {
|
||||
const std::string beg(xloc.begin(),dash);
|
||||
const std::string end(dash+1,xloc.end());
|
||||
|
||||
const int bot = atoi(beg.c_str()) - 1;
|
||||
const int top = atoi(end.c_str()) - 1;
|
||||
|
||||
if(loc.x < bot || loc.x > top)
|
||||
return false;
|
||||
} else {
|
||||
const int xval = atoi(xloc.c_str()) - 1;
|
||||
if(xval != loc.x)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!yloc.empty()) {
|
||||
const std::string::const_iterator dash =
|
||||
std::find(yloc.begin(),yloc.end(),'-');
|
||||
|
||||
if(dash != yloc.end()) {
|
||||
const std::string beg(yloc.begin(),dash);
|
||||
const std::string end(dash+1,yloc.end());
|
||||
|
||||
const int bot = atoi(beg.c_str()) - 1;
|
||||
const int top = atoi(end.c_str()) - 1;
|
||||
|
||||
if(loc.y < bot || loc.y > top)
|
||||
return false;
|
||||
} else {
|
||||
const int yval = atoi(yloc.c_str()) - 1;
|
||||
if(yval != loc.y)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool filter_loc(const gamemap::location& loc, config& cfg)
|
||||
{
|
||||
const std::string& xloc = cfg.values["x"];
|
||||
const std::string& yloc = cfg.values["y"];
|
||||
|
||||
return filter_loc_impl(loc,xloc,yloc);
|
||||
}
|
||||
|
||||
bool process_event(event_handler& handler, const queued_event& ev)
|
||||
{
|
||||
if(handler.disabled())
|
||||
return false;
|
||||
|
||||
std::map<gamemap::location,unit>::iterator unit1 = units->find(ev.loc1);
|
||||
std::map<gamemap::location,unit>::iterator unit2 = units->find(ev.loc2);
|
||||
|
||||
std::vector<config*>& first_filters = handler.first_arg_filters();
|
||||
for(std::vector<config*>::iterator ffi = first_filters.begin();
|
||||
ffi != first_filters.end(); ++ffi) {
|
||||
if(!filter_loc(ev.loc1,**ffi))
|
||||
return false;
|
||||
|
||||
if(unit1 != units->end() && !unit1->second.matches_filter(**ffi)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<config*>& second_filters = handler.second_arg_filters();
|
||||
for(std::vector<config*>::iterator sfi = second_filters.begin();
|
||||
sfi != second_filters.end(); ++sfi) {
|
||||
if(!filter_loc(ev.loc2,**sfi))
|
||||
return false;
|
||||
|
||||
if(unit2 != units->end() && !unit2->second.matches_filter(**sfi)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//the event hasn't been filtered out, so execute the handler
|
||||
handler.handle_event(ev);
|
||||
|
||||
if(handler.first_time_only())
|
||||
handler.disable();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} //end anonymous namespace
|
||||
|
||||
namespace game_events {
|
||||
|
||||
manager::manager(config& cfg, display& gui_, gamemap& map_,
|
||||
std::map<gamemap::location,unit>& units_,
|
||||
game_state& state_of_game_, game_data& game_data_)
|
||||
{
|
||||
std::vector<config*>& events_list = cfg.children["event"];
|
||||
for(std::vector<config*>::iterator i = events_list.begin();
|
||||
i != events_list.end(); ++i) {
|
||||
event_handler new_handler(*i);
|
||||
events_map.insert(std::pair<std::string,event_handler>(
|
||||
new_handler.name(), new_handler));
|
||||
}
|
||||
|
||||
screen = &gui_;
|
||||
game_map = &map_;
|
||||
units = &units_;
|
||||
state_of_game = &state_of_game_;
|
||||
game_data_ptr = &game_data_;
|
||||
}
|
||||
|
||||
manager::~manager() {
|
||||
events_queue.clear();
|
||||
events_map.clear();
|
||||
screen = NULL;
|
||||
game_map = NULL;
|
||||
units = NULL;
|
||||
state_of_game = NULL;
|
||||
game_data_ptr = NULL;
|
||||
}
|
||||
|
||||
void raise(const std::string& event,
|
||||
const gamemap::location& loc1,
|
||||
const gamemap::location& loc2)
|
||||
{
|
||||
if(!events_init())
|
||||
return;
|
||||
|
||||
events_queue.push_back(queued_event(event,loc1,loc2));
|
||||
}
|
||||
|
||||
bool fire(const std::string& event,
|
||||
const gamemap::location& loc1,
|
||||
const gamemap::location& loc2)
|
||||
{
|
||||
raise(event,loc1,loc2);
|
||||
return pump();
|
||||
}
|
||||
|
||||
bool pump()
|
||||
{
|
||||
if(!events_init())
|
||||
return false;
|
||||
|
||||
bool result = false;
|
||||
|
||||
while(events_queue.empty() == false) {
|
||||
queued_event ev = events_queue.front();
|
||||
events_queue.pop_front(); //pop now for exception safety
|
||||
const std::string& event_name = ev.name;
|
||||
typedef std::multimap<std::string,event_handler>::iterator itor;
|
||||
|
||||
//find all handlers for this event in the map
|
||||
std::pair<itor,itor> i = events_map.equal_range(event_name);
|
||||
|
||||
while(i.first != i.second) {
|
||||
event_handler& handler = i.first->second;
|
||||
if(process_event(handler, ev))
|
||||
result = true;
|
||||
++i.first;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} //end namespace game_events
|
50
game_events.hpp
Normal file
50
game_events.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef GAME_EVENTS_H_INCLUDED
|
||||
#define GAME_EVENTS_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "display.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "map.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace game_events
|
||||
{
|
||||
|
||||
bool conditional_passed(game_state& state_of_game,
|
||||
const std::map<gamemap::location,unit>* units,
|
||||
config& cond);
|
||||
|
||||
struct manager {
|
||||
manager(config& cfg, display& disp, gamemap& map,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
game_state& state_of_game, game_data& data);
|
||||
~manager();
|
||||
};
|
||||
|
||||
void raise(const std::string& event,
|
||||
const gamemap::location& loc1=gamemap::location::null_location,
|
||||
const gamemap::location& loc2=gamemap::location::null_location);
|
||||
|
||||
bool fire(const std::string& event,
|
||||
const gamemap::location& loc1=gamemap::location::null_location,
|
||||
const gamemap::location& loc2=gamemap::location::null_location);
|
||||
|
||||
bool pump();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
145
gamestatus.cpp
Normal file
145
gamestatus.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "filesystem.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
|
||||
gamestatus::gamestatus(int num_turns) :
|
||||
timeofday_(DAWN),turn_(1),numTurns_(num_turns)
|
||||
{}
|
||||
|
||||
const std::string& gamestatus::timeofdayDescription(gamestatus::TIME t)
|
||||
{
|
||||
static const std::string times[] = { "Dawn", "Morning", "Afternoon",
|
||||
"Dusk", "FirstWatch", "SecondWatch" };
|
||||
return times[t];
|
||||
}
|
||||
|
||||
gamestatus::TIME gamestatus::timeofday() const
|
||||
{
|
||||
return timeofday_;
|
||||
}
|
||||
|
||||
int gamestatus::turn() const
|
||||
{
|
||||
return turn_;
|
||||
}
|
||||
|
||||
int gamestatus::number_of_turns() const
|
||||
{
|
||||
return numTurns_;
|
||||
}
|
||||
|
||||
bool gamestatus::next_turn()
|
||||
{
|
||||
timeofday_ = static_cast<TIME>(static_cast<int>(timeofday_)+1);
|
||||
if(timeofday_ == NUM_TIMES)
|
||||
timeofday_ = DAWN;
|
||||
++turn_;
|
||||
return turn_ <= numTurns_;
|
||||
}
|
||||
|
||||
game_state read_game(game_data& data, config* cfg)
|
||||
{
|
||||
game_state res;
|
||||
res.label = cfg->values["label"];
|
||||
res.gold = atoi(cfg->values["gold"].c_str());
|
||||
res.scenario = atoi(cfg->values["scenario"].c_str());
|
||||
|
||||
res.difficulty = cfg->values["difficulty"];
|
||||
if(res.difficulty.empty())
|
||||
res.difficulty = "NORMAL";
|
||||
|
||||
res.campaign_type = cfg->values["campaign_type"];
|
||||
if(res.campaign_type.empty())
|
||||
res.campaign_type = "scenario";
|
||||
|
||||
std::vector<config*>& units = cfg->children["unit"];
|
||||
for(std::vector<config*>::iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
res.available_units.push_back(unit(data,**i));
|
||||
}
|
||||
|
||||
std::vector<config*>& vars = cfg->children["variables"];
|
||||
if(vars.empty() == false) {
|
||||
res.variables = vars[0]->values;
|
||||
}
|
||||
|
||||
std::vector<config*>& replays = cfg->children["replay"];
|
||||
if(replays.empty() == false) {
|
||||
res.replay_data = *replays[0];
|
||||
}
|
||||
|
||||
std::vector<config*>& starts = cfg->children["start"];
|
||||
if(starts.empty() == false) {
|
||||
res.starting_pos = *starts[0];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void write_game(const game_state& game, config& cfg)
|
||||
{
|
||||
cfg.values["label"] = game.label;
|
||||
|
||||
char buf[50];
|
||||
sprintf(buf,"%d",game.gold);
|
||||
cfg.values["gold"] = buf;
|
||||
|
||||
sprintf(buf,"%d",game.scenario);
|
||||
cfg.values["scenario"] = buf;
|
||||
|
||||
cfg.values["campaign_type"] = game.campaign_type;
|
||||
|
||||
cfg.values["difficulty"] = game.difficulty;
|
||||
|
||||
config* vars = new config();
|
||||
vars->values = game.variables;
|
||||
cfg.children["variables"].push_back(vars);
|
||||
|
||||
for(std::vector<unit>::const_iterator i = game.available_units.begin();
|
||||
i != game.available_units.end(); ++i) {
|
||||
config* const new_cfg = new config;
|
||||
i->write(*new_cfg);
|
||||
cfg.children["unit"].push_back(new_cfg);
|
||||
}
|
||||
|
||||
if(game.replay_data.children.empty() == false) {
|
||||
cfg.children["replay"].push_back(new config(game.replay_data));
|
||||
}
|
||||
|
||||
cfg.children["start"].push_back(new config(game.starting_pos));
|
||||
}
|
||||
|
||||
std::vector<std::string> get_saves_list()
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
get_files_in_dir(get_saves_dir(),&res);
|
||||
return res;
|
||||
}
|
||||
|
||||
void load_game(game_data& data, const std::string& name, game_state& state)
|
||||
{
|
||||
config cfg(read_file(get_saves_dir() + "/" + name));
|
||||
state = read_game(data,&cfg);
|
||||
}
|
||||
|
||||
void save_game(const game_state& state)
|
||||
{
|
||||
const std::string& name = state.label;
|
||||
|
||||
config cfg;
|
||||
write_game(state,cfg);
|
||||
write_file(get_saves_dir() + "/" + name,cfg.write());
|
||||
}
|
72
gamestatus.hpp
Normal file
72
gamestatus.hpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef GAME_STATUS_HPP_INCLUDED
|
||||
#define GAME_STATUS_HPP_INCLUDED
|
||||
|
||||
#include "unit.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class gamestatus
|
||||
{
|
||||
public:
|
||||
gamestatus(int num_turns);
|
||||
enum TIME { DAWN, DAY1, DAY2, DUSK, NIGHT1, NIGHT2, NUM_TIMES };
|
||||
|
||||
static const std::string& timeofdayDescription(TIME t);
|
||||
TIME timeofday() const;
|
||||
int turn() const;
|
||||
int number_of_turns() const;
|
||||
|
||||
bool next_turn();
|
||||
|
||||
struct load_game_failed {
|
||||
load_game_failed() {}
|
||||
load_game_failed(const std::string& msg) : message(msg) {}
|
||||
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct game_error {
|
||||
game_error(const std::string& msg) : message(msg) {}
|
||||
std::string message;
|
||||
};
|
||||
|
||||
private:
|
||||
TIME timeofday_;
|
||||
int turn_;
|
||||
int numTurns_;
|
||||
};
|
||||
|
||||
struct game_state
|
||||
{
|
||||
game_state() : gold(-1), difficulty("NORMAL") {}
|
||||
std::string label;
|
||||
std::string campaign_type;
|
||||
int scenario;
|
||||
int gold;
|
||||
std::vector<unit> available_units;
|
||||
std::map<std::string,std::string> variables;
|
||||
std::string difficulty;
|
||||
|
||||
config replay_data;
|
||||
config starting_pos;
|
||||
};
|
||||
|
||||
std::vector<std::string> get_saves_list();
|
||||
|
||||
void load_game(game_data& data, const std::string& name, game_state& state);
|
||||
void save_game(const game_state& state);
|
||||
|
||||
#endif
|
182
hotkeys.cpp
Normal file
182
hotkeys.cpp
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "config.hpp"
|
||||
#include "hotkeys.hpp"
|
||||
#include "language.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "preferences.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
|
||||
namespace {
|
||||
|
||||
HOTKEY_COMMAND string_to_command(const std::string& str)
|
||||
{
|
||||
static std::map<std::string,HOTKEY_COMMAND> m;
|
||||
if(m.empty()) {
|
||||
typedef std::pair<std::string,HOTKEY_COMMAND> val;
|
||||
m.insert(val("cycle",HOTKEY_CYCLE_UNITS));
|
||||
m.insert(val("endunitturn",HOTKEY_END_UNIT_TURN));
|
||||
m.insert(val("leader",HOTKEY_LEADER));
|
||||
m.insert(val("undo",HOTKEY_UNDO));
|
||||
m.insert(val("redo",HOTKEY_REDO));
|
||||
m.insert(val("zoomin",HOTKEY_ZOOM_IN));
|
||||
m.insert(val("zoomout",HOTKEY_ZOOM_OUT));
|
||||
m.insert(val("zoomdefault",HOTKEY_ZOOM_DEFAULT));
|
||||
m.insert(val("fullscreen",HOTKEY_FULLSCREEN));
|
||||
m.insert(val("accelerated",HOTKEY_ACCELERATED));
|
||||
m.insert(val("resistance",HOTKEY_ATTACK_RESISTANCE));
|
||||
m.insert(val("terraintable",HOTKEY_TERRAIN_TABLE));
|
||||
}
|
||||
|
||||
const std::map<std::string,HOTKEY_COMMAND>::const_iterator i = m.find(str);
|
||||
if(i == m.end())
|
||||
return HOTKEY_NULL;
|
||||
else
|
||||
return i->second;
|
||||
}
|
||||
|
||||
struct hotkey {
|
||||
explicit hotkey(config& cfg);
|
||||
|
||||
HOTKEY_COMMAND action;
|
||||
int keycode;
|
||||
bool alt, ctrl, shift;
|
||||
mutable bool lastres;
|
||||
};
|
||||
|
||||
hotkey::hotkey(config& cfg) : lastres(false)
|
||||
{
|
||||
std::map<std::string,std::string>& m = cfg.values;
|
||||
action = string_to_command(m["command"]);
|
||||
|
||||
keycode = m["key"].empty() ? 0 : m["key"][0];
|
||||
alt = (m["alt"] == "yes");
|
||||
ctrl = (m["ctrl"] == "yes");
|
||||
shift = (m["shift"] == "yes");
|
||||
}
|
||||
|
||||
bool operator==(const hotkey& a, const hotkey& b)
|
||||
{
|
||||
return a.keycode == b.keycode && a.alt == b.alt &&
|
||||
a.ctrl == b.ctrl && a.shift == b.shift;
|
||||
}
|
||||
|
||||
bool operator!=(const hotkey& a, const hotkey& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
std::vector<hotkey> hotkeys;
|
||||
|
||||
}
|
||||
|
||||
struct hotkey_pressed {
|
||||
hotkey_pressed(CKey& key);
|
||||
|
||||
bool operator()(const hotkey& hk) const;
|
||||
|
||||
private:
|
||||
const bool shift_, ctrl_, alt_;
|
||||
CKey& key_;
|
||||
};
|
||||
|
||||
hotkey_pressed::hotkey_pressed(CKey& key) :
|
||||
shift_(key[SDLK_LSHIFT] || key[SDLK_RSHIFT]),
|
||||
ctrl_(key[SDLK_LCTRL] || key[SDLK_RCTRL]),
|
||||
alt_(key[SDLK_LALT] || key[SDLK_RALT]), key_(key) {}
|
||||
|
||||
bool hotkey_pressed::operator()(const hotkey& hk) const
|
||||
{
|
||||
const bool res = shift_ == hk.shift && ctrl_ == hk.ctrl &&
|
||||
alt_ == hk.alt && key_[hk.keycode];
|
||||
|
||||
//for zoom in and zoom out, allow it to happen multiple consecutive times
|
||||
if(hk.action == HOTKEY_ZOOM_IN || hk.action == HOTKEY_ZOOM_OUT) {
|
||||
hk.lastres = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
//don't let it return true on multiple consecutive occurrences
|
||||
if(hk.lastres) {
|
||||
hk.lastres = res;
|
||||
return false;
|
||||
} else {
|
||||
hk.lastres = res;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
void add_hotkey(config& cfg)
|
||||
{
|
||||
const hotkey new_hotkey(cfg);
|
||||
const std::vector<hotkey>::iterator i =
|
||||
std::find(hotkeys.begin(),hotkeys.end(),new_hotkey);
|
||||
if(i != hotkeys.end()) {
|
||||
*i = new_hotkey;
|
||||
} else {
|
||||
hotkeys.push_back(new_hotkey);
|
||||
}
|
||||
}
|
||||
|
||||
void add_hotkeys(config& cfg)
|
||||
{
|
||||
std::vector<config*>& children = cfg.children["hotkey"];
|
||||
for(std::vector<config*>::iterator i = children.begin();
|
||||
i != children.end(); ++i) {
|
||||
add_hotkey(**i);
|
||||
}
|
||||
}
|
||||
|
||||
HOTKEY_COMMAND check_keys(display& disp)
|
||||
{
|
||||
const double zoom_amount = 5.0;
|
||||
|
||||
CKey key;
|
||||
if(key[KEY_ESCAPE]) {
|
||||
const int res = gui::show_dialog(disp,NULL,"",
|
||||
string_table["quit_message"],gui::YES_NO);
|
||||
if(res == 0) {
|
||||
throw end_level_exception(QUIT);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<hotkey>::iterator i =
|
||||
std::find_if(hotkeys.begin(),hotkeys.end(),hotkey_pressed(key));
|
||||
if(i == hotkeys.end())
|
||||
return HOTKEY_NULL;
|
||||
|
||||
switch(i->action) {
|
||||
case HOTKEY_ZOOM_IN:
|
||||
disp.zoom(zoom_amount);
|
||||
break;
|
||||
case HOTKEY_ZOOM_OUT:
|
||||
disp.zoom(-zoom_amount);
|
||||
break;
|
||||
case HOTKEY_ZOOM_DEFAULT:
|
||||
disp.default_zoom();
|
||||
break;
|
||||
case HOTKEY_FULLSCREEN:
|
||||
preferences::set_fullscreen(!preferences::fullscreen());
|
||||
break;
|
||||
case HOTKEY_ACCELERATED:
|
||||
preferences::set_turbo(!preferences::turbo());
|
||||
break;
|
||||
default:
|
||||
return i->action;
|
||||
}
|
||||
|
||||
return HOTKEY_NULL;
|
||||
}
|
30
hotkeys.hpp
Normal file
30
hotkeys.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef HOTKEYS_HPP_INCLUDED
|
||||
#define HOTKEYS_HPP_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "display.hpp"
|
||||
#include "key.hpp"
|
||||
|
||||
enum HOTKEY_COMMAND { HOTKEY_CYCLE_UNITS, HOTKEY_END_UNIT_TURN, HOTKEY_LEADER,
|
||||
HOTKEY_UNDO, HOTKEY_REDO,
|
||||
HOTKEY_ZOOM_IN, HOTKEY_ZOOM_OUT, HOTKEY_ZOOM_DEFAULT,
|
||||
HOTKEY_FULLSCREEN, HOTKEY_ACCELERATED,
|
||||
HOTKEY_TERRAIN_TABLE, HOTKEY_ATTACK_RESISTANCE,
|
||||
HOTKEY_NULL };
|
||||
|
||||
void add_hotkeys(config& cfg);
|
||||
|
||||
HOTKEY_COMMAND check_keys(display& disp);
|
||||
|
||||
#endif
|
259
intro.cpp
Normal file
259
intro.cpp
Normal file
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "font.hpp"
|
||||
#include "intro.hpp"
|
||||
#include "key.hpp"
|
||||
#include "language.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "widgets/button.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
void show_intro(display& screen, config& data)
|
||||
{
|
||||
CKey key;
|
||||
|
||||
gui::button next_button(screen,string_table["next_button"] + ">>>");
|
||||
gui::button skip_button(screen,string_table["skip_button"]);
|
||||
|
||||
next_button.set_x(700);
|
||||
next_button.set_y(500);
|
||||
skip_button.set_x(700);
|
||||
skip_button.set_y(550);
|
||||
|
||||
std::vector<config*>& parts = data.children["part"];
|
||||
|
||||
for(std::vector<config*>::iterator i = parts.begin(); i != parts.end();++i){
|
||||
gui::draw_solid_tinted_rectangle(0,0,screen.x()-1,screen.y()-1,
|
||||
0,0,0,1.0,screen.video().getSurface());
|
||||
const std::string& image_name = (*i)->values["image"];
|
||||
SDL_Surface* image = NULL;
|
||||
if(image_name.empty() == false) {
|
||||
image = screen.getImage(image_name,display::UNSCALED);
|
||||
}
|
||||
|
||||
int textx = 200;
|
||||
int texty = 400;
|
||||
|
||||
if(image != NULL) {
|
||||
SDL_Rect dstrect;
|
||||
dstrect.x = screen.x()/2 - image->w/2;
|
||||
dstrect.y = screen.y()/2 - image->h/2;
|
||||
dstrect.w = image->w;
|
||||
dstrect.h = image->h;
|
||||
|
||||
SDL_BlitSurface(image,NULL,screen.video().getSurface(),&dstrect);
|
||||
|
||||
textx = dstrect.x;
|
||||
texty = dstrect.y + dstrect.h + 10;
|
||||
|
||||
next_button.set_x(dstrect.x+dstrect.w);
|
||||
next_button.set_y(dstrect.y+dstrect.h+20);
|
||||
skip_button.set_x(dstrect.x+dstrect.w);
|
||||
skip_button.set_y(dstrect.y+dstrect.h+70);
|
||||
}
|
||||
|
||||
const std::string& id = (*i)->values["id"];
|
||||
const std::string& lang_story = string_table[id];
|
||||
const std::string& story = lang_story.empty() ? (*i)->values["story"] :
|
||||
lang_story;
|
||||
|
||||
const int max_length = 60;
|
||||
std::stringstream stream;
|
||||
int cur_length = 0;
|
||||
|
||||
for(std::string::const_iterator j = story.begin(); j!=story.end();++j){
|
||||
char c = *j;
|
||||
if(c == ' ' && cur_length >= max_length)
|
||||
c = '\n';
|
||||
|
||||
if(c == '\n') {
|
||||
cur_length = 0;
|
||||
} else {
|
||||
++cur_length;
|
||||
}
|
||||
|
||||
stream << c;
|
||||
}
|
||||
|
||||
static const SDL_Rect area = {0,0,1024,768};
|
||||
font::draw_text(&screen,area,16,font::NORMAL_COLOUR,stream.str(),
|
||||
textx,texty);
|
||||
next_button.draw();
|
||||
skip_button.draw();
|
||||
screen.video().update(0,0,screen.x(),screen.y());
|
||||
|
||||
const std::string& delay = (*i)->values["delay"];
|
||||
const int ndelay = atoi(delay.c_str());
|
||||
|
||||
bool last = true;
|
||||
for(int d = 0; d < ndelay*10; d += 50) {
|
||||
SDL_Delay(10);
|
||||
|
||||
int mousex, mousey;
|
||||
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
|
||||
|
||||
const bool right_button = mouse_flags&SDL_BUTTON_RMASK;
|
||||
const bool left_button = mouse_flags&SDL_BUTTON_LMASK;
|
||||
|
||||
|
||||
if(key[KEY_ESCAPE] ||
|
||||
skip_button.process(mousex,mousey,left_button))
|
||||
return;
|
||||
|
||||
if(key[KEY_SPACE] || key[KEY_ENTER] ||
|
||||
next_button.process(mousex,mousey,left_button)) {
|
||||
if(!last)
|
||||
break;
|
||||
} else {
|
||||
last = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gui::draw_solid_tinted_rectangle(0,0,screen.x()-1,screen.y()-1,0,0,0,1.0,
|
||||
screen.video().getSurface());
|
||||
}
|
||||
|
||||
void show_map_scene(display& screen, config& data)
|
||||
{
|
||||
//clear the screen
|
||||
gui::draw_solid_tinted_rectangle(0,0,screen.x()-1,screen.y()-1,0,0,0,1.0,
|
||||
screen.video().getSurface());
|
||||
|
||||
std::vector<config*>& sequence = data.children["bigmap"];
|
||||
if(sequence.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
config& cfg = *sequence[0];
|
||||
|
||||
std::vector<config*>& dots = cfg.children["dot"];
|
||||
|
||||
const std::string& image_file = cfg.values["image"];
|
||||
|
||||
SDL_Surface* const image = screen.getImage(image_file,display::UNSCALED);
|
||||
SDL_Surface* const dot_image =
|
||||
screen.getImage("misc/dot.png",display::UNSCALED);
|
||||
SDL_Surface* const cross_image =
|
||||
screen.getImage("misc/cross.png",display::UNSCALED);
|
||||
if(image == NULL || dot_image == NULL || cross_image == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Rect dstrect;
|
||||
dstrect.x = screen.x()/2 - image->w/2;
|
||||
dstrect.y = screen.y()/2 - image->h/2;
|
||||
dstrect.w = image->w;
|
||||
dstrect.h = image->h;
|
||||
|
||||
SDL_BlitSurface(image,NULL,screen.video().getSurface(),&dstrect);
|
||||
|
||||
const std::string& id = data.values["id"];
|
||||
const std::string& scenario_name = string_table[id];
|
||||
|
||||
const std::string& scenario = scenario_name.empty() ? data.values["name"] :
|
||||
scenario_name;
|
||||
screen.video().update(0,0,screen.x(),screen.y());
|
||||
|
||||
CKey key;
|
||||
|
||||
|
||||
for(std::vector<config*>::iterator d = dots.begin(); d != dots.end(); ++d){
|
||||
const std::string& xloc = (*d)->values["x"];
|
||||
const std::string& yloc = (*d)->values["y"];
|
||||
const int x = atoi(xloc.c_str());
|
||||
const int y = atoi(yloc.c_str());
|
||||
if(x < 0 || x >= image->w || y < 0 || y >= image->w)
|
||||
continue;
|
||||
|
||||
SDL_Surface* img = dot_image;
|
||||
if((*d)->values["type"] == "cross") {
|
||||
img = cross_image;
|
||||
}
|
||||
|
||||
int xdot = x - img->w/2;
|
||||
int ydot = y - img->h/2;
|
||||
|
||||
if(xdot < 0)
|
||||
xdot = 0;
|
||||
|
||||
if(ydot < 0)
|
||||
ydot = 0;
|
||||
|
||||
SDL_Rect dot_rect;
|
||||
dot_rect.x = xdot;
|
||||
dot_rect.y = ydot;
|
||||
dot_rect.w = img->w;
|
||||
dot_rect.h = img->h;
|
||||
|
||||
SDL_BlitSurface(img,NULL,image,&dot_rect);
|
||||
|
||||
SDL_BlitSurface(image,NULL,screen.video().getSurface(),&dstrect);
|
||||
|
||||
for(int i = 0; i != 10; ++i) {
|
||||
if(key[KEY_ESCAPE]) {
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_Delay(50);
|
||||
|
||||
int a, b;
|
||||
const int mouse_flags = SDL_GetMouseState(&a,&b);
|
||||
if(key[KEY_ENTER] || key[KEY_SPACE] || mouse_flags) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(key[KEY_ESCAPE]) {
|
||||
break;
|
||||
}
|
||||
|
||||
screen.video().update(0,0,screen.x(),screen.y());
|
||||
}
|
||||
|
||||
if(!key[KEY_ESCAPE]) {
|
||||
SDL_Delay(500);
|
||||
}
|
||||
|
||||
static const SDL_Rect area = {0,0,1024,768};
|
||||
const SDL_Rect scenario_size =
|
||||
font::draw_text(NULL,area,24,font::NORMAL_COLOUR,scenario,0,0);
|
||||
font::draw_text(&screen,area,24,font::NORMAL_COLOUR,scenario,
|
||||
dstrect.x,dstrect.y - scenario_size.h - 4);
|
||||
|
||||
SDL_BlitSurface(image,NULL,screen.video().getSurface(),&dstrect);
|
||||
screen.video().update(0,0,screen.x(),screen.y());
|
||||
|
||||
bool last_state = true;
|
||||
for(;;) {
|
||||
int a, b;
|
||||
const int mouse_flags = SDL_GetMouseState(&a,&b);
|
||||
|
||||
const bool new_state = mouse_flags || key[KEY_ESCAPE] ||
|
||||
key[KEY_ENTER] || key[KEY_SPACE];
|
||||
|
||||
if(new_state && !last_state)
|
||||
break;
|
||||
|
||||
last_state = new_state;
|
||||
|
||||
SDL_Delay(20);
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
|
||||
//clear the screen
|
||||
gui::draw_solid_tinted_rectangle(0,0,screen.x()-1,screen.y()-1,0,0,0,1.0,
|
||||
screen.video().getSurface());
|
||||
}
|
25
intro.hpp
Normal file
25
intro.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef INTRO_HPP_INCLUDED
|
||||
#define INTRO_HPP_INCLUDED
|
||||
|
||||
#include "SDL.h"
|
||||
#include "config.hpp"
|
||||
#include "display.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
void show_intro(display& screen, config& data);
|
||||
|
||||
void show_map_scene(display& screen, config& data);
|
||||
|
||||
#endif
|
51
key.cpp
Normal file
51
key.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "key.hpp"
|
||||
|
||||
#define KEY_TEST 0
|
||||
|
||||
#if (KEY_TEST == 1)
|
||||
|
||||
#include "video.hpp"
|
||||
|
||||
int main( void )
|
||||
{
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
CVideo video( 640, 480, 16, 0 );
|
||||
CKey key;
|
||||
printf( "press enter (escape exits)...\n" );
|
||||
for(;;) {
|
||||
if( key[KEY_RETURN] != 0 )
|
||||
printf( "key(ENTER) pressed\n" );
|
||||
if( key[KEY_ESCAPE] != 0 )
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
CKey::CKey() : is_enabled(true)
|
||||
{
|
||||
static int num_keys = 300;
|
||||
key_list = SDL_GetKeyState( &num_keys );
|
||||
}
|
||||
|
||||
int CKey::operator[]( int code )
|
||||
{
|
||||
SDL_PumpEvents();
|
||||
return (code == KEY_ESCAPE || is_enabled) && int(key_list[code]);
|
||||
}
|
||||
|
||||
void CKey::SetEnabled( bool enable )
|
||||
{
|
||||
is_enabled = enable;
|
||||
}
|
95
key.hpp
Normal file
95
key.hpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef KEY_HPP_INCLUDED
|
||||
#define KEY_HPP_INCLUDED
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
class CKey {
|
||||
public:
|
||||
CKey();
|
||||
|
||||
int operator[](int);
|
||||
void SetEnabled(bool enable);
|
||||
private:
|
||||
Uint8 *key_list;
|
||||
bool is_enabled;
|
||||
};
|
||||
|
||||
#define KEY_PAGEUP SDLK_PAGEUP
|
||||
#define KEY_PAGEDOWN SDLK_PAGEDOWN
|
||||
#define KEY_BACKSPACE SDLK_BACKSPACE
|
||||
#define KEY_DELETE SDLK_DELETE
|
||||
#define KEY_TAB SDLK_TAB
|
||||
#define KEY_CLEAR SDLK_CLEAR
|
||||
#define KEY_RETURN SDLK_RETURN
|
||||
#define KEY_ENTER KEY_RETURN
|
||||
#define KEY_ESCAPE SDLK_ESCAPE
|
||||
#define KEY_SPACE SDLK_SPACE
|
||||
#define KEY_0 SDLK_0
|
||||
#define KEY_1 SDLK_1
|
||||
#define KEY_2 SDLK_2
|
||||
#define KEY_3 SDLK_3
|
||||
#define KEY_4 SDLK_4
|
||||
#define KEY_5 SDLK_5
|
||||
#define KEY_6 SDLK_6
|
||||
#define KEY_7 SDLK_7
|
||||
#define KEY_8 SDLK_8
|
||||
#define KEY_9 SDLK_9
|
||||
#define KEY_KP0 SDLK_KP0
|
||||
#define KEY_KP1 SDLK_KP1
|
||||
#define KEY_KP2 SDLK_KP2
|
||||
#define KEY_KP3 SDLK_KP3
|
||||
#define KEY_KP4 SDLK_KP4
|
||||
#define KEY_KP5 SDLK_KP5
|
||||
#define KEY_KP6 SDLK_KP6
|
||||
#define KEY_KP7 SDLK_KP7
|
||||
#define KEY_KP8 SDLK_KP8
|
||||
#define KEY_KP9 SDLK_KP9
|
||||
#define KEY_A SDLK_a
|
||||
#define KEY_B SDLK_b
|
||||
#define KEY_C SDLK_c
|
||||
#define KEY_D SDLK_d
|
||||
#define KEY_E SDLK_e
|
||||
#define KEY_F SDLK_f
|
||||
#define KEY_G SDLK_g
|
||||
#define KEY_H SDLK_h
|
||||
#define KEY_I SDLK_i
|
||||
#define KEY_J SDLK_j
|
||||
#define KEY_K SDLK_k
|
||||
#define KEY_L SDLK_l
|
||||
#define KEY_M SDLK_m
|
||||
#define KEY_N SDLK_n
|
||||
#define KEY_O SDLK_o
|
||||
#define KEY_P SDLK_p
|
||||
#define KEY_Q SDLK_q
|
||||
#define KEY_R SDLK_r
|
||||
#define KEY_S SDLK_s
|
||||
#define KEY_T SDLK_t
|
||||
#define KEY_U SDLK_u
|
||||
#define KEY_V SDLK_v
|
||||
#define KEY_W SDLK_w
|
||||
#define KEY_X SDLK_x
|
||||
#define KEY_Y SDLK_y
|
||||
#define KEY_Z SDLK_z
|
||||
#define KEY_RSHIFT SDLK_RSHIFT
|
||||
#define KEY_LSHIFT SDLK_LSHIFT
|
||||
#define KEY_RCONTROL SDLK_RCTRL
|
||||
#define KEY_LCONTROL SDLK_LCTRL
|
||||
#define KEY_RALT SDLK_RALT
|
||||
#define KEY_LALT SDLK_LALT
|
||||
#define KEY_UP SDLK_UP
|
||||
#define KEY_DOWN SDLK_DOWN
|
||||
#define KEY_LEFT SDLK_LEFT
|
||||
#define KEY_RIGHT SDLK_RIGHT
|
||||
|
||||
#endif
|
74
language.cpp
Normal file
74
language.cpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "config.hpp"
|
||||
#include "hotkeys.hpp"
|
||||
#include "language.hpp"
|
||||
#include "preferences.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
std::map<std::string,std::string> string_table;
|
||||
|
||||
std::vector<std::string> get_languages(config& cfg)
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
|
||||
const std::vector<config*>& lang = cfg.children["language"];
|
||||
for(std::vector<config*>::const_iterator i = lang.begin();
|
||||
i != lang.end(); ++i) {
|
||||
res.push_back((*i)->values["language"]);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool set_language(const std::string& locale, config& cfg)
|
||||
{
|
||||
const std::vector<config*>& lang = cfg.children["language"];
|
||||
for(std::vector<config*>::const_iterator i = lang.begin();
|
||||
i != lang.end(); ++i) {
|
||||
if((*i)->values["id"] == locale || (*i)->values["language"] == locale) {
|
||||
string_table = (*i)->values;
|
||||
add_hotkeys(**i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string get_locale()
|
||||
{
|
||||
//TODO: Add in support for querying the locale on Windows
|
||||
|
||||
const std::string& prefs_locale = preferences::locale();
|
||||
if(prefs_locale.empty() == false) {
|
||||
return prefs_locale;
|
||||
}
|
||||
|
||||
const char* const locale = getenv("LANG");
|
||||
if(locale != NULL && strlen(locale) >= 2) {
|
||||
//we can't pass pointers into the string to the std::string
|
||||
//constructor because some STL implementations don't support
|
||||
//it (*cough* MSVC++6)
|
||||
std::string res(2,'z');
|
||||
res[0] = tolower(locale[0]);
|
||||
res[1] = tolower(locale[1]);
|
||||
return res;
|
||||
}
|
||||
|
||||
std::cerr << "locale could not be determined; defaulting to locale 'en'\n";
|
||||
return "en";
|
||||
}
|
28
language.hpp
Normal file
28
language.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef LANGUAGE_HPP_INCLUDED
|
||||
#define LANGUAGE_HPP_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern std::map<std::string,std::string> string_table;
|
||||
|
||||
std::vector<std::string> get_languages(config& cfg);
|
||||
bool set_language(const std::string& locale, config& cfg);
|
||||
|
||||
std::string get_locale();
|
||||
|
||||
#endif
|
15
log.cpp
Normal file
15
log.cpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "log.hpp"
|
||||
|
||||
int scope_logger::indent = 0;
|
61
log.hpp
Normal file
61
log.hpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef LOG_HPP_INCLUDED
|
||||
#define LOG_HPP_INCLUDED
|
||||
|
||||
#define LOG_DATA
|
||||
|
||||
#ifdef LOG_DATA
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
struct scope_logger
|
||||
{
|
||||
scope_logger(const std::string& str) : ticks_(SDL_GetTicks()), str_(str) {
|
||||
for(int i = 0; i != indent; ++i)
|
||||
std::cerr << " ";
|
||||
++indent;
|
||||
std::cerr << "BEGIN: " << str_ << "\n";
|
||||
}
|
||||
|
||||
~scope_logger() {
|
||||
const int ticks = SDL_GetTicks() - ticks_;
|
||||
--indent;
|
||||
for(int i = 0; i != indent; ++i)
|
||||
std::cerr << " ";
|
||||
std::cerr << "END: " << str_ << " (took " << ticks << "ms)\n";
|
||||
}
|
||||
|
||||
private:
|
||||
int ticks_;
|
||||
std::string str_;
|
||||
static int indent;
|
||||
};
|
||||
|
||||
#define log(a) std::cerr << a << "\n";
|
||||
#define log1(a,b) std::cerr << a << " info: " << b << "\n";
|
||||
#define log2(a,b,c) std::cerr << a << " info: " << b << ", " << c << "\n";
|
||||
|
||||
#define log_scope(a) scope_logger scope_logging_object__(a);
|
||||
|
||||
#else
|
||||
#define log(a)
|
||||
#define log1(a,b)
|
||||
#define log2(a,b,c)
|
||||
|
||||
#define log_scope(a)
|
||||
#endif
|
||||
|
||||
#endif
|
144
make_translation.cpp
Normal file
144
make_translation.cpp
Normal file
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void process_config(const std::string& element_name, const config& cfg,
|
||||
std::map<std::string,std::string>& out)
|
||||
{
|
||||
typedef std::pair<string,string> pair;
|
||||
|
||||
for(map<string,vector<config*> >::const_iterator i =
|
||||
cfg.children.begin(); i != cfg.children.end(); ++i) {
|
||||
for(vector<config*>::const_iterator j = i->second.begin();
|
||||
j != i->second.end(); ++j) {
|
||||
process_config(i->first,**j,out);
|
||||
}
|
||||
}
|
||||
|
||||
const map<string,string>& table = cfg.values;
|
||||
const map<string,string>::const_iterator id = table.find("id");
|
||||
|
||||
if(element_name == "message") {
|
||||
const map<string,string>::const_iterator msg = table.find("message");
|
||||
|
||||
if(id == table.end()) {
|
||||
static const std::string dummy;
|
||||
const std::string& text = msg != table.end() ? msg->second:dummy;
|
||||
std::cerr << "message found with no id: " << text << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if(msg == table.end()) {
|
||||
std::cerr << "message found with no text\n";
|
||||
return;
|
||||
}
|
||||
|
||||
out.insert(std::pair<string,string>(id->second,msg->second));
|
||||
} else if(element_name == "part") {
|
||||
const map<string,string>::const_iterator msg = table.find("story");
|
||||
if(id == table.end() || msg == table.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
out.insert(std::pair<string,string>(id->second,msg->second));
|
||||
} else if(element_name == "multiplayer" || element_name == "scenario") {
|
||||
map<string,string>::const_iterator msg = table.find("name");
|
||||
if(id == table.end() || msg == table.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
out.insert(std::pair<string,string>(id->second,msg->second));
|
||||
|
||||
if(element_name == "scenario") {
|
||||
msg = table.find("objectives");
|
||||
if(msg != table.end()) {
|
||||
out.insert(std::pair<string,string>(id->second + "_objectives",
|
||||
msg->second));
|
||||
}
|
||||
}
|
||||
} else if(element_name == "language") {
|
||||
const map<string,string>::const_iterator name = table.find("language");
|
||||
if(name != table.end() && name->second == "English") {
|
||||
for(map<string,string>::const_iterator i = table.begin();
|
||||
i != table.end(); ++i) {
|
||||
if(i->first != "language") {
|
||||
out.insert(*i);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(element_name == "unit") {
|
||||
const map<string,string>::const_iterator name_it = table.find("name");
|
||||
if(name_it == table.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string name = name_it->second;
|
||||
name.erase(std::remove(name.begin(),name.end(),' '),name.end());
|
||||
|
||||
out.insert(std::pair<string,string>(name,name_it->second));
|
||||
|
||||
const map<string,string>::const_iterator description_it =
|
||||
table.find("unit_description");
|
||||
if(description_it != table.end()) {
|
||||
out.insert(std::pair<string,string>(name + "_description",
|
||||
description_it->second));
|
||||
}
|
||||
|
||||
const map<string,string>::const_iterator ability_it =
|
||||
table.find("ability");
|
||||
if(ability_it != table.end()) {
|
||||
out.insert(pair("ability_" + ability_it->second,
|
||||
ability_it->second));
|
||||
}
|
||||
} else if(element_name == "attack") {
|
||||
const map<string,string>::const_iterator name_it=table.find("name");
|
||||
const map<string,string>::const_iterator type_it=table.find("type");
|
||||
const map<string,string>::const_iterator spec_it=table.find("special");
|
||||
if(name_it == table.end() || type_it == table.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
out.insert(pair("weapon_name_" + name_it->second,name_it->second));
|
||||
out.insert(pair("weapon_type_" + type_it->second,type_it->second));
|
||||
|
||||
if(spec_it == table.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
out.insert(pair("weapon_special_" + spec_it->second,spec_it->second));
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
config cfg(preprocess_file("data/game.cfg"));
|
||||
|
||||
map<string,string> table;
|
||||
process_config("",cfg,table);
|
||||
std::cout << "[language]\n\tlanguage=\"Language Name Goes Here\"\n" <<
|
||||
"id=en #language code - English=en, French=fr, etc\n";
|
||||
for(map<string,string>::const_iterator i = table.begin();
|
||||
i != table.end(); ++i) {
|
||||
std::cout << "\t" << i->first << "=\"" << i->second << "\"\n";
|
||||
}
|
||||
std::cout << "[/language]\n";
|
||||
|
||||
return 0;
|
||||
}
|
223
map.cpp
Normal file
223
map.cpp
Normal file
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "map.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
gamemap::location gamemap::location::null_location;
|
||||
|
||||
const std::string& gamemap::terrain_name(gamemap::TERRAIN terrain) const
|
||||
{
|
||||
static const std::string default_val;
|
||||
const std::map<TERRAIN,terrain_type>::const_iterator i =
|
||||
letterToTerrain_.find(terrain);
|
||||
if(i == letterToTerrain_.end())
|
||||
return default_val;
|
||||
else
|
||||
return i->second.name();
|
||||
}
|
||||
|
||||
const std::string& gamemap::underlying_terrain_name(gamemap::TERRAIN terrain) const
|
||||
{
|
||||
static const std::string default_val;
|
||||
const std::map<TERRAIN,terrain_type>::const_iterator i =
|
||||
letterToTerrain_.find(terrain);
|
||||
if(i == letterToTerrain_.end()) {
|
||||
return default_val;
|
||||
} else {
|
||||
if(i->second.is_alias()) {
|
||||
//we could call underlying_terrain_name, but that could allow
|
||||
//infinite recursion with bad data files, so we call terrain_name
|
||||
//to be safe
|
||||
return terrain_name(i->second.type());
|
||||
} else {
|
||||
return i->second.name();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gamemap::TERRAIN gamemap::underlying_terrain(TERRAIN terrain) const
|
||||
{
|
||||
const std::map<TERRAIN,terrain_type>::const_iterator i =
|
||||
letterToTerrain_.find(terrain);
|
||||
if(i == letterToTerrain_.end()) {
|
||||
return terrain;
|
||||
} else {
|
||||
return i->second.type();
|
||||
}
|
||||
}
|
||||
|
||||
gamemap::location::location(config& cfg) : x(-1), y(-1)
|
||||
{
|
||||
const std::string& xstr = cfg.values["x"];
|
||||
const std::string& ystr = cfg.values["y"];
|
||||
|
||||
//the co-ordinates in config files will be 1-based, while we
|
||||
//want them as 0-based
|
||||
if(xstr.empty() == false)
|
||||
x = atoi(xstr.c_str()) - 1;
|
||||
|
||||
if(ystr.empty() == false)
|
||||
y = atoi(ystr.c_str()) - 1;
|
||||
}
|
||||
|
||||
bool gamemap::location::operator==(const gamemap::location& a) const
|
||||
{
|
||||
return x == a.x && y == a.y;
|
||||
}
|
||||
|
||||
bool gamemap::location::operator!=(const gamemap::location& a) const
|
||||
{
|
||||
return !operator==(a);
|
||||
}
|
||||
|
||||
bool gamemap::location::operator<(const gamemap::location& a) const
|
||||
{
|
||||
return x < a.x || x == a.x && y < a.y;
|
||||
}
|
||||
|
||||
gamemap::location gamemap::location::get_direction(
|
||||
gamemap::location::DIRECTION dir) const
|
||||
{
|
||||
switch(dir) {
|
||||
case NORTH: return gamemap::location(x,y-1);
|
||||
case NORTH_EAST: return gamemap::location(x+1,y-((x%2) == 0 ? 1:0));
|
||||
case SOUTH_EAST: return gamemap::location(x+1,y+((x%2) == 0 ? 0:1));
|
||||
case SOUTH: return gamemap::location(x,y+1);
|
||||
case SOUTH_WEST: return gamemap::location(x-1,y+((x%2) == 0 ? 0:1));
|
||||
case NORTH_WEST: return gamemap::location(x-1,y+((x%2) == 0 ? 1:0));
|
||||
default:
|
||||
assert(false);
|
||||
return gamemap::location();
|
||||
}
|
||||
}
|
||||
|
||||
gamemap::gamemap(config& cfg, const std::string& data) : tiles_(1)
|
||||
{
|
||||
std::vector<config*>& terrains = cfg.children["terrain"];
|
||||
create_terrain_maps(terrains,terrainPrecedence_,letterToTerrain_,terrain_);
|
||||
|
||||
int x = 0, y = 0;
|
||||
for(std::string::const_iterator i = data.begin(); i != data.end(); ++i) {
|
||||
char c = *i;
|
||||
if(c == '\n') {
|
||||
tiles_.push_back(std::vector<TERRAIN>());
|
||||
y = 0;
|
||||
++x;
|
||||
} else {
|
||||
if(letterToTerrain_.count(c) == 0) {
|
||||
if(isdigit(*i)) {
|
||||
startingPositions_[c - '0'] = location(x,y);
|
||||
c = CASTLE;
|
||||
} else {
|
||||
std::cerr << "Illegal character in map: '" << c << "'\n";
|
||||
throw incorrect_format_exception("Illegal character");
|
||||
}
|
||||
}
|
||||
|
||||
if(c == TOWER) {
|
||||
towers_.push_back(location(x,y));
|
||||
}
|
||||
|
||||
tiles_.back().push_back(c);
|
||||
|
||||
++y;
|
||||
}
|
||||
}
|
||||
|
||||
if(tiles_.empty())
|
||||
throw incorrect_format_exception("empty map");
|
||||
|
||||
for(int n = 0; n != tiles_.size(); ++n) {
|
||||
if(tiles_[n].size() != this->y()) {
|
||||
tiles_.erase(tiles_.begin()+n);
|
||||
--n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string gamemap::write() const
|
||||
{
|
||||
std::stringstream str;
|
||||
for(int i = 0; i != x(); ++i) {
|
||||
for(int j = 0; j != y(); ++j) {
|
||||
int n;
|
||||
for(n = 0; n != 10; ++n) {
|
||||
if(startingPositions_[n] == location(i,j))
|
||||
break;
|
||||
}
|
||||
|
||||
if(n < 10)
|
||||
str << n;
|
||||
else
|
||||
str << tiles_[i][j];
|
||||
}
|
||||
|
||||
str << "\n";
|
||||
}
|
||||
|
||||
return str.str();
|
||||
}
|
||||
|
||||
int gamemap::x() const { return tiles_.size(); }
|
||||
int gamemap::y() const { return tiles_[0].size(); }
|
||||
|
||||
const std::vector<gamemap::TERRAIN>& gamemap::operator[](int index) const
|
||||
{
|
||||
return tiles_[index];
|
||||
}
|
||||
|
||||
const gamemap::location& gamemap::starting_position(int n) const
|
||||
{
|
||||
return startingPositions_[n];
|
||||
}
|
||||
|
||||
int gamemap::num_starting_positions() const
|
||||
{
|
||||
return sizeof(startingPositions_)/sizeof(*startingPositions_);
|
||||
}
|
||||
|
||||
bool gamemap::is_starting_position(const gamemap::location& loc) const
|
||||
{
|
||||
const gamemap::location* const end
|
||||
= startingPositions_+num_starting_positions();
|
||||
return std::find(startingPositions_,end,loc) != end;
|
||||
}
|
||||
|
||||
const terrain_type& gamemap::get_terrain_info(TERRAIN terrain) const
|
||||
{
|
||||
static const terrain_type default_terrain;
|
||||
const std::map<TERRAIN,terrain_type>::const_iterator i =
|
||||
letterToTerrain_.find(terrain);
|
||||
if(i != letterToTerrain_.end())
|
||||
return i->second;
|
||||
else
|
||||
return default_terrain;
|
||||
}
|
||||
|
||||
const std::vector<gamemap::TERRAIN>& gamemap::get_terrain_precedence() const
|
||||
{
|
||||
return terrainPrecedence_;
|
||||
}
|
||||
|
||||
void gamemap::set_terrain(const gamemap::location& loc, gamemap::TERRAIN ter)
|
||||
{
|
||||
if(!on_board(loc))
|
||||
return;
|
||||
|
||||
tiles_[loc.x][loc.y] = ter;
|
||||
}
|
100
map.hpp
Normal file
100
map.hpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef MAP_H_INCLUDED
|
||||
#define MAP_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "terrain.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class gamemap
|
||||
{
|
||||
public:
|
||||
|
||||
typedef char TERRAIN;
|
||||
|
||||
enum { VOID_TERRAIN = ' ', CASTLE = 'C', TOWER = 't', FOREST = 'f' };
|
||||
|
||||
//the name of the terrain is the terrain itself, the underlying terrain
|
||||
//is the name of the terrain for game-logic purposes. I.e. if the terrain
|
||||
//is simply an alias, the underlying terrain name is the name of the
|
||||
//terrain that it's aliased to
|
||||
const std::string& terrain_name(TERRAIN terrain) const;
|
||||
const std::string& underlying_terrain_name(TERRAIN terrain) const;
|
||||
TERRAIN underlying_terrain(TERRAIN terrain) const;
|
||||
|
||||
struct incorrect_format_exception {
|
||||
incorrect_format_exception(const char* msg) : msg_(msg) {}
|
||||
const char* const msg_;
|
||||
};
|
||||
|
||||
struct location {
|
||||
enum DIRECTION { NORTH, NORTH_EAST, SOUTH_EAST, SOUTH,
|
||||
SOUTH_WEST, NORTH_WEST };
|
||||
|
||||
location() : x(-1), y(-1) {}
|
||||
location(int x, int y) : x(x), y(y) {}
|
||||
location(config& cfg);
|
||||
|
||||
bool valid() const { return x >= 0 && y >= 0; }
|
||||
|
||||
int x, y;
|
||||
|
||||
bool operator<(const location& a) const;
|
||||
bool operator==(const location& a) const;
|
||||
bool operator!=(const location& a) const;
|
||||
|
||||
location get_direction(DIRECTION d) const;
|
||||
|
||||
static location null_location;
|
||||
};
|
||||
|
||||
gamemap(config& cfg,
|
||||
const std::string& data); //throw(incorrect_format_exception)
|
||||
|
||||
std::string write() const;
|
||||
|
||||
int x() const;
|
||||
int y() const;
|
||||
|
||||
const std::vector<TERRAIN>& operator[](int index) const;
|
||||
|
||||
const location& starting_position(int n) const;
|
||||
int num_starting_positions() const;
|
||||
bool is_starting_position(const location& loc) const;
|
||||
|
||||
bool on_board(const location& loc) const
|
||||
{
|
||||
return loc.valid() && loc.x < x() && loc.y < y();
|
||||
}
|
||||
|
||||
const std::vector<location>& towers() const { return towers_; }
|
||||
|
||||
const terrain_type& get_terrain_info(TERRAIN terrain) const;
|
||||
|
||||
const std::vector<TERRAIN>& get_terrain_precedence() const;
|
||||
|
||||
void set_terrain(const location& loc, TERRAIN ter);
|
||||
private:
|
||||
std::vector<TERRAIN> terrainPrecedence_;
|
||||
std::map<TERRAIN,terrain_type> letterToTerrain_;
|
||||
std::map<std::string,terrain_type> terrain_;
|
||||
|
||||
std::vector<std::vector<TERRAIN> > tiles_;
|
||||
std::vector<location> towers_;
|
||||
location startingPositions_[10];
|
||||
};
|
||||
|
||||
#endif
|
908
menu.cpp
Normal file
908
menu.cpp
Normal file
|
@ -0,0 +1,908 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "config.hpp"
|
||||
#include "font.hpp"
|
||||
#include "language.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "language.hpp"
|
||||
#include "sdl_utils.hpp"
|
||||
#include "widgets/button.hpp"
|
||||
#include "widgets/textbox.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
|
||||
namespace gui {
|
||||
|
||||
void draw_dialog_frame(int x, int y, int w, int h, display& disp)
|
||||
{
|
||||
const int border_size = 6;
|
||||
|
||||
SDL_Surface* const scr = disp.video().getSurface();
|
||||
|
||||
const display::Pixel border_colour = SDL_MapRGB(scr->format,200,0,0);
|
||||
|
||||
draw_solid_tinted_rectangle(x-border_size,y-border_size,
|
||||
w+border_size,h+border_size,0,0,0,0.6,scr);
|
||||
draw_solid_tinted_rectangle(x,y,w+border_size,h+border_size,0,0,0,0.6,scr);
|
||||
|
||||
draw_rectangle(x-border_size,y-border_size,w+border_size,h+border_size,
|
||||
border_colour,scr);
|
||||
draw_rectangle(x,y,w+border_size,h+border_size,border_colour,scr);
|
||||
|
||||
SDL_Rect update = {x-border_size,y-border_size,
|
||||
w+border_size*2+1,h+border_size*2+1};
|
||||
disp.update_rect(update);
|
||||
}
|
||||
|
||||
void draw_rectangle(int x, int y, int w, int h, short colour,
|
||||
SDL_Surface* target)
|
||||
{
|
||||
if(x < 0 || y < 0 || x+w >= target->w || y+h >= target->h) {
|
||||
std::cerr << "Rectangle has illegal co-ordinates: " << x << "," << y
|
||||
<< "," << w << "," << h << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
short* top_left = reinterpret_cast<short*>(target->pixels) + target->w*y+x;
|
||||
short* top_right = top_left + w;
|
||||
short* bot_left = top_left + target->w*h;
|
||||
short* bot_right = bot_left + w;
|
||||
|
||||
std::fill(top_left,top_right+1,colour);
|
||||
std::fill(bot_left,bot_right+1,colour);
|
||||
while(top_left != bot_left) {
|
||||
*top_left = colour;
|
||||
*top_right = colour;
|
||||
top_left += target->w;
|
||||
top_right += target->w;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_solid_tinted_rectangle(int x, int y, int w, int h,
|
||||
int r, int g, int b,
|
||||
double alpha, SDL_Surface* target)
|
||||
{
|
||||
if(x < 0 || y < 0 || x+w >= target->w || y+h >= target->h) {
|
||||
std::cerr << "Rectangle has illegal co-ordinates: " << x << "," << y
|
||||
<< "," << w << "," << h << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
const SDL_PixelFormat* const fmt = target->format;
|
||||
short* p = reinterpret_cast<short*>(target->pixels) + target->w*y + x;
|
||||
while(h > 0) {
|
||||
short* beg = p;
|
||||
short* const end = p + w;
|
||||
while(beg != end) {
|
||||
const int cur_r = ((*beg&fmt->Rmask) >> fmt->Rshift) << fmt->Rloss;
|
||||
const int cur_g = ((*beg&fmt->Gmask) >> fmt->Gshift) << fmt->Gloss;
|
||||
const int cur_b = ((*beg&fmt->Bmask) >> fmt->Bshift) << fmt->Bloss;
|
||||
|
||||
const int new_r = int(double(cur_r)*(1.0-alpha) + double(r)*alpha);
|
||||
const int new_g = int(double(cur_g)*(1.0-alpha) + double(g)*alpha);
|
||||
const int new_b = int(double(cur_b)*(1.0-alpha) + double(b)*alpha);
|
||||
|
||||
*beg = ((new_r >> fmt->Rloss) << fmt->Rshift) |
|
||||
((new_g >> fmt->Gloss) << fmt->Gshift) |
|
||||
((new_b >> fmt->Bloss) << fmt->Bshift);
|
||||
|
||||
++beg;
|
||||
}
|
||||
|
||||
p += target->w;
|
||||
--h;
|
||||
}
|
||||
}
|
||||
|
||||
} //end namespace gui
|
||||
|
||||
namespace {
|
||||
const int max_menu_items = 18;
|
||||
const int menu_font_size = 16;
|
||||
const int menu_cell_padding = 10;
|
||||
|
||||
class menu
|
||||
{
|
||||
display* display_;
|
||||
int x_, y_;
|
||||
std::vector<std::vector<std::string> > items_;
|
||||
mutable std::vector<int> column_widths_;
|
||||
|
||||
scoped_sdl_surface buffer_;
|
||||
int selected_;
|
||||
bool click_selects_;
|
||||
bool previous_button_;
|
||||
bool drawn_;
|
||||
|
||||
mutable int height_;
|
||||
mutable int width_;
|
||||
|
||||
mutable int first_item_on_screen_;
|
||||
gui::button uparrow_, downarrow_;
|
||||
|
||||
const std::vector<int>& column_widths() const
|
||||
{
|
||||
if(column_widths_.empty()) {
|
||||
for(int row = 0; row != items_.size(); ++row) {
|
||||
for(int col = 0; col != items_[row].size(); ++col) {
|
||||
static const SDL_Rect area = {0,0,1024,768};
|
||||
|
||||
const SDL_Rect res =
|
||||
font::draw_text(NULL,area,menu_font_size,
|
||||
font::NORMAL_COLOUR,items_[row][col],x_,y_);
|
||||
|
||||
if(col == column_widths_.size()) {
|
||||
column_widths_.push_back(res.w + menu_cell_padding);
|
||||
} else if(res.w > column_widths_[col] - menu_cell_padding) {
|
||||
column_widths_[col] = res.w + menu_cell_padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return column_widths_;
|
||||
}
|
||||
|
||||
void draw_item(int item) {
|
||||
SDL_Rect rect = get_item_rect(item);
|
||||
if(rect.w == 0)
|
||||
return;
|
||||
|
||||
short* const dstptr = reinterpret_cast<short*>(
|
||||
display_->video().getSurface()->pixels) +
|
||||
rect.y*display_->x() + x_;
|
||||
|
||||
if(buffer_.get() != NULL) {
|
||||
const int ypos = items_start()+(item-first_item_on_screen_)*rect.h;
|
||||
SDL_Rect srcrect = {0,ypos,rect.w,rect.h};
|
||||
SDL_BlitSurface(buffer_,&srcrect,
|
||||
display_->video().getSurface(),&rect);
|
||||
}
|
||||
|
||||
short* dst = dstptr;
|
||||
|
||||
gui::draw_solid_tinted_rectangle(x_,rect.y,width(),rect.h,
|
||||
item == selected_ ? 150:0,0,0,
|
||||
0.7,display_->video().getSurface());
|
||||
|
||||
SDL_Rect area = {0,0,1024,768};
|
||||
|
||||
const std::vector<int>& widths = column_widths();
|
||||
|
||||
int xpos = rect.x;
|
||||
for(int i = 0; i != items_[item].size(); ++i) {
|
||||
font::draw_text(display_,area,menu_font_size,font::NORMAL_COLOUR,
|
||||
items_[item][i],xpos,rect.y);
|
||||
xpos += widths[i];
|
||||
}
|
||||
}
|
||||
|
||||
void draw() {
|
||||
drawn_ = true;
|
||||
|
||||
for(int i = 0; i != items_.size(); ++i)
|
||||
draw_item(i);
|
||||
|
||||
display_->video().update(x_,y_,width(),height());
|
||||
}
|
||||
|
||||
int hit(int x, int y) const {
|
||||
if(x > x_ && x < x_ + width() && y > y_ && y < y_ + height()){
|
||||
for(int i = 0; i != items_.size(); ++i) {
|
||||
const SDL_Rect& rect = get_item_rect(i);
|
||||
if(y > rect.y && y < rect.y + rect.h)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
mutable std::map<int,SDL_Rect> itemRects_;
|
||||
|
||||
SDL_Rect get_item_rect(int item) const
|
||||
{
|
||||
const SDL_Rect empty_rect = {0,0,0,0};
|
||||
if(item < first_item_on_screen_ ||
|
||||
item >= first_item_on_screen_ + max_menu_items) {
|
||||
return empty_rect;
|
||||
}
|
||||
|
||||
const std::map<int,SDL_Rect>::const_iterator i = itemRects_.find(item);
|
||||
if(i != itemRects_.end())
|
||||
return i->second;
|
||||
|
||||
int y = y_ + items_start();
|
||||
if(item != first_item_on_screen_) {
|
||||
const SDL_Rect& prev = get_item_rect(item-1);
|
||||
y = prev.y + prev.h;
|
||||
}
|
||||
|
||||
static const SDL_Rect area = {0,0,1024,768};
|
||||
|
||||
SDL_Rect res = font::draw_text(NULL,area,menu_font_size,
|
||||
font::NORMAL_COLOUR,items_[item][0],x_,y);
|
||||
|
||||
res.w = width();
|
||||
|
||||
//only insert into the cache if the menu's co-ordinates have
|
||||
//been initialized
|
||||
if(x_ > 0 && y_ > 0)
|
||||
itemRects_.insert(std::pair<int,SDL_Rect>(item,res));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int items_start() const
|
||||
{
|
||||
if(items_.size() > max_menu_items)
|
||||
return uparrow_.height();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int items_end() const
|
||||
{
|
||||
if(items_.size() > max_menu_items)
|
||||
return height() - downarrow_.height();
|
||||
else
|
||||
return height();
|
||||
}
|
||||
|
||||
int items_height() const
|
||||
{
|
||||
return items_end() - items_start();
|
||||
}
|
||||
|
||||
public:
|
||||
menu(display& disp, const std::vector<std::string>& items,
|
||||
bool click_selects=false)
|
||||
: display_(&disp), x_(0), y_(0), buffer_(NULL),
|
||||
selected_(-1), click_selects_(click_selects),
|
||||
previous_button_(true), drawn_(false), height_(-1), width_(-1),
|
||||
first_item_on_screen_(0),
|
||||
uparrow_(disp,"",gui::button::TYPE_PRESS,"uparrow"),
|
||||
downarrow_(disp,"",gui::button::TYPE_PRESS,"downarrow")
|
||||
{
|
||||
for(std::vector<std::string>::const_iterator item = items.begin();
|
||||
item != items.end(); ++item) {
|
||||
items_.push_back(config::split(*item));
|
||||
|
||||
//make sure there is always at least one item
|
||||
if(items_.back().empty())
|
||||
items_.back().push_back(" ");
|
||||
}
|
||||
}
|
||||
|
||||
int height() const {
|
||||
if(height_ == -1) {
|
||||
const SDL_Rect area = { 0, 0, 1024, 768 };
|
||||
height_ = 0;
|
||||
for(int i = 0; i != items_.size() && i != max_menu_items; ++i) {
|
||||
height_ += get_item_rect(i).h;
|
||||
}
|
||||
|
||||
if(items_.size() > max_menu_items) {
|
||||
height_ += uparrow_.height() + downarrow_.height();
|
||||
}
|
||||
}
|
||||
|
||||
return height_;
|
||||
}
|
||||
|
||||
int width() const {
|
||||
if(width_ == -1) {
|
||||
const std::vector<int>& widths = column_widths();
|
||||
width_ = std::accumulate(widths.begin(),widths.end(),0);
|
||||
}
|
||||
|
||||
return width_;
|
||||
}
|
||||
|
||||
int selection() const { return selected_; }
|
||||
|
||||
void set_loc(int x, int y) {
|
||||
x_ = x;
|
||||
y_ = y;
|
||||
|
||||
const int w = width();
|
||||
|
||||
SDL_Rect portion = {x_,y_,w,height()};
|
||||
SDL_Surface* const screen = display_->video().getSurface();
|
||||
buffer_.assign(get_surface_portion(screen, portion));
|
||||
|
||||
if(items_.size() > max_menu_items) {
|
||||
uparrow_.set_x(x_);
|
||||
uparrow_.set_y(y_);
|
||||
downarrow_.set_x(x_);
|
||||
downarrow_.set_y(y_+items_end());
|
||||
}
|
||||
}
|
||||
|
||||
int process(int x, int y, bool button,bool up_arrow,bool down_arrow,
|
||||
bool page_up, bool page_down) {
|
||||
if(items_.size() > max_menu_items) {
|
||||
const bool up = uparrow_.process(x,y,button);
|
||||
if(up && first_item_on_screen_ > 0) {
|
||||
itemRects_.clear();
|
||||
--first_item_on_screen_;
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
const bool down = downarrow_.process(x,y,button);
|
||||
if(down && first_item_on_screen_ + max_menu_items < items_.size()) {
|
||||
itemRects_.clear();
|
||||
++first_item_on_screen_;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
if(up_arrow && !click_selects_ && selected_ > 0) {
|
||||
--selected_;
|
||||
if(selected_ < first_item_on_screen_) {
|
||||
itemRects_.clear();
|
||||
first_item_on_screen_ = selected_;
|
||||
}
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
if(down_arrow && !click_selects_ && selected_ < items_.size()-1) {
|
||||
++selected_;
|
||||
if(selected_ - first_item_on_screen_ == max_menu_items) {
|
||||
itemRects_.clear();
|
||||
++first_item_on_screen_;
|
||||
}
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
if(page_up && !click_selects_) {
|
||||
selected_ -= max_menu_items;
|
||||
if(selected_ < 0)
|
||||
selected_ = 0;
|
||||
|
||||
itemRects_.clear();
|
||||
first_item_on_screen_ = selected_;
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
if(page_down && !click_selects_) {
|
||||
selected_ += max_menu_items;
|
||||
if(selected_ >= items_.size())
|
||||
selected_ = items_.size()-1;
|
||||
|
||||
first_item_on_screen_ = selected_ - (max_menu_items-1);
|
||||
if(first_item_on_screen_ < 0)
|
||||
first_item_on_screen_ = 0;
|
||||
|
||||
itemRects_.clear();
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
const int starting_selected = selected_;
|
||||
|
||||
const int hit_item = hit(x,y);
|
||||
|
||||
if(click_selects_) {
|
||||
selected_ = hit_item;
|
||||
if(button && !previous_button_)
|
||||
return selected_;
|
||||
else {
|
||||
if(!drawn_ || selected_ != starting_selected)
|
||||
draw();
|
||||
previous_button_ = button;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(button && hit_item != -1){
|
||||
selected_ = hit_item;
|
||||
}
|
||||
|
||||
if(selected_ == -1)
|
||||
selected_ = 0;
|
||||
|
||||
if(selected_ != starting_selected)
|
||||
draw();
|
||||
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace gui
|
||||
{
|
||||
|
||||
int show_dialog(display& disp, SDL_Surface* image,
|
||||
const std::string& caption, const std::string& msg,
|
||||
DIALOG_TYPE type,
|
||||
const std::vector<std::string>* menu_items_ptr,
|
||||
const std::vector<unit>* units_ptr,
|
||||
const std::string& text_widget_label,
|
||||
std::string* text_widget_text)
|
||||
{
|
||||
if(disp.update_locked())
|
||||
return -1;
|
||||
|
||||
const std::vector<std::string>& menu_items =
|
||||
(menu_items_ptr == NULL) ? std::vector<std::string>() : *menu_items_ptr;
|
||||
const std::vector<unit>& units =
|
||||
(units_ptr == NULL) ? std::vector<unit>() : *units_ptr;
|
||||
|
||||
static const int message_font_size = 16;
|
||||
static const int caption_font_size = 18;
|
||||
|
||||
CVideo& screen = disp.video();
|
||||
SDL_Surface* const scr = screen.getSurface();
|
||||
|
||||
SDL_Rect clipRect = { 0, 0, 1024, 768 };
|
||||
|
||||
const bool use_textbox = text_widget_text != NULL;
|
||||
static const std::string default_text_string = "";
|
||||
const unsigned int text_box_width = 350;
|
||||
textbox text_widget(disp,text_box_width,
|
||||
use_textbox ? *text_widget_text : default_text_string);
|
||||
|
||||
int text_widget_width = 0;
|
||||
int text_widget_height = 0;
|
||||
if(use_textbox) {
|
||||
text_widget_width =
|
||||
font::draw_text(NULL, clipRect, message_font_size,
|
||||
font::NORMAL_COLOUR, text_widget_label, 0, 0, NULL).w +
|
||||
text_widget.width();
|
||||
text_widget_height = text_widget.height();
|
||||
}
|
||||
|
||||
menu menu_(disp,menu_items,type == MESSAGE);
|
||||
|
||||
const int border_size = 6;
|
||||
const short text_colour = 0xFFFF;
|
||||
const short border_colour = 0xF000;
|
||||
int nlines = 1;
|
||||
int longest_line = 0;
|
||||
int cur_line = 0;
|
||||
|
||||
const int max_line_length = 58;
|
||||
|
||||
std::string message = msg;
|
||||
for(std::string::iterator message_it = message.begin();
|
||||
message_it != message.end(); ++message_it) {
|
||||
if(*message_it == ' ' && cur_line > max_line_length)
|
||||
*message_it = '\n';
|
||||
|
||||
if(*message_it == '\n') {
|
||||
if(cur_line > longest_line)
|
||||
longest_line = cur_line;
|
||||
|
||||
cur_line = 0;
|
||||
|
||||
++nlines;
|
||||
} else {
|
||||
++cur_line;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Rect text_size = { 0, 0, 0, 0 };
|
||||
if(!message.empty()) {
|
||||
text_size = font::draw_text(NULL, clipRect, message_font_size,
|
||||
font::NORMAL_COLOUR, message, 0, 0, NULL);
|
||||
}
|
||||
|
||||
SDL_Rect caption_size = { 0, 0, 0, 0 };
|
||||
if(!caption.empty()) {
|
||||
caption_size = font::draw_text(NULL, clipRect, caption_font_size,
|
||||
font::NORMAL_COLOUR,caption,0,0,NULL);
|
||||
}
|
||||
|
||||
const std::string* button_list = NULL;
|
||||
std::vector<button> buttons;
|
||||
switch(type) {
|
||||
case MESSAGE:
|
||||
break;
|
||||
case OK_ONLY: {
|
||||
static const std::string thebuttons[] = { "ok_button", "" };
|
||||
button_list = thebuttons;
|
||||
break;
|
||||
}
|
||||
|
||||
case YES_NO: {
|
||||
static const std::string thebuttons[] = { "yes_button",
|
||||
"no_button", ""};
|
||||
button_list = thebuttons;
|
||||
break;
|
||||
}
|
||||
|
||||
case OK_CANCEL: {
|
||||
static const std::string thebuttons[] = { "ok_button",
|
||||
"cancel_button",""};
|
||||
button_list = thebuttons;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const int button_height_padding = 10;
|
||||
int button_width_padding = 0;
|
||||
int button_heights = 0;
|
||||
int button_widths = 0;
|
||||
if(button_list != NULL) {
|
||||
try {
|
||||
while(button_list->empty() == false) {
|
||||
buttons.push_back(button(disp,string_table[*button_list]));
|
||||
|
||||
if(buttons.back().height() > button_heights)
|
||||
button_heights = buttons.back().height();
|
||||
|
||||
button_widths += buttons.back().width();
|
||||
button_width_padding += 5;
|
||||
|
||||
++button_list;
|
||||
}
|
||||
|
||||
} catch(button::error&) {
|
||||
std::cerr << "error initializing button!\n";
|
||||
}
|
||||
}
|
||||
|
||||
if(button_heights > 0) {
|
||||
button_heights += button_height_padding;
|
||||
}
|
||||
|
||||
if(cur_line > longest_line)
|
||||
longest_line = cur_line;
|
||||
|
||||
const int left_padding = 10;
|
||||
const int right_padding = 10;
|
||||
const int image_h_padding = image != NULL ? 10 : 0;
|
||||
const int top_padding = 10;
|
||||
const int bottom_padding = 10;
|
||||
const int padding_width = left_padding + right_padding + image_h_padding;
|
||||
const int padding_height = top_padding + bottom_padding;
|
||||
const int caption_width = caption_size.w;
|
||||
const int image_width = image != NULL ? image->w : 0;
|
||||
const int total_image_width = caption_width > image_width ?
|
||||
caption_width : image_width;
|
||||
const int image_height = image != NULL ? image->h : 0;
|
||||
|
||||
int text_width = text_size.w;
|
||||
if(menu_.width() > text_width)
|
||||
text_width = menu_.width();
|
||||
|
||||
int total_width = total_image_width + text_width +
|
||||
padding_width;
|
||||
if(button_widths + button_width_padding > total_width)
|
||||
total_width = button_widths + button_width_padding;
|
||||
|
||||
if(text_widget_width+left_padding+right_padding > total_width)
|
||||
total_width = text_widget_width+left_padding+right_padding;
|
||||
|
||||
const int total_height = (image_height+8 > text_size.h ?
|
||||
image_height+8 : text_size.h) +
|
||||
padding_height + button_heights + menu_.height() +
|
||||
text_widget_height;
|
||||
|
||||
if(total_width > scr->w - 100 || total_height > scr->h - 100)
|
||||
return false;
|
||||
|
||||
int xloc = scr->w/2 - total_width/2;
|
||||
int yloc = scr->h/2 - total_height/2;
|
||||
|
||||
int unitx = 0;
|
||||
int unity = 0;
|
||||
//if we are showing a dialog with unit details, then we have
|
||||
//to make more room for it
|
||||
if(!units.empty()) {
|
||||
xloc += scr->w/10;
|
||||
unitx = xloc - 300;
|
||||
if(unitx < 10)
|
||||
unitx = 10;
|
||||
|
||||
unity = yloc;
|
||||
}
|
||||
|
||||
//make sure that the dialog doesn't overlap the right part of the screen
|
||||
if(xloc + total_width+border_size >= disp.mapx()-1) {
|
||||
xloc = disp.mapx()-(total_width+border_size+2);
|
||||
if(xloc < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
const int button_hpadding = total_width - button_widths;
|
||||
int button_offset = 0;
|
||||
for(int button_num = 0; button_num != buttons.size(); ++button_num) {
|
||||
const int padding_amount = button_hpadding/(buttons.size()+1);
|
||||
buttons[button_num].set_x(xloc + padding_amount*(button_num+1) +
|
||||
button_offset);
|
||||
buttons[button_num].set_y(yloc + total_height - button_heights);
|
||||
button_offset += buttons[button_num].width();
|
||||
}
|
||||
|
||||
if(menu_.height() > 0)
|
||||
menu_.set_loc(xloc+total_image_width+left_padding+image_h_padding,
|
||||
yloc+top_padding+text_size.h);
|
||||
|
||||
draw_dialog_frame(xloc,yloc,total_width,total_height,disp);
|
||||
|
||||
if(image != NULL) {
|
||||
const int x = xloc + left_padding;
|
||||
const int y = yloc + top_padding;
|
||||
|
||||
disp.blit_surface(x,y,image);
|
||||
|
||||
int center_font = 0;
|
||||
if(caption_size.w < image->w) {
|
||||
center_font = image->w/2 - caption_size.w/2;
|
||||
}
|
||||
|
||||
font::draw_text(&disp, clipRect, caption_font_size,
|
||||
font::NORMAL_COLOUR, caption,
|
||||
xloc+left_padding+center_font,
|
||||
yloc+top_padding+image->h-6, NULL);
|
||||
}
|
||||
|
||||
if(!units.empty()) {
|
||||
const int unitw = 200;
|
||||
const int unith = 768/2;
|
||||
draw_solid_tinted_rectangle(unitx,unity,unitw,unith,
|
||||
0,0,0,1.0,scr);
|
||||
draw_rectangle(unitx,unity,unitw,unith,border_colour,scr);
|
||||
}
|
||||
|
||||
font::draw_text(&disp, clipRect, message_font_size,
|
||||
font::NORMAL_COLOUR, message,
|
||||
xloc+total_image_width+left_padding+image_h_padding,
|
||||
yloc+top_padding);
|
||||
|
||||
if(use_textbox) {
|
||||
const int image_h = image != NULL ? image->h : 0;
|
||||
const int text_widget_y = yloc+top_padding+image_h-6+text_size.h;
|
||||
text_widget.set_location(xloc + left_padding +
|
||||
text_widget_width - text_widget.width(),
|
||||
text_widget_y);
|
||||
text_widget.draw();
|
||||
font::draw_text(&disp, clipRect, message_font_size,
|
||||
font::NORMAL_COLOUR, text_widget_label,
|
||||
xloc + left_padding,text_widget_y);
|
||||
}
|
||||
|
||||
screen.update(0,0,scr->w,scr->h);
|
||||
|
||||
CKey key;
|
||||
|
||||
bool left_button = true, right_button = true, key_down = true,
|
||||
up_arrow = false, down_arrow = false,
|
||||
page_up = false, page_down = false;
|
||||
|
||||
disp.invalidate_all();
|
||||
|
||||
int cur_selection = -1;
|
||||
|
||||
SDL_Rect unit_details_rect, unit_profile_rect;
|
||||
unit_details_rect.w = 0;
|
||||
unit_profile_rect.w = 0;
|
||||
|
||||
bool first_time = true;
|
||||
|
||||
for(;;) {
|
||||
int mousex, mousey;
|
||||
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
|
||||
|
||||
const bool new_right_button = mouse_flags&SDL_BUTTON_RMASK;
|
||||
const bool new_left_button = mouse_flags&SDL_BUTTON_LMASK;
|
||||
const bool new_key_down = key[KEY_SPACE] || key[KEY_ENTER] ||
|
||||
key[KEY_ESCAPE];
|
||||
|
||||
const bool new_up_arrow = key[KEY_UP];
|
||||
const bool new_down_arrow = key[KEY_DOWN];
|
||||
|
||||
const bool new_page_up = key[SDLK_PAGEUP];
|
||||
const bool new_page_down = key[SDLK_PAGEDOWN];
|
||||
|
||||
|
||||
if(!key_down && key[KEY_ENTER] &&
|
||||
(type == YES_NO || type == OK_CANCEL)) {
|
||||
if(menu_.height() == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return menu_.selection();
|
||||
}
|
||||
}
|
||||
|
||||
if(menu_.selection() != cur_selection || first_time) {
|
||||
cur_selection = menu_.selection();
|
||||
|
||||
int selection = cur_selection;
|
||||
if(first_time)
|
||||
selection = 0;
|
||||
|
||||
if(selection >= 0 && selection < units.size()) {
|
||||
SDL_Surface* const screen = disp.video().getSurface();
|
||||
if(unit_details_rect.w > 0) {
|
||||
SDL_FillRect(screen,&unit_details_rect,0);
|
||||
disp.update_rect(unit_details_rect);
|
||||
}
|
||||
|
||||
if(unit_profile_rect.w > 0) {
|
||||
SDL_FillRect(screen,&unit_profile_rect,0);
|
||||
disp.update_rect(unit_profile_rect);
|
||||
}
|
||||
|
||||
disp.draw_unit_details(unitx+left_padding,
|
||||
unity+top_padding, gamemap::location(), units[selection],
|
||||
unit_details_rect, unit_profile_rect);
|
||||
disp.update_display();
|
||||
}
|
||||
}
|
||||
|
||||
first_time = false;
|
||||
|
||||
if(menu_.height() > 0) {
|
||||
const int res = menu_.process(mousex,mousey,new_left_button,
|
||||
!up_arrow && new_up_arrow,
|
||||
!down_arrow && new_down_arrow,
|
||||
!page_up && new_page_up,
|
||||
!page_down && new_page_down);
|
||||
if(res != -1)
|
||||
return res;
|
||||
}
|
||||
|
||||
up_arrow = new_up_arrow;
|
||||
down_arrow = new_down_arrow;
|
||||
page_up = new_page_up;
|
||||
page_down = new_page_down;
|
||||
|
||||
if(use_textbox) {
|
||||
text_widget.process();
|
||||
}
|
||||
|
||||
|
||||
if(buttons.empty() && (new_left_button && !left_button ||
|
||||
new_right_button && !right_button) ||
|
||||
buttons.size() < 2 && new_key_down && !key_down &&
|
||||
menu_.height() == 0)
|
||||
break;
|
||||
|
||||
left_button = new_left_button;
|
||||
right_button = new_right_button;
|
||||
key_down = new_key_down;
|
||||
|
||||
for(std::vector<button>::iterator button_it = buttons.begin();
|
||||
button_it != buttons.end(); ++button_it) {
|
||||
if(button_it->process(mousex,mousey,left_button)) {
|
||||
if(text_widget_text != NULL && use_textbox)
|
||||
*text_widget_text = text_widget.text();
|
||||
|
||||
//if the menu is not used, then return the index of the
|
||||
//button pressed, otherwise return the index of the menu
|
||||
//item selected if the last button is not pressed, and
|
||||
//cancel (-1) otherwise
|
||||
if(menu_.height() == 0) {
|
||||
return button_it - buttons.begin();
|
||||
} else if(buttons.size() <= 1 ||
|
||||
button_it - buttons.begin() != buttons.size()-1) {
|
||||
return menu_.selection();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
TITLE_RESULT show_title(display& screen)
|
||||
{
|
||||
SDL_Surface* const title_surface = screen.getImage("title.png",
|
||||
display::UNSCALED);
|
||||
|
||||
if(title_surface == NULL) {
|
||||
std::cerr << "Could not find title image 'title.png'\n";
|
||||
return QUIT_GAME;
|
||||
}
|
||||
|
||||
const int x = screen.x()/2 - title_surface->w/2;
|
||||
const int y = 100;
|
||||
|
||||
screen.blit_surface(x,y,title_surface);
|
||||
|
||||
button tutorial_button(screen,string_table["tutorial_button"]);
|
||||
button new_button(screen,string_table["campaign_button"]);
|
||||
button load_button(screen,string_table["load_button"]);
|
||||
button multi_button(screen,string_table["multiplayer_button"]);
|
||||
button quit_button(screen,string_table["quit_button"]);
|
||||
button language_button(screen,string_table["language_button"]);
|
||||
|
||||
tutorial_button.set_x(700);
|
||||
new_button.set_x(700);
|
||||
load_button.set_x(700);
|
||||
multi_button.set_x(700);
|
||||
quit_button.set_x(700);
|
||||
language_button.set_x(700);
|
||||
|
||||
tutorial_button.set_y(200);
|
||||
new_button.set_y(250);
|
||||
load_button.set_y(300);
|
||||
multi_button.set_y(350);
|
||||
language_button.set_y(400);
|
||||
quit_button.set_y(450);
|
||||
|
||||
bool right_button = true;
|
||||
bool left_button = true;
|
||||
|
||||
tutorial_button.draw();
|
||||
new_button.draw();
|
||||
load_button.draw();
|
||||
multi_button.draw();
|
||||
quit_button.draw();
|
||||
language_button.draw();
|
||||
screen.video().update(0,0,1027,768);
|
||||
|
||||
CKey key;
|
||||
|
||||
for(;;) {
|
||||
int mousex, mousey;
|
||||
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
|
||||
|
||||
const bool right_button = mouse_flags&SDL_BUTTON_RMASK;
|
||||
const bool left_button = mouse_flags&SDL_BUTTON_LMASK;
|
||||
|
||||
if(tutorial_button.process(mousex,mousey,left_button))
|
||||
return TUTORIAL;
|
||||
|
||||
if(new_button.process(mousex,mousey,left_button))
|
||||
return NEW_CAMPAIGN;
|
||||
|
||||
if(load_button.process(mousex,mousey,left_button))
|
||||
return LOAD_GAME;
|
||||
|
||||
if(multi_button.process(mousex,mousey,left_button))
|
||||
return MULTIPLAYER;
|
||||
|
||||
if(quit_button.process(mousex,mousey,left_button))
|
||||
return QUIT_GAME;
|
||||
|
||||
if(key[KEY_ESCAPE])
|
||||
return QUIT_GAME;
|
||||
|
||||
if(language_button.process(mousex,mousey,left_button))
|
||||
return CHANGE_LANGUAGE;
|
||||
|
||||
SDL_PumpEvents();
|
||||
|
||||
SDL_Delay(20);
|
||||
}
|
||||
|
||||
return QUIT_GAME;
|
||||
}
|
||||
|
||||
void check_quit(display& gui)
|
||||
{
|
||||
CKey key;
|
||||
if(key[KEY_ESCAPE]) {
|
||||
const int res = gui::show_dialog(gui,NULL,"",
|
||||
string_table["quit_message"],gui::YES_NO);
|
||||
if(res == 0) {
|
||||
throw end_level_exception(QUIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} //end namespace gui
|
57
menu.hpp
Normal file
57
menu.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef MENU_HPP_INCLUDED
|
||||
#define MENU_HPP_INCLUDED
|
||||
|
||||
#include "display.hpp"
|
||||
#include "SDL.h"
|
||||
#include "unit.hpp"
|
||||
#include "video.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gui
|
||||
{
|
||||
|
||||
void draw_dialog_frame(int x, int y, int w, int h, display& disp);
|
||||
|
||||
void draw_rectangle(int x, int y, int w, int h, short colour, SDL_Surface* tg);
|
||||
|
||||
void draw_solid_tinted_rectangle(int x, int y, int w, int h,
|
||||
int r, int g, int b,
|
||||
double alpha, SDL_Surface* target);
|
||||
|
||||
enum DIALOG_TYPE { MESSAGE, OK_ONLY, YES_NO, OK_CANCEL };
|
||||
|
||||
//if a menu is given, then returns -1 if the dialog was cancelled, and the
|
||||
//index of the selection otherwise. If no menu is given, returns the index
|
||||
//of the button that was pressed
|
||||
int show_dialog(display& screen, SDL_Surface* image,
|
||||
const std::string& caption, const std::string& message,
|
||||
DIALOG_TYPE type=MESSAGE,
|
||||
const std::vector<std::string>* menu_items=NULL,
|
||||
const std::vector<unit>* units=NULL,
|
||||
const std::string& text_widget_label="",
|
||||
std::string* text_widget_text=NULL
|
||||
);
|
||||
|
||||
enum TITLE_RESULT { TUTORIAL, NEW_CAMPAIGN, MULTIPLAYER, LOAD_GAME, QUIT_GAME,
|
||||
CHANGE_LANGUAGE };
|
||||
|
||||
TITLE_RESULT show_title(display& screen);
|
||||
|
||||
void check_quit(display& screen);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
103
merge_translations.cpp
Normal file
103
merge_translations.cpp
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if(argc != 3) {
|
||||
std::cerr << "Usage: " << argv[0]
|
||||
<< " translation default-translation\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::string& data1 = read_file(argv[1]);
|
||||
const std::string& data2 = read_file(argv[2]);
|
||||
|
||||
if(data1.empty()) {
|
||||
std::cerr << "Could not read '" << argv[1] << "'\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(data2.empty()) {
|
||||
std::cerr << "Could not read '" << argv[2] << "'\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
config cfg;
|
||||
|
||||
try {
|
||||
cfg.read(data1);
|
||||
} catch(config::error& e) {
|
||||
std::cerr << "error parsing '" << argv[1] << "': " << e.message << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<config*> translations = cfg.children["language"];
|
||||
if(translations.empty()) {
|
||||
std::cerr << "no translation data found in '" << argv[1] << "'\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(translations.size() > 1) {
|
||||
std::cerr << "warning: found multiple translations in '" << argv[1]
|
||||
<< "'\n";
|
||||
}
|
||||
|
||||
const std::map<std::string,std::string> strings = translations[0]->values;
|
||||
|
||||
try {
|
||||
cfg.read(data2);
|
||||
} catch(config::error& e) {
|
||||
std::cerr << "error parsing '" << argv[2] << "': " << e.message << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
translations = cfg.children["language"];
|
||||
if(translations.empty()) {
|
||||
std::cerr << "no translation data found in '" << argv[2] << "'\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(translations.size() > 1) {
|
||||
std::cerr << "warning: found multiple translations in '" << argv[2]
|
||||
<< "'\n";
|
||||
}
|
||||
|
||||
const std::map<std::string,std::string> default_strings =
|
||||
translations[0]->values;
|
||||
|
||||
std::cout << "[language]\n\n"
|
||||
<< "#strings that were not found in the translation, \n"
|
||||
<< "#and which should be translated now:\n";
|
||||
|
||||
for(std::map<std::string,std::string>::const_iterator i =
|
||||
default_strings.begin(); i != default_strings.end(); ++i) {
|
||||
if(strings.find(i->first) == strings.end()) {
|
||||
std::cout << i->first << "=\"" << i->second << "\"\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n#---------------------------------"
|
||||
<< "\n#strings that have already been translated\n";
|
||||
|
||||
for(std::map<std::string,std::string>::const_iterator i = strings.begin();
|
||||
i != strings.end(); ++i) {
|
||||
std::cout << i->first << "=\"" << i->second << "\"\n";
|
||||
}
|
||||
|
||||
std::cout << "[/language]\n";
|
||||
return 0;
|
||||
}
|
121
multiplayer.cpp
Normal file
121
multiplayer.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "language.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "multiplayer.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "replay.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
void play_multiplayer(display& disp, game_data& units_data, config& cfg,
|
||||
game_state& state)
|
||||
{
|
||||
std::vector<std::string> options;
|
||||
std::vector<config*>& levels = cfg.children["multiplayer"];
|
||||
for(std::vector<config*>::iterator i = levels.begin(); i!=levels.end();++i){
|
||||
const std::string& lang_name = string_table[(*i)->values["id"]];
|
||||
if(lang_name.empty() == false)
|
||||
options.push_back(lang_name);
|
||||
else
|
||||
options.push_back((*i)->values["name"]);
|
||||
}
|
||||
|
||||
int res = gui::show_dialog(disp,NULL,"",
|
||||
string_table["choose_scenario"],gui::OK_CANCEL,
|
||||
&options);
|
||||
if(res == -1)
|
||||
return;
|
||||
|
||||
config& level = *levels[res];
|
||||
state.label = level.values["name"];
|
||||
|
||||
state.scenario = res;
|
||||
|
||||
std::vector<config*>& sides = level.children["side"];
|
||||
std::vector<config*>& possible_sides = cfg.children["multiplayer_side"];
|
||||
if(sides.empty() || possible_sides.empty()) {
|
||||
std::cerr << "no multiplayer sides found\n";
|
||||
return;
|
||||
}
|
||||
|
||||
for(std::vector<config*>::iterator sd = sides.begin();
|
||||
sd != sides.end(); ++sd) {
|
||||
(*sd)->values["name"] = possible_sides.front()->values["name"];
|
||||
(*sd)->values["type"] = possible_sides.front()->values["type"];
|
||||
(*sd)->values["recruit"] = possible_sides.front()->values["recruit"];
|
||||
}
|
||||
|
||||
res = 0;
|
||||
while(res != sides.size()) {
|
||||
std::vector<std::string> sides_list;
|
||||
for(std::vector<config*>::iterator sd = sides.begin();
|
||||
sd != sides.end(); ++sd) {
|
||||
std::stringstream details;
|
||||
details << (*sd)->values["side"] << ","
|
||||
<< (*sd)->values["name"] << ","
|
||||
<< ((*sd)->values["controller"] == "human" ?
|
||||
string_table["human_controlled"] :
|
||||
string_table["ai_controlled"]);
|
||||
sides_list.push_back(details.str());
|
||||
}
|
||||
|
||||
sides_list.push_back(string_table["start_game"]);
|
||||
|
||||
res = gui::show_dialog(disp,NULL,"",string_table["configure_sides"],
|
||||
gui::MESSAGE,&sides_list);
|
||||
|
||||
if(res >= 0 && res < sides.size()) {
|
||||
std::vector<std::string> choices;
|
||||
|
||||
for(int n = 0; n != 2; ++n) {
|
||||
for(std::vector<config*>::iterator i = possible_sides.begin();
|
||||
i != possible_sides.end(); ++i) {
|
||||
std::stringstream choice;
|
||||
choice << (*i)->values["name"] << " - "
|
||||
<< (n == 0 ? string_table["human_controlled"] :
|
||||
string_table["ai_controlled"]);
|
||||
choices.push_back(choice.str());
|
||||
}
|
||||
}
|
||||
|
||||
int result = gui::show_dialog(disp,NULL,"",
|
||||
string_table["choose_side"],
|
||||
gui::MESSAGE,&choices);
|
||||
if(result >= 0) {
|
||||
sides[res]->values["controller"] = (result >= choices.size()/2)
|
||||
? "ai" : "human";
|
||||
if(result >= choices.size()/2)
|
||||
result -= choices.size()/2;
|
||||
|
||||
assert(result < possible_sides.size());
|
||||
|
||||
std::map<std::string,std::string>& values =
|
||||
possible_sides[result]->values;
|
||||
sides[res]->values["name"] = values["name"];
|
||||
sides[res]->values["type"] = values["type"];
|
||||
sides[res]->values["recruit"] = values["recruit"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.starting_pos = level;
|
||||
|
||||
recorder.set_save_info(state);
|
||||
|
||||
std::vector<config*> story;
|
||||
play_level(units_data,cfg,&level,disp.video(),state,story);
|
||||
recorder.clear();
|
||||
}
|
24
multiplayer.hpp
Normal file
24
multiplayer.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef MULTIPLAYER_HPP_INCLUDED
|
||||
#define MULTIPLAYER_HPP_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "display.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "unit_types.hpp"
|
||||
#include "video.hpp"
|
||||
|
||||
void play_multiplayer(display& disp, game_data& units_data,
|
||||
config& cfg, game_state& state);
|
||||
|
||||
#endif
|
237
pathfind.cpp
Normal file
237
pathfind.cpp
Normal file
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "pathfind.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
|
||||
namespace {
|
||||
gamemap::location find_vacant(const gamemap& map,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, int depth,
|
||||
gamemap::TERRAIN terrain,
|
||||
std::set<gamemap::location>& touched)
|
||||
{
|
||||
if(touched.count(loc))
|
||||
return gamemap::location();
|
||||
|
||||
touched.insert(loc);
|
||||
|
||||
if(map.on_board(loc) && units.find(loc) == units.end() &&
|
||||
map[loc.x][loc.y] != gamemap::TOWER &&
|
||||
(terrain == 0 || terrain == map[loc.x][loc.y])) {
|
||||
return loc;
|
||||
} else if(depth == 0) {
|
||||
return gamemap::location();
|
||||
} else {
|
||||
gamemap::location adj[6];
|
||||
get_adjacent_tiles(loc,adj);
|
||||
for(int i = 0; i != 6; ++i) {
|
||||
if(!map.on_board(adj[i]) ||
|
||||
terrain != 0 && terrain != map[adj[i].x][adj[i].y])
|
||||
continue;
|
||||
|
||||
const gamemap::location res =
|
||||
find_vacant(map,units,adj[i],depth-1,terrain,touched);
|
||||
|
||||
if(map.on_board(res))
|
||||
return res;
|
||||
}
|
||||
|
||||
return gamemap::location();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
gamemap::location find_vacant_tile(const gamemap& map,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
gamemap::TERRAIN terrain)
|
||||
{
|
||||
for(int i = 1; i != 50; ++i) {
|
||||
std::set<gamemap::location> touch;
|
||||
const gamemap::location res = find_vacant(map,units,loc,i,terrain,touch);
|
||||
if(map.on_board(res))
|
||||
return res;
|
||||
}
|
||||
|
||||
return gamemap::location();
|
||||
}
|
||||
|
||||
void get_adjacent_tiles(const gamemap::location& a, gamemap::location* res)
|
||||
{
|
||||
res->x = a.x;
|
||||
res->y = a.y-1;
|
||||
++res;
|
||||
res->x = a.x+1;
|
||||
res->y = a.y + ((a.x%2) == 0 ? -1:0);
|
||||
++res;
|
||||
res->x = a.x+1;
|
||||
res->y = a.y + ((a.x%2) == 0 ? 0:1);
|
||||
++res;
|
||||
res->x = a.x;
|
||||
res->y = a.y+1;
|
||||
++res;
|
||||
res->x = a.x-1;
|
||||
res->y = a.y + ((a.x%2) == 0 ? 0:1);
|
||||
++res;
|
||||
res->x = a.x-1;
|
||||
res->y = a.y + ((a.x%2) == 0 ? -1:0);
|
||||
}
|
||||
|
||||
bool tiles_adjacent(const gamemap::location& a, const gamemap::location& b)
|
||||
{
|
||||
//two tiles are adjacent if y is different by 1, and x by 0, or if
|
||||
//x is different by 1 and y by 0, or if x and y are each different by 1,
|
||||
//and the x value of the hex with the greater y value is odd
|
||||
|
||||
const int xdiff = abs(a.x - b.x);
|
||||
const int ydiff = abs(a.y - b.y);
|
||||
return ydiff == 1 && a.x == b.x || xdiff == 1 && a.y == b.y ||
|
||||
xdiff == 1 && ydiff == 1 && (a.y > b.y ? (a.x%2) == 1 : (b.x%2) == 1);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool enemy_zoc(const gamemap& map,const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, const team& current_team, int side)
|
||||
{
|
||||
gamemap::location locs[6];
|
||||
get_adjacent_tiles(loc,locs);
|
||||
for(int i = 0; i != 6; ++i) {
|
||||
const std::map<gamemap::location,unit>::const_iterator it
|
||||
= units.find(locs[i]);
|
||||
if(it != units.end() && it->second.side() != side &&
|
||||
current_team.is_enemy(it->second.side()) &&
|
||||
!it->second.invisible(map.underlying_terrain(
|
||||
map[it->first.x][it->first.y]))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void find_routes(const gamemap& map, const game_data& gamedata,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const unit& u,
|
||||
const gamemap::location& loc,
|
||||
int move_left,
|
||||
std::map<gamemap::location,paths::route>& routes,
|
||||
std::vector<team>& teams,
|
||||
bool ignore_zocs, bool allow_teleport)
|
||||
{
|
||||
//find adjacent tiles
|
||||
std::vector<gamemap::location> locs(6);
|
||||
get_adjacent_tiles(loc,&locs[0]);
|
||||
|
||||
//check for teleporting units
|
||||
if(allow_teleport && map[loc.x][loc.y] == gamemap::TOWER) {
|
||||
const std::vector<gamemap::location>& towers = map.towers();
|
||||
|
||||
//if we are on a tower, see all friendly towers that we can
|
||||
//teleport to
|
||||
for(std::vector<gamemap::location>::const_iterator t = towers.begin();
|
||||
t != towers.end(); ++t) {
|
||||
if(!teams[u.side()-1].owns_tower(*t) ||
|
||||
units.find(*t) != units.end())
|
||||
continue;
|
||||
|
||||
locs.push_back(*t);
|
||||
}
|
||||
}
|
||||
|
||||
//iterate over all adjacent tiles
|
||||
for(int i = 0; i != locs.size(); ++i) {
|
||||
const gamemap::location& currentloc = locs[i];
|
||||
|
||||
//check if the adjacent location is off the board
|
||||
if(currentloc.x < 0 || currentloc.y < 0 ||
|
||||
currentloc.x >= map.x() || currentloc.y >= map.y())
|
||||
continue;
|
||||
|
||||
//see if the tile is on top of an enemy unit
|
||||
const std::map<gamemap::location,unit>::const_iterator unit_it =
|
||||
units.find(locs[i]);
|
||||
if(unit_it != units.end() && unit_it->second.side() != u.side())
|
||||
continue;
|
||||
|
||||
//find the terrain of the adjacent location
|
||||
const gamemap::TERRAIN terrain = map[currentloc.x][currentloc.y];
|
||||
|
||||
//find the movement cost of this type onto the terrain
|
||||
const int move_cost = u.movement_cost(map,terrain);
|
||||
if(move_cost <= move_left) {
|
||||
const std::map<gamemap::location,paths::route>::const_iterator
|
||||
rtit = routes.find(currentloc);
|
||||
|
||||
//if a better route to that tile has already been found
|
||||
if(rtit != routes.end() &&
|
||||
rtit->second.move_left >= move_left - move_cost)
|
||||
continue;
|
||||
|
||||
const bool zoc = enemy_zoc(map,units,currentloc,
|
||||
teams[u.side()-1],u.side()) &&
|
||||
!ignore_zocs;
|
||||
paths::route new_route = routes[loc];
|
||||
new_route.steps.push_back(loc);
|
||||
new_route.move_left = zoc ? 0 : move_left - move_cost;
|
||||
routes[currentloc] = new_route;
|
||||
|
||||
if(new_route.move_left > 0) {
|
||||
find_routes(map,gamedata,units,u,currentloc,
|
||||
new_route.move_left,routes,teams,ignore_zocs,
|
||||
allow_teleport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} //end anon namespace
|
||||
|
||||
paths::paths(const gamemap& map, const game_data& gamedata,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
std::vector<team>& teams,
|
||||
bool ignore_zocs, bool allow_teleport)
|
||||
{
|
||||
const std::map<gamemap::location,unit>::const_iterator i = units.find(loc);
|
||||
if(i == units.end()) {
|
||||
std::cerr << "unit not found\n";
|
||||
return;
|
||||
}
|
||||
|
||||
routes[loc].move_left = i->second.movement_left();
|
||||
find_routes(map,gamedata,units,i->second,loc,
|
||||
i->second.movement_left(),routes,teams,
|
||||
ignore_zocs,allow_teleport);
|
||||
|
||||
if(i->second.can_attack()) {
|
||||
gamemap::location adjacent[6];
|
||||
get_adjacent_tiles(loc,adjacent);
|
||||
for(int j = 0; j != 6; ++j) {
|
||||
const std::map<gamemap::location,unit>::const_iterator enemy =
|
||||
units.find(adjacent[j]);
|
||||
if(enemy != units.end() &&
|
||||
enemy->second.side() != i->second.side() &&
|
||||
teams[i->second.side()-1].is_enemy(enemy->second.side())) {
|
||||
route new_route;
|
||||
new_route.move_left = -1;
|
||||
routes.insert(std::pair<gamemap::location,route>(
|
||||
adjacent[j],new_route));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
152
pathfind.hpp
Normal file
152
pathfind.hpp
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef PATHFIND_H_INCLUDED
|
||||
#define PATHFIND_H_INCLUDED
|
||||
|
||||
#include "map.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
void get_adjacent_tiles(const gamemap::location& a, gamemap::location* res);
|
||||
bool tiles_adjacent(const gamemap::location& a, const gamemap::location& b);
|
||||
|
||||
gamemap::location find_vacant_tile(const gamemap& map,
|
||||
const std::map<gamemap::location,unit>& un,
|
||||
const gamemap::location& loc,
|
||||
gamemap::TERRAIN terrain=0);
|
||||
|
||||
struct paths
|
||||
{
|
||||
paths() {}
|
||||
paths(const gamemap& map, const game_data& gamedata,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, std::vector<team>& teams,
|
||||
bool ignore_zocs, bool allow_teleport);
|
||||
|
||||
struct route
|
||||
{
|
||||
route() : move_left(0) {}
|
||||
std::vector<gamemap::location> steps;
|
||||
int move_left;
|
||||
};
|
||||
|
||||
std::map<gamemap::location,route> routes;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
struct node {
|
||||
node(const gamemap::location& pos, const gamemap::location& dst,
|
||||
double cost, node* parent)
|
||||
: parent(parent), loc(pos), g(cost),
|
||||
h(sqrt(pow(abs(dst.x-pos.x),2) + pow(abs(dst.y-pos.y),2)))
|
||||
{
|
||||
f = g + h;
|
||||
}
|
||||
|
||||
node* parent;
|
||||
gamemap::location loc;
|
||||
double g, h, f;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
paths::route a_star_search(const gamemap::location& src,
|
||||
const gamemap::location& dst, double stop_at, T obj)
|
||||
{
|
||||
std::cout << "a* search: " << src.x << ", " << src.y << " - " << dst.x << ", " << dst.y << "\n";
|
||||
using namespace detail;
|
||||
typedef gamemap::location location;
|
||||
std::list<node> open_list, closed_list;
|
||||
|
||||
open_list.push_back(node(src,dst,0.0,NULL));
|
||||
|
||||
while(!open_list.empty()) {
|
||||
|
||||
//find the lowest estimated cost node on the open list
|
||||
std::list<node>::iterator lowest = open_list.end(), i;
|
||||
for(i = open_list.begin(); i != open_list.end(); ++i) {
|
||||
if(lowest == open_list.end() || i->f < lowest->f) {
|
||||
lowest = i;
|
||||
}
|
||||
}
|
||||
|
||||
if(lowest->f > stop_at) {
|
||||
break;
|
||||
}
|
||||
|
||||
//std::cerr << "processing " << (lowest->loc.x+1) << "," << (lowest->loc.y+1) << " with cost = " << lowest->g << " (known) + " << lowest->h << " (estimated) = " << lowest->f << "\n";
|
||||
|
||||
//move the lowest element from the open list to the closed list
|
||||
closed_list.splice(closed_list.begin(),open_list,lowest);
|
||||
|
||||
//find nodes we can go to from this node
|
||||
location locs[6];
|
||||
get_adjacent_tiles(lowest->loc,locs);
|
||||
for(int j = 0; j != 6; ++j) {
|
||||
|
||||
//if we have found a solution
|
||||
if(locs[j] == dst) {
|
||||
paths::route rt;
|
||||
for(node* n = &*lowest; n != NULL; n = n->parent) {
|
||||
rt.steps.push_back(n->loc);
|
||||
}
|
||||
|
||||
std::reverse(rt.steps.begin(),rt.steps.end());
|
||||
rt.steps.push_back(dst);
|
||||
rt.move_left = int(lowest->f);
|
||||
|
||||
std::cout << "exiting a* search (solved)\n";
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
const node nd(locs[j],dst,lowest->g+obj.cost(locs[j]),&*lowest);
|
||||
|
||||
for(i = open_list.begin(); i != open_list.end(); ++i) {
|
||||
if(i->loc == nd.loc && i->f <= nd.f) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(i != open_list.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for(i = closed_list.begin(); i != closed_list.end(); ++i) {
|
||||
if(i != lowest && i->loc == nd.loc && i->f <= nd.f) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(i != closed_list.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
open_list.push_back(nd);
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "aborted a* search\n";
|
||||
paths::route val;
|
||||
val.move_left = 100000;
|
||||
return val;
|
||||
}
|
||||
|
||||
#endif
|
292
playlevel.cpp
Normal file
292
playlevel.cpp
Normal file
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "game_events.hpp"
|
||||
#include "intro.hpp"
|
||||
#include "language.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "playturn.hpp"
|
||||
#include "preferences.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "sound.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
LEVEL_RESULT play_level(game_data& gameinfo, config& terrain_config,
|
||||
config* level, CVideo& video,
|
||||
game_state& state_of_game,
|
||||
std::vector<config*>& story)
|
||||
{
|
||||
const int num_turns = atoi(level->values["turns"].c_str());
|
||||
gamestatus status(num_turns);
|
||||
|
||||
gamemap map(terrain_config,read_file("data/maps/" + level->values["map"]));
|
||||
|
||||
CKey key;
|
||||
typedef std::map<gamemap::location,unit> units_map;
|
||||
units_map units;
|
||||
|
||||
std::vector<team> teams;
|
||||
|
||||
std::vector<config*>& unit_cfg = level->children["side"];
|
||||
for(std::vector<config*>::iterator ui = unit_cfg.begin();
|
||||
ui != unit_cfg.end(); ++ui) {
|
||||
unit new_unit(gameinfo, **ui);
|
||||
if(ui == unit_cfg.begin()) {
|
||||
for(std::vector<unit>::iterator it =
|
||||
state_of_game.available_units.begin();
|
||||
it != state_of_game.available_units.end(); ++it) {
|
||||
if(it->can_recruit()) {
|
||||
new_unit = *it;
|
||||
state_of_game.available_units.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string gold = (*ui)->values["gold"];
|
||||
if(gold.empty())
|
||||
gold = "100";
|
||||
|
||||
int ngold = ::atoi(gold.c_str());
|
||||
if(ui == unit_cfg.begin() && state_of_game.gold >= 0)
|
||||
ngold = state_of_game.gold;
|
||||
|
||||
units.insert(std::pair<gamemap::location,unit>(
|
||||
map.starting_position(new_unit.side()), new_unit));
|
||||
teams.push_back(team(**ui,ngold));
|
||||
|
||||
//if there are additional starting units on this side
|
||||
std::vector<config*>& starting_units = (*ui)->children["unit"];
|
||||
for(std::vector<config*>::iterator su = starting_units.begin();
|
||||
su != starting_units.end(); ++su) {
|
||||
unit new_unit(gameinfo,**su);
|
||||
const std::string& x = (*su)->values["x"];
|
||||
const std::string& y = (*su)->values["y"];
|
||||
|
||||
const gamemap::location loc(**su);
|
||||
if(x.size() == 0 || y.size() == 0 || !map.on_board(loc)) {
|
||||
state_of_game.available_units.push_back(new_unit);
|
||||
} else {
|
||||
units.insert(std::pair<gamemap::location,unit>(loc,new_unit));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
display gui(units,video,map,status,teams);
|
||||
const preferences::display_manager prefs_disp_manager(&gui);
|
||||
|
||||
if(recorder.skipping() == false) {
|
||||
for(std::vector<config*>::iterator story_i = story.begin();
|
||||
story_i != story.end(); ++story_i) {
|
||||
show_intro(gui,**story_i);
|
||||
}
|
||||
|
||||
show_map_scene(gui,*level);
|
||||
}
|
||||
|
||||
const std::string& music = level->values["music"];
|
||||
if(!music.empty()) {
|
||||
sound::play_music(music);
|
||||
}
|
||||
|
||||
game_events::manager events_manager(*level,gui,map,units,
|
||||
state_of_game,gameinfo);
|
||||
|
||||
//find a list of 'items' (i.e. overlays) on the level, and add them
|
||||
std::vector<config*>& overlays = level->children["item"];
|
||||
for(std::vector<config*>::iterator overlay = overlays.begin();
|
||||
overlay != overlays.end(); ++overlay) {
|
||||
gui.add_overlay(gamemap::location(**overlay),
|
||||
(*overlay)->values["image"]);
|
||||
}
|
||||
|
||||
const double scroll_speed = 30.0;
|
||||
const double zoom_amount = 5.0;
|
||||
|
||||
for(units_map::iterator i = units.begin(); i != units.end(); ++i) {
|
||||
i->second.new_turn();
|
||||
}
|
||||
|
||||
bool left_button = false, right_button = false;
|
||||
|
||||
gamemap::location selected_hex;
|
||||
|
||||
gui.scroll_to_tile(map.starting_position(1).x,map.starting_position(1).y,
|
||||
display::WARP);
|
||||
|
||||
bool replaying = (recorder.empty() == false);
|
||||
|
||||
std::cout << "starting main loop\n";
|
||||
for(bool first_time = true; true; first_time = false) {
|
||||
try {
|
||||
|
||||
if(first_time) {
|
||||
update_locker lock_display(gui,recorder.skipping());
|
||||
game_events::fire("start");
|
||||
}
|
||||
|
||||
gui.invalidate_game_status();
|
||||
|
||||
for(std::vector<team>::iterator team_it = teams.begin();
|
||||
team_it != teams.end(); ++team_it) {
|
||||
const int player_number = (team_it - teams.begin()) + 1;
|
||||
|
||||
calculate_healing(gui,map,units,player_number);
|
||||
|
||||
//scroll the map to the leader
|
||||
const units_map::iterator leader =
|
||||
find_leader(units,player_number);
|
||||
|
||||
if(leader != units.end() && !recorder.skipping()) {
|
||||
gui.scroll_to_tile(leader->first.x,leader->first.y);
|
||||
}
|
||||
|
||||
if(replaying) {
|
||||
replaying = do_replay(gui,map,gameinfo,units,teams,
|
||||
player_number,status,state_of_game);
|
||||
}
|
||||
|
||||
if(!replaying && team_it->is_human()) {
|
||||
play_turn(gameinfo,state_of_game,status,terrain_config,
|
||||
level, video, key, gui, events_manager, map,
|
||||
teams, player_number, units);
|
||||
|
||||
if(game_config::debug)
|
||||
display::clear_debug_highlights();
|
||||
|
||||
} else if(!replaying) {
|
||||
ai::do_move(gui,map,gameinfo,units,teams,
|
||||
player_number,status);
|
||||
|
||||
gui.invalidate_unit();
|
||||
gui.invalidate_game_status();
|
||||
gui.invalidate_all();
|
||||
gui.draw();
|
||||
SDL_Delay(1000);
|
||||
}
|
||||
|
||||
for(units_map::iterator uit = units.begin();
|
||||
uit != units.end(); ++uit) {
|
||||
if(uit->second.side() == player_number)
|
||||
uit->second.end_turn();
|
||||
}
|
||||
|
||||
game_events::pump();
|
||||
|
||||
const int victory = check_victory(units);
|
||||
if(victory > 1) {
|
||||
throw end_level_exception(DEFEAT);
|
||||
} else if(victory == 1) {
|
||||
throw end_level_exception(VICTORY);
|
||||
}
|
||||
}
|
||||
|
||||
//time has run out
|
||||
if(!status.next_turn()) {
|
||||
game_events::fire("time over");
|
||||
throw end_level_exception(DEFEAT);
|
||||
}
|
||||
|
||||
std::stringstream event_stream;
|
||||
event_stream << "turn " << status.turn();
|
||||
|
||||
{
|
||||
update_locker lock_display(gui,recorder.skipping());
|
||||
game_events::fire(event_stream.str());
|
||||
}
|
||||
|
||||
std::map<int,int> expenditure;
|
||||
for(units_map::iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
i->second.new_turn();
|
||||
expenditure[i->second.side()]++;
|
||||
}
|
||||
|
||||
int team_num = 1;
|
||||
for(std::vector<team>::iterator it = teams.begin();
|
||||
it != teams.end(); ++it, ++team_num) {
|
||||
it->new_turn();
|
||||
it->spend_gold(expenditure[team_num]);
|
||||
}
|
||||
} catch(end_level_exception& end_level) {
|
||||
|
||||
if(end_level.result == QUIT || end_level.result == REPLAY) {
|
||||
return end_level.result;
|
||||
} else if(end_level.result == DEFEAT) {
|
||||
try {
|
||||
game_events::fire("defeat");
|
||||
} catch(end_level_exception&) {
|
||||
}
|
||||
|
||||
gui::show_dialog(gui,NULL,
|
||||
string_table["defeat_heading"],
|
||||
string_table["defeat_message"],
|
||||
gui::OK_ONLY);
|
||||
return DEFEAT;
|
||||
} else if(end_level.result == VICTORY) {
|
||||
try {
|
||||
game_events::fire("victory");
|
||||
} catch(end_level_exception&) {
|
||||
}
|
||||
|
||||
//add all the units that survived the scenario
|
||||
for(std::map<gamemap::location,unit>::iterator un =
|
||||
units.begin(); un != units.end(); ++un) {
|
||||
if(un->second.side() == 1) {
|
||||
un->second.new_level();
|
||||
state_of_game.available_units.
|
||||
push_back(un->second);
|
||||
}
|
||||
}
|
||||
|
||||
const int remaining_gold = teams[0].gold();
|
||||
const int finishing_bonus_per_turn =
|
||||
map.towers().size()*game_config::tower_income;
|
||||
const int turns_left = status.number_of_turns() - status.turn();
|
||||
const int finishing_bonus = end_level.gold_bonus ?
|
||||
(finishing_bonus_per_turn * turns_left) : 0;
|
||||
state_of_game.gold = (remaining_gold+finishing_bonus)/2;
|
||||
|
||||
gui::show_dialog(gui,NULL,string_table["victory_heading"],
|
||||
string_table["victory_message"],gui::OK_ONLY);
|
||||
std::stringstream report;
|
||||
report << string_table["remaining_gold"] << ": "
|
||||
<< remaining_gold << "\n";
|
||||
if(end_level.gold_bonus) {
|
||||
report << string_table["early_finish_bonus"] << ": "
|
||||
<< finishing_bonus_per_turn
|
||||
<< " " << string_table["per_turn"] << "\n"
|
||||
<< string_table["turns_finished_early"] << ": "
|
||||
<< string_table["bonus"] << ": "
|
||||
<< finishing_bonus << "\n"
|
||||
<< string_table["gold"] << ": "
|
||||
<< (remaining_gold+finishing_bonus);
|
||||
}
|
||||
|
||||
report << "\n" << string_table["fifty_percent"] << "\n"
|
||||
<< string_table["retained_gold"] << ": "
|
||||
<< state_of_game.gold;
|
||||
|
||||
gui::show_dialog(gui,NULL,"",report.str(),gui::OK_ONLY);
|
||||
return VICTORY;
|
||||
}
|
||||
} //end catch
|
||||
catch(replay::error& e) {
|
||||
gui::show_dialog(gui,NULL,"","The file you loaded is corrupt "
|
||||
"or from a different version of the game",gui::OK_ONLY);
|
||||
return DEFEAT;
|
||||
}
|
||||
|
||||
} //end for(;;)
|
||||
|
||||
return QUIT;
|
||||
}
|
51
playlevel.hpp
Normal file
51
playlevel.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef PLAY_LEVEL_HPP_INCLUDED
|
||||
#define PLAY_LEVEL_HPP_INCLUDED
|
||||
|
||||
#include "actions.hpp"
|
||||
#include "ai.hpp"
|
||||
#include "config.hpp"
|
||||
#include "dialogs.hpp"
|
||||
#include "display.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "key.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "team.hpp"
|
||||
#include "unit_types.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "video.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
enum LEVEL_RESULT { VICTORY, DEFEAT, QUIT, REPLAY };
|
||||
|
||||
struct end_level_exception {
|
||||
end_level_exception(LEVEL_RESULT res, bool bonus=true)
|
||||
: result(res), gold_bonus(bonus)
|
||||
{}
|
||||
LEVEL_RESULT result;
|
||||
bool gold_bonus;
|
||||
};
|
||||
|
||||
LEVEL_RESULT play_level(game_data& gameinfo, config& terrain_config,
|
||||
config* level, CVideo& video,
|
||||
game_state& state_of_game,
|
||||
std::vector<config*>& story);
|
||||
|
||||
#endif
|
837
playturn.cpp
Normal file
837
playturn.cpp
Normal file
|
@ -0,0 +1,837 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "hotkeys.hpp"
|
||||
#include "language.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "playturn.hpp"
|
||||
#include "preferences.hpp"
|
||||
#include "replay.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
struct undo_action {
|
||||
undo_action(const std::vector<gamemap::location>& rt,int sm,int orig=-1)
|
||||
: route(rt), starting_moves(sm), original_village_owner(orig) {}
|
||||
std::vector<gamemap::location> route;
|
||||
int starting_moves;
|
||||
int original_village_owner;
|
||||
};
|
||||
|
||||
void play_turn(game_data& gameinfo, game_state& state_of_game,
|
||||
gamestatus& status, config& terrain_config, config* level,
|
||||
CVideo& video, CKey& key, display& gui,
|
||||
game_events::manager& events_manager, gamemap& map,
|
||||
std::vector<team>& teams, int team_num,
|
||||
std::map<gamemap::location,unit>& units)
|
||||
{
|
||||
gui.set_team(team_num-1);
|
||||
gui.invalidate_all();
|
||||
gui.draw();
|
||||
gui.update_display();
|
||||
|
||||
team& current_team = teams[team_num-1];
|
||||
|
||||
const double scroll_speed = 30.0;
|
||||
const double zoom_amount = 5.0;
|
||||
|
||||
const std::string menu_items[] = {"scenario_objectives","recruit",
|
||||
"recall","unit_list","save_game",
|
||||
"preferences","end_turn"};
|
||||
std::vector<std::string> menu;
|
||||
for(const std::string* menu_items_ptr = menu_items;
|
||||
menu_items_ptr != menu_items + sizeof(menu_items)/sizeof(*menu_items);
|
||||
++menu_items_ptr) {
|
||||
menu.push_back(string_table[*menu_items_ptr]);
|
||||
}
|
||||
|
||||
typedef std::map<gamemap::location,unit> units_map;
|
||||
|
||||
gamemap::location next_unit;
|
||||
|
||||
bool left_button = false, right_button = false;
|
||||
|
||||
const paths_wiper wiper(gui);
|
||||
paths current_paths;
|
||||
bool enemy_paths = false;
|
||||
|
||||
gamemap::location last_hex;
|
||||
gamemap::location selected_hex;
|
||||
|
||||
std::deque<undo_action> undo_stack, redo_stack;
|
||||
|
||||
for(;;) {
|
||||
int mousex, mousey;
|
||||
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
|
||||
const bool new_left_button = mouse_flags & SDL_BUTTON_LMASK;
|
||||
const bool new_right_button = mouse_flags & SDL_BUTTON_RMASK;
|
||||
|
||||
gamemap::location new_hex = gui.hex_clicked_on(mousex,mousey);
|
||||
|
||||
//highlight the hex that is currently moused over
|
||||
if(new_hex != last_hex) {
|
||||
gui.highlight_hex(new_hex);
|
||||
|
||||
if(enemy_paths && new_hex != last_hex) {
|
||||
gui.set_paths(NULL);
|
||||
current_paths = paths();
|
||||
enemy_paths = false;
|
||||
}
|
||||
}
|
||||
|
||||
HOTKEY_COMMAND command = HOTKEY_NULL;
|
||||
|
||||
if(new_left_button) {
|
||||
const gamemap::location loc =gui.minimap_location_on(mousex,mousey);
|
||||
if(loc.valid()) {
|
||||
gui.scroll_to_tile(loc.x,loc.y,display::WARP);
|
||||
}
|
||||
} else if(new_hex != last_hex && current_paths.routes.empty()) {
|
||||
const units_map::iterator u = units.find(new_hex);
|
||||
if(u != units.end() && u->second.side() != team_num) {
|
||||
const bool ignore_zocs = u->second.type().is_skirmisher();
|
||||
const bool teleport = u->second.type().teleports();
|
||||
current_paths = paths(map,gameinfo,units,new_hex,teams,
|
||||
ignore_zocs,teleport);
|
||||
gui.set_paths(¤t_paths);
|
||||
enemy_paths = true;
|
||||
}
|
||||
}
|
||||
|
||||
last_hex = new_hex;
|
||||
|
||||
if(!left_button && new_left_button) {
|
||||
const gamemap::location& hex = gui.hex_clicked_on(mousex,mousey);
|
||||
|
||||
units_map::iterator u = units.find(selected_hex);
|
||||
|
||||
//if we can move to that tile
|
||||
const std::map<gamemap::location,paths::route>::const_iterator
|
||||
route = enemy_paths ? current_paths.routes.end() :
|
||||
current_paths.routes.find(hex);
|
||||
units_map::iterator enemy = units.find(hex);
|
||||
|
||||
//see if we're trying to attack an enemy
|
||||
if(route != current_paths.routes.end() && enemy != units.end() &&
|
||||
hex != selected_hex &&
|
||||
enemy->second.side() != u->second.side()) {
|
||||
|
||||
const unit_type& type = u->second.type();
|
||||
const unit_type& enemy_type = enemy->second.type();
|
||||
const std::vector<attack_type>& attacks = u->second.attacks();
|
||||
std::vector<std::string> items;
|
||||
const std::vector<attack_type>& defends =
|
||||
enemy->second.attacks();
|
||||
|
||||
std::vector<unit> units_list;
|
||||
|
||||
for(int a = 0; a != attacks.size(); ++a) {
|
||||
const battle_stats stats = evaluate_battle_stats(
|
||||
map,selected_hex,hex,
|
||||
a,units,status,gameinfo);
|
||||
|
||||
const std::string& lang_attack_name =
|
||||
string_table["weapon_name_"+stats.attack_name];
|
||||
const std::string& lang_attack_type =
|
||||
string_table["weapon_type_"+stats.attack_type];
|
||||
const std::string& lang_range =
|
||||
string_table[stats.range == "Melee" ?
|
||||
"short_range" : "long_range"];
|
||||
|
||||
const std::string& lang_defend_name =
|
||||
string_table["weapon_name_"+stats.defend_name];
|
||||
const std::string& lang_defend_type =
|
||||
string_table["weapon_type_"+stats.defend_type];
|
||||
|
||||
const std::string& attack_name = lang_attack_name.empty() ?
|
||||
stats.attack_name : lang_attack_name;
|
||||
const std::string& attack_type = lang_attack_type.empty() ?
|
||||
stats.attack_type : lang_attack_type;
|
||||
const std::string& defend_name = lang_defend_name.empty() ?
|
||||
stats.defend_name : lang_defend_name;
|
||||
const std::string& defend_type = lang_defend_type.empty() ?
|
||||
stats.defend_type : lang_defend_type;
|
||||
|
||||
const std::string& range = lang_range.empty() ?
|
||||
stats.range : lang_range;
|
||||
|
||||
std::stringstream att;
|
||||
att << attack_name << " (" << attack_type
|
||||
<< ") " << stats.damage_defender_takes << "-"
|
||||
<< stats.nattacks << " " << range << " "
|
||||
<< int(ceil(100.0*stats.chance_to_hit_defender)) << "%";
|
||||
|
||||
att << "," << string_table["versus"] << ",";
|
||||
att << defend_name << " (" << defend_type
|
||||
<< ") " << stats.damage_attacker_takes << "-"
|
||||
<< stats.ndefends << " "
|
||||
<< int(ceil(100.0*stats.chance_to_hit_attacker)) << "%";
|
||||
|
||||
items.push_back(att.str());
|
||||
units_list.push_back(enemy->second);
|
||||
}
|
||||
|
||||
//make it so that when we attack an enemy, the attacking unit
|
||||
//is again shown in the status bar, so that we can easily
|
||||
//compare between the attacking and defending unit
|
||||
gui.highlight_hex(gamemap::location());
|
||||
gui.draw(true,true);
|
||||
|
||||
const int res = gui::show_dialog(gui,NULL,"",
|
||||
string_table["choose_weapon"]+":\n",
|
||||
gui::OK_CANCEL,&items,&units_list);
|
||||
|
||||
if(res >= 0 && res < attacks.size()) {
|
||||
undo_stack.clear();
|
||||
redo_stack.clear();
|
||||
|
||||
current_paths = paths();
|
||||
gui.set_paths(NULL);
|
||||
|
||||
game_events::fire("attack",selected_hex,hex);
|
||||
|
||||
//the event could have killed either the attacker or
|
||||
//defender, so we have to make sure they still exist
|
||||
u = units.find(selected_hex);
|
||||
enemy = units.find(hex);
|
||||
|
||||
if(u == units.end() || enemy == units.end() ||
|
||||
res >= u->second.attacks().size())
|
||||
continue;
|
||||
|
||||
gui.invalidate_all();
|
||||
gui.draw();
|
||||
|
||||
recorder.add_attack(selected_hex,hex,res);
|
||||
|
||||
attack(gui,map,selected_hex,hex,res,units,
|
||||
status,gameinfo,true);
|
||||
|
||||
dialogs::advance_unit(gameinfo,units,selected_hex,gui);
|
||||
dialogs::advance_unit(gameinfo,units,hex,gui);
|
||||
|
||||
selected_hex = gamemap::location();
|
||||
|
||||
gui.invalidate_unit();
|
||||
gui.draw(); //clear the screen
|
||||
|
||||
const int winner = check_victory(units);
|
||||
|
||||
//if the player has won
|
||||
if(winner == team_num) {
|
||||
throw end_level_exception(VICTORY);
|
||||
} else if(winner >= 1) {
|
||||
throw end_level_exception(DEFEAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//otherwise we're just trying to move to a hex
|
||||
else if(selected_hex.valid() && selected_hex != hex &&
|
||||
enemy == units.end() &&
|
||||
route != current_paths.routes.end()) {
|
||||
std::vector<gamemap::location> steps = route->second.steps;
|
||||
steps.push_back(hex);
|
||||
|
||||
//if an event mutates the game environment, then this
|
||||
//move must be marked un-redoable
|
||||
bool event_mutated = false;
|
||||
|
||||
int orig_tower_owner = -1;
|
||||
int starting_moves = 0;
|
||||
int unit_side = 0;
|
||||
if(u != units.end()) {
|
||||
unit un = u->second;
|
||||
starting_moves = un.movement_left();
|
||||
unit_side = un.side();
|
||||
units.erase(u);
|
||||
gui.move_unit(steps,un);
|
||||
recorder.add_movement(selected_hex,hex);
|
||||
un.set_movement(route->second.move_left);
|
||||
|
||||
//see if the unit has gotten a tower
|
||||
if(map[hex.x][hex.y] == gamemap::TOWER) {
|
||||
orig_tower_owner = tower_owner(hex,teams);
|
||||
get_tower(hex,teams,team_num-1);
|
||||
un.set_movement(0); //end of turn if you get a tower
|
||||
}
|
||||
|
||||
units.insert(std::pair<gamemap::location,unit>(hex,un));
|
||||
gui.invalidate_unit();
|
||||
|
||||
//fire the move to event. If the event mutates
|
||||
//the game state, then the user cannot undo the move
|
||||
event_mutated = game_events::fire("moveto",hex);
|
||||
} else {
|
||||
assert(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
selected_hex = gamemap::location();
|
||||
gui.select_hex(gamemap::location());
|
||||
gui.set_paths(NULL);
|
||||
current_paths = paths();
|
||||
|
||||
//if there is an enemy in a surrounding hex, then
|
||||
//highlight attack options
|
||||
gamemap::location adj[6];
|
||||
get_adjacent_tiles(steps.back(),adj);
|
||||
|
||||
int n;
|
||||
for(n = 0; n != 6; ++n) {
|
||||
const units_map::const_iterator u_it = units.find(adj[n]);
|
||||
if(u_it != units.end() && u_it->second.side() != unit_side
|
||||
&& current_team.is_enemy(u_it->second.side())){
|
||||
current_paths.routes[adj[n]] = paths::route();
|
||||
}
|
||||
}
|
||||
|
||||
if(current_paths.routes.empty() == false) {
|
||||
current_paths.routes[steps.back()] = paths::route();
|
||||
selected_hex = steps.back();
|
||||
gui.select_hex(steps.back());
|
||||
gui.set_paths(¤t_paths);
|
||||
}
|
||||
|
||||
undo_stack.push_back(undo_action(steps,starting_moves,
|
||||
orig_tower_owner));
|
||||
redo_stack.clear();
|
||||
|
||||
if(event_mutated) {
|
||||
undo_stack.clear();
|
||||
}
|
||||
|
||||
} else {
|
||||
gui.set_paths(NULL);
|
||||
current_paths = paths();
|
||||
|
||||
selected_hex = hex;
|
||||
gui.select_hex(hex);
|
||||
|
||||
const units_map::iterator it = units.find(hex);
|
||||
if(it != units.end() && it->second.side() == team_num) {
|
||||
const bool ignore_zocs = it->second.type().is_skirmisher();
|
||||
const bool teleport = it->second.type().teleports();
|
||||
current_paths = paths(map,gameinfo,units,hex,teams,
|
||||
ignore_zocs,teleport);
|
||||
gui.set_paths(¤t_paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
left_button = new_left_button;
|
||||
|
||||
if(!right_button && new_right_button) {
|
||||
if(!current_paths.routes.empty()) {
|
||||
selected_hex = gamemap::location();
|
||||
gui.select_hex(gamemap::location());
|
||||
gui.set_paths(NULL);
|
||||
current_paths = paths();
|
||||
} else {
|
||||
const units_map::const_iterator un = units.find(
|
||||
gui.hex_clicked_on(mousex,mousey));
|
||||
if(un != units.end()) {
|
||||
menu.push_back(string_table["describe_unit"]);
|
||||
}
|
||||
|
||||
const int res = gui::show_dialog(gui,NULL,"",
|
||||
string_table["options"]+":\n",
|
||||
gui::MESSAGE,&menu);
|
||||
|
||||
const std::string result = res != -1 ? menu[res] : "";
|
||||
|
||||
if(un != units.end()) {
|
||||
menu.pop_back();
|
||||
}
|
||||
|
||||
if(result == string_table["describe_unit"]) {
|
||||
|
||||
const std::string description
|
||||
= un->second.type().unit_description() +
|
||||
"\n\n" + string_table["see_also"];
|
||||
|
||||
std::vector<std::string> options;
|
||||
|
||||
options.push_back(string_table["terrain_info"]);
|
||||
options.push_back(string_table["attack_resistance"]);
|
||||
options.push_back(string_table["close_window"]);
|
||||
|
||||
std::vector<unit> units_list(options.size(),un->second);
|
||||
|
||||
SDL_Surface* const unit_image =
|
||||
gui.getImage(un->second.type().image_profile(),
|
||||
display::UNSCALED);
|
||||
|
||||
const int res = gui::show_dialog(gui,unit_image,
|
||||
un->second.type().language_name(),
|
||||
description,gui::MESSAGE,&options,&units_list);
|
||||
|
||||
//terrain table
|
||||
if(res == 0) {
|
||||
command = HOTKEY_TERRAIN_TABLE;
|
||||
}
|
||||
|
||||
//attack resistance table
|
||||
else if(res == 1) {
|
||||
command = HOTKEY_ATTACK_RESISTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
else if(result == string_table["preferences"]) {
|
||||
preferences::show_preferences_dialog(gui);
|
||||
}
|
||||
|
||||
else if(result == string_table["end_turn"]) {
|
||||
recorder.end_turn();
|
||||
return;
|
||||
}
|
||||
|
||||
else if(result == string_table["scenario_objectives"]) {
|
||||
static const std::string no_objectives(
|
||||
string_table["no_objectives"]);
|
||||
const std::string& id = level->values["id"];
|
||||
const std::string& lang_objectives =
|
||||
string_table[id + "_objectives"];
|
||||
|
||||
const std::string& objectives = lang_objectives.empty() ?
|
||||
level->values["objectives"] : lang_objectives;
|
||||
gui::show_dialog(
|
||||
gui, NULL, "",
|
||||
objectives.empty() ? no_objectives : objectives,
|
||||
gui::OK_ONLY);
|
||||
}
|
||||
|
||||
else if(result == string_table["recall"]) {
|
||||
//sort the available units into order by value
|
||||
//so that the most valuable units are shown first
|
||||
std::sort(state_of_game.available_units.begin(),
|
||||
state_of_game.available_units.end(),
|
||||
compare_unit_values());
|
||||
|
||||
gui.draw(); //clear the old menu
|
||||
if(state_of_game.available_units.empty()) {
|
||||
gui::show_dialog(gui,NULL,"",string_table["no_recall"]);
|
||||
} else if(current_team.gold() < game_config::recall_cost) {
|
||||
std::stringstream msg;
|
||||
msg << string_table["not_enough_gold_to_recall_1"]
|
||||
<< " " << game_config::recall_cost << " "
|
||||
<< string_table["not_enough_gold_to_recall_2"];
|
||||
gui::show_dialog(gui, NULL,"",msg.str());
|
||||
} else {
|
||||
std::vector<std::string> options;
|
||||
for(std::vector<unit>::const_iterator unit =
|
||||
state_of_game.available_units.begin();
|
||||
unit != state_of_game.available_units.end(); ++unit) {
|
||||
std::stringstream option;
|
||||
option << unit->type().language_name() << ","
|
||||
<< string_table["level"] << ": "
|
||||
<< unit->type().level() << ","
|
||||
<< string_table["xp"] << ": "
|
||||
<< unit->experience() << "/"
|
||||
<< unit->max_experience();
|
||||
options.push_back(option.str());
|
||||
}
|
||||
|
||||
const int res = gui::show_dialog(gui,NULL,"",
|
||||
string_table["select_unit"] + ":\n",
|
||||
gui::OK_CANCEL,&options,
|
||||
&state_of_game.available_units);
|
||||
if(res >= 0) {
|
||||
|
||||
const std::string err = recruit_unit(map,team_num,
|
||||
units,state_of_game.available_units[res],
|
||||
last_hex,&gui);
|
||||
if(!err.empty()) {
|
||||
gui::show_dialog(gui,NULL,"",err,gui::OK_ONLY);
|
||||
} else {
|
||||
current_team.spend_gold(
|
||||
game_config::recall_cost);
|
||||
state_of_game.available_units.erase(
|
||||
state_of_game.available_units.begin()+res);
|
||||
|
||||
recorder.add_recall(res,last_hex);
|
||||
|
||||
undo_stack.clear();
|
||||
redo_stack.clear();
|
||||
|
||||
gui.invalidate_game_status();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(result == string_table["recruit"]) {
|
||||
|
||||
std::vector<unit> sample_units;
|
||||
|
||||
gui.draw(); //clear the old menu
|
||||
std::vector<std::string> item_keys;
|
||||
std::vector<std::string> items;
|
||||
const std::set<std::string>& recruits
|
||||
= current_team.recruits();
|
||||
for(std::set<std::string>::const_iterator it =
|
||||
recruits.begin(); it != recruits.end(); ++it) {
|
||||
item_keys.push_back(*it);
|
||||
const std::map<std::string,unit_type>::const_iterator
|
||||
u_type = gameinfo.unit_types.find(*it);
|
||||
if(u_type == gameinfo.unit_types.end()) {
|
||||
std::cerr << "could not find " << *it << std::endl;
|
||||
assert(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
const unit_type& type = u_type->second;
|
||||
std::stringstream description;
|
||||
|
||||
description << type.language_name() << ": "
|
||||
<< type.cost() << " gold";
|
||||
items.push_back(description.str());
|
||||
sample_units.push_back(unit(&type,team_num));
|
||||
}
|
||||
|
||||
const int recruit_res =
|
||||
gui::show_dialog(gui,NULL,"",
|
||||
string_table["recruit_unit"] + ":\n",
|
||||
gui::OK_CANCEL,&items,&sample_units);
|
||||
if(recruit_res != -1) {
|
||||
const std::string& name = item_keys[recruit_res];
|
||||
const std::map<std::string,unit_type>::const_iterator
|
||||
u_type = gameinfo.unit_types.find(name);
|
||||
assert(u_type != gameinfo.unit_types.end());
|
||||
|
||||
if(u_type->second.cost() > current_team.gold()) {
|
||||
gui::show_dialog(gui,NULL,"",
|
||||
string_table["not_enough_gold_to_recruit"],
|
||||
gui::OK_ONLY);
|
||||
} else {
|
||||
recorder.add_recruit(recruit_res,last_hex);
|
||||
|
||||
//create a unit with traits
|
||||
unit new_unit(&(u_type->second),team_num,true);
|
||||
const std::string& msg =
|
||||
recruit_unit(map,team_num,units,new_unit,
|
||||
last_hex,&gui);
|
||||
if(msg.empty())
|
||||
current_team.spend_gold(u_type->second.cost());
|
||||
else
|
||||
gui::show_dialog(gui,NULL,"",msg,gui::OK_ONLY);
|
||||
|
||||
undo_stack.clear();
|
||||
redo_stack.clear();
|
||||
|
||||
gui.invalidate_game_status();
|
||||
}
|
||||
}
|
||||
} else if(result == string_table["unit_list"]) {
|
||||
const std::string heading = string_table["name"] + "," +
|
||||
string_table["hp"] + "," +
|
||||
string_table["xp"] + "," +
|
||||
string_table["moves"] + "," +
|
||||
string_table["location"];
|
||||
|
||||
std::vector<std::string> items;
|
||||
items.push_back(heading);
|
||||
|
||||
std::vector<unit> units_list;
|
||||
for(units_map::const_iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
if(i->second.side() != team_num)
|
||||
continue;
|
||||
|
||||
std::stringstream row;
|
||||
row << i->second.name() << "," << i->second.hitpoints()
|
||||
<< "/" << i->second.max_hitpoints() << ","
|
||||
<< i->second.experience() << "/"
|
||||
<< i->second.max_experience() << ","
|
||||
<< i->second.movement_left() << "/"
|
||||
<< i->second.total_movement() << ","
|
||||
<< i->first.x << "-" << i->first.y;
|
||||
|
||||
items.push_back(row.str());
|
||||
|
||||
//extra unit for the first row to make up the heading
|
||||
if(units_list.empty())
|
||||
units_list.push_back(i->second);
|
||||
|
||||
units_list.push_back(i->second);
|
||||
}
|
||||
|
||||
gui::show_dialog(gui,NULL,string_table["unit_list"],"",
|
||||
gui::OK_ONLY,&items,&units_list);
|
||||
} else if(result == string_table["save_game"]) {
|
||||
std::stringstream stream;
|
||||
stream << string_table["scenario"]
|
||||
<< " " << (state_of_game.scenario+1)
|
||||
<< " " << string_table["turn"]
|
||||
<< " " << status.turn();
|
||||
std::string label = stream.str();
|
||||
|
||||
const int res = gui::show_dialog(gui, NULL, "", "",
|
||||
gui::OK_CANCEL,NULL,NULL,
|
||||
string_table["save_game_label"],
|
||||
&label);
|
||||
|
||||
if(res == 0) {
|
||||
recorder.save_game(gameinfo,label);
|
||||
gui::show_dialog(gui,NULL,"",
|
||||
string_table["save_confirm_message"], gui::OK_ONLY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
right_button = new_right_button;
|
||||
|
||||
if(key[KEY_UP] || mousey == 0)
|
||||
gui.scroll(0.0,-scroll_speed);
|
||||
|
||||
if(key[KEY_DOWN] || mousey == gui.y()-1)
|
||||
gui.scroll(0.0,scroll_speed);
|
||||
|
||||
if(key[KEY_LEFT] || mousex == 0)
|
||||
gui.scroll(-scroll_speed,0.0);
|
||||
|
||||
if(key[KEY_RIGHT] || mousex == gui.x()-1)
|
||||
gui.scroll(scroll_speed,0.0);
|
||||
|
||||
if(command == HOTKEY_NULL)
|
||||
command = check_keys(gui);
|
||||
|
||||
units_map::const_iterator un = units.find(new_hex);
|
||||
if(un == units.end())
|
||||
un = units.find(selected_hex);
|
||||
|
||||
if(un != units.end() && command == HOTKEY_ATTACK_RESISTANCE) {
|
||||
gui.draw();
|
||||
|
||||
std::vector<std::string> items;
|
||||
items.push_back(string_table["attack_type"] + "," +
|
||||
string_table["attack_resistance"]);
|
||||
const std::map<std::string,std::string>& table =
|
||||
un->second.type().movement_type().damage_table();
|
||||
for(std::map<std::string,std::string>::const_iterator i
|
||||
= table.begin(); i != table.end(); ++i) {
|
||||
double resistance = atof(i->second.c_str());
|
||||
|
||||
//if resistance is less than 0, display in red
|
||||
std::string prefix = "";
|
||||
if(resistance > 1.0) {
|
||||
prefix = "#";
|
||||
}
|
||||
|
||||
const int resist=int(100.0-ceil(100.0*resistance));
|
||||
|
||||
std::stringstream str;
|
||||
str << i->first << "," << prefix << resist << "%";
|
||||
items.push_back(str.str());
|
||||
}
|
||||
|
||||
const std::vector<unit> units_list(items.size(),
|
||||
un->second);
|
||||
SDL_Surface* const unit_image =
|
||||
gui.getImage(un->second.type().image_profile(),display::UNSCALED);
|
||||
gui::show_dialog(gui,unit_image,
|
||||
un->second.type().language_name(),
|
||||
"Unit resistance table",
|
||||
gui::MESSAGE,&items,&units_list);
|
||||
}
|
||||
|
||||
if(un != units.end() && command == HOTKEY_TERRAIN_TABLE) {
|
||||
gui.draw();
|
||||
|
||||
std::vector<std::string> items;
|
||||
items.push_back(string_table["terrain"] + "," +
|
||||
string_table["movement"] + "," +
|
||||
string_table["defense"]);
|
||||
|
||||
const unit_type& type = un->second.type();
|
||||
const unit_movement_type& move_type =
|
||||
type.movement_type();
|
||||
const std::vector<gamemap::TERRAIN>& terrains =
|
||||
map.get_terrain_precedence();
|
||||
for(std::vector<gamemap::TERRAIN>::const_iterator t =
|
||||
terrains.begin(); t != terrains.end(); ++t) {
|
||||
const terrain_type& info = map.get_terrain_info(*t);
|
||||
if(!info.is_alias()) {
|
||||
const std::string& name = map.terrain_name(*t);
|
||||
const std::string& lang_name = string_table[name];
|
||||
const int moves = move_type.movement_cost(map,*t);
|
||||
|
||||
const double defense = move_type.defense_modifier(map,*t);
|
||||
|
||||
const int def = int(100.0-ceil(100.0*defense));
|
||||
|
||||
std::stringstream str;
|
||||
str << lang_name << ",";
|
||||
if(moves < 10)
|
||||
str << moves;
|
||||
else
|
||||
str << "--";
|
||||
|
||||
str << "," << def << "%";
|
||||
|
||||
items.push_back(str.str());
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<unit> units_list(items.size(),un->second);
|
||||
SDL_Surface* const unit_image =
|
||||
gui.getImage(un->second.type().image_profile(),display::UNSCALED);
|
||||
gui::show_dialog(gui,unit_image,un->second.type().language_name(),
|
||||
string_table["terrain_info"],
|
||||
gui::MESSAGE,&items,&units_list);
|
||||
}
|
||||
|
||||
if(command == HOTKEY_END_UNIT_TURN) {
|
||||
const units_map::iterator un = units.find(selected_hex);
|
||||
if(un != units.end() && un->second.side() == team_num &&
|
||||
un->second.movement_left() > 0) {
|
||||
std::vector<gamemap::location> steps;
|
||||
steps.push_back(selected_hex);
|
||||
undo_stack.push_back(undo_action(
|
||||
steps,un->second.movement_left(),-1));
|
||||
redo_stack.clear();
|
||||
un->second.set_movement(0);
|
||||
gui.draw_tile(selected_hex.x,selected_hex.y);
|
||||
|
||||
gui.set_paths(NULL);
|
||||
current_paths = paths();
|
||||
recorder.add_movement(selected_hex,selected_hex);
|
||||
|
||||
command = HOTKEY_CYCLE_UNITS;
|
||||
}
|
||||
}
|
||||
|
||||
//look for the next unit that is unmoved on our side
|
||||
if(command == HOTKEY_CYCLE_UNITS) {
|
||||
units_map::const_iterator it = units.find(next_unit);
|
||||
if(it != units.end()) {
|
||||
for(++it; it != units.end(); ++it) {
|
||||
if(it->second.side() == team_num &&
|
||||
it->second.movement_left() > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(it == units.end()) {
|
||||
for(it = units.begin(); it != units.end();
|
||||
++it) {
|
||||
if(it->second.side() == team_num &&
|
||||
it->second.movement_left() > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(it != units.end()) {
|
||||
const bool ignore_zocs =
|
||||
it->second.type().is_skirmisher();
|
||||
const bool teleport = it->second.type().teleports();
|
||||
current_paths = paths(map,gameinfo,units,
|
||||
it->first,teams,ignore_zocs,teleport);
|
||||
gui.set_paths(¤t_paths);
|
||||
|
||||
gui.scroll_to_tile(it->first.x,it->first.y,
|
||||
display::WARP);
|
||||
}
|
||||
|
||||
if(it != units.end()) {
|
||||
next_unit = it->first;
|
||||
selected_hex = next_unit;
|
||||
gui.select_hex(selected_hex);
|
||||
} else
|
||||
next_unit = gamemap::location();
|
||||
}
|
||||
|
||||
if(command == HOTKEY_LEADER) {
|
||||
for(units_map::const_iterator i = units.begin(); i != units.end();
|
||||
++i) {
|
||||
if(i->second.side() == team_num && i->second.can_recruit()) {
|
||||
gui.scroll_to_tile(i->first.x,i->first.y,display::WARP);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//undo
|
||||
if(command == HOTKEY_UNDO && !undo_stack.empty()) {
|
||||
const int starting_moves = undo_stack.back().starting_moves;
|
||||
std::vector<gamemap::location> route = undo_stack.back().route;
|
||||
std::reverse(route.begin(),route.end());
|
||||
const units_map::iterator u = units.find(route.front());
|
||||
if(u == units.end()) {
|
||||
assert(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(map[route.front().x][route.front().y] == gamemap::TOWER) {
|
||||
get_tower(route.front(),teams,
|
||||
undo_stack.back().original_village_owner);
|
||||
}
|
||||
|
||||
undo_stack.back().starting_moves = u->second.movement_left();
|
||||
|
||||
unit un = u->second;
|
||||
units.erase(u);
|
||||
gui.move_unit(route,un);
|
||||
un.set_movement(starting_moves);
|
||||
units.insert(std::pair<gamemap::location,unit>(route.back(),un));
|
||||
gui.invalidate_unit();
|
||||
gui.draw_tile(route.back().x,route.back().y);
|
||||
|
||||
redo_stack.push_back(undo_stack.back());
|
||||
undo_stack.pop_back();
|
||||
|
||||
gui.set_paths(NULL);
|
||||
current_paths = paths();
|
||||
|
||||
recorder.undo();
|
||||
}
|
||||
|
||||
if(command == HOTKEY_REDO && !redo_stack.empty()) {
|
||||
const int starting_moves = redo_stack.back().starting_moves;
|
||||
std::vector<gamemap::location> route = redo_stack.back().route;
|
||||
const units_map::iterator u = units.find(route.front());
|
||||
if(u == units.end()) {
|
||||
assert(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
redo_stack.back().starting_moves = u->second.movement_left();
|
||||
|
||||
unit un = u->second;
|
||||
units.erase(u);
|
||||
gui.move_unit(route,un);
|
||||
un.set_movement(starting_moves);
|
||||
units.insert(std::pair<gamemap::location,unit>(route.back(),un));
|
||||
gui.invalidate_unit();
|
||||
|
||||
recorder.add_movement(route.front(),route.back());
|
||||
|
||||
if(map[route.back().x][route.back().y] == gamemap::TOWER) {
|
||||
get_tower(route.back(),teams,un.side()-1);
|
||||
}
|
||||
|
||||
gui.draw_tile(route.back().x,route.back().y);
|
||||
|
||||
undo_stack.push_back(redo_stack.back());
|
||||
redo_stack.pop_back();
|
||||
}
|
||||
|
||||
gui.draw();
|
||||
|
||||
game_events::pump();
|
||||
}
|
||||
}
|
52
playturn.hpp
Normal file
52
playturn.hpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef PLAYTURN_HPP_INCLUDED
|
||||
#define PLAYTURN_HPP_INCLUDED
|
||||
|
||||
#include "actions.hpp"
|
||||
#include "ai.hpp"
|
||||
#include "config.hpp"
|
||||
#include "dialogs.hpp"
|
||||
#include "display.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "game_events.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "key.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "team.hpp"
|
||||
#include "unit_types.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "video.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
struct paths_wiper
|
||||
{
|
||||
paths_wiper(display& gui) : gui_(gui)
|
||||
{}
|
||||
|
||||
~paths_wiper() { gui_.set_paths(NULL); }
|
||||
|
||||
private:
|
||||
display& gui_;
|
||||
};
|
||||
|
||||
void play_turn(game_data& gameinfo, game_state& state_of_game,
|
||||
gamestatus& status, config& terrain_config, config* level,
|
||||
CVideo& video, CKey& key, display& gui,
|
||||
game_events::manager& events_manager, gamemap& map,
|
||||
std::vector<team>& teams, int team_num,
|
||||
std::map<gamemap::location,unit>& units);
|
||||
|
||||
#endif
|
312
preferences.cpp
Normal file
312
preferences.cpp
Normal file
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "filesystem.hpp"
|
||||
#include "font.hpp"
|
||||
#include "language.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "preferences.hpp"
|
||||
#include "sound.hpp"
|
||||
#include "util.hpp"
|
||||
#include "widgets/button.hpp"
|
||||
#include "widgets/slider.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
|
||||
namespace {
|
||||
|
||||
config prefs;
|
||||
|
||||
display* disp;
|
||||
|
||||
}
|
||||
|
||||
namespace preferences {
|
||||
|
||||
manager::manager()
|
||||
{
|
||||
prefs.read(read_file(get_prefs_file()));
|
||||
|
||||
set_music_volume(music_volume());
|
||||
set_sound_volume(sound_volume());
|
||||
}
|
||||
|
||||
manager::~manager()
|
||||
{
|
||||
write_file(get_prefs_file(),prefs.write());
|
||||
}
|
||||
|
||||
display_manager::display_manager(display* d)
|
||||
{
|
||||
disp = d;
|
||||
|
||||
set_grid(grid());
|
||||
set_turbo(turbo());
|
||||
set_fullscreen(fullscreen());
|
||||
}
|
||||
|
||||
display_manager::~display_manager()
|
||||
{
|
||||
disp = NULL;
|
||||
}
|
||||
|
||||
bool fullscreen()
|
||||
{
|
||||
const string_map::const_iterator fullscreen =
|
||||
prefs.values.find("fullscreen");
|
||||
return fullscreen == prefs.values.end() || fullscreen->second == "true";
|
||||
}
|
||||
|
||||
void set_fullscreen(bool ison)
|
||||
{
|
||||
prefs.values["fullscreen"] = (ison ? "true" : "false");
|
||||
|
||||
if(disp != NULL) {
|
||||
CVideo& video = disp->video();
|
||||
if(video.isFullScreen() != ison) {
|
||||
const int flags = ison ? FULL_SCREEN : 0;
|
||||
if(video.modePossible(1024,768,16,flags)) {
|
||||
video.setMode(1024,768,16,flags);
|
||||
disp->redraw_everything();
|
||||
} else {
|
||||
gui::show_dialog(*disp,NULL,"",string_table["video_mode_fail"],
|
||||
gui::MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool turbo()
|
||||
{
|
||||
const string_map::const_iterator turbo = prefs.values.find("turbo");
|
||||
return turbo != prefs.values.end() && turbo->second == "true";
|
||||
}
|
||||
|
||||
void set_turbo(bool ison)
|
||||
{
|
||||
prefs.values["turbo"] = (ison ? "true" : "false");
|
||||
|
||||
if(disp != NULL) {
|
||||
disp->set_turbo(ison);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& locale()
|
||||
{
|
||||
return prefs.values["locale"];
|
||||
}
|
||||
|
||||
void set_locale(const std::string& s)
|
||||
{
|
||||
prefs.values["locale"] = s;
|
||||
}
|
||||
|
||||
double music_volume()
|
||||
{
|
||||
static const double default_value = 1.0;
|
||||
const string_map::const_iterator volume = prefs.values.find("music_volume");
|
||||
if(volume != prefs.values.end() && volume->second.empty() == false)
|
||||
return atof(volume->second.c_str());
|
||||
else
|
||||
return default_value;
|
||||
}
|
||||
|
||||
void set_music_volume(double vol)
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << vol;
|
||||
prefs.values["music_volume"] = stream.str();
|
||||
|
||||
sound::set_music_volume(vol);
|
||||
}
|
||||
|
||||
double sound_volume()
|
||||
{
|
||||
static const double default_value = 1.0;
|
||||
const string_map::const_iterator volume = prefs.values.find("sound_volume");
|
||||
if(volume != prefs.values.end() && volume->second.empty() == false)
|
||||
return atof(volume->second.c_str());
|
||||
else
|
||||
return default_value;
|
||||
}
|
||||
|
||||
void set_sound_volume(double vol)
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << vol;
|
||||
prefs.values["sound_volume"] = stream.str();
|
||||
|
||||
sound::set_sound_volume(vol);
|
||||
}
|
||||
|
||||
bool grid()
|
||||
{
|
||||
const string_map::const_iterator turbo = prefs.values.find("grid");
|
||||
return turbo != prefs.values.end() && turbo->second == "true";
|
||||
}
|
||||
|
||||
void set_grid(bool ison)
|
||||
{
|
||||
prefs.values["grid"] = (ison ? "true" : "false");
|
||||
|
||||
if(disp != NULL) {
|
||||
disp->set_grid(ison);
|
||||
}
|
||||
}
|
||||
|
||||
void show_preferences_dialog(display& disp)
|
||||
{
|
||||
const int border_size = 6;
|
||||
const int xpos = 1024/2 - 300;
|
||||
const int ypos = 768/2 - 200;
|
||||
const int width = 600;
|
||||
const int height = 400;
|
||||
|
||||
disp.invalidate_all();
|
||||
disp.draw();
|
||||
|
||||
SDL_Rect clip_rect = {0,0,1024,768};
|
||||
SDL_Rect title_rect = font::draw_text(NULL,clip_rect,16,font::NORMAL_COLOUR,
|
||||
string_table["preferences"],0,0);
|
||||
|
||||
gui::button close_button(disp,string_table["close_window"]);
|
||||
|
||||
close_button.set_x(xpos + width/2 - close_button.width()/2);
|
||||
close_button.set_y(ypos + height - close_button.height()-14);
|
||||
|
||||
const std::string& music_label = string_table["music_volume"];
|
||||
const std::string& sound_label = string_table["sound_volume"];
|
||||
|
||||
SDL_Rect music_rect = {0,0,0,0};
|
||||
music_rect = font::draw_text(NULL,clip_rect,14,font::NORMAL_COLOUR,
|
||||
music_label,0,0);
|
||||
|
||||
SDL_Rect sound_rect = {0,0,0,0};
|
||||
sound_rect = font::draw_text(NULL,clip_rect,14,font::NORMAL_COLOUR,
|
||||
sound_label,0,0);
|
||||
|
||||
const int text_right = xpos + maximum(music_rect.w,sound_rect.w) + 5;
|
||||
|
||||
const int music_pos = ypos + title_rect.h + 20;
|
||||
const int sound_pos = music_pos + 50;
|
||||
|
||||
music_rect.x = text_right - music_rect.w;
|
||||
music_rect.y = music_pos;
|
||||
|
||||
sound_rect.x = text_right - sound_rect.w;
|
||||
sound_rect.y = sound_pos;
|
||||
|
||||
const int slider_left = text_right + 10;
|
||||
const int slider_right = xpos + width - 5;
|
||||
if(slider_left >= slider_right)
|
||||
return;
|
||||
|
||||
SDL_Rect slider_rect = { slider_left,sound_pos,slider_right-slider_left,10};
|
||||
gui::slider sound_slider(disp,slider_rect,sound_volume());
|
||||
|
||||
slider_rect.y = music_pos;
|
||||
gui::slider music_slider(disp,slider_rect,music_volume());
|
||||
|
||||
gui::button fullscreen_button(disp,string_table["full_screen"],
|
||||
gui::button::TYPE_CHECK);
|
||||
|
||||
fullscreen_button.set_check(fullscreen());
|
||||
|
||||
fullscreen_button.set_x(slider_left);
|
||||
fullscreen_button.set_y(sound_pos + 80);
|
||||
|
||||
gui::button turbo_button(disp,string_table["speed_turbo"],
|
||||
gui::button::TYPE_CHECK);
|
||||
turbo_button.set_check(turbo());
|
||||
|
||||
turbo_button.set_x(slider_left);
|
||||
turbo_button.set_y(sound_pos + 80 + 50);
|
||||
|
||||
gui::button grid_button(disp,string_table["grid_button"],
|
||||
gui::button::TYPE_CHECK);
|
||||
grid_button.set_check(grid());
|
||||
|
||||
grid_button.set_x(slider_left);
|
||||
grid_button.set_y(sound_pos + 80 + 100);
|
||||
|
||||
bool redraw_all = true;
|
||||
|
||||
for(;;) {
|
||||
int mousex, mousey;
|
||||
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
|
||||
|
||||
const bool left_button = mouse_flags&SDL_BUTTON_LMASK;
|
||||
const bool right_button = mouse_flags&SDL_BUTTON_RMASK;
|
||||
|
||||
if(close_button.process(mousex,mousey,left_button)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const double new_music=music_slider.process(mousex,mousey,left_button);
|
||||
const double new_sound=sound_slider.process(mousex,mousey,left_button);
|
||||
|
||||
if(new_sound >= 0.0) {
|
||||
set_sound_volume(new_sound);
|
||||
}
|
||||
|
||||
if(new_music >= 0.0) {
|
||||
set_music_volume(new_music);
|
||||
}
|
||||
|
||||
if(fullscreen_button.process(mousex,mousey,left_button)) {
|
||||
set_fullscreen(fullscreen_button.checked());
|
||||
redraw_all = true;
|
||||
}
|
||||
|
||||
if(redraw_all) {
|
||||
gui::draw_dialog_frame(xpos,ypos,width,height,disp);
|
||||
sound_slider.background_changed();
|
||||
music_slider.background_changed();
|
||||
sound_slider.draw();
|
||||
music_slider.draw();
|
||||
fullscreen_button.draw();
|
||||
turbo_button.draw();
|
||||
grid_button.draw();
|
||||
close_button.draw();
|
||||
|
||||
font::draw_text(&disp,clip_rect,14,font::NORMAL_COLOUR,music_label,
|
||||
music_rect.x,music_rect.y);
|
||||
|
||||
font::draw_text(&disp,clip_rect,14,font::NORMAL_COLOUR,sound_label,
|
||||
sound_rect.x,sound_rect.y);
|
||||
|
||||
font::draw_text(&disp,clip_rect,18,font::NORMAL_COLOUR,
|
||||
string_table["preferences"],
|
||||
xpos+(width-title_rect.w)/2,ypos+10);
|
||||
|
||||
redraw_all = false;
|
||||
}
|
||||
|
||||
if(turbo_button.process(mousex,mousey,left_button)) {
|
||||
set_turbo(turbo_button.checked());
|
||||
}
|
||||
|
||||
if(grid_button.process(mousex,mousey,left_button)) {
|
||||
set_grid(grid_button.checked());
|
||||
}
|
||||
|
||||
disp.update_display();
|
||||
|
||||
SDL_Delay(10);
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
|
||||
disp.invalidate_all();
|
||||
disp.draw();
|
||||
}
|
||||
|
||||
}
|
55
preferences.hpp
Normal file
55
preferences.hpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef PREFERENCES_HPP_INCLUDED
|
||||
#define PREFERENCES_HPP_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "display.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace preferences {
|
||||
|
||||
struct manager
|
||||
{
|
||||
manager();
|
||||
~manager();
|
||||
};
|
||||
|
||||
struct display_manager
|
||||
{
|
||||
display_manager(display* disp);
|
||||
~display_manager();
|
||||
};
|
||||
|
||||
bool fullscreen();
|
||||
void set_fullscreen(bool ison);
|
||||
|
||||
bool turbo();
|
||||
void set_turbo(bool ison);
|
||||
|
||||
const std::string& locale();
|
||||
void set_locale(const std::string& s);
|
||||
|
||||
double music_volume();
|
||||
void set_music_volume(double vol);
|
||||
|
||||
double sound_volume();
|
||||
void set_sound_volume(double vol);
|
||||
|
||||
bool grid();
|
||||
void set_grid(bool ison);
|
||||
|
||||
void show_preferences_dialog(display& disp);
|
||||
}
|
||||
|
||||
#endif
|
482
replay.cpp
Normal file
482
replay.cpp
Normal file
|
@ -0,0 +1,482 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "actions.hpp"
|
||||
#include "ai.hpp"
|
||||
#include "ai_attack.hpp"
|
||||
#include "ai_move.hpp"
|
||||
#include "dialogs.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "log.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "playturn.hpp"
|
||||
#include "replay.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
replay recorder;
|
||||
|
||||
replay::replay() : pos_(0), current_(NULL), skip_(0)
|
||||
{}
|
||||
|
||||
replay::replay(config& cfg) : cfg_(cfg), pos_(0), current_(NULL), skip_(0)
|
||||
{}
|
||||
|
||||
config& replay::get_config()
|
||||
{
|
||||
return cfg_;
|
||||
}
|
||||
|
||||
void replay::set_save_info(const game_state& save)
|
||||
{
|
||||
saveInfo_ = save;
|
||||
}
|
||||
|
||||
const game_state& replay::get_save_info() const
|
||||
{
|
||||
return saveInfo_;
|
||||
}
|
||||
|
||||
void replay::set_skip(int turns_to_skip)
|
||||
{
|
||||
skip_ = turns_to_skip;
|
||||
}
|
||||
|
||||
void replay::next_skip()
|
||||
{
|
||||
if(skip_ > 0)
|
||||
--skip_;
|
||||
}
|
||||
|
||||
bool replay::skipping() const
|
||||
{
|
||||
return skip_ != 0;
|
||||
}
|
||||
|
||||
void replay::save_game(game_data& data, const std::string& label)
|
||||
{
|
||||
saveInfo_.replay_data = cfg_;
|
||||
saveInfo_.label = label;
|
||||
|
||||
::save_game(saveInfo_);
|
||||
|
||||
saveInfo_.replay_data = config();
|
||||
}
|
||||
|
||||
void replay::add_recruit(int value, const gamemap::location& loc)
|
||||
{
|
||||
config* const cmd = add_command();
|
||||
|
||||
config* const val = new config();
|
||||
|
||||
char buf[100];
|
||||
sprintf(buf,"%d",value);
|
||||
val->values["value"] = buf;
|
||||
|
||||
sprintf(buf,"%d",loc.x+1);
|
||||
val->values["x"] = buf;
|
||||
|
||||
sprintf(buf,"%d",loc.y+1);
|
||||
val->values["y"] = buf;
|
||||
|
||||
cmd->children["recruit"].push_back(val);
|
||||
}
|
||||
|
||||
void replay::add_recall(int value, const gamemap::location& loc)
|
||||
{
|
||||
config* const cmd = add_command();
|
||||
|
||||
config* const val = new config();
|
||||
|
||||
char buf[100];
|
||||
sprintf(buf,"%d",value);
|
||||
val->values["value"] = buf;
|
||||
|
||||
sprintf(buf,"%d",loc.x+1);
|
||||
val->values["x"] = buf;
|
||||
|
||||
sprintf(buf,"%d",loc.y+1);
|
||||
val->values["y"] = buf;
|
||||
|
||||
cmd->children["recall"].push_back(val);
|
||||
}
|
||||
|
||||
void replay::add_movement(const gamemap::location& a,const gamemap::location& b)
|
||||
{
|
||||
add_pos("move",a,b);
|
||||
current_ = NULL;
|
||||
}
|
||||
|
||||
void replay::add_attack(const gamemap::location& a, const gamemap::location& b,
|
||||
int weapon)
|
||||
{
|
||||
add_pos("attack",a,b);
|
||||
char buf[100];
|
||||
sprintf(buf,"%d",weapon);
|
||||
current_->children["attack"][0]->values["weapon"] = buf;
|
||||
}
|
||||
|
||||
void replay::add_pos(const std::string& type,
|
||||
const gamemap::location& a, const gamemap::location& b)
|
||||
{
|
||||
config* const cmd = add_command();
|
||||
config* const move = new config();
|
||||
config* const src = new config();
|
||||
config* const dst = new config();
|
||||
|
||||
char buf[100];
|
||||
sprintf(buf,"%d",a.x+1);
|
||||
src->values["x"] = buf;
|
||||
sprintf(buf,"%d",a.y+1);
|
||||
src->values["y"] = buf;
|
||||
sprintf(buf,"%d",b.x+1);
|
||||
dst->values["x"] = buf;
|
||||
sprintf(buf,"%d",b.y+1);
|
||||
dst->values["y"] = buf;
|
||||
|
||||
move->children["source"].push_back(src);
|
||||
move->children["destination"].push_back(dst);
|
||||
cmd->children[type].push_back(move);
|
||||
|
||||
current_ = cmd;
|
||||
}
|
||||
|
||||
void replay::add_value(const std::string& type, int value)
|
||||
{
|
||||
config* const cmd = add_command();
|
||||
|
||||
config* const val = new config();
|
||||
|
||||
char buf[100];
|
||||
sprintf(buf,"%d",value);
|
||||
val->values["value"] = buf;
|
||||
|
||||
cmd->children[type].push_back(val);
|
||||
}
|
||||
|
||||
void replay::choose_option(int index)
|
||||
{
|
||||
add_value("choose",index);
|
||||
}
|
||||
|
||||
void replay::end_turn()
|
||||
{
|
||||
config* const cmd = add_command();
|
||||
|
||||
cmd->children["end_turn"].push_back(new config());
|
||||
}
|
||||
|
||||
void replay::undo()
|
||||
{
|
||||
std::vector<config*>& cmd = commands();
|
||||
if(!cmd.empty()) {
|
||||
delete cmd.back();
|
||||
cmd.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<config*>& replay::commands()
|
||||
{
|
||||
return cfg_.children["command"];
|
||||
}
|
||||
|
||||
config* replay::add_command()
|
||||
{
|
||||
config* const cmd = new config();
|
||||
commands().push_back(cmd);
|
||||
current_ = cmd;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
int replay::get_random()
|
||||
{
|
||||
if(current_ == NULL) {
|
||||
throw error();
|
||||
}
|
||||
|
||||
//random numbers are in a 'list' meaning that each random
|
||||
//number contains another random numbers unless it's at
|
||||
//the end of the list. Generating a new random number means
|
||||
//nesting a new node inside the current node, and making
|
||||
//the current node the new node
|
||||
std::vector<config*>& random = current_->children["random"];
|
||||
if(random.empty()) {
|
||||
const int res = rand();
|
||||
char buf[100];
|
||||
sprintf(buf,"%d",res);
|
||||
|
||||
current_ = new config();
|
||||
current_->values["value"] = buf;
|
||||
random.push_back(current_);
|
||||
|
||||
return res;
|
||||
} else {
|
||||
const int res = atol(random.front()->values["value"].c_str());
|
||||
current_ = random.front();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
void replay::start_replay()
|
||||
{
|
||||
pos_ = 0;
|
||||
}
|
||||
|
||||
config* replay::get_next_action()
|
||||
{
|
||||
if(pos_ >= commands().size())
|
||||
return NULL;
|
||||
|
||||
current_ = commands()[pos_];
|
||||
++pos_;
|
||||
return current_;
|
||||
}
|
||||
|
||||
void replay::clear()
|
||||
{
|
||||
cfg_ = config();
|
||||
pos_ = 0;
|
||||
current_ = NULL;
|
||||
skip_ = 0;
|
||||
}
|
||||
|
||||
bool replay::empty()
|
||||
{
|
||||
return commands().empty();
|
||||
}
|
||||
|
||||
bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
std::vector<team>& teams, int team_num, const gamestatus& state,
|
||||
game_state& state_of_game)
|
||||
{
|
||||
update_locker lock_update(disp,recorder.skipping());
|
||||
|
||||
//a list of units that have promoted from the last attack
|
||||
std::deque<gamemap::location> advancing_units;
|
||||
|
||||
team& current_team = teams[team_num-1];
|
||||
|
||||
for(;;) {
|
||||
config* const cfg = recorder.get_next_action();
|
||||
|
||||
std::map<std::string,std::vector<config*> >::iterator it;
|
||||
|
||||
//if we are expecting promotions here
|
||||
if(advancing_units.empty() == false) {
|
||||
if(cfg == NULL ||
|
||||
(it = cfg->children.find("choose")) == cfg->children.end()) {
|
||||
std::cerr << "promotion expected, but none found\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
const std::map<gamemap::location,unit>::iterator u =
|
||||
units.find(advancing_units.front());
|
||||
assert(u != units.end());
|
||||
|
||||
const std::string& num = it->second.front()->values["value"];
|
||||
const int val = atoi(num.c_str());
|
||||
|
||||
const std::vector<std::string>& options =
|
||||
u->second.type().advances_to();
|
||||
if(val < 0 || val >= options.size()) {
|
||||
std::cerr << "illegal advancement type\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
advance_unit(gameinfo,units,advancing_units.front(),options[val]);
|
||||
|
||||
advancing_units.pop_front();
|
||||
}
|
||||
|
||||
//if there is nothing more in the records
|
||||
else if(cfg == NULL) {
|
||||
recorder.set_skip(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
//if there is an end turn directive
|
||||
else if(cfg->children.find("end_turn") != cfg->children.end()) {
|
||||
recorder.next_skip();
|
||||
return true;
|
||||
}
|
||||
|
||||
else if((it = cfg->children.find("recruit")) != cfg->children.end()) {
|
||||
assert(!it->second.empty());
|
||||
const std::string& recruit_num=it->second.front()->values["value"];
|
||||
const int val = atoi(recruit_num.c_str());
|
||||
|
||||
const gamemap::location loc(*(it->second.front()));
|
||||
|
||||
const std::set<std::string>& recruits = current_team.recruits();
|
||||
std::set<std::string>::const_iterator itor = recruits.begin();
|
||||
std::advance(itor,val);
|
||||
const std::map<std::string,unit_type>::const_iterator u_type =
|
||||
gameinfo.unit_types.find(*itor);
|
||||
if(u_type == gameinfo.unit_types.end())
|
||||
throw replay::error();
|
||||
|
||||
unit new_unit(&(u_type->second),team_num,true);
|
||||
recruit_unit(map,team_num,units,new_unit,loc);
|
||||
|
||||
current_team.spend_gold(u_type->second.cost());
|
||||
}
|
||||
|
||||
else if((it = cfg->children.find("recall")) != cfg->children.end()) {
|
||||
std::sort(state_of_game.available_units.begin(),
|
||||
state_of_game.available_units.end(),
|
||||
compare_unit_values());
|
||||
|
||||
assert(!it->second.empty());
|
||||
const std::string recall_num = it->second.front()->values["value"];
|
||||
const int val = atoi(recall_num.c_str());
|
||||
|
||||
const gamemap::location loc(*(it->second.front()));
|
||||
|
||||
recruit_unit(map,team_num,units,
|
||||
state_of_game.available_units[val],loc);
|
||||
state_of_game.available_units.erase(
|
||||
state_of_game.available_units.begin()+val);
|
||||
current_team.spend_gold(game_config::recall_cost);
|
||||
}
|
||||
|
||||
else if((it = cfg->children.find("move")) != cfg->children.end()) {
|
||||
assert(!it->second.empty());
|
||||
|
||||
config* const move = it->second.front();
|
||||
|
||||
assert(!move->children["destination"].empty());
|
||||
assert(!move->children["source"].empty());
|
||||
|
||||
const gamemap::location src(*(move->children["source"][0]));
|
||||
const gamemap::location dst(*(move->children["destination"][0]));
|
||||
|
||||
const std::map<gamemap::location,unit>::iterator u=units.find(src);
|
||||
if(u == units.end()) {
|
||||
std::cerr << "unfound location for source of movement: "
|
||||
<< src.x << "," << src.y << "-"
|
||||
<< dst.x << "," << dst.y << "\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
const bool ignore_zocs = u->second.type().is_skirmisher();
|
||||
const bool teleport = u->second.type().teleports();
|
||||
|
||||
paths paths_list(map,gameinfo,units,src,teams,ignore_zocs,teleport);
|
||||
paths_wiper wiper(disp);
|
||||
|
||||
if(!recorder.skipping()) {
|
||||
disp.set_paths(&paths_list);
|
||||
|
||||
disp.scroll_to_tiles(src.x,src.y,dst.x,dst.y);
|
||||
}
|
||||
|
||||
unit current_unit = u->second;
|
||||
units.erase(u);
|
||||
|
||||
std::map<gamemap::location,paths::route>::iterator rt =
|
||||
paths_list.routes.find(dst);
|
||||
if(rt == paths_list.routes.end()) {
|
||||
std::cerr << "src cannot get to dst: " << paths_list.routes.size() << "\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
rt->second.steps.push_back(dst);
|
||||
|
||||
if(!recorder.skipping())
|
||||
disp.move_unit(rt->second.steps,current_unit);
|
||||
|
||||
current_unit.set_movement(rt->second.move_left);
|
||||
units.insert(std::pair<gamemap::location,unit>(dst,current_unit));
|
||||
if(map[dst.x][dst.y] == gamemap::TOWER) {
|
||||
current_unit.set_movement(0);
|
||||
get_tower(dst,teams,team_num-1);
|
||||
}
|
||||
|
||||
if(!recorder.skipping()) {
|
||||
disp.draw_tile(dst.x,dst.y);
|
||||
disp.update_display();
|
||||
}
|
||||
|
||||
game_events::fire("moveto",dst);
|
||||
}
|
||||
|
||||
else if((it = cfg->children.find("attack")) != cfg->children.end()) {
|
||||
assert(!it->second.empty());
|
||||
|
||||
config* const move = it->second.front();
|
||||
|
||||
assert(!move->children["destination"].empty());
|
||||
assert(!move->children["source"].empty());
|
||||
|
||||
const gamemap::location src(*(move->children["source"][0]));
|
||||
const gamemap::location dst(*(move->children["destination"][0]));
|
||||
|
||||
const std::string& weapon = move->values["weapon"];
|
||||
const int weapon_num = atoi(weapon.c_str());
|
||||
|
||||
std::map<gamemap::location,unit>::iterator u=units.find(src);
|
||||
if(u == units.end()) {
|
||||
std::cerr << "unfound location for source of attack\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
if(weapon_num < 0 || weapon_num >= u->second.attacks().size()) {
|
||||
std::cerr << "illegal weapon type in attack\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
std::map<gamemap::location,unit>::const_iterator tgt =
|
||||
units.find(dst);
|
||||
|
||||
if(tgt == units.end()) {
|
||||
std::cerr << "unfound defender for attack\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
game_events::fire("attack",src,dst);
|
||||
|
||||
u = units.find(src);
|
||||
tgt = units.find(dst);
|
||||
|
||||
if(u != units.end() && tgt != units.end()) {
|
||||
attack(disp,map,src,dst,weapon_num,units,state,gameinfo,false);
|
||||
const int res = check_victory(units);
|
||||
if(res == 1)
|
||||
throw end_level_exception(VICTORY);
|
||||
else if(res > 1)
|
||||
throw end_level_exception(DEFEAT);
|
||||
}
|
||||
|
||||
u = units.find(src);
|
||||
tgt = units.find(dst);
|
||||
|
||||
if(u != units.end() && u->second.advances() &&
|
||||
u->second.type().advances_to().empty() == false) {
|
||||
advancing_units.push_back(u->first);
|
||||
}
|
||||
|
||||
if(tgt != units.end() && tgt->second.advances() &&
|
||||
tgt->second.type().advances_to().empty() == false) {
|
||||
advancing_units.push_back(tgt->first);
|
||||
}
|
||||
} else {
|
||||
std::cerr << "unrecognized action\n";
|
||||
throw replay::error();
|
||||
}
|
||||
}
|
||||
}
|
84
replay.hpp
Normal file
84
replay.hpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef REPLAY_H_INCLUDED
|
||||
#define REPLAY_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "display.hpp"
|
||||
#include "map.hpp"
|
||||
|
||||
class replay
|
||||
{
|
||||
public:
|
||||
replay();
|
||||
replay(config& cfg);
|
||||
|
||||
config& get_config();
|
||||
|
||||
void set_save_info(const game_state& save);
|
||||
const game_state& get_save_info() const;
|
||||
|
||||
void set_skip(int turns_to_skip);
|
||||
void next_skip();
|
||||
bool skipping() const;
|
||||
|
||||
void save_game(game_data& data, const std::string& label);
|
||||
|
||||
void add_recruit(int unit_index, const gamemap::location& loc);
|
||||
void add_recall(int unit_index, const gamemap::location& loc);
|
||||
void add_movement(const gamemap::location& a, const gamemap::location& b);
|
||||
void add_attack(const gamemap::location& a, const gamemap::location& b,
|
||||
int weapon);
|
||||
void choose_option(int index);
|
||||
void end_turn();
|
||||
|
||||
void undo();
|
||||
|
||||
int get_random();
|
||||
|
||||
void start_replay();
|
||||
config* get_next_action();
|
||||
|
||||
void clear();
|
||||
bool empty();
|
||||
|
||||
struct error {};
|
||||
|
||||
private:
|
||||
//generic for add_movement and add_attack
|
||||
void add_pos(const std::string& type,
|
||||
const gamemap::location& a, const gamemap::location& b);
|
||||
|
||||
void add_value(const std::string& type, int value);
|
||||
|
||||
std::vector<config*>& commands();
|
||||
config* add_command();
|
||||
config cfg_;
|
||||
unsigned int pos_;
|
||||
|
||||
config* current_;
|
||||
|
||||
game_state saveInfo_;
|
||||
|
||||
int skip_;
|
||||
};
|
||||
|
||||
extern replay recorder;
|
||||
|
||||
//replays up to one turn from the recorder object
|
||||
//returns true if it got to the end of the turn without data running out
|
||||
bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
std::vector<team>& teams, int team_num, const gamestatus& state,
|
||||
game_state& state_of_game);
|
||||
|
||||
#endif
|
177
scoped_resource.hpp
Normal file
177
scoped_resource.hpp
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef SCOPED_RESOURCE_H_INCLUDED
|
||||
#define SCOPED_RESOURCE_H_INCLUDED
|
||||
|
||||
/**
|
||||
* The util namespace should take all classes which are of a generic type,
|
||||
* used to perform common tasks which are not BibleTime-specific. See
|
||||
* @ref scoped_resource for an example.
|
||||
*/
|
||||
namespace util
|
||||
{
|
||||
/**
|
||||
* A class template, scoped_resource, designed to
|
||||
* implement the Resource Acquisition Is Initialization (RAII) approach
|
||||
* to resource management. scoped_resource is designed to be used when
|
||||
* a resource is initialized at the beginning or middle of a scope,
|
||||
* and released at the end of the scope. The template argument
|
||||
* ReleasePolicy is a functor which takes an argument of the
|
||||
* type of the resource, and releases it.
|
||||
*
|
||||
* Usage example, for working with files:
|
||||
*
|
||||
* @code
|
||||
* struct close_file { void operator(int fd) const {close(fd);} };
|
||||
* ...
|
||||
* {
|
||||
* const scoped_resource<int,close_file> file(open("file.txt",O_RDONLY));
|
||||
* read(file, buf, 1000);
|
||||
* } // file is automatically closed here
|
||||
* @endcode
|
||||
*
|
||||
* Note that scoped_resource has an explicit constructor, and prohibits
|
||||
* copy-construction, and thus the initialization syntax, rather than
|
||||
* the assignment syntax must be used when initializing.
|
||||
*
|
||||
* i.e. using scoped_resource<int,close_file> file = open("file.txt",O_RDONLY);
|
||||
* in the above example is illegal.
|
||||
*
|
||||
*/
|
||||
template<typename T,typename ReleasePolicy>
|
||||
class scoped_resource
|
||||
{
|
||||
T resource;
|
||||
ReleasePolicy release;
|
||||
|
||||
//prohibited operations
|
||||
scoped_resource(const scoped_resource&);
|
||||
scoped_resource& operator=(const scoped_resource&);
|
||||
public:
|
||||
typedef T resource_type;
|
||||
typedef ReleasePolicy release_type;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @ param res This is the resource to be managed
|
||||
* @ param rel This is the functor to release the object
|
||||
*/
|
||||
scoped_resource(resource_type res,release_type rel=release_type())
|
||||
: resource(res), release(rel) {}
|
||||
|
||||
/**
|
||||
* The destructor is the main point in this class. It takes care of proper
|
||||
* deletion of the resource, using the provided release policy.
|
||||
*/
|
||||
~scoped_resource()
|
||||
{
|
||||
release(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* This operator makes sure you can access and use the scoped_resource
|
||||
* just like you were using the resource itself.
|
||||
*
|
||||
* @ret the underlying resource
|
||||
*/
|
||||
operator resource_type() const { return resource; }
|
||||
|
||||
/**
|
||||
* This function provides explicit access to the resource. Its behaviour
|
||||
* is identical to operator resource_type()
|
||||
*
|
||||
* @ret the underlying resource
|
||||
*/
|
||||
resource_type get() const { return resource; }
|
||||
|
||||
/**
|
||||
* This function provides convenient direct access to the -> operator
|
||||
* if the underlying resource is a pointer. Only call this function
|
||||
* if resource_type is a pointer type.
|
||||
*/
|
||||
resource_type operator->() const { return resource; }
|
||||
|
||||
resource_type& assign(const resource_type& o) {
|
||||
release(resource);
|
||||
resource = o;
|
||||
|
||||
return resource;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A helper policy for scoped_ptr.
|
||||
* It will call the delete operator on a pointer, and assign the pointer to 0
|
||||
*/
|
||||
struct delete_item {
|
||||
template<typename T>
|
||||
void operator()(T*& p) const { delete p; p = 0; }
|
||||
};
|
||||
/**
|
||||
* A helper policy for scoped_array.
|
||||
* It will call the delete[] operator on a pointer, and assign the pointer to 0
|
||||
*/
|
||||
struct delete_array {
|
||||
template<typename T>
|
||||
void operator()(T*& p) const { delete [] p; p = 0; }
|
||||
};
|
||||
|
||||
/**
|
||||
* A class which implements an approximation of
|
||||
* template<typename T>
|
||||
* typedef scoped_resource<T*,delete_item> scoped_ptr<T>;
|
||||
*
|
||||
* It is a convenient synonym for a common usage of @ref scoped_resource.
|
||||
* See scoped_resource for more details on how this class behaves.
|
||||
*
|
||||
* Usage example:
|
||||
* @code
|
||||
* {
|
||||
* const scoped_ptr<Object> ptr(new Object);
|
||||
* ...use ptr as you would a normal Object*...
|
||||
* } // ptr is automatically deleted here
|
||||
* @endcode
|
||||
*
|
||||
* NOTE: use this class only to manage a single object, *never* an array.
|
||||
* Use scoped_array to manage arrays. This distinction is because you
|
||||
* may call delete only on objects allocated with new, delete[] only
|
||||
* on objects allocated with new[].
|
||||
*/
|
||||
template<typename T>
|
||||
struct scoped_ptr : public scoped_resource<T*,delete_item>
|
||||
{
|
||||
explicit scoped_ptr(T* p) : scoped_resource<T*,delete_item>(p) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* This class has identical behaviour to @ref scoped_ptr, except it manages
|
||||
* heap-allocated arrays instead of heap-allocated single objects
|
||||
*
|
||||
* Usage example:
|
||||
* @code
|
||||
* {
|
||||
* const scoped_array<char> ptr(new char[n]);
|
||||
* ...use ptr as you would a normal char*...
|
||||
* } // ptr is automatically deleted here
|
||||
* @endcode
|
||||
*
|
||||
*/
|
||||
template<typename T>
|
||||
struct scoped_array : public scoped_resource<T*,delete_array>
|
||||
{
|
||||
explicit scoped_array(T* p) : scoped_resource<T*,delete_array>(p) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
68
sdl_utils.cpp
Normal file
68
sdl_utils.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "sdl_utils.hpp"
|
||||
|
||||
SDL_Surface* scale_surface(SDL_Surface* surface, int w, int h)
|
||||
{
|
||||
SDL_Surface* const dest = SDL_CreateRGBSurface(SDL_SWSURFACE,w,h,
|
||||
surface->format->BitsPerPixel,
|
||||
surface->format->Rmask,
|
||||
surface->format->Gmask,
|
||||
surface->format->Bmask,
|
||||
surface->format->Amask);
|
||||
if(dest == NULL)
|
||||
return NULL;
|
||||
|
||||
const double xratio = static_cast<double>(surface->w)/
|
||||
static_cast<double>(w);
|
||||
const double yratio = static_cast<double>(surface->h)/
|
||||
static_cast<double>(h);
|
||||
|
||||
const int srcxpad = (surface->w%2) == 1 ? 1:0;
|
||||
const int dstxpad = (dest->w%2) == 1 ? 1:0;
|
||||
|
||||
double ysrc = 0.0;
|
||||
for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) {
|
||||
double xsrc = 0.0;
|
||||
for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) {
|
||||
int xsrcint = static_cast<int>(xsrc);
|
||||
const int ysrcint = static_cast<int>(ysrc);
|
||||
|
||||
xsrcint += srcxpad*ysrcint;
|
||||
|
||||
const int dstpad = dstxpad*ydst;
|
||||
|
||||
reinterpret_cast<short*>(dest->pixels)[ydst*w + xdst + dstpad] =
|
||||
reinterpret_cast<short*>(surface->pixels)[
|
||||
ysrcint*surface->w + xsrcint];
|
||||
}
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
SDL_Surface* get_surface_portion(SDL_Surface* src, SDL_Rect& area)
|
||||
{
|
||||
if(area.x + area.w >= src->w || area.y + area.h >= src->h)
|
||||
return NULL;
|
||||
|
||||
const SDL_PixelFormat* const fmt = src->format;
|
||||
SDL_Surface* const dst = SDL_CreateRGBSurface(0,area.w,area.h,
|
||||
fmt->BitsPerPixel,fmt->Rmask,
|
||||
fmt->Gmask,fmt->Bmask,
|
||||
fmt->Amask);
|
||||
SDL_Rect dstarea = {0,0,0,0};
|
||||
|
||||
SDL_BlitSurface(src,&area,dst,&dstarea);
|
||||
|
||||
return dst;
|
||||
}
|
83
sdl_utils.hpp
Normal file
83
sdl_utils.hpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef SDL_UTILS_INCLUDED
|
||||
#define SDL_UTILS_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "scoped_resource.hpp"
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
struct free_sdl_surface {
|
||||
void operator()(SDL_Surface* surface) const { SDL_FreeSurface(surface); }
|
||||
};
|
||||
|
||||
typedef util::scoped_resource<SDL_Surface*,free_sdl_surface> scoped_sdl_surface;
|
||||
|
||||
SDL_Surface* scale_surface(SDL_Surface* surface, int w, int h);
|
||||
|
||||
SDL_Surface* get_surface_portion(SDL_Surface* src, SDL_Rect& rect);
|
||||
|
||||
struct pixel_data
|
||||
{
|
||||
pixel_data() : r(0), g(0), b(0)
|
||||
{}
|
||||
|
||||
pixel_data(int red, int green, int blue) : r(red), g(green), b(blue)
|
||||
{}
|
||||
|
||||
pixel_data(int pixel, SDL_PixelFormat* fmt) {
|
||||
unformat(pixel, fmt);
|
||||
}
|
||||
|
||||
pixel_data(config& cfg) {
|
||||
read(cfg);
|
||||
}
|
||||
|
||||
int format(SDL_PixelFormat* fmt) const {
|
||||
return SDL_MapRGB(fmt,r,g,b);
|
||||
}
|
||||
|
||||
void unformat(int pixel, SDL_PixelFormat* fmt) {
|
||||
r = ((pixel&fmt->Rmask) >> fmt->Rshift);
|
||||
g = ((pixel&fmt->Gmask) >> fmt->Gshift);
|
||||
b = ((pixel&fmt->Bmask) >> fmt->Bshift);
|
||||
}
|
||||
|
||||
void read(config& cfg) {
|
||||
const std::string& red = cfg.values["red"];
|
||||
const std::string& green = cfg.values["green"];
|
||||
const std::string& blue = cfg.values["blue"];
|
||||
|
||||
if(red.empty())
|
||||
r = 0;
|
||||
else
|
||||
r = atoi(red.c_str());
|
||||
|
||||
if(green.empty())
|
||||
g = 0;
|
||||
else
|
||||
g = atoi(green.c_str());
|
||||
|
||||
if(blue.empty())
|
||||
b = 0;
|
||||
else
|
||||
b = atoi(blue.c_str());
|
||||
}
|
||||
|
||||
int r, g, b;
|
||||
};
|
||||
|
||||
#endif
|
190
sound.cpp
Normal file
190
sound.cpp
Normal file
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "sound.hpp"
|
||||
|
||||
#include "SDL_mixer.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
namespace {
|
||||
|
||||
bool mix_ok = false;
|
||||
std::map<std::string,Mix_Chunk*> sound_cache;
|
||||
std::map<std::string,Mix_Music*> music_cache;
|
||||
|
||||
std::string current_music = "";
|
||||
|
||||
bool music_off = false;
|
||||
bool sound_off = false;
|
||||
|
||||
}
|
||||
|
||||
namespace sound {
|
||||
|
||||
manager::manager()
|
||||
{
|
||||
const int res =
|
||||
Mix_OpenAudio(MIX_DEFAULT_FREQUENCY,MIX_DEFAULT_FORMAT,2,4096);
|
||||
if(res >= 0) {
|
||||
mix_ok = true;
|
||||
} else {
|
||||
mix_ok = false;
|
||||
std::cerr << "Could not initialize audio: " << SDL_GetError() << "\n";
|
||||
}
|
||||
|
||||
Mix_AllocateChannels(8);
|
||||
}
|
||||
|
||||
manager::~manager()
|
||||
{
|
||||
if(!mix_ok)
|
||||
return;
|
||||
|
||||
Mix_HaltMusic();
|
||||
Mix_HaltChannel(-1);
|
||||
|
||||
for(std::map<std::string,Mix_Chunk*>::iterator i = sound_cache.begin();
|
||||
i != sound_cache.end(); ++i) {
|
||||
Mix_FreeChunk(i->second);
|
||||
}
|
||||
|
||||
for(std::map<std::string,Mix_Music*>::iterator j = music_cache.begin();
|
||||
j != music_cache.end(); ++j) {
|
||||
Mix_FreeMusic(j->second);
|
||||
}
|
||||
|
||||
Mix_CloseAudio();
|
||||
}
|
||||
|
||||
void play_music(const std::string& file)
|
||||
{
|
||||
if(!mix_ok || current_music == file)
|
||||
return;
|
||||
|
||||
if(music_off) {
|
||||
current_music = file;
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<std::string,Mix_Music*>::const_iterator itor =
|
||||
music_cache.find(file);
|
||||
if(itor == music_cache.end()) {
|
||||
static const std::string music_prefix = "music/";
|
||||
|
||||
std::string filename;
|
||||
Mix_Music* music = NULL;
|
||||
|
||||
#ifdef WESNOTH_PATH
|
||||
filename = WESNOTH_PATH + std::string("/") + music_prefix + file;
|
||||
music = Mix_LoadMUS(filename.c_str());
|
||||
#endif
|
||||
|
||||
if(music == NULL) {
|
||||
filename = music_prefix + file;
|
||||
music = Mix_LoadMUS(filename.c_str());
|
||||
}
|
||||
|
||||
if(music == NULL) {
|
||||
std::cerr << "Could not load music file '" << filename << "': "
|
||||
<< SDL_GetError() << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
itor = music_cache.insert(std::pair<std::string,Mix_Music*>(
|
||||
file,music)).first;
|
||||
}
|
||||
|
||||
if(Mix_PlayingMusic()) {
|
||||
Mix_FadeOutMusic(500);
|
||||
}
|
||||
|
||||
const int res = Mix_FadeInMusic(itor->second,-1,500);
|
||||
if(res < 0) {
|
||||
std::cerr << "Could not play music: " << SDL_GetError() << "\n";
|
||||
}
|
||||
|
||||
current_music = file;
|
||||
}
|
||||
|
||||
void play_sound(const std::string& file)
|
||||
{
|
||||
if(!mix_ok || sound_off)
|
||||
return;
|
||||
|
||||
std::map<std::string,Mix_Chunk*>::const_iterator itor =
|
||||
sound_cache.find(file);
|
||||
if(itor == sound_cache.end()) {
|
||||
static const std::string sound_prefix = "sounds/";
|
||||
std::string filename;
|
||||
Mix_Chunk* sfx = NULL;
|
||||
|
||||
#ifdef WESNOTH_PATH
|
||||
filename = WESNOTH_PATH + std::string("/") + sound_prefix + file;
|
||||
sfx = Mix_LoadWAV(filename.c_str());
|
||||
#endif
|
||||
|
||||
if(sfx == NULL) {
|
||||
filename = sound_prefix + file;
|
||||
sfx = Mix_LoadWAV(filename.c_str());
|
||||
}
|
||||
|
||||
if(sfx == NULL) {
|
||||
std::cerr << "Could not load sound file '" << filename << "': "
|
||||
<< SDL_GetError() << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
itor = sound_cache.insert(std::pair<std::string,Mix_Chunk*>(
|
||||
file,sfx)).first;
|
||||
}
|
||||
|
||||
//play on the first available channel
|
||||
const int res = Mix_PlayChannel(-1,itor->second,0);
|
||||
if(res < 0) {
|
||||
std::cerr << "error playing sound effect: " << SDL_GetError() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void set_music_volume(double vol)
|
||||
{
|
||||
if(vol < 0.05) {
|
||||
Mix_HaltMusic();
|
||||
music_off = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Mix_VolumeMusic(int(vol*double(MIX_MAX_VOLUME)));
|
||||
|
||||
//if the music was off completely, start playing it again now
|
||||
if(music_off) {
|
||||
music_off = false;
|
||||
const std::string music = current_music;
|
||||
current_music = "";
|
||||
if(!music.empty()) {
|
||||
play_music(music);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_sound_volume(double vol)
|
||||
{
|
||||
if(vol < 0.05) {
|
||||
sound_off = true;
|
||||
return;
|
||||
} else {
|
||||
sound_off = false;
|
||||
Mix_Volume(-1,int(vol*double(MIX_MAX_VOLUME)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
32
sound.hpp
Normal file
32
sound.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef SOUND_HPP_INCLUDED
|
||||
#define SOUND_HPP_INCLUDED
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace sound {
|
||||
|
||||
struct manager {
|
||||
manager();
|
||||
~manager();
|
||||
};
|
||||
|
||||
void play_music(const std::string& file);
|
||||
void play_sound(const std::string& file);
|
||||
|
||||
void set_music_volume(double vol);
|
||||
void set_sound_volume(double vol);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
177
team.cpp
Normal file
177
team.cpp
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "game_config.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "team.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
team::target::target(config& cfg)
|
||||
: criteria(cfg), value(atof(cfg.values["value"].c_str()))
|
||||
{
|
||||
}
|
||||
|
||||
team::team_info::team_info(config& cfg)
|
||||
{
|
||||
gold = cfg.values["gold"];
|
||||
name = cfg.values["name"];
|
||||
aggression = atof(cfg.values["aggression"].c_str());
|
||||
if(aggression == 0.0)
|
||||
aggression = 0.5;
|
||||
|
||||
const std::string& enemies_list = cfg.values["enemy"];
|
||||
if(!enemies_list.empty()) {
|
||||
std::vector<std::string> venemies = config::split(enemies_list);
|
||||
for(std::vector<std::string>::const_iterator i = venemies.begin();
|
||||
i != venemies.end(); ++i) {
|
||||
enemies.push_back(atoi(i->c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
if(cfg.values["controller"] == "human")
|
||||
human = true;
|
||||
else
|
||||
human = false;
|
||||
|
||||
const std::string& leader_val = cfg.values["leader_value"];
|
||||
if(leader_val.empty()) {
|
||||
leader_value = 3.0;
|
||||
} else {
|
||||
leader_value = atof(leader_val.c_str());
|
||||
}
|
||||
|
||||
const std::string& village_val = cfg.values["village_value"];
|
||||
if(village_val.empty()) {
|
||||
village_value = 1.0;
|
||||
} else {
|
||||
village_value = atof(village_val.c_str());
|
||||
}
|
||||
|
||||
std::vector<std::string> recruits = config::split(cfg.values["recruit"]);
|
||||
for(std::vector<std::string>::const_iterator i = recruits.begin();
|
||||
i != recruits.end(); ++i) {
|
||||
can_recruit.insert(*i);
|
||||
}
|
||||
|
||||
recruitment_pattern = config::split(cfg.values["recruitment_pattern"]);
|
||||
|
||||
//default recruitment pattern is to buy 2 fighters for every 1 archer
|
||||
if(recruitment_pattern.empty()) {
|
||||
recruitment_pattern.push_back("fighter");
|
||||
recruitment_pattern.push_back("fighter");
|
||||
recruitment_pattern.push_back("archer");
|
||||
}
|
||||
|
||||
//additional targets
|
||||
std::vector<config*>& tgts = cfg.children["target"];
|
||||
for(std::vector<config*>::iterator tgt = tgts.begin();
|
||||
tgt != tgts.end(); ++tgt) {
|
||||
targets.push_back(target(**tgt));
|
||||
}
|
||||
}
|
||||
|
||||
team::team(config& cfg, int gold) : gold_(gold), info_(cfg)
|
||||
{
|
||||
if(info_.gold.empty() == false)
|
||||
gold_ = ::atoi(info_.gold.c_str());
|
||||
}
|
||||
|
||||
void team::get_tower(const gamemap::location& loc)
|
||||
{
|
||||
towers_.insert(loc);
|
||||
}
|
||||
|
||||
void team::lose_tower(const gamemap::location& loc)
|
||||
{
|
||||
towers_.erase(towers_.find(loc));
|
||||
}
|
||||
|
||||
int team::towers() const
|
||||
{
|
||||
return towers_.size();
|
||||
}
|
||||
|
||||
bool team::owns_tower(const gamemap::location& loc) const
|
||||
{
|
||||
return towers_.count(loc) > 0;
|
||||
}
|
||||
|
||||
int team::gold() const
|
||||
{
|
||||
return gold_;
|
||||
}
|
||||
|
||||
int team::income() const
|
||||
{
|
||||
return towers_.size()*game_config::tower_income+game_config::base_income;
|
||||
}
|
||||
|
||||
void team::new_turn()
|
||||
{
|
||||
gold_ += income();
|
||||
}
|
||||
|
||||
void team::spend_gold(int amount)
|
||||
{
|
||||
gold_ -= amount;
|
||||
}
|
||||
|
||||
const std::set<std::string>& team::recruits() const
|
||||
{
|
||||
return info_.can_recruit;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& team::recruitment_pattern() const
|
||||
{
|
||||
return info_.recruitment_pattern;
|
||||
}
|
||||
|
||||
const std::string& team::name() const
|
||||
{
|
||||
return info_.name;
|
||||
}
|
||||
|
||||
bool team::is_enemy(int n) const
|
||||
{
|
||||
//if enemies aren't listed, then everyone is an enemy
|
||||
if(info_.enemies.empty())
|
||||
return true;
|
||||
|
||||
return std::find(info_.enemies.begin(),info_.enemies.end(),n) !=
|
||||
info_.enemies.end();
|
||||
}
|
||||
|
||||
double team::aggression() const
|
||||
{
|
||||
return info_.aggression;
|
||||
}
|
||||
|
||||
bool team::is_human() const
|
||||
{
|
||||
return info_.human;
|
||||
}
|
||||
|
||||
double team::leader_value() const
|
||||
{
|
||||
return info_.leader_value;
|
||||
}
|
||||
|
||||
double team::village_value() const
|
||||
{
|
||||
return info_.village_value;
|
||||
}
|
||||
|
||||
std::vector<team::target>& team::targets()
|
||||
{
|
||||
return info_.targets;
|
||||
}
|
80
team.hpp
Normal file
80
team.hpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef TEAM_H_INCLUDED
|
||||
#define TEAM_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "map.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class team
|
||||
{
|
||||
public:
|
||||
|
||||
struct target {
|
||||
explicit target(config& cfg);
|
||||
config criteria;
|
||||
double value;
|
||||
};
|
||||
|
||||
struct team_info
|
||||
{
|
||||
team_info(config& cfg);
|
||||
std::string name;
|
||||
std::string gold;
|
||||
std::set<std::string> can_recruit;
|
||||
std::vector<std::string> recruitment_pattern;
|
||||
double aggression;
|
||||
std::vector<int> enemies;
|
||||
bool human;
|
||||
|
||||
double leader_value, village_value;
|
||||
|
||||
std::vector<target> targets;
|
||||
};
|
||||
|
||||
team(config& cfg, int gold=100);
|
||||
void get_tower(const gamemap::location&);
|
||||
void lose_tower(const gamemap::location&);
|
||||
int towers() const;
|
||||
bool owns_tower(const gamemap::location&) const;
|
||||
|
||||
int gold() const;
|
||||
int income() const;
|
||||
void new_turn();
|
||||
void spend_gold(int amount);
|
||||
|
||||
const std::set<std::string>& recruits() const;
|
||||
const std::vector<std::string>& recruitment_pattern() const;
|
||||
const std::string& name() const;
|
||||
|
||||
bool is_enemy(int side) const;
|
||||
double aggression() const;
|
||||
|
||||
bool is_human() const;
|
||||
|
||||
double leader_value() const;
|
||||
double village_value() const;
|
||||
|
||||
std::vector<target>& targets();
|
||||
private:
|
||||
int gold_;
|
||||
std::set<gamemap::location> towers_;
|
||||
|
||||
team_info info_;
|
||||
};
|
||||
|
||||
#endif
|
80
terrain.cpp
Normal file
80
terrain.cpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "terrain.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
|
||||
terrain_type::terrain_type() : image_("void"), type_(' '), letter_(' ')
|
||||
{}
|
||||
|
||||
terrain_type::terrain_type(config& cfg)
|
||||
{
|
||||
image_ = cfg.values["image"];
|
||||
name_ = cfg.values["name"];
|
||||
const std::string& letter = cfg.values["char"];
|
||||
assert(!letter.empty());
|
||||
letter_ = letter[0];
|
||||
|
||||
const std::string& alias = cfg.values["aliasof"];
|
||||
if(alias.empty())
|
||||
type_ = letter_;
|
||||
else
|
||||
type_ = alias[0];
|
||||
|
||||
colour_.read(cfg);
|
||||
}
|
||||
|
||||
const std::string& terrain_type::image() const
|
||||
{
|
||||
return image_;
|
||||
}
|
||||
|
||||
const std::string& terrain_type::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
char terrain_type::letter() const
|
||||
{
|
||||
return letter_;
|
||||
}
|
||||
|
||||
char terrain_type::type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
pixel_data terrain_type::get_rgb() const
|
||||
{
|
||||
return colour_;
|
||||
}
|
||||
|
||||
bool terrain_type::is_alias() const
|
||||
{
|
||||
return type_ != letter_;
|
||||
}
|
||||
|
||||
void create_terrain_maps(std::vector<config*>& cfgs,
|
||||
std::vector<char>& terrain_precedence,
|
||||
std::map<char,terrain_type>& letter_to_terrain,
|
||||
std::map<std::string,terrain_type>& str_to_terrain)
|
||||
{
|
||||
for(std::vector<config*>::iterator i = cfgs.begin(); i != cfgs.end(); ++i) {
|
||||
terrain_type terrain(**i);
|
||||
terrain_precedence.push_back(terrain.letter());
|
||||
letter_to_terrain.insert(std::pair<char,terrain_type>(
|
||||
terrain.letter(),terrain));
|
||||
str_to_terrain.insert(std::pair<std::string,terrain_type>(
|
||||
terrain.name(),terrain));
|
||||
}
|
||||
}
|
51
terrain.hpp
Normal file
51
terrain.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef TERRAIN_H_INCLUDED
|
||||
#define TERRAIN_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "sdl_utils.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
class terrain_type
|
||||
{
|
||||
public:
|
||||
terrain_type();
|
||||
terrain_type(config& cfg);
|
||||
|
||||
const std::string& image() const;
|
||||
const std::string& name() const;
|
||||
char letter() const;
|
||||
char type() const;
|
||||
|
||||
pixel_data get_rgb() const;
|
||||
|
||||
bool is_alias() const;
|
||||
private:
|
||||
std::string image_, name_;
|
||||
|
||||
//the 'letter' is the letter that represents this
|
||||
//terrain type. The 'type' is the letter of the
|
||||
//terrain type which this is equivalent to, which
|
||||
//may be the same as 'letter'
|
||||
char type_, letter_;
|
||||
|
||||
pixel_data colour_;
|
||||
};
|
||||
|
||||
void create_terrain_maps(std::vector<config*>& cfgs,
|
||||
std::vector<char>& terrain_precedence,
|
||||
std::map<char,terrain_type>& letter_to_terrain,
|
||||
std::map<std::string,terrain_type>& str_to_terrain);
|
||||
|
||||
#endif
|
640
unit.cpp
Normal file
640
unit.cpp
Normal file
|
@ -0,0 +1,640 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "game_config.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "unit.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace {
|
||||
const std::string ModificationTypes[] = { "object", "trait" };
|
||||
const int NumModificationTypes = sizeof(ModificationTypes)/
|
||||
sizeof(*ModificationTypes);
|
||||
}
|
||||
|
||||
bool compare_unit_values::operator()(const unit& a, const unit& b) const
|
||||
{
|
||||
const int lvla = a.type().level();
|
||||
const int lvlb = b.type().level();
|
||||
|
||||
const std::string& namea = a.type().name();
|
||||
const std::string& nameb = b.type().name();
|
||||
|
||||
const int xpa = a.max_experience() - a.experience();
|
||||
const int xpb = b.max_experience() - b.experience();
|
||||
|
||||
return lvla > lvlb || lvla == lvlb && namea < nameb ||
|
||||
lvla == lvlb && namea == nameb && xpa < xpb;
|
||||
}
|
||||
|
||||
//constructor for reading a unit
|
||||
unit::unit(game_data& data, config& cfg) : moves_(0), facingLeft_(true),
|
||||
recruit_(false),
|
||||
state_(STATE_NORMAL),
|
||||
guardian_(false)
|
||||
{
|
||||
read(data,cfg);
|
||||
}
|
||||
|
||||
//constructor for creating a new unit
|
||||
unit::unit(const unit_type* t, int side, bool use_traits) :
|
||||
type_(t), facingLeft_(side != 1), state_(STATE_NORMAL),
|
||||
hitpoints_(t->hitpoints()),
|
||||
experience_(0), side_(side), moves_(0),
|
||||
recruit_(false), attacks_(t->attacks()),
|
||||
backupAttacks_(t->attacks()),
|
||||
maxHitpoints_(t->hitpoints()),
|
||||
backupMaxHitpoints_(t->hitpoints()),
|
||||
maxMovement_(t->movement()),
|
||||
backupMaxMovement_(t->movement()),
|
||||
maxExperience_(t->experience_needed()),
|
||||
backupMaxExperience_(t->experience_needed()),
|
||||
guardian_(false)
|
||||
{
|
||||
//calculate the unit's traits
|
||||
std::vector<config*> traits = t->possible_traits();
|
||||
const int num_traits = 2;
|
||||
if(use_traits && traits.size() >= num_traits) {
|
||||
std::set<int> chosen_traits;
|
||||
for(int i = 0; i != num_traits; ++i) {
|
||||
int num = recorder.get_random()%(traits.size()-i);
|
||||
while(chosen_traits.count(num)) {
|
||||
++num;
|
||||
}
|
||||
|
||||
chosen_traits.insert(num);
|
||||
|
||||
add_modification("trait",*traits[num]);
|
||||
|
||||
}
|
||||
|
||||
//build the traits description, making sure the traits are always
|
||||
//in the same order.
|
||||
for(std::set<int>::const_iterator itor = chosen_traits.begin();
|
||||
itor != chosen_traits.end(); ++itor) {
|
||||
traitsDescription_ += traits[*itor]->values["name"];
|
||||
traitsDescription_ += ",";
|
||||
}
|
||||
|
||||
//get rid of the trailing comma
|
||||
if(!traitsDescription_.empty())
|
||||
traitsDescription_.resize(traitsDescription_.size()-1);
|
||||
}
|
||||
}
|
||||
|
||||
//constructor for advancing a unit from a lower level
|
||||
unit::unit(const unit_type* t, const unit& u) :
|
||||
type_(t), facingLeft_(u.facingLeft_), state_(STATE_NORMAL),
|
||||
hitpoints_(t->hitpoints()),
|
||||
experience_(0), side_(u.side()), moves_(u.moves_),
|
||||
recruit_(u.recruit_), description_(u.description_),
|
||||
role_(u.role_), statusFlags_(u.statusFlags_),
|
||||
attacks_(t->attacks()), backupAttacks_(t->attacks()),
|
||||
maxHitpoints_(t->hitpoints()),
|
||||
backupMaxHitpoints_(t->hitpoints()),
|
||||
maxMovement_(t->movement()),
|
||||
backupMaxMovement_(t->movement()),
|
||||
maxExperience_(t->experience_needed()),
|
||||
backupMaxExperience_(t->experience_needed()),
|
||||
modifications_(u.modifications_),
|
||||
traitsDescription_(u.traitsDescription_),
|
||||
guardian_(false)
|
||||
{
|
||||
//apply modifications etc, refresh the unit
|
||||
new_level();
|
||||
}
|
||||
|
||||
const unit_type& unit::type() const
|
||||
{
|
||||
return *type_;
|
||||
}
|
||||
|
||||
std::string unit::name() const
|
||||
{
|
||||
if(description_.empty() == false)
|
||||
return description_;
|
||||
else
|
||||
return type().language_name();
|
||||
}
|
||||
|
||||
const std::string& unit::description() const
|
||||
{
|
||||
return description_;
|
||||
}
|
||||
|
||||
int unit::side() const
|
||||
{
|
||||
return side_;
|
||||
}
|
||||
|
||||
double unit::alpha() const
|
||||
{
|
||||
return type().alpha();
|
||||
}
|
||||
|
||||
void unit::make_recruiter()
|
||||
{
|
||||
recruit_ = true;
|
||||
}
|
||||
|
||||
bool unit::can_recruit() const
|
||||
{
|
||||
return recruit_;
|
||||
}
|
||||
|
||||
int unit::total_movement() const
|
||||
{
|
||||
return maxMovement_;
|
||||
}
|
||||
|
||||
int unit::movement_left() const
|
||||
{
|
||||
return moves_ < 0 ? 0 : moves_;
|
||||
}
|
||||
|
||||
bool unit::can_attack() const
|
||||
{
|
||||
return moves_ != -1;
|
||||
}
|
||||
|
||||
void unit::set_movement(int moves)
|
||||
{
|
||||
if(moves_ != -1)
|
||||
moves_ = moves;
|
||||
}
|
||||
|
||||
void unit::set_attacked()
|
||||
{
|
||||
moves_ = -1;
|
||||
}
|
||||
|
||||
void unit::new_turn()
|
||||
{
|
||||
moves_ = total_movement();
|
||||
if(type().has_ability("ambush"))
|
||||
set_flag("ambush");
|
||||
}
|
||||
|
||||
void unit::end_turn()
|
||||
{
|
||||
remove_flag("slowed");
|
||||
}
|
||||
|
||||
void unit::new_level()
|
||||
{
|
||||
//revert stats to the beginning of the level
|
||||
attacks_ = backupAttacks_;
|
||||
maxHitpoints_ = backupMaxHitpoints_;
|
||||
maxMovement_ = backupMaxMovement_;
|
||||
maxExperience_ = backupMaxExperience_;
|
||||
|
||||
//reapply all permanent modifications
|
||||
apply_modifications();
|
||||
|
||||
heal_all();
|
||||
statusFlags_.clear();
|
||||
}
|
||||
|
||||
int unit::hitpoints() const
|
||||
{
|
||||
return hitpoints_;
|
||||
}
|
||||
|
||||
int unit::max_hitpoints() const
|
||||
{
|
||||
return maxHitpoints_;
|
||||
}
|
||||
|
||||
int unit::experience() const
|
||||
{
|
||||
return experience_;
|
||||
}
|
||||
|
||||
int unit::max_experience() const
|
||||
{
|
||||
return maxExperience_;
|
||||
}
|
||||
|
||||
bool unit::get_experience(int xp)
|
||||
{
|
||||
experience_ += xp;
|
||||
if(experience_ > max_experience())
|
||||
experience_ = max_experience();
|
||||
return advances();
|
||||
}
|
||||
|
||||
bool unit::advances() const
|
||||
{
|
||||
return experience_ >= max_experience() && !type().advances_to().empty();
|
||||
}
|
||||
|
||||
bool unit::gets_hit(int damage)
|
||||
{
|
||||
hitpoints_ -= damage;
|
||||
if(hitpoints_ > max_hitpoints() && damage > 0)
|
||||
hitpoints_ = max_hitpoints();
|
||||
return hitpoints_ <= 0;
|
||||
}
|
||||
|
||||
void unit::heal()
|
||||
{
|
||||
heal(game_config::heal_amount);
|
||||
}
|
||||
|
||||
void unit::heal(int amount)
|
||||
{
|
||||
if(hitpoints_ < max_hitpoints()) {
|
||||
hitpoints_ += amount;
|
||||
if(hitpoints_ > max_hitpoints())
|
||||
hitpoints_ = max_hitpoints();
|
||||
}
|
||||
}
|
||||
|
||||
void unit::heal_all()
|
||||
{
|
||||
hitpoints_ = max_hitpoints();
|
||||
}
|
||||
|
||||
bool unit::invisible(gamemap::TERRAIN terrain) const
|
||||
{
|
||||
static const std::string forest_invisible("ambush");
|
||||
if(terrain == gamemap::FOREST && has_flag(forest_invisible)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool unit::matches_filter(config& cfg) const
|
||||
{
|
||||
const std::string& description = cfg.values["description"];
|
||||
const std::string& type = cfg.values["type"];
|
||||
const std::string& ability = cfg.values["ability"];
|
||||
const std::string& side = cfg.values["side"];
|
||||
const std::string& weapon = cfg.values["has_weapon"];
|
||||
const std::string& role = cfg.values["role"];
|
||||
|
||||
if(description.empty() == false && description != this->description()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& this_type = this->type().name();
|
||||
|
||||
//the type could be a comma-seperated list of types
|
||||
if(type.empty() == false && type != this_type) {
|
||||
|
||||
//we only do the full CSV search if we find a comma in there,
|
||||
//and if the subsequence is found within the main sequence. This
|
||||
//is because doing the full CSV split is expensive
|
||||
if(std::find(type.begin(),type.end(),',') != type.end() &&
|
||||
std::search(type.begin(),type.end(),this_type.begin(),this_type.end()) !=
|
||||
type.end()) {
|
||||
const std::vector<std::string>& vals = config::split(type);
|
||||
|
||||
if(std::find(vals.begin(),vals.end(),this_type) == vals.end()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(ability.empty() == false && this->type().has_ability(ability) == false)
|
||||
return false;
|
||||
|
||||
if(side.empty() == false && this->side() != atoi(side.c_str()))
|
||||
return false;
|
||||
|
||||
if(weapon.empty() == false) {
|
||||
bool has_weapon = false;
|
||||
const std::vector<attack_type>& attacks = this->type().attacks();
|
||||
for(std::vector<attack_type>::const_iterator i = attacks.begin();
|
||||
i != attacks.end(); ++i) {
|
||||
if(i->name() == weapon) {
|
||||
has_weapon = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!has_weapon)
|
||||
return false;
|
||||
}
|
||||
|
||||
if(role.empty() == false && role_ != role)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void unit::set_flag(const std::string& flag)
|
||||
{
|
||||
statusFlags_.insert(flag);
|
||||
}
|
||||
|
||||
void unit::remove_flag(const std::string& flag)
|
||||
{
|
||||
statusFlags_.erase(flag);
|
||||
}
|
||||
|
||||
bool unit::has_flag(const std::string& flag) const
|
||||
{
|
||||
return statusFlags_.count(flag) != 0;
|
||||
}
|
||||
|
||||
void unit::read(game_data& data, config& cfg)
|
||||
{
|
||||
std::map<std::string,unit_type>::iterator i = data.unit_types.find(
|
||||
cfg.values["type"]);
|
||||
if(i != data.unit_types.end())
|
||||
type_ = &i->second;
|
||||
else
|
||||
throw gamestatus::load_game_failed("Unit not found: '"
|
||||
+ cfg.values["type"] + "'");
|
||||
|
||||
attacks_ = type_->attacks();
|
||||
backupAttacks_ = attacks_;
|
||||
maxHitpoints_ = type_->hitpoints();
|
||||
backupMaxHitpoints_ = type_->hitpoints();
|
||||
maxMovement_ = type_->movement();
|
||||
backupMaxMovement_ = type_->movement();
|
||||
maxExperience_ = type_->experience_needed();
|
||||
backupMaxExperience_ = type_->experience_needed();
|
||||
|
||||
const std::string& hitpoints = cfg.values["hitpoints"];
|
||||
if(hitpoints.size() == 0)
|
||||
hitpoints_ = type().hitpoints();
|
||||
else
|
||||
hitpoints_ = atoi(hitpoints.c_str());
|
||||
|
||||
const std::string& experience = cfg.values["experience"];
|
||||
if(experience.size() == 0)
|
||||
experience_ = 0;
|
||||
else
|
||||
experience_ = atoi(experience.c_str());
|
||||
|
||||
|
||||
side_ = atoi(cfg.values["side"].c_str());
|
||||
description_ = cfg.values["description"];
|
||||
traitsDescription_ = cfg.values["traits_description"];
|
||||
const std::map<std::string,std::string>::const_iterator recruit_itor =
|
||||
cfg.values.find("canrecruit");
|
||||
if(recruit_itor != cfg.values.end() && recruit_itor->second == "1") {
|
||||
recruit_ = true;
|
||||
}
|
||||
|
||||
const std::vector<config*>& mods = cfg.children["modifications"];
|
||||
if(!mods.empty()) {
|
||||
modifications_ = *mods.front();
|
||||
apply_modifications();
|
||||
}
|
||||
|
||||
const std::string& facing = cfg.values["facing"];
|
||||
if(facing == "reverse")
|
||||
facingLeft_ = false;
|
||||
else
|
||||
facingLeft_ = true;
|
||||
|
||||
const std::string& ai_special = cfg.values["ai_special"];
|
||||
if(ai_special == "guardian") {
|
||||
guardian_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void unit::write(config& cfg) const
|
||||
{
|
||||
cfg.values["type"] = type_->name();
|
||||
|
||||
std::stringstream hp;
|
||||
hp << hitpoints_;
|
||||
cfg.values["hitpoints"] = hp.str();
|
||||
|
||||
std::stringstream xp;
|
||||
xp << experience_;
|
||||
cfg.values["experience"] = xp.str();
|
||||
|
||||
std::stringstream sd;
|
||||
sd << side_;
|
||||
cfg.values["side"] = sd.str();
|
||||
|
||||
cfg.values["description"] = description_;
|
||||
|
||||
cfg.values["traits_description"] = traitsDescription_;
|
||||
|
||||
if(can_recruit())
|
||||
cfg.values["canrecruit"] = "1";
|
||||
|
||||
cfg.children["modifications"].push_back(new config(modifications_));
|
||||
|
||||
cfg.values["facing"] = facingLeft_ ? "normal" : "reverse";
|
||||
}
|
||||
|
||||
void unit::assign_role(const std::string& role)
|
||||
{
|
||||
role_ = role;
|
||||
}
|
||||
|
||||
const std::vector<attack_type>& unit::attacks() const
|
||||
{
|
||||
return attacks_;
|
||||
}
|
||||
|
||||
int unit::movement_cost(const gamemap& map, gamemap::TERRAIN terrain) const
|
||||
{
|
||||
const int res = type_->movement_type().movement_cost(map,terrain);
|
||||
|
||||
static const std::string slowed_string("slowed");
|
||||
if(has_flag(slowed_string)) {
|
||||
return res*2;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
double unit::defense_modifier(const gamemap& map,
|
||||
gamemap::TERRAIN terrain) const
|
||||
{
|
||||
return type_->movement_type().defense_modifier(map,terrain);
|
||||
}
|
||||
|
||||
int unit::damage_against(const attack_type& attack) const
|
||||
{
|
||||
return type_->movement_type().damage_against(attack);
|
||||
}
|
||||
|
||||
const std::string& unit::image() const
|
||||
{
|
||||
switch(state_) {
|
||||
case STATE_NORMAL: return type_->image();
|
||||
case STATE_DEFENDING: return type_->image_defensive();
|
||||
case STATE_ATTACKING: {
|
||||
if(attackType_ == NULL)
|
||||
return type_->image();
|
||||
|
||||
const std::string* const img =
|
||||
attackType_->get_frame(attackingMilliseconds_);
|
||||
|
||||
if(img == NULL)
|
||||
return type_->image();
|
||||
else
|
||||
return *img;
|
||||
}
|
||||
default: return type_->image();
|
||||
}
|
||||
}
|
||||
|
||||
void unit::set_defending(bool newval)
|
||||
{
|
||||
state_ = newval ? STATE_DEFENDING : STATE_NORMAL;
|
||||
}
|
||||
|
||||
void unit::set_attacking(bool newval, const attack_type* type, int ms)
|
||||
{
|
||||
state_ = newval ? STATE_ATTACKING : STATE_NORMAL;
|
||||
attackType_ = type;
|
||||
attackingMilliseconds_ = ms;
|
||||
}
|
||||
|
||||
bool unit::facing_left() const
|
||||
{
|
||||
return facingLeft_;
|
||||
}
|
||||
|
||||
void unit::set_facing_left(bool newval)
|
||||
{
|
||||
facingLeft_ = newval;
|
||||
}
|
||||
|
||||
const std::string& unit::traits_description() const
|
||||
{
|
||||
return traitsDescription_;
|
||||
}
|
||||
|
||||
int unit::value() const
|
||||
{
|
||||
return type().cost();
|
||||
}
|
||||
|
||||
bool unit::is_guardian() const
|
||||
{
|
||||
return guardian_;
|
||||
}
|
||||
|
||||
void unit::add_modification(const std::string& type, config& mod, bool no_add)
|
||||
{
|
||||
const std::string& span = mod.values["duration"];
|
||||
|
||||
if(no_add == false && (span.empty() || span == "forever"))
|
||||
modifications_.children[type].push_back(new config(mod));
|
||||
|
||||
const std::vector<config*>& effects = mod.children["effect"];
|
||||
for(std::vector<config*>::const_iterator i = effects.begin();
|
||||
i != effects.end(); ++i) {
|
||||
|
||||
const std::string& apply_to = (*i)->values["apply_to"];
|
||||
|
||||
if(apply_to == "new_attack") {
|
||||
attacks_.push_back(attack_type(**i));
|
||||
} else if(apply_to == "attack") {
|
||||
for(std::vector<attack_type>::iterator a = attacks_.begin();
|
||||
a != attacks_.end(); ++a) {
|
||||
a->apply_modification(**i);
|
||||
}
|
||||
} else if(apply_to == "hitpoints") {
|
||||
const std::string& increase_hp = (*i)->values["increase"];
|
||||
const std::string& heal_full = (*i)->values["heal_full"];
|
||||
const std::string& increase_total = (*i)->values["increase_total"];
|
||||
const std::string& mult_total = (*i)->values["multiply_total"];
|
||||
|
||||
//if the hitpoints are allowed to end up greater than max hitpoints
|
||||
const std::string& violate_max = (*i)->values["violate_maximum"];
|
||||
|
||||
if(increase_total.empty() == false) {
|
||||
const int increase = atoi(increase_total.c_str());
|
||||
maxHitpoints_ += increase;
|
||||
}
|
||||
|
||||
if(mult_total.empty() == false) {
|
||||
const double factor = atoi(mult_total.c_str());
|
||||
maxHitpoints_ = int(double(maxHitpoints_)*factor);
|
||||
}
|
||||
|
||||
if(maxHitpoints_ < 1)
|
||||
maxHitpoints_ = 1;
|
||||
|
||||
if(heal_full.empty() == false && heal_full != "no") {
|
||||
heal_all();
|
||||
}
|
||||
|
||||
if(increase_hp.empty() == false) {
|
||||
const int increase = atoi(increase_hp.c_str());
|
||||
hitpoints_ += increase;
|
||||
}
|
||||
|
||||
if(hitpoints_ > maxHitpoints_ && violate_max.empty())
|
||||
hitpoints_ = maxHitpoints_;
|
||||
|
||||
if(hitpoints_ < 1)
|
||||
hitpoints_ = 1;
|
||||
} else if(apply_to == "movement") {
|
||||
const std::string& increase = (*i)->values["increase"];
|
||||
const std::string& mult = (*i)->values["multiply"];
|
||||
const std::string& set_to = (*i)->values["set"];
|
||||
|
||||
if(increase.empty() == false) {
|
||||
maxMovement_ += atoi(increase.c_str());
|
||||
if(maxMovement_ < 1)
|
||||
maxMovement_ = 1;
|
||||
}
|
||||
|
||||
if(mult.empty() == false) {
|
||||
maxMovement_ = int(double(maxMovement_)*atof(mult.c_str()));
|
||||
}
|
||||
|
||||
if(set_to.empty() == false) {
|
||||
maxMovement_ = atoi(set_to.c_str());
|
||||
}
|
||||
|
||||
if(moves_ > maxMovement_)
|
||||
moves_ = maxMovement_;
|
||||
} else if(apply_to == "max_experience") {
|
||||
const std::string& increase = (*i)->values["increase"];
|
||||
const std::string& multiply = (*i)->values["multiply"];
|
||||
if(increase.empty() == false) {
|
||||
maxExperience_ += atoi(increase.c_str());
|
||||
}
|
||||
|
||||
if(multiply.empty() == false) {
|
||||
maxExperience_ = int(double(maxExperience_)*
|
||||
atof(multiply.c_str()));
|
||||
}
|
||||
|
||||
if(maxExperience_ < 1) {
|
||||
maxExperience_ = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unit::apply_modifications()
|
||||
{
|
||||
for(int i = 0; i != NumModificationTypes; ++i) {
|
||||
const std::string& mod = ModificationTypes[i];
|
||||
std::vector<config*>& mods = modifications_.children[mod];
|
||||
for(std::vector<config*>::iterator j = mods.begin();
|
||||
j != mods.end(); ++j) {
|
||||
add_modification(ModificationTypes[i],**j,true);
|
||||
}
|
||||
}
|
||||
}
|
143
unit.hpp
Normal file
143
unit.hpp
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef UNIT_H_INCLUDED
|
||||
#define UNIT_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "map.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class unit
|
||||
{
|
||||
public:
|
||||
unit(game_data& data, config& cfg);
|
||||
unit(const unit_type* t, int side, bool use_traits=false);
|
||||
|
||||
//a constructor used when advancing a unit
|
||||
unit(const unit_type* t, const unit& u);
|
||||
const unit_type& type() const;
|
||||
std::string name() const;
|
||||
const std::string& description() const;
|
||||
int hitpoints() const;
|
||||
int max_hitpoints() const;
|
||||
int experience() const;
|
||||
int max_experience() const;
|
||||
bool get_experience(int xp);
|
||||
bool advances() const;
|
||||
int side() const;
|
||||
double alpha() const;
|
||||
void make_recruiter();
|
||||
bool can_recruit() const;
|
||||
int total_movement() const;
|
||||
int movement_left() const;
|
||||
bool can_attack() const;
|
||||
void set_movement(int moves);
|
||||
void set_attacked();
|
||||
void new_turn();
|
||||
void end_turn();
|
||||
void new_level();
|
||||
|
||||
bool gets_hit(int damage);
|
||||
void heal();
|
||||
void heal(int amount);
|
||||
void heal_all();
|
||||
|
||||
bool invisible(gamemap::TERRAIN terrain) const;
|
||||
|
||||
bool matches_filter(config& cfg) const;
|
||||
|
||||
void set_flag(const std::string& flag);
|
||||
void remove_flag(const std::string& flag);
|
||||
bool has_flag(const std::string& flag) const;
|
||||
|
||||
void read(game_data& data, config& cfg);
|
||||
|
||||
void write(config& cfg) const;
|
||||
|
||||
void assign_role(const std::string& role);
|
||||
|
||||
const std::vector<attack_type>& attacks() const;
|
||||
|
||||
int movement_cost(const gamemap& map, gamemap::TERRAIN terrain) const;
|
||||
double defense_modifier(const gamemap& map, gamemap::TERRAIN terrain) const;
|
||||
int damage_against(const attack_type& attack) const;
|
||||
|
||||
//gets the unit image that should currently be displayed
|
||||
//(could be in the middle of an attack etc)
|
||||
const std::string& image() const;
|
||||
|
||||
void set_defending(bool newval);
|
||||
void set_attacking(bool newval, const attack_type* type=NULL, int ms=0);
|
||||
|
||||
bool facing_left() const;
|
||||
void set_facing_left(bool newval);
|
||||
|
||||
const std::string& traits_description() const;
|
||||
|
||||
int value() const;
|
||||
bool is_guardian() const;
|
||||
|
||||
void add_modification(const std::string& type, config& modification,
|
||||
bool no_add=false);
|
||||
|
||||
|
||||
private:
|
||||
const unit_type* type_;
|
||||
|
||||
bool facingLeft_;
|
||||
|
||||
enum STATE { STATE_NORMAL, STATE_ATTACKING, STATE_DEFENDING };
|
||||
STATE state_;
|
||||
const attack_type* attackType_;
|
||||
int attackingMilliseconds_;
|
||||
|
||||
int hitpoints_;
|
||||
int maxHitpoints_, backupMaxHitpoints_;
|
||||
int experience_;
|
||||
int maxExperience_, backupMaxExperience_;
|
||||
|
||||
int side_;
|
||||
|
||||
//is set to the number of moves left, and -1 if the unit has attacked
|
||||
int moves_;
|
||||
int maxMovement_, backupMaxMovement_;
|
||||
|
||||
std::string description_;
|
||||
|
||||
bool recruit_;
|
||||
|
||||
std::string role_;
|
||||
|
||||
std::set<std::string> statusFlags_;
|
||||
|
||||
std::vector<attack_type> attacks_;
|
||||
std::vector<attack_type> backupAttacks_;
|
||||
|
||||
config modifications_;
|
||||
|
||||
std::string traitsDescription_;
|
||||
|
||||
bool guardian_;
|
||||
|
||||
void apply_modifications();
|
||||
};
|
||||
|
||||
struct compare_unit_values
|
||||
{
|
||||
bool operator()(const unit& a, const unit& b) const;
|
||||
};
|
||||
|
||||
#endif
|
563
unit_types.cpp
Normal file
563
unit_types.cpp
Normal file
|
@ -0,0 +1,563 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "gamestatus.hpp"
|
||||
#include "language.hpp"
|
||||
#include "unit_types.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
//these headers are used to check for file existence
|
||||
#ifdef linux
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
attack_type::attack_type(config& cfg)
|
||||
{
|
||||
name_ = cfg.values["name"];
|
||||
type_ = cfg.values["type"];
|
||||
special_ = cfg.values["special"];
|
||||
range_ = cfg.values["range"] == "long" ? LONG_RANGE : SHORT_RANGE;
|
||||
damage_ = atol(cfg.values["damage"].c_str());
|
||||
num_attacks_ = atol(cfg.values["number"].c_str());
|
||||
|
||||
std::vector<config*>& frames = cfg.children["frame"];
|
||||
std::vector<config*>::iterator i;
|
||||
for(i = frames.begin(); i != frames.end(); ++i){
|
||||
const int beg = atoi((*i)->values["begin"].c_str());
|
||||
const int end = atoi((*i)->values["end"].c_str());
|
||||
const std::string& img = (*i)->values["image"];
|
||||
frames_[UNIT_FRAME].push_back(frame(beg,end,img));
|
||||
}
|
||||
|
||||
std::vector<config*>& missile_frames = cfg.children["missile_frame"];
|
||||
for(i = missile_frames.begin(); i != missile_frames.end(); ++i){
|
||||
const int beg = atoi((*i)->values["begin"].c_str());
|
||||
const int end = atoi((*i)->values["end"].c_str());
|
||||
const std::string& img = (*i)->values["image"];
|
||||
const std::string& img_diag = (*i)->values["image_diagonal"];
|
||||
if(img_diag.empty())
|
||||
frames_[MISSILE_FRAME].push_back(frame(beg,end,img));
|
||||
else
|
||||
frames_[MISSILE_FRAME].push_back(frame(beg,end,img,img_diag));
|
||||
|
||||
}
|
||||
|
||||
std::vector<config*>& sounds = cfg.children["sound"];
|
||||
for(i = sounds.begin(); i != sounds.end(); ++i) {
|
||||
sfx sound;
|
||||
sound.time = atoi((*i)->values["time"].c_str());
|
||||
sound.on_hit = (*i)->values["sound"];
|
||||
sound.on_miss = (*i)->values["sound_miss"];
|
||||
if(sound.on_miss.empty())
|
||||
sound.on_miss = sound.on_hit;
|
||||
|
||||
if(sound.on_miss == "null")
|
||||
sound.on_miss = "";
|
||||
|
||||
sfx_.push_back(sound);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& attack_type::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
const std::string& attack_type::type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
const std::string& attack_type::special() const
|
||||
{
|
||||
return special_;
|
||||
}
|
||||
|
||||
attack_type::RANGE attack_type::range() const
|
||||
{
|
||||
return range_;
|
||||
}
|
||||
|
||||
int attack_type::damage() const
|
||||
{
|
||||
return damage_;
|
||||
}
|
||||
|
||||
int attack_type::num_attacks() const
|
||||
{
|
||||
return num_attacks_;
|
||||
}
|
||||
|
||||
int attack_type::get_first_frame(attack_type::FRAME_TYPE type) const
|
||||
{
|
||||
if(frames_[type].empty())
|
||||
return 0;
|
||||
else
|
||||
return minimum<int>(frames_[type].front().start,0);
|
||||
}
|
||||
|
||||
int attack_type::get_last_frame(attack_type::FRAME_TYPE type) const
|
||||
{
|
||||
if(frames_[type].empty())
|
||||
return 0;
|
||||
else
|
||||
return maximum<int>(frames_[type].back().end,0);
|
||||
}
|
||||
|
||||
const std::string* attack_type::get_frame(int milliseconds,
|
||||
attack_type::FRAME_TYPE type,
|
||||
attack_type::FRAME_DIRECTION dir) const
|
||||
{
|
||||
for(std::vector<frame>::const_iterator i = frames_[type].begin();
|
||||
i != frames_[type].end(); ++i) {
|
||||
if(i->start > milliseconds)
|
||||
return NULL;
|
||||
|
||||
if(i->start <= milliseconds && i->end > milliseconds) {
|
||||
if(dir == DIAGONAL && i->image_diagonal != NULL)
|
||||
return i->image_diagonal;
|
||||
else
|
||||
return i->image;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const std::vector<attack_type::sfx>& attack_type::sound_effects() const
|
||||
{
|
||||
return sfx_;
|
||||
}
|
||||
|
||||
bool attack_type::matches_filter(config& cfg) const
|
||||
{
|
||||
const std::string& filter_range = cfg.values["range"];
|
||||
const std::string& filter_name = cfg.values["name"];
|
||||
const std::string& filter_type = cfg.values["type"];
|
||||
const std::string& filter_special = cfg.values["special"];
|
||||
|
||||
if(filter_range.empty() == false) {
|
||||
if(filter_range == "short" && range() == LONG_RANGE ||
|
||||
filter_range == "long" && range() == SHORT_RANGE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(filter_name.empty() == false && filter_name != name())
|
||||
return false;
|
||||
|
||||
if(filter_type.empty() == false && filter_type != type())
|
||||
return false;
|
||||
|
||||
if(filter_special.empty() == false && filter_special != special())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void attack_type::apply_modification(config& cfg)
|
||||
{
|
||||
if(!matches_filter(cfg))
|
||||
return;
|
||||
|
||||
const std::string& set_name = cfg.values["set_name"];
|
||||
const std::string& set_type = cfg.values["set_type"];
|
||||
const std::string& set_special = cfg.values["set_special"];
|
||||
const std::string& increase_damage = cfg.values["increase_damage"];
|
||||
const std::string& multiply_damage = cfg.values["multiply_damage"];
|
||||
const std::string& increase_attacks = cfg.values["increase_attacks"];
|
||||
|
||||
if(set_name.empty() == false) {
|
||||
name_ = set_name;
|
||||
}
|
||||
|
||||
if(set_type.empty() == false) {
|
||||
type_ = set_type;
|
||||
}
|
||||
|
||||
if(set_special.empty() == false) {
|
||||
special_ = set_special;
|
||||
}
|
||||
|
||||
if(increase_damage.empty() == false) {
|
||||
const int increase = atoi(increase_damage.c_str());
|
||||
damage_ += increase;
|
||||
if(damage_ < 1)
|
||||
damage_ = 1;
|
||||
}
|
||||
|
||||
if(multiply_damage.empty() == false) {
|
||||
const double multiply = atof(increase_damage.c_str());
|
||||
if(multiply != 0.0) {
|
||||
damage_ = int(double(damage_)*multiply);
|
||||
if(damage_ < 1)
|
||||
damage_ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(increase_attacks.empty() == false) {
|
||||
const int increase = atoi(increase_attacks.c_str());
|
||||
num_attacks_ += increase;
|
||||
if(num_attacks_ < 1) {
|
||||
num_attacks_ = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unit_movement_type::unit_movement_type(config& cfg) : cfg_(cfg)
|
||||
{}
|
||||
|
||||
const std::string& unit_movement_type::name() const
|
||||
{
|
||||
return cfg_.values["name"];
|
||||
}
|
||||
|
||||
int unit_movement_type::movement_cost(const gamemap& map,
|
||||
gamemap::TERRAIN terrain) const
|
||||
{
|
||||
const std::map<gamemap::TERRAIN,int>::const_iterator i =
|
||||
moveCosts_.find(terrain);
|
||||
if(i != moveCosts_.end()) {
|
||||
return i->second;
|
||||
}
|
||||
|
||||
const std::vector<config*>& v = cfg_.children["movement costs"];
|
||||
if(v.empty())
|
||||
return 1;
|
||||
|
||||
config* movement_costs = v[0];
|
||||
const std::string& name = map.underlying_terrain_name(terrain);
|
||||
if(terrain == 'b') {
|
||||
std::cout << "underlying terrain: " << name << "\n";
|
||||
}
|
||||
const std::string& val = movement_costs->values[name];
|
||||
int res = atoi(val.c_str());
|
||||
|
||||
//don't allow 0-movement terrain
|
||||
if(res == 0) {
|
||||
res = 100;
|
||||
}
|
||||
|
||||
moveCosts_.insert(std::pair<gamemap::TERRAIN,int>(terrain,res));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
double unit_movement_type::defense_modifier(const gamemap& map,
|
||||
gamemap::TERRAIN terrain) const
|
||||
{
|
||||
const std::map<gamemap::TERRAIN,double>::const_iterator i =
|
||||
defenseMods_.find(terrain);
|
||||
if(i != defenseMods_.end()) {
|
||||
return i->second;
|
||||
}
|
||||
|
||||
const std::vector<config*>& v = cfg_.children["defense"];
|
||||
if(v.empty())
|
||||
return 1;
|
||||
|
||||
config* defense = v[0];
|
||||
const std::string& name = map.underlying_terrain_name(terrain);
|
||||
const std::string& val = defense->values[name];
|
||||
|
||||
const double res = atof(val.c_str());
|
||||
defenseMods_.insert(std::pair<gamemap::TERRAIN,double>(terrain,res));
|
||||
return res;
|
||||
}
|
||||
|
||||
int unit_movement_type::damage_against(const attack_type& attack) const
|
||||
{
|
||||
const std::vector<config*>& v = cfg_.children["resistance"];
|
||||
if(v.empty())
|
||||
return 1;
|
||||
|
||||
config* resistance = v[0];
|
||||
const std::string& val = resistance->values[attack.type()];
|
||||
const double resist = atof(val.c_str());
|
||||
return static_cast<int>(resist * static_cast<double>(attack.damage()));
|
||||
}
|
||||
|
||||
const std::map<std::string,std::string>& unit_movement_type::damage_table() const
|
||||
{
|
||||
const std::vector<config*>& v = cfg_.children["resistance"];
|
||||
if(v.empty()) {
|
||||
static const std::map<std::string,std::string> default_val;
|
||||
return default_val;
|
||||
}
|
||||
|
||||
return v[0]->values;
|
||||
}
|
||||
|
||||
unit_type::unit_type(config& cfg, const movement_type_map& mv_types,
|
||||
std::vector<config*>& traits)
|
||||
: cfg_(cfg), possibleTraits_(traits), alpha_(1.0)
|
||||
{
|
||||
heals_ = has_ability("heals");
|
||||
regenerates_ = has_ability("regenerates");
|
||||
leadership_ = has_ability("leadership");
|
||||
lightbringer_ = has_ability("lightbringer");
|
||||
skirmish_ = has_ability("skirmisher");
|
||||
teleport_ = has_ability("teleport");
|
||||
nightvision_ = has_ability("night vision");
|
||||
|
||||
const std::string& alpha_blend = cfg_.values["alpha"];
|
||||
if(alpha_blend.empty() == false) {
|
||||
alpha_ = atof(alpha_blend.c_str());
|
||||
}
|
||||
|
||||
const std::string& move_type = cfg_.values["movement_type"];
|
||||
if(move_type.empty()) {
|
||||
throw gamestatus::load_game_failed("Movement type not specified for "
|
||||
"unit '" + name() + "'");
|
||||
}
|
||||
|
||||
const movement_type_map::const_iterator it = mv_types.find(move_type);
|
||||
if(it == mv_types.end()) {
|
||||
throw gamestatus::load_game_failed("Undefined movement type '" +
|
||||
move_type + "'");
|
||||
}
|
||||
|
||||
movementType_ = &(it->second);
|
||||
|
||||
//check if the images necessary for units exist
|
||||
#ifdef linux
|
||||
struct stat stat_buf;
|
||||
#ifdef WESNOTH_PATH
|
||||
if(::stat((WESNOTH_PATH + std::string("/images/") +
|
||||
cfg_.values["image"]).c_str(),&stat_buf) >= 0) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(::stat(("images/" + cfg_.values["image"]).c_str(),&stat_buf) < 0) {
|
||||
std::cerr << "image '" << cfg_.values["image"] << "' does not exist!\n";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string unit_type::id() const
|
||||
{
|
||||
std::string n = name();
|
||||
n.erase(std::remove(n.begin(),n.end(),' '),n.end());
|
||||
return n;
|
||||
}
|
||||
|
||||
std::string unit_type::language_name() const
|
||||
{
|
||||
const std::string& lang_name = string_table[id()];
|
||||
if(lang_name.empty() == false)
|
||||
return lang_name;
|
||||
else
|
||||
return name();
|
||||
}
|
||||
|
||||
const std::string& unit_type::name() const
|
||||
{
|
||||
return cfg_.values["name"];
|
||||
}
|
||||
|
||||
const std::string& unit_type::image() const
|
||||
{
|
||||
return cfg_.values["image"];
|
||||
}
|
||||
|
||||
const std::string& unit_type::image_defensive() const
|
||||
{
|
||||
const std::string& val = cfg_.values["image_defensive"];
|
||||
if(val.empty())
|
||||
return cfg_.values["image"];
|
||||
else
|
||||
return val;
|
||||
}
|
||||
|
||||
const std::string& unit_type::image_profile() const
|
||||
{
|
||||
const std::string& val = cfg_.values["profile"];
|
||||
if(val.size() == 0)
|
||||
return image();
|
||||
else
|
||||
return val;
|
||||
}
|
||||
|
||||
const std::string& unit_type::unit_description() const
|
||||
{
|
||||
static const std::string default_val("No description available");
|
||||
|
||||
const std::string& lang_desc = string_table[id() + "_description"];
|
||||
if(lang_desc.empty() == false)
|
||||
return lang_desc;
|
||||
|
||||
const std::string& desc = cfg_.values["unit_description"];
|
||||
if(desc.empty())
|
||||
return default_val;
|
||||
else
|
||||
return desc;
|
||||
}
|
||||
|
||||
int unit_type::hitpoints() const
|
||||
{
|
||||
return atoi(cfg_.values["hitpoints"].c_str());
|
||||
}
|
||||
|
||||
std::vector<attack_type> unit_type::attacks() const
|
||||
{
|
||||
std::vector<attack_type> res;
|
||||
const std::vector<config*>& v = cfg_.children["attack"];
|
||||
for(std::vector<config*>::const_iterator i = v.begin(); i != v.end(); ++i)
|
||||
res.push_back(attack_type(**i));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
const unit_movement_type& unit_type::movement_type() const
|
||||
{
|
||||
return *movementType_;
|
||||
}
|
||||
|
||||
int unit_type::cost() const
|
||||
{
|
||||
return atoi(cfg_.values["cost"].c_str());
|
||||
}
|
||||
|
||||
int unit_type::experience_needed() const
|
||||
{
|
||||
return atoi(cfg_.values["experience"].c_str());
|
||||
}
|
||||
|
||||
std::vector<std::string> unit_type::advances_to() const
|
||||
{
|
||||
const std::string& val = cfg_.values["advanceto"];
|
||||
if(val == "null" || val == "")
|
||||
return std::vector<std::string>();
|
||||
else
|
||||
return config::split(val);
|
||||
}
|
||||
|
||||
const std::string& unit_type::usage() const
|
||||
{
|
||||
return cfg_.values["usage"];
|
||||
}
|
||||
|
||||
int unit_type::level() const
|
||||
{
|
||||
return atoi(cfg_.values["level"].c_str());
|
||||
}
|
||||
|
||||
int unit_type::movement() const
|
||||
{
|
||||
return atoi(cfg_.values["movement"].c_str());
|
||||
}
|
||||
|
||||
unit_type::ALIGNMENT unit_type::alignment() const
|
||||
{
|
||||
const std::string& align = cfg_.values["alignment"];
|
||||
if(align == "lawful")
|
||||
return LAWFUL;
|
||||
else if(align == "chaotic")
|
||||
return CHAOTIC;
|
||||
else
|
||||
return NEUTRAL;
|
||||
}
|
||||
|
||||
const std::string& unit_type::alignment_description(unit_type::ALIGNMENT align)
|
||||
{
|
||||
static const std::string aligns[] = { "lawful", "neutral", "chaotic" };
|
||||
const std::map<std::string,std::string>::const_iterator i =
|
||||
string_table.find(aligns[align]);
|
||||
if(i != string_table.end())
|
||||
return i->second;
|
||||
else
|
||||
return aligns[align];
|
||||
}
|
||||
|
||||
double unit_type::alpha() const
|
||||
{
|
||||
return alpha_;
|
||||
}
|
||||
|
||||
const std::string& unit_type::ability() const
|
||||
{
|
||||
return cfg_.values["ability"];
|
||||
}
|
||||
|
||||
bool unit_type::heals() const
|
||||
{
|
||||
return heals_;
|
||||
}
|
||||
|
||||
bool unit_type::regenerates() const
|
||||
{
|
||||
return regenerates_;
|
||||
}
|
||||
|
||||
bool unit_type::is_leader() const
|
||||
{
|
||||
return leadership_;
|
||||
}
|
||||
|
||||
bool unit_type::is_lightbringer() const
|
||||
{
|
||||
return lightbringer_;
|
||||
}
|
||||
|
||||
bool unit_type::is_skirmisher() const
|
||||
{
|
||||
return skirmish_;
|
||||
}
|
||||
|
||||
bool unit_type::teleports() const
|
||||
{
|
||||
return teleport_;
|
||||
}
|
||||
|
||||
bool unit_type::nightvision() const
|
||||
{
|
||||
return nightvision_;
|
||||
}
|
||||
|
||||
bool unit_type::has_ability(const std::string& ability) const
|
||||
{
|
||||
return config::has_value(this->ability(),ability);
|
||||
}
|
||||
|
||||
const std::vector<config*>& unit_type::possible_traits() const
|
||||
{
|
||||
return possibleTraits_;
|
||||
}
|
||||
|
||||
game_data::game_data(config& cfg)
|
||||
{
|
||||
std::vector<config*>& unit_traits = cfg.children["trait"];
|
||||
|
||||
std::vector<config*>& move_types = cfg.children["movetype"];
|
||||
for(std::vector<config*>::iterator i = move_types.begin();
|
||||
i != move_types.end(); ++i) {
|
||||
const unit_movement_type move_type(**i);
|
||||
movement_types.insert(
|
||||
std::pair<std::string,unit_movement_type>(move_type.name(),
|
||||
move_type));
|
||||
}
|
||||
|
||||
std::vector<config*>& u_types = cfg.children["unit"];
|
||||
for(std::vector<config*>::iterator j = u_types.begin();
|
||||
j != u_types.end(); ++j) {
|
||||
const unit_type u_type(**j,movement_types,unit_traits);
|
||||
unit_types.insert(
|
||||
std::pair<std::string,unit_type>(u_type.name(),u_type));
|
||||
}
|
||||
}
|
186
unit_types.hpp
Normal file
186
unit_types.hpp
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef UNIT_TYPES_H_INCLUDED
|
||||
#define UNIT_TYPES_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "map.hpp"
|
||||
#include "team.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
//the 'attack type' is the type of attack, how many times it strikes,
|
||||
//and how much damage it does.
|
||||
class attack_type
|
||||
{
|
||||
public:
|
||||
enum RANGE { SHORT_RANGE, LONG_RANGE };
|
||||
|
||||
attack_type(config& cfg);
|
||||
const std::string& name() const;
|
||||
const std::string& type() const;
|
||||
const std::string& special() const;
|
||||
RANGE range() const;
|
||||
int damage() const;
|
||||
int num_attacks() const;
|
||||
|
||||
enum FRAME_TYPE { UNIT_FRAME, MISSILE_FRAME };
|
||||
enum FRAME_DIRECTION { VERTICAL, DIAGONAL };
|
||||
|
||||
int get_first_frame(FRAME_TYPE type=UNIT_FRAME) const;
|
||||
int get_last_frame(FRAME_TYPE type=UNIT_FRAME) const;
|
||||
|
||||
//function which gets an attack animation frame. The argument
|
||||
//is 0 for the frame at the time of impact, and negative for
|
||||
//frames before the time of impact
|
||||
const std::string* get_frame(int milliseconds, FRAME_TYPE type=UNIT_FRAME,
|
||||
FRAME_DIRECTION direction=VERTICAL) const;
|
||||
|
||||
struct sfx {
|
||||
int time;
|
||||
std::string on_hit, on_miss;
|
||||
};
|
||||
|
||||
const std::vector<sfx>& sound_effects() const;
|
||||
|
||||
bool matches_filter(config& cfg) const;
|
||||
void apply_modification(config& cfg);
|
||||
private:
|
||||
std::string name_;
|
||||
std::string type_;
|
||||
std::string special_;
|
||||
RANGE range_;
|
||||
int damage_;
|
||||
int num_attacks_;
|
||||
|
||||
struct frame {
|
||||
frame(int i1, int i2, const std::string& img)
|
||||
: start(i1), end(i2), image(&img), image_diagonal(NULL)
|
||||
{}
|
||||
|
||||
frame(int i1, int i2, const std::string& img, const std::string& diag)
|
||||
: start(i1), end(i2), image(&img), image_diagonal(&diag)
|
||||
{}
|
||||
|
||||
int start, end;
|
||||
const std::string* image;
|
||||
const std::string* image_diagonal;
|
||||
};
|
||||
|
||||
std::vector<frame> frames_[2];
|
||||
|
||||
std::vector<sfx> sfx_;
|
||||
};
|
||||
|
||||
//the 'unit movement type' is the basic size of the unit - flying, small land,
|
||||
//large land, etc etc.
|
||||
class unit_movement_type
|
||||
{
|
||||
public:
|
||||
//this class assumes that the passed in reference will remain valid
|
||||
//for at least as long as the class instance
|
||||
unit_movement_type(config& cfg);
|
||||
|
||||
const std::string& name() const;
|
||||
int movement_cost(const gamemap& map, gamemap::TERRAIN terrain) const;
|
||||
double defense_modifier(const gamemap& map, gamemap::TERRAIN terrain) const;
|
||||
int damage_against(const attack_type& attack) const;
|
||||
|
||||
const std::map<std::string,std::string>& damage_table() const;
|
||||
|
||||
private:
|
||||
mutable config& cfg_;
|
||||
|
||||
mutable std::map<gamemap::TERRAIN,int> moveCosts_;
|
||||
mutable std::map<gamemap::TERRAIN,double> defenseMods_;
|
||||
};
|
||||
|
||||
typedef std::map<std::string,unit_movement_type> movement_type_map;
|
||||
|
||||
class unit_type
|
||||
{
|
||||
public:
|
||||
//this class assumes that the passed in references will remain valid
|
||||
//for at least as long as the class instance
|
||||
unit_type(config& cfg, const movement_type_map& movement_types,
|
||||
std::vector<config*>& traits);
|
||||
|
||||
//the name of the unit in the current language setting
|
||||
std::string language_name() const;
|
||||
|
||||
//unique identifier that doesn't have any whitespace
|
||||
std::string id() const;
|
||||
const std::string& name() const;
|
||||
const std::string& image() const;
|
||||
const std::string& image_profile() const;
|
||||
const std::string& image_defensive() const;
|
||||
const std::string& unit_description() const;
|
||||
int hitpoints() const;
|
||||
std::vector<attack_type> attacks() const;
|
||||
const unit_movement_type& movement_type() const;
|
||||
|
||||
int experience_needed() const;
|
||||
std::vector<std::string> advances_to() const;
|
||||
const std::string& usage() const;
|
||||
|
||||
int level() const;
|
||||
int movement() const;
|
||||
int cost() const;
|
||||
|
||||
enum ALIGNMENT { LAWFUL, NEUTRAL, CHAOTIC };
|
||||
|
||||
ALIGNMENT alignment() const;
|
||||
static const std::string& alignment_description(ALIGNMENT align);
|
||||
|
||||
double alpha() const;
|
||||
|
||||
const std::string& ability() const;
|
||||
|
||||
bool heals() const;
|
||||
bool regenerates() const;
|
||||
bool is_leader() const;
|
||||
bool is_lightbringer() const;
|
||||
bool is_skirmisher() const;
|
||||
bool teleports() const;
|
||||
bool nightvision() const;
|
||||
|
||||
bool has_ability(const std::string& ability) const;
|
||||
|
||||
const std::vector<config*>& possible_traits() const;
|
||||
|
||||
private:
|
||||
mutable config& cfg_;
|
||||
|
||||
double alpha_;
|
||||
|
||||
bool heals_;
|
||||
bool regenerates_;
|
||||
bool leadership_;
|
||||
bool lightbringer_;
|
||||
bool skirmish_;
|
||||
bool teleport_;
|
||||
bool nightvision_;
|
||||
|
||||
const unit_movement_type* movementType_;
|
||||
|
||||
std::vector<config*>& possibleTraits_;
|
||||
};
|
||||
|
||||
struct game_data
|
||||
{
|
||||
game_data(config& cfg);
|
||||
movement_type_map movement_types;
|
||||
std::map<std::string,unit_type> unit_types;
|
||||
};
|
||||
|
||||
#endif
|
31
util.hpp
Normal file
31
util.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef UTIL_H_INCLUDED
|
||||
#define UTIL_H_INCLUDED
|
||||
|
||||
#include <map>
|
||||
|
||||
//instead of playing with VC++'s crazy definitions of min and max,
|
||||
//just define our own
|
||||
template<typename T>
|
||||
T& minimum(T& a, T& b) { return a < b ? a : b; }
|
||||
|
||||
template<typename T>
|
||||
const T& minimum(const T& a, const T& b) { return a < b ? a : b; }
|
||||
|
||||
template<typename T>
|
||||
T& maximum(T& a, T& b) { return a < b ? b : a; }
|
||||
|
||||
template<typename T>
|
||||
const T& maximum(const T& a, const T& b) { return a < b ? b : a; }
|
||||
|
||||
#endif
|
321
video.cpp
Normal file
321
video.cpp
Normal file
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
|
||||
#include "video.hpp"
|
||||
|
||||
#define TEST_VIDEO_ON 0
|
||||
|
||||
#if (TEST_VIDEO_ON==1)
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
//test program takes three args - x-res y-res colour-depth
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
if( argc != 4 ) {
|
||||
printf( "usage: %s x-res y-res bitperpixel\n", argv[0] );
|
||||
return 1;
|
||||
}
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
CVideo video;
|
||||
|
||||
printf( "args: %s, %s, %s\n", argv[1], argv[2], argv[3] );
|
||||
|
||||
printf( "(%d,%d,%d)\n", strtoul(argv[1],0,10), strtoul(argv[2],0,10),
|
||||
strtoul(argv[3],0,10) );
|
||||
|
||||
if( video.setMode( strtoul(argv[1],0,10), strtoul(argv[2],0,10),
|
||||
strtoul(argv[3],0,10), FULL_SCREEN ) ) {
|
||||
printf( "video mode possible\n" );
|
||||
} else printf( "video mode NOT possible\n" );
|
||||
printf( "%d, %d, %d\n", video.getx(), video.gety(),
|
||||
video.getBitsPerPixel() );
|
||||
|
||||
for( int s = 0; s < 50; s++ ) {
|
||||
video.lock();
|
||||
for( int i = 0; i < video.getx(); i++ )
|
||||
video.setPixel( i, 90, 255, 0, 0 );
|
||||
if( s%10==0)
|
||||
printf( "frame %d\n", s );
|
||||
video.unlock();
|
||||
video.update( 0, 90, video.getx(), 1 );
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
bool fullScreen = false;
|
||||
}
|
||||
|
||||
CVideo::CVideo(const char* text) : frameBuffer(NULL), backBuffer(NULL)
|
||||
{
|
||||
SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE);
|
||||
|
||||
for(int i = 0; i != sizeof(text_); ++i) {
|
||||
text_[i] = text[i];
|
||||
}
|
||||
}
|
||||
|
||||
CVideo::CVideo( int x, int y, int bits_per_pixel, int flags, const char* text )
|
||||
: frameBuffer(NULL), backBuffer(NULL)
|
||||
{
|
||||
const int res = SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE);
|
||||
if(res < 0) {
|
||||
throw error();
|
||||
}
|
||||
|
||||
setMode( x, y, bits_per_pixel, flags );
|
||||
|
||||
for(int i = 0; i != sizeof(text_); ++i) {
|
||||
text_[i] = text[i];
|
||||
}
|
||||
}
|
||||
|
||||
CVideo::~CVideo()
|
||||
{
|
||||
SDL_FreeSurface( backBuffer );
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
int CVideo::modePossible( int x, int y, int bits_per_pixel, int flags )
|
||||
{
|
||||
return SDL_VideoModeOK( x, y, bits_per_pixel, flags );
|
||||
}
|
||||
|
||||
int CVideo::setMode( int x, int y, int bits_per_pixel, int flags )
|
||||
{
|
||||
const int res = SDL_VideoModeOK( x, y, bits_per_pixel, flags );
|
||||
|
||||
if( res == 0 )
|
||||
return 0;
|
||||
|
||||
fullScreen = (flags & FULL_SCREEN) != 0;
|
||||
frameBuffer = SDL_SetVideoMode( x, y, bits_per_pixel, flags );
|
||||
|
||||
if( frameBuffer != NULL ) {
|
||||
if(backBuffer != NULL)
|
||||
SDL_FreeSurface(backBuffer);
|
||||
|
||||
backBuffer = SDL_ConvertSurface( frameBuffer,
|
||||
frameBuffer->format, 0 );
|
||||
if( backBuffer == NULL )
|
||||
fprintf( stderr, "out of memory\n" );
|
||||
return bits_per_pixel;
|
||||
} else return 0;
|
||||
}
|
||||
|
||||
int CVideo::getx() const
|
||||
{
|
||||
return backBuffer->w;
|
||||
}
|
||||
|
||||
int CVideo::gety() const
|
||||
{
|
||||
return backBuffer->h;
|
||||
}
|
||||
|
||||
int CVideo::getBitsPerPixel()
|
||||
{
|
||||
return backBuffer->format->BitsPerPixel;
|
||||
}
|
||||
|
||||
int CVideo::getBytesPerPixel()
|
||||
{
|
||||
return backBuffer->format->BytesPerPixel;
|
||||
}
|
||||
|
||||
int CVideo::getRedMask()
|
||||
{
|
||||
return backBuffer->format->Rmask;
|
||||
}
|
||||
|
||||
int CVideo::getGreenMask()
|
||||
{
|
||||
return backBuffer->format->Gmask;
|
||||
}
|
||||
|
||||
int CVideo::getBlueMask()
|
||||
{
|
||||
return backBuffer->format->Bmask;
|
||||
}
|
||||
|
||||
void* CVideo::getAddress()
|
||||
{
|
||||
return backBuffer->pixels;
|
||||
}
|
||||
|
||||
void CVideo::lock()
|
||||
{
|
||||
if( SDL_MUSTLOCK(backBuffer) )
|
||||
SDL_LockSurface( backBuffer );
|
||||
}
|
||||
|
||||
void CVideo::unlock()
|
||||
{
|
||||
if( SDL_MUSTLOCK(backBuffer) )
|
||||
SDL_UnlockSurface( backBuffer );
|
||||
}
|
||||
|
||||
int CVideo::mustLock()
|
||||
{
|
||||
return SDL_MUSTLOCK(backBuffer);
|
||||
}
|
||||
|
||||
void CVideo::setPixel( int x, int y, int r, int g, int b )
|
||||
{
|
||||
int pixel = ((r<<(backBuffer->format->Rshift-backBuffer->format->Rloss))
|
||||
& backBuffer->format->Rmask)+
|
||||
((g<<(backBuffer->format->Gshift-backBuffer->format->Gloss))
|
||||
& backBuffer->format->Gmask)+
|
||||
((b>>(backBuffer->format->Bloss-backBuffer->format->Bshift))
|
||||
& backBuffer->format->Bmask);
|
||||
|
||||
setPixel( x, y, pixel );
|
||||
}
|
||||
|
||||
int CVideo::convertColour(int r, int g, int b)
|
||||
{
|
||||
return ((r<<(backBuffer->format->Rshift-backBuffer->format->Rloss))
|
||||
& backBuffer->format->Rmask)+
|
||||
((g<<(backBuffer->format->Gshift-backBuffer->format->Gloss))
|
||||
& backBuffer->format->Gmask)+
|
||||
((b>>(backBuffer->format->Bloss-backBuffer->format->Bshift))
|
||||
& backBuffer->format->Bmask);
|
||||
}
|
||||
|
||||
void CVideo::setPixel( int x, int y, int p )
|
||||
{
|
||||
static int pixel;
|
||||
static char* p1 = ((char*)&pixel);
|
||||
static char* p2 = ((char*)&pixel)+1;
|
||||
static char* p3 = ((char*)&pixel)+2;
|
||||
static short* sp = ((short*)&pixel);
|
||||
pixel = p;
|
||||
|
||||
if( x < 0 || x >= backBuffer->w || y < 0 || y >= backBuffer->h )
|
||||
return;
|
||||
|
||||
switch( backBuffer->format->BytesPerPixel ) {
|
||||
case 1:
|
||||
*((char*)backBuffer->pixels+y*backBuffer->w+x) = *p1;
|
||||
break;
|
||||
case 2:
|
||||
*((short*)backBuffer->pixels+y*backBuffer->w+x) = *sp;
|
||||
break;
|
||||
case 3:
|
||||
*((char*)backBuffer->pixels+y*backBuffer->w*3+x*3)
|
||||
= *p1;
|
||||
*((char*)backBuffer->pixels+y*backBuffer->w*3+x*3+1)
|
||||
= *p2;
|
||||
*((char*)backBuffer->pixels+y*backBuffer->w*3+x*3+2)
|
||||
= *p3;
|
||||
break;
|
||||
case 4:
|
||||
*((int*)backBuffer->pixels+y*backBuffer->w+x) = pixel;
|
||||
break;
|
||||
default:
|
||||
fprintf( stderr, "Unknown colour depth\n" );
|
||||
}
|
||||
}
|
||||
|
||||
void CVideo::update( int x, int y, int w, int h )
|
||||
{
|
||||
if( w == 0 || h == 0 )
|
||||
return;
|
||||
|
||||
if( x < 0 ) {
|
||||
w += x;
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if( y < 0 ) {
|
||||
h += y;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
if( x+w > frameBuffer->w )
|
||||
w = frameBuffer->w - x;
|
||||
|
||||
if( y+h > frameBuffer->h )
|
||||
h = frameBuffer->h - y;
|
||||
|
||||
SRectangle rect = {x,y,w,h};
|
||||
SDL_BlitSurface( backBuffer, &rect, frameBuffer, &rect );
|
||||
SDL_UpdateRect( frameBuffer, x, y, w, h );
|
||||
}
|
||||
|
||||
void CVideo::update( SRectangle* rect )
|
||||
{
|
||||
SDL_BlitSurface( backBuffer, rect, frameBuffer, rect );
|
||||
SDL_UpdateRect( frameBuffer, rect->x, rect->y, rect->w, rect->h );
|
||||
}
|
||||
|
||||
SDL_Surface* CVideo::getSurface( void )
|
||||
{
|
||||
return backBuffer;
|
||||
}
|
||||
|
||||
void CVideo::drawChar(int x, int y, int pixel, int bg, char c, int sz)
|
||||
{
|
||||
const char* const data = text_ + c*8;
|
||||
for(int i = 0; i != 8*sz; ++i) {
|
||||
if(y+i >= backBuffer->h)
|
||||
return;
|
||||
|
||||
for(int j = 0; j != 8*sz; ++j) {
|
||||
if(x+j >= backBuffer->w)
|
||||
break;
|
||||
|
||||
if(data[i/sz] & (128 >> (j/sz))) {
|
||||
setPixel(x+j,y+i,pixel);
|
||||
} else {
|
||||
if(bg != pixel)
|
||||
setPixel(x+j,y+i,bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int CVideo::drawText(int x, int y, int pixel, int bg, const char* text, int sz)
|
||||
{
|
||||
const int good_colour = 0x0F00;
|
||||
const int bad_colour = 0xF000;
|
||||
int colour = pixel;
|
||||
const int startx = x;
|
||||
const int starty = y;
|
||||
while(*text) {
|
||||
if(*text == '@') {
|
||||
colour = good_colour;
|
||||
} else if(*text == '#') {
|
||||
colour = bad_colour;
|
||||
} else if(*text == '\n') {
|
||||
colour = pixel;
|
||||
y += 8*sz;
|
||||
x = startx;
|
||||
} else {
|
||||
drawChar(x,y,colour,bg == pixel ? colour : bg,*text,sz);
|
||||
x += 8*sz;
|
||||
}
|
||||
++text;
|
||||
}
|
||||
|
||||
y += 8*sz;
|
||||
|
||||
return y - starty;
|
||||
}
|
||||
|
||||
bool CVideo::isFullScreen() const { return fullScreen; }
|
70
video.hpp
Normal file
70
video.hpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef VIDEO_HPP_INCLUDED
|
||||
#define VIDEO_HPP_INCLUDED
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
//possible flags when setting video modes
|
||||
#define FULL_SCREEN SDL_FULLSCREEN
|
||||
#define VIDEO_MEMORY SDL_HWSURFACE
|
||||
#define SYSTEM_MEMORY SDL_SWSURFACE
|
||||
|
||||
typedef SDL_Rect SRectangle;
|
||||
|
||||
class CVideo {
|
||||
public:
|
||||
CVideo(const char* text);
|
||||
CVideo( int x, int y, int bits_per_pixel, int flags, const char* text );
|
||||
~CVideo();
|
||||
|
||||
int modePossible( int x, int y, int bits_per_pixel, int flags );
|
||||
int setMode( int x, int y, int bits_per_pixel, int flags );
|
||||
|
||||
//functions to get the dimensions of the current video-mode
|
||||
int getx() const;
|
||||
int gety() const;
|
||||
int getBitsPerPixel();
|
||||
int getBytesPerPixel();
|
||||
int getRedMask();
|
||||
int getGreenMask();
|
||||
int getBlueMask();
|
||||
|
||||
//functions to access the screen
|
||||
void* getAddress();
|
||||
void lock();
|
||||
void unlock();
|
||||
int mustLock();
|
||||
void setPixel( int x, int y, int r, int g, int b );
|
||||
void setPixel( int x, int y, int pixel );
|
||||
int convertColour(int r, int g, int b);
|
||||
void update( int x, int y, int w, int h );
|
||||
void update( SRectangle* area );
|
||||
|
||||
SDL_Surface* getSurface( void );
|
||||
|
||||
int drawText(int x, int y, int pixel, int bg, const char* text,int size=1);
|
||||
|
||||
bool isFullScreen() const;
|
||||
|
||||
struct error {};
|
||||
|
||||
private:
|
||||
|
||||
void drawChar(int x, int y, int pixel, int bg, char c, int size=1);
|
||||
|
||||
SDL_Surface* frameBuffer;
|
||||
SDL_Surface* backBuffer;
|
||||
char text_[256*8];
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue