[atanks] 05/13: Import Upstream version 6.4~dfsg
Markus Koschany
apo at moszumanska.debian.org
Sat Nov 26 23:05:36 UTC 2016
This is an automated email from the git hooks/post-receive script.
apo pushed a commit to branch master
in repository atanks.
commit a29b77907e90ba934ac1c6a0e54a7856497ab1a9
Author: Markus Koschany <apo at debian.org>
Date: Sat Nov 26 23:42:43 2016 +0100
Import Upstream version 6.4~dfsg
---
Changelog | 347 +-
Makefile | 413 +-
Makefile.bsd | 298 ++
README | 17 +-
TODO | 19 +-
allegro.cfg | 2 +
allegro.supp | 238 ++
cb/atanks.cbp | 438 ++
cb/atanks.workspace | 6 +
credits.txt | 2 +
dep/.keep_dir | 0
dep/aicore.d | 5 +
dep/atanks.d | 8 +
dep/beam.d | 5 +
dep/button.d | 3 +
dep/client.d | 6 +
dep/clock.d | 1 +
dep/debris_pool.d | 3 +
dep/debug.d | 1 +
dep/decor.d | 4 +
dep/environment.d | 5 +
dep/explosion.d | 5 +
dep/files.d | 3 +
dep/floattext.d | 3 +
dep/gameloop.d | 7 +
dep/gfxData.d | 3 +
dep/globaldata.d | 5 +
dep/globaltypes.d | 1 +
dep/land.d | 4 +
dep/main.d | 3 +
dep/menu.d | 6 +
dep/missile.d | 6 +
dep/network.d | 3 +
dep/optionitembase.d | 6 +
dep/optionitemcolour.d | 4 +
dep/optionitemmenu.d | 5 +
dep/optionitemplayer.d | 5 +
dep/optionscreens.d | 6 +
dep/optiontypes.d | 1 +
dep/perlin.d | 3 +
dep/physobj.d | 3 +
dep/player.d | 7 +
dep/player_types.d | 3 +
dep/satellite.d | 4 +
dep/shop.d | 4 +
dep/sky.d | 4 +
dep/sound.d | 3 +
dep/tank.d | 5 +
dep/teleport.d | 5 +
dep/text.d | 3 +
dep/update.d | 3 +
dep/virtobj.d | 3 +
do_helgrind.sh | 5 +
do_memcheck.sh | 7 +
gdb_memcheck.sh | 7 +
obj/.keep_dir | 0
sound/0.wav | Bin 12461 -> 0 bytes
sound/00.wav | Bin 0 -> 25118 bytes
sound/01.wav | Bin 0 -> 72094 bytes
sound/02.wav | Bin 0 -> 67612 bytes
sound/03.wav | Bin 0 -> 82466 bytes
sound/04.wav | Bin 0 -> 107806 bytes
sound/05.wav | Bin 0 -> 30746 bytes
sound/06.wav | Bin 0 -> 120580 bytes
sound/07.wav | Bin 0 -> 170304 bytes
sound/1.wav | Bin 35948 -> 0 bytes
sound/10.wav | Bin 966 -> 173796 bytes
sound/11.wav | Bin 30139 -> 177384 bytes
sound/12.wav | Bin 81202 -> 178404 bytes
sound/13.wav | Bin 0 -> 265464 bytes
sound/14.wav | Bin 0 -> 505014 bytes
sound/15.wav | Bin 0 -> 69952 bytes
sound/16.wav | Bin 0 -> 138894 bytes
sound/17.wav | Bin 0 -> 145640 bytes
sound/18.wav | Bin 0 -> 256332 bytes
sound/19.wav | Bin 0 -> 288116 bytes
sound/2.wav | Bin 33708 -> 0 bytes
sound/20.wav | Bin 0 -> 177500 bytes
sound/21.wav | Bin 0 -> 45320 bytes
sound/22.wav | Bin 0 -> 63704 bytes
sound/3.wav | Bin 41126 -> 0 bytes
sound/30.wav | Bin 0 -> 162548 bytes
sound/31.wav | Bin 0 -> 327384 bytes
sound/{10.wav => 32.wav} | Bin
sound/4.wav | Bin 43436 -> 0 bytes
sound/{8.wav => 40.wav} | Bin
sound/5.wav | Bin 44332 -> 0 bytes
sound/6.wav | Bin 44588 -> 0 bytes
sound/7.wav | Bin 66348 -> 0 bytes
sound/9.wav | Bin 34904 -> 0 bytes
src/Makefile | 149 -
src/Makefile.bsd | 151 -
src/Makefile.windows | 157 -
src/aicore.cpp | 5584 +++++++++++++++++++++++++
src/aicore.h | 950 +++++
src/allegro.h | 82 -
src/atanks.cpp | 7461 +++++++---------------------------
src/atanks.rc | 119 +-
src/beam.cpp | 679 +++-
src/beam.h | 108 +-
src/button.cpp | 158 +-
src/button.h | 73 +-
src/client.cpp | 640 ++-
src/client.h | 16 +-
src/clock.cpp | 66 +
src/clock.h | 25 +
src/debris_pool.cpp | 238 ++
src/debris_pool.h | 73 +
src/debug.cpp | 64 +
src/debug.h | 168 +
src/decor.cpp | 441 ++
src/decor.h | 215 +-
src/environment.cpp | 2491 +++++++-----
src/environment.h | 396 +-
src/explosion.cpp | 1313 ++++--
src/explosion.h | 116 +-
src/extern/dirent.c | 152 +
src/extern/dirent.h | 50 +
src/externs.h | 67 +-
src/fade.cpp | 235 --
src/files.cpp | 1731 ++++----
src/files.h | 91 +-
src/floattext.cpp | 567 ++-
src/floattext.h | 131 +-
src/gameloop.cpp | 2716 ++++++++++---
src/gameloop.h | 65 +-
src/gfxData.cpp | 295 ++
src/gfxData.h | 77 +
src/globaldata.cpp | 2363 +++++------
src/globaldata.h | 402 +-
src/globals.h | 317 +-
src/globaltypes.cpp | 68 +
src/globaltypes.h | 337 ++
src/land.cpp | 423 +-
src/land.h | 191 +-
src/main.cpp | 156 +
src/main.h | 855 ++--
src/menu.cpp | 1214 ++++++
src/menu.h | 463 ++-
src/menucontent.h | 852 ----
src/missile.cpp | 1823 +++++----
src/missile.h | 116 +-
src/network.cpp | 71 +-
src/network.h | 33 +-
src/optioncontent.h | 1326 ++++++
src/optionitem.h | 567 +++
src/optionitembase.cpp | 768 ++++
src/optionitembase.h | 207 +
src/optionitemcolour.cpp | 203 +
src/optionitemcolour.h | 94 +
src/optionitemmenu.cpp | 123 +
src/optionitemmenu.h | 86 +
src/optionitemplayer.cpp | 231 ++
src/optionitemplayer.h | 92 +
src/optionscreens.cpp | 1038 +++++
src/{imagedefs.h => optionscreens.h} | 28 +-
src/optiontypes.cpp | 187 +
src/optiontypes.h | 128 +
src/perlin.cpp | 6 +-
src/physobj.cpp | 797 +++-
src/physobj.h | 109 +-
src/player.cpp | 7291 +++++++++++++--------------------
src/player.h | 388 +-
src/player_types.cpp | 150 +
src/player_types.h | 130 +
src/resource.h | 1569 +++++++
src/satellite.cpp | 100 +-
src/satellite.h | 54 +-
src/shop.cpp | 1002 +++++
src/shop.h | 12 +
src/sky.cpp | 560 ++-
src/sky.h | 190 +-
src/sound.cpp | 174 +
src/{imagedefs.h => sound.h} | 32 +-
src/tank.cpp | 2891 +++++++------
src/tank.h | 179 +-
src/team.cpp | 35 -
src/team.h | 26 -
src/teleport.cpp | 340 +-
src/teleport.h | 78 +-
src/text.cpp | 400 +-
src/text.h | 79 +-
src/update.cpp | 209 +-
src/update.h | 68 +-
src/virtobj.cpp | 322 +-
src/virtobj.h | 191 +-
src/winclock.h | 105 +
tankgun/2.bmp | Bin 3126 -> 3126 bytes
tankgun/4.bmp | Bin 4150 -> 3126 bytes
tankgun/5.bmp | Bin 3126 -> 3126 bytes
tankgun/7.bmp | Bin 4150 -> 3126 bytes
text/panic.pt_BR.txt | 8 +
text/panic.txt | 8 +
text/panic_ES.txt | 8 +
text/panic_de.txt | 8 +
text/panic_fr.txt | 8 +
text/panic_it.txt | 8 +
text/panic_ru.txt | 8 +
text/panic_sk.txt | 8 +
text/weapons.pt_BR.txt | 85 -
text/weapons.txt | 138 +-
text/weapons_ES.txt | 85 -
text/weapons_de.txt | 85 -
text/weapons_fr.txt | 85 -
text/weapons_it.txt | 85 -
text/weapons_ru.txt | 85 -
text/weapons_sk.txt | 85 -
title/.directory | 3 -
unicode.dat | Bin 969257 -> 5604 bytes
vs12/README_allegro.txt | 14 +
vs12/atanks.sln | 28 +
vs12/atanks.vcxproj | 285 ++
vs12/atanks.vcxproj.filters | 293 ++
213 files changed, 40093 insertions(+), 23652 deletions(-)
diff --git a/Changelog b/Changelog
index ecc9b51..ec05dc1 100644
--- a/Changelog
+++ b/Changelog
@@ -1,5 +1,74 @@
NOTE: From now on, new changes appear at the top of this file.
+============ Atanks-6.3 released ===================
+
+ Sven has done a huge amount of code changes for this release. The
+ key highlights are as follows:
+ - Improved AI.
+ - Smoother/faster buying screen navigation.
+ - Huge re-work of the Options screen and other menus to
+ avoid crashes and random symbols appearing.
+ - Improved demo mode.
+ - The AI does a better job of buying items/weapons.
+ - Updated Makefiles to reduce the number of separate Makefiles required.
+ - Atanks should now build natively in Visual Studio on Windows.
+ - Improved Unicode support.
+ - Various code clean-up.
+ - Added dirt debris.
+ - Improved revenge mode for AI.
+ - Menus should now respond faster.
+ - Network code clean-up.
+ - Added more in-code documentation.
+
+============ Atanks-6.2 released ===================
+
+ - Large code clean-up by Bruno Victal
+ - More style/code clean-up by Bruno Victal
+ Indentation
+ Removing unnecessary braces
+ Removing unnecessary parentheses
+ Removing unnecessary spaces
+ Removing many blank lines (line wasting)
+ Expanding some ifs and switch cases
+
+ - Bruno Victal provided further code clean-up
+ Renamed old referenced files in the perror calls (from .cc to .cpp)
+ Removed some useless casts in printf calls [ printf((char *)"String here"); ]
+ Deleted the macros about DOS support (they were already commented out)
+ Fixed abs() related math functions so they do not throw compiler warnings.
+
+ - Removed extra call to Get_Team_Name() that was not required.
+
+ Daniel sent in a patch which addresses the following:
+ - fixes a bug where the address of (stack allocated) temporal arrays
+ was stored in the menu structure causing
+ wrong behavior, e.g. printing garbage in the Network configuration
+ menu title.
+ - fixes const-correctness for all the menu structure
+ - moves stack-allocated read-only arrays to .rodata so no large stack
+ memory is used.
+ - adds Daniel to the list of people (feel free to remove me if this
+
+ Bruno Victal provided two more fixes.
+ - Fix isnan() ambiguity when compiled with C++11 standards
+ - Removed OLD_GAMELOOP code and references. Stick with
+ modern game loop.
+
+
+=========== Atanks-6.1 released ==============
+ All of this releases patches were provided by Bill Buerger.
+ - Extend the amount of time the AI will play against itself
+ from 10 seconds to 60 seconds.
+ - Fixed "clean" command in Windows Makefile.
+ - Players now wait to start their turn until they
+ can control their tank. No more waiting at the start
+ of their turn.
+ - Rollers now properly explode when hitting steel wall.
+ - Scoreboard shows current income and turn order.
+ This can be toggled with the ~ key.
+ - Fixed navigation keys on the store/buying sceen.
+
+
=========== Atanks-6.0 released ==============
- Removed extra alleg42.dll file from src directory.
- Clarified license.
@@ -101,7 +170,7 @@ Feb 23, 2012
============ Atanks-5.0 released ===============
June 4, 2011
- - Window's close button now works on buying screen
+ - Window's close button now works on buying screen
and during rounds.
@@ -109,7 +178,7 @@ June 4, 2011
March 23, 2011
- - Added Italian language support.
+ - Added Italian language support.
(provided by Roby Alice.)
@@ -450,7 +519,7 @@ September 19, 2009
- Removed old lineseq.h file and references.
- Cleaned up renderTextLines function to display cyrillic
text properly.
-
+
September 14, 2009
- Added unicode fonts to support cyrillic.
@@ -696,7 +765,7 @@ June 19, 2009
June 15, 2009
- Fixed abs() calls in player.cpp and tank.cpp
- - Replaced old Teleport animation with Yama's.
+ - Replaced old Teleport animation with Yama's.
June 4, 2009
- Added war quotes to display at the end of games. (files.cpp, files.h,
@@ -765,179 +834,179 @@ March 19, 2009
February 21, 2009
-- Finally "-Wno-write-strings" is no longer needed to compile the sources
- -- Rewrote the environment method to calculate average background colors. It is
- now inline and somewhat optimized, making shadowed + fading text more
+ -- Rewrote the environment method to calculate average background colors. It is
+ now inline and somewhat optimized, making shadowed + fading text more
performant.
- -- Rewrote File handling, apart from three tiny points atanks is now fully ISO
+ -- Rewrote File handling, apart from three tiny points atanks is now fully ISO
C++ compliant.
-- Made rollers a bit faster dropping
- -- No more roller-breaking of shields by hammering them above the enemy onto
+ -- No more roller-breaking of shields by hammering them above the enemy onto
the steel/wrap ceiling! They explode now.
- -- Two tiny patches make bots targetting a bit faster and destroy (hopefully)
+ -- Two tiny patches make bots targetting a bit faster and destroy (hopefully)
any possibility for the system to go infinite
-- prepared all neccessary files to be translated. But please note:
-- English: Is always there
- -- Portuguese: Is borked. But maybe it's only my utf-8 system showing the
- wrong characters. I looked into the atanks-2.9 files and it is broken there as
+ -- Portuguese: Is borked. But maybe it's only my utf-8 system showing the
+ wrong characters. I looked into the atanks-2.9 files and it is broken there as
well. If its just my system, don't mind. ;)
- -- French: Has some tiny things that has to be translated. Retaliation is
- still english, and I am not sure how to translate "Shadowed Text" and "Fading
+ -- French: Has some tiny things that has to be translated. Retaliation is
+ still english, and I am not sure how to translate "Shadowed Text" and "Fading
Text", so some french natural speaker might do this?
-- German: Only weapons_de.txt needs to be translated, I'll do it next year.
- -- Slovak: Only the gloating texts *are* translated so the rest is missing
- completely, but I have prepared the files and menucontent.h for a slovak
+ -- Slovak: Only the gloating texts *are* translated so the rest is missing
+ completely, but I have prepared the files and menucontent.h for a slovak
natural speaker to be translated.
- -- Added weapons_*language*.txt files so we can (finally) translate the weapon
+ -- Added weapons_*language*.txt files so we can (finally) translate the weapon
texts as well.
-- Fixed a bug that could cause roller tracking to go infinite.
-- Balanced fading texts some more
-- Corrected some text messages
-- added new roller code. Rollers now really roll!
- -- fixed a bug that made tracking report wrong hit points when tracking
+ -- fixed a bug that made tracking report wrong hit points when tracking
rollers and burrowers
- -- Added average background color calculation and fixed fading text offset. It
+ -- Added average background color calculation and fixed fading text offset. It
should look far better now.
-- Balanced shadowed text color calculation
- -- added a new retaliation text. When bots aim at opponents they have a grudge
+ -- added a new retaliation text. When bots aim at opponents they have a grudge
against, they might tell you now.
- For this to work you'll have to copy retaliation*.txt into
- /usr/share/games/atanks/ (or wherever you have your atanks data files)
+ For this to work you'll have to copy retaliation*.txt into
+ /usr/share/games/atanks/ (or wherever you have your atanks data files)
directory.
- -- removed waiting for explosions to finish when tanks blow up after being hit
+ -- removed waiting for explosions to finish when tanks blow up after being hit
by violent death missiles.
- I was longing to change that for ages, because I grew rather sick of tanks
- being suspended in mid-air for ages while all those violent death missile fly
- around. Now, they blow up immediately. However, when a normal missile is shot
+ I was longing to change that for ages, because I grew rather sick of tanks
+ being suspended in mid-air for ages while all those violent death missile fly
+ around. Now, they blow up immediately. However, when a normal missile is shot
and destroys a tank, it still waits for the missile's explosion to finish.
- -- changed tank falling behavior: They now can fall a short distance (2-5
+ -- changed tank falling behavior: They now can fall a short distance (2-5
pixels) without getting damage and without the parachutes to open.
- I was longing for that one, too. The reason is, that it is madness to waste
- 30+ parachutes and/or getting alot of damage while moving (!!) a tank down a
+ I was longing for that one, too. The reason is, that it is madness to waste
+ 30+ parachutes and/or getting alot of damage while moving (!!) a tank down a
flat slope.
- -- Fixed a bug with the tracing of spreads which resulted in bots hitting self
+ -- Fixed a bug with the tracing of spreads which resulted in bots hitting self
but not the opponent.
- -- added a security check when calculating offsets to napalm. Bots should not
+ -- added a security check when calculating offsets to napalm. Bots should not
drop napalm onto themselves any more when trying to hit a neighbor.
- -- added shadows to the text to increase readability. (Has to be turned on in
+ -- added shadows to the text to increase readability. (Has to be turned on in
the graphics menu, for compatibilities sake this option defaults to "off")
- -- added fading to the text, to ... well... to have a bit of eye candy. ;)
- (Has to be turned on in the graphics menu, for compatibilities sake this
+ -- added fading to the text, to ... well... to have a bit of eye candy. ;)
+ (Has to be turned on in the graphics menu, for compatibilities sake this
option defaults to "off")
-- cleaned up aiming calculation
-- cleaned up debugging messages for "DEBUG_AIM" and added some more info.
- -- Fixed a bug that could cause riot shots and blasts to start 10-20 pixels
+ -- Fixed a bug that could cause riot shots and blasts to start 10-20 pixels
away from the tank cannon.
- -- Fixed a bug that caused the the aiming system to fail in a very special
- (and rare, but still existing) situation, resulting in bots shooting into the
+ -- Fixed a bug that caused the the aiming system to fail in a very special
+ (and rare, but still existing) situation, resulting in bots shooting into the
ceiling.
-- Added a cleanup for all objects. No more debris on the screen!
- -- As the new aiming system doesn't need it any more, the wall type and boxed
- mode aren't changed any more once the last human player dies and skipping is
+ -- As the new aiming system doesn't need it any more, the wall type and boxed
+ mode aren't changed any more once the last human player dies and skipping is
turned on.
-- added boxed mode (Finally!)
- -- riot blasts fixed, they no longer shoot downwards when too less power is
+ -- riot blasts fixed, they no longer shoot downwards when too less power is
chosen
-- chain missiles now blast through dirt. (vertical spreads, too)
- -- Walltypes on non-human rounds with skip-AI on will now all change to
- Wrapped Walls & non-Boxed mode, but not before the current destructions
+ -- Walltypes on non-human rounds with skip-AI on will now all change to
+ Wrapped Walls & non-Boxed mode, but not before the current destructions
(violent deaths, falling tanks) are finished. (happy, sylikc? ;))
- -- a completely new aiming system has been written to make the boxed mode
+ -- a completely new aiming system has been written to make the boxed mode
possible
-- added tweaks to reduce "debris" left on screen
-- added tweaks to reduce "choking" of the display when too much is going on
- -- removed HLR_DEBUG, because in over 1000 rounds the speed-up-system never
+ -- removed HLR_DEBUG, because in over 1000 rounds the speed-up-system never
failed
-- There are now three different defines for debugging:
-- DEBUG
- Will show everything concerning inventories, shopping system, item
+ Will show everything concerning inventories, shopping system, item
selection and target selection of the bots on the console.
-- DEBUG_AIM
Will show all relevant numbers of the new aiming system on the console
-- DEBUG_AIM_SHOW
- Will draw dots and circles on the screen to make the "thinking" of the
+ Will draw dots and circles on the screen to make the "thinking" of the
aiming process visible.
(Warning: The game is no longer playable in this mode)
- -- balanced computer player values, according to the new aiming system.
- Generally speaking, the "useless" bot won't hit a barn with a pumpgun, while
+ -- balanced computer player values, according to the new aiming system.
+ Generally speaking, the "useless" bot won't hit a barn with a pumpgun, while
the "deadly" bot is very precise.
- -- If no target can be reached by normal means, bots now might teleport (or
- swap) out of their corner, or try to get rid of the obstacle by using riot
+ -- If no target can be reached by normal means, bots now might teleport (or
+ swap) out of their corner, or try to get rid of the obstacle by using riot
blasts
- -- While aiming bots now try to avoid hitting themselves or team mebers (when
- non-neutral) and are somewhat carefull not to hit themselves or team members
- with the blast damage their weapon produces. If a target can't be hit
+ -- While aiming bots now try to avoid hitting themselves or team mebers (when
+ non-neutral) and are somewhat carefull not to hit themselves or team members
+ with the blast damage their weapon produces. If a target can't be hit
otherwise, there have sacrefices to be made, though...
- -- target selection now uses the new aiming system to value targets lower
+ -- target selection now uses the new aiming system to value targets lower
which are hard (or impossible) to hit
- -- item selection now uses the new aiming system to value weapons lower with
+ -- item selection now uses the new aiming system to value weapons lower with
which the chosen target can't be hit properly
- -- The target system now calculates burrowers and rollers. (Which leads to
+ -- The target system now calculates burrowers and rollers. (Which leads to
some highly entertaining results! :))
- -- The more intelligent a bot is, the more "bounces" of walls or "wraps" it
+ -- The more intelligent a bot is, the more "bounces" of walls or "wraps" it
can calculate.
- -- The more intelligent a bot is, the more spread it can calculate. So don't
- wonder if a useless or guesser bot fires a super spread, happily hitting
+ -- The more intelligent a bot is, the more spread it can calculate. So don't
+ wonder if a useless or guesser bot fires a super spread, happily hitting
itself. It simply couldn't see that coming.
-- Velocity check rewritten. It now a) works and b) fixes the "Repulsor-
Shield-Bug" :D Yes, I am very happy about that!
It is handled in three different ways:
- -- No Spring Wall: The limit is set by maximum power, influenced by
+ -- No Spring Wall: The limit is set by maximum power, influenced by
the mass of the weapon.
- -- Spring Wall and Not Boxed: The Limit is doubled, or shooting at the
+ -- Spring Wall and Not Boxed: The Limit is doubled, or shooting at the
wall would destroy your missile.
- -- Spring Wall in a box: The limit is four times the normal limit, or
+ -- Spring Wall in a box: The limit is four times the normal limit, or
it won't be any fun!
I had very entertaining results with that! :)
- -- hit 2500! This means I have now tested over 2500 rounds with the new
+ -- hit 2500! This means I have now tested over 2500 rounds with the new
shopping- and target-selection systems since the last changes and/or bugfixes!
-- The Boost Value calculation is changed to be more dynamic to future changes
-- added a private member to count how many boost items are bought
- -- changed boost buying: inserted a per-round-limit aka iBoostItemsBought with
+ -- changed boost buying: inserted a per-round-limit aka iBoostItemsBought with
the help of the above count
-- language menu fixed
-- changed interest to be dynamically gained
- -- Added a new define, HLR_DEBUG which, when set, causes the round to end
- after each bot having one last shot if no human players are left. (Default,
+ -- Added a new define, HLR_DEBUG which, when set, causes the round to end
+ after each bot having one last shot if no human players are left. (Default,
currently, is 16)
- -- Fixed a bug that caused aTanks to freeze for seconds everytime too much is
- going on. (Explo, Text, Smoke, etc.) On some occasions there still can be a
- "hang" for a split second or so. But that could have been my computer, the
+ -- Fixed a bug that caused aTanks to freeze for seconds everytime too much is
+ going on. (Explo, Text, Smoke, etc.) On some occasions there still can be a
+ "hang" for a split second or so. But that could have been my computer, the
issue needs to be tested and watched.
- -- Started to add boxed mode (not implemented yet! But the menu item is there
+ -- Started to add boxed mode (not implemented yet! But the menu item is there
and the Wall-Display is changed)
-- Fixed a bug causing accelerated AI to crash
-- Included sylikc's Anti-Crash-Fix
- -- When bots have enough money, they do not spend everything. Jedi keep 50%,
+ -- When bots have enough money, they do not spend everything. Jedi keep 50%,
Neutrals 25% and sith 5% (the squanderers! ;) *hrhr*) for "bad times"
-- Repulsor Shield handling changed:
- changed ydist to be always negative, so missiles are *always* handled
- like they came from above! It remains to be tested whether this
- balances the repulsor shields, or renders them unbeatable (which could
+ changed ydist to be always negative, so missiles are *always* handled
+ like they came from above! It remains to be tested whether this
+ balances the repulsor shields, or renders them unbeatable (which could
happen...)
- Note: In over 500 rounds they perform better, but are still far away
+ Note: In over 500 rounds they perform better, but are still far away
from being "unbeatable"
- -- When determining pre-Buy items bots enforce buying of boost-Items if they
+ -- When determining pre-Buy items bots enforce buying of boost-Items if they
are too far away from the peak.
-- Limit for items raised up to 999 (like humans)
- -- limited AI-Only Rounds. If the last human players dies the remaining bots
- get 10 tries each to find a winner. If they do not succeed, the round is ended
+ -- limited AI-Only Rounds. If the last human players dies the remaining bots
+ get 10 tries each to find a winner. If they do not succeed, the round is ended
and the most healthy tank wins. (Or the round draws if two or more are equal)
- I added this, because there is a tiny chance (it never happened in the last
- weeks, but thrice only yesterday!) that the games gos infinite in AI-only
- turns. Situation: bots left have only small missiles and regenerate more each
+ I added this, because there is a tiny chance (it never happened in the last
+ weeks, but thrice only yesterday!) that the games gos infinite in AI-only
+ turns. Situation: bots left have only small missiles and regenerate more each
round than the small missile delivers.
With this limit it won't happen any more.
- -- When the last human dies and the wall type is changed, it is redrawn at
+ -- When the last human dies and the wall type is changed, it is redrawn at
once now.
-- limited interest && team money
- It is too easy to become super-marvel-universium-mega-hero by just waiting a
- couple of rounds until the bank has pumped up your account to some millions.
- Currently the bank refuses to pay more interest than 100k, but that hard limit
+ It is too easy to become super-marvel-universium-mega-hero by just waiting a
+ couple of rounds until the bank has pumped up your account to some millions.
+ Currently the bank refuses to pay more interest than 100k, but that hard limit
will be changed to a dynamic one. (see todo list)
- As for team money, bots now refuse to spend more than 500k into the pot.
+ As for team money, bots now refuse to spend more than 500k into the pot.
Humans, too, of course.
-- team win->WinRoundBonus divided instead of applied full.
- It is just extremely unfair that the teams get much more money for winning a
+ It is just extremely unfair that the teams get much more money for winning a
round when it is easier for them to do so.
-- 75% was too much, thus jedi now only pay 50% into the team account.
-- Bots try to save up money to get better equipment
@@ -948,69 +1017,69 @@ February 21, 2009
-- "Tools" to free themselves like Riot Blasts
-- Shields, if enough money is there
-- if all is set, look for dimpled/slick projectiles!
- Armor/Amps (aka "Boost-Items"), however, are limited, so they will only buy
- them, if someone has a higher value in boost-items. As these items can be
- bought randomly as well, they won't neccessarily wait for the human player(s)
+ Armor/Amps (aka "Boost-Items"), however, are limited, so they will only buy
+ them, if someone has a higher value in boost-items. As these items can be
+ bought randomly as well, they won't neccessarily wait for the human player(s)
to go ahead!
- -- Buying is no longer purely random but takes the bots weapon preferences
+ -- Buying is no longer purely random but takes the bots weapon preferences
into account.
-- The amount of money a bot wants to save up is used for the target-finding-
system as well:
- If a bot has less money, it is more likely to target weak opponents to fetch
- the kill-bonus. If it has enough money, it will more likely chose the biggest
+ If a bot has less money, it is more likely to target weak opponents to fetch
+ the kill-bonus. If it has enough money, it will more likely chose the biggest
threat.
- -- Money-saving is now limited, so that no bot tries to sum up millions. Right
- now the highest maximum, depending on type and weapon preferences, is 1M.
+ -- Money-saving is now limited, so that no bot tries to sum up millions. Right
+ now the highest maximum, depending on type and weapon preferences, is 1M.
(Deadly(=5) * 2 * Armageddon cost)
-- Teams are now handled differently in alot of places:
- -- Prior the shopping tour, every jedi adds 75%, and every sith 25% of
- their money to the "team-account". The team-money is reduced by a
- "transaction fee" (jedi: 5%, sith: 10%) and then divided onto all
+ -- Prior the shopping tour, every jedi adds 75%, and every sith 25% of
+ their money to the "team-account". The team-money is reduced by a
+ "transaction fee" (jedi: 5%, sith: 10%) and then divided onto all
team-members to help each other out.
- -- While searching for a target jedi try not to hit their team mates,
+ -- While searching for a target jedi try not to hit their team mates,
while sith do not care so much.
- -- While searching for a target jedi give alot less than neutrals
- about revenge, while sith can become furious (even against team
+ -- While searching for a target jedi give alot less than neutrals
+ about revenge, while sith can become furious (even against team
members!)
- -- jedi *can* become "super-defensive" (defensive > 1.0) while sith
- *can* become "super-offensive (defensive < -1.0). By this jedi can be
- extremely cautious every now and then, while sith like to throw
+ -- jedi *can* become "super-defensive" (defensive > 1.0) while sith
+ *can* become "super-offensive (defensive < -1.0). By this jedi can be
+ extremely cautious every now and then, while sith like to throw
caution out of the window. :)
-- Riot blasts/bombs are no longer chosen to be shot onto enemies!
- -- fixed a bug with the rating of buried targets while searching for a
+ -- fixed a bug with the rating of buried targets while searching for a
target/chosing an apropriate weapon
-- target selection and weapon selection now work together:
- A bot choses a weapon and tries to find the best target for it. Then the bot
- decides whether another weapon would be better for that target and switches to
+ A bot choses a weapon and tries to find the best target for it. Then the bot
+ decides whether another weapon would be better for that target and switches to
that. But they value weapons higher they like more, now.
-- corrected the _targetX modification for shaped charges and napalm:
- Bots trie to aim shaped charges so that they do not directly hit, for aiming
+ Bots trie to aim shaped charges so that they do not directly hit, for aiming
napalm they try to take wind into account.
- -- added blast check, to avoid using weapons which blast hit self, depending
+ -- added blast check, to avoid using weapons which blast hit self, depending
on intelligence and defensiveness
- -- added laser check to see whether a laser can be fired or would hit solid
+ -- added laser check to see whether a laser can be fired or would hit solid
rock only
- -- added check for buried targets and appropriate weapons (burrower,
+ -- added check for buried targets and appropriate weapons (burrower,
penetrator, tremor-tectonic)
- -- added a check to get more intelligent bots to do collateral damage when
+ -- added a check to get more intelligent bots to do collateral damage when
possible
- -- Added a custom Kamikaze text for the situation when bots decide to blow
+ -- Added a custom Kamikaze text for the situation when bots decide to blow
them selves up.
- -- Money interest was added right before entering the shop, resulting in
- interest added everytime a game was reloaded (and reloaded, and reloaded...).
- I changed that so that interest is added after leaving the shop, fixing the
- "Add-money-by-reload"-Bug. Further there is no more interest for money that is
+ -- Money interest was added right before entering the shop, resulting in
+ interest added everytime a game was reloaded (and reloaded, and reloaded...).
+ I changed that so that interest is added after leaving the shop, fixing the
+ "Add-money-by-reload"-Bug. Further there is no more interest for money that is
just earned in the last round.
- -- added offset if no human players left, raising the class of the bots to
- speed up the game without changing the difference in intelligence between bots
+ -- added offset if no human players left, raising the class of the bots to
+ speed up the game without changing the difference in intelligence between bots
too much.
- -- bots perform best on rubber and wrap walls. If no human players are left,
- the wall type is changed if steel/spring is chosen to speed up the accelerated
+ -- bots perform best on rubber and wrap walls. If no human players are left,
+ the wall type is changed if steel/spring is chosen to speed up the accelerated
game.
-- calculation of the shaped charge type weapons was improved (and corrected).
-- Added preference generation for bots that are set to "per play" when a game
- is loaded. The loading of the preferences is (unfortunately) disabled and I do
- not know why. As long as they *are* disabled, PerPlay-Config bots have to get
+ is loaded. The loading of the preferences is (unfortunately) disabled and I do
+ not know why. As long as they *are* disabled, PerPlay-Config bots have to get
a new config on game load, or they use the global config, rendering PerPlay-
Config useless.
-- For PhysObjects: reduced "virtual" height to 25 times screen height and
@@ -1043,7 +1112,7 @@ September 2, 2008
-- Credits and Help screens now ignore extra DOS return
characters in the text file.
(Submitted by sylikc)
- -- Added German language support.
+ -- Added German language support.
(Provided by Yama.)
-- Changed formula for dealing damage with shaped charges.
(Provided by Yama.)
@@ -1175,7 +1244,7 @@ March 18, 2008
-- Removed old satellite system
-- Created new satellite object.
-- Satellite now fires lasers.
- -- AI controlled tanks no longer adjust power for items.
+ -- AI controlled tanks no longer adjust power for items.
Saves time.
@@ -1262,7 +1331,7 @@ January 7, 2008
-- AI players randomly select weapons rather than
using an item until they run out.
-
+
December 28, 2007
Jesse (jessefrgsmith at yahoo.ca)
-- Firing a chian missile no longer fires another
@@ -1306,7 +1375,7 @@ December 9, 2007
November 29, 2007
Jesse (jessefrgsmith at yahoo.ca)
-- Fixed bug where, if a tank shoots itself and falls
- and the money penalty for self-damage drops the player's money
+ and the money penalty for self-damage drops the player's money
below $0, then the money is "corrected" to $1,000,000,000.
Thanks to Andy for pointing this out.
-- If both teams have the same number of wins, then
@@ -1334,7 +1403,7 @@ November 18, 2007
-- Wind Strength is now saved in the settings file.
-- Increased buffer size for options to avoid over-flow.
-
+
November 15, 2007
Jesse (jessefrgsmith at yahoo.ca)
-- Corrected timing bug that causes freeze on
@@ -1621,8 +1690,8 @@ March 21, 2007
Jesse (jessefrgsmith at yahoo.ca)
-- The player can now select to use either the custom
Atomic Tanks mouse cursor or the default OS cursor.
- Note: The OS cursor is probably faster.
- The option is located under the Graphics menu and
+ Note: The OS cursor is probably faster.
+ The option is located under the Graphics menu and
may require the game be restarted to take effect.
-- The ENTER key on the number pad can now be used
to indicate you are done entering text on Options
@@ -1654,7 +1723,7 @@ March 15, 2007
causes all tanks to fire their weapons.
See tank::activateSelection()
-
+
March 12, 2007
Jesse (jessefrgsmith at yahoo.ca)
-- The spelling of "lazer" has been changed to "laser".
@@ -1663,7 +1732,7 @@ March 12, 2007
the other items.
-- The Page Up and Page Down keys now cause the player's
power to go up or down dramatically (100 points instead of 5).
- -- Players can now choose to make their tanks
+ -- Players can now choose to make their tanks
appear differently. The options are Normal or Classic.
This is changed in the Players menu.
-- Tank style preferences are now saved in the config file.
@@ -1709,7 +1778,7 @@ Feb 24, 2007
-- Added a device called Swapper, which will cause the
player's tank to switch places with another tank. The
other tank is picked at random.
- -- Added a new wall type, Spring. The spring wall type
+ -- Added a new wall type, Spring. The spring wall type
causes missiles to bounce off the walls and floor
with more speed (x1.25) than they had before.
-- When one player kills another, the attacker gloats.
@@ -1722,7 +1791,7 @@ Feb 21, 2007
-- Disabled window closing in Windows to prevent hangs.
-- Added item: repair kit. A device which will slowly
repair the player's tank each turn.
- -- The number of rounds of play remaining
+ -- The number of rounds of play remaining
can be adjusted on the buying screen.
This is performed using the "-" and "+" signs on the keyboard.
Note, the "=" will also act like a "+" to add rounds.
@@ -1773,7 +1842,7 @@ Feb 9, 2007
-- Changed config file to save the sound option.
-- Made sure the command line option "--nosound"
over-rides the sound setting in the config file.
- -- The width and height of the Atanks window can now be
+ -- The width and height of the Atanks window can now be
changed from the Options menu, under Graphics. Changes
take effect after the game is exited and re-started.
-- Added screen width and height to the config file.
@@ -1809,7 +1878,7 @@ Jan 31, 2007
Jan 27, 2007
Jesse (jessefrgsmith at yahoo.ca)
- -- Allow player names to be more than 10 characters. Names
+ -- Allow player names to be more than 10 characters. Names
should now be able to expand to 23. Changed some output
code to allow for longer names without over-writing other
text on the screen. Changed file format to allow longer names
@@ -1826,7 +1895,7 @@ Jan 21, 2007
Jesse (jessefrgsmith at yahoo.ca)
-- Floating text no longer carries over to the next round.
Added newRound() function to FLOATTEXT object.
- -- When the window's "X" or close button is clicked, the game
+ -- When the window's "X" or close button is clicked, the game
will exit immediately.
=============== Atanks-2.0 Released ===========================
@@ -1888,7 +1957,7 @@ Nov 27, 2006
-- Made player money unsigned to avoid negative funds.
-- When player runs out of a type of ammo, game automatically
switches to a new ammo type.
-
+
================= Atanks-1.1.0 released ===================================
@@ -1921,7 +1990,7 @@ Nov 27, 2006
-- speed up redrawing algorith
-- new title screen
-- new top-bar in game
-
+
15. 9. 2002
-- fixed bug with game engine (this bug came with
@@ -1940,7 +2009,7 @@ Nov 27, 2006
-- icons of weapons
-- upgrade graphics
-- release of 0.5
-
+
5. 01. 2003 - Tom Hudson (tom at singular.org.uk):
-- Modified code to allow resizing of screen (currently only
at compile time, positions now scale)
diff --git a/Makefile b/Makefile
index 4092015..82dc1c9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,40 +1,321 @@
-VERSION=6.0
-PREFIX ?= /usr/
-DESTDIR ?=
-BINPREFIX = $(PREFIX)
+.PHONY: all install clean veryclean user winuser osxuser ubuntu \
+dist tarball zipfile source-dist i686-dist win32-dist
-BINDIR = ${BINPREFIX}bin/
-INSTALLDIR = ${PREFIX}share/games/atanks
+VERSION := 6.4
-export VERSION
-export PREFIX
-export INSTALLDIR
-FILENAME=atanks-${VERSION}
-INSTALL=$(PREFIX)bin/install -c
-DISTCOMMON=atanks/*.dat atanks/COPYING atanks/README atanks/TODO atanks/Changelog atanks/BUGS atanks/*.txt
-INCOMMON=COPYING README TODO Changelog *.txt unicode.dat
+DEBUG ?= NO
+# Note: Submit as "YES" to enable debugging
+# Note: If any flag starting with -g is found in the CXXFLAGS, DEBUG is
+# switched to YES no matter whether set otherwise or not.
-all:
- FLAGS=-DLINUX $(MAKE) -C src
+# The following switches can be used to fine-tune the debugging output:
+# Note: DEBUG_AICORE can be used to enable both DEBUG_AIMING and DEBUG_EMOTION
+# together with a single flag.
+DEBUG_AICORE ?= NO
+DEBUG_AIMING ?= NO
+DEBUG_EMOTION ?= NO
+DEBUG_FINANCE ?= NO
+DEBUG_OBJECTS ?= NO
+DEBUG_PHYSICS ?= NO
-install:
- mkdir -p $(DESTDIR)${BINDIR}
+# If the debug output shall be written to atanks.log, set this to YES
+DEBUG_LOG_TO_FILE ?= NO
+
+# These three are mutually exclusive. If all are set to yes,
+# address-sanitizing has priority, followed by leak, thread
+# is last.
+SANITIZE_ADDRESS ?= NO
+SANITIZE_LEAK ?= NO
+SANITIZE_THREAD ?= NO
+
+# The following is only used on gcc-4.9+ and only without debugging enabled.
+USE_LTO ?= NO
+
+# ------------------------------------
+# Install and target directories
+# ------------------------------------
+PREFIX ?= /usr
+DESTDIR ?=
+BINPREFIX ?= $(PREFIX)
+BINDIR ?= ${BINPREFIX}/bin
+INSTALLDIR ?= ${PREFIX}/share/games/atanks
+
+
+# ------------------------------------
+# Source files and objects
+# ------------------------------------
+SOURCES := $(wildcard src/*.cpp)
+MODULES := $(addprefix obj/,$(notdir $(SOURCES:.cpp=.o)))
+DEPENDS := $(addprefix dep/,$(notdir $(SOURCES:.cpp=.d)))
+
+
+# -------------------------------------
+# Platform to build for (Can be forced)
+# -------------------------------------
+PLATFORM ?= none
+ifeq (none,$(PLATFORM))
+ # The easiest way is to go through our make goals
+ # I know this looks weird, but the following simply means:
+ # "If the search for "win" in MAKECMDGOALS does not return an empty string"
+ ifneq (,$(findstring win,$(MAKECMDGOALS)))
+ PLATFORM := WIN32
+ else ifneq (,$(findstring osx,$(MAKECMDGOALS)))
+ PLATFORM := MACOSX
+ else
+ PLATFORM := LINUX
+ endif
+endif
+
+# If this is a user make goal, the install directory is forced to be local:
+ifneq (,$(findstring user,$(MAKECMDGOALS)))
+ INSTALLDIR := .
+endif
+
+
+# --------------------------------------------
+# Target executable and distribution file name
+# --------------------------------------------
+TARGET := atanks
+FILENAME := $(TARGET)-$(VERSION)
+
+
+# ------------------------------------
+# Tools to use
+# ------------------------------------
+INSTALL := $(shell which install)
+RM := $(shell which rm) -f
+CXX ?= g++
+SED := $(shell which sed)
+WINDRES :=
+
+ifeq (,$(findstring /,$(CXX)))
+ CXX := $(shell which $(CXX))
+endif
+
+
+# if this is a Windows target, prefer mingw32-g++ over g++
+# Further more the WIN32 Platform needs windres.exe to create src/atanks.res
+ifeq (WIN32,$(PLATFORM))
+ ifneq (,$(findstring /g++,$(CXX)))
+ CXX := $(shell which mingw32-g++)
+ endif
+ WINDRES := $(shell which windres.exe)
+ MODULES := ${MODULES} obj/atanks.res
+ TARGET := ${TARGET}.exe
+ RM := del /q
+endif
+
+# Use the compiler as the linker.
+LD := $(CXX)
+
+# --------------------------------------------------------------------------
+# Determine proper C++11 standard flag, and if and how stack protector works
+# --------------------------------------------------------------------------
+GCCVERSGTEQ47 := 0
+GCCVERSGTEQ49 := 0
+GCCUSESGOLD := 0
+GCC_STACKPROT :=
+GCC_CXXSTD := 0x
+PEDANDIC_FLAG := -pedantic
+
+# Note: It has to be evaluated which versions of clang and mingw
+# start using c++11 instead of c++0x.
+ifneq (,$(findstring /g++,$(CXX)))
+ GCCVERSGTEQ47 := $(shell expr `$(CXX) -dumpversion | cut -f1,2 -d. | tr -d '.'` \>= 47)
+ GCCVERSGTEQ49 := $(shell expr `$(CXX) -dumpversion | cut -f1,2 -d. | tr -d '.'` \>= 49)
+endif
+
+ifeq "$(GCCVERSGTEQ47)" "1"
+ GCC_CXXSTD := 11
+ PEDANDIC_FLAG := -Wpedantic
+ ifeq "$(GCCVERSGTEQ49)" "1"
+ GCC_STACKPROT := -fstack-protector-strong
+ GCCUSESGOLD := $(shell ld --version | head -n 1 | grep -c "GNU gold")
+ else
+ GCC_STACKPROT := -fstack-protector
+ endif
+endif
+
+
+# ------------------------------------
+# Flags for compiler and linker
+# ------------------------------------
+CPPFLAGS += -DDATA_DIR=\"${INSTALLDIR}\" -D$(PLATFORM) -DVERSION=\"${VERSION}\"
+CXXFLAGS += -Wall -Wextra $(PEDANDIC_FLAG) -std=c++$(GCC_CXXSTD)
+LDFLAGS +=
+
+# Depending on the platform, some values have to be appended:
+ifeq (MACOSX,$(PLATFORM))
+ CPPFLAGS := ${CPPFLAGS} -I/usr/local/include $(shell allegro-config --cppflags)
+ LDFLAGS := ${LDFLAGS} $(shell allegro-config --libs)
+else ifeq (WIN32,$(PLATFORM))
+ CPPFLAGS := ${CPPFLAGS} -I/usr/local/include
+ CXXFLAGS := ${CXXFLAGS} -mwindows
+ LDFLAGS := ${LDFLAGS} -mwindows -L. -lalleg44
+else
+ ifneq (,$(findstring bsd,$(MAKECMDGOALS)))
+ C_INCLUDE_PATH := /usr/local/include
+ CPLUS_INCLUDE_PATH := /usr/local/include
+ CXXFLAGS := ${CXXFLAGS} -Wno-c99-extensions
+ export C_INCLUDE_PATH
+ export CPLUS_INCLUDE_PATH
+ endif
+ CPPFLAGS := ${CPPFLAGS} -DNETWORK $(shell allegro-config --cppflags)
+ CXXFLAGS := ${CXXFLAGS} -pthread
+ LDFLAGS := ${LDFLAGS} $(shell allegro-config --libs) -lm -lpthread
+endif
+
+
+# If the make goal is "ubuntu", a special define is to be added:
+ifeq (UBUNTU,$(MAKECMDGOALS))
+ CPPFLAGS := ${CPPFLAGS} -DUBUNTU
+endif
+
+
+# ------------------------------------
+# Debug Mode settings
+# ------------------------------------
+HAS_DEBUG_FLAG := NO
+ifneq (,$(findstring -g,$(CXXFLAGS)))
+ ifneq (,$(findstring -ggdb,$(CXXFLAGS)))
+ HAS_DEBUG_FLAG := YES
+ endif
+ DEBUG := YES
+endif
+
+ifeq (YES,$(DEBUG))
+ ifeq (NO,$(HAS_DEBUG_FLAG))
+ CXXFLAGS := -ggdb ${CXXFLAGS} -O0
+ endif
+
+ CPPFLAGS := ${CPPFLAGS} -DATANKS_DEBUG
+ CXXFLAGS := ${CXXFLAGS} ${GCC_STACKPROT} -Wunused
+
+ # LTO is hard blocked now:
+ USE_LTO := NO
+
+ # address / thread sanitizer activation
+ ifeq (YES,$(SANITIZE_ADDRESS))
+ CXXFLAGS := ${CXXFLAGS} -fsanitize=address
+ LDFLAGS := ${LDFLAGS} -fsanitize=address
+ else ifeq (YES,$(SANITIZE_LEAK))
+ CXXFLAGS := ${CXXFLAGS} -fsanitize=leak
+ LDFLAGS := ${LDFLAGS} -fsanitize=leak
+ else ifeq (YES,$(SANITIZE_THREAD))
+ CPPFLAGS := ${CPPFLAGS} -DUSE_MUTEX_INSTEAD_OF_SPINLOCK
+ CXXFLAGS := ${CXXFLAGS} -fsanitize=thread -fPIC -O2 -ggdb
+ LDFLAGS := ${LDFLAGS} -fsanitize=thread -pie -O2 -ggdb
+ endif
+
+ # Add specific debug message flavours
+ ifeq (YES,$(DEBUG_AICORE))
+ CPPFLAGS := ${CPPFLAGS} -DATANKS_DEBUG_AIMING -DATANKS_DEBUG_EMOTIONS
+ endif
+ ifeq (YES,$(DEBUG_AIMING))
+ CPPFLAGS := ${CPPFLAGS} -DATANKS_DEBUG_AIMING
+ endif
+ ifeq (YES,$(DEBUG_EMOTION))
+ CPPFLAGS := ${CPPFLAGS} -DATANKS_DEBUG_EMOTIONS
+ endif
+ ifeq (YES,$(DEBUG_FINANCE))
+ CPPFLAGS := ${CPPFLAGS} -DATANKS_DEBUG_FINANCE
+ endif
+ ifeq (YES,$(DEBUG_OBJECTS))
+ CPPFLAGS := ${CPPFLAGS} -DATANKS_DEBUG_OBJECTS
+ endif
+ ifeq (YES,$(DEBUG_PHYSICS))
+ CPPFLAGS := ${CPPFLAGS} -DATANKS_DEBUG_PHYSICS
+ endif
+ ifeq (YES,$(DEBUG_LOG_TO_FILE))
+ CPPFLAGS := ${CPPFLAGS} -DATANKS_DEBUG_LOGTOFILE
+ endif
+
+else
+ CXXFLAGS := -march=native ${CXXFLAGS} -O2
+endif
+
+
+# Potentially enable LTO if this is gcc-4.9 and greater
+ifeq (YES,$(USE_LTO))
+ ifeq "$(GCCVERSGTEQ49)" "1"
+ CXXFLAGS := ${CXXFLAGS} -flto
+ endif
+ ifeq "$(GCCUSESGOLD)" "1"
+ CXXFLAGS := ${CXXFLAGS} -fuse-linker-plugin
+ endif
+endif
+
+
+
+# ------------------------------------
+# Distribution file lists
+# ------------------------------------
+DISTCOMMON := \
+atanks/*.dat atanks/COPYING atanks/README atanks/TODO \
+atanks/Changelog atanks/BUGS atanks/*.txt
+
+INCOMMON := COPYING README TODO Changelog *.txt unicode.dat
+
+# ------------------------------------
+# Default target
+# ------------------------------------
+
+all: $(TARGET)
+
+
+# ------------------------------------
+# Create dependencies
+# This is the standard as described
+# on the GNU make info manual.
+# (See Chapter 4.14)
+# ------------------------------------
+dep/%.d: src/%.cpp
+ @set -e; $(RM) $@; \
+ $(CXX) -MM $(CPPFLAGS) $(CXXFLAGS) $< > $@.$$$$; \
+ $(SED) 's,\($*\)\.o[ :]*,obj/\1.o $@ : ,g' < $@.$$$$ > $@; \
+ $(RM) $@.$$$$
+
+
+# ------------------------------------
+# Compile modules
+# ------------------------------------
+obj/%.o: src/%.cpp
+ @echo "Compiling $@"
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $<
+
+
+# ------------------------------------
+# Build windows res file
+# ------------------------------------
+obj/atanks.res:
+ifeq (WIN32,$(PLATFORM))
+ $(WINDRES) -i src/atanks.rc --input-format=rc -o obj/atanks.res -O coff
+else
+ @echo "This is no WIN32 platform, so why?"
+endif
+
+
+# ------------------------------------
+# Regular targets
+# ------------------------------------
+install: $(TARGET)
+ $(INSTALL) -d $(DESTDIR)${BINDIR}
$(INSTALL) -m 755 atanks $(DESTDIR)${BINDIR}
- mkdir -p $(DESTDIR)/usr/share/applications
- $(INSTALL) -m 644 atanks.desktop $(DESTDIR)/usr/share/applications
- mkdir -p $(DESTDIR)/usr/share/icons/hicolor/48x48/apps
- $(INSTALL) -m 644 atanks.png $(DESTDIR)/usr/share/icons/hicolor/48x48/apps
- mkdir -p $(DESTDIR)${INSTALLDIR}
- mkdir -p $(DESTDIR)${INSTALLDIR}/button
- mkdir -p $(DESTDIR)${INSTALLDIR}/misc
- mkdir -p $(DESTDIR)${INSTALLDIR}/missile
- mkdir -p $(DESTDIR)${INSTALLDIR}/sound
- mkdir -p $(DESTDIR)${INSTALLDIR}/stock
- mkdir -p $(DESTDIR)${INSTALLDIR}/tank
- mkdir -p $(DESTDIR)${INSTALLDIR}/tankgun
- mkdir -p $(DESTDIR)${INSTALLDIR}/title
- mkdir -p $(DESTDIR)${INSTALLDIR}/text
+ $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/applications
+ $(INSTALL) -m 644 atanks.desktop $(DESTDIR)$(PREFIX)/share/applications
+ $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps
+ $(INSTALL) -m 644 atanks.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps
+ $(INSTALL) -d $(DESTDIR)${INSTALLDIR}
+ $(INSTALL) -d $(DESTDIR)${INSTALLDIR}/button
+ $(INSTALL) -d $(DESTDIR)${INSTALLDIR}/misc
+ $(INSTALL) -d $(DESTDIR)${INSTALLDIR}/missile
+ $(INSTALL) -d $(DESTDIR)${INSTALLDIR}/sound
+ $(INSTALL) -d $(DESTDIR)${INSTALLDIR}/stock
+ $(INSTALL) -d $(DESTDIR)${INSTALLDIR}/tank
+ $(INSTALL) -d $(DESTDIR)${INSTALLDIR}/tankgun
+ $(INSTALL) -d $(DESTDIR)${INSTALLDIR}/title
+ $(INSTALL) -d $(DESTDIR)${INSTALLDIR}/text
$(INSTALL) -m 644 $(INCOMMON) $(DESTDIR)${INSTALLDIR}
$(INSTALL) -m 644 button/* $(DESTDIR)${INSTALLDIR}/button
$(INSTALL) -m 644 misc/* $(DESTDIR)${INSTALLDIR}/misc
@@ -46,45 +327,61 @@ install:
$(INSTALL) -m 644 title/* $(DESTDIR)${INSTALLDIR}/title
$(INSTALL) -m 644 text/* $(DESTDIR)${INSTALLDIR}/text
-user:
- INSTALLDIR=./ FLAGS=-DLINUX $(MAKE) -C src
+$(TARGET): $(MODULES)
+ $(LD) -o $@ $(MODULES) $(CPPFLAGS) $(LDFLAGS) $(CXXFLAGS)
-winuser:
- INSTALLDIR=./ FLAGS=-DWIN32 $(MAKE) -C src -f Makefile.windows
+clean:
+ $(RM) obj/*
-osxuser:
- INSTALLDIR=./ FLAGS="-DMACOSX" $(MAKE) -C src -f Makefile.bsd
+veryclean: clean
+ifeq (WIN32,$(PLATFORM))
+ $(RM) $(TARGET).exe
+else
+ $(RM) $(TARGET)
+endif
-ubuntu:
- FLAGS="-DLINUX -DUBUNTU" $(MAKE) -C src
-clean:
- rm -f atanks atanks.exe
- $(MAKE) -C src clean
+# ------------------------------------
+# User (local) targets
+# ------------------------------------
+
+user: $(TARGET)
+winuser: $(TARGET)
+osxuser: $(TARGET)
+bsduser: $(TARGET)
+ubuntu: $(TARGET)
+
+# ------------------------------------
+# Distribution targets
+# ------------------------------------
dist: source-dist i686-dist win32-dist
-tarball: clean
- cd .. && tar czf atanks-$(VERSION).tar.gz atanks-$(VERSION)
+tarball: veryclean
+ cd .. && tar czf $(FILENAME).tar.gz $(FILENAME) --exclude=.git
-zipfile: clean
- cd .. && zip -r atanks-$(VERSION)-source.zip atanks-$(VERSION)
+zipfile: veryclean
+ cd .. && zip -r $(FILENAME)-source.zip $(FILENAME) -x *.git*
-source-dist:
+source-dist: $(TARGET)
cd ../; \
- rm -f ${FILENAME}.tar.gz; \
- tar cvf ${FILENAME}.tar atanks/src/*.cpp atanks/src/*.h atanks/src/Makefile atanks/src/Makefile.windows atanks/Makefile ${DISTCOMMON}; \
- gzip ${FILENAME}.tar
+ $(RM) $(FILENAME).tar.gz; \
+ tar czf $(FILENAME).tar.gz atanks/src/*.cpp atanks/src/*.h atanks/Makefile $(DISTCOMMON)
-i686-dist:
+i686-dist: $(TARGET)
cd ../; \
- rm -f ${FILENAME}-i686-dist.tar; \
- rm -f ${FILENAME}-i686-dist.tar.gz; \
+ $(RM) $(FILENAME)-i686-dist.tar.gz; \
strip atanks/atanks; \
- tar cvf ${FILENAME}-i686-dist.tar atanks/atanks ${DISTCOMMON}; \
- gzip ${FILENAME}-i686-dist.tar;
+ tar czf $(FILENAME)-i686-dist.tar atanks/atanks $(DISTCOMMON)
-win32-dist:
+win32-dist: $(TARGET)
cd ../; \
- rm -f ${FILENAME}-win32-dist.zip; \
- zip ${FILENAME}-win32-dist.zip atanks/Atanks.exe atanks/alleg40.dll ${DISTCOMMON};
+ $(RM) $(FILENAME)-win32-dist.zip; \
+ zip -r $(FILENAME)-win32-dist.zip atanks/atanks.exe atanks/alleg40.dll $(DISTCOMMON)
+
+# ------------------------------------
+# Include all dependency files
+# ------------------------------------
+ifeq (,$(findstring clean,$(MAKECMDGOALS)))
+ -include $(DEPENDS)
+endif
diff --git a/Makefile.bsd b/Makefile.bsd
new file mode 100644
index 0000000..f710e94
--- /dev/null
+++ b/Makefile.bsd
@@ -0,0 +1,298 @@
+.PHONY: all clean veryclean
+
+HERE := ${.CURDIR}
+MAKEOBJDIRPREFIX := obj
+MODULES := network.o aicore.o debris_pool.o sky.o \
+ teleport.o gfxData.o land.o atanks.o text.o \
+ floattext.o files.o virtobj.o player_types.o \
+ shop.o perlin.o player.o beam.o \
+ optionitemplayer.o satellite.o physobj.o \
+ globaldata.o optionitemcolour.o clock.o menu.o \
+ client.o optiontypes.o tank.o environment.o \
+ decor.o debug.o globaltypes.o missile.o \
+ optionitemmenu.o gameloop.o main.o sound.o \
+ optionitembase.o optionscreens.o button.o \
+ explosion.o update.o
+
+VERSION := 6.3
+PREFIX := .
+INSTALLDIR := ${PREFIX}/
+
+CXX ?= clang++
+LD ?= $(CXX)
+LIB := ar
+
+ALLEGLD != allegro-config --libs
+CXXFLAGS += -g -std=c++0x -Wall -Wextra -pedantic -Wno-c99-extensions -I../src -I/usr/local/include -pthread
+CPPFLAGS += -DNETWORK -DDATA_DIR=\"${INSTALLDIR}\" -DVERSION=\"${VERSION}\"
+LDFLAGS := $(ALLEGLD) -lm -lpthread
+
+OUTPUT := $(HERE)/atanks
+
+all: $(OUTPUT)
+
+clean:
+ rm -f $(MODULES)
+
+veryclean: clean
+ rm $(OUTPUT)
+
+network.o: ../src/network.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o network.o -c ../src/network.cpp
+aicore.o: ../src/aicore.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o aicore.o -c ../src/aicore.cpp
+debris_pool.o: ../src/debris_pool.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o debris_pool.o -c ../src/debris_pool.cpp
+sky.o: ../src/sky.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o sky.o -c ../src/sky.cpp
+teleport.o: ../src/teleport.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o teleport.o -c ../src/teleport.cpp
+gfxData.o: ../src/gfxData.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o gfxData.o -c ../src/gfxData.cpp
+land.o: ../src/land.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o land.o -c ../src/land.cpp
+atanks.o: ../src/atanks.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o atanks.o -c ../src/atanks.cpp
+text.o: ../src/text.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o text.o -c ../src/text.cpp
+floattext.o: ../src/floattext.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o floattext.o -c ../src/floattext.cpp
+files.o: ../src/files.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o files.o -c ../src/files.cpp
+virtobj.o: ../src/virtobj.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o virtobj.o -c ../src/virtobj.cpp
+player_types.o: ../src/player_types.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o player_types.o -c ../src/player_types.cpp
+shop.o: ../src/shop.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o shop.o -c ../src/shop.cpp
+perlin.o: ../src/perlin.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o perlin.o -c ../src/perlin.cpp
+player.o: ../src/player.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o player.o -c ../src/player.cpp
+beam.o: ../src/beam.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o beam.o -c ../src/beam.cpp
+optionitemplayer.o: ../src/optionitemplayer.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o optionitemplayer.o -c ../src/optionitemplayer.cpp
+satellite.o: ../src/satellite.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o satellite.o -c ../src/satellite.cpp
+physobj.o: ../src/physobj.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o physobj.o -c ../src/physobj.cpp
+globaldata.o: ../src/globaldata.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o globaldata.o -c ../src/globaldata.cpp
+optionitemcolour.o: ../src/optionitemcolour.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o optionitemcolour.o -c ../src/optionitemcolour.cpp
+clock.o: ../src/clock.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o clock.o -c ../src/clock.cpp
+menu.o: ../src/menu.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o menu.o -c ../src/menu.cpp
+client.o: ../src/client.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o client.o -c ../src/client.cpp
+optiontypes.o: ../src/optiontypes.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o optiontypes.o -c ../src/optiontypes.cpp
+tank.o: ../src/tank.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o tank.o -c ../src/tank.cpp
+environment.o: ../src/environment.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o environment.o -c ../src/environment.cpp
+decor.o: ../src/decor.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o decor.o -c ../src/decor.cpp
+debug.o: ../src/debug.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o debug.o -c ../src/debug.cpp
+globaltypes.o: ../src/globaltypes.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o globaltypes.o -c ../src/globaltypes.cpp
+missile.o: ../src/missile.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o missile.o -c ../src/missile.cpp
+optionitemmenu.o: ../src/optionitemmenu.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o optionitemmenu.o -c ../src/optionitemmenu.cpp
+gameloop.o: ../src/gameloop.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o gameloop.o -c ../src/gameloop.cpp
+main.o: ../src/main.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o main.o -c ../src/main.cpp
+sound.o: ../src/sound.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o sound.o -c ../src/sound.cpp
+optionitembase.o: ../src/optionitembase.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o optionitembase.o -c ../src/optionitembase.cpp
+optionscreens.o: ../src/optionscreens.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o optionscreens.o -c ../src/optionscreens.cpp
+button.o: ../src/button.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o button.o -c ../src/button.cpp
+explosion.o: ../src/explosion.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o explosion.o -c ../src/explosion.cpp
+update.o: ../src/update.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o update.o -c ../src/update.cpp
+
+$(OUTPUT): $(MODULES)
+ $(CXX) $(MODULES) -o $(OUTPUT) $(CPPFLAGS) $(LDFLAGS) $(CXXFLAGS)
+
+
+# Dependencies:
+aicore.o : ../src/aicore.cpp ../src/aicore.h ../src/player_types.h ../src/main.h \
+ ../src/debug.h ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h \
+ ../src/globaldata.h ../src/text.h ../src/environment.h ../src/network.h \
+ ../src/gfxData.h ../src/floattext.h ../src/virtobj.h ../src/player.h ../src/tank.h \
+ ../src/physobj.h ../src/missile.h ../src/beam.h ../src/explosion.h
+atanks.o : ../src/atanks.cpp ../src/debug.h ../src/globals.h ../src/globaldata.h \
+ ../src/main.h ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h \
+ ../src/environment.h ../src/network.h ../src/gfxData.h ../src/text.h \
+ ../src/optionscreens.h ../src/menu.h ../src/optionitem.h ../src/optionitembase.h \
+ ../src/optiontypes.h ../src/optionitemmenu.h ../src/optionitemplayer.h \
+ ../src/button.h ../src/player.h ../src/player_types.h ../src/files.h ../src/update.h \
+ ../src/tank.h ../src/physobj.h ../src/virtobj.h ../src/floattext.h ../src/beam.h \
+ ../src/missile.h ../src/gameloop.h ../src/clock.h ../src/client.h
+beam.o : ../src/beam.cpp ../src/environment.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/network.h ../src/gfxData.h ../src/physobj.h ../src/virtobj.h \
+ ../src/player.h ../src/player_types.h ../src/decor.h ../src/debris_pool.h ../src/tank.h \
+ ../src/floattext.h ../src/beam.h ../src/explosion.h ../src/sound.h
+button.o : ../src/button.cpp ../src/button.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/environment.h ../src/network.h ../src/gfxData.h ../src/sound.h
+client.o : ../src/client.cpp ../src/button.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/environment.h ../src/network.h ../src/gfxData.h ../src/files.h \
+ ../src/satellite.h ../src/update.h ../src/client.h ../src/beam.h ../src/physobj.h \
+ ../src/virtobj.h ../src/explosion.h ../src/missile.h ../src/teleport.h \
+ ../src/floattext.h ../src/player.h ../src/player_types.h ../src/tank.h ../src/sky.h
+clock.o : ../src/clock.cpp ../src/clock.h ../src/debug.h
+debris_pool.o : ../src/debris_pool.cpp ../src/debris_pool.h ../src/main.h \
+ ../src/debug.h ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h \
+ ../src/globaldata.h ../src/text.h ../src/environment.h ../src/network.h \
+ ../src/gfxData.h
+debug.o : ../src/debug.cpp ../src/debug.h
+decor.o : ../src/decor.cpp ../src/decor.h ../src/physobj.h ../src/globaltypes.h \
+ ../src/virtobj.h ../src/main.h ../src/debug.h ../src/imagedefs.h ../src/externs.h \
+ ../src/globaldata.h ../src/text.h ../src/environment.h ../src/network.h \
+ ../src/gfxData.h ../src/debris_pool.h ../src/sound.h ../src/tank.h ../src/floattext.h
+environment.o : ../src/environment.cpp ../src/main.h ../src/debug.h ../src/imagedefs.h \
+ ../src/globaltypes.h ../src/externs.h ../src/globaldata.h ../src/text.h \
+ ../src/environment.h ../src/network.h ../src/gfxData.h ../src/missile.h \
+ ../src/physobj.h ../src/virtobj.h ../src/tank.h ../src/floattext.h ../src/files.h \
+ ../src/sound.h ../src/player.h ../src/player_types.h
+explosion.o : ../src/explosion.cpp ../src/main.h ../src/debug.h ../src/imagedefs.h \
+ ../src/globaltypes.h ../src/externs.h ../src/globaldata.h ../src/text.h \
+ ../src/environment.h ../src/network.h ../src/gfxData.h ../src/explosion.h \
+ ../src/physobj.h ../src/virtobj.h ../src/missile.h ../src/decor.h ../src/debris_pool.h \
+ ../src/tank.h ../src/floattext.h ../src/player.h ../src/player_types.h
+files.o : ../src/files.cpp ../src/main.h ../src/debug.h ../src/imagedefs.h \
+ ../src/globaltypes.h ../src/externs.h ../src/globaldata.h ../src/text.h \
+ ../src/environment.h ../src/network.h ../src/gfxData.h ../src/player.h \
+ ../src/player_types.h ../src/files.h
+floattext.o : ../src/floattext.cpp ../src/floattext.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/environment.h ../src/network.h ../src/gfxData.h ../src/virtobj.h
+gameloop.o : ../src/gameloop.cpp ../src/main.h ../src/debug.h ../src/imagedefs.h \
+ ../src/globaltypes.h ../src/externs.h ../src/globaldata.h ../src/text.h \
+ ../src/environment.h ../src/network.h ../src/gfxData.h ../src/files.h \
+ ../src/satellite.h ../src/update.h ../src/land.h ../src/clock.h ../src/floattext.h \
+ ../src/virtobj.h ../src/tank.h ../src/physobj.h ../src/explosion.h ../src/beam.h \
+ ../src/missile.h ../src/decor.h ../src/debris_pool.h ../src/teleport.h ../src/sky.h \
+ ../src/sound.h ../src/gameloop.h ../src/player.h ../src/player_types.h ../src/aicore.h \
+ ../src/shop.h
+gfxData.o : ../src/gfxData.cpp ../src/main.h ../src/debug.h ../src/imagedefs.h \
+ ../src/globaltypes.h ../src/externs.h ../src/globaldata.h ../src/text.h \
+ ../src/environment.h ../src/network.h ../src/gfxData.h
+globaldata.o : ../src/globaldata.cpp ../src/player.h ../src/player_types.h \
+ ../src/main.h ../src/debug.h ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h \
+ ../src/globaldata.h ../src/text.h ../src/environment.h ../src/network.h \
+ ../src/gfxData.h ../src/files.h ../src/tank.h ../src/physobj.h ../src/virtobj.h \
+ ../src/floattext.h ../src/sound.h ../src/debris_pool.h
+globaltypes.o : ../src/globaltypes.cpp ../src/globaltypes.h
+land.o : ../src/land.cpp ../src/land.h ../src/main.h ../src/debug.h ../src/imagedefs.h \
+ ../src/globaltypes.h ../src/externs.h ../src/globaldata.h ../src/text.h \
+ ../src/environment.h ../src/network.h ../src/gfxData.h ../src/files.h ../src/gameloop.h \
+ ../src/player.h ../src/player_types.h
+main.o : ../src/main.cpp ../src/main.h ../src/debug.h ../src/imagedefs.h \
+ ../src/globaltypes.h ../src/externs.h ../src/globaldata.h ../src/text.h \
+ ../src/environment.h ../src/network.h ../src/gfxData.h
+menu.o : ../src/menu.cpp ../src/optioncontent.h ../src/optiontypes.h \
+ ../src/globaltypes.h ../src/optionitemcolour.h ../src/optionitembase.h \
+ ../src/environment.h ../src/main.h ../src/debug.h ../src/imagedefs.h ../src/externs.h \
+ ../src/globaldata.h ../src/text.h ../src/network.h ../src/gfxData.h ../src/button.h \
+ ../src/menu.h ../src/optionitem.h ../src/optionitemmenu.h ../src/optionitemplayer.h \
+ ../src/player.h ../src/player_types.h ../src/clock.h
+missile.o : ../src/missile.cpp ../src/explosion.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/environment.h ../src/network.h ../src/gfxData.h ../src/physobj.h \
+ ../src/virtobj.h ../src/missile.h ../src/decor.h ../src/debris_pool.h ../src/tank.h \
+ ../src/floattext.h ../src/player.h ../src/player_types.h ../src/beam.h ../src/sound.h \
+ ../src/aicore.h
+network.o : ../src/network.cpp ../src/network.h ../src/player.h ../src/player_types.h \
+ ../src/main.h ../src/debug.h ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h \
+ ../src/globaldata.h ../src/text.h ../src/environment.h ../src/gfxData.h
+optionitembase.o : ../src/optionitembase.cpp ../src/button.h ../src/main.h \
+ ../src/debug.h ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h \
+ ../src/globaldata.h ../src/text.h ../src/environment.h ../src/network.h \
+ ../src/gfxData.h ../src/menu.h ../src/optionitem.h ../src/optionitembase.h \
+ ../src/optiontypes.h ../src/optionitemmenu.h ../src/optionitemplayer.h \
+ ../src/floattext.h ../src/virtobj.h
+optionitemcolour.o : ../src/optionitemcolour.cpp ../src/optionitemcolour.h \
+ ../src/optionitembase.h ../src/environment.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/network.h ../src/gfxData.h ../src/optiontypes.h
+optionitemmenu.o : ../src/optionitemmenu.cpp ../src/optionitemmenu.h \
+ ../src/optionitembase.h ../src/environment.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/network.h ../src/gfxData.h ../src/optiontypes.h ../src/menu.h \
+ ../src/optionitem.h ../src/optionitemplayer.h ../src/button.h ../src/clock.h
+optionitemplayer.o : ../src/optionitemplayer.cpp ../src/optionitemplayer.h \
+ ../src/optionitembase.h ../src/environment.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/network.h ../src/gfxData.h ../src/optiontypes.h ../src/player.h \
+ ../src/player_types.h ../src/floattext.h ../src/virtobj.h
+optionscreens.o : ../src/optionscreens.cpp ../src/optionscreens.h ../src/menu.h \
+ ../src/optionitem.h ../src/optionitembase.h ../src/environment.h ../src/main.h \
+ ../src/debug.h ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h \
+ ../src/globaldata.h ../src/text.h ../src/network.h ../src/gfxData.h \
+ ../src/optiontypes.h ../src/optionitemmenu.h ../src/optionitemplayer.h \
+ ../src/button.h ../src/player.h ../src/player_types.h ../src/files.h ../src/sound.h
+optiontypes.o : ../src/optiontypes.cpp ../src/optiontypes.h
+perlin.o : ../src/perlin.cpp ../src/main.h ../src/debug.h ../src/imagedefs.h \
+ ../src/globaltypes.h ../src/externs.h ../src/globaldata.h ../src/text.h \
+ ../src/environment.h ../src/network.h ../src/gfxData.h
+physobj.o : ../src/physobj.cpp ../src/physobj.h ../src/globaltypes.h ../src/virtobj.h \
+ ../src/main.h ../src/debug.h ../src/imagedefs.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/environment.h ../src/network.h ../src/gfxData.h
+player.o : ../src/player.cpp ../src/environment.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/network.h ../src/gfxData.h ../src/player.h ../src/player_types.h \
+ ../src/tank.h ../src/physobj.h ../src/virtobj.h ../src/floattext.h ../src/menu.h \
+ ../src/optionitem.h ../src/optionitembase.h ../src/optiontypes.h \
+ ../src/optionitemmenu.h ../src/optionitemplayer.h ../src/button.h ../src/files.h \
+ ../src/missile.h ../src/aicore.h
+player_types.o : ../src/player_types.cpp ../src/player_types.h ../src/main.h \
+ ../src/debug.h ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h \
+ ../src/globaldata.h ../src/text.h ../src/environment.h ../src/network.h \
+ ../src/gfxData.h
+satellite.o : ../src/satellite.cpp ../src/environment.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/network.h ../src/gfxData.h ../src/satellite.h ../src/beam.h \
+ ../src/physobj.h ../src/virtobj.h
+shop.o : ../src/shop.cpp ../src/shop.h ../src/player.h ../src/player_types.h \
+ ../src/main.h ../src/debug.h ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h \
+ ../src/globaldata.h ../src/text.h ../src/environment.h ../src/network.h \
+ ../src/gfxData.h ../src/files.h ../src/gameloop.h
+sky.o : ../src/sky.cpp ../src/externs.h ../src/globaldata.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/text.h ../src/environment.h \
+ ../src/network.h ../src/gfxData.h ../src/sky.h ../src/files.h ../src/gameloop.h \
+ ../src/player.h ../src/player_types.h
+sound.o : ../src/sound.cpp ../src/sound.h ../src/externs.h ../src/globaldata.h \
+ ../src/main.h ../src/debug.h ../src/imagedefs.h ../src/globaltypes.h ../src/text.h \
+ ../src/environment.h ../src/network.h ../src/gfxData.h
+tank.o : ../src/tank.cpp ../src/floattext.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/environment.h ../src/network.h ../src/gfxData.h ../src/virtobj.h \
+ ../src/explosion.h ../src/physobj.h ../src/teleport.h ../src/missile.h ../src/player.h \
+ ../src/player_types.h ../src/beam.h ../src/tank.h ../src/sound.h
+teleport.o : ../src/teleport.cpp ../src/environment.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/network.h ../src/gfxData.h ../src/teleport.h ../src/virtobj.h \
+ ../src/tank.h ../src/physobj.h ../src/floattext.h ../src/sound.h ../src/player.h \
+ ../src/player_types.h
+text.o : ../src/text.cpp ../src/text.h ../src/main.h ../src/debug.h ../src/imagedefs.h \
+ ../src/globaltypes.h ../src/externs.h ../src/globaldata.h ../src/environment.h \
+ ../src/network.h ../src/gfxData.h
+update.o : ../src/update.cpp ../src/debug.h ../src/update.h ../src/network.h \
+ ../src/externs.h ../src/globaldata.h ../src/main.h ../src/imagedefs.h \
+ ../src/globaltypes.h ../src/text.h ../src/environment.h ../src/gfxData.h
+virtobj.o : ../src/virtobj.cpp ../src/virtobj.h ../src/main.h ../src/debug.h \
+ ../src/imagedefs.h ../src/globaltypes.h ../src/externs.h ../src/globaldata.h \
+ ../src/text.h ../src/environment.h ../src/network.h ../src/gfxData.h
diff --git a/README b/README
index 1239007..d1023f3 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-README file for Atomic Tanks
+README file for Atomic Tanks
============================================
What is Atomic Tanks?
@@ -149,7 +149,7 @@ Use the left and right arrow buttons on your keyboard to
aim your tank's gun. The up and down arrow keys adjust the
speed (power) of the shot. The space bar fires the tanks gun. If
you want to change which weapon you will be using, press the
-TAB button or the BACKSPACE button. The round is over when
+TAB button or the BACKSPACE button. The round is over when
there is one tank left standing.
@@ -167,14 +167,19 @@ SPACEBAR -- Fires weapons and selects/toggles menu items.
ENTER -- Is similar to pressing the OK button on a menu.
UP/DOWN arrows -- Adjusts tank power and cycles through menu items. Scrolls on the buying screen.
LEFT/RIGHT arrows -- Aim the tank's gun. Also buys and sells on the buying screen or adjusts values in menus.
-ESC -- Cancel out of a menu.
+ESC -- Cancel out of a menu.
-F10 -- Tells the computer to take over your tank for the remainder of the round.
- Also saves games when on the buying screen.
+F1 -- Take a screenshot
+
+F10 -- Tells the computer to take over your tank for the remainder of the round.
+ Also saves games when on the buying screen.
V, v -- The "v" key controls the volume during matches. Pressing lower-case "v"
decreases the volume and upper-case "V" increases the volume.
+~ -- Show/hide the scoreboard during rounds. On german (and possibly other)
+ keyboards the scoreboard is switched with the key '#'.
+
@@ -237,7 +242,7 @@ Atomic Tanks is a great game, but it isn't perfect.
We, the developers, are always trying to improve the
experience our game brings to you. If you have
a problem with installing or playing Atomic Tanks,
-please let us know. Mail me at jessefrgsmith at yahoo.ca
+please let us know. Mail me at jessefrgsmith at yahoo.ca
We encourage you to give your feedback. Please give
as many details as possible so we can help you.
diff --git a/TODO b/TODO
index 05f7dc5..be3d592 100644
--- a/TODO
+++ b/TODO
@@ -5,20 +5,32 @@ lower priority at the bottom. It is currently only vaguely correct.
Bugs:
- Player options can become scrambled
if accessed too often.
+ -> Should be fixed with atanks-5.9_aiu1
- Wrap around ceiling should not destroy projectile.
+ -> Of course it should. Wrapping is only horizontal, not vertical.
+ -> Added a new option to enable that in atanks-6.0_aiu8
- Make sure network client doesn't get
unlimited shots.
- Atanks crashes when Windows players click the Network option
in the main Options menu.
+ -> Should be fixed with atanks-5.9_aiu1
- When cycling through tanks on Players screen,
the old tank does not get erased.
+ -> Should be fixed with atanks-5.9_aiu1
+ - AI controlled tanks buy a limited number of defensive
+ upgrades during very long games. Plating, for example.
+In player.cpp the PLAYER::computerSelectItem selects a weapon or item for the bot to use.
+Values less than WEAPONS are weapons and values from WEAPONS to THINGS are items.
+After lots of black magic to select a weapon or item, finally in line 3567 the current_weapon containing a value from 0 to THINGS is passed to PLAYER::computerSelectTarget, which in line 3189 uses it as index into the array weapon which only has size WEAPONS, resulting in a segfault.
+Line 3561 also calls PLAYER::computerSelectTarget with a value from 0 to THINGS, so probably the same can happen there.
+I don't know how to fix this, since I don't know how PLAYER::computerSelectTarget should behave when an item is passed instead of a weapon, but probaly some of you know this and can fix.
+ -> This is fixed in atanks-6.1_aiu2
Features:
- Add scroll bar to buying screen.
- Add randomize button to buying screen to have
items automatically purchased.
-
- Field repair kit: Spend a turn to repair your tank
rather than fire.
Limited uses, heals more than Auto Repair Kit
@@ -27,10 +39,13 @@ lower priority at the bottom. It is currently only vaguely correct.
its heat signature
Yield: Large missile
- Better AI against tanks with SDI.
+ -> Added in atanks-6.1_aiu3
- Update client ground surface more often.
- When switching languages, player menu should display correct text.
+ -> Should be fixed with atanks-5.9_aiu1
- Client needs a buying screen.
- Make tank size variable.
+ -> It is in atanks-6.0_aiu? Don't remember which...
- Add rocks as semi-destructable items.
@@ -51,8 +66,10 @@ lower priority at the bottom. It is currently only vaguely correct.
-- Make main window scalable
(Needs to wait until Allegro 5.x)
+ -> Allegro 5 is a no-opt. I gave up on it but will try SFML-2 soon.
-- Harder ground
+ -> What is that supposed to mean?
-- High voltage missiles (discharge on impact or when within range)
(Bharat Dhareshwar)
diff --git a/allegro.cfg b/allegro.cfg
new file mode 100644
index 0000000..118b6a8
--- /dev/null
+++ b/allegro.cfg
@@ -0,0 +1,2 @@
+[graphics]
+disable_vsync = yes
diff --git a/allegro.supp b/allegro.supp
new file mode 100644
index 0000000..670a1c2
--- /dev/null
+++ b/allegro.supp
@@ -0,0 +1,238 @@
+{
+ Ignore_glibc_conditional_jump
+ Memcheck:Cond
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:dl_*
+ fun:_dl_*
+ fun:_dl_*
+ obj:/lib64/ld-*.so
+}
+
+{
+ Ignore_install_sound_mempool_called
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ obj:*
+ fun:install_*
+ fun:_Z18init_game_settingsP10GLOBALDATA
+ fun:main
+}
+
+{
+ Ignore_install_mempool_called_short
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ obj:*
+ fun:install_*
+ fun:_Z18init_game_settingsP10GLOBALDATA
+ fun:main
+}
+
+{
+ Ignore_install_mempool_called_short_def
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ obj:*
+ fun:install_*
+ fun:_Z18init_game_settingsP10GLOBALDATA
+ fun:main
+}
+
+{
+ Ignore_install_mempool_called_short_noobj_def
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:install_*
+ fun:_Z18init_game_settingsP10GLOBALDATA
+ fun:main
+}
+
+{
+ Ignore_install_mempool_called_mid
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ obj:*
+ fun:install_*
+ fun:_Z18init_game_settingsP10GLOBALDATA
+ fun:main
+}
+
+{
+ Ignore_install_mempool_inside
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:snd_pcm_open
+ obj:*
+ fun:install_*
+}
+
+{
+ Ignore_install_sound_pcm_open_config_load
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:snd_pcm_open
+}
+
+{
+ Ignore_install_sound_pcm_open_config_parse
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:snd_config_*
+}
+
+{
+ Ignore_snd_open_on_unknown_obj
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:snd_pcm_open
+ obj:*
+}
+
+{
+ Ignore_install_from_main_with_obj_def
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:*
+ fun:*
+ obj:*
+ fun:install_*
+ fun:_Z18init_game_settingsP10GLOBALDATA
+ fun:main
+}
+
+{
+ Ignore_install_from_main_with_obj
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ obj:*
+ fun:install_*
+ fun:_Z18init_game_settingsP10GLOBALDATA
+ fun:main
+}
+
+{
+ Ignore_install_from_init_game_setting_with_obj
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ fun:*
+ obj:*
+ fun:install_*
+ fun:_Z18init_game_settingsP10GLOBALDATA
+}
+
+{
+ Ignore_allegro_back_pool
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:malloc
+ fun:_al_malloc
+ obj:*
+ fun:install_*
+ fun:_Z18init_game_settingsP10GLOBALDATA
+ fun:main
+}
+
+{
+ Ignore_X_init_realloc
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:realloc
+ fun:add_codeset.isra.10
+ fun:load_generic
+ fun:initialize
+ fun:_XlcCreateLC
+ fun:_XlcDefaultLoader
+ fun:_XOpenLC
+ fun:_XrmInitParseInfo
+ fun:NewDatabase
+ fun:XrmGetStringDatabase
+ fun:InitDefaults
+ fun:XGetDefault
+}
diff --git a/cb/atanks.cbp b/cb/atanks.cbp
new file mode 100644
index 0000000..4d885da
--- /dev/null
+++ b/cb/atanks.cbp
@@ -0,0 +1,438 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<CodeBlocks_project_file>
+ <FileVersion major="1" minor="6" />
+ <Project>
+ <Option title="atanks" />
+ <Option makefile_is_custom="1" />
+ <Option execution_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option pch_mode="2" />
+ <Option compiler="gcc" />
+ <Build>
+ <Target title="veryclean">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-O2" />
+ </Compiler>
+ <Linker>
+ <Add option="-s" />
+ </Linker>
+ <MakeCommands>
+ <Build command="$make -f $makefile $target" />
+ <CompileFile command="$make -f $makefile $file" />
+ <Clean command="$make -f $makefile clean" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target" />
+ <SilentBuild command="$make -f $makefile $target > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="user">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile $target" />
+ <CompileFile command="$make -f $makefile $file" />
+ <Clean command="$make -f $makefile veryclean" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target" />
+ <SilentBuild command="$make -j17 -f $makefile $target > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="user LTO">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user USE_LTO=YES" />
+ <CompileFile command="$make -f $makefile $file USE_LTO=YES" />
+ <Clean command="$make -f $makefile veryclean USE_LTO=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile user USE_LTO=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user USE_LTO=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="user ggdb3">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command='CXXFLAGS="-march=native -ggdb3 -O2" $make -j17 -f $makefile user' />
+ <CompileFile command='CXXFLAGS="-march=native -ggdb3 -O2" $make -f $makefile $file' />
+ <Clean command='CXXFLAGS="-march=native -ggdb3 -O2" $make -f $makefile veryclean' />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command='CXXFLAGS="-march=native -ggdb3 -O2" $make -q -f $makefile user' />
+ <SilentBuild command='CXXFLAGS="-march=native -ggdb3 -O2" $make -j17 -f $makefile user > $(CMD_NULL)' />
+ </MakeCommands>
+ </Target>
+ <Target title="user debug">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile user DEBUG=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="AICORE">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES DEBUG_AICORE=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES DEBUG_AICORE=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES DEBUG_AICORE=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES DEBUG_AICORE=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES DEBUG_AICORE=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="AICORE to Log">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES DEBUG_AICORE=YES DEBUG_LOG_TO_FILE=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES DEBUG_AICORE=YES DEBUG_LOG_TO_FILE=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES DEBUG_AICORE=YES DEBUG_LOG_TO_FILE=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES DEBUG_AICORE=YES DEBUG_LOG_TO_FILE=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES DEBUG_AICORE=YES DEBUG_LOG_TO_FILE=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="AICORE FIN">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES DEBUG_AICORE=YES DEBUG_FINANCE=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES DEBUG_AICORE=YES DEBUG_FINANCE=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES DEBUG_AICORE=YES DEBUG_FINANCE=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES DEBUG_AICORE=YES DEBUG_FINANCE=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES DEBUG_AICORE=YES DEBUG_FINANCE=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="AICORE FIN to LOG">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES DEBUG_AICORE=YES DEBUG_FINANCE=YES DEBUG_LOG_TO_FILE=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES DEBUG_AICORE=YES DEBUG_FINANCE=YES DEBUG_LOG_TO_FILE=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES DEBUG_AICORE=YES DEBUG_FINANCE=YES DEBUG_LOG_TO_FILE=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES DEBUG_AICORE=YES DEBUG_FINANCE=YES DEBUG_LOG_TO_FILE=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES DEBUG_AICORE=YES DEBUG_FINANCE=YES DEBUG_LOG_TO_FILE=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="AIM">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES DEBUG_AIMING=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES DEBUG_AIMING=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES DEBUG_AIMING=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES DEBUG_AIMING=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES DEBUG_AIMING=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="EMO">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES DEBUG_EMOTION=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES DEBUG_EMOTION=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES DEBUG_EMOTION=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES DEBUG_EMOTION=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES DEBUG_EMOTION=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="FIN">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES DEBUG_FINANCE=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES DEBUG_FINANCE=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES DEBUG_FINANCE=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES DEBUG_FINANCE=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES DEBUG_FINANCE=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="OBJ">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -f $makefile user DEBUG=YES DEBUG_OBJECTS=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES DEBUG_OBJECTS=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES DEBUG_OBJECTS=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES DEBUG_OBJECTS=YES" />
+ <SilentBuild command="$make -f $makefile user DEBUG=YES DEBUG_OBJECTS=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="PHY">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES DEBUG_PHYSICS=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES DEBUG_PHYSICS=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES DEBUG_PHYSICS=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES DEBUG_PHYSICS=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES DEBUG_PHYSICS=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="FIN PHY">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES DEBUG_FINANCE=YES DEBUG_PHYSICS=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES DEBUG_FINANCE=YES DEBUG_PHYSICS=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES DEBUG_FINANCE=YES DEBUG_PHYSICS=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES DEBUG_FINANCE=YES DEBUG_PHYSICS=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES DEBUG_FINANCE=YES DEBUG_PHYSICS=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="SAN address">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES SANITIZE_ADDRESS=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES SANITIZE_ADDRESS=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES SANITIZE_ADDRESS=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES SANITIZE_ADDRESS=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES SANITIZE_ADDRESS=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="SAN leak">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES SANITIZE_LEAK=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES SANITIZE_LEAK=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES SANITIZE_LEAK=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES SANITIZE_LEAK=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES SANITIZE_LEAK=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ <Target title="SAN thread">
+ <Option output="/home/sed/pryde/PrydeWorX/atanks_aiu/atanks" prefix_auto="1" extension_auto="1" />
+ <Option working_dir="/home/sed/pryde/PrydeWorX/atanks_aiu" />
+ <Option object_output="../obj/" />
+ <Option type="1" />
+ <Option compiler="gcc" />
+ <Compiler>
+ <Add option="-g" />
+ </Compiler>
+ <MakeCommands>
+ <Build command="$make -j17 -f $makefile user DEBUG=YES SANITIZE_THREAD=YES" />
+ <CompileFile command="$make -f $makefile $file DEBUG=YES SANITIZE_THREAD=YES" />
+ <Clean command="$make -f $makefile veryclean DEBUG=YES SANITIZE_THREAD=YES" />
+ <DistClean command="$make -f $makefile distclean$target" />
+ <AskRebuildNeeded command="$make -q -f $makefile $target DEBUG=YES SANITIZE_THREAD=YES" />
+ <SilentBuild command="$make -j17 -f $makefile user DEBUG=YES SANITIZE_THREAD=YES > $(CMD_NULL)" />
+ </MakeCommands>
+ </Target>
+ </Build>
+ <Compiler>
+ <Add option="-Wall" />
+ </Compiler>
+ <Unit filename="../Makefile" />
+ <Unit filename="../Makefile.bsd" />
+ <Unit filename="../TODO_akut" />
+ <Unit filename="../src/aicore.cpp" />
+ <Unit filename="../src/aicore.h" />
+ <Unit filename="../src/atanks.cpp" />
+ <Unit filename="../src/beam.cpp" />
+ <Unit filename="../src/beam.h" />
+ <Unit filename="../src/button.cpp" />
+ <Unit filename="../src/button.h" />
+ <Unit filename="../src/client.cpp" />
+ <Unit filename="../src/client.h" />
+ <Unit filename="../src/clock.cpp" />
+ <Unit filename="../src/clock.h" />
+ <Unit filename="../src/debris_pool.cpp" />
+ <Unit filename="../src/debris_pool.h" />
+ <Unit filename="../src/debug.cpp" />
+ <Unit filename="../src/debug.h" />
+ <Unit filename="../src/decor.cpp" />
+ <Unit filename="../src/decor.h" />
+ <Unit filename="../src/environment.cpp" />
+ <Unit filename="../src/environment.h" />
+ <Unit filename="../src/explosion.cpp" />
+ <Unit filename="../src/explosion.h" />
+ <Unit filename="../src/externs.h" />
+ <Unit filename="../src/files.cpp" />
+ <Unit filename="../src/files.h" />
+ <Unit filename="../src/floattext.cpp" />
+ <Unit filename="../src/floattext.h" />
+ <Unit filename="../src/gameloop.cpp" />
+ <Unit filename="../src/gameloop.h" />
+ <Unit filename="../src/gfxData.cpp" />
+ <Unit filename="../src/gfxData.h" />
+ <Unit filename="../src/globaldata.cpp" />
+ <Unit filename="../src/globaldata.h" />
+ <Unit filename="../src/globals.h" />
+ <Unit filename="../src/globaltypes.cpp" />
+ <Unit filename="../src/globaltypes.h" />
+ <Unit filename="../src/land.cpp" />
+ <Unit filename="../src/land.h" />
+ <Unit filename="../src/main.cpp" />
+ <Unit filename="../src/main.h" />
+ <Unit filename="../src/menu.cpp" />
+ <Unit filename="../src/menu.h" />
+ <Unit filename="../src/missile.cpp" />
+ <Unit filename="../src/missile.h" />
+ <Unit filename="../src/network.cpp" />
+ <Unit filename="../src/network.h" />
+ <Unit filename="../src/optioncontent.h" />
+ <Unit filename="../src/optionitem.h" />
+ <Unit filename="../src/optionitembase.cpp" />
+ <Unit filename="../src/optionitembase.h" />
+ <Unit filename="../src/optionitemcolour.cpp" />
+ <Unit filename="../src/optionitemcolour.h" />
+ <Unit filename="../src/optionitemmenu.cpp" />
+ <Unit filename="../src/optionitemmenu.h" />
+ <Unit filename="../src/optionitemplayer.cpp" />
+ <Unit filename="../src/optionitemplayer.h" />
+ <Unit filename="../src/optionscreens.cpp" />
+ <Unit filename="../src/optionscreens.h" />
+ <Unit filename="../src/optiontypes.cpp" />
+ <Unit filename="../src/optiontypes.h" />
+ <Unit filename="../src/perlin.cpp" />
+ <Unit filename="../src/physobj.cpp" />
+ <Unit filename="../src/physobj.h" />
+ <Unit filename="../src/player.cpp" />
+ <Unit filename="../src/player.h" />
+ <Unit filename="../src/player_types.cpp" />
+ <Unit filename="../src/player_types.h" />
+ <Unit filename="../src/satellite.cpp" />
+ <Unit filename="../src/satellite.h" />
+ <Unit filename="../src/shop.cpp" />
+ <Unit filename="../src/shop.h" />
+ <Unit filename="../src/sky.cpp" />
+ <Unit filename="../src/sky.h" />
+ <Unit filename="../src/sound.cpp" />
+ <Unit filename="../src/sound.h" />
+ <Unit filename="../src/tank.cpp" />
+ <Unit filename="../src/tank.h" />
+ <Unit filename="../src/teleport.cpp" />
+ <Unit filename="../src/teleport.h" />
+ <Unit filename="../src/text.cpp" />
+ <Unit filename="../src/text.h" />
+ <Unit filename="../src/update.cpp" />
+ <Unit filename="../src/update.h" />
+ <Unit filename="../src/virtobj.cpp" />
+ <Unit filename="../src/virtobj.h" />
+ <Unit filename="../src/winclock.h" />
+ <Unit filename="../text/weapons.txt" />
+ <Extensions>
+ <envvars />
+ <code_completion />
+ <debugger />
+ <lib_finder disable_auto="1" />
+ </Extensions>
+ </Project>
+</CodeBlocks_project_file>
diff --git a/cb/atanks.workspace b/cb/atanks.workspace
new file mode 100644
index 0000000..89c6a72
--- /dev/null
+++ b/cb/atanks.workspace
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<CodeBlocks_workspace_file>
+ <Workspace title="Atomic Tanks">
+ <Project filename="atanks.cbp" />
+ </Workspace>
+</CodeBlocks_workspace_file>
diff --git a/credits.txt b/credits.txt
index 62e5dd0..3ed7804 100644
--- a/credits.txt
+++ b/credits.txt
@@ -7,6 +7,8 @@ Jesse Smith (jessefrgsmith at yahoo.ca)
Mike Wilson (CaptainNeeda+atanks at gmail.com)
Kevin (sylikc at gmail.com)
Sven Eden (yamakuzure at users.sourceforge.net)
+Bruno Victal
+Bill Buerger
Graphics
Tom Hudson (tom at singular.org.uk)
diff --git a/dep/.keep_dir b/dep/.keep_dir
new file mode 100644
index 0000000..e69de29
diff --git a/dep/aicore.d b/dep/aicore.d
new file mode 100644
index 0000000..1acdde0
--- /dev/null
+++ b/dep/aicore.d
@@ -0,0 +1,5 @@
+obj/aicore.o dep/aicore.d : src/aicore.cpp src/aicore.h src/player_types.h src/main.h \
+ src/debug.h src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/environment.h src/network.h src/gfxData.h src/floattext.h \
+ src/virtobj.h src/player.h src/tank.h src/physobj.h src/missile.h \
+ src/beam.h src/explosion.h
diff --git a/dep/atanks.d b/dep/atanks.d
new file mode 100644
index 0000000..6b243dd
--- /dev/null
+++ b/dep/atanks.d
@@ -0,0 +1,8 @@
+obj/atanks.o dep/atanks.d : src/atanks.cpp src/debug.h src/globals.h src/globaldata.h \
+ src/main.h src/globaltypes.h src/externs.h src/environment.h \
+ src/network.h src/gfxData.h src/text.h src/optionscreens.h src/menu.h \
+ src/optionitem.h src/optionitembase.h src/optiontypes.h \
+ src/optionitemmenu.h src/optionitemplayer.h src/button.h src/player.h \
+ src/player_types.h src/files.h src/update.h src/tank.h src/physobj.h \
+ src/virtobj.h src/floattext.h src/beam.h src/missile.h src/gameloop.h \
+ src/clock.h src/client.h
diff --git a/dep/beam.d b/dep/beam.d
new file mode 100644
index 0000000..c087c34
--- /dev/null
+++ b/dep/beam.d
@@ -0,0 +1,5 @@
+obj/beam.o dep/beam.d : src/beam.cpp src/environment.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/network.h src/gfxData.h src/physobj.h src/virtobj.h src/player.h \
+ src/player_types.h src/decor.h src/debris_pool.h src/tank.h \
+ src/floattext.h src/beam.h src/explosion.h src/sound.h
diff --git a/dep/button.d b/dep/button.d
new file mode 100644
index 0000000..145cd74
--- /dev/null
+++ b/dep/button.d
@@ -0,0 +1,3 @@
+obj/button.o dep/button.d : src/button.cpp src/button.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/environment.h src/network.h src/gfxData.h src/sound.h
diff --git a/dep/client.d b/dep/client.d
new file mode 100644
index 0000000..3f6222c
--- /dev/null
+++ b/dep/client.d
@@ -0,0 +1,6 @@
+obj/client.o dep/client.d : src/client.cpp src/button.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/environment.h src/network.h src/gfxData.h src/files.h \
+ src/satellite.h src/update.h src/client.h src/beam.h src/physobj.h \
+ src/virtobj.h src/explosion.h src/missile.h src/teleport.h \
+ src/floattext.h src/player.h src/player_types.h src/tank.h src/sky.h
diff --git a/dep/clock.d b/dep/clock.d
new file mode 100644
index 0000000..39b0138
--- /dev/null
+++ b/dep/clock.d
@@ -0,0 +1 @@
+obj/clock.o dep/clock.d : src/clock.cpp src/clock.h src/debug.h
diff --git a/dep/debris_pool.d b/dep/debris_pool.d
new file mode 100644
index 0000000..7f81bb4
--- /dev/null
+++ b/dep/debris_pool.d
@@ -0,0 +1,3 @@
+obj/debris_pool.o dep/debris_pool.d : src/debris_pool.cpp src/debris_pool.h src/main.h \
+ src/debug.h src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/environment.h src/network.h src/gfxData.h
diff --git a/dep/debug.d b/dep/debug.d
new file mode 100644
index 0000000..4ec2261
--- /dev/null
+++ b/dep/debug.d
@@ -0,0 +1 @@
+obj/debug.o dep/debug.d : src/debug.cpp src/debug.h
diff --git a/dep/decor.d b/dep/decor.d
new file mode 100644
index 0000000..5e1846e
--- /dev/null
+++ b/dep/decor.d
@@ -0,0 +1,4 @@
+obj/decor.o dep/decor.d : src/decor.cpp src/decor.h src/physobj.h src/globaltypes.h \
+ src/virtobj.h src/main.h src/debug.h src/externs.h src/globaldata.h \
+ src/text.h src/environment.h src/network.h src/gfxData.h \
+ src/debris_pool.h src/sound.h src/tank.h src/floattext.h
diff --git a/dep/environment.d b/dep/environment.d
new file mode 100644
index 0000000..54ce493
--- /dev/null
+++ b/dep/environment.d
@@ -0,0 +1,5 @@
+obj/environment.o dep/environment.d : src/environment.cpp src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/environment.h src/network.h src/gfxData.h src/missile.h \
+ src/physobj.h src/virtobj.h src/tank.h src/floattext.h src/files.h \
+ src/sound.h src/player.h src/player_types.h
diff --git a/dep/explosion.d b/dep/explosion.d
new file mode 100644
index 0000000..9f62343
--- /dev/null
+++ b/dep/explosion.d
@@ -0,0 +1,5 @@
+obj/explosion.o dep/explosion.d : src/explosion.cpp src/main.h src/debug.h src/globaltypes.h \
+ src/externs.h src/globaldata.h src/text.h src/environment.h \
+ src/network.h src/gfxData.h src/explosion.h src/physobj.h src/virtobj.h \
+ src/missile.h src/decor.h src/debris_pool.h src/tank.h src/floattext.h \
+ src/player.h src/player_types.h
diff --git a/dep/files.d b/dep/files.d
new file mode 100644
index 0000000..b488329
--- /dev/null
+++ b/dep/files.d
@@ -0,0 +1,3 @@
+obj/files.o dep/files.d : src/files.cpp src/main.h src/debug.h src/globaltypes.h \
+ src/externs.h src/globaldata.h src/text.h src/environment.h \
+ src/network.h src/gfxData.h src/player.h src/player_types.h src/files.h
diff --git a/dep/floattext.d b/dep/floattext.d
new file mode 100644
index 0000000..4df50a5
--- /dev/null
+++ b/dep/floattext.d
@@ -0,0 +1,3 @@
+obj/floattext.o dep/floattext.d : src/floattext.cpp src/floattext.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/environment.h src/network.h src/gfxData.h src/virtobj.h
diff --git a/dep/gameloop.d b/dep/gameloop.d
new file mode 100644
index 0000000..86365c8
--- /dev/null
+++ b/dep/gameloop.d
@@ -0,0 +1,7 @@
+obj/gameloop.o dep/gameloop.d : src/gameloop.cpp src/main.h src/debug.h src/globaltypes.h \
+ src/externs.h src/globaldata.h src/text.h src/environment.h \
+ src/network.h src/gfxData.h src/files.h src/satellite.h src/update.h \
+ src/land.h src/clock.h src/floattext.h src/virtobj.h src/tank.h \
+ src/physobj.h src/explosion.h src/beam.h src/missile.h src/decor.h \
+ src/debris_pool.h src/teleport.h src/sky.h src/sound.h src/gameloop.h \
+ src/player.h src/player_types.h src/aicore.h src/shop.h
diff --git a/dep/gfxData.d b/dep/gfxData.d
new file mode 100644
index 0000000..827498c
--- /dev/null
+++ b/dep/gfxData.d
@@ -0,0 +1,3 @@
+obj/gfxData.o dep/gfxData.d : src/gfxData.cpp src/main.h src/debug.h src/globaltypes.h \
+ src/externs.h src/globaldata.h src/text.h src/environment.h \
+ src/network.h src/gfxData.h
diff --git a/dep/globaldata.d b/dep/globaldata.d
new file mode 100644
index 0000000..36e6b37
--- /dev/null
+++ b/dep/globaldata.d
@@ -0,0 +1,5 @@
+obj/globaldata.o dep/globaldata.d : src/globaldata.cpp src/player.h src/player_types.h \
+ src/main.h src/debug.h src/globaltypes.h src/externs.h src/globaldata.h \
+ src/text.h src/environment.h src/network.h src/gfxData.h src/files.h \
+ src/tank.h src/physobj.h src/virtobj.h src/floattext.h src/sound.h \
+ src/debris_pool.h
diff --git a/dep/globaltypes.d b/dep/globaltypes.d
new file mode 100644
index 0000000..5548261
--- /dev/null
+++ b/dep/globaltypes.d
@@ -0,0 +1 @@
+obj/globaltypes.o dep/globaltypes.d : src/globaltypes.cpp src/globaltypes.h
diff --git a/dep/land.d b/dep/land.d
new file mode 100644
index 0000000..46a24b0
--- /dev/null
+++ b/dep/land.d
@@ -0,0 +1,4 @@
+obj/land.o dep/land.d : src/land.cpp src/land.h src/main.h src/debug.h src/globaltypes.h \
+ src/externs.h src/globaldata.h src/text.h src/environment.h \
+ src/network.h src/gfxData.h src/files.h src/gameloop.h src/player.h \
+ src/player_types.h
diff --git a/dep/main.d b/dep/main.d
new file mode 100644
index 0000000..e2fa4fe
--- /dev/null
+++ b/dep/main.d
@@ -0,0 +1,3 @@
+obj/main.o dep/main.d : src/main.cpp src/main.h src/debug.h src/globaltypes.h \
+ src/externs.h src/globaldata.h src/text.h src/environment.h \
+ src/network.h src/gfxData.h
diff --git a/dep/menu.d b/dep/menu.d
new file mode 100644
index 0000000..300f35d
--- /dev/null
+++ b/dep/menu.d
@@ -0,0 +1,6 @@
+obj/menu.o dep/menu.d : src/menu.cpp src/optioncontent.h src/optiontypes.h \
+ src/globaltypes.h src/optionitemcolour.h src/optionitembase.h \
+ src/environment.h src/main.h src/debug.h src/externs.h src/globaldata.h \
+ src/text.h src/network.h src/gfxData.h src/button.h src/menu.h \
+ src/optionitem.h src/optionitemmenu.h src/optionitemplayer.h \
+ src/player.h src/player_types.h src/clock.h
diff --git a/dep/missile.d b/dep/missile.d
new file mode 100644
index 0000000..d7dac69
--- /dev/null
+++ b/dep/missile.d
@@ -0,0 +1,6 @@
+obj/missile.o dep/missile.d : src/missile.cpp src/explosion.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/environment.h src/network.h src/gfxData.h src/physobj.h \
+ src/virtobj.h src/missile.h src/decor.h src/debris_pool.h src/tank.h \
+ src/floattext.h src/player.h src/player_types.h src/beam.h src/sound.h \
+ src/aicore.h
diff --git a/dep/network.d b/dep/network.d
new file mode 100644
index 0000000..ad310b6
--- /dev/null
+++ b/dep/network.d
@@ -0,0 +1,3 @@
+obj/network.o dep/network.d : src/network.cpp src/network.h src/player.h src/player_types.h \
+ src/main.h src/debug.h src/globaltypes.h src/externs.h src/globaldata.h \
+ src/text.h src/environment.h src/gfxData.h
diff --git a/dep/optionitembase.d b/dep/optionitembase.d
new file mode 100644
index 0000000..c533418
--- /dev/null
+++ b/dep/optionitembase.d
@@ -0,0 +1,6 @@
+obj/optionitembase.o dep/optionitembase.d : src/optionitembase.cpp src/button.h src/main.h \
+ src/debug.h src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/environment.h src/network.h src/gfxData.h src/menu.h \
+ src/optionitem.h src/optionitembase.h src/optiontypes.h \
+ src/optionitemmenu.h src/optionitemplayer.h src/floattext.h \
+ src/virtobj.h
diff --git a/dep/optionitemcolour.d b/dep/optionitemcolour.d
new file mode 100644
index 0000000..e43bd25
--- /dev/null
+++ b/dep/optionitemcolour.d
@@ -0,0 +1,4 @@
+obj/optionitemcolour.o dep/optionitemcolour.d : src/optionitemcolour.cpp src/optionitemcolour.h \
+ src/optionitembase.h src/environment.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/network.h src/gfxData.h src/optiontypes.h
diff --git a/dep/optionitemmenu.d b/dep/optionitemmenu.d
new file mode 100644
index 0000000..d11bd0a
--- /dev/null
+++ b/dep/optionitemmenu.d
@@ -0,0 +1,5 @@
+obj/optionitemmenu.o dep/optionitemmenu.d : src/optionitemmenu.cpp src/optionitemmenu.h \
+ src/optionitembase.h src/environment.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/network.h src/gfxData.h src/optiontypes.h src/menu.h \
+ src/optionitem.h src/optionitemplayer.h src/button.h src/clock.h
diff --git a/dep/optionitemplayer.d b/dep/optionitemplayer.d
new file mode 100644
index 0000000..be5458b
--- /dev/null
+++ b/dep/optionitemplayer.d
@@ -0,0 +1,5 @@
+obj/optionitemplayer.o dep/optionitemplayer.d : src/optionitemplayer.cpp src/optionitemplayer.h \
+ src/optionitembase.h src/environment.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/network.h src/gfxData.h src/optiontypes.h src/player.h \
+ src/player_types.h src/floattext.h src/virtobj.h
diff --git a/dep/optionscreens.d b/dep/optionscreens.d
new file mode 100644
index 0000000..fcfc44a
--- /dev/null
+++ b/dep/optionscreens.d
@@ -0,0 +1,6 @@
+obj/optionscreens.o dep/optionscreens.d : src/optionscreens.cpp src/optionscreens.h src/menu.h \
+ src/optionitem.h src/optionitembase.h src/environment.h src/main.h \
+ src/debug.h src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/network.h src/gfxData.h src/optiontypes.h src/optionitemmenu.h \
+ src/optionitemplayer.h src/button.h src/player.h src/player_types.h \
+ src/files.h src/sound.h
diff --git a/dep/optiontypes.d b/dep/optiontypes.d
new file mode 100644
index 0000000..b6d2618
--- /dev/null
+++ b/dep/optiontypes.d
@@ -0,0 +1 @@
+obj/optiontypes.o dep/optiontypes.d : src/optiontypes.cpp src/optiontypes.h
diff --git a/dep/perlin.d b/dep/perlin.d
new file mode 100644
index 0000000..44d39b2
--- /dev/null
+++ b/dep/perlin.d
@@ -0,0 +1,3 @@
+obj/perlin.o dep/perlin.d : src/perlin.cpp src/main.h src/debug.h src/globaltypes.h \
+ src/externs.h src/globaldata.h src/text.h src/environment.h \
+ src/network.h src/gfxData.h
diff --git a/dep/physobj.d b/dep/physobj.d
new file mode 100644
index 0000000..7c9e336
--- /dev/null
+++ b/dep/physobj.d
@@ -0,0 +1,3 @@
+obj/physobj.o dep/physobj.d : src/physobj.cpp src/physobj.h src/globaltypes.h src/virtobj.h \
+ src/main.h src/debug.h src/externs.h src/globaldata.h src/text.h \
+ src/environment.h src/network.h src/gfxData.h
diff --git a/dep/player.d b/dep/player.d
new file mode 100644
index 0000000..381fe45
--- /dev/null
+++ b/dep/player.d
@@ -0,0 +1,7 @@
+obj/player.o dep/player.d : src/player.cpp src/environment.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/network.h src/gfxData.h src/player.h src/player_types.h src/tank.h \
+ src/physobj.h src/virtobj.h src/floattext.h src/menu.h src/optionitem.h \
+ src/optionitembase.h src/optiontypes.h src/optionitemmenu.h \
+ src/optionitemplayer.h src/button.h src/files.h src/missile.h \
+ src/aicore.h
diff --git a/dep/player_types.d b/dep/player_types.d
new file mode 100644
index 0000000..f23b730
--- /dev/null
+++ b/dep/player_types.d
@@ -0,0 +1,3 @@
+obj/player_types.o dep/player_types.d : src/player_types.cpp src/player_types.h src/main.h \
+ src/debug.h src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/environment.h src/network.h src/gfxData.h
diff --git a/dep/satellite.d b/dep/satellite.d
new file mode 100644
index 0000000..f485bd2
--- /dev/null
+++ b/dep/satellite.d
@@ -0,0 +1,4 @@
+obj/satellite.o dep/satellite.d : src/satellite.cpp src/environment.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/network.h src/gfxData.h src/satellite.h src/beam.h src/physobj.h \
+ src/virtobj.h
diff --git a/dep/shop.d b/dep/shop.d
new file mode 100644
index 0000000..b84a1bf
--- /dev/null
+++ b/dep/shop.d
@@ -0,0 +1,4 @@
+obj/shop.o dep/shop.d : src/shop.cpp src/shop.h src/player.h src/player_types.h \
+ src/main.h src/debug.h src/globaltypes.h src/externs.h src/globaldata.h \
+ src/text.h src/environment.h src/network.h src/gfxData.h src/files.h \
+ src/gameloop.h
diff --git a/dep/sky.d b/dep/sky.d
new file mode 100644
index 0000000..7385e85
--- /dev/null
+++ b/dep/sky.d
@@ -0,0 +1,4 @@
+obj/sky.o dep/sky.d : src/sky.cpp src/externs.h src/globaldata.h src/main.h src/debug.h \
+ src/globaltypes.h src/text.h src/environment.h src/network.h \
+ src/gfxData.h src/sky.h src/files.h src/gameloop.h src/player.h \
+ src/player_types.h
diff --git a/dep/sound.d b/dep/sound.d
new file mode 100644
index 0000000..9b54b70
--- /dev/null
+++ b/dep/sound.d
@@ -0,0 +1,3 @@
+obj/sound.o dep/sound.d : src/sound.cpp src/sound.h src/externs.h src/globaldata.h \
+ src/main.h src/debug.h src/globaltypes.h src/text.h src/environment.h \
+ src/network.h src/gfxData.h
diff --git a/dep/tank.d b/dep/tank.d
new file mode 100644
index 0000000..6209f26
--- /dev/null
+++ b/dep/tank.d
@@ -0,0 +1,5 @@
+obj/tank.o dep/tank.d : src/tank.cpp src/floattext.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/environment.h src/network.h src/gfxData.h src/virtobj.h \
+ src/explosion.h src/physobj.h src/teleport.h src/missile.h src/player.h \
+ src/player_types.h src/beam.h src/tank.h src/sound.h
diff --git a/dep/teleport.d b/dep/teleport.d
new file mode 100644
index 0000000..d2ce83e
--- /dev/null
+++ b/dep/teleport.d
@@ -0,0 +1,5 @@
+obj/teleport.o dep/teleport.d : src/teleport.cpp src/environment.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/network.h src/gfxData.h src/teleport.h src/virtobj.h src/tank.h \
+ src/physobj.h src/floattext.h src/sound.h src/player.h \
+ src/player_types.h
diff --git a/dep/text.d b/dep/text.d
new file mode 100644
index 0000000..8aa6bcf
--- /dev/null
+++ b/dep/text.d
@@ -0,0 +1,3 @@
+obj/text.o dep/text.d : src/text.cpp src/text.h src/main.h src/debug.h src/globaltypes.h \
+ src/externs.h src/globaldata.h src/environment.h src/network.h \
+ src/gfxData.h
diff --git a/dep/update.d b/dep/update.d
new file mode 100644
index 0000000..6cddf67
--- /dev/null
+++ b/dep/update.d
@@ -0,0 +1,3 @@
+obj/update.o dep/update.d : src/update.cpp src/debug.h src/update.h src/network.h \
+ src/externs.h src/globaldata.h src/main.h src/globaltypes.h src/text.h \
+ src/environment.h src/gfxData.h
diff --git a/dep/virtobj.d b/dep/virtobj.d
new file mode 100644
index 0000000..d37876a
--- /dev/null
+++ b/dep/virtobj.d
@@ -0,0 +1,3 @@
+obj/virtobj.o dep/virtobj.d : src/virtobj.cpp src/virtobj.h src/main.h src/debug.h \
+ src/globaltypes.h src/externs.h src/globaldata.h src/text.h \
+ src/environment.h src/network.h src/gfxData.h
diff --git a/do_helgrind.sh b/do_helgrind.sh
new file mode 100755
index 0000000..ff8744f
--- /dev/null
+++ b/do_helgrind.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+/usr/bin/valgrind -v --trace-children=yes --tool=helgrind --read-var-info=yes ./atanks
+
+
diff --git a/do_memcheck.sh b/do_memcheck.sh
new file mode 100755
index 0000000..e8f5efb
--- /dev/null
+++ b/do_memcheck.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+/usr/bin/valgrind -v --trace-children=yes --tool=memcheck \
+--track-origins=yes --leak-check=full --show-reachable=no \
+--read-var-info=yes ./atanks
+
+
diff --git a/gdb_memcheck.sh b/gdb_memcheck.sh
new file mode 100755
index 0000000..040b743
--- /dev/null
+++ b/gdb_memcheck.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+/usr/bin/valgrind -v --trace-children=yes --tool=memcheck \
+--track-origins=yes --leak-check=full --show-reachable=no \
+--read-var-info=yes --vgdb=full --vgdb-error=0 ./atanks
+
+
diff --git a/obj/.keep_dir b/obj/.keep_dir
new file mode 100644
index 0000000..e69de29
diff --git a/sound/0.wav b/sound/0.wav
deleted file mode 100644
index be6007b..0000000
Binary files a/sound/0.wav and /dev/null differ
diff --git a/sound/00.wav b/sound/00.wav
new file mode 100644
index 0000000..993d72b
Binary files /dev/null and b/sound/00.wav differ
diff --git a/sound/01.wav b/sound/01.wav
new file mode 100644
index 0000000..4cf2c00
Binary files /dev/null and b/sound/01.wav differ
diff --git a/sound/02.wav b/sound/02.wav
new file mode 100644
index 0000000..199f802
Binary files /dev/null and b/sound/02.wav differ
diff --git a/sound/03.wav b/sound/03.wav
new file mode 100644
index 0000000..2429644
Binary files /dev/null and b/sound/03.wav differ
diff --git a/sound/04.wav b/sound/04.wav
new file mode 100644
index 0000000..efefa7d
Binary files /dev/null and b/sound/04.wav differ
diff --git a/sound/05.wav b/sound/05.wav
new file mode 100644
index 0000000..662ec1b
Binary files /dev/null and b/sound/05.wav differ
diff --git a/sound/06.wav b/sound/06.wav
new file mode 100644
index 0000000..2cd3f5e
Binary files /dev/null and b/sound/06.wav differ
diff --git a/sound/07.wav b/sound/07.wav
new file mode 100644
index 0000000..6bc1253
Binary files /dev/null and b/sound/07.wav differ
diff --git a/sound/1.wav b/sound/1.wav
deleted file mode 100644
index 0ed9d07..0000000
Binary files a/sound/1.wav and /dev/null differ
diff --git a/sound/10.wav b/sound/10.wav
index 9ae290c..da5d044 100644
Binary files a/sound/10.wav and b/sound/10.wav differ
diff --git a/sound/11.wav b/sound/11.wav
index c2ee04d..5528fac 100644
Binary files a/sound/11.wav and b/sound/11.wav differ
diff --git a/sound/12.wav b/sound/12.wav
index a52bc4e..4ab1dd7 100644
Binary files a/sound/12.wav and b/sound/12.wav differ
diff --git a/sound/13.wav b/sound/13.wav
new file mode 100644
index 0000000..479c4fb
Binary files /dev/null and b/sound/13.wav differ
diff --git a/sound/14.wav b/sound/14.wav
new file mode 100644
index 0000000..79f38bb
Binary files /dev/null and b/sound/14.wav differ
diff --git a/sound/15.wav b/sound/15.wav
new file mode 100644
index 0000000..0b45dcb
Binary files /dev/null and b/sound/15.wav differ
diff --git a/sound/16.wav b/sound/16.wav
new file mode 100644
index 0000000..cd5fd08
Binary files /dev/null and b/sound/16.wav differ
diff --git a/sound/17.wav b/sound/17.wav
new file mode 100644
index 0000000..254bea2
Binary files /dev/null and b/sound/17.wav differ
diff --git a/sound/18.wav b/sound/18.wav
new file mode 100644
index 0000000..50f81f3
Binary files /dev/null and b/sound/18.wav differ
diff --git a/sound/19.wav b/sound/19.wav
new file mode 100644
index 0000000..ff5d133
Binary files /dev/null and b/sound/19.wav differ
diff --git a/sound/2.wav b/sound/2.wav
deleted file mode 100644
index 86200ee..0000000
Binary files a/sound/2.wav and /dev/null differ
diff --git a/sound/20.wav b/sound/20.wav
new file mode 100644
index 0000000..41e20e6
Binary files /dev/null and b/sound/20.wav differ
diff --git a/sound/21.wav b/sound/21.wav
new file mode 100644
index 0000000..f3b1cea
Binary files /dev/null and b/sound/21.wav differ
diff --git a/sound/22.wav b/sound/22.wav
new file mode 100644
index 0000000..7ad0c48
Binary files /dev/null and b/sound/22.wav differ
diff --git a/sound/3.wav b/sound/3.wav
deleted file mode 100644
index fab1992..0000000
Binary files a/sound/3.wav and /dev/null differ
diff --git a/sound/30.wav b/sound/30.wav
new file mode 100644
index 0000000..7569fac
Binary files /dev/null and b/sound/30.wav differ
diff --git a/sound/31.wav b/sound/31.wav
new file mode 100644
index 0000000..45faf55
Binary files /dev/null and b/sound/31.wav differ
diff --git a/sound/10.wav b/sound/32.wav
similarity index 100%
copy from sound/10.wav
copy to sound/32.wav
diff --git a/sound/4.wav b/sound/4.wav
deleted file mode 100644
index 3f74387..0000000
Binary files a/sound/4.wav and /dev/null differ
diff --git a/sound/8.wav b/sound/40.wav
similarity index 100%
rename from sound/8.wav
rename to sound/40.wav
diff --git a/sound/5.wav b/sound/5.wav
deleted file mode 100644
index b66b9e4..0000000
Binary files a/sound/5.wav and /dev/null differ
diff --git a/sound/6.wav b/sound/6.wav
deleted file mode 100644
index 6038f20..0000000
Binary files a/sound/6.wav and /dev/null differ
diff --git a/sound/7.wav b/sound/7.wav
deleted file mode 100644
index e3f4e63..0000000
Binary files a/sound/7.wav and /dev/null differ
diff --git a/sound/9.wav b/sound/9.wav
deleted file mode 100644
index 845db24..0000000
Binary files a/sound/9.wav and /dev/null differ
diff --git a/src/Makefile b/src/Makefile
deleted file mode 100644
index e2aa5a1..0000000
--- a/src/Makefile
+++ /dev/null
@@ -1,149 +0,0 @@
-.PHONY: all clean veryclean
-
-MODULES = atanks.o beam.o button.o environment.o explosion.o fade.o files.o globaldata.o \
- missile.o perlin.o physobj.o player.o satellite.o sky.o tank.o team.o teleport.o virtobj.o \
- update.o network.o floattext.o land.o text.o client.o gameloop.o
-
-CXX?=clang++
-LIB=ar
-WINDRES=
-# FLAGS += -DDATA_DIR=\".\" -Wno-write-strings -DNETWORK -DTHREADS
-FLAGS += -DDATA_DIR=\"${INSTALLDIR}\" -DNEW_GAMELOOP -Wno-write-strings -DNETWORK -DTHREADS -pthread -Wextra -pedantic -ggdb3
-OUTPUT = ../atanks
-WFLAGS =
-OFLAGS =
-LFLAGS +=
-LDFLAGS += `allegro-config --libs` -lm -lpthread
-
-CXXFLAGS += -Wall -Iinclude # -fprofile-arcs -ftest-coverage
-
-SRCS = $(MODULES:.o=.cpp)
-GLOBALS = main.h imagedefs.h externs.h
-
-all: $(OUTPUT)
-
-clean:
- rm -f *.o
-
-veryclean: clean
- rm $(OUTPUT)
-
-$(MODULES): Makefile
-
-atanks.o: atanks.cpp globals.h main.h menucontent.h
- $(CXX) -c atanks.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-button.o: button.cpp button.h
- $(CXX) -c button.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-client.o: client.h client.cpp
- $(CXX) -c client.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-environment.o: environment.cpp environment.h
- $(CXX) -c environment.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-explosion.o: explosion.cpp explosion.h
- $(CXX) -c explosion.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-files.o: files.cpp files.h text.h text.cpp
- $(CXX) -c files.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-floattext.o: floattext.cpp floattext.h
- $(CXX) -c floattext.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-gameloop.o: gameloop.cpp atanks.cpp main.h
- $(CXX) -c gameloop.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-globaldata.o: globaldata.cpp globaldata.h
- $(CXX) -c globaldata.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-land.o: land.cpp land.h globaldata.h environment.h
- $(CXX) -c land.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-missile.o: missile.cpp missile.h
- $(CXX) -c missile.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-teleport.o: teleport.cpp teleport.h
- $(CXX) -c teleport.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-physobj.o: physobj.cpp physobj.h
- $(CXX) -c physobj.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-player.o: player.cpp player.h
- $(CXX) -c player.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-tank.o: tank.cpp tank.h
- $(CXX) -c tank.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-team.o: team.cpp team.h
- $(CXX) -c team.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-virtobj.o: virtobj.cpp virtobj.h
- $(CXX) -c virtobj.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-fade.o: fade.cpp
- $(CXX) -c fade.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-perlin.o: perlin.cpp
- $(CXX) -c perlin.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-sky.o: sky.cpp sky.h
- $(CXX) -c sky.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-satellite.o: satellite.cpp satellite.h
- $(CXX) -c satellite.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-text.o: text.cpp text.h
- $(CXX) -c text.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-update.o: update.cpp update.h
- $(CXX) -c update.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-network.o: network.cpp network.h
- $(CXX) -c network.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-%.o: %.cpp %.h
- $(CXX) -c $< -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CXXFLAGS)
-
-$(OUTPUT): $(OBJECTS) $(MODULES)
- $(CXX) $(MODULES) -o $(OUTPUT) $(FLAGS) $(LFLAGS) $(LDFLAGS) $(SFLAGS) $(CXXFLAGS)
- # strip $(OUTPUT)
-
-# dependencies:
-physobj.h: main.h virtobj.h globaldata.h
-virtobj.h: main.h player.h
-main.h: imagedefs.h externs.h
-globaldata.h: main.h
-player.h: main.h menu.h
-tank.h: physobj.h
-floattext.h: virtobj.h main.h environment.h
-menu.h: globaldata.h
-environment.h: main.h tank.h
-files.h: globaldata.h environment.h
-globals.h: virtobj.h floattext.h physobj.h tank.h missile.h explosion.h player.h environment.h globaldata.h teleport.h decor.h beam.h
-button.h: globaldata.h environment.h
-team.h: globaldata.h
-satellite.h: environment.h globaldata.h virtobj.h
-beam.h: main.h virtobj.h physobj.h
-missile.h: main.h physobj.h
-teleport.h: main.h virtobj.h
-decor.h: main.h physobj.h environment.h globaldata.h
-explosion.h: main.h physobj.h
-virtobj.cpp: virtobj.h environment.h
-physobj.cpp: physobj.h environment.h
-atanks.cpp: globals.h menu.h button.h team.h files.h satellite.h menucontent.h
-beam.cpp: environment.h globaldata.h physobj.h player.h decor.h tank.h beam.h
-button.cpp: button.h
-environment.cpp: environment.h globaldata.h virtobj.h missile.h tank.h files.h
-explosion.cpp: environment.h globaldata.h explosion.h missile.h decor.h tank.h player.h
-fade.cpp: globaldata.h main.h
-files.cpp: player.h files.h main.h
-globaldata.cpp: player.h globaldata.h files.h
-missile.cpp: environment.h globaldata.h explosion.h missile.h decor.h tank.h
-perlin.cpp: main.h
-player.cpp: environment.h globaldata.h player.h tank.h menu.h files.h floattext.h
-satellite.cpp: environment.h satellite.h beam.h
-sky.cpp: globaldata.h main.h sky.h
-tank.cpp: environment.h globaldata.h floattext.h explosion.h teleport.h missile.h player.h beam.h tank.h
-team.cpp: tank.h team.h player.h
-teleport.cpp: environment.h globaldata.h teleport.h
diff --git a/src/Makefile.bsd b/src/Makefile.bsd
deleted file mode 100644
index e07dc3c..0000000
--- a/src/Makefile.bsd
+++ /dev/null
@@ -1,151 +0,0 @@
-.PHONY: all clean veryclean
-
-MODULES = atanks.o beam.o button.o environment.o explosion.o fade.o files.o globaldata.o \
- missile.o perlin.o physobj.o player.o satellite.o sky.o tank.o team.o teleport.o virtobj.o \
- update.o network.o floattext.o land.o text.o client.o gameloop.o
-
-CPP=clang++
-CC=clang
-LD=clang++
-LIB=ar
-WINDRES=
-# FLAGS += -DDATA_DIR=\".\" -Wno-write-strings -DNETWORK -DTHREADS
-FLAGS += -DDATA_DIR=\"${INSTALLDIR}\" -DNEW_GAMELOOP -Wno-write-strings -DTHREADS
-OUTPUT = ../atanks
-WFLAGS =
-OFLAGS =
-LFLAGS +=
-LDFLAGS = `allegro-config --libs`
-
-CFLAGS += -g -Wall -Iinclude -I/usr/local/include
-
-SRCS = $(MODULES:.o=.cpp)
-GLOBALS = main.h imagedefs.h externs.h
-
-all: $(OUTPUT)
-
-clean:
- rm -f *.o
-
-veryclean: clean
- rm $(OUTPUT)
-
-$(MODULES): Makefile
-
-atanks.o: atanks.cpp globals.h main.h menucontent.h
- $(CPP) -c atanks.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-button.o: button.cpp button.h
- $(CPP) -c button.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-client.o: client.h client.cpp
- $(CPP) -c client.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-environment.o: environment.cpp environment.h
- $(CPP) -c environment.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-explosion.o: explosion.cpp explosion.h
- $(CPP) -c explosion.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-files.o: files.cpp files.h text.h text.cpp
- $(CPP) -c files.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-floattext.o: floattext.cpp floattext.h
- $(CPP) -c floattext.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-gameloop.o: gameloop.cpp atanks.cpp main.h
- $(CPP) -c gameloop.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-globaldata.o: globaldata.cpp globaldata.h
- $(CPP) -c globaldata.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-land.o: land.cpp land.h globaldata.h environment.h
- $(CPP) -c land.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-missile.o: missile.cpp missile.h
- $(CPP) -c missile.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-teleport.o: teleport.cpp teleport.h
- $(CPP) -c teleport.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-physobj.o: physobj.cpp physobj.h
- $(CPP) -c physobj.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-player.o: player.cpp player.h
- $(CPP) -c player.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-tank.o: tank.cpp tank.h
- $(CPP) -c tank.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-team.o: team.cpp team.h
- $(CPP) -c team.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-virtobj.o: virtobj.cpp virtobj.h
- $(CPP) -c virtobj.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-fade.o: fade.cpp
- $(CPP) -c fade.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-perlin.o: perlin.cpp
- $(CPP) -c perlin.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-sky.o: sky.cpp sky.h
- $(CPP) -c sky.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-satellite.o: satellite.cpp satellite.h
- $(CPP) -c satellite.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-text.o: text.cpp text.h
- $(CPP) -c text.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-update.o: update.cpp update.h
- $(CPP) -c update.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-network.o: network.cpp network.h
- $(CPP) -c network.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-%.o: %.cpp %.h
- $(CPP) -c $< -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-$(OUTPUT): $(OBJECTS) $(MODULES)
- $(CPP) $(MODULES) -o $(OUTPUT) $(FLAGS) $(LFLAGS) $(LDFLAGS) $(SFLAGS) $(CFLAGS)
- # strip $(OUTPUT)
-
-# dependencies:
-physobj.h: main.h virtobj.h globaldata.h
-virtobj.h: main.h player.h
-main.h: imagedefs.h externs.h
-globaldata.h: main.h
-player.h: main.h menu.h
-tank.h: physobj.h
-floattext.h: virtobj.h main.h environment.h
-menu.h: globaldata.h
-environment.h: main.h tank.h
-files.h: globaldata.h environment.h
-globals.h: virtobj.h floattext.h physobj.h tank.h missile.h explosion.h player.h environment.h globaldata.h teleport.h decor.h beam.h
-button.h: globaldata.h environment.h
-team.h: globaldata.h
-satellite.h: environment.h globaldata.h virtobj.h
-beam.h: main.h virtobj.h physobj.h
-missile.h: main.h physobj.h
-teleport.h: main.h virtobj.h
-decor.h: main.h physobj.h environment.h globaldata.h
-explosion.h: main.h physobj.h
-virtobj.cpp: virtobj.h environment.h
-physobj.cpp: physobj.h environment.h
-atanks.cpp: globals.h menu.h button.h team.h files.h satellite.h menucontent.h
-beam.cpp: environment.h globaldata.h physobj.h player.h decor.h tank.h beam.h
-button.cpp: button.h
-environment.cpp: environment.h globaldata.h virtobj.h missile.h tank.h files.h
-explosion.cpp: environment.h globaldata.h explosion.h missile.h decor.h tank.h player.h
-fade.cpp: globaldata.h main.h
-files.cpp: player.h files.h main.h
-globaldata.cpp: player.h globaldata.h files.h
-missile.cpp: environment.h globaldata.h explosion.h missile.h decor.h tank.h
-perlin.cpp: main.h
-player.cpp: environment.h globaldata.h player.h tank.h menu.h files.h floattext.h
-satellite.cpp: environment.h satellite.h beam.h
-sky.cpp: globaldata.h main.h sky.h
-tank.cpp: environment.h globaldata.h floattext.h explosion.h teleport.h missile.h player.h beam.h tank.h
-team.cpp: tank.h team.h player.h
-teleport.cpp: environment.h globaldata.h teleport.h
diff --git a/src/Makefile.windows b/src/Makefile.windows
deleted file mode 100644
index 3aaf235..0000000
--- a/src/Makefile.windows
+++ /dev/null
@@ -1,157 +0,0 @@
-.PHONY: all clean veryclean
-
-MODULES = atanks.o beam.o button.o environment.o explosion.o fade.o files.o globaldata.o \
- missile.o perlin.o physobj.o player.o satellite.o sky.o tank.o team.o teleport.o virtobj.o update.o network.o \
- floattext.o land.o text.o client.o gameloop.o
-MODULES += atanks.res
-
-# CPP=g++
-CPP=mingw32-g++
-CC=gcc
-LD=g++
-LIB=ar
-WINDRES=windres.exe
-FLAGS += -DNEW_GAMELOOP -DDATA_DIR=\".\" -Wno-write-strings
-#FLAGS += -DDATA_DIR=\"${INSTALLDIR}\" -Wno-write-strings -DTHREADS
-OUTPUT = ../atanks.exe
-WFLAGS =
-OFLAGS =
-LFLAGS += -mwindows
-LDFLAGS = -L../.. -lalleg
-#LDFLAGS += -lpthread
-
-CFLAGS += -Wall -Iinclude # -fprofile-arcs -ftest-coverage
-
-SRCS = $(MODULES:.o=.cpp)
-GLOBALS = main.h imagedefs.h externs.h
-
-all: $(OUTPUT)
-
-clean:
- rm -f *.o
-
-veryclean: clean
- rm $(OUTPUT)
-
-$(MODULES): Makefile
-
-atanks.res:
- $(WINDRES) -i atanks.rc --input-format=rc -o atanks.res -O coff
-
-atanks.o: atanks.cpp globals.h main.h menucontent.h
- $(CPP) -c atanks.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-button.o: button.cpp button.h
- $(CPP) -c button.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-client.o: client.h client.cpp
- $(CPP) -c client.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-environment.o: environment.cpp environment.h
- $(CPP) -c environment.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-explosion.o: explosion.cpp explosion.h
- $(CPP) -c explosion.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-files.o: files.cpp files.h text.h text.cpp
- $(CPP) -c files.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-floattext.o: floattext.cpp floattext.h
- $(CPP) -c floattext.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-gameloop.o: gameloop.h gameloop.cpp
- $(CPP) -c gameloop.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-globaldata.o: globaldata.cpp globaldata.h
- $(CPP) -c globaldata.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-land.o: land.cpp land.h globaldata.h environment.h
- $(CPP) -c land.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-missile.o: missile.cpp missile.h
- $(CPP) -c missile.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-teleport.o: teleport.cpp teleport.h
- $(CPP) -c teleport.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-physobj.o: physobj.cpp physobj.h
- $(CPP) -c physobj.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-player.o: player.cpp player.h
- $(CPP) -c player.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-tank.o: tank.cpp tank.h
- $(CPP) -c tank.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-team.o: team.cpp team.h
- $(CPP) -c team.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-virtobj.o: virtobj.cpp virtobj.h
- $(CPP) -c virtobj.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-fade.o: fade.cpp
- $(CPP) -c fade.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-perlin.o: perlin.cpp
- $(CPP) -c perlin.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-sky.o: sky.cpp sky.h
- $(CPP) -c sky.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-satellite.o: satellite.cpp satellite.h
- $(CPP) -c satellite.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-text.o: text.cpp text.h
- $(CPP) -c text.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-update.o: update.cpp update.h
- $(CPP) -c update.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-network.o: network.cpp network.h
- $(CPP) -c network.cpp -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-%.o: %.cpp %.h
- $(CPP) -c $< -o $@ $(FLAGS) $(LFLAGS) $(OFLAGS) $(CFLAGS)
-
-$(OUTPUT): $(OBJECTS) $(MODULES)
- $(CPP) $(MODULES) -o $(OUTPUT) $(FLAGS) $(LFLAGS) $(LDFLAGS) $(SFLAGS) $(CFLAGS)
-
-# dependencies:
-physobj.h: main.h virtobj.h globaldata.h
-virtobj.h: main.h player.h
-main.h: imagedefs.h externs.h
-globaldata.h: main.h
-player.h: main.h menu.h
-tank.h: physobj.h
-floattext.h: virtobj.h main.h environment.h
-menu.h: globaldata.h
-environment.h: main.h tank.h
-files.h: globaldata.h environment.h
-globals.h: virtobj.h floattext.h physobj.h tank.h missile.h explosion.h player.h environment.h globaldata.h teleport.h decor.h beam.h
-button.h: globaldata.h environment.h
-team.h: globaldata.h
-satellite.h: environment.h globaldata.h virtobj.h
-beam.h: main.h virtobj.h physobj.h
-missile.h: main.h physobj.h
-teleport.h: main.h virtobj.h
-decor.h: main.h physobj.h environment.h globaldata.h
-explosion.h: main.h physobj.h
-virtobj.cpp: virtobj.h environment.h
-physobj.cpp: physobj.h environment.h
-atanks.cpp: globals.h menu.h button.h team.h files.h satellite.h menucontent.h
-beam.cpp: environment.h globaldata.h physobj.h player.h decor.h tank.h beam.h
-button.cpp: button.h
-environment.cpp: environment.h globaldata.h virtobj.h missile.h tank.h files.h
-explosion.cpp: environment.h globaldata.h explosion.h missile.h decor.h tank.h player.h
-fade.cpp: globaldata.h main.h
-files.cpp: player.h files.h main.h
-globaldata.cpp: player.h globaldata.h files.h
-missile.cpp: environment.h globaldata.h explosion.h missile.h decor.h tank.h
-perlin.cpp: main.h
-player.cpp: environment.h globaldata.h player.h tank.h menu.h files.h floattext.h
-satellite.cpp: environment.h satellite.h beam.h
-sky.cpp: globaldata.h main.h sky.h
-tank.cpp: environment.h globaldata.h floattext.h explosion.h teleport.h missile.h player.h beam.h tank.h
-team.cpp: tank.h team.h player.h
-teleport.cpp: environment.h globaldata.h teleport.h
-
diff --git a/src/aicore.cpp b/src/aicore.cpp
new file mode 100644
index 0000000..3746d8c
--- /dev/null
+++ b/src/aicore.cpp
@@ -0,0 +1,5584 @@
+#include "aicore.h"
+#include "player.h"
+#include "tank.h"
+#include "missile.h"
+#include "beam.h"
+#include "explosion.h"
+
+#include <cassert>
+
+
+/// @brief Maximum AI Level is the highest level being lucky, thus +1.
+const int32_t maxAiLevel = DEADLY_PLAYER + 1;
+
+
+/** @struct sItemListEntry
+ * @brief doubly linked list element to organize the AIs item preferences.
+**/
+struct sItemListEntry
+{
+ int32_t amount = 0; //!< Number of items in stock.
+ bool escape = false; //!< Set to true if "getting away" points are awarded.
+ bool kamikaze = false; //!< Set to true if self destruct points are awarded.
+ sItemListEntry* next = nullptr;
+ int32_t preference = 0; //!< Shortcut to the players preferences.
+ sItemListEntry* prev = nullptr;
+ int32_t score = 0; //!< How likely the AI uses this item.
+ bool selectable = false; //!< Some are not selectable, like parachutes.
+ int32_t type = 0; //!< The (enum) itemType of the item
+
+ explicit sItemListEntry(sItemListEntry* prev_);
+ ~sItemListEntry();
+
+ const char* getName() { return item[type].getName(); }
+};
+
+
+/** @struct sOppMemEntry
+ * @brief doubly linked list element to organize the AIs opponent memory.
+**/
+struct sOppMemEntry
+{
+ bool alive = true; //!< False if the tank is destroyed.
+ int32_t attempts = 0; //!< How often tried to hit this round.
+ int32_t buried_l = 0; //!< Buried level to the left.
+ int32_t buried_r = 0; //!< Buried level to the right.
+ double diffLife = 0.; //!< Difference to bots life value: (this - opp).
+ double distance = 0.; //!< Shortcut to the absolute distance between both tanks.
+ int32_t dmgDone = 0; //!< damage done in simulation to calculate hit score.
+ sOpponent* entry = nullptr; //!< The AIs sOpponent memory (see players.h).
+ bool hasRepulse = false; //!< Whether or not the opponent has a repulsor shield up.
+ sOppMemEntry* next = nullptr;
+ bool is_buried = 0; //!< Whether buried_l+buried_r is greater than BURIED_LEVEL.
+ bool onSameTeam = false; //!< True if on the same team as the player.
+ double opLife = 0.; //!< Full opponents life, which is tank->sh + tank->l.
+ double opX = 0; //!< X-coordinate of the opponents tank.
+ double opY = 0; //!< Y-coordinate of the opponents tank.
+ sOppMemEntry* prev = nullptr;
+ bool revengeDone = false; //!< Wether the score has already taken revenge into account.
+ int32_t score = 0; //!< How likely the AI attacks this opponent.
+ double team_mod = 1.; //!< Multiplier for the score according to which teams both belong to.
+
+ explicit sOppMemEntry(sOppMemEntry* prev_);
+ ~sOppMemEntry();
+
+ const char* getName() { return entry->opponent->getName(); }
+};
+
+
+/** @struct sWeapListEntry
+ * @brief doubly linked list element to organize the AIs weapon preferences
+**/
+struct sWeapListEntry
+{
+ int32_t amount = 0; //!< Number of weapons in stock.
+ bool blastOut = false; //!< Set to true if blasting out points are awarded.
+ double dmgCluster = 0.; //!< Cluster full damage.
+ double dmgSingle = 0.; //!< Single shot damage.
+ double dmgSpread = 0.; //!< Spread full damage.
+ bool kamikaze = false; //!< Set to true if self destruct points are awarded.
+ sWeapListEntry* next = nullptr;
+ int32_t preference = 0; //!< Shortcut to the players preferences.
+ sWeapListEntry* prev = nullptr;
+ int32_t radius = 0; //!< Blast radius of the weapon.
+ int32_t score = 0; //!< How likely the AI uses this weapon.
+ int32_t spread = 1; //!< Checked weapon spread value. (See AICore::getMemory())
+ int32_t subMunCount= 0; //!< Number of sub munition "bomblets"
+ int32_t subMunType = -1; //!< Clusters and such have sub munition.
+ int32_t type = 0; //!< The (enum) weaponType of the weapon.
+
+ explicit sWeapListEntry(sWeapListEntry* prev_);
+ ~sWeapListEntry();
+
+ const char* getName() { return weapon[type].getName(); }
+};
+
+
+/// @brief Template swapper, the types just need prev/next pointers
+template<typename T> void swap_entries(T* lhs, T* rhs)
+{
+ if (lhs && rhs && (lhs != rhs)) {
+ // backup neighbourhood (and use as short cuts ;-) )
+ T* l_next = lhs->next;
+ T* l_prev = lhs->prev;
+ T* r_next = rhs->next;
+ T* r_prev = rhs->prev;
+
+ // Insert rhs into lhs location
+ if (l_next && (l_next != rhs)) l_next->prev = rhs;
+ if (l_prev && (l_prev != rhs)) l_prev->next = rhs;
+
+ // Insert lhs into rhs location
+ if (r_next && (r_next != lhs)) r_next->prev = lhs;
+ if (r_prev && (r_prev != lhs)) r_prev->next = lhs;
+
+ // Move rhs to lhs location
+ rhs->next = l_next == rhs ? lhs : l_next;
+ rhs->prev = l_prev == rhs ? lhs : l_prev;
+
+ // Move lhs to (former) rhs location
+ lhs->next = r_next == lhs ? rhs : r_next;
+ lhs->prev = r_prev == lhs ? rhs : r_prev;
+ }
+}
+
+
+/// @brief Template sorter, the types need prev, next and score.
+/// Sorting is done by score in descending order. If *head is sorted
+/// down the list, it is set to the new first element.
+template<typename T> void sort_entries(T** head)
+{
+ if (!head || !(*head) )
+ return;
+
+ bool sorted = false;
+
+ while (!sorted) {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ sorted = true;
+
+ T* curr = *head;
+ T* next = curr->next;
+
+ while (next) {
+ if (next->score > curr->score) {
+ sorted = false;
+ swap_entries(curr, next);
+ if (*head == curr)
+ *head = next;
+ } else
+ curr = next;
+ next = curr->next;
+ } // End of having next
+ } // end of not sorted
+
+#if defined(ATANKS_DEBUG_AIMING) || defined(ATANKS_DEBUG_EMOTIONS)
+ DEBUG_LOG_AI("Memory Sorting", "Sorting results:", 0)
+ T* curr = *head;
+ int32_t nr = 1;
+ while (curr) {
+ if (curr->score > -10000) {
+ DEBUG_LOG_AI("Memory Sorting", "% 3d: Score %5d (%s)",
+ nr++, curr->score, curr->getName())
+ curr = curr->next;
+ } else
+ curr = nullptr;
+ }
+#endif // ATANKS_DEBUG_AIMING || ATANKS_DEBUG_EMOTIONS
+}
+
+
+/// @brief AICore default constructor
+AICore::AICore() :
+ textAllowed(ATOMIC_VAR_INIT(true))
+{
+ // As the opponent counts, and both weapons and items
+ // list sizes are fixed, memory is reserved here.
+
+ /// 1) Items
+ for (int32_t i = 0; canWork && (i < ITEMS); ++i) {
+ try {
+ item_curr = new itEntry_t(item_last);
+ if (!item_head)
+ item_head = item_curr;
+ item_last = item_curr;
+ } catch (std::exception &e) {
+ cerr << "Unable to reserve " << sizeof(itEntry_t);
+ cerr << " bytes for AI item chain: " << e.what() << endl;
+
+ destroy();
+ canWork = false;
+ }
+ }
+
+ /// 2) Opponents
+ for (int32_t i = 0; canWork && (i < env.numGamePlayers); ++i) {
+ try {
+ mem_curr = new opEntry_t(mem_last);
+ if (!mem_head)
+ mem_head = mem_curr;
+ mem_last = mem_curr;
+ } catch (std::exception &e) {
+ cerr << "Unable to reserve " << sizeof(opEntry_t);
+ cerr << " bytes for AI memory chain: " << e.what() << endl;
+
+ destroy();
+ canWork = false;
+ }
+ }
+
+ /// 3) Weapons
+ for (int32_t i = 0; canWork && (i < WEAPONS); ++i) {
+ try {
+ weap_curr = new weEntry_t(weap_last);
+ if (!weap_head)
+ weap_head = weap_curr;
+ weap_last = weap_curr;
+ } catch (std::exception &e) {
+ cerr << "Unable to reserve " << sizeof(weEntry_t);
+ cerr << " bytes for AI weapon chain: " << e.what() << endl;
+
+ destroy();
+ canWork = false;
+ }
+ }
+
+ // Stop if no work can be done
+ isStopped = !canWork;
+
+ DEBUG_LOG_AI("AICore", "Instance created", 0)
+}
+
+
+/// @brief AICore destructor
+AICore::~AICore()
+{
+ if (isWorking) {
+ if (!isStopped)
+ this->stop();
+ while (isWorking)
+ std::this_thread::yield();
+ }
+
+ // Clean up memory chains:
+ this->destroy();
+
+ DEBUG_LOG_AI("AICore", "Instance destroyed", 0)
+}
+
+
+/// @brief return the currently active player or nullptr if not working
+PLAYER* AICore::active_player() const
+{
+ if (isWorking)
+ return player;
+ return nullptr;
+}
+
+
+/** @brief aim the current selection
+ * @param[in] is_last if set to true, the best result is accepted, no matter
+ * what the outcome might be.
+ * @return true if the aiming resulted in a usable hit.
+**/
+bool AICore::aim(bool is_last)
+{
+ plStage = PS_AIM;
+
+ DEBUG_LOG_AIM(player->getName(), "Starting to aim %s at %s",
+ weapon[weap_idx].getName(), mem_curr->entry->opponent->getName())
+
+ int32_t attempt = 0;
+
+ // reset current values as there can be no guarantee that the
+ // last selected combination works for the current weapon/opponent
+ // selection.
+ sanitizeCurr();
+ hill_detected = false;
+ // Note: curr_overshoot is reset to MAX_OVERSHOOT in calcAttack() but
+ // might have an actual traced value from calcBoxed(), so do not reset
+ // it here again.
+
+
+ // Reset aiming round memory
+ best_score = NEUTRAL_ROUND_SCORE;
+ best_angle = angle;
+ best_power = power;
+ best_prime_hit = false;
+ best_overshoot = MAX_OVERSHOOT;
+ last_ang_mod = 0;
+ last_overshoot = MAX_OVERSHOOT;
+ last_pow_mod = 0;
+ last_reverted = false;
+ last_score = 0;
+ last_was_better = false;
+ reached_x = x;
+ reached_y = y;
+
+
+ // loop until finished, forced off or ending unsuccessfully
+ while (!isStopped && (++attempt <= findRngAttempts) ) {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ int32_t hit_score = 0;
+ int32_t has_crashed = 0;
+ int32_t has_finished = 0;
+
+ // Modifications for this round:
+ int32_t ang_mod = 1 + RAND_AI_1P; // [ 1; 7]
+ int32_t pow_mod = (10 + RAND_AI_1P) // [10; 17]
+ * curr_power / 100; // [10;340]
+
+ DEBUG_LOG_AIM(player->getName(),
+ "[%d/%d] Angle % 3d, Power % 4d",
+ attempt, findRngAttempts,
+ GET_DISP_ANGLE(curr_angle), curr_power)
+
+ // See where we are going:
+ traceWeapon(has_crashed, has_finished);
+ hit_score = calcHitScore(is_last && needSuccess);
+
+
+ // See whether this shot actually got nearer to the opponent.
+ // (Note: Otherwise a better score just means less collateral damage!)
+ bool is_nearer = std::abs(last_overshoot) >= std::abs(curr_overshoot);
+
+
+ DEBUG_LOG_AIM(player->getName(),
+ "[%d/%d] => Score %d, Overshoot %d (%s [last: %d])",
+ attempt, findRngAttempts, hit_score, curr_overshoot,
+ is_nearer ? "Nearer" : "Farther", last_overshoot)
+
+
+ // Note down a new best score:
+ bool new_best_score = (hit_score > best_score);
+ if ( ( new_best_score && curr_prime_hit)
+ || (!best_prime_hit && (new_best_score || curr_prime_hit) ) ) {
+ // Note: Better score with prime hit, prime hit for the first time,
+ // or better score with prime never hit.
+ DEBUG_LOG_AIM(player->getName(),
+ "[%d/%d] => New best score %d [%d best]",
+ attempt, findRngAttempts,
+ hit_score, best_score)
+
+ sanitizeCurr();
+
+ best_angle = curr_angle;
+ best_overshoot = curr_overshoot;
+ best_power = curr_power;
+ best_prime_hit = curr_prime_hit;
+ best_score = hit_score;
+
+ // The last modifications seem to have brought something
+ ang_mod = (last_ang_mod + (SIGN(last_ang_mod) * ang_mod)) / 2;
+ pow_mod = (last_pow_mod + pow_mod) / 2 * SIGN(best_overshoot);
+ }
+
+
+ // The outcome has to be checked:
+ if (canWork && !isStopped) {
+
+ /* The following situations can occur:
+ *
+ * A) The shot (or part of them) have not been finished.
+ * In this case power must be reduced and the angle
+ * has to be moved in a more neutral position if it
+ * is too steep or flat.
+ * B) With steel or wrap wall the shot might crash into
+ * a wrap ceiling or the wall and ceiling made of steel.
+ * Generally this can only fixed by lowering power and
+ * getting away from 45° angles.
+ * C) The hit is nearer than the last one.
+ * This is good. If it even has a positive hit_score,
+ * it can be noted down as a new best hit.
+ * D) The hit is farther away. If the score is better,
+ * the direction of modification seems correct.
+ * Otherwise it might be better to revert those changes.
+ */
+
+ // --- Situation A) The shot was not finished. ---
+ //-------------------------------------------------
+ if ( !has_finished // Pure situation A must be taken care of
+ || ( (has_finished < weap_curr->spread)
+ && ( (has_finished - RAND_AI_0P) < 0) ) ) {
+
+ DEBUG_LOG_AIM(player->getName(),
+ "[%d/%d] %d / %d finished, trying to correct",
+ attempt, findRngAttempts,
+ has_finished, weap_curr->spread)
+
+ fixUnfinished(ang_mod, pow_mod);
+ last_reverted = (SIGN(last_ang_mod) != SIGN(ang_mod));
+ last_was_better = false;
+ } // End of having lost the shot prediction
+
+
+ // --- Situation B) The shot(s) crashed ---
+ //------------------------------------------------
+ else if ( (has_crashed == weap_curr->spread)
+ || ( (has_crashed > 0)
+ && ((has_crashed + RAND_AI_0P) >= weap_curr->spread)) ) {
+
+ DEBUG_LOG_AIM(player->getName(),
+ "[%d/%d] %d / %d crashed, trying to correct",
+ attempt, findRngAttempts,
+ has_crashed, weap_curr->spread)
+
+ fixCrashed(ang_mod, pow_mod);
+
+ // If there is a positive hit_score, halve it for every
+ // shot that crashed:
+ if (hit_score > 0) {
+ for (int32_t i = 0; i < has_crashed; ++i) {
+ if (RAND_AI_0P)
+ hit_score /= 2;
+ }
+ }
+
+ last_reverted = (SIGN(last_ang_mod) != SIGN(ang_mod));
+ last_was_better = false;
+ }
+
+
+ // --- Situation C) The shot is nearer to the target. ---
+ //--------------------------------------------------------
+ else if (is_nearer) {
+ // Note: if this is a new best score, some adaptation has
+ // already been made above.
+ if (hit_score < last_score) {
+ DEBUG_LOG_AIM(player->getName(),
+ "[%d/%d] => Nearer but not a better score %d [%d best]",
+ attempt, findRngAttempts,
+ hit_score, best_score)
+
+ // Just modify the new angle mod to mimic the last
+ // with new values
+ ang_mod = std::abs(ang_mod) * SIGN(last_ang_mod);
+
+ last_was_better = true;
+ } else
+ last_was_better = false;
+
+ last_reverted = (SIGN(last_ang_mod) != SIGN(ang_mod));
+
+ // pow_mod must have the opposite sign of the overshoot:
+ pow_mod = std::abs(pow_mod) * SIGN(curr_overshoot) * -1;
+
+ DEBUG_LOG_AIM(player->getName(),
+ "[%d/%d] New angle mod %d, new power mod %d",
+ attempt, findRngAttempts,
+ ang_mod, pow_mod)
+ } // End of having a nearer hit
+
+
+ // --- Situation D) The shot hit farther away than the best. ---
+ //---------------------------------------------------------------
+ else {
+ DEBUG_LOG_AIM(player->getName(),
+ "[%d/%d] Farther impact (%d curr, %d best)"
+ " [score %d]",
+ attempt, findRngAttempts, curr_overshoot,
+ best_overshoot, hit_score)
+
+ fixOvershoot(ang_mod, pow_mod, hit_score);
+
+ last_reverted = SIGN(ang_mod) != SIGN(last_ang_mod);
+
+ DEBUG_LOG_AIM(player->getName(),
+ "[%d/%d] New angle mod %d, new power mod %d",
+ attempt, findRngAttempts,
+ ang_mod, pow_mod)
+ } // End of situation C
+
+
+ // Try to fix 180° shots if no positive score was achieved:
+ if ( (180 == curr_angle) && !ang_mod && (hit_score < 1) ) {
+ ang_mod = SIGN(mem_curr->opX - x)
+ * (RAND_AI_1P + 1)
+ * -1.;
+ DEBUG_LOG_AIM(player->getName(),
+ "Vertical shot detected, new angle mod %d",
+ ang_mod)
+ }
+
+
+ // Power modification can be modified by a difference between
+ // the overshoot and the actual modification according to
+ // AI settings:
+ // ----------------------------------------------------------
+ double power_diff = (std::abs(curr_overshoot) - std::abs(pow_mod))
+ / (std::abs(ang_mod) ? std::abs(ang_mod) : 1);
+
+ if ( (curr_overshoot < MAX_OVERSHOOT)
+ && (power_diff > std::abs(pow_mod))
+ && (hit_score < 1) ) {
+ DEBUG_LOG_AIM(player->getName(),
+ "Too low power mod difference %d"
+ " (overshoot %d, pow_mod %d)",
+ ROUND(power_diff), curr_overshoot, ROUND(pow_mod))
+
+ pow_mod += power_diff * focusRate / 2. * SIGNd(pow_mod);
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Hopefully fixed power mod: %d",
+ pow_mod)
+ }
+
+
+ // Make sure both modifications applied end in a sane results:
+ // -----------------------------------------------------------
+
+ // Test angle to the right
+ if ( (curr_angle + ang_mod) < 90) {
+ if (curr_angle > 90)
+ // Just sanitize
+ ang_mod = 90 - curr_angle;
+ else
+ // Pull up to try again from a very different view
+ ang_mod = ROUNDu(60. * focusRate) // + [10;60]
+ - (RAND_AI_0P * 5); // - [ 5;25]
+ } // End of sanitizing angle right
+
+ // Test angle to the left
+ else if ( (curr_angle + ang_mod) > 270) {
+ if (curr_angle < 270)
+ // Just sanitize
+ ang_mod = 270 - curr_angle;
+ else
+ // Pull up to try again from a very different view
+ ang_mod = (-60. * focusRate) // - [10;60]
+ + (RAND_AI_0P * 5); // + [ 0;25]
+ } // End of sanitizing angle left
+
+
+ // Test bottom power range
+ if ( (curr_power + pow_mod) < MIN_POWER) {
+ if (curr_power > MIN_POWER)
+ // Just sanitize
+ pow_mod = MIN_POWER - curr_power;
+ else
+ // Give more power to go somewhere else
+ pow_mod = (900. * focusRate) // + [150;900]
+ - (RAND_AI_0P * 50); // - [ 0;250]
+ }
+
+ // Test upper power range
+ if ( (curr_power + pow_mod) > MAX_POWER) {
+ if (curr_power < MAX_POWER)
+ // Just sanitize
+ pow_mod = MAX_POWER - curr_power;
+ else
+ // Give more power to go somewhere else
+ pow_mod = (-900. * focusRate) // - [150;900]
+ + (RAND_AI_0P * 50); // + [ 0;250]
+ }
+
+
+ // Apply mods:
+ curr_angle += ang_mod;
+ curr_power += pow_mod;
+
+
+ // Save current score, mods and overshot:
+ last_ang_mod = ang_mod;
+ last_overshoot = curr_overshoot;
+ last_pow_mod = pow_mod;
+ last_score = hit_score;
+ } // End of canWork and not isStopped
+
+
+ } // end of aiming loop
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Final score with angle %d, power %d : %d => %s%s",
+ GET_DISP_ANGLE(best_angle), best_power, best_score,
+ (best_score > 0) || (is_last && needSuccess) ? "Success!" : "Failure!",
+ is_last && needSuccess ? " (is_last forced!)" : "")
+
+ // If this was the last try and it did not reach the target having
+ // a negative best score, assume that the path is blocked.
+ // However, if a best setup is already known, revert to that.
+ if ( (weap_idx < WEAPONS) && is_last && needSuccess
+ && (best_setup_score < 0)
+ && (best_round_score < 0)
+ && (best_score < 0)
+ && (best_overshoot < 0) // too short
+ && ( ( (reached_y > BOXED_TOP) // Not a ceiling crash,
+ && (-best_overshoot > weap_curr->radius) ) // but can't hit
+ || hill_detected ) /* if a hill was detected, it must be removed */ ) {
+ bool free_tank = std::abs(reached_x - x) < weapon[RIOT_BLAST].radius;
+
+ if (useFreeingTool(free_tank, is_last)) {
+ needAim = false;
+ isBlocked = true;
+ hill_detected = true;
+
+ if (free_tank)
+ calcUnbury(is_last);
+ else {
+ // Write back best values
+ curr_angle = best_angle;
+ curr_power = best_power;
+
+ // If this is a shot that got too short and a riot bomb
+ // is chosen, flatten the angle to hit the mountain in between
+ flattenCurrAng();
+
+ // Now set the results:
+ sanitizeCurr();
+ angle = curr_angle;
+ power = curr_power;
+ DEBUG_LOG_AIM(player->getName(),
+ "Obstacle detected, trying to clear path using %s",
+ weap_idx < WEAPONS
+ ? weapon[weap_idx].getName()
+ : item[weap_idx - WEAPONS].getName())
+ }
+ return true;
+ }
+ }
+
+ // Write back best values if this is a success:
+ if (best_score > best_round_score) {
+ best_round_score = best_score;
+ curr_angle = best_angle;
+ curr_power = best_power;
+ sanitizeCurr();
+ }
+
+
+ return ( (best_round_score > 0) || (is_last && needSuccess) );
+}
+
+
+/// @brief Allow AICore to create FLOATTEXT instances
+void AICore::allowText()
+{
+ textAllowed.store(true, ATOMIC_WRITE);
+}
+
+
+/** @brief calculate basic attack values or set up the last ones
+ * @param[in] attempt If this equals findTgtAttempts, this method is forced to
+ * not check too harshly, so it always returns true.
+ * @return true if the method came up with something sane.
+**/
+bool AICore::calcAttack(int32_t attempt)
+{
+ bool is_last = ((attempt == findTgtAttempts) && needSuccess);
+ plStage = PS_CALCULATE;
+ isBlocked = false;
+ needAim = false;
+ curr_overshoot = MAX_OVERSHOOT;
+ offset_x = 0;
+ offset_y = 0;
+
+ // If an item is chosen over a weapon, nothing is to be done
+ if (item_curr && !weap_curr) {
+ // If an item is selected, write back the currently used
+ // angle and power, nothing is to be changed now.
+ curr_angle = angle;
+ curr_power = power;
+ return true;
+ }
+
+ assert(weap_curr
+ && "ERROR: weap_curr is nullptr in calcAttack() with no item chosen!");
+
+ // If the currently chosen opponent is the one attacked
+ // in the last round, simply copy back the old attack
+ // values and be done
+ if ( last_opp
+ && (last_opp->opponent != player) // don't repeat self destruct attempts
+ && (last_opp == mem_curr->entry)
+ && (last_weap == weap_idx) ) {
+ curr_angle = last_ang;
+ curr_power = last_pow;
+
+ if (weap_idx < WEAPONS)
+ needAim = true;
+
+ return true;
+ }
+
+ /* If the current target is different or there was no last target,
+ * a basic set of values must be generated.
+ *
+ * Outline:
+ * --------
+ * There are five possible scenarios:
+ * a) The tank is not buried (enough) and a laser is chosen:
+ * -> a direct angle will do, make sure power is sane.
+ * b) The tank is buried and an appropriate tool is chosen:
+ * -> fire tool at the most filled side or, if the difference is less
+ * than the AI level, in the direction of the chosen opponent.
+ * c) Kamikaze
+ * -> indicated by setting mem_curr to the own entry
+ * -> if shaped weapon is chosen, fire 45° and power 150 to the side
+ * where the terrain height is nearest to this tanks bottom.
+ * -> if napalm is chosen, fire against the wind with power 100 - 300
+ * -> otherwise fire 180° and power 200 + spread modification.
+ * d) Fire in non-boxed mode
+ * -> normal calculation
+ * e) Fire in boxed mode
+ * -> extended power-control after normal calculation
+ * -> if the target can't be reached while staying below the ceiling,
+ * check for an obstacle that can be removed and do so if found.
+ */
+
+ DEBUG_LOG_AIM(player->getName(), "[%d / %d] Starting to aim at %s",
+ attempt, findTgtAttempts, mem_curr->entry->opponent->getName())
+ DEBUG_LOG_AIM(player->getName(), "Aim from %d/%d to %d/%d [distance %d/%d]",
+ ROUND(x), ROUND(y),
+ ROUND(mem_curr->opX), ROUND(mem_curr->opY),
+ ROUND(mem_curr->opX - x), ROUND(mem_curr->opY - y))
+
+ /* Case a) The tank is not buried (enough) and a laser is chosen
+ * ===================================================================
+ */
+ if ( (buried < BURIED_LEVEL)
+ && (SML_LAZER <= weap_idx)
+ && (LRG_LAZER >= weap_idx) )
+ return calcLaser(is_last);
+
+
+ /* Case b) The tank is buried and an appropriate tool is chosen
+ * ===================================================================
+ * (This means that it must be checked whether this is an appropriate
+ * tool or not. Here the method might fail if it isn't suitable.)
+ */
+ if (buried >= BURIED_LEVEL)
+ return calcUnbury(is_last);
+
+
+ /* Case c) Kamikaze
+ * ===================================================================
+ */
+ if (mem_curr->entry->opponent == player)
+ return calcKamikaze(is_last);
+
+
+ /* Case d) Fire in non-boxed mode
+ * ===================================================================
+ * This is always done, the boxed mode variant below simply checks the
+ * values and tries to adapt.
+ * The flipping is only allowed if the same opponent is tried again.
+ */
+ bool result = calcStandard(is_last, (0 == (++mem_curr->attempts % 2)));
+
+
+ /* Case e) Fire in boxed mode
+ * ===================================================================
+ * -> extended power-control after normal calculation
+ * -> if the target can't be reached while staying below the ceiling,
+ * check for an obstacle that can be removed and do so if found.
+ */
+ if (result && env.isBoxed && !isBlocked && needAim && (weap_idx < WEAPONS))
+ result = calcBoxed(is_last);
+
+
+ return result;
+}
+
+
+/** @brief Case e) Fire in boxed mode
+ *
+ * Note: calcAttack() has to make sure this method is only called if it is
+ * appropriate. No further checks are made within this method.
+ *
+ * @param[in] is_last If this is set to true, the method is forced to succeed.
+ * @return true if sane values could be found.
+**/
+bool AICore::calcBoxed(bool is_last)
+{
+ // Return at once if the bot "forgets" that there is a ceiling:
+ if (!is_last && RAND_AI_1N)
+ // With this even the useless bot has only a ~33% chance to forget...
+ return true;
+
+ bool crashed = true; // Assume the shot crashed in the ceiling
+ bool finished = false;
+ int32_t reached_x = x;
+ int32_t reached_y = y;
+ double end_xv = 0.;
+ double end_yv = 0.;
+ bool can_mod_a = true;
+ bool can_mod_p = true;
+ bool top_wrap = false; // Whether the shot wrapped through a wrap ceiling
+ bool can_dig = (weap_idx >= BURROWER)
+ && (weap_idx <= PENETRATOR);
+
+ // Cycle until the ceiling isn't hit any more.
+ while ( canWork && !isStopped && crashed
+ && (can_mod_a || can_mod_p)
+ && traceShot(curr_angle, finished, top_wrap, reached_x, reached_y,
+ end_xv, end_yv)
+ && finished ) {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ crashed = false;
+
+ if ( (reached_y <= BOXED_TOP) // crashed on top
+ // wrapped to bottom with dirt above is a ceiling crash, too.
+ || ( top_wrap && !can_dig // (Unless a penetrator trick shot is tried)
+ && (reached_y > global.surface[reached_x].load()) )
+ // Steel wall have additional wall crashes
+ || ( (WALL_STEEL == env.current_wallType)
+ && ( (reached_x <= 2)
+ || (reached_x >= (env.screenWidth - 3) ) ) ) ) {
+
+ crashed = true;
+
+ // Either reduce angle (-1), or power (1), or both (0)
+ int32_t mod_mode = RAND_AI_1P ? (rand() % 2 ? -1 : 1) : 0;
+
+ // Apply mods but do not reduce into nothingness
+ if ( (mod_mode < 1) && can_mod_a) {
+ if ( (curr_angle > 90) && (curr_angle < 270) )
+ curr_angle += curr_angle < 180 ? -1 : 1;
+ else
+ can_mod_a = false;
+ }
+ if ( (mod_mode > -1) && can_mod_p) {
+ if (curr_power > MIN_POWER)
+ curr_power -= 5;
+ else
+ can_mod_p = false;
+ }
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Ceiling crash! Reducing %s [%d°/%d]",
+ 1 == mod_mode ? "power " :
+ 0 == mod_mode ? "angle and power" :
+ "angle ",
+ GET_DISP_ANGLE(curr_angle), curr_power)
+
+ }
+ } // End of crashed loop
+
+ // If the last (not crashed) shot is finished but doesn't get to
+ // the target, it is blocked. But this is only considered if
+ // a) This is the last attempt and
+ // b) This map has a wrap or steel ceiling and
+ // c) There was no positive setup score already
+ if ( finished && !crashed
+ && (best_setup_score <= 0)
+ // But do not bail out on first try!
+ && (best_setup_score > NEUTRAL_ROUND_SCORE)
+ && (weap_idx < WEAPONS)
+ && (curr_overshoot < 0) // too short
+ && is_last
+ && ( (WALL_STEEL == env.current_wallType)
+ || (WALL_WRAP == env.current_wallType) )
+ && (-curr_overshoot > weap_curr->radius) // Can't hit
+ && (-curr_overshoot > (mem_curr->distance / 3 * 2)) ) {
+ // Note: With big weapons an near opponents, the radius might
+ // be larger than two thirds the distance, hence two checks.
+ bool free_tank = FABSDISTANCE2(x, y, reached_x, reached_y)
+ < weapon[RIOT_CHARGE].radius;
+
+ if (useFreeingTool(free_tank, is_last)) {
+ needAim = false;
+ isBlocked = true;
+ if (free_tank)
+ calcUnbury(is_last);
+ else {
+ // If a riot bomb is chosen, flatten the angle:
+ flattenCurrAng();
+ sanitizeCurr();
+ angle = curr_angle;
+ power = curr_power;
+ DEBUG_LOG_AIM(player->getName(),
+ "Obstacle detected, trying to clear path using %s",
+ weap_idx < WEAPONS
+ ? weapon[weap_idx].getName()
+ : item[weap_idx - WEAPONS].getName())
+ }
+ return true;
+ }
+
+ // This did not work. (useFreeingTool always
+ // succeeds if is_last is true)
+ return false;
+ }
+
+ return (is_last || !crashed);
+}
+
+
+/** @brief calculate a hit score off the dmgDone values in the opponent memory
+ *
+ * This method cycles through the opponents memory, and sums up
+ * the damage done with curr_weap to a total score according
+ * to a) how much damage over the opponents health (aka overkill)
+ * has been done and b) on which team they are compared to us.
+ *
+ * If the primary target was not hit, the score is ensured to be
+ * negative, as collateral damage is discouraged. However, this is
+ * only done if @a is_last is false, as collateral damage with a
+ * total positive score is better than nothing on the very last attempt.
+ *
+ * @param[in] is_last if set to true then any score is accepted.
+ * @return The accumulated score.
+**/
+int32_t AICore::calcHitScore(bool is_last)
+{
+ int32_t hit_score = 0;
+ opEntry_t* opp = mem_head;
+ bool can_overkill = true;
+ eTeamTypes target_team = mem_curr
+ ? mem_curr->entry->opponent->team
+ : TEAM_NEUTRAL;
+ bool tgt_team_hit = false;
+ weaponType weapType = static_cast<weaponType>(weap_curr->type);
+
+
+ // Dirt weapons and the reducer can not overkill
+ if ( ( (DIRT_BALL <= weapType)
+ && (SUP_DIRT_BALL >= weapType) )
+ || ( REDUCER == weapType) )
+ can_overkill = false;
+
+
+ while (opp) {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ if (opp->dmgDone > 0) {
+ int32_t overkill = 0;
+ bool is_killed = false;
+ bool shock_hit = (isShocked && (opp->entry == shocker));
+ double self_mod = opp->entry->opponent == player
+ ? (player->selfPreservation + 1.) * ai_level
+ : 1.;
+ // Note: team_mod is negative if same team, so self_mod must
+ // be positive, or self hits generate huge scores!
+
+ // 1: Determine whether the opponent was killed.
+ if (can_overkill && (opp->dmgDone >= opp->opLife) ) {
+ overkill = opp->dmgDone - opp->opLife;
+ opp->dmgDone = opp->opLife;
+ is_killed = true;
+ }
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Total damage against %10s: %d (%s, %d overkill)",
+ opp->entry->opponent->getName(), opp->dmgDone,
+ is_killed ? "KILLED" : "not killed", overkill)
+
+
+ // 2: Add the simple damage to the score:
+ hit_score += static_cast<double>(opp->dmgDone)
+ * opp->team_mod * self_mod
+ * (shock_hit ? ai_over_mod : 1.);
+
+
+ // 3: Raise the score a bit if it is collateral damage on
+ // non-neutral team members of our target, but not our team.
+ if ( (TEAM_NEUTRAL != target_team)
+ && (player->team != target_team)
+ && (opp->entry->opponent->team == target_team) ) {
+ hit_score *= 1. + ((player->defensive + 2.5) / 10.);
+ tgt_team_hit = true;
+ }
+
+
+ // 4: If the opponent is killed, add a bonus to the score depending
+ // on team hit, self hit and whether the bot needs money or not.
+ if (is_killed) {
+ double kill_bonus = opp->dmgDone;
+
+ if (needMoney)
+ kill_bonus *= ai_type_mod;
+
+ // Add some more for killing the shocker:
+ if (shock_hit)
+ kill_bonus += kill_bonus / ai_over_mod;
+
+ hit_score += kill_bonus * opp->team_mod * self_mod;
+ }
+
+
+ // 5: Check for overkill and dock points for it.
+ if (overkill > 0) {
+ double over_score = overkill;
+
+ // It is bad if the bot needs money, as it wastes expensive ammo
+ if (needMoney)
+ over_score *= ai_type_mod + .5;
+
+ // On the other hand, if the bot hit the shocker, the overkill
+ // isn't considered that bad, though.
+ if (shock_hit)
+ over_score /= 3. - ai_over_mod;
+
+ // Generate a generally negative score:
+ over_score = std::abs(over_score) * -1.;
+
+ // The more aggressive, the less the reduction will be,
+ // but only if it is neithe rus nor our team that got hit
+ if (!opp->onSameTeam)
+ over_score /= (player->defensive - 2.) * -1.;
+
+ // Add a fraction of the overkill score
+ hit_score += over_score / (10. - ai_level_d);
+ } // End of overkill score
+ } // end of having damage done
+ opp = opp->next;
+ } // End of looping opponents
+
+ // If the primary target was not hit and this is not the last
+ // attempt, make hit_score to be negative, unless the enemy
+ // team is decimated. In the latter case the score is simply
+ // reduced according to whether the bot needs money or not.
+ if (!curr_prime_hit && !is_last && (hit_score > 0.)) {
+ if (tgt_team_hit)
+ hit_score /= (ai_type_mod + ai_level_d)
+ / (player->defensive + (needMoney ? 2.5 : 4.0));
+ else
+ hit_score = -1 * std::abs(hit_score);
+ }
+
+ return hit_score;
+}
+
+
+/** @brief calculate a score according to where the shot hit and
+ * collateral damage done to friend and foe.
+ *
+ * Damage is recorded in the opponent memory dmgDone value.
+ *
+ * @param[in] hit_x x coordinate where the current selection hit.
+ * @param[in] hit_y y coordinate where the current selection hit.
+ * @param[in] weap_rad Calculated radius of the weapon.
+ * @param[in] dmg Calculated damage of the weapon.
+ * @param[in] weapType Type of the weapon.
+ * @return The resulting score
+**/
+void AICore::calcHitDamage(int32_t hit_x, int32_t hit_y, double weap_rad,
+ double dmg, weaponType weapType)
+{
+ if ( (nullptr == weap_curr) // no weapon no score
+ || (0 == weap_rad) ) // no radius, no hit
+ return;
+
+ // If this has no damage it is either a dirt ball, a reducer
+ // or a riot weapon.
+ // As dirt balls and reducers must be evaluated, they get a fake
+ // damage of their radius so a score can be generated.
+ if (0 == dmg) {
+ if ( ( (DIRT_BALL <= weapType)
+ && (SUP_DIRT_BALL >= weapType) )
+ || ( REDUCER == weapType) )
+ dmg = weap_rad;
+ else
+ return;
+ }
+
+ // Napalm blobs have a much higher full damage output
+ // than listed, as they do damage over time:
+ if (NAPALM_JELLY == weapType)
+ dmg *= static_cast<double>(EXPLOSIONFRAMES * weapon[NAPALM_JELLY].etime)
+ / ai_over_mod;
+
+ // Now the score can be calculated
+ opEntry_t* opp = mem_head;
+ DEBUG_LOG_AIM(player->getName(), "Checking impact at %d x %d", hit_x, hit_y)
+
+ while (opp) {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ TANK* oppTank = opp->entry->opponent->tank;
+
+ if (oppTank) {
+ // Now calculate the score if in range
+ double part_dmg = 0.;
+
+ // For dirt balls, only whether the tank is in x-range counts
+ // as dirt falls down
+ if ( (DIRT_BALL <= weapType)
+ && (SUP_DIRT_BALL >= weapType)
+ && (oppTank->x > (hit_x - weap_rad))
+ && (oppTank->x < (hit_x + weap_rad)) )
+ part_dmg = dmg;
+ else
+ // All others need a normal check
+ part_dmg = get_hit_damage(oppTank, weapType, hit_x, hit_y);
+
+ if (part_dmg > 0.) {
+
+ // REDUCER must be set, it is only checked as valid, yet:
+ if (REDUCER == weapType)
+ part_dmg = opp->entry->opponent->damageMultiplier * 25.;
+
+ // Set hit damage according to primary or collateral damage
+ if (opp == mem_curr) {
+ curr_prime_hit = true;
+ opp->dmgDone += ROUND(part_dmg);
+ DEBUG_LOG_AIM(player->getName(),
+ "%10s in blast range : primary damage : %4d, total %d",
+ opp->entry->opponent->getName(),
+ ROUND(part_dmg), opp->dmgDone)
+ } else {
+ opp->dmgDone += ROUND(part_dmg);
+ DEBUG_LOG_AIM(player->getName(),
+ "%10s in blast range : collateral damage: %4d, total %d",
+ opp->entry->opponent->getName(),
+ ROUND(part_dmg), opp->dmgDone)
+ }
+
+ } // end of having damage delivered
+ } // end of having a tank to consider
+
+ opp = opp->next;
+ }
+}
+
+
+/** @brief Case c) Kamikaze
+ *
+ * Note: calcAttack() has to make sure this method is only called if it is
+ * appropriate. No further checks are made within this method.
+ *
+ * @param[in] is_last If this is set to true, the method is forced to succeed.
+ * @return true if sane values could be found.
+**/
+bool AICore::calcKamikaze(bool is_last)
+{
+ DEBUG_LOG_AIM(player->getName(), "I have decided to go bye bye!", 0)
+
+ // Is the selection sane?
+ if (weap_curr->kamikaze) {
+
+ bool is_good = true;
+
+ // Only weapons need any angle/power adaptation
+ if (weap_curr) {
+ if ( (SHAPED_CHARGE <= weap_idx)
+ && (CUTTER >= weap_idx) ) {
+
+ // For this it is necessary to look at the terrain.
+ // This does not make sense if there isn't a flat area
+ // at either side of the tank with an even height.
+ int32_t bottom = tank->getBottom();
+ int32_t max_rad = weapon[weap_idx].radius / 20;
+
+ // With a power of 150, the weapon can be hurled ~45 pixels.
+ // So check that place plus some pixels around according
+ // to ai_level.
+ int32_t to_go = 1 + ai_level + (RAND_AI_1P);
+ int32_t dist = 45 - (to_go / 2);
+ int32_t diff_l = 0;
+ int32_t diff_r = 0;
+ int32_t xl = x - dist;
+ int32_t xr = x + dist;
+
+ for (int32_t i = 0; i < to_go; ++i) {
+ --xl;
+ ++xr;
+
+ // If any x is in/ beyond a non-wrap wall, screw it:
+ // a - check left side
+ if ( xl < 1 ) {
+ if (WALL_WRAP == env.current_wallType)
+ xl = env.screenWidth - 1 - (1 - std::abs(xl));
+ else
+ diff_l += env.screenHeight;
+ } else
+ diff_l += std::abs(global.surface[xl].load() - bottom);
+
+ // b - check right side
+ if ( xr > (env.screenWidth - 2) ) {
+ if (WALL_WRAP == env.current_wallType)
+ xr = 1 + (env.screenWidth - 1 - xr);
+ else
+ diff_r += env.screenHeight;
+ } else
+ diff_r += std::abs(global.surface[xr].load() - bottom);
+ } // End of looping distance to_go
+
+ // The average distance is taken. This ignores sudden peaks
+ // and gaps that might stop/swallow the projectile, but
+ // that should be okay.
+ diff_l /= to_go;
+ diff_r /= to_go;
+
+ int32_t a_mod = rand() % (10 - ai_level)
+ * (rand() % 2 ? -1 : 1);
+ int32_t p_mod = rand() % (20 - (2 * ai_level))
+ * (rand() % 2 ? -1 : 1);
+ if ( (diff_l < diff_r) && (diff_l < max_rad) )
+ curr_angle = 225 + a_mod;
+ else if (diff_r < max_rad)
+ curr_angle = 135 + a_mod;
+ else
+ is_good = false; // May need emergency plan below
+
+ // Set power and write back values
+ if (is_good)
+ curr_power = 150 + p_mod;
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Firing %s at %d° with power %d%s",
+ weapon[weap_idx].getName(),
+ GET_DISP_ANGLE(curr_angle), curr_power,
+ is_good ? "!" : " will not work! Need a plan!")
+
+ } else if ( (SML_NAPALM <= weap_idx)
+ && (LRG_NAPALM >= weap_idx) ) {
+
+ // Adapt according to the wind:
+ int32_t wind = ROUND(global.wind);
+ int32_t wind_mod = 10 + (std::abs(wind) * (1 + RAND_AI_0P));
+
+ if (wind > 0)
+ curr_angle = 225;
+ else if (wind < 0)
+ curr_angle = 135;
+ else
+ curr_angle = 180;
+
+ curr_power = 100 + wind_mod;
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Firing %s at %d° with power %d (wind %d, wind_mod %d)",
+ weapon[weap_idx].getName(),
+ GET_DISP_ANGLE(curr_angle), curr_power,
+ wind, wind_mod)
+
+ } else {
+ int32_t spread_mod = 50 + (10 * (1 + RAND_AI_0P));
+ int32_t spread = weapon[weap_idx].spread; // shortcut
+ curr_angle = 180;
+ curr_power = 200 + ( (spread * spread_mod) / (2 - (spread % 2)));
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Firing %s at %d° with power %d",
+ weapon[weap_idx].getName(),
+ GET_DISP_ANGLE(curr_angle), curr_power)
+ }
+ }
+
+ if (is_good) {
+ sanitizeCurr();
+ angle = curr_angle;
+ power = curr_power;
+ return true;
+ }
+ } // end of sane item/weapon selection
+
+ // No, this selection does not make sense.
+ // However, if this is a last try, it must work somehow.
+ if ( is_last
+ && ( useItem(ITEM_FATAL_FURY)
+ || useItem(ITEM_DYING_WRATH)
+ || useItem(ITEM_VENGEANCE)
+ || useWeapon(DTH_HEAD)
+ || useWeapon(NUKE)
+ || useWeapon(SML_NUKE)
+ || useWeapon(LRG_MIS)
+ || useWeapon(MED_MIS)
+ || useWeapon(SML_MIS) ) ) {
+ // Note: The trick is, that the first working selection
+ // results in the if-statement to end, and SML_MIS always
+ // works.
+ curr_angle = 180;
+ curr_power = 250;
+ angle = curr_angle;
+ power = curr_power;
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Emergency plan: Firing %s at %d° with power %d",
+ weapon[weap_idx].getName(),
+ GET_DISP_ANGLE(curr_angle), curr_power)
+
+ return true;
+ }
+
+ // This can not work, and does not need to be forced.
+ return false;
+}
+
+
+/** @brief Case a) The tank is not buried (enough) and a laser is chosen.
+ *
+ * Note: calcAttack() has to make sure this method is only called if it is
+ * appropriate. No further checks are made within this method.
+ *
+ * @return True if the opponent can be hit, false if the shot is blocked or
+ * pumped into a wall or the ceiling.
+**/
+bool AICore::calcLaser(bool is_last)
+{
+ int32_t old_angle = curr_angle;
+ int32_t old_power = curr_power;
+ double drift = static_cast<double>(maxAiLevel + 2 - RAND_AI_0P)
+ * errorMultiplier * (rand() % 2 ? -1. : 1.);
+
+ curr_power = tank->p;
+ curr_angle = GET_SAFE_ANGLE(mem_curr->opX - x, mem_curr->opY - y, drift);
+
+ // Power doesn't matter, but the values must be sane nonetheless:
+ sanitizeCurr();
+
+ // Lets see where the laser ends:
+ double start_x = 0;
+ double start_y = 0;
+ player->tank->getGuntop(curr_angle, start_x, start_y);
+
+ BEAM mind_beam(player, start_x, start_y, curr_angle, weap_curr->type,
+ BT_MIND_SHOT);
+
+ int32_t end_x = 0;
+ int32_t end_y = 0;
+ mind_beam.getEndPoint(end_x, end_y);
+
+ // Generate a score for this
+ curr_prime_hit = false;
+ // Note: calcHitDamage() sets curr_prime_hit to true if we hit our target.
+
+ calcHitDamage(end_x, end_y, weapon[weap_curr->type].radius,
+ weap_curr->dmgSingle, static_cast<weaponType>(weap_curr->type));
+ int32_t hit_score = calcHitScore(is_last && needSuccess);
+
+ // If the target is behind a dirt wall, break up this attempt
+ bool crashed = false;
+ if ( !tank->shootClearance(curr_angle, mem_curr->distance, crashed)
+ || crashed) {
+
+ // ...unless this is a forced success ...
+ if (is_last)
+ // at least reduce the score.
+ hit_score = hit_score;
+ else {
+ curr_angle = old_angle;
+ curr_power = old_power;
+ needAim = true; // for the next try or the closure
+ DEBUG_LOG_AI(player->getName(), "Cancelling laser shot: %s",
+ crashed ? "Wrong angle" : "Not enough clearance")
+ return false;
+ }
+ }
+
+ // If a positive score was achieved, we are set
+ if ( (hit_score > 0) || is_last) {
+ // Write back values
+ sanitizeCurr();
+ angle = curr_angle;
+ power = curr_power;
+ if ( (hit_score > best_round_score) || is_last)
+ best_round_score = std::abs(hit_score);
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Firing %s at %d° with power %d",
+ weapon[weap_curr->type].getName(),
+ GET_DISP_ANGLE(curr_angle), curr_power)
+
+ return true;
+ }
+
+ curr_angle = old_angle;
+ curr_power = old_power;
+ needAim = true; // for the next try or the closure
+ DEBUG_LOG_AI(player->getName(), "Cancelling laser shot: %d score too low",
+ hit_score)
+
+ return false;
+}
+
+
+/** @brief calculate x and y offsets for weapons that need it.
+ *
+ * These offsets are stored in offset_x and offset_y, as they are needed
+ * in multiple places.
+ *
+ * If the needed offset is off the screen, or makes no sense, the method
+ * returns false. But if @a is_last is set to true, insane offsets are tried
+ * to be fixed. The idea is, that the bot tries nevertheless out of pure
+ * desperation.
+ *
+ * @param[in] is_last If set to true, the method never fails
+ * @return true if sane offsets were found.
+**/
+bool AICore::calcOffset(bool is_last)
+{
+ bool result = true;
+
+ offset_x = 0;
+ offset_y = 0;
+
+ /* Weapon type 1: Napalm bombs
+ * There are two situations to consider, the normal shot using wind
+ * and the under-run trick. The latter is evil and will almost always
+ * destroy any tank even with a small napalm bomb. Just place the
+ * bomb with tail wind under a tank. No shield can protect the tank
+ * and most jellies will burn into the hull. So this option must be
+ * limited.
+ * Otherwise the wind is what has to be taken into account plus the
+ * surroundings. If the wind side is above the opponent, more distance
+ * is needed than when the area is below the target tank.
+ */
+ if ( (weap_idx >= SML_NAPALM) && (weap_idx <= LRG_NAPALM) ) {
+ offset_x = global.wind * (ai_level + RAND_AI_1P) * (-1. - focusRate);
+
+ // The farther away the opponent is, the more power is needed to
+ // bring the package to the target. More impact power means a higher
+ // initial velocity of the blobs, so the offset must be tweaked a bit.
+ offset_x *= 1.
+ + ( mem_curr->distance
+ / static_cast<double>(env.screenWidth)
+ * focusRate);
+
+
+ int32_t pos_x = ROUND(mem_curr->opX) + offset_x;
+
+
+ // If the resulting x position is not on the screen,
+ // the calculation already failed.
+ if (pos_x < 2) {
+ if (is_last) {
+ // But this must be taken...
+ pos_x = 2;
+ offset_x = pos_x - ROUND(mem_curr->opX);
+ } else
+ result = false;
+ } else if (pos_x > (env.screenWidth - 2)) {
+ if (is_last) {
+ // The same...
+ pos_x = env.screenWidth - 2;
+ offset_x = ROUND(mem_curr->opX) - pos_x;
+ } else
+ result = false;
+ }
+
+ // If the result is true, the area around pos_x must be checked
+ // to determine the y offset and whether to adapt offset_x
+ // any further or not.
+ if (result) {
+ bool found = false;
+ int32_t pos_y = global.surface[pos_x].load();
+
+ if (pos_y < (mem_curr->opY - std::abs(offset_x) + ai_level)) {
+ // This means more distance is needed. So we search for
+ // the next valid x position that goes down again or that
+ // doubles the x distance, whatever comes earlier.
+ int32_t mov_x = SIGN(global.wind) * -1;
+ int32_t max_x = pos_x + offset_x;
+
+ if (max_x < 2)
+ max_x = 2;
+ if (max_x > (env.screenWidth - 2))
+ max_x = (env.screenWidth - 2);
+
+ for ( ; !found && (pos_x != max_x); pos_x += mov_x) {
+ if (global.surface[pos_x].load() > pos_y) {
+ pos_x -= mov_x; // One step back
+ found = true;
+ }
+ }
+ } else if (pos_y > (mem_curr->opY + std::abs(offset_x) - ai_level)) {
+ // Here check the area towards the enemy. But going nearer
+ // than half the distance means an under-run, which is only
+ // allowed after an additional check.
+ int32_t mov_x = SIGN(global.wind);
+ int32_t max_x = pos_x - (offset_x / (RAND_AI_0P ? 1 : 2));
+ // Note: Yes, the RAND_AI_0P is the mentioned additional check. ;)
+ int32_t max_y = mem_curr->entry->opponent->tank->getBottom();
+
+ for ( ; !found && (pos_x != max_x); pos_x += mov_x) {
+ if (global.surface[pos_x].load() <= (max_y - ai_level))
+ found = true;
+ }
+ }
+
+ // adapt offset_x and offset_y now:
+ offset_x = pos_x - mem_curr->opX;
+ offset_y = global.surface[pos_x].load() - mem_curr->opY;
+ }
+ } // End of handling napalm
+
+ /* Weapon type 2: Shaped charges.
+ * These are easy. The shaped charge have an y radius of 1/20 of the
+ * x radius. Within this y radius of the centre no damage is done, so
+ * the area from this radius + 1 to + (ai_level * 2) is checked on
+ * either sides whether there is an y position that allows to actually
+ * catch the enemy in the blast. If not, this try is a failure.
+ * However, there is another trick shot here: Place a big shaped charge
+ * like the cutter at the right height behind a hill and blast the
+ * opponents tank through it.
+ */
+ else if ( (weap_idx >= SHAPED_CHARGE) && (weap_idx <= CUTTER) ) {
+ int32_t rad_y = weapon[weap_idx].radius / 20;
+ int32_t dist_x = rad_y + RAND_AI_1P;
+ int32_t max_dist = RAND_AI_0P
+ ? (dist_x * 2) + RAND_AI_1P // normal shot
+ : weapon[weap_idx].radius * 2 / 3; // trick shot
+ int32_t seek_y = (mem_curr->opY
+ + mem_curr->entry->opponent->tank->getBottom()) / 2;
+ int32_t left_x = mem_curr->opX - dist_x;
+ int32_t right_x = mem_curr->opX + dist_x;
+ int32_t left_y = left_x > 2
+ ? std::abs(global.surface[left_x].load()) : 0;
+ int32_t right_y = right_x < (env.screenWidth - 2)
+ ? std::abs(global.surface[right_x].load()) : 0;
+ bool go_left = (mem_curr->opX > x); // Which side to prefer
+ bool found_l = false;
+ bool found_r = false;
+
+ for ( ; !found_l && !found_r && (dist_x < max_dist); ++dist_x) {
+ left_x = mem_curr->opX - dist_x;
+ right_x = mem_curr->opX + dist_x;
+ left_y = left_x > 2
+ ? std::abs(global.surface[left_x].load()) : 0;
+ right_y = right_x < (env.screenWidth - 2)
+ ? std::abs(global.surface[right_x].load()) : 0;
+
+ if (std::abs(left_y - seek_y) <= rad_y)
+ found_l = true;
+ if (std::abs(right_y - seek_y) <= rad_y)
+ found_r = true;
+ }
+
+ // If both are valid, use what is preferred
+ if (found_l && found_r) {
+ if (go_left)
+ found_r = false;
+ else
+ found_l = false;
+ }
+
+ // if none is found but this is the last_try, take the simple
+ // distance to the preferred side
+ if (!found_l && !found_r) {
+ if (is_last) {
+ if ( (go_left && ((mem_curr->opX - rad_y - 1) > 1))
+ || ((mem_curr->opX + rad_y + 1) > (env.screenWidth - 2)) ) {
+ found_l = true;
+ left_x = mem_curr->opX - rad_y - 1;
+ left_y = global.surface[left_x].load();
+ } else {
+ found_r = true;
+ right_x = mem_curr->opX + rad_y + 1;
+ right_y = global.surface[right_x].load();
+ }
+ } else
+ result = false;
+ }
+
+ // If something is found, set the real offsets
+ if (found_l) {
+ offset_x = left_x - mem_curr->opX;
+ offset_y = left_y - mem_curr->opY;
+ } else if (found_r) {
+ offset_x = right_x - mem_curr->opX;
+ offset_y = right_y - mem_curr->opY;
+ }
+ } // End of handling shaped charges
+
+ /* Weapon type 3: Driller
+ * The driller must be placed above an enemy tank. This means it is
+ * only useful if the tank is buried, or the tank shall be sunk into
+ * the surface. However, there might be the possibility of an under
+ * shot if the tank is placed on a mountain side.
+ */
+ else if (DRILLER == weap_idx) {
+ int32_t rad_x = weapon[weap_idx].radius / 20;
+ int32_t pos_x = ROUND(mem_curr->opX);
+ int32_t pos_y = global.surface[pos_x].load();
+ int32_t max_dist = rad_x * 2 / 3;
+ int32_t min_y = mem_curr->opY - rad_x;
+ int32_t max_y = mem_curr->entry->opponent->tank->getBottom() + rad_x;
+ bool found_l = false;
+ bool found_r = false;
+
+ // If the direct coordinates are already in order, do not search
+ // further. Otherwise try to shift left and right.
+ if ( (pos_y > min_y) && (pos_y < max_y) ) {
+ for (int32_t off_x = 1
+ ; !found_l && !found_r && (off_x < max_dist)
+ ; ++off_x) {
+
+ int32_t left_x = pos_x - off_x;
+ int32_t right_x = pos_x + off_x;
+ int32_t left_y = left_x > 1
+ ? global.surface[left_x].load() : mem_curr->opY;
+ int32_t right_y = right_x < (env.screenWidth - 1)
+ ? global.surface[right_x].load() : mem_curr->opY;
+ if ( (left_y < min_y) || (left_y > max_y) ) {
+ found_l = true;
+ pos_x = left_x;
+ pos_y = left_y;
+ } else if ( (right_y < min_y) || (right_y > max_y) ) {
+ found_r = true;
+ pos_x = right_x;
+ pos_y = right_y;
+ }
+ }
+
+ // If this did not succeed, but it is the last_shot or
+ // the AI chooses to bury its opponent, use the opponents
+ // coordinates.
+ if (!found_l && !found_r) {
+ if (is_last || (RAND_AI_0N)) {
+ pos_x = mem_curr->opX;
+ pos_y = mem_curr->opY;
+ } else
+ result = false;
+ }
+ } // end of searching a position to use
+
+ // Set offsets if all is well
+ if (result) {
+ offset_x = pos_x - mem_curr->opX;
+ offset_y = pos_y - mem_curr->opY;
+ }
+ } // End of handling drillers
+
+ return result;
+}
+
+
+/** @brief Case d) Fire in non-boxed mode
+ *
+ * Note: calcAttack() has to make sure this method is only called if it is
+ * appropriate. No further checks are made within this method.
+ *
+ * @param[in] is_last If this is set to true, the method is forced to succeed.
+ * @param[in] allow_flip_shot If set to true, the bot is allowed to shoot in
+ * the opposite direction. On steel walls, this parameter is ignored.
+ * @return true if sane values could be found.
+**/
+bool AICore::calcStandard(bool is_last, bool allow_flip_shot)
+{
+ bool result = calcOffset(is_last);
+ needAim = true;
+
+
+ // --- 1) Get a basic raw angle firing directly ---
+ // ------------------------------------------------
+ bool wrapped = false;
+ double opX = mem_curr->opX + offset_x;
+ double opY = mem_curr->opY + offset_y;
+ // just some shortcuts
+ double dist_x = opX - x;
+ double dist_y = opY - y;
+ int32_t scrWidth = env.screenWidth;
+
+ // Do not start horizontally, this might happen quite often.
+ // If the opponent is above, limit the angle to somewhere between
+ // 60° and 75°. If it is below, limit angle between 20° and 35° and
+ // limit the angle between 40° and 55° if ~equal.
+ int32_t new_angle = GET_SAFE_ANGLE(dist_x, dist_y, 0);
+ int32_t ang_limit = (focusRate * static_cast<double>(rand() % 16));
+
+ if (dist_y < -100) /* above */ ang_limit += 60;
+ else if (dist_y > 100) /* below */ ang_limit += 20;
+ else /* equal */ ang_limit += 40;
+
+ // Apply limit:
+ if (new_angle < ( 90 + ang_limit)) new_angle = 90 + ang_limit;
+ if (new_angle > (270 - ang_limit)) new_angle = 270 - ang_limit;
+
+
+ // --- 2) Modify the beginning angle according to focusRate ---
+ // --- Keeping this more variable gives a larger range of ---
+ // --- starting points to go forth from. ---
+ // ------------------------------------------------------------
+ double angle_mod = (rand() % 13) * focusRate // useless: 0-2, deadly+1: 0-12
+ * ((rand() % 2) ? -1. : 1.);
+ while ( (std::abs(angle_mod > 0.))
+ && ( ( (new_angle > 180)
+ && ( ( (new_angle + angle_mod) < 190)
+ || ( (new_angle + angle_mod) > 260) ) )
+ || ( (new_angle < 180)
+ && ( ( (new_angle + angle_mod) > 170)
+ || ( (new_angle + angle_mod) < 100) ) ) ) ) {
+ angle_mod /= 2.;
+ }
+ new_angle += angle_mod;
+
+
+ // --- 3) If this is a wrap wall, check whether shooting ---
+ // --- through the wall is actually shorter. ---
+ // --- A note on allow_flip_shot: If shooting wrapped is ---
+ // --- shorter, the AI will chose it more often the ---
+ // --- higher the AI level. (80% for a deadly bot) ---
+ // --- The flipping is then a possibility to shoot non- ---
+ // --- wrapped again. ---
+ // ---------------------------------------------------------
+ if ( (WALL_WRAP == env.current_wallType) && RAND_AI_0P ) {
+ int32_t wrapDist = opX > x
+ ? x + scrWidth - 3 - opX
+ : (scrWidth - x - 3 + opX) * -1;
+
+ if (std::abs(wrapDist) < std::abs(dist_x)) {
+ wrapped = true;
+ dist_x = wrapDist;
+ new_angle = FLIP_ANGLE(new_angle);
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Flipping through wrap wall at %d°",
+ GET_DISP_ANGLE(new_angle))
+ }
+ }
+
+
+ // --- 4) Switch sides if possible and allowed ---
+ // -----------------------------------------------
+ if ( (WALL_STEEL != env.current_wallType)
+ && allow_flip_shot
+ && (rand() % ( (ai_level + 3) / 2)) ) {
+ new_angle = FLIP_ANGLE(new_angle);
+
+ // The result of this flip is different for each wall type
+ if (WALL_RUBBER == env.current_wallType) {
+ dist_x += opX > x
+ ? (x - 1.) + ( (x - 1.) / BOUNCE_CHANGE)
+ : (scrWidth - opX - 2.)
+ + ((scrWidth - opX - 2.) / BOUNCE_CHANGE);
+ } else if (WALL_SPRING == env.current_wallType) {
+ dist_x += opX > x
+ ? (x - 1.) + ( (x - 1.) / SPRING_CHANGE)
+ : (scrWidth - opX - 2.)
+ + ((scrWidth - opX - 2.) / SPRING_CHANGE);
+ } else if (WALL_WRAP == env.current_wallType) {
+ if (wrapped)
+ // Shoot directly again
+ dist_x = opX - x;
+ else
+ dist_x = opX > x
+ ? x + scrWidth - 3 - opX
+ : (scrWidth - x - 3 + opX) * -1;
+ }
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Flipping %s wall at %d°",
+ wrapped ? "back from" : "towards",
+ GET_DISP_ANGLE(new_angle))
+
+ // wrap / unwrap
+ wrapped = !wrapped;
+ }
+
+
+ // --- 5) Adjust angle giving shooting clearance ---
+ // --- Here the clearance is either needed to reach ---
+ // --- the next wall or half the distance to the ---
+ // --- selected opponent. ---
+ // ----------------------------------------------------
+ int32_t clearance = std::abs(dist_x / 2);
+ int32_t old_angle = new_angle;
+ int32_t max_drift = (ai_level + 1) / 2; // [1;3]
+ bool crashed = false;
+
+ if (wrapped)
+ // wrapped shots need clearance to the wall away from the opponent:
+ clearance = opX > x ? x - 2 : env.screenWidth - x - 2;
+
+ while ( (new_angle < (180 - max_drift))
+ && !tank->shootClearance(new_angle, clearance, crashed)
+ && !crashed )
+ ++new_angle;
+ while ( (new_angle > (180 + max_drift))
+ && !tank->shootClearance(new_angle, clearance, crashed)
+ && !crashed)
+ --new_angle;
+
+
+ // --- 6) Revert to half the distance between both angles ---
+ // --- if no full clearance is possible. ---
+ // --- An attempt to remove possible obstacles might be ---
+ // --- triggered here. ---
+ // ----------------------------------------------------------
+ if ( ( new_angle >= (180 - max_drift) )
+ && ( new_angle <= (180 + max_drift) ) ) {
+ new_angle = (new_angle + old_angle) / 2;
+
+ // If this is the last chance, try to clear the obstacle.
+ // Alternatively a bot with high pain sensitivity might chose
+ // to remove the obstacle earlier. The idea here is, that the
+ // bot does not want to "piss off" its opponent before the
+ // obstacle is removed.
+ // However, if there is already a setup with a positive
+ // score, revert to that.
+ if ( (best_setup_score <= 0)
+ && (is_last
+ || ( (rand() % (ai_level * 20))
+ < (player->painSensitivity * ai_level * 5) ) ) ) {
+ /* Range is from Useless and pain resistant (0.1) to
+ * (Deadly + 1) and very pain sensitive: [max rand value]
+ * Useless : 0.1 * 1 * 5 = 0.5 [ 20]
+ * Deadly + 1 : 3.0 * 6 * 5 = 90.0 [120]
+ */
+ isBlocked = true;
+ result = useFreeingTool(false, is_last);
+ curr_angle = new_angle;
+
+ // Try not to bomb the ceiling:
+ if ( (curr_angle >= 150) && (curr_angle <= 210)) {
+ flattenCurrAng();
+
+ // Write back curr_ang, or it gets overwritten below.
+ new_angle = curr_angle;
+ }
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Obstacle detected, trying to clear path using %s",
+ weap_idx < WEAPONS
+ ? weapon[weap_idx].getName()
+ : item[weap_idx - WEAPONS].getName())
+ } else
+ // This did not work out
+ result = false;
+ } // End of being blocked
+
+
+ // --- 7) Find necessary power ---
+ // --- This is just an estimation on possible ---
+ // --- "air time" of the projectile ---
+ // ----------------------------------------------
+ if (result) {
+ // As there is nothing that can fail now, the new
+ // angle is the one to go with:
+ curr_angle = new_angle;
+
+ double slope_x = env.slope[curr_angle][0];
+ double slope_y = env.slope[curr_angle][1];
+ double rawTime = slope_x != 0.
+ ? dist_x / slope_x
+ : dist_x / 0.00000001; // 180° should be impossible though.
+
+ // lower target, less power.
+ // If the target is above, the projectile hits earlier than
+ // on lower targets, where the projectile has to fall down there.
+ double airTime = std::abs(rawTime) + (
+ dist_y * slope_y * env.gravity
+ * env.FPS_mod * 2.0);
+
+ // Less airTime doesn't necessarily mean less power
+ // Horizontal firing means more power needed even though
+ // air time is minimised.
+ curr_power = ROUNDu(std::sqrt(
+ airTime * env.gravity * env.FPS_mod)
+ * static_cast<double>(env.frames_per_second));
+
+ // Power modification according to the bots focus rate
+ // This helps having slightly different starting powers to
+ // begin aiming with.
+ curr_power += focusRate * static_cast<double>(rand() % 51)
+ * (rand() % 2 ? -1. : 1.);
+ // With a focus rate of [0.166;1] this results in a modification
+ // between [-8.3;8.3] and [-50;50].
+
+ // Be sure power is in the valid range:
+ if (curr_power > MAX_POWER) curr_power = MAX_POWER;
+ if (curr_power < MIN_POWER) curr_power = MIN_POWER;
+
+ // Power is only available in a stepping of five
+ curr_power -= curr_power % 5;
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Firing %s at %d° with power %d",
+ weapon[weap_idx].getName(),
+ GET_DISP_ANGLE(curr_angle), curr_power)
+ } // End of having a result
+
+ // Write back values and stop aiming if this is a blocked path freeing attempt
+ if (result && isBlocked) {
+ sanitizeCurr();
+ angle = curr_angle;
+ power = curr_power;
+ needAim = false;
+ }
+
+
+ return result;
+}
+
+
+/** @brief Case b) The tank is buried and an appropriate tool is chosen
+ *
+ * Note: This means that it must be checked whether this is an appropriate
+ * tool or not. Here the method might fail if it isn't suitable.
+ *
+ * @param[in] is_last If this is set to true, the method is forced to succeed.
+ * @return true if sane values could be found.
+**/
+bool AICore::calcUnbury(bool is_last)
+{
+ DEBUG_LOG_AIM(player->getName(), "I am buried! (%d >= %d)",
+ buried, BURIED_LEVEL)
+
+ // Suitable tool?
+ if ( ( (RIOT_BOMB <= weap_curr->type) // Clear freeing tool
+ && (RIOT_BLAST >= weap_curr->type) ) // that clears dirt
+ || ( ( (SHAPED_CHARGE > weap_curr->type) // shaped charges can not
+ || (CUTTER < weap_curr->type) ) // be used, and neither can
+ && (DRILLER != weap_curr->type) // the driller, to self
+ && weap_curr->kamikaze) ) { // destruct while buried
+
+ // To not blast away an obstacle towards a wall with no
+ // enemies behind it, count how many enemies are on each
+ // side, first:
+ opEntry_t* op = mem_head;
+ int32_t op_left = 0;
+ int32_t op_right = 0;
+ while (op) {
+ if ( op->alive && !op->onSameTeam ) {
+ if (op->opX < tank->x)
+ op_left++;
+ else
+ op_right++;
+ }
+ op = op->next;
+ }
+
+ // Determine starting values according to which side
+ // is buried stronger, and where the opponents are:
+ bool go_left = true;
+
+ // It is better to go right instead of left if:
+ // a) more enemies are on the right
+ // b) the count is equal but the right side is more buried or
+ // c) the current favourite target is on the right and the left
+ // is not that much more buried. (depends on AI level)
+ if ( (op_right > op_left) // a)
+ || ( (op_right == op_left) && (buried_r > buried_l) ) // b)
+ || ( (mem_curr->opX > x)
+ && (std::abs(buried_r - buried_l) < ai_level) ) ) { // c)
+
+ go_left = false;
+ }
+
+ // find a good starting angle where the obstacle begins:
+ int32_t dist = ai_level * (player->defensive + 3.) * 2;
+ bool crashed = false;
+ curr_angle = 180;
+
+ if (go_left) {
+ while ( (curr_angle < 250)
+ && ( tank->shootClearance(curr_angle, dist, crashed)
+ || !crashed) )
+ ++curr_angle;
+ } else {
+ while ( (curr_angle > 110)
+ && ( tank->shootClearance(curr_angle, dist, crashed)
+ || !crashed) )
+ --curr_angle;
+ }
+
+ // Add a variant to the angle:
+ curr_angle += ( (rand() % 21) - 10) / ai_level;
+
+ // If riot charges are used, 45° is the lower border.
+ if ( (RIOT_BOMB <= weap_curr->type)
+ && (RIOT_BLAST >= weap_curr->type) ) {
+ if (curr_angle < 135) curr_angle = 135;
+ if (curr_angle > 225) curr_angle = 225;
+ }
+
+ // Be sure current values are sane:
+ sanitizeCurr();
+
+ angle = curr_angle;
+ power = curr_power;
+ needAim = false; // Already done here!
+ isBlocked = true;
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Freeing myself using %s at %d° with power %d",
+ weapon[weap_idx].getName(),
+ GET_DISP_ANGLE(angle), power)
+
+ return true;
+ } // End of having selected an appropriate tool.
+
+ // The only non-self-destruct way to use a weapon for freeing
+ // one self is the shaped charge:
+ if ( ( (SHAPED_CHARGE <= weap_curr->type)
+ && (CUTTER >= weap_curr->type) )
+ || ( DRILLER == weap_curr->type ) ) {
+ curr_angle = 180;
+ curr_power = 10 + RAND_AI_0P;
+
+ sanitizeCurr();
+
+ angle = curr_angle;
+ power = curr_power;
+ needAim = false; // Already done here!
+ isBlocked = true;
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Freeing myself using %s at %d° with power %d",
+ weapon[weap_idx].getName(),
+ GET_DISP_ANGLE(angle), power)
+
+ return true;
+ }
+
+ // emergency values if this is our last try:
+ if (is_last) {
+ if (!useFreeingTool(true, true))
+ useWeapon(SML_MIS);
+ curr_angle = buried_l > buried_r ? 200 : 100
+ + (rand() % 61);
+ curr_power = 500 + (rand() % 501);
+
+ sanitizeCurr();
+
+ angle = curr_angle;
+ power = curr_power;
+ needAim = false; // Already done here!
+ isBlocked = true;
+
+ DEBUG_LOG_AIM(player->getName(),
+ "(last!) Freeing myself using %s at %d° with power %d",
+ weapon[weap_idx].getName(),
+ GET_DISP_ANGLE(angle), power)
+
+ return true;
+ }
+
+ // Otherwise this has failed
+
+ DEBUG_LOG_AIM(player->getName(), "Nothing suitable selected (%s)",
+ item_curr
+ ? item[weap_idx - WEAPONS].getName()
+ : weap_curr
+ ? weapon[weap_idx].getName() : "NOTHING")
+
+ return false;
+}
+
+
+/// @return false if the initialization of this instance failed
+bool AICore::can_work() const
+{
+ return canWork;
+}
+
+
+/** @brief check the currently set item list and update its organization
+ *
+ * This checks every item compared to the currently selected
+ * target and sets a score on usability. The list is then
+ * sorted by score in descending order.
+**/
+void AICore::checkItemMem()
+{
+ item_curr = item_head;
+
+ DEBUG_LOG_AIM(player->getName(), "Starting to check item memory", 0)
+
+ while (item_curr) {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ // Update score
+ updateItemScore(item_curr);
+
+ // Advance
+ item_curr = item_curr->next;
+ }
+
+ // Eventually sort the list
+ sort_entries(&item_head);
+}
+
+
+/** @brief check the currently set memory and update its organization
+ *
+ * This looks into each entry whether there is new damage for
+ * this turn, and updates the score.
+ * After the score updates, the list is reordered, so the entry
+ * with the highest score becomes mem_head, and the list ends with
+ * the lowest scored entry.
+**/
+void AICore::checkOppMem()
+{
+ mem_curr = mem_head;
+
+ DEBUG_LOG_AIM(player->getName(), "Starting to check opponent memory", 0)
+
+ while (mem_curr) {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ // Update score
+ updateOppScore(mem_curr);
+
+ // Advance
+ mem_curr = mem_curr->next;
+ }
+
+ // Do we have a new revengee?
+ if (revengee && (player->revenge != revengee->opponent))
+ player->revenge = revengee->opponent;
+
+ // Not a single one?
+ if (nullptr == revengee)
+ player->revenge = nullptr;
+
+ // Eventually sort the list
+ sort_entries(&mem_head);
+}
+
+
+/** @brief check the currently set weapon list and update its organization
+ *
+ * This checks every weapon compared to the currently selected
+ * target and sets a score on usability. The list is then
+ * sorted by score in descending order.
+**/
+void AICore::checkWeapMem()
+{
+ weap_curr = weap_head;
+
+ DEBUG_LOG_AIM(player->getName(), "Starting to check weapon memory", 0)
+
+ while (weap_curr) {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ // Update score
+ updateWeapScore(weap_curr);
+
+ // Advance
+ weap_curr = weap_curr->next;
+ }
+
+ // Eventually sort the list
+ sort_entries(&weap_head);
+}
+
+
+/// @brief destroy all memory chains
+void AICore::destroy()
+{
+ while (item_head) {
+ item_curr = item_head;
+ item_head = item_curr->next;
+ delete item_curr;
+ }
+ item_curr = nullptr;
+ item_last = nullptr;
+
+ while (mem_head) {
+ mem_curr = mem_head;
+ mem_head = mem_curr->next;
+ delete mem_curr;
+ }
+ mem_curr = nullptr;
+ mem_last = nullptr;
+
+ while (weap_head) {
+ weap_curr = weap_head;
+ weap_head = weap_curr->next;
+ delete weap_curr;
+ }
+ weap_curr = nullptr;
+ weap_last = nullptr;
+}
+
+
+/// @brief Forbid AICore to create FLOATTEXT instances
+void AICore::forbidText()
+{
+ textAllowed.store(false);
+}
+
+
+/// @brief Get a string describing the given AI @a level
+const char* AICore::getLevelName (int32_t level) const
+{
+ switch (level) {
+ case 0:
+ return "HUMAN (!!!)";
+ break;
+ case 1:
+ return "Useless";
+ break;
+ case 2:
+ return "Guesser";
+ break;
+ case 3:
+ return "Range Finder";
+ break;
+ case 4:
+ return "Targetter";
+ break;
+ case 5:
+ return "Deadly";
+ break;
+ case 6:
+ return "Deadly + 1";
+ break;
+ default:
+ break;
+ }
+ return "OUT OF RANGE (!!!)";
+}
+
+
+/** @brief get the set players memory and check it
+ *
+ * This method fetches all sOpponent entries from the handled player,
+ * and fills the item and weapon chains with the stock count and weapon
+ * preferences.
+ *
+ * The scores are not calculated, and the list is not sorted.
+ * Therefore checkOppMem() must be called first when the thread
+ * starts in operator().
+ *
+ * @return true if the memory could be copied
+**/
+bool AICore::getMemory()
+{
+ assert(player && "ERROR: getMemory() reached with nullptr player?");
+ assert( tank && "ERROR: getMemory() reached with nullptr tank?");
+ assert(!tank->destroy && "ERROR: getMemory() reached with destroyed tank?");
+
+
+ /// === 1) Copy item information ===
+
+ int32_t idx = 0;
+ int32_t pref = 0;
+
+ item_curr = item_head;
+ item_last = nullptr;
+
+ assert(item_head && "ERROR: getMemory() called without item memory set up!");
+
+ if (!item_head)
+ return false;
+
+ do {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ if ( -1 < (pref = player->getItemPref(idx) ) ) {
+ item_curr->amount = player->ni[idx];
+ item_curr->preference = pref;
+ item_curr->selectable = item[idx].selectable;
+ item_curr->type = idx;
+
+ // The kamikaze value is only pre-set to true for vengeance
+ // items, all other must be determined if the bot really
+ // chooses to self-destruct.
+ if ( (item_curr->type >= ITEM_VENGEANCE)
+ && (item_curr->type <= ITEM_FATAL_FURY) )
+ item_curr->kamikaze = true;
+ else
+ item_curr->kamikaze = false;
+
+ // Advance current
+ item_curr = item_curr->next;
+ }
+ } while ((++idx < ITEMS) && item_curr);
+
+
+ /// === 2) Copy opponents information ===
+
+ idx = 0;
+ int32_t jcnt = 0; // Jedi count
+ int32_t scnt = 0; // Sith count;
+ double dail = ai_level_d; // [d]ouble [ai]_[l]evel
+ sOpponent* opp = nullptr;
+
+ mem_curr = mem_head;
+ mem_last = nullptr;
+
+ assert(mem_head && "ERROR: getMemory() called without opponents memory set up!");
+
+ if (!mem_head)
+ return false;
+
+ do {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ mem_curr->alive = false; // must be confirmed
+
+ if ( (opp = player->getOppMem(idx) ) ) {
+ TANK* oppTank = nullptr;
+
+ mem_curr->attempts = 0;
+ mem_curr->entry = opp;
+ mem_curr->revengeDone = false;
+
+ if ( opp->opponent
+ && opp->opponent->tank
+ && !opp->opponent->tank->destroy)
+ oppTank = opp->opponent->tank;
+
+ // The other values depend on whether an active tank was found:
+ if (oppTank) {
+ mem_curr->is_buried = oppTank->howBuried(&mem_curr->buried_l,
+ &mem_curr->buried_r)
+ > BURIED_LEVEL ? true : false;
+ mem_curr->hasRepulse = oppTank->hasRepulsorActivated();
+ mem_curr->opLife = oppTank->l + oppTank->sh;
+ mem_curr->opX = oppTank->x;
+ mem_curr->opY = oppTank->y;
+ mem_curr->diffLife = currLife - mem_curr->opLife;
+ mem_curr->distance = FABSDISTANCE2(x, y, mem_curr->opX, mem_curr->opY);
+
+ if (oppTank->l > 0)
+ mem_curr->alive = true;
+
+ // Is this the last one? then set as default best choice:
+ if (last_opp == opp)
+ best_setup_mem = mem_curr;
+ } else {
+ // Reset some values if there is no tank
+ mem_curr->opLife = 0.;
+ mem_curr->diffLife = currLife;
+ mem_curr->distance = env.screenWidth * env.screenHeight;
+ }
+
+ // Some calculations can be cut short if this is ourselves:
+ if (opp->opponent == player) {
+ mem_curr->onSameTeam = true;
+ mem_curr->team_mod = ( ( (player->painSensitivity + 1.) // [1;4]
+ * (player->selfPreservation + 1.) ) // [1;4]
+ + 2.) // [ 3 ; 18]
+ / -1.; // [-1.5; -9]
+ } else {
+ mem_curr->onSameTeam = ( (TEAM_NEUTRAL != player->team)
+ && (opp->opponent->team == player->team) );
+
+ // team_mod is a multiplier reflecting the general behaviour
+ // against the own and other teams.
+ if ( TEAM_JEDI == player->team ) {
+ // Jedi go strongly for Sith and protect their team
+ if (mem_curr->onSameTeam)
+ mem_curr->team_mod = (2. + dail) / -2.; // [-1.5; -4.]
+ else if ( TEAM_SITH == opp->opponent->team ) {
+ mem_curr->team_mod = 2. * ai_level; // [2;12]
+ } else
+ mem_curr->team_mod = ai_level; // [1; 6]
+ } else if ( TEAM_SITH == player->team ) {
+ // Sith go for everyone, slightly favouring Jedi and do
+ // not care that much hitting their own team members.
+ if (mem_curr->onSameTeam)
+ mem_curr->team_mod = (2. + dail) / -3.; // [-1; -2.66]
+ else if ( TEAM_JEDI == opp->opponent->team ) {
+ mem_curr->team_mod = 1.25 * ai_level; // [1.25;7.5]
+ } else
+ mem_curr->team_mod = ai_level; // [1 ;6 ]
+ } else {
+ // Neutrals go slightly more for the teams, and less for
+ // other neutrals. This is supposed to reflect the fact
+ // that Jedi and Sith have friends with them helping them
+ // out. Neutrals are all alone and considered less dangerous.
+ if ( TEAM_NEUTRAL == opp->opponent->team )
+ mem_curr->team_mod = 1. + (dail / 2.); // => [1.5;4.]
+ else {
+ mem_curr->team_mod = .5 + dail; // => [1.5;6.5]
+ if (TEAM_JEDI == opp->opponent->team)
+ ++jcnt;
+ else
+ ++scnt;
+ }
+ } // End of team_mod determination
+ } // End of opponent handling
+
+ // Advance current
+ mem_curr = mem_curr->next;
+ }
+ ++idx;
+ } while (opp && mem_curr);
+
+ // If this is a neutral player, it has counted jedi and sith. This is
+ // done to raise the team_mod whenever any of these teams sport more
+ // than one remaining tank.
+ if ( (TEAM_NEUTRAL == player->team) && ( (jcnt > 1) || (scnt > 1) ) ) {
+ double j_mod = dail / 10. * static_cast<double>(jcnt - 1);
+ double s_mod = dail / 10. * static_cast<double>(scnt - 1);
+ mem_curr = mem_head;
+
+ while (mem_curr) {
+
+ if ( (TEAM_JEDI == mem_curr->entry->opponent->team)
+ && (jcnt > 1) )
+ mem_curr->team_mod += j_mod;
+ else if ( (TEAM_SITH == mem_curr->entry->opponent->team)
+ && (scnt > 1) )
+ mem_curr->team_mod += s_mod;
+
+ mem_curr = mem_curr->next;
+ }
+ }
+
+ /// === 3) Copy weapon information ===
+
+ double dmgMod = player->damageMultiplier;
+ idx = 0;
+ pref = 0;
+
+ weap_curr = weap_head;
+ weap_last = nullptr;
+
+ assert(weap_head && "ERROR: getMemory() called without weapon memory set up!");
+
+ if (!weap_head)
+ return false;
+
+ // Reset blast values, they have to be found anew:
+ blast_min = 0.;
+ blast_med = 0.;
+ blast_big = 0.;
+ blast_max = 0.;
+
+ do {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ if ( -1 < (pref = player->getWeapPref(idx) ) ) {
+ int32_t subMun = weapon[idx].submunition; // short-cut
+ double damage = weapon[idx].damage * dmgMod
+ * weapon[idx].getDelayDiv();
+
+ // === Dirt weapons have a "damage" based on their radius ===
+ if ( (DIRT_BALL <= idx)
+ && (SMALL_DIRT_SPREAD >= idx) )
+ damage = weapon[idx].radius * (1.5 + player->defensive);
+
+ weap_curr->amount = player->nm[idx];
+
+ // To be able to track weapons that trigger other sub munitions,
+ // their configuration must be remembered, too:
+ weap_curr->subMunCount= weapon[idx].numSubmunitions;
+ weap_curr->subMunType = subMun;
+ // Non-spread weapons have their spread value set to 1. To catch
+ // future cases where this might be different, the value is
+ // stored and sanitized here:
+ weap_curr->spread = weapon[idx].spread > 0 ? weapon[idx].spread : 1;
+ weap_curr->dmgCluster = weapon[idx].numSubmunitions > 0
+ ? dmgMod
+ * static_cast<double>(weapon[subMun].damage)
+ * static_cast<double>(weapon[idx].numSubmunitions)
+ : 0.;
+ weap_curr->dmgSingle = damage;
+ weap_curr->dmgSpread = damage
+ * static_cast<double>(weap_curr->spread);
+ weap_curr->preference = pref;
+ weap_curr->radius = weapon[idx].radius;
+ weap_curr->type = idx;
+
+ // Save blast value for opponents score calculation
+ double ds = weap_curr->dmgSingle;
+ if (SML_MIS == idx) {
+ if (blast_min < ds) blast_min = ds;
+ if (blast_med < ds) blast_med = ds;
+ if (blast_big < ds) blast_big = ds;
+ if (blast_max < ds) blast_max = ds;
+ } else if ( ( (MED_MIS == idx) || (LRG_MIS == idx) )
+ && (weap_curr->amount > 0) ) {
+ if (blast_med < ds) blast_med = ds;
+ if (blast_big < ds) blast_big = ds;
+ if (blast_max < ds) blast_max = ds;
+ } else if ( ( (SML_NUKE == idx) || (NUKE == idx) )
+ && (weap_curr->amount > 0) ) {
+ if (blast_big < ds) blast_big = ds;
+ if (blast_max < ds) blast_max = ds;
+ } else if ( ( (DTH_HEAD == idx) )
+ && (weap_curr->amount > 0)
+ && (blast_max < ds) )
+ blast_max = ds;
+
+ // If this is the last weapon used, store as default best choice
+ if (last_weap == idx)
+ best_setup_weap = weap_curr;
+
+ // Advance current
+ weap_curr = weap_curr->next;
+ }
+ } while ((++idx < WEAPONS) && weap_curr);
+
+ return true;
+}
+
+
+/// @brief flatten curr_ang if a riot bom is chosen to clear the path
+/// Note: No checks about being blocked, call it when appropriate. Only
+/// the weapon is checked against riot bombs.
+void AICore::flattenCurrAng()
+{
+ if ( (RIOT_BOMB <= weap_curr->type)
+ && (HVY_RIOT_BOMB >= weap_curr->type) ) {
+ int32_t div = 1 + ( (RAND_AI_1P + 2) / 2); // [2;4]
+ // Minimum : 1 + ( (0 + 2) / 2 ) = 1 + ( 2 / 2 ) = 1 + 1 = 2
+ // Useless: 1 + ( (1 + 2) / 2 ) = 1 + ( 3 / 2 ) = 1 + 1 = 2
+ // Deadly+1: 1 + ( (5 + 2) / 2 ) = 1 + ( 7 / 2 ) = 1 + 3 = 4
+ if (curr_angle > 180)
+ curr_angle += (270 - curr_angle) / div;
+ else
+ curr_angle -= (curr_angle - 90) / div;
+ }
+}
+
+
+/// @brief adapt @a ang_mod and @a pow_mod when a shot using them crashed.
+void AICore::fixCrashed (int32_t &ang_mod, int32_t &pow_mod)
+{
+ // Unless a hill was detected, the angle mod must not be greater than 1
+ if (!hill_detected && (ang_mod > 1))
+ ang_mod = 1;
+
+ // Use a unified angle or there has to be an if/else for the same code
+ int32_t fix_ang = curr_angle > 180 ? FLIP_ANGLE(curr_angle) : curr_angle;
+
+ // The following rules must be applied:
+ // 1) If boxed mode is on, the angle must not be higher than 150°
+ // or it has to be reduced more.
+ // 2) Otherwise lower the angle further away from 45° (aka 135° here) if
+ // it is already low.
+ // If it isn't, it is raised anyway.
+ // 3) If none of the above apply, assume the angle to be in order if it
+ // is between 130 and 140, which is 45° +/- 5°.
+ if (env.isBoxed && (fix_ang > 150)) { // 1)
+ ang_mod *= -1 * RAND_AI_1P;
+ // Do not overdo it:
+ while (std::abs(ang_mod) > (ai_level + 2))
+ ang_mod /= 2;
+ } else if ( (fix_ang > 100) && (fix_ang <= 135) ) // 2)
+ ang_mod *= -1;
+ else if ( (fix_ang > 130) && (fix_ang < 140) ) // 3)
+ ang_mod = 0; // None needed
+
+ // now flip ang_mod if the angle was flipped:
+ ang_mod *= curr_angle > 180 ? -1 : 1;
+
+ // The power must be reduced if it is greater than the x distance.
+ int32_t pow_diff = curr_power - std::abs(mem_curr->opX - x);
+ if (pow_diff > 0) {
+ pow_mod = std::abs(pow_mod) * -1;
+ // And strengthen the power reduction more if
+ // the power is more than 50% over the distance
+ if ( pow_diff >= (std::abs(mem_curr->opX - x) / 2) )
+ pow_mod *= 2 * RAND_AI_1P;
+ }
+}
+
+
+/// @brief Try to adapt @a ang_mod and @a pow_mod according to the current
+/// overshoot and where the best hit landed.
+void AICore::fixOvershoot(int32_t& ang_mod, int32_t& pow_mod, int32_t hit_score)
+{
+ // Here are some more possible (sub) situations to consider:
+ // 1) The current score is at least better than the last.
+ // This can happen if the shot does no longer hit team mates.
+ // The important situation is, if the overshoot is very small
+ // and a new best score is achieved. The bigger the weapon, the
+ // higher the probability that this might be the case.
+ // 2) Both the current and the last overshoot were negative, the
+ // angle was optimized towards 45° and the power was raised.
+ // Having a worse overshoot then can happen if the gun was
+ // lowered and the shot crashes into the side of a hill or
+ // mountain.
+ // The angle must then be brought towards 180° more than the
+ // last angle modification brought it away from it.
+ // 3) The current score is worse than the last score.
+ // a) The last score was better than the one before.
+ // The modifications might have been too strong, try
+ // values between the two.
+ // b) That was two worse tries in a row.
+ // The direction was wrong, and the last modifications
+ // must be reverted and strengthened by the current set
+ // mods.
+ // 4) No last score or the same, just adapt the mods according to
+ // whether the shot was too short or too long.
+
+
+ bool angle_was_optimized = false;
+ if ( ( (curr_angle > 180) && (curr_angle <= 235) && (last_ang_mod > 0) )
+ || ( (curr_angle < 180) && (curr_angle >= 135) && (last_ang_mod < 0) ) )
+ angle_was_optimized = true; // Optimized towards 45° on its side
+
+ // reset hill detection if the current overshoot isn't short
+ if ( (curr_overshoot > 0) || (hit_score > 0) )
+ hill_detected = false;
+
+ if (last_score && (hit_score > 0) && (hit_score > last_score)) {
+
+ // 1) Better score with worse overshoot.
+ // Here a best score might happen. But this is only noteworthy
+ // if the hit_score is positive. Otherwise it would simply
+ // mean that less damage was done (team and others) and that is
+ // hardly anything to note down.
+ // Note: if this is a new best score, some adaptation has
+ // already been made in aim().
+ if (hit_score < best_score) {
+ // Only adapt the signedness of the mods and note that
+ // the aiming is not there, yet:
+ DEBUG_LOG_AIM(player->getName(),
+ " => Better score %d [%d last]",
+ hit_score, last_score)
+ ang_mod = std::abs(ang_mod) * SIGN(last_ang_mod);
+ pow_mod = std::abs(pow_mod) * SIGN(curr_overshoot) * -1;
+ }
+
+ // At least better than the last
+ last_was_better = true;
+ hill_detected = false;
+ } else if ( (hit_score <= 0)
+ && (last_overshoot < 0)
+ && (curr_overshoot <= last_overshoot) // Keeps being too short
+ && angle_was_optimized) {
+
+ // 2) Assume a hill in the path
+ pow_mod = (std::abs(pow_mod) + std::abs(last_pow_mod)) / 2; // raise it
+ ang_mod = static_cast<double>(std::abs(last_ang_mod) + std::abs(ang_mod))
+ * ai_over_mod * SIGNd(last_ang_mod) * -1.;
+ // Note: This accumulates the last and the current angle modification,
+ // strengthens depending on AI level and ensures it has the
+ // opposite direction from the last modification.
+
+ // Make sure the new ang_mod really gets the angle upwards:
+ if ( SIGN(curr_angle - 180) == SIGN(ang_mod) )
+ ang_mod *= -1;
+
+ // Make sure the new ang_mod doesn't make the angle to flip over:
+ if ( (curr_angle > 180) && ((curr_angle + ang_mod) <= 180) )
+ ang_mod = 181 - curr_angle;
+ if ( (curr_angle < 180) && ((curr_angle + ang_mod) >= 180) )
+ ang_mod = curr_angle - 181;
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Assuming hill crash, reverting ang_mod to %d",
+ ang_mod)
+
+ last_was_better = false; // false, so this change won't get directly
+ // reverted again.
+ hill_detected = true;
+ } else if (last_score && (last_score > hit_score) ) {
+ // 3) Wrong direction!
+ if (last_was_better) {
+
+ DEBUG_LOG_AIM(player->getName(),
+ " => Worse score %d [%d was better]",
+ hit_score, last_score)
+
+ // Try a mod in between the two
+ if (std::abs(last_ang_mod) > 1)
+ ang_mod = -1 * (last_ang_mod / 2);
+ else
+ ang_mod = -1 * SIGN(last_ang_mod);
+ if (std::abs(last_pow_mod) > 9)
+ pow_mod = -1 * (last_pow_mod / 2);
+ else
+ pow_mod = -5 * SIGN(last_pow_mod);
+ } else {
+ // b) Revert and go in the opposite direction:
+
+ DEBUG_LOG_AIM(player->getName(),
+ " => Worse score %d [%d was worse]",
+ hit_score, last_score)
+
+ // If the last was reverted already, strengthen the
+ // move in the opposite direction
+ if (last_reverted) {
+ // First make positive and strengthen
+ ang_mod = std::abs(ang_mod) * (RAND_AI_0P + 1);
+ pow_mod = std::abs(pow_mod) * (RAND_AI_1P + 1);
+ }
+
+ // Then strengthen the last values by the current and revert:
+ ang_mod = -1 * ( last_ang_mod
+ + (SIGN(last_ang_mod) * ang_mod) );
+ pow_mod = -1 * ( last_pow_mod
+ + (SIGN(last_pow_mod) * pow_mod) );
+
+ // Adapt by overshoot: (Yes, still necessary)
+ if (SIGN(curr_overshoot) == SIGN(pow_mod))
+ pow_mod *= -1;
+ }
+
+ last_was_better = false;
+ } else {
+ // 4) Just do the set mod according to overshoot
+
+ DEBUG_LOG_AIM(player->getName(), "=> Same score %d, overshoot %d",
+ hit_score, curr_overshoot)
+
+ // Put in some limits for the angle according to where the opponent is
+ int32_t ang_limit = (focusRate * static_cast<double>(rand() % 16));
+ int32_t dist_y = mem_curr->opY - y;
+ if (dist_y < -100) /* above */ ang_limit += 60;
+ else if (dist_y > 100) /* below */ ang_limit += 20;
+ else /* equal */ ang_limit += 40;
+
+ // If a hill was detected, do not modify angle more than 1
+ if (hill_detected && (ang_mod > 1))
+ ang_mod = 1;
+
+ if ( ( (curr_angle <= 180) && (curr_angle > (90 + ang_limit)) )
+ || ( (curr_angle > (270 - ang_limit) ) ) )
+ ang_mod *= -1;
+
+ // Adapt pow_mod by overshoot
+ pow_mod = std::abs(pow_mod) * SIGN(curr_overshoot) * -1;
+
+ last_was_better = false;
+ }
+}
+
+
+/// @brief adapt @a ang_mod and @a pow_mod when a shot using them did not
+/// finish.
+void AICore::fixUnfinished (int32_t &ang_mod, int32_t &pow_mod)
+{
+ // Put in some limits for the angle according to where the opponent is
+ int32_t ang_limit = (focusRate * static_cast<double>(rand() % 16));
+ int32_t dist_y = mem_curr->opY - y;
+ if (dist_y < -100) /* above */ ang_limit += 60;
+ else if (dist_y > 100) /* below */ ang_limit += 20;
+ else /* equal */ ang_limit += 40;
+
+ // If a hill was detected on the path, do not alter the angle
+ // more than by 1
+ if (hill_detected && (ang_mod > 1))
+ ang_mod = 1;
+
+ // If the angle is too steep to the right, or too flat
+ // to the left, make ang_mod negative:
+ // Note: If the shot is to the right, it is in the range
+ // 90-180 and going down needs a negative mod.
+ // If going to the left it needs a positive mod to go
+ // down as it is in the range 180 - 270.
+
+ if ( ( (curr_angle <= 180) && (curr_angle > (90 + ang_limit)) )
+ || ( (curr_angle > (270 - ang_limit) ) ) )
+ ang_mod *= -1;
+
+ // The power must be reduced if it is greater than twice the
+ // x distance, but raised if less than the simple x distance.
+ // If the power is too low, shots can quickly end up with too
+ // many bounces if the wall is rubber or spring.
+ int32_t dist_x = std::abs(mem_curr->opX - x);
+ if (curr_power > (2 * dist_x))
+ pow_mod *= -1;
+ else if (curr_power > dist_x)
+ pow_mod = 0; // Do not change
+}
+
+
+/// @return true once the operator() ends
+bool AICore::hasExited() const
+{
+ return isFinished;
+}
+
+
+/// @brief initialize work with the current players data
+bool AICore::initialize()
+{
+ DEBUG_LOG_AI(player->getName(), "Starting think work, setting up.", 0)
+
+ /// === Step 1 : Copy relevant data ===
+ ai_level = static_cast<int32_t>(player->type);
+ ai_level_d = static_cast<double>(ai_level);
+ ai_over_mod = 1. + (ai_level_d / 10.); // [1.1;1.5]
+ ai_type_mod = (1. + ai_level_d) / 2.; // [1.0;3.0]
+ blast_min = 0.;
+ blast_med = 0.;
+ blast_big = 0.;
+ blast_max = 0.;
+ isShocked = false;
+ revengee = nullptr;
+ shocker = nullptr;
+ needSuccess = true;
+ needAim = true;
+ isBlocked = false;
+ hill_detected = false;
+
+ // Data from player:
+ needMoney = ((player->getMoneyToSave(false) - player->money) > 0);
+ last_opp = player->getOppMem(-1);
+
+ // Data from tank:
+ tank = player->tank;
+ if (tank && !tank->destroy) {
+ angle = tank->a;
+ power = tank->p;
+ weap_idx = tank->cw;
+ buried = tank->howBuried(&buried_l, &buried_r);
+ currLife = tank->l + tank->sh;
+ maxLife = tank->getMaxLife();
+ x = tank->x;
+ y = tank->y;
+ last_ang = 180;
+ last_pow = 1000;
+ last_weap = 0;
+
+ // Is there a last opponent?
+ if (last_opp) {
+ last_ang = angle;
+ last_pow = power;
+ last_weap = weap_idx;
+ DEBUG_LOG_AI(player->getName(), "Last opponent was %s",
+ last_opp->opponent->getName())
+ }
+
+ // Select last weapon/item used
+ if (weap_idx > WEAPONS)
+ useItem(weap_idx);
+ else
+ useWeapon(weap_idx);
+ } else
+ return false;
+
+ // reset calculation values
+ curr_angle = angle;
+ curr_power = power;
+
+ // Reset setup values:
+ best_round_score = NEUTRAL_ROUND_SCORE;
+ best_setup_angle = angle;
+ best_setup_item = nullptr;
+ best_setup_mem = nullptr;
+ best_setup_overshoot = MAX_OVERSHOOT;
+ best_setup_power = power;
+ best_setup_score = NEUTRAL_ROUND_SCORE;
+ best_setup_weap = nullptr;
+
+
+ /// === Step 2: See whether this bot gets lucky ===
+ if ((rand() % 100) < ai_level) {
+ // So the useless bot has a 1% and the deadly bot a 5% chance
+ int32_t raise = 1 + ( (5 - ai_level) / 2);
+ /* Useless: 1 + ((5 - 1) / 2) => 1 + (4 / 2) => 1 + 2 => 3
+ * Guesser: 1 + ((5 - 2) / 2) => 1 + (3 / 2) => 1 + 1 => 2
+ * Ranger : 1 + ((5 - 3) / 2) => 1 + (2 / 2) => 1 + 1 => 2
+ * Target : 1 + ((5 - 4) / 2) => 1 + (1 / 2) => 1 + 0 => 1
+ * Deadly : 1 + ((5 - 5) / 2) => 1 + (0 / 2) => 1 + 0 => 1
+ */
+ DEBUG_LOG_AI(player->getName(),
+ "Lucky Turn: Raise from \"%s\" to \"%s\"",
+ getLevelName(ai_level),
+ getLevelName(ai_level + raise))
+ ai_level += raise;
+ ai_level_d = static_cast<double>(ai_level);
+ ai_over_mod = 1. + (ai_level_d / 10.); // [1.1;1.5]
+ ai_type_mod = (1. + ai_level_d) / 2.; // [1.0;3.0]
+ showFeedback("*lucky*", GREEN, -.8, TS_NO_SWAY, 100);
+ }
+
+
+ /// === Step 3 : Set stage and allow the work to be done ===
+ if (tank && !tank->destroy && !isStopped && canWork && getMemory()) {
+ // Note: Without the memory, no real work is possible.
+
+ textAllowed.store(true);
+ plStage = PS_SELECT_TARGET;
+ isWorking = true;
+ return true;
+ }
+
+ return false;
+}
+
+
+/// @brief Sanitize curr_angle and curr_power.
+void AICore::sanitizeCurr()
+{
+ if (curr_angle < 90) curr_angle = 90;
+ if (curr_angle > 270) curr_angle = 270;
+ if (curr_power > MAX_POWER) curr_power = MAX_POWER;
+ if (curr_power < MIN_POWER) curr_power = MIN_POWER;
+ curr_power -= curr_power % 5;
+}
+
+
+/// @brief show ai feedback if allowed and not skipping computer play.
+/// Whenever a feedback message is shown, the AI sleeps for dur/10 + 1 ms.
+void AICore::showFeedback(const char* const feedback, int32_t col, double yv,
+ eTextSway text_sway, int32_t dur)
+{
+ if (env.showAIFeedback && !global.skippingComputerPlay) {
+ // Wait for the AI to be allowed to create texts
+ while (!textAllowed.load(ATOMIC_READ))
+ std::this_thread::yield();
+
+ int32_t y_pos = y - (50 + (rand() % 21));
+ new FLOATTEXT(feedback, x, y_pos, .0, yv, col, CENTRE, text_sway, dur);
+ MSLEEP( (dur / 10) + 1 )
+ }
+}
+
+
+/** @brief Select the next item to use on the current target.
+ *
+ * This method tries to determine the best item / weapon selection
+ * to be used on the currently selected target (mem_curr).
+ *
+ * Some of the thinking depend on random numbers, so calling this
+ * method twice on the same target might lead to different selections.
+ *
+ * This is wanted, so many tries on higher ai levels with a small
+ * number of difficult to reach targets might eventually lead to
+ * a sane result.
+ *
+ * The selected item / weapon is saved in item_curr or weap_curr. The
+ * method makes sure that it is not the same as item_last / weap_last.
+ * Please note, however, that if there is only one selectable item,
+ * if the bot is out of stock of everything but small missiles for example,
+ * then item_curr / weap_curr might end up the same as the last selections.
+ *
+ * The method returns true if the selection it ends up with makes sense.
+ * If @a is_last is set to true, the method itself returns true, too.
+ *
+ * @param[in] is_last If set to true, the method will return true in any case.
+ * @return true if the selection makes sense, or if @a is_last is set to true.
+**/
+bool AICore::selectItem(bool is_last)
+{
+ // Back up current selections
+ item_last = item_curr;
+ weap_last = weap_curr;
+
+ // Advance to the next weapon
+ bool has_weap = true;
+
+ // If a best setup with primary target hit has been achieved
+ // already, or if the bot is shocked, select a random weapon.
+ // Otherwise do an ordered advance down the chain.
+ if (best_setup_prime || isShocked) {
+ int32_t weap_num = rand() % WEAPONS;
+ weap_curr = weap_head;
+
+ // If weap_last was not set, set it to head, too.
+ if (!weap_last)
+ weap_last = weap_curr;
+
+ // Now rotate until the weapon is found.
+ while (weap_num) {
+ weap_curr = weap_curr->next;
+
+ // Skip not available weapons, non-damage entries, the last weapon
+ // and weapons with a negative score.
+ while ( weap_curr
+ && ( (weap_curr->amount <= 0)
+ || (weap_curr->dmgSingle < 2.)
+ || (weap_curr->score <= 0)
+ || (weap_curr == weap_last)
+ // Reducer and dirt weapons are non-damage, too.
+ // they have a fake damage set, so filter them here.
+ || (REDUCER == weap_curr->type)
+ || ( (DIRT_BALL <= weap_curr->type)
+ && (SMALL_DIRT_SPREAD >= weap_curr->type) ) ) )
+ weap_curr = weap_curr->next;
+
+ // Rotate if the end was hit
+ if (!weap_curr)
+ weap_curr = weap_head;
+
+ --weap_num;
+ }
+ } else {
+ while ( has_weap
+ && ( !weap_curr
+ || (weap_curr == weap_last)
+ || (weap_curr->amount <= 0)
+ || (weap_curr->dmgSingle < 2.)
+ || (weap_curr->score <= 0)
+ || ( mem_curr->hasRepulse
+ && RAND_AI_1P
+ && (SML_NAPALM <= weap_curr->type)
+ && (LRG_NAPALM >= weap_curr->type)
+ && RAND_AI_1P ) ) ) {
+ weap_curr = weap_curr ? weap_curr->next : weap_head;
+
+ // If no weapon was selected at the start, weap_last is
+ // now nullptr, but must be weap_head once weap_curr is
+ // beyond head.
+ if (!weap_last && (weap_curr != weap_head))
+ weap_last = weap_head;
+
+ // If this rotated once through everything, there is
+ // only this one weapon left or a lot of tries are through:
+ if (weap_last == weap_curr)
+ has_weap = false;
+ }
+ } // end of ordered rotation
+
+ // Use weap_head if there is no other weapon:
+ if (!has_weap) {
+ weap_curr = weap_head;
+ weap_last = weap_curr;
+ }
+
+ // If the bot is shocked, the next weapon is selected,
+ // someone in panic does not do much thinking any more
+ if (isShocked) {
+ item_curr = nullptr;
+ if (nullptr == weap_curr)
+ weap_curr = weap_head;
+
+ weap_idx = weap_curr->type;
+
+ DEBUG_LOG_EMO(player->getName(), "(SHOCKED) Quick selected %s",
+ weapon[weap_idx].getName())
+ return true;
+ }
+
+ // Advance to the next item
+ bool has_item = true;
+ while ( has_item
+ && ( !item_curr
+ || (item_curr == item_last)
+ || (item_curr->amount <= 0)
+ || !item_curr->selectable
+ || (item_curr->score <= 0) ) ) {
+ item_curr = item_curr ? item_curr->next : item_head;
+
+ // If no item was selected at the start, item_last is
+ // now nullptr, but must be item_head once item_curr is
+ // beyond head.
+ if (!item_last && (item_curr != item_head))
+ item_last = item_head;
+
+ // If this rotated once through everything, there is
+ // only this one weapon left:
+ if (item_last == item_curr)
+ has_item = false;
+ }
+
+ // If no items are available, it has to be taken out of consideration:
+ if (!has_item) {
+ item_curr = nullptr;
+ item_last = nullptr;
+ }
+
+
+ // Do not use items with a negative score, as those are items
+ // that are unavailable.
+ if (item_curr
+ && ( (item_curr->score < 0)
+ || (player->ni[item_curr->type] <= 0) ) )
+ item_curr = nullptr;
+
+ // Note: sub-optimal weapons ( too low damage ) can be negative.
+
+ // Do not use self destruct items/weapons unless the
+ // bot wants to self destruct
+ if (mem_curr->opLife <= (currLife * 10.)) {
+ if (item_curr && (item_curr->kamikaze))
+ item_curr = nullptr;
+ if (weap_curr && (weap_curr->kamikaze))
+ weap_curr = nullptr;
+ }
+
+ // Do not use teleporters unless buried or blocked
+ if ( item_curr
+ && (item_curr->type >= ITEM_TELEPORT)
+ && (item_curr->type <= ITEM_MASS_TELEPORT)
+ && !isBlocked
+ && !item_curr->escape)
+ item_curr = nullptr;
+
+ // Do not use riot bombs if the path is not blocked
+ if ( weap_curr
+ && (weap_curr->type >= RIOT_BOMB)
+ && (weap_curr->type <= HVY_RIOT_BOMB)
+ && !isBlocked)
+ weap_curr = nullptr;
+
+ // If both are still set, take what has the higher score
+ if (item_curr && weap_curr) {
+ if (item_curr->score > weap_curr->score) {
+ weap_curr = nullptr;
+ weap_idx = item_curr->type + WEAPONS;
+ } else {
+ item_curr = nullptr;
+ weap_idx = weap_curr->type;
+ }
+ } else if (item_curr)
+ weap_idx = item_curr->type + WEAPONS;
+ else if (weap_curr)
+ weap_idx = weap_curr->type;
+ else
+ weap_idx = -1;
+
+ if (!item_curr && !weap_curr && is_last)
+ // If nothing is set but this is the last chance,
+ // use the small missile as a fallback weapon
+ useWeapon(SML_MIS);
+
+
+ DEBUG_LOG_EMO(player->getName(), "Next selection: %s",
+ weap_curr
+ ? weapon[weap_idx].getName()
+ : item_curr
+ ? item[weap_idx - WEAPONS].getName()
+ : "NOTHING (fail)")
+
+ return (item_curr || weap_curr);
+}
+
+
+/** @brief Select the next target to try to hit or handle.
+ *
+ * This method selects and sets the current target. Basically it
+ * just wanders down the memory chain as it is sorted by score already.
+ *
+ * The following additional rules (besides the ordering by score) apply:
+ * - If the bot is shocked, the shocker is always selected.
+ * - If a revenge is sought, that opponent is always selected if it
+ * is not currently selected or was the last one.
+ * - If @a is_last is set to true, the target with the highest score
+ * or that is sought revenge against is selected and the method
+ * returns true.
+ *
+ * Whenever the selection makes sense, the method returns true.
+ *
+ * @param[in] is_last If set to true, the method will return true in any case.
+ * @return true if the selection makes sense, or if @a is_last is set to true.
+**/
+bool AICore::selectTarget(bool is_last)
+{
+ // Be quickly done if this bot is shocked
+ if (isShocked) {
+
+ // Is the shocker still there?
+ if (shocker->opponent->tank && !shocker->opponent->tank->destroy) {
+ mem_last = mem_curr;
+ if (!mem_curr || (mem_curr->entry != shocker)) {
+ mem_curr = mem_head;
+ while (mem_curr && (mem_curr->entry != shocker))
+ mem_curr = mem_curr->next;
+ }
+
+ DEBUG_LOG_EMO(player->getName(), "(SHOCKED) Targetting %s",
+ mem_curr ? mem_curr->entry->opponent->getName() : "NONE?")
+
+ return (mem_curr->entry == shocker);
+ } else {
+ // Nope, gone with the wind.
+ shocker = nullptr;
+ isShocked = false;
+ }
+ }
+
+ // Preselect the revengee if not done already and there is one:
+ // Note: Of course the revengee is forced if this is the very last try!
+ if ( revengee
+ && ( is_last
+ || ( (!mem_curr || (mem_curr->entry != revengee))
+ && (!mem_last || (mem_last->entry != revengee)) ) ) ) {
+
+ // is the revengee still alive?
+ if (revengee->opponent->tank && !revengee->opponent->tank->destroy) {
+ mem_last = mem_curr;
+ mem_curr = mem_head;
+ while (mem_curr && (mem_curr->entry != revengee))
+ mem_curr = mem_curr->next;
+
+ DEBUG_LOG_EMO(player->getName(), "(REVENGE) Targetting %s",
+ mem_curr ? mem_curr->entry->opponent->getName() : "NONE?")
+
+ return (mem_curr->entry == revengee);
+ } else
+ // No longer relevant...
+ revengee = nullptr;
+ }
+
+ // If nothing was preselected, a simple walk down the chain
+ // is in order. (revengees must be skipped, though)
+ // However, if this is the last_try, the primary target is always selected.
+ sOppMemEntry* mem_old = mem_curr; // backup
+
+ if (is_last || (nullptr == mem_curr))
+ mem_curr = mem_head;
+
+ // If the revengee is currently selected, the "walk" must continue
+ // from the last opponent on, or the bot will have a flip between
+ // the revengee and the first other opponent only.
+ if (!is_last && revengee && mem_curr && (mem_curr->entry == revengee)) {
+ if (mem_last)
+ mem_curr = mem_last;
+ else
+ mem_curr = mem_head;
+ }
+
+ // Now walk down the list skipping the revengee if set
+ if (!is_last) {
+ while ( mem_curr
+ && ( (mem_curr == mem_old)
+ || (mem_curr == mem_last)
+ || (revengee && (mem_curr->entry == revengee))
+ || (mem_curr->entry->opponent == player)
+ || (nullptr == mem_curr->entry->opponent->tank)
+ || mem_curr->entry->opponent->tank->destroy) )
+ mem_curr = mem_curr->next;
+ mem_last = mem_old;
+ }
+
+ // If is_last is set, mem_curr must not be nullptr. Otherwise a
+ // nullptr can happen if too few tanks are left.
+ assert ( (!is_last || mem_curr) && "ERROR: Is last but nullptr curr!");
+
+ DEBUG_LOG_EMO(player->getName(), "( normal) Targetting %s",
+ mem_curr ? mem_curr->entry->opponent->getName() : "nobody")
+
+ return (nullptr != mem_curr);
+}
+
+
+/** @brief setup the basic attack values
+ *
+ * This method tries to find a sane target-weapon-combination.
+ * The number of tries to do so is dictated by the AI level,
+ * and if this is the very last targeting try, the method will
+ * come up with the minimum possible combination.
+ *
+ * @param[in] is_last If set to true, something usable is forced to be set.
+ * @return true if a viable combination was found, false otherwise.
+**/
+bool AICore::setupAttack(bool is_last, int32_t &opp_attempt, int32_t &weap_attempt)
+{
+ bool selectDone = false;
+ bool breakUp = false;
+ bool has_new_target = false;
+
+ while (isWorking && !isStopped && !selectDone && !breakUp) {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ // 1) Select a target
+ //====================
+ if (!weap_attempt || !mem_curr) {
+ plStage = PS_SELECT_TARGET;
+
+ DEBUG_LOG_AIM(player->getName(), "Selecting target, try %d / %d",
+ opp_attempt + 1, findOppAttempts)
+
+ selectDone = selectTarget( (++opp_attempt == findOppAttempts)
+ && is_last && needSuccess);
+ if (selectDone && (mem_curr != mem_last)) {
+ best_round_score = NEUTRAL_ROUND_SCORE; // New target!
+ item_curr = nullptr;
+ item_last = nullptr;
+ weap_curr = nullptr;
+ weap_last = nullptr;
+ has_new_target = true;
+ }
+ } else {
+ has_new_target = false;
+ selectDone = true;
+ }
+
+ // 2) Select an item / weapon
+ //============================
+ if (selectDone) {
+ plStage = PS_SELECT_WEAPON;
+
+ /* --- Ensure dedicated lists and starting values --- */
+ if (has_new_target) {
+ checkWeapMem();
+ checkItemMem();
+ weap_idx = -1;
+ }
+
+ /* --- Now do the selection --- */
+ selectDone = false;
+ while ( isWorking && !isStopped && !selectDone
+ && (weap_attempt < findWeapAttempts) ) {
+ DEBUG_LOG_AIM(player->getName(), "Selecting item, try %d / %d",
+ weap_attempt + 1, findWeapAttempts)
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ selectDone = selectItem( (++weap_attempt == findWeapAttempts)
+ && is_last && needSuccess);
+ }
+
+ // selectItem() must have set weap_idx properly:
+ assert( ( !selectDone
+ || ( item_curr && (weap_idx == WEAPONS + item_curr->type) )
+ || ( weap_curr && (weap_idx == weap_curr->type) ) )
+ && "ERROR: Selection done but weap_idx does not"
+ && " contain the correct index!");
+
+ // If this was the last attempt to select a weapon, and another
+ // opponent selection is in order, reset weap_attempt so a new
+ // opponent can be selected
+ if ( (weap_attempt == findWeapAttempts)
+ && (opp_attempt < findOppAttempts) )
+ weap_attempt = 0;
+ } // end of item selection
+
+ // break up this round if no selection was done but the
+ // opponent selection has ended
+ if (!selectDone
+ && (opp_attempt >= findOppAttempts)
+ && (weap_attempt >= findWeapAttempts) )
+ breakUp = true;
+ } // end of basic setup cycle
+
+ // If the selection was not done properly (not possible) but this
+ // is the absolutely final round and there has been no good setup, yet,
+ // an emergency plan is used:
+ if (isWorking && !isStopped && is_last && !selectDone && needSuccess) {
+ // Try to "get out" first:
+ selectDone = useItem(ITEM_TELEPORT);
+
+ if (!selectDone && !mem_curr->is_buried)
+ selectDone = useItem(ITEM_SWAPPER);
+
+ if (!selectDone)
+ selectDone = useItem(ITEM_MASS_TELEPORT);
+
+ // If this still isn't going anywhere, revert to first target with
+ // small missiles, that might be useless now, but there is always
+ // a next round.
+ if (!selectDone) {
+ item_curr = nullptr;
+ mem_curr = mem_head;
+ selectDone = useWeapon(SML_MIS);
+
+ DEBUG_LOG_EMO(player->getName(),
+ "Last Try Selection: %s against %s",
+ weapon[SML_MIS].getName(),
+ mem_head->entry->opponent->getName())
+
+ assert(selectDone && "ERROR: Not even small missile can be selected?");
+ } else
+ weap_curr = nullptr; // Emergency plan working.
+ }
+
+
+ // The last thing to consider is kamikaze.
+ // While the appropriate items *are* self destruct devices, choosing
+ // a weapon with kamikaze potential (and score) does not mean that the
+ // bot *must* destroy themselves. So in that case an extra test is in order.
+ if ( selectDone
+ && ( (item_curr && item_curr->kamikaze)
+ || (weap_curr && weap_curr->kamikaze) ) ) {
+ bool self_destruct = true;
+
+ // selfPreservation is a value in the interval [0;3]
+ if (weap_curr && ( (rand() % 35) < (player->selfPreservation * 10.)))
+ self_destruct = false;
+
+ // Another "way out" is if a weapon shall be used but it does
+ // not do enough damage:
+ if (self_destruct && weap_curr && (weap_curr->dmgSingle < currLife))
+ self_destruct = false;
+
+ // do not self destruct if a good best setup was already found
+ if (!needSuccess && best_setup_prime && (best_setup_score > 0))
+ self_destruct = false;
+
+ // If the bot still wants to self destruct, change mem_curr
+ // to reflect this:
+ if (self_destruct && (mem_curr->entry->opponent != player) ) {
+ mem_last = mem_curr;
+ mem_curr = mem_head;
+ while (mem_curr->entry->opponent != player)
+ mem_curr = mem_curr->next;
+
+ // This must never fail:
+ assert(mem_curr && "ERROR: Self not found in memory?");
+
+ DEBUG_LOG_EMO(player->getName(),
+ "Chosen to self destruct using %s",
+ weap_curr
+ ? weapon[weap_idx].getName()
+ : item_curr
+ ? item[weap_idx - WEAPONS].getName()
+ : "NOTHING (fail)")
+
+ } else {
+ // To chicken out, the small missile is chosen if this is
+ // the last thing to consider
+ if (is_last && needSuccess)
+ useWeapon(SML_MIS);
+ else
+ selectDone = false;
+ }
+ }
+
+
+ // If both attempts, selecting an opponent and a weapon, have reached
+ // the maximum tries, they get reset, so the next full targeting cycle
+ // is triggered:
+ if ( !is_last && breakUp) {
+ opp_attempt = 0;
+ weap_attempt = 0;
+ }
+
+ return selectDone;
+}
+
+
+/** @brief start the work on one player
+ *
+ * This method starts working on a new player. All relevant
+ * data is fetched from @a player_ that must not be nullptr.
+ *
+ * If a job is running, no new one is started.
+ * If @a player_ is not an AI player, it is not handled.
+ *
+ * @param[in] player Pointer to the player to handle.
+ * @return true if a job was started, false otherwise.
+ */
+bool AICore::start(PLAYER* player_)
+{
+ if ( canWork && player_ && !isWorking && !isStopped
+ && (player_->type > HUMAN_PLAYER)
+ && (player_->type < NETWORK_CLIENT)
+ && (PS_AI_IS_IDLE == plStage) ) {
+
+ DEBUG_LOG_AI(player_->getName(), "==============================", 0)
+ DEBUG_LOG_AI(player_->getName(), " AICore started for %s", player_->getName())
+ DEBUG_LOG_AI(player_->getName(), "------------------------------", 0)
+
+ lguard_t guard(actionMutex);
+ plStage = PS_AI_INITIALIZE;
+ player = player_;
+ isWorking = true;
+ actionCondition.notify_one();
+
+ return true;
+ }
+
+ return false;
+}
+
+
+/** @brief Retrieve the current status of the AI
+ *
+ * The arguments will not be changed if the AI is not working
+ * on any player.
+ *
+ * @param[out] aItem Receives the currently selected weapon/item
+ * @param[out] aAngle Receives the currently set angle
+ * @param[out] aPower Receives the currently set power
+ * @param[out] pl_stage Receives the current stage of the AI. This is always sent.
+ * @return true if the AI is still working, false if it has finished.
+ */
+bool AICore::status(int32_t &aItem, int32_t &aAngle,
+ int32_t &aPower, ePlayerStages &pl_stage)
+{
+ pl_stage = plStage;
+
+ if (isWorking) {
+ aItem = weap_idx;
+ aAngle = angle;
+ aPower = power;
+ return true;
+ }
+
+ return false;
+}
+
+
+/// @brief Tell the thread to stop even if it is not finished.
+void AICore::stop()
+{
+ lguard_t guard(actionMutex);
+ isStopped = true;
+ actionCondition.notify_one();
+}
+
+
+/** @brief Trace the sub munition of a cluster type weapon
+ *
+ * Damage is recorded in the opponent memory dmgDone value.
+ *
+ * @param[in] subType The type of the sub munition. No checks done!
+ * @param[in] subCount The number of sub munition parts.
+ * @param[in] sub_x Trigger x coordinate.
+ * @param[in] sub_y Trigger y coordinate.
+ * @param[in] inh_xv Parent missile xv the moment it triggered.
+ * @param[in] inh_yv Parent missile yv the moment it triggered.
+**/
+void AICore::traceCluster(int32_t subType, int32_t subCount,
+ int32_t sub_x, int32_t sub_y,
+ double inh_xv, double inh_yv)
+{
+ double divergence = weapon[weap_curr->type].divergence;
+ double speedVar = weapon[weap_curr->type].speedVariation;
+ double spreadVar = weapon[weap_curr->type].spreadVariation;
+ WEAPON* sub_weap = &weapon[subType];
+ double divStep = static_cast<double>(divergence)
+ / static_cast<double>(subCount - 1);
+ int32_t startPoint = divStep < 0. ? 0 : 180;
+ int32_t randStart = rand () % 1000000;
+ ePhysType subPhys = PT_NORMAL;
+ int32_t startY = sub_y - 20;
+ int32_t cl_overshoot = MAX_OVERSHOOT;
+ int32_t old_overshoot= curr_overshoot; // overshoot is only used for mirvs and funkies
+ double radius = sub_weap->radius;
+ double sub_dmg = sub_weap->damage * player->damageMultiplier;
+
+ // If the weapon is fired into a ceiling, adapt starting y
+ if (env.isBoxed
+ && (startY <= BOXED_TOP)
+ && ( (WALL_STEEL == env.current_wallType)
+ || ( (WALL_WRAP == env.current_wallType)
+ && (!env.isBoxed || !env.do_box_wrap) ) ) )
+ startY = MENUHEIGHT + 20;
+
+ // Change physics of the sub munitions for the funky bomb
+ if ( (weap_curr->type == FUNKY_BOMB) || (weap_curr->type == FUNKY_DEATH) )
+ subPhys = PT_FUNKY_FLOAT;
+
+ // If this is a steel wall hit, the start point angle needs
+ // to be adapted. And erased if this is a ceiling hit
+ if (WALL_STEEL == env.current_wallType) {
+ if ( (CLUSTER <= weap_curr->type) && (SUP_CLUSTER >= weap_curr->type) ) {
+ if ( x < 2 )
+ startPoint -= divergence + 1 + (rand() % 10);
+ else if ( x > (env.screenWidth - 3) )
+ startPoint += divergence + 1 + (rand() % 10);
+ else if ( y <= BOXED_TOP)
+ startPoint = 0;
+ } else if ( (SML_NAPALM <= weap_curr->type)
+ && (LRG_NAPALM >= weap_curr->type) ) {
+ if ( x < 2 )
+ startPoint -= 10 + rand() % 21;
+ else if ( x > (env.screenWidth - 3) )
+ startPoint += 10 + rand() % 21;
+ else if ( y <= BOXED_TOP)
+ startPoint = 0;
+ } else if ( ( (SMALL_MIRV == weap_curr->type)
+ || (CLUSTER_MIRV == weap_curr->type) )
+ && ( y <= BOXED_TOP) ) {
+ startPoint = 0;
+ inh_yv = std::abs(inh_yv);
+ }
+ }
+
+ // The spread can be created!
+ for (int32_t sc = 0; sc < subCount; ++sc) {
+ double speed = weapon[weap_curr->type].launchSpeed;
+ int32_t newMissCount = sub_weap->countdown;
+ int32_t newMissAngle = ROUND(
+ (divStep * static_cast<double>(sc))
+ + static_cast<double>(startPoint)
+ - (static_cast<double>(divergence) / 2.) );
+
+ // trace hard, but yield per sub mun
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ // Manipulate angle if applicable
+ if (speedVar > 0.)
+ newMissAngle += ROUND(
+ static_cast<double>(divergence)
+ * spreadVar
+ * Noise(randStart + 1054 + sc) );
+
+ // Be sure the angle is valid
+ while (newMissAngle < 0)
+ newMissAngle += 360;
+ newMissAngle %= 360;
+
+ // Manipulate number of submunition projectiles if applicable
+ if (sub_weap->countVariation > 0) {
+ newMissCount += ROUND(
+ static_cast<double>(sub_weap->countdown)
+ * sub_weap->countVariation
+ * Noise(randStart + 78689 + sc) );
+ // This might go wrong, so be sure it doesn't
+ if (newMissCount <= 0)
+ newMissCount = 0;
+ }
+
+ // Manipulate launching speed if applicable
+ if (speedVar > 0)
+ speed += ROUND(speedVar * speed * Noise(randStart + 124786 + sc) );
+
+ // Launch new submunition missile
+ MISSILE mind_shot(player, sub_x, startY,
+ env.slope[newMissAngle][0] * speed * env.FPS_mod + inh_xv,
+ env.slope[newMissAngle][1] * speed * env.FPS_mod + inh_yv,
+ subType, MT_MIND_SHOT, ai_level);
+ mind_shot.update_submun(subPhys, newMissCount);
+
+ // Keep flying/rolling/digging/whatever until the missile hits something
+ // or the number of bounces is too high for this bot to keep track.
+ while (!mind_shot.destroy && (maxBounce >= mind_shot.bounced())) {
+ mind_shot.applyPhysics();
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+ }
+
+ // If the missile is destroyed, the number of bounces is in order.
+ if (mind_shot.destroy) {
+ // The distance from the target must take both the direction
+ // of the last movement of the mind shot and the positions of
+ // both tanks into account:
+ int32_t tank_dir = SIGN(mem_curr->opX - x);
+ int32_t hit_dir = SIGN(mem_curr->opX - mind_shot.x);
+ int32_t shot_dir = mind_shot.direction();
+
+ curr_overshoot = ABSDISTANCE2(mem_curr->opX + offset_x,
+ mem_curr->opY + offset_y,
+ mind_shot.x, mind_shot.y);
+
+ if (tank_dir == shot_dir)
+ curr_overshoot *= (tank_dir == hit_dir ? -1 : 1);
+ else
+ curr_overshoot *= (tank_dir == hit_dir ? 1 : -1);
+
+ // Note it down if this mind shot is better than the best
+ if (std::abs(curr_overshoot) < std::abs(cl_overshoot))
+ cl_overshoot = curr_overshoot;
+
+ // eventually add the score:
+ calcHitDamage(mind_shot.x, mind_shot.y, radius, sub_dmg,
+ static_cast<weaponType>(subType));
+
+ }
+ } // End of looping submunitions
+
+ // Write back best overshoot if this is a MIRV or funky weapon.
+ // clusters and napalm weapons spread stuff out, there the hit
+ // of the parent weapon is what counts.
+ if ( (SMALL_MIRV == weap_curr->type)
+ || (FUNKY_BOMB == weap_curr->type)
+ || (FUNKY_DEATH == weap_curr->type)
+ || (CLUSTER_MIRV == weap_curr->type) )
+ curr_overshoot = cl_overshoot;
+ else
+ curr_overshoot = old_overshoot;
+}
+
+
+/** @brief Fire a mind shot and see where it goes.
+ *
+ * If the mind shot got destroyed, curr_overshoot is set to the distance of
+ * mem_curr->opponent to reached_x_/reached_y_. Positive values mean "too far",
+ * negative values mean the shot went "too short".
+ *
+ * @param[in] trace_angle The angle to use, normally a spread variation of
+ * curr_angle.
+ * @param[out] finished true if the mind shot got destroyed before reaching
+ * maxBounce wall bounces/wraps.
+ * @param[out] top_wrapped Set to true if the the missile wrapped through a
+ * wrap wall ceiling.
+ * @param[out] reached_x_ The x-coordinate on destruction. If the mind shot was
+ * cancelled before being destroyed, @a reached_x_ is not changed.
+ * @param[out] reached_y_ The y-coordinate on destruction. If the mind shot was
+ * cancelled before being destroyed, @a reached_y_ is not changed.
+ * @param[out] end_xv The x velocity the moment the projectile ends.
+ * @param[out] end_yv The y velocity the moment the projectile ends.
+ * @return true if all went well, false if any new() operator failed. If this
+ * method returns false, the AI can no longer work.
+**/
+bool AICore::traceShot(int32_t trace_angle,
+ bool &finished, bool &top_wrapped,
+ int32_t &reached_x_, int32_t &reached_y_,
+ double &end_xv, double &end_yv)
+{
+ double top_x = x;
+ double top_y = y;
+ double vel_mod = static_cast<double>(curr_power) * env.FPS_mod;
+ double vel_x = env.slope[trace_angle][0] * vel_mod / 100.;
+ double vel_y = env.slope[trace_angle][1] * vel_mod / 100.;
+ bool can_top_wrap = ( env.isBoxed
+ && (WALL_WRAP == env.current_wallType)
+ && env.do_box_wrap );
+ double old_yv = 0;
+ double old_y = 0;
+
+ tank->getGuntop(trace_angle, top_x, top_y);
+
+ MISSILE mind_shot(player, top_x, top_y, vel_x, vel_y, weap_idx,
+ MT_MIND_SHOT, ai_level);
+
+ // Adapt missile drag if the player has dimpled/slick projectiles
+ if (player->ni[ITEM_DIMPLEP])
+ mind_shot.drag *= item[ITEM_DIMPLEP].vals[0];
+ else if (player->ni[ITEM_SLICKP])
+ mind_shot.drag *= item[ITEM_SLICKP].vals[0];
+
+ // Keep flying/rolling/digging/whatever until the missile hits something
+ // or the number of bounces is too high for this bot to keep track.
+ while (!mind_shot.destroy && (maxBounce >= mind_shot.bounced())) {
+ if (can_top_wrap) {
+ old_yv = vel_y;
+ old_y = mind_shot.y;
+ }
+
+ mind_shot.applyPhysics();
+
+ if (can_top_wrap) {
+ mind_shot.getVelocity(vel_x, vel_y);
+ if ( ( (old_yv < 0.) && (vel_y < 0.) && (mind_shot.y > old_y) )
+ || ( (old_yv > 0.) && (vel_y > 0.) && (mind_shot.y < old_y) ) )
+ top_wrapped = true;
+ }
+
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+ }
+
+ // If the missile is destroyed, the number of bounces is in order.
+ if (mind_shot.destroy) {
+ mind_shot.getVelocity(end_xv, end_yv);
+ finished = true;
+ reached_x_ = mind_shot.x;
+ reached_y_ = mind_shot.y;
+
+ // The distance from the target must take both the direction
+ // of the last movement of the mind shot and the positions of
+ // both tanks into account:
+ int32_t tank_dir = SIGN(mem_curr->opX - x);
+ int32_t hit_dir = SIGN(mem_curr->opX - reached_x_);
+ int32_t shot_dir = mind_shot.direction();
+
+ curr_overshoot = ABSDISTANCE2(mem_curr->opX + offset_x,
+ mem_curr->opY + offset_y,
+ reached_x_, reached_y_);
+
+ if (tank_dir == shot_dir)
+ curr_overshoot *= (tank_dir == hit_dir ? -1 : 1);
+ else
+ curr_overshoot *= (tank_dir == hit_dir ? 1 : -1);
+ } else {
+ finished = false;
+ curr_overshoot = MAX_OVERSHOOT;
+ }
+
+ return true;
+}
+
+
+/** @brief trace all shots from weap_curr and fill in the arguments.
+ *
+ * Damage is recorded in the opponent memory dmgDone value.
+ *
+ * @return best overshoot.
+**/
+void AICore::traceWeapon(int32_t &has_crashed, int32_t &has_finished)
+{
+ assert(weap_curr && "ERROR: traceWeapon() with nullptr weap_curr?");
+
+ int32_t trace_overshoot= MAX_OVERSHOOT;
+ int32_t curr_reached_x = reached_x;
+ int32_t curr_reached_y = reached_y;
+ double end_xv = 0.;
+ double end_yv = 0.;
+
+ has_crashed = 0;
+ has_finished = 0;
+ curr_prime_hit = false;
+
+ // reset virtual damage on opponents.
+ opEntry_t* opp = mem_head;
+ while (opp) {
+ opp->dmgDone = 0;
+ opp = opp->next;
+ }
+
+ // Loop by spread, weapons that do not spread have a value of 1.
+ for (int32_t i = 0 ;
+ canWork && !isStopped && (i < weap_curr->spread) ;
+ ++i) {
+ int32_t tr_a = curr_angle
+ + ( (SPREAD * i)
+ - (SPREAD * (weap_curr->spread - 1) / 2) );
+ bool finished = false;
+ bool top_wrap = false;
+ reached_x = x;
+ reached_y = y;
+
+ if (traceShot(tr_a, finished, top_wrap, curr_reached_x, curr_reached_y,
+ end_xv, end_yv)
+ && finished) {
+
+ ++has_finished;
+
+ // Check whether the shot crashed
+ if ( ( env.isBoxed
+ && ( (curr_reached_y <= BOXED_TOP ) // crashed on top
+ // wrapped to bottom with dirt above is a ceiling crash, too.
+ || ( top_wrap
+ && ( (weap_curr->type < BURROWER) || (weap_curr->type > PENETRATOR) )
+ && (curr_reached_y > global.surface[curr_reached_x].load()) ) ) )
+ // Steel wall have additional wall crashes
+ || ( (WALL_STEEL == env.current_wallType)
+ && (weap_curr->subMunCount < 1) // Clusters never crash
+ && ( (curr_reached_x <= 2)
+ || (curr_reached_x >= (env.screenWidth - 3) ) ) ) ) {
+
+ ++has_crashed;
+ }
+
+ if (weap_curr->subMunCount > 0) {
+ double inh_xv = weapon[weap_curr->type].impartVelocity * end_xv;
+ double inh_yv = weapon[weap_curr->type].impartVelocity * end_yv;
+ // Trace the cluster parts and add their score:
+ traceCluster(weap_curr->subMunType, weap_curr->subMunCount,
+ curr_reached_x, curr_reached_y, inh_xv, inh_yv);
+ } else
+ // Calculate a score for the hit, crashes are handled later
+ calcHitDamage(curr_reached_x, curr_reached_y,
+ static_cast<double>(weap_curr->radius),
+ weap_curr->dmgSingle,
+ static_cast<weaponType>(weap_curr->type));
+
+ // Note best overshoot if any
+ if (std::abs(curr_overshoot) < std::abs(trace_overshoot)) {
+ trace_overshoot = curr_overshoot;
+ reached_x = curr_reached_x;
+ reached_y = curr_reached_y;
+ }
+ } // End of if traceShot
+ } // end of spread loop
+
+ // Write back best data found:
+ curr_overshoot = trace_overshoot;
+}
+
+
+/// @brief Set a new score to an items entry
+void AICore::updateItemScore(itEntry_t* pItem)
+{
+ /* There aren't many items that are actually usable.
+ * 1. Teleporters
+ * These can be used to get out of a buried scenario.
+ * Further they might be an alternative if the
+ * targeted tank is far away.
+ * 2. Fan
+ * This item has no real use for the AI but one:
+ * If the enemy is behind a mountain and the AI has tail wind,
+ * then it might be helpful to change the wind direction.
+ * However, this does only make sense if not that many other
+ * bots have their shot until this one gets its next try.
+ * 3. Self destruct devices
+ * If this bots tanks is almost dead, and the preferred target
+ * has a lot of health left, then trying to take them with us
+ * using a big boom is somewhat compelling.
+ * 4. Fuel and rockets.
+ * Fuel can be used to get away from a steep wall, rockets can
+ * be used to get out of a steep canyon.
+ */
+
+
+ // === Get out quickly if the chosen item is out of stock ===
+ // ==========================================================
+ if (0 == pItem->amount) {
+ pItem->score = -50000;
+ return;
+ }
+
+
+ // === Only evaluate items that are available ===
+ // ==============================================
+ if (!env.isItemAvailable(pItem->type + WEAPONS)) {
+ pItem->score = -100000;
+ return;
+ }
+
+ DEBUG_LOG_AI(player->getName(), "Evaluating score for %s",
+ item[pItem->type].getName())
+
+ // reset helper boolean
+ pItem->escape = false;
+
+
+ /* -------------------------------------------------------------
+ * --- 1) Set score for freeing capabilities while buried ---
+ * ------------------------------------------------------------- */
+ double unbury_score = 0;
+ if (buried > BURIED_LEVEL) {
+ double bury_diff = buried - BURIED_LEVEL;
+ double off_mod = (player->defensive - 1.5) / -2.;
+
+ if (ITEM_TELEPORT == pItem->type)
+ unbury_score = bury_diff * ai_level * off_mod
+ // It is more valuable when the target is buried,
+ // as it is not desirable to swap with them
+ * (mem_curr->is_buried ? 3. : 1.);
+
+ else if (ITEM_SWAPPER == pItem->type)
+ unbury_score = bury_diff * ai_level * off_mod * 2.
+ // If the target is buried, the swapper is no good.
+ * (mem_curr->is_buried ? -50. : 2.);
+
+ else if (ITEM_MASS_TELEPORT == pItem->type)
+ unbury_score = bury_diff * ai_level * off_mod * 1.25;
+
+ else if (ITEM_FAN == pItem->type)
+ // at least the bot might think the usage is safe.
+ unbury_score = bury_diff
+ * static_cast<double>(maxAiLevel + 1 - ai_level)
+ * off_mod / 2.;
+ else
+ // Everything else is useless
+ unbury_score = -5000.;
+
+ // "escape" tool ?
+ if ( (ITEM_TELEPORT <= pItem->type)
+ && (ITEM_MASS_TELEPORT >= pItem->type) )
+ pItem->escape = true;
+ }
+
+
+ /* -------------------------------------------------------------
+ * --- 2) The wind direction change score for fans ---
+ * ------------------------------------------------------------- */
+ double fan_score = 0.;
+ if ( (ITEM_FAN == pItem->type)
+ // The fan can only be considered useful if the bot has tail wind:
+ && ( ( (mem_curr->opX > tank->x) && (global.wind > 0.) )
+ || ( (mem_curr->opX < tank->x) && (global.wind < 0.) ) ) ) {
+
+ // First count how many other bots can have their turn until
+ // this one will get its next chance:
+ opEntry_t* check = mem_head;
+ int32_t between = 0;
+ while (check) {
+ if ( (check->entry->opponent != player) && check->alive)
+ ++between;
+ check = check->next;
+ }
+
+ // Now look whether there is a mountain in between:
+ int32_t check_x = ROUND(mem_curr->opX);
+ int32_t checked = 0;
+ int32_t direction = SIGN(global.wind) * -1;
+ int32_t range_x = 10 * (ai_level + RAND_AI_0P);
+ int32_t top_ledge = env.screenHeight;
+
+ while ( (checked < range_x)
+ && (check_x > 1)
+ && (check_x < (env.screenWidth - 2)) ) {
+ int32_t check_y = global.surface[check_x].load(ATOMIC_READ);
+ if (check_y < top_ledge)
+ top_ledge = check_y;
+ check_x += direction;
+ }
+
+ // Now the score is a simple height difference modified by defensiveness
+ // (The more defensive the player is, the more it is inclined to prepare
+ // the next attack instead of pissing them of with a weak shot.)
+ fan_score = static_cast<double>(top_ledge - mem_curr->opY)
+ * (player->defensive + ai_level_d);
+
+ // However, the score is multiplied again with the count of opponents
+ // that will have their try until this one gets its next shot.
+ fan_score *= fan_score > 0.
+ ? static_cast<double>(ai_level - between) // normal multiplier
+ : static_cast<double>(between); // The more bots, the more useless.
+
+ // However, if the bot already used the fan in the last round,
+ // do not repeat, no matter what:
+ if (last_weap == (ITEM_FAN + WEAPONS)) {
+ DEBUG_LOG_EMO(player->getName(),
+ "=> Reducing fan score %6.2lf to %62lf (no repeat)",
+ fan_score, std::abs(fan_score * ai_level) * -1.);
+ fan_score = std::abs(fan_score * ai_level) * -1.;
+ }
+ } // End of calculating a fan score
+
+
+ /* -------------------------------------------------------------
+ * --- 3) Set score for self destruct probability ---
+ * ------------------------------------------------------------- */
+ double selfde_score = 0.;
+ if ( (mem_curr->opLife > (currLife * 10.))
+ || (isShocked && (mem_curr->opLife > (currLife * 5.))) ) {
+ if ( (ITEM_VENGEANCE >= pItem->type)
+ && (ITEM_FATAL_FURY <= pItem->type) ) {
+ selfde_score = static_cast<double>(pItem->type - ITEM_VENGEANCE + 1)
+ * mem_curr->diffLife / (player->selfPreservation + .5);
+ }
+ }
+
+
+ /* -------------------------------------------------------------
+ * --- 4) The "useless" score, for not usable items ---
+ * ------------------------------------------------------------- */
+ double useless_score = 0.;
+ if ( (ITEM_FATAL_FURY < pItem->type)
+ && (ITEM_ROCKET != pItem->type) )
+ useless_score = -50000.;
+ /// @todo : FUEL should be made available to the AI somehow.
+
+
+ /* -------------------------------------------------------------
+ * --- 5) Sum up the score ---
+ * --- This will be used for sorting the items list ---
+ * ------------------------------------------------------------- */
+ double pref_score = pItem->preference / static_cast<double>(ai_level * 10);
+
+ double xScore = unbury_score + selfde_score + useless_score + fan_score;
+
+ if (useless_score > -1.) {
+ DEBUG_LOG_EMO(player->getName(), " preference : %6.2lf%s", pref_score,
+ xScore > 1. ? "" : " (ignored)")
+ DEBUG_LOG_EMO(player->getName(), " unbury_score : %6.2lf", unbury_score)
+ DEBUG_LOG_EMO(player->getName(), " fan_score : %6.2lf", fan_score)
+ DEBUG_LOG_EMO(player->getName(), " selfde_score : %6.2lf", selfde_score)
+ DEBUG_LOG_EMO(player->getName(), " useless_score: %6.2lf", useless_score)
+ }
+
+ // Only add preferences if there is any use for the item:
+ if (xScore > 1.)
+ xScore += pref_score;
+
+ pItem->score = ROUND(xScore);
+
+ DEBUG_LOG_EMO(player->getName(), " Final Score : %8d", pItem->score)
+}
+
+
+/// @brief Set a new score to an opponents entry
+void AICore::updateOppScore(opEntry_t* pOpp)
+{
+ sOpponent* entry = pOpp->entry;
+ PLAYER* opponent = entry->opponent;
+ TANK* oppTank = opponent->tank;
+
+ DEBUG_LOG_AI(player->getName(), "Evaluating score for %s", opponent->getName())
+
+ /* Quickly handle dead tanks and the own entry */
+ if ( (player == opponent) || !oppTank || oppTank->destroy ) {
+ entry->damage_from += entry->damage_last;
+ entry->damage_last = 0;
+ pOpp->score = (player == opponent) ? -2 : -1;
+
+ DEBUG_LOG_AI(player->getName(), "%s%s",
+ player == opponent ? "" : opponent->getName(),
+ player == opponent
+ ? "Not evaluating myself!"
+ : " is dead and not selectable!")
+ return;
+ }
+
+
+ /* -------------------------------------------------------------
+ * --- 1) Set up a fear value (if needed) ---
+ * --- This is used to possibly trigger actions that may not ---
+ * --- be wise but are imposed by a sudden surge of fear. ---
+ * ------------------------------------------------------------- */
+ double fear_damage = 0.;
+ double fear_shock = 0.;
+ if (!pOpp->onSameTeam) {
+ entry->fear_shock = 0.;
+
+ // The higher the AI level, the more the taken over fear is
+ // reduced. *But* the more defensive the player is, the less
+ // it is reduced. (Even more on a lucky turn. ;-)
+ // Ranges are from useless full defensive to deadly full offensive:
+ // From: 2.5 + 1 - ( 1 + 1) => 3.5 - 2 => 1.5 (only one third reduced)
+ // To : 2.5 + 5 - (-1 + 1) => 7.5 - 0 => 7.5 (~87% taken off)
+ entry->fear /= 2.5 + ai_level_d
+ - (player->defensive + 1.0);
+
+ // Only add new fear if there was any damage
+ if (entry->damage_last > 0) {
+ fear_damage = player->painSensitivity * entry->damage_last;
+ entry->fear += player->selfPreservation;
+ }
+
+ // first fear check:
+ // If the AI can not stand the pain, the damage done is multiplied
+ // with the fear value. This does not trigger any action, yet, but
+ // the score will go up a lot.
+ fear_shock = entry->fear - static_cast<double>(RAND_AI_0P);
+ if ( (fear_damage > 0.) && (fear_shock > 0.) ) {
+ fear_damage *= entry->fear;
+ DEBUG_LOG_EMO(player->getName(),
+ "%s caused fear shock %lf with damage %u",
+ opponent->getName(), fear_shock,
+ ROUNDu(fear_damage))
+ // Is this the new shocker?
+ if ( (nullptr == shocker)
+ || (fear_shock > shocker->fear_shock) ) {
+ DEBUG_LOG_EMO(player->getName(),
+ "%s %s%s%s as new shocker", opponent->getName(),
+ shocker ? "replaces" : "set",
+ shocker ? " " : "",
+ shocker ? shocker->opponent->getName() : "")
+ shocker = entry;
+ }
+ } // End of having a fear shock
+ entry->fear_shock = fear_shock;
+ } // end of fear value handling
+
+ /* -------------------------------------------------------------
+ * --- 2) Check damage for whether revenge is called for ---
+ * ------------------------------------------------------------- */
+ double revenge_score = 0.;
+ if ( (entry->damage_last > 0) && !pOpp->onSameTeam) {
+
+ // First reduce the current damage accumulated. More for lower level bots.
+ if (!pOpp->revengeDone) {
+ DEBUG_LOG_EMO(player->getName(), "Current anger damage from %s: %d",
+ opponent->getName(), entry->revenge_dmg)
+
+ entry->revenge_dmg /= 4.5 - ai_type_mod;
+
+ DEBUG_LOG_EMO(player->getName(), " --> Anger cooled down to : %d",
+ entry->revenge_dmg)
+
+ // Add current damage
+ entry->revenge_dmg += entry->damage_last;
+
+ DEBUG_LOG_EMO(player->getName(), " --> Anger raised again to : %d",
+ entry->revenge_dmg)
+
+ // Revenge damage handled:
+ pOpp->revengeDone = true;
+ }
+
+ // Now see whether a new act of vengeance is initiated:
+ if ( (entry->revenge_dmg > (player->vengeanceThreshold * maxLife))
+ && ( (rand() % 100) <= player->vengeful) ) {
+
+ // Okay, the potential is there...
+ revenge_score = static_cast<double>(entry->damage_last * player->vengeful)
+ / 100.;
+ if ( (nullptr == revengee)
+ || (entry->revenge_dmg > revengee->revenge_dmg) ) {
+ // A new one!
+ DEBUG_LOG_EMO(player->getName(), " --> [%d] %s %s%s for revenge!",
+ entry->revenge_dmg,
+ entry->opponent->getName(),
+ revengee ? "replaces " : "is set ",
+ revengee ? revengee->opponent->getName() : "")
+ revengee = entry;
+ }
+ }
+ } // end of revenge value handling
+
+
+ /* -------------------------------------------------------------
+ * --- 3) Check opponents health compared to this tank ---
+ * --- The more health they got, the more money can be made. ---
+ * --- On the other hand, the bigger the difference, the ---
+ * --- more impressive they are. ---
+ * -------------------------------------------------------------
+ */
+ double life_score = 0.;
+ if (pOpp->diffLife < 0.) {
+ // The opponent has more health. This might impress the bot:
+ if ( (rand() % static_cast<int32_t>(DEADLY_PLAYER)) < ai_level) {
+ // No, there is nothing impressive with that...
+ life_score = (player->defensive - 3.) / 2. * pOpp->diffLife;
+ // Note:
+ // Full Defensive : (-1 - 3) / 2 * -x => -4 / 2 * -x => -2 * -x = 2 * x
+ // Full Offensive : ( 1 - 3) / 2 * -x => -2 / 2 * -x => -1 * -x = 1 * x
+
+ // If the bot needs money, the opponents health might be added:
+ if (needMoney && RAND_AI_0P)
+ life_score += pOpp->opLife;
+ }
+ } else
+ // Add points for their weakness, more if the bot is offensive
+ life_score = (player->defensive + 3.) / 2. * pOpp->diffLife;
+ // Note:
+ // Full Defensive : (-1 + 3) / 2 * x => 2 / 2 * x = 1 * x
+ // Full Offensive : ( 1 + 3) / 2 * x => 4 / 2 * x = 2 * x
+
+
+ /* -------------------------------------------------------------
+ * --- 4) Add points for distance ---
+ * --- The theory is, that weaker bots concentrate on nearer ---
+ * --- enemies first, while stronger bots do not mind. ---
+ * ------------------------------------------------------------- */
+ double dist_score = (static_cast<double>(env.halfWidth) - pOpp->distance)
+ / ai_level_d;
+
+
+ /* -------------------------------------------------------------
+ * --- 5) Add points the easier the target is to be killed. ---
+ * --- The easier, and cheaper, the better. But even much ---
+ * --- better if this bot needs money. ---
+ * ------------------------------------------------------------- */
+ double vict_score = 0.;
+ double vict_mod = needMoney ? static_cast<double>(8 - ai_level) : 1.;
+ if (pOpp->opLife < blast_max)
+ vict_score += vict_mod * (blast_max - pOpp->opLife) * 1. * ai_type_mod;
+ if (pOpp->opLife < blast_big)
+ vict_score += vict_mod * (blast_big - pOpp->opLife) * 2. * ai_type_mod;
+ if (pOpp->opLife < blast_med)
+ vict_score += vict_mod * (blast_med - pOpp->opLife) * 4. * ai_type_mod;
+ if (pOpp->opLife < blast_min)
+ vict_score += vict_mod * (blast_min - pOpp->opLife) * 8. * ai_type_mod;
+
+
+ /* --------------------------------------------------------------
+ * --- 6) Add or dock points regarding AI level ---
+ * --- More powerful opponents are targeted preferably, while ---
+ * --- weaker ones are not considered to be such a threat. ---
+ * --- Note: Human players are handled like deadly bots. ---
+ * -------------------------------------------------------------- */
+ double level_score = 0.;
+ if (!pOpp->onSameTeam) {
+ if ( (HUMAN_PLAYER < opponent->type)
+ && (LAST_PLAYER_TYPE > opponent->type) )
+ level_score = static_cast<double>(opponent->type - player->type);
+ else
+ level_score = static_cast<double>(DEADLY_PLAYER - player->type);
+
+ // The higher the self preservation, the more urgent deadlier
+ // bots are targeted to get them down early.
+ if (level_score > 0.)
+ level_score *= player->selfPreservation + 1.;
+
+ // The more defensive the bot is, the more does it want to target
+ // weaker opponents to not aggravate the stronger ones
+ if (level_score < 0.)
+ level_score *= player->defensive + 2.;
+
+ // The more health this bots tank has, the more prominent is this score
+ level_score = std::abs(level_score) * currLife;
+ }
+
+
+ /* -------------------------------------------------------------
+ * --- 7) Add points for score difference ---
+ * --- Target the leading bots earlier, losing ones later. ---
+ * ------------------------------------------------------------- */
+ double win_score = pOpp->onSameTeam ? 0. :
+ (opponent->score - player->score)
+ * (player->selfPreservation + 1.)
+ * (player->defensive + 2.)
+ * static_cast<double>(ai_level + 1)
+ * (static_cast<double>(pOpp->opLife) / 10.)
+ / (player->painSensitivity + 0.5);
+ // Note: The win_score is only used if positive.
+ // 1 - Self preservation: Get rid of the winner as a threat soon.
+ // 2 - Defensiveness : Even more if of the defensive type.
+ // 3 - The smarter the more they do care.
+ // 4 - Multiply with 10% of the opponents tank life
+ // 5 - Pain Sensitivity: Can they stand the answer? ( If this value
+ // is lower than 0.5, they care so less, that the score is raised. Up
+ // to a doubling is possible - If they really feel no pain.)
+ // Maximum score:
+ // deadly + 1 (lucky turn), maximum defensiveness and self preservation, painless:
+ // (4 * 3 * 7) / 0.5 = 84 / 0.5 = 168 points per opponent health point and
+ // round win difference.
+
+
+ /* -------------------------------------------------------------
+ * --- 8) Sum up the score ---
+ * --- This will be used for sorting the opponents list ---
+ * ------------------------------------------------------------- */
+ double damage_score = entry->damage_from - entry->damage_to;
+ double kill_score = (entry->killed_me - entry->killed_them) * maxLife;
+ double last_score = entry->damage_last * type_mod;
+
+ DEBUG_LOG_EMO(player->getName(), " team_mod : %6.2lf", pOpp->team_mod)
+ DEBUG_LOG_EMO(player->getName(), " type_mod : %6.2lf", type_mod)
+ DEBUG_LOG_EMO(player->getName(), " damage_score : %6.2lf", damage_score)
+ DEBUG_LOG_EMO(player->getName(), " kill_score : %6.2lf", kill_score)
+ DEBUG_LOG_EMO(player->getName(), " last_score : %6.2lf", last_score)
+ DEBUG_LOG_EMO(player->getName(), " fear_damage : %6.2lf", fear_damage)
+ DEBUG_LOG_EMO(player->getName(), " revenge_score: %6.2lf", revenge_score)
+ DEBUG_LOG_EMO(player->getName(), " life_score : %6.2lf", life_score)
+ DEBUG_LOG_EMO(player->getName(), " dist_score : %6.2lf", dist_score)
+ DEBUG_LOG_EMO(player->getName(), " vict_score : %6.2lf", vict_score)
+ DEBUG_LOG_EMO(player->getName(), " level_score : %6.2lf", level_score)
+ DEBUG_LOG_EMO(player->getName(), " win_score : %6.2lf", win_score)
+
+ double xScore = ( damage_score > 0. ? pOpp->team_mod * damage_score : 0. )
+ + ( kill_score > 0. ? pOpp->team_mod * kill_score : 0. )
+ + ( last_score > 0. ? pOpp->team_mod * last_score : 0. )
+ + ( fear_damage > 0. ? pOpp->team_mod * fear_damage : 0. )
+ + ( fear_shock > 0. ? fear_shock * fear_damage : 0. )
+ + ( revenge_score > 0. ? pOpp->team_mod * revenge_score : 0. )
+ + ( life_score > 0. ? pOpp->team_mod * life_score : 0. )
+ + ( vict_score > 0. ? pOpp->team_mod * vict_score : 0. )
+ + ( win_score > 0. ? win_score : 0. )
+ + dist_score + level_score;
+ pOpp->score = ROUND(xScore);
+
+ DEBUG_LOG_EMO(player->getName(), " Final Score : %8d", pOpp->score)
+
+ // --- clean up damage_last ---
+ if (entry->damage_last) {
+ entry->damage_from += entry->damage_last;
+ entry->damage_last = 0;
+ }
+}
+
+
+/// @brief Set a new score to a weapons entry
+void AICore::updateWeapScore(weEntry_t* pWeap)
+{
+ // As this is used a few dozen times, a shortcut to pWeap->type is nice:
+ weaponType wType = pWeap ? static_cast<weaponType>(pWeap->type) : SML_MIS;
+
+
+ // === Get out quickly if the chosen item is out of stock ===
+ // ==========================================================
+ if (0 == pWeap->amount) {
+ pWeap->score = -50000;
+ return;
+ }
+
+
+ // === Only evaluate items that are available ===
+ // ==============================================
+ if (!env.isItemAvailable(wType)) {
+ pWeap->score = -100000;
+ return;
+ }
+
+ DEBUG_LOG_AI(player->getName(), "Evaluating score for %s",
+ weapon[wType].getName())
+
+ // reset boolean helpers
+ pWeap->blastOut = false;
+ pWeap->kamikaze = false;
+
+
+ // === If no opponent is chosen (however this may happen) then ===
+ // === the pure preferences count. ===
+ // ===============================================================
+ if (nullptr == mem_curr) {
+ pWeap->score = pWeap->preference;
+ DEBUG_LOG_AI(player->getName(), " -> Use preference %d", pWeap->preference)
+ return;
+ }
+
+
+ // === If this is a laser, it will only be evaluated if the tank is ===
+ // === not below this players tanks as it can not be reached then. ===
+ // ====================================================================
+ if ( (SML_LAZER <= wType) && (LRG_LAZER >= wType)
+ && (mem_curr->opY > y) ) {
+ pWeap->score = -45000;
+ DEBUG_LOG_AI(player->getName(), " -> Target y %d is not reachable from %d",
+ ROUND(mem_curr->opY), ROUND(y))
+ return;
+ }
+
+
+ // === If this is the percent bomb, its damage must be adapted, ===
+ // === as it depends on the selected target. ===
+ // ================================================================
+ if (PERCENT_BOMB == wType) {
+ pWeap->dmgCluster = 0.;
+ pWeap->dmgSingle = mem_curr->opLife / 2;
+ pWeap->dmgSpread = pWeap->dmgSingle;
+ }
+
+ // === The same applies to the reducer ===
+ if (REDUCER == wType) {
+ pWeap->dmgCluster = 0.;
+ pWeap->dmgSingle = (mem_curr->opLife / 2.)
+ * (mem_curr->entry->opponent->damageMultiplier / 2.)
+ * (player->painSensitivity + ai_over_mod)
+ / (-1. * (player->defensive - 1.25 - ai_over_mod));
+ pWeap->dmgSpread = pWeap->dmgSingle;
+ }
+
+
+ /* -------------------------------------------------------------
+ * --- 1) Set score for reaching the target health ---
+ * ------------------------------------------------------------- */
+ double weap_dmg = 0.; // Filled here, used for splash score, too
+ double dmg_diff = 0.;
+ double point_score = mem_curr->opLife;
+ // If the bot is shocked, spread and cluster weapons get a bonus:
+ double shock_bonus = isShocked
+ ? static_cast<double>(maxAiLevel - ai_level) + ai_over_mod
+ : 1.;
+
+ if (pWeap->dmgCluster > 1.) {
+ weap_dmg = pWeap->dmgCluster;
+ dmg_diff = (weap_dmg / ai_over_mod) - point_score;
+ if (dmg_diff > 0.)
+ dmg_diff *= shock_bonus;
+ } else if (pWeap->dmgSpread > pWeap->dmgSingle) {
+ weap_dmg = pWeap->dmgSpread;
+ dmg_diff = (weap_dmg / (ai_over_mod / 2.)) - point_score;
+ if (dmg_diff > 0.)
+ dmg_diff *= shock_bonus;
+ } else if (pWeap->dmgSingle > 1.) {
+ weap_dmg = pWeap->dmgSingle;
+ dmg_diff = weap_dmg / ai_over_mod - point_score;
+ } else
+ dmg_diff = -point_score;
+
+ if (dmg_diff < 0.)
+ // Too less damage
+ point_score += dmg_diff;
+ else if (dmg_diff > 0.)
+ // Otherwise chop off a modified difference
+ point_score -= dmg_diff
+ / ( -(player->defensive - 2.5) // 3.5 full offensive, 2.5 full defensive
+ * ai_over_mod ); // the higher the level, the more the reduction.
+
+ // If this is a REDUCER or dirt weapon, and the fake damage is
+ // higher than the target health, modify the score. The AI wants
+ // to finish off the almost dead and not debuff them
+ if ( (REDUCER == wType)
+ || ( (DIRT_BALL <= wType) && (SMALL_DIRT_SPREAD >= wType) ) ) {
+ if (mem_curr->opLife <= blast_min)
+ point_score = 0;
+ else if (mem_curr->opLife <= blast_med)
+ point_score /= static_cast<double>(ai_level + 3);
+ else if (mem_curr->opLife <= blast_big)
+ point_score /= static_cast<double>(ai_level + 1) / ai_over_mod;
+ else if (dmg_diff > 0.)
+ point_score /= ai_over_mod;
+ }
+
+
+ /* -------------------------------------------------------------
+ * --- 2) check buried state, shaped charges and the driller ---
+ * --- might still be usable. ---
+ * ------------------------------------------------------------- */
+ double unbury_score = 0.;
+ if (buried > BURIED_LEVEL) {
+ // Shaped charges refer to the y coordinate
+ if ( (SHAPED_CHARGE <= wType)
+ && (CUTTER >= wType)
+ && (std::abs(mem_curr->opY - y) < (weapon[wType].radius / 20))
+ && (mem_curr->distance < weapon[wType].radius) )
+ // This one is usable.
+ unbury_score = pWeap->dmgSingle * ai_over_mod;
+
+ // The driller is only usable in a vertical way:
+ else if ( (DRILLER == wType)
+ && (std::abs(mem_curr->opX - x) < (weapon[wType].radius / 20))
+ && (mem_curr->distance < weapon[wType].radius) )
+ unbury_score = pWeap->dmgSingle * ai_over_mod;
+
+ // Riot bombs and charges are the ultimate tools, of course
+ else if ( ( (RIOT_CHARGE <= wType) && (RIOT_BLAST >= wType) )
+ ||( (RIOT_BOMB <= wType) && (HVY_RIOT_BOMB >= wType) ) )
+ unbury_score = ai_type_mod
+ * static_cast<double>(weapon[wType].radius)
+ * static_cast<double>(buried - BURIED_LEVEL + ai_level);
+
+ // Everything else is (mostly) useless
+ else {
+ if (pWeap->dmgCluster > 1.)
+ unbury_score -= ai_type_mod * pWeap->dmgCluster * pWeap->dmgSingle;
+ else {
+ unbury_score -= ai_type_mod * (pWeap->dmgSpread + pWeap->dmgSingle);
+ // However, if the target is in range and a self hit would not
+ // kill our own tank...
+ if ( (mem_curr->distance < weapon[wType].radius)
+ && (currLife > pWeap->dmgSingle)
+ && (currLife > pWeap->dmgSpread) )
+ unbury_score += ai_over_mod * pWeap->dmgSingle / player->selfPreservation;
+ }
+ } // End of "useless" weapons
+
+ if (unbury_score > 1.)
+ pWeap->blastOut = true;
+ } // End of unbury score.
+
+ // If not buried, riot weapons are useless:
+ else if ( ( (RIOT_CHARGE <= wType) && (RIOT_BLAST >= wType) )
+ ||( (RIOT_BOMB <= wType) && (HVY_RIOT_BOMB >= wType) ) )
+ unbury_score = -50000.;
+
+
+ /* -------------------------------------------------------------
+ * --- 3) Panic score - If this bot has panicked, the more ---
+ * --- damage the better. ---
+ * ------------------------------------------------------------- */
+ double panic_score = 0.;
+ if (isShocked && (mem_curr->entry == shocker) && (buried <= BURIED_LEVEL) ) {
+ panic_score = pWeap->dmgCluster > 1.
+ ? pWeap->dmgCluster
+ : pWeap->dmgSpread;
+ // If this is a debuffing weapon like reducer or percent bomb,
+ // it is valued even higher.
+ if (REDUCER == wType)
+ panic_score += mem_curr->entry->opponent->damageMultiplier
+ * mem_curr->opLife * player->selfPreservation;
+ else if (PERCENT_BOMB == wType)
+ panic_score += pWeap->dmgSingle / player->selfPreservation;
+ else if ((DIRT_BALL <= wType)
+ && (SMALL_DIRT_SPREAD >= wType))
+ panic_score += weapon[wType].radius
+ * weapon[wType].spread
+ * (1.5 + player->defensive);
+ }
+
+
+ /* ----------------------------------------------------------------------
+ * --- 4) Score for reaching buried opponents. ---
+ * --- If an opponent is buried, burrowers and tremors are useful. ---
+ * ---------------------------------------------------------------------- */
+ double dig_score = 0.;
+ if ( mem_curr->is_buried
+ || ( (mem_curr->opX > x) && (mem_curr->buried_l >= BURIED_LEVEL_HALF) )
+ || ( (mem_curr->opX < x) && (mem_curr->buried_r >= BURIED_LEVEL_HALF) ) ) {
+
+ // Chain weapons can push through dirt, but are bad when the own tank
+ // is buried.
+ if ( (CHAIN_GUN <= wType) && (JACK_HAMMER >= wType) )
+ dig_score = pWeap->dmgSingle
+ * static_cast<double>(weapon[wType].getDelayDiv())
+ / (1.75 + player->defensive)
+ * (buried > BURIED_LEVEL ? -1. : 1.);
+
+ // Burrowers can actually directly reach the target
+ else if ( (BURROWER <= wType) && (PENETRATOR >= wType) )
+ dig_score = pWeap->dmgSingle * ai_type_mod;
+
+ // tremors are somewhat weak, but the do not only (possibly) reach
+ // the target but remove dirt as well.
+ else if ( (TREMOR <= wType) && (TECTONIC >= wType) )
+ dig_score = (pWeap->dmgSingle + weapon[wType].radius)
+ * ai_over_mod * (2.1 + player->defensive);
+
+ // Riot bombs are useful to undig an opponent as well.
+ else if ( (RIOT_BOMB <= wType) && (HVY_RIOT_BOMB >= wType) )
+ dig_score = weapon[wType].radius
+ // Note: pain sensitivity is used, as not doing any
+ // damage won't trigger a vengeance reaction.
+ * (1. + player->defensive + player->painSensitivity);
+
+ // remember that this is chosen for blasting out an opponent:
+ if (dig_score > 1.)
+ pWeap->blastOut = true;
+ }
+
+
+ /* --------------------------------------------------------------
+ * --- 5) Splash damage ---
+ * --- Check all tanks whether they are in "splash range" and ---
+ * --- add or dock points according to the team_mod value of ---
+ * --- the hit tanks. This score can be negative and is meant ---
+ * --- to help bots to decide against oversized weapons if ---
+ * --- good working alternatives are present. ---
+ * -------------------------------------------------------------- */
+ double splash_score = 0.;
+ double money_made = 0.; // build here, used below
+ double money_cost = 0.; // build here, used below
+ if (buried <= BURIED_LEVEL) {
+ opEntry_t* op = mem_head;
+
+ // Always assume a full direct hit:
+ double xhit = mem_curr->opX;
+ double yhit = mem_curr->opY;
+
+ // The minimum in_rate depends on the defensive level. EXPLOSION takes
+ // different values for the shaped weapons and tectonics. Further the
+ // full rate limit is 10% damage. The bot does not calculate minimum
+ // axis rates, and the full rate limit might become lower or higher than
+ // this 10%. This is wanted as bots "only estimate".
+ double rate_limit = (player->defensive + .75) / 10.;
+ // result: Over-offensive Sith: (-1.25 + 0.75) / 10. => 0.5 / 10. => 5%
+ // Over-defensive Jedi: ( 1.25 + 0.75) / 10. => 2.0 / 10. => 20%
+
+ while (op) {
+
+ // Do not evaluate the target, as it will get the hit anyway
+ if (op == mem_curr) {
+ op =op->next;
+ continue;
+ }
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ PLAYER* pl = op->entry->opponent;
+ TANK* lt = pl ? pl->tank : nullptr; // short cut
+
+ if (!lt || lt->destroy || (op->opLife < 1.)) {
+ // irrelevant
+ op = op->next;
+ continue;
+ }
+
+ double weap_rad = weap_curr->radius;
+ double xrad = DRILLER == wType
+ ? weap_rad / 20. : weap_rad;
+ double yrad = ( (SHAPED_CHARGE <= wType)
+ && (CUTTER >= wType) )
+ ? weap_rad / 20. : weap_rad;
+ double in_rate_x = 0.;
+ double in_rate_y = 0.;
+
+ if (lt->isInEllipse(xhit, yhit, xrad, yrad, in_rate_x, in_rate_y)) {
+ double in_rate = in_rate_x * in_rate_y;
+
+ if (in_rate < rate_limit)
+ in_rate = rate_limit;
+
+ double score = std::min(weap_dmg * in_rate, op->opLife)
+ * op->team_mod;
+
+ // Do not overdo positive scores
+ if (score >= 0)
+ // Note: That is [1.1;4.8]
+ score /= ai_over_mod * static_cast<double>((ai_level + 1) / 2);
+
+ splash_score += score;
+
+ DEBUG_LOG_EMO(player->getName(),
+ "%s in splash range, %s %d points",
+ pl == player ? "I am" : pl->getName(),
+ score > 0 ? "add " : "dock",
+ std::abs(ROUND(score)))
+
+ // Note down money made or cost:
+ if (op->team_mod > 0.)
+ money_made += ( std::min(weap_dmg * in_rate, op->opLife)
+ * static_cast<double>(env.scoreHitUnit) )
+ + ( (weap_dmg * in_rate) >= op->opLife
+ ? static_cast<double>(env.scoreUnitDestroyBonus)
+ : 0.);
+ else if (lt == tank)
+ money_cost += ( std::min(weap_dmg * in_rate, currLife)
+ * static_cast<double>(env.scoreSelfHit) )
+ + ( (weap_dmg * in_rate) >= currLife
+ ? static_cast<double>(env.scoreUnitSelfDestroy)
+ : 0.);
+ else
+ money_cost += ( std::min(weap_dmg * in_rate, op->opLife)
+ * static_cast<double>(env.scoreTeamHit) )
+ + ( (weap_dmg * in_rate) >= op->opLife
+ ? static_cast<double>(env.scoreUnitSelfDestroy)
+ : 0.);
+ } // End of opponent in explosion
+
+ op = op->next;
+ } // End of looping opponents memory
+ } // end of calculating splash damage score
+
+
+ /* -------------------------------------------------------------
+ * --- 6) Kamikaze potential ---
+ * --- If the bot decides to self destruct, it is important ---
+ * --- to check what this weapon would do. ---
+ * ------------------------------------------------------------- */
+ double selfde_score = 0.;
+ if ( (mem_curr->opLife > (currLife * 10.))
+ || (isShocked && (mem_curr->opLife > (currLife * 5.))) ) {
+
+ // Only some weapons are considered for a big boom bye bye
+ if ( ( (SML_NUKE <= pWeap->type) && (DTH_HEAD >= pWeap->type) )
+ || ( (WIDE_BOY == pWeap->type) || (CUTTER == pWeap->type) )
+ || ( (MED_NAPALM == pWeap->type) || (LRG_NAPALM == pWeap->type) ) ) {
+ double kRad = pWeap->radius;
+ double kDmg = pWeap->dmgSingle;
+
+ // for a kamikaze, the shaped weapons have to be shot somewhat to
+ // the side, so extend the radius if this tank is not buried, or
+ // reduce it to zero if it is.
+ if ((WIDE_BOY == pWeap->type) || (CUTTER == pWeap->type)) {
+ if (buried >= (BURIED_LEVEL / ai_level))
+ kRad = 0.;
+ else
+ kRad += 50. * ai_over_mod;
+ }
+
+ // The same counts for the napalm, although it does not really have
+ // a radius. This must be estimated according to the current wind.
+ else if ((MED_NAPALM == pWeap->type) || (LRG_NAPALM == pWeap->type)) {
+ if (buried >= (BURIED_LEVEL / ai_level))
+ kRad = 0.;
+ else {
+ kRad = std::abs(global.wind / (env.windstrength / 4.)) + 1.;
+ /* This produces the following multiplier: (with max wind = 8)
+ * wind = 0 : (0 / (8 / 4)) + 1 = (0 / 2) + 1 = = 1
+ * wind = 1 : (1 / (8 / 4)) + 1 = (1 / 2) + 1 = = 1.5
+ * wind = 4 : (4 / (8 / 4)) + 1 = (4 / 2) + 1 = = 3
+ * wind = 6 : (6 / (8 / 4)) + 1 = (6 / 2) + 1 = = 4
+ * wind = 8 : (8 / (8 / 4)) + 1 = (8 / 2) + 1 = = 5
+ */
+ kRad *= weapon[pWeap->type].launchSpeed / ai_level;
+
+ // Napalm is a cluster, but not everything will hit
+ kDmg = pWeap->dmgCluster / ai_level_d;
+ }
+ }
+
+ // Check against collateral damage unless shocked and the shocker is
+ // in range
+ bool tgt_in_range = false;
+ if ( isShocked
+ && (mem_curr->entry == shocker) // can this be false?
+ && (mem_curr->distance < kRad) ) {
+ // No check, just do it
+ selfde_score = (pWeap->dmgCluster + pWeap->dmgSingle) * ai_over_mod;
+ tgt_in_range = true;
+ } else {
+ // Nope, be reasonable
+ selfde_score = kDmg;
+ sOppMemEntry* check = mem_head;
+ while (check) {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ if ( (check->opLife > 0.) && (check->distance < kRad) ) {
+ if (check->onSameTeam)
+ selfde_score -= kDmg * ai_type_mod
+ * ( 1.25 + player->defensive);
+ else
+ selfde_score += kDmg * ai_over_mod * check->team_mod;
+
+ // Award extra points if this is the current target
+ if (mem_curr == check) {
+ selfde_score += check->opLife * check->team_mod;
+ tgt_in_range = true;
+ }
+ }
+ check = check->next;
+ }
+ } // End of checking done damage
+
+ // Now, if the score is positive, this is a kamikaze choice:
+ if ( (selfde_score > 0.) && tgt_in_range)
+ pWeap->kamikaze = true;
+
+ } else
+ // Unsuitable
+ selfde_score -= pWeap->dmgSpread * ai_type_mod;
+ }
+
+
+ /* -------------------------------------------------------------
+ * --- 7) Economic evaluation ---
+ * --- If the maximum damage bounty the weapon can generate ---
+ * --- is lower than the weapon score, points are docked. ---
+ * --- Generating more money than the weapon is worth adds ---
+ * --- some bonus points. ---
+ * ------------------------------------------------------------- */
+ double eco_score = 0.;
+ double money_mod = needMoney
+ ? ai_type_mod
+ : (static_cast<double>(RAND_AI_1P + 1) * 10.);
+ double money_diff = money_made - money_cost;
+ if (!isShocked) {
+ // Note: Shocked bots do not care about money!
+ if ( money_diff >= weapon[pWeap->type].cost)
+ eco_score += (money_made / money_mod)
+ - (money_cost / money_mod);
+ else
+ eco_score -= money_diff / money_mod;
+ }
+
+
+ /* -------------------------------------------------------------
+ * --- 8) Sum up the score ---
+ * --- This will be used for sorting the weapons list ---
+ * ------------------------------------------------------------- */
+ double pref_score = pWeap->preference / ai_level_d
+ / (std::abs(dmg_diff) > 1. ? std::abs(dmg_diff) : 1.);
+ // the further away, the less likely.
+
+ DEBUG_LOG_EMO(player->getName(), " preference : %6.2lf", pref_score)
+ DEBUG_LOG_EMO(player->getName(), " point_score : %6.2lf [diff %6.2lf]",
+ point_score, dmg_diff)
+ DEBUG_LOG_EMO(player->getName(), " unbury_score : %6.2lf", unbury_score)
+ DEBUG_LOG_EMO(player->getName(), " panic_score : %6.2lf", panic_score)
+ DEBUG_LOG_EMO(player->getName(), " splash_score : %6.2lf", splash_score)
+ DEBUG_LOG_EMO(player->getName(), " selfde_score : %6.2lf", selfde_score)
+ DEBUG_LOG_EMO(player->getName(), " dig_score : %6.2lf", dig_score)
+ DEBUG_LOG_EMO(player->getName(), " eco_score : %6.2lf (M %6.2lf / C -%6.2lf / D %6.2lf)",
+ eco_score, money_made, money_cost, money_diff)
+
+ double xScore = pref_score + point_score + unbury_score + panic_score
+ + splash_score + selfde_score + dig_score + eco_score;
+
+ pWeap->score = ROUND(xScore);
+
+ DEBUG_LOG_EMO(player->getName(), " Final Score : %8d", pWeap->score)
+}
+
+
+/** @brief Select a tool to free the tank or clear a path
+ * @param[in] free_tank If set to true, a tool to free the tank is chosen,
+ * a tool to clear the path otherwise.
+ * @param[in] is_last If set to true, an emergency selection is done to force
+ * this method to succeed.
+ * @return true if the selection succeeded, false otherwise.
+**/
+bool AICore::useFreeingTool(bool free_tank, bool is_last)
+{
+ // If the current weapon is already used to blast out
+ // an opponent, no other tool is needed.
+ if (!free_tank && weap_curr && weap_curr->blastOut)
+ return true;
+
+ if ( ( free_tank
+ && ( useWeapon(RIOT_BLAST)
+ || useWeapon(RIOT_CHARGE) ) )
+ || useWeapon(HVY_RIOT_BOMB)
+ || useWeapon(RIOT_BOMB)
+ || (!free_tank
+ && ( useWeapon(CHAIN_GUN)
+ || useWeapon(DRILLER)
+ || useWeapon(CHAIN_MISSILE) ) )
+ || (free_tank
+ && ( useItem(ITEM_TELEPORT) // Note: No mass teleport here!
+ || ( !mem_curr->is_buried
+ && useItem(ITEM_SWAPPER) ) ) ) ) {
+
+ DEBUG_LOG_AIM(player->getName(), "Selected %s to %s",
+ weap_idx < WEAPONS
+ ? weapon[weap_idx].getName()
+ : item[weap_idx - WEAPONS].getName(),
+ free_tank ? "free my tank" : "clear firing path")
+
+ return true;
+ }
+
+ // If the "normal" selection is not possible (out of stock)
+ // but this is the last try, the bot has to revert to standard
+ // missiles. Expensive, but should work.
+ else if ( is_last
+ && ( ( !free_tank
+ && ( useWeapon(SML_NUKE)
+ || useWeapon(LRG_MIS)
+ || useWeapon(MED_MIS) ) )
+ || useItem(ITEM_TELEPORT)
+ || ( useItem(ITEM_SWAPPER)
+ && !mem_curr->is_buried )
+ || useItem(ITEM_MASS_TELEPORT) // As a last resort this is okay.
+ || useWeapon(SML_MIS) ) ) {
+
+ DEBUG_LOG_AIM(player->getName(), "(LAST) Selected %s to %s",
+ weap_idx < WEAPONS
+ ? weapon[weap_idx].getName()
+ : item[weap_idx - WEAPONS].getName(),
+ free_tank ? "free my tank" : "clear firing path")
+
+ return true;
+ }
+
+ return is_last;
+}
+
+
+/// @brief explicitly select @a item_type, returns true if available and chosen.
+bool AICore::useItem (itemType item_type)
+{
+ if (env.isItemAvailable(item_type) && (player->ni[item_type] > 0)) {
+ item_curr = item_head;
+ while (item_curr && (item_curr->type != item_type))
+ item_curr = item_curr->next;
+
+ if (item_curr && (item_curr->type == item_type)) {
+ weap_idx = WEAPONS + item_type;
+ weap_curr = nullptr;
+ return true;
+ } else if (weap_curr)
+ item_curr = nullptr;
+ }
+
+ return false;
+}
+
+
+/// @brief convenience function to use the full index as an integer to choose
+/// an item. Full index means the value is beyond the WEAPONS constant.
+bool AICore::useItem(int32_t item_index)
+{
+ if ( (item_index >= WEAPONS) && (item_index < THINGS) )
+ return useItem(static_cast<itemType>(item_index - WEAPONS));
+ return false;
+}
+
+
+/// @brief explicitly select @a weapon_type, returns true if available and chosen.
+bool AICore::useWeapon (weaponType weap_type)
+{
+ if (env.isItemAvailable(weap_type) && (player->nm[weap_type] > 0)) {
+ weap_curr = weap_head;
+ while (weap_curr && (weap_curr->type != weap_type))
+ weap_curr = weap_curr->next;
+
+ if (weap_curr && (weap_curr->type == weap_type)) {
+ weap_idx = weap_type;
+ item_curr = nullptr;
+ return true;
+ } else if (item_curr)
+ weap_curr = nullptr;
+ }
+
+ return false;
+}
+
+
+/// @brief convenience function to use the numeric index as an integer to choose
+/// a weapon.
+bool AICore::useWeapon(int32_t weap_index)
+{
+ if (weap_index < WEAPONS)
+ return useWeapon(static_cast<weaponType>(weap_index));
+ return false;
+}
+
+
+/// @brief Call this once the AI weapon is fired to signal the end of the
+/// players turn
+void AICore::weapon_fired()
+{
+ if (isWorking && !isStopped && (PS_FIRE == plStage)) {
+ DEBUG_LOG_AI(player->getName(), "------------------------------", 0)
+ DEBUG_LOG_AI(player->getName(), " Weapon fired for %s", player->getName())
+
+ lguard_t guard(actionMutex);
+ plStage = PS_CLEANUP;
+ actionCondition.notify_one();
+ }
+}
+
+
+/// @brief Core threading operator
+void AICore::operator()()
+{
+ while (canWork && !isStopped) {
+
+ // Go to sleep until the thread is woken up
+ luniq_t actionLock(actionMutex);
+ actionCondition.wait(actionLock, [this]{
+ return (isWorking || isStopped);
+ } );
+
+ // If the thread is to be stopped, exit the loop
+ if (isStopped)
+ // Cleaner than "break", but only on a philosophical level... ;-)
+ continue;
+
+ if (!initialize()) {
+ plStage = PS_AI_IS_IDLE;
+ isWorking = false;
+ continue;
+ }
+
+ // --------------------------------------------------------------------
+ // --- First update the foe list, only then a target can be picked- ---
+ // --------------------------------------------------------------------
+ checkOppMem();
+
+
+ // -----------------------------------------------------------------
+ // --- See whether the bot falls for a fear shock. ---
+ // --- If they are mortally afraid of a shocker, no other target ---
+ // --- will be picked. It is fixed on that one then. ---
+ // -----------------------------------------------------------------
+ if (shocker) {
+ DEBUG_LOG_EMO(player->getName(),
+ "Terrified by %s (fear shock: %lf)",
+ shocker->opponent->getName(), shocker->fear_shock)
+ double reshock = shocker->fear
+ - (static_cast<double>(RAND_AI_0P + 2) / 2.);
+ if (reshock >= shocker->fear_shock) {
+ isShocked = true;
+
+ // Generate a nice message telling the world that we are in awe:
+ if (!isStopped && !global.skippingComputerPlay) {
+ const char* text = player->selectPanicPhrase(shocker->opponent);
+ try {
+ if (text) {
+ // Wait for the AI to be allowed to create texts
+ while (!textAllowed.load(ATOMIC_READ))
+ std::this_thread::yield();
+
+ // Now create the instance
+ new FLOATTEXT(text,x, y - 30, .0, -.4, player->color,
+ CENTRE, TS_NO_SWAY, 150);
+ }
+ } catch (...) {
+ perror ( "aicore.cpp: Failed to allocate memory for"
+ " panic text in operator().");
+ }
+ if (text)
+ free(const_cast<char*>(text));
+ }
+
+
+ DEBUG_LOG_EMO(player->getName(),
+ "Shock confirmed with %lf over %lf",
+ reshock, shocker->fear_shock)
+ } else {
+ isShocked = false;
+ DEBUG_LOG_EMO(player->getName(),
+ "Overcame shock with %lf under %lf",
+ reshock, shocker->fear_shock)
+ }
+ }
+
+
+ // ---------------------------------------------------
+ // --- Set basic behaviour values ---
+ // --- Done here and not in initialize so the full ---
+ // --- shock check is already done. ---
+ // ---------------------------------------------------
+ findOppAttempts = ai_level + 2 - (isShocked ? ai_level / 2 : 0);
+ findRngAttempts = (std::pow(ai_level + 1, 2) + 1)
+ / (isShocked ? ai_level + 1 : 1)
+ + (isShocked ? 0 : ai_level + 4);
+ findTgtAttempts = ai_level
+ - (isShocked ? ai_level - 1 : 0)
+ + (isShocked ? 0 : 1);
+ findWeapAttempts= ai_level * 2 / (isShocked ? ai_level : 1);
+ focusRate = ai_level_d * 2.
+ / static_cast<double>(maxAiLevel * 2);
+ errorMultiplier = static_cast<double>(maxAiLevel + 1 - ai_level)
+ / static_cast<double>(findRngAttempts);
+ maxBounce = ROUNDu(std::pow(ai_level, 2) / 2.) + 2;
+ /* The results should be [if shocked]:
+ * findOppAttempts : Useless 3 [2], Deadly + 1: 8 [4]
+ * findRngAttempts : Useless: 10 [2], Deadly + 1: 60 [7]
+ * findTgtAttempts : Useless: 2 [1], Deadly + 1: 7 [1]
+ * findWeapAttempts: Useless: 2 [1], Deadly + 1: 12 [2]
+ * focusRate : Useless: 0.166, Deadly + 1: 1.0
+ * errorMultiplier : Useless: 1.2 [3], Deadly + 1: 0.02 [0.14]
+ * maxBounce : Useless: 3, Deadly + 1: 20
+ */
+
+ DEBUG_LOG_AI(player->getName(), "AI Level : %d (%s)", ai_level,
+ getLevelName(ai_level))
+ DEBUG_LOG_AI(player->getName(), "type_mod : %4.3lf", type_mod)
+ DEBUG_LOG_AI(player->getName(), "errorMultiplier: %4.3lf",
+ errorMultiplier)
+ DEBUG_LOG_AI(player->getName(), "findOppAttempts: %d", findOppAttempts)
+ DEBUG_LOG_AI(player->getName(), "findRngAttempts: %d", findRngAttempts)
+ DEBUG_LOG_AI(player->getName(), "findTgtAttempts: %d", findTgtAttempts)
+ DEBUG_LOG_AI(player->getName(), "focusRate : %4.3lf", focusRate)
+ DEBUG_LOG_AI(player->getName(), "maxBounce : %d", maxBounce)
+ DEBUG_LOG_AI(player->getName(), "needMoney : %s",
+ needMoney ? "Yes" : "No")
+
+
+ // ------------------------------------------------------------------
+ // --- The full cycle of target selection, weapon/item selection, ---
+ // --- setting up the basic combat values and targeting the ---
+ // --- selected weapon might need a few attempts. The higher the ---
+ // --- AI level, the more attempts the bot gets. If the maximum ---
+ // --- number of attempts is reached, all used methods are forced ---
+ // --- to come up with a minimum result. ---
+ // ------------------------------------------------------------------
+ int32_t tgt_attempts = 0;
+ int32_t opp_attempts = 0;
+ int32_t weap_attempts = 0;
+ bool done = false;
+
+ while (canWork && isWorking && !isStopped
+ && (needAim || !isBlocked) // End if a free is needed
+ && (tgt_attempts < findTgtAttempts) ) {
+
+ // Yield on each iteration to not hog the CPUs
+ if (!global.skippingComputerPlay)
+ std::this_thread::yield();
+
+ // ----------------------------------------------------------
+ // --- 1) Cycle target and item selection. ---
+ // --- Those are combined, because selecting a different ---
+ // --- target later might make the current item selection ---
+ // --- less effective or even useless. Thus the item is ---
+ // --- chosen individually. ---
+ // ----------------------------------------------------------
+ if (!opp_attempts && !weap_attempts) {
+ ++tgt_attempts;
+ mem_curr = nullptr;
+ DEBUG_LOG_AIM(player->getName(), "Starting setup %d / %d",
+ tgt_attempts, findTgtAttempts)
+ }
+ done = setupAttack(tgt_attempts == findTgtAttempts,
+ opp_attempts, weap_attempts);
+
+ // ----------------------------------------------------------
+ // --- 2) Calculate basic attack values. ---
+ // --- If the target and item selection is different than ---
+ // --- in the last round, new basic values must be ---
+ // --- calculated. If the selections are what this player ---
+ // --- had in the last round, this won't be needed. Just ---
+ // --- continue were we left off last round. ---
+ // ----------------------------------------------------------
+ if (done)
+ done = calcAttack(tgt_attempts);
+
+ // ----------------------------------------------------------
+ // --- 3) Aim the current selection ---
+ // ----------------------------------------------------------
+ if (done && needAim && !isBlocked)
+ done = aim( (tgt_attempts == findTgtAttempts) && needSuccess );
+ else if (!needAim || isBlocked) {
+ DEBUG_LOG_AIM(player->getName(), "No aiming done: %s, %s",
+ needAim ? "Aiming needed" : "Aiming NOT needed",
+ isBlocked ? "shot is blocked" : "Shot is NOT blocked")
+ }
+
+
+ // ------------------------------------------------------
+ // --- 4) If this round was successful, check whether ---
+ // --- A new best setup is found ---
+ // ------------------------------------------------------
+ if (done) {
+ // Reset opponent and weapon attempts if a positive score
+ // was achieved and the AI has tried enough items or the
+ // opponent selection is finished.
+ if (best_round_score > 0) {
+ if ( (weap_attempts > ai_level)
+ || (opp_attempts == findOppAttempts) ) {
+ opp_attempts = 0;
+ weap_attempts = 0;
+ }
+
+ // Tweak the score if the primary target was hit:
+ if (best_prime_hit) {
+
+ // add the weapon and opponent score, so attacks, even
+ // if they are not perfect, get emphasized if the preferred
+ // setup is chosen:
+ if ( mem_curr && revengee
+ && (player != mem_curr->entry->opponent) )
+ best_round_score += mem_curr->score
+ / (revengee == mem_curr->entry
+ ? ai_level : ai_level * 10);
+ if (weap_curr && (weap_curr->dmgSingle > 0) )
+ best_round_score += weap_curr->score / (ai_level * 10);
+ }
+ } // End of having a best_round_score greater than zero
+
+ // Note down best setup score and settings if better or
+ // forced to succeed due to last attempt condition
+ bool new_best_setup_score = (best_round_score > best_setup_score);
+ if ( ( ( new_best_setup_score && best_prime_hit)
+ || ( !best_setup_prime
+ && (new_best_setup_score || best_prime_hit) ) )
+ || ( (NEUTRAL_ROUND_SCORE == best_setup_score)
+ && (tgt_attempts == findTgtAttempts)
+ && needSuccess) ) {
+ best_setup_angle = curr_angle;
+ best_setup_item = item_curr;
+ best_setup_mem = mem_curr;
+ best_setup_overshoot = best_overshoot;
+ best_setup_power = curr_power;
+ best_setup_prime = best_prime_hit;
+ best_setup_weap = weap_curr;
+ DEBUG_LOG_AIM(player->getName(),
+ "New best setup with angle %d, power %d using %s : (%d > %d)",
+ GET_DISP_ANGLE(curr_angle), curr_power,
+ weap_idx < WEAPONS
+ ? weapon[weap_idx].getName()
+ : item[weap_idx - WEAPONS].getName(),
+ best_round_score, best_setup_score)
+ best_setup_score = best_round_score;
+
+ // This targeting round is definitely over
+ opp_attempts = 0;
+ weap_attempts = 0;
+
+ if ( needSuccess
+ && best_setup_prime
+ && (best_setup_score > 0) )
+ // There is no need to force anything any more:
+ needSuccess = false;
+
+ // Give feedback according to what has happened
+ if ( (best_round_score > 0) && best_prime_hit )
+ showFeedback("!!!", GREEN, -.5, TS_NO_SWAY, 150);
+ else
+ showFeedback("!", GREEN, -.6, TS_HORIZONTAL, 120);
+ } else if (needSuccess)
+ showFeedback("?", RED, -.7, TS_HORIZONTAL, 90);
+ } // End of setup score handling
+ } // End of full preparation cycle
+
+
+ // ---------------------------------------------------------
+ // --- If the revengee has been changed due to the score ---
+ // --- considerations, write back the new victim: ---
+ // ---------------------------------------------------------
+ if (revengee && (revengee->opponent != player->revenge)) {
+ if (revengee->opponent != player)
+ player->revenge = revengee->opponent;
+ else
+ revengee = nullptr;
+ } else if (!revengee)
+ player->revenge = nullptr;
+
+
+ // --------------------------------------------
+ // --- If no real setup could be found, see ---
+ // --- whether a freeing attempt is needed. ---
+ // --------------------------------------------
+ if (!isStopped && !isShocked
+ && !isBlocked // If these fail, aim() already has set up
+ && needAim // a freeing attempt. Do not do it twice!
+ && best_setup_weap
+ && ( (best_setup_score < 0)
+ || ( (0 == best_setup_score ) && RAND_AI_0P) ) ) {
+ DEBUG_LOG_AIM(player->getName(), "Best setup score %d too low!",
+ best_setup_score)
+
+ // First, copy best noted data (if any)
+ if (NEUTRAL_ROUND_SCORE != best_setup_score) {
+ curr_angle = best_setup_angle;
+ curr_power = best_setup_power;
+ }
+
+ // Now see whether to unbury or clear the path:
+ if ( ( buried_l >= (BURIED_LEVEL_HALF / 2) )
+ || ( buried_r >= (BURIED_LEVEL_HALF / 2) ) ) {
+ useFreeingTool(true, true);
+ calcUnbury(true);
+ } else {
+ curr_angle = best_setup_angle;
+ curr_power = best_setup_power;
+ if (RAND_AI_0P
+ || hill_detected
+ || !best_setup_weap
+ || (best_setup_weap->spread > 1)
+ || (best_setup_weap->subMunCount > 0)
+ || (REDUCER == best_setup_weap->type)
+ || (PERCENT_BOMB == best_setup_weap->type) ) {
+ useFreeingTool(false, true);
+
+ // If this is a riot bomb, flatten the angle,
+ // but only if the best overshoot (we took the
+ // angle and power from its setup) was too long.
+ // No use in firing a riot bomb behind the opponent
+ if (best_setup_overshoot > 0)
+ flattenCurrAng();
+ } else {
+ // In this case use the current weapon, but go a bit down
+ // with the angle:
+ int32_t ang_mod = 5 + RAND_AI_1P;
+
+ if (curr_angle < 180) {
+ curr_angle -= ang_mod;
+ if (curr_angle < 95)
+ curr_angle = 95;
+ } else {
+ curr_angle += ang_mod;
+ if (curr_angle > 265)
+ curr_angle = 265;
+ }
+
+ if ( (REDUCER != best_setup_weap->type)
+ && (PERCENT_BOMB != best_setup_weap->type)
+ && ( (RIOT_BOMB > best_setup_weap->type)
+ || (RIOT_BLAST < best_setup_weap->type) ) )
+ useWeapon(best_setup_weap->type);
+ else
+ useWeapon(SML_MIS);
+ } // end of using best setup weapon.
+ }
+
+ sanitizeCurr();
+ angle = curr_angle;
+ power = curr_power;
+ needAim = false;
+ best_setup_weap = nullptr;
+ isBlocked = true;
+
+ showFeedback("???", PURPLE, -.6, TS_NO_SWAY, 100);
+ }
+
+
+ // ----------------------------------------
+ // --- Write back the best attack setup ---
+ // ----------------------------------------
+ if (!isStopped && needAim && !isBlocked ) {
+ curr_angle = best_setup_angle;
+ curr_power = best_setup_power;
+ item_curr = best_setup_weap ? nullptr : best_setup_item;
+ mem_curr = best_setup_mem;
+ weap_curr = item_curr ? nullptr : best_setup_weap;
+ weap_idx = weap_curr ? weap_curr->type
+ : item_curr ? item_curr->type + WEAPONS
+ : 0;
+
+ sanitizeCurr();
+ angle = curr_angle;
+ power = curr_power;
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Using best setup with angle %d, power %d using %s (Score %d)",
+ GET_DISP_ANGLE(angle), power,
+ weap_idx < WEAPONS
+ ? weapon[weap_idx].getName()
+ : item[weap_idx - WEAPONS].getName(),
+ best_setup_score)
+ } else if (!isStopped) {
+ // Note: Without aiming or when blocked, the setup memory
+ // was not used in some cases.
+ curr_angle = angle;
+ curr_power = power;
+ sanitizeCurr();
+ angle = curr_angle;
+ power = curr_power;
+ }
+
+
+ // ------------------------------------------------
+ // --- If a weapon is chosen and the target is ---
+ // --- the revengee, get a message out for them ---
+ // ------------------------------------------------
+ if (!isStopped && weap_curr && revengee && needAim
+ && (revengee == best_setup_mem->entry)
+ && best_setup_prime && !needSuccess
+ && !global.skippingComputerPlay
+ && (weap_curr->dmgSingle > 1.)
+ && (weap_curr->dmgSingle >= (mem_curr->opLife * ai_over_mod / 2.))
+ && RAND_AI_1P) {
+ const char* text = player->selectRetaliationPhrase();
+ try {
+ if (text) {
+ // Wait for the AI to be allowed to create texts
+ while (!textAllowed.load(ATOMIC_READ))
+ std::this_thread::yield();
+
+ // Now create the instance
+ new FLOATTEXT(text,x, y - 30, .0, -.4, player->color,
+ CENTRE, TS_NO_SWAY, 150);
+ }
+ } catch (...) {
+ perror ( "aicore.cpp: Failed to allocate memory for"
+ " retaliation text in operator().");
+ }
+ if (text)
+ free(const_cast<char*>(text));
+ }
+
+
+ // -------------------------------------------------
+ // --- Tell the world this tank is going bye bye ---
+ // -------------------------------------------------
+ if ( !isStopped
+ && (mem_curr->entry->opponent == player)
+ && !global.skippingComputerPlay) {
+ try {
+ // Wait for the AI to be allowed to create texts
+ while (!textAllowed.load(ATOMIC_READ))
+ std::this_thread::yield();
+
+ // Now create it
+ new FLOATTEXT (player->selectKamikazePhrase(),
+ x, y - 30, .0, -.4,
+ player->color, CENTRE, TS_NO_SWAY, 300);
+ } catch (...) {
+ perror ( "aicore.cpp: Failed allocating memory for"
+ " kamikazeText in operator().");
+ }
+ }
+
+
+ // ---------------------------------------
+ // --- Apply some "last second" errors ---
+ // ---------------------------------------
+ if (!isStopped && needAim && !isBlocked
+ // Assume that bots can 'fix' errors from the last round:
+ && ( (nullptr == mem_curr) || (mem_curr->entry != last_opp) )
+ && RAND_AI_1N) {
+ double ang_err = (rand() % 8) * errorMultiplier;
+ double pow_err = (rand() % 36) * errorMultiplier;
+
+ // Angles always go 'up', but never over the top
+ if (angle > (180. + ang_err))
+ curr_angle = angle - ang_err;
+ else if (angle < (180. - ang_err))
+ curr_angle = angle + ang_err;
+
+ // Power error is always a raise
+ curr_power = power + pow_err;
+
+ sanitizeCurr();
+
+ DEBUG_LOG_AIM(player->getName(),
+ "Last second errors: Angle %d° -> %d°), Power: %d -> %d)",
+ GET_DISP_ANGLE(angle), GET_DISP_ANGLE(curr_angle),
+ power, curr_power)
+
+ showFeedback("*fumble*", RED, -.8, TS_NO_SWAY, 100);
+
+ angle = curr_angle;
+ power = curr_power;
+
+ }
+ assert( (angle == curr_angle) && "ERROR: Finished but angle not set!");
+ assert( (power == (curr_power - (curr_power % 5)))
+ && "ERROR: Finished but power not set!");
+ assert( ( (weap_idx >= WEAPONS) || (0 == weapon[weap_idx].warhead) )
+ && "ERROR: Not usable warhead chosen!");
+ assert( (weap_idx >= 0) && (weap_idx < THINGS)
+ && env.isItemAvailable(weap_idx)
+ && "ERROR: Unavailable or invalid weap_idx!");
+ assert( ( (weap_idx >= WEAPONS) || (player->nm[weap_idx] > 0) )
+ && "ERROR: Weapon chosen that is out of stock!");
+ assert( ( (weap_idx < WEAPONS) || (player->ni[weap_idx - WEAPONS] > 0) )
+ && "ERROR: Item chosen that is out of stock!");
+ assert( ( (weap_idx < WEAPONS)
+ || ((weap_idx - WEAPONS) < ITEM_LGT_SHIELD)
+ || ((weap_idx - WEAPONS) == ITEM_FUEL)
+ || ((weap_idx - WEAPONS) == ITEM_ROCKET) )
+ && "ERROR: The chosen item is not usable!");
+
+
+ // ---------------------------------------
+ // --- Wait for the weapon to be fired ---
+ // ---------------------------------------
+ if (!isStopped) {
+ plStage = PS_FIRE; // It can be fired now
+
+ DEBUG_LOG_AI(player->getName(),
+ "Finished thinking, waiting to fire %s against %s",
+ weap_curr
+ ? weapon[weap_idx].getName()
+ : item[weap_idx - WEAPONS].getName(),
+ mem_curr
+ ? mem_curr->entry->opponent->getName()
+ : "Nobody")
+
+ actionCondition.wait(actionLock, [this]{
+ return (!canWork || !isWorking || isStopped
+ || (PS_CLEANUP == plStage) );
+ } );
+ }
+
+
+ // --------------------------------------
+ // --- Remember the current selection ---
+ // --- (But only if it was hit) ---
+ // --------------------------------------
+ if (!isStopped && best_setup_prime && (best_setup_mem == mem_curr) )
+ player->setLastOpponent(mem_curr ? mem_curr->entry : nullptr);
+ else
+ player->setLastOpponent(nullptr);
+
+ DEBUG_LOG_AI(player->getName(), "Cleaning up...", 0)
+
+ // --------------------
+ // --- Clean up ---
+ // --------------------
+ angle = 180;
+ power = MAX_POWER / 2;
+ curr_angle = 180;
+ curr_power = MAX_POWER / 2;
+ curr_overshoot = MAX_OVERSHOOT;
+ weap_idx = SML_MIS;
+ blast_big = 0.;
+ blast_max = 0.;
+ blast_med = 0.;
+ blast_min = 0.;
+ textAllowed.store(false, ATOMIC_WRITE);
+
+ // Note: There is no need to clean up the memory chain.
+ // It is only created once, all players have the same
+ // size, and the getMemory() method reuses an existing
+ // one.
+ player = nullptr;
+ tank = nullptr;
+
+ // Eventually signal that the work has finished.
+ plStage = PS_AI_IS_IDLE;
+ isWorking = false;
+ } // End of not being stopped
+
+ isFinished = true;
+}
+
+
+/// =========================================
+/// === Helper list entry implementations ===
+/// =========================================
+
+
+/// @brief explicit constructor adding the instance to the list
+sItemListEntry::sItemListEntry(sItemListEntry* prev_) :
+ prev(prev_)
+{
+ if (prev) {
+ next = prev->next;
+ prev->next = this;
+
+ if (next)
+ next->prev = this;
+ }
+}
+
+
+/// @brief The destructor removes the element from the list
+sItemListEntry::~sItemListEntry()
+{
+ if (prev) {
+ prev->next = next;
+ prev = nullptr;
+ }
+ if (next) {
+ next->prev = prev;
+ next = nullptr;
+ }
+}
+
+
+/// @brief explicit constructor adding the instance to the list
+sOppMemEntry::sOppMemEntry(sOppMemEntry* prev_) :
+ prev(prev_)
+{
+ if (prev) {
+ next = prev->next;
+ prev->next = this;
+
+ if (next)
+ next->prev = this;
+ }
+}
+
+
+/// @brief The destructor removes the element from the list
+sOppMemEntry::~sOppMemEntry()
+{
+ if (prev) {
+ prev->next = next;
+ prev = nullptr;
+ }
+ if (next) {
+ next->prev = prev;
+ next = nullptr;
+ }
+ entry = nullptr;
+}
+
+
+/// @brief explicit constructor adding the instance to the list
+sWeapListEntry::sWeapListEntry(sWeapListEntry* prev_) :
+ prev(prev_)
+{
+ if (prev) {
+ next = prev->next;
+ prev->next = this;
+
+ if (next)
+ next->prev = this;
+ }
+}
+
+
+/// @brief The destructor removes the element from the list
+sWeapListEntry::~sWeapListEntry()
+{
+ if (prev) {
+ prev->next = next;
+ prev = nullptr;
+ }
+ if (next) {
+ next->prev = prev;
+ next = nullptr;
+ }
+}
+
+
diff --git a/src/aicore.h b/src/aicore.h
new file mode 100644
index 0000000..f99eff9
--- /dev/null
+++ b/src/aicore.h
@@ -0,0 +1,950 @@
+#pragma once
+#ifndef ATANKS_SRC_AICORE_H_INCLUDED
+#define ATANKS_SRC_AICORE_H_INCLUDED
+
+/*
+ * atanks - obliterate each other with oversize weapons
+ * Copyright (C) 2003 Thomas Hudson
+ *
+ * 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.
+ *
+ */
+
+/** @file aicore.h
+ * @brief Home of the AICore class
+ *
+ * This class substituted the old AI code in the PLAYER class.
+ *
+ * While the old AI worked pretty well in most situations, it did
+ * lag the game on more intense calculations.
+ * Further there were some situations (like peaks in the way and
+ * such things) that the old AI could not handle.
+ * It could have been fixed, but would have lagged the game even more.
+ *
+ * By outsourcing the AI to its own class and providing an
+ * operator()(), the AI calculations could be put in a background
+ * thread. This resulted in a much smoother gaming experience.
+ *
+ * Further the AI is now easier to debug, to maintain and to extend
+ * than it was possible with the old AI code.
+**/
+
+
+#include "player_types.h"
+#include "globaltypes.h"
+#include "floattext.h"
+
+#include <condition_variable>
+
+// A few often needed randomization shortcuts
+#define RAND_AI_0P (rand() % ai_level)
+#define RAND_AI_0N (0 == (rand() % ai_level))
+#define RAND_AI_1P (rand() % (ai_level + 1))
+#define RAND_AI_1N (0 == (rand() % (ai_level + 1)))
+
+// Init and check for round scores
+#define NEUTRAL_ROUND_SCORE -1000000
+
+#ifndef HAS_PLAYER
+class PLAYER;
+struct sOpponent;
+#endif // HAS_PLAYER
+class TANK;
+
+// These are restricted to aicore.cpp, as
+// they are of no use anywhere else. - sed
+struct sItemListEntry;
+struct sOppMemEntry;
+struct sWeapListEntry;
+
+
+/** @class AICore
+ * @brief Core AI, written to be used as a background thread.
+ *
+ * The AI operator() is the main function that does all the work.
+ *
+ * If you need to edit something in that work flow, please try to
+ * keep the documentation up-to-date.
+ *
+ * The following is the work flow in AICore::operator().
+ *
+ *
+ * Initialization in initialize():
+ * --------------------------------
+ * ai_level : (player->type) This is meant as a short cut being a widely usable int32_t.
+ * ai_level_d : (ai_level) ai_level cast to double.
+ * ai_over_mod : (from level) Multiplier [1.1;1.5] according to ai_level_d, used to evaluate overkills and similar.
+ * ai_type_mod : (from level) Multiplier [1.0;3.0] according to ai_level_d, used on important decisions to
+ * strengthen higher bot levels.
+ * blast_* : (0) Damage values of the available normal missiles.
+ * isShocked : (false) Whether the bot is shocked by anothers massive damage they dealt.
+ * revengee : (nullptr) sOpponent pointer set to a bot this one wants revenge against.
+ * shocker : (nullptr) sOpponent pointer set to the bot that caused isShocked to become true.
+ * needSuccess : (true) This is set to true and later set to false if a full targetting round resulted in a
+ * primary target hit, or the full score is greater than zero.
+ * needAim : (true) Causes aim() to be called. Set to false if the bot needs to free themselves.
+ * isBlocked : (false) Set to true if an obstacle is detected or the bot is buried.
+ * hill_detected : (false) Set to true if the progress in aiming suggest, that a hill is in the firing path.
+ * needMoney : (check done) Set to true if getMoneyToSave() returns more money than the player has.
+ * tank : (player->tank) Short cut.
+ * angle : (tank->a) Written back current angle set on the tank.
+ * power : (tank->p) Written back current power set on the tank.
+ * weap_idx : (tank->cw) Written back currently chosen weapon.
+ * x : (tank->x) Written back current x position of the tank.
+ * y : (tank->y) Written back current y position of the tank.
+ * currLife : (tank->l + sh) Both live and remaining shield strength.
+ * buried : (check done) Number of true angles where the turret is directly covered by dirt.
+ * buried_l : (check done) Same as buried, but left side only
+ * buried_r : (check done) Same as buried, but right side only
+ * maxLife : (check done) The current maxLife value of the tank for this game round.
+ *
+ * Information from last round:
+ * last_opp : (from player) sOpponent pointer set to the opponent that was attacked last.
+ * last_ang : (180 / angle) If a last_opp is set, store current tank angle, initialize with 180 otherwise.
+ * last_pow : (1000 / power) If a last_opp is set, store current tank power, initialize with 1000 otherwise.
+ * last_weap : (0 / weapon) If a last_opp is set, store current tank weapon, initialize with 0 (Small Missile)
+ * otherwise.
+ * If a last weapon/item is set, it is selected for the current first evaluation, too.
+ * This is done so the first weapon/item selection will chose something different than
+ * was tried in the last round.
+ *
+ * Information for the current targetting round:
+ * curr_angle : (angle) The angle for the current aiming round.
+ * curr_power : (power) The power for the current aiming round.
+ * best_round_score: (NEUTRAL_ROUND_SCORE) Best score achieved for the current opponent.
+ *
+ * Best achieved values over the full targetting cycle:
+ * best_setup_angle : (angle)
+ * best_setup_item : (nullptr)
+ * best_setup_mem : (nullptr)
+ * best_setup_overshoot : (MAX_OVERSHOOT)
+ * best_setup_power : (power)
+ * best_setup_score : (NEUTRAL_ROUND_SCORE)
+ * best_setup_weap : (nullptr)
+ *
+ * Eventually a check is made to decide whether the bot gets lucky. This temporarily raises its AI level.
+ *
+ *
+ * Initialization in checkOppMem():
+ * --------------------------------
+ * Every opponent entry is evaluated, and a (possible) current shocker and/or revengee are determined.
+ * The revengee is written back to the player if it is a new one.
+ * The opponent memory list is then sorted by score in descending order.
+ *
+ *
+ * Re-Check shocked state:
+ * --------------------------------
+ * At this point it is checked again whether the bot is really in terror. Although difficult,
+ * it is possible for a bot to overcome their shock here. This is done so the bots won't be
+ * shocked almost constantly from round 2 on.
+ *
+ *
+ * Initialization in operator():
+ * --------------------------------
+ * findTgtAttempts : Targetting attempts. Number of full cycles of finding an attack setup.
+ * Counted using tgt_attempts in the operator() loop.
+ * findOppAttempts : Opponent attempts. How many opponents are tried per targetting attempt.
+ * Counted using opp_attempts in the operator() loop.
+ * findWeapAttempts: Weapon attempts. How many weapons / items are tried per opponent attempt.
+ * Counted using weap_attempts in the operator() loop.
+ * findRngAttempts : Range find attempts. How many corrections to the aiming are done per weapon attempt.
+ * Counted using rng_attempts in aim() loop.
+ * focusRate : This is a modifier that lessens some corrections the lower the ai level is.
+ * errorMultiplier : This is a modifier that lessens some errors the higher the ai level is.
+ * maxBounce : The higher the AI level the more bounces/wraps from walls it can follow before
+ * assuming the shot has crashed.
+ *
+ * The results should be [if shocked]:
+ * findTgtAttempts : Useless: 2 [1], Deadly + 1: 7 [1]
+ * findOppAttempts : Useless 3 [2], Deadly + 1: 8 [4]
+ * findWeapAttempts: Useless: 2 [1], Deadly + 1: 12 [2]
+ * findRngAttempts : Useless: 10 [2], Deadly + 1: 60 [7]
+ * focusRate : Useless: 0.166, Deadly + 1: 1.0
+ * errorMultiplier : Useless: 1.2 [3], Deadly + 1: 0.02 [0.14]
+ * maxBounce : Useless: 3, Deadly + 1: 20
+ *
+ * As the full number of aimings can be up to Tgt*Opp*Weap*Rng attempts, the current targetting attempt
+ * is finished once a new best attack plan is found.
+ *
+ * The full cycle of target selection, weapon/item selection, setting up the basic combat values and
+ * targeting the selected weapon might need a few attempts.
+ * The higher the AI level, the more attempts the bot gets. If the maximum number of attempts is
+ * reached, all used methods are forced to come up with a minimum result.
+ *
+ * Local counters for the operator() loop:
+ * ---------------------------------------
+ * tgt_attempts : (0) The number of attempts to set up a succesful attack the AI already had.
+ * opp_attempts : (0) The number of attempts to find a suitable opponent the AI already had.
+ * weap_attempts : (0) The number of attempts to find a suitable weapons or item the AI already had.
+ * done : (false) Control variable set by each section method on success (true) or failure (false).
+ *
+ * Cycle in operator():
+ * --------------------------------
+ *
+ * The loop is working until the AI can no longer work, aiming is needed or the AI is not blocked, and
+ * tgt_attempts is lower than findTgtAttempts.
+ *
+ * 1 Cycle target and item selection if both opp_attempts and weap_attempts are both zero.
+ * Those are combined, because selecting a different target later might make the current item selection less
+ * effective or even useless. Thus the item is chosen individually.
+ * tgt_attempts : (+1) The new target and item selections marks the beginning of a new targetting attempt.
+ * mem_curr : (nullptr) The currently selected opponent.
+ *
+ * 2 done = setupAttack(bool is_last, int32_t &opp_attempt, int32_t &weap_attempt)
+ *
+ * Param 1 : is_last : is set to true if tgt_attempts equals findTgtAttempts.
+ * Param 2 : opp_attempt : Reference to opp_attempts to have the actual opponent selection counted.
+ * Param 3 : weap_attempt: Reference to weap_attempts to have the actual weapon/item selection counted.
+ *
+ * Here, the basic setup is done by selecting an opponent and a suitable item
+ * or weapon to use against them.
+ *
+ * 2.1 If either weap_attempt is 0 or mem_curr is nullptr, a new opponent selection round is started.
+ * mem_curr can be nullptr if the previous try to find an opponent failed.
+ * plStage : (PS_SELECT_TARGET)
+ * opp_attempt : (+1) Raised by one when calling selectTarget() below.
+ *
+ * 2.1.1 selectDone = selectTarget(bool is_last)
+ * Param 1 : is_last : This is set to true if the raised opp_attempt equals findOppAttempts and is_last
+ * was already set to true in setupAttack() and needSuccess is true.
+ * 2.1.1.1 If the bot is shocked, their shocker is preselected and true is returned.
+ * 2.1.1.2 If the bot has a grudge against someone, the revengee is preselected and true is returned, but only
+ * if either no opponent was selected yet, or the last opponent was someone else.
+ * However, if is_last is true, the revengee is selected even if it is two times in a row this way.
+ * 2.1.1.3 If nothing was preselected, the ordered list of opponents is simply walked down. However, if is_last
+ * is true, the first entry is always selected, because they are primarily wanted anyway.
+ *
+ * 2.1.2 If an opponent is selected and it is not the same as the last time one was selected, then the
+ * following initializations are done:
+ * item_curr : (nullptr) The currently selected item or nullptr if weap_curr is used.
+ * item_last : (nullptr) The item selected in the last item selection or nullptr if none was selected.
+ * weap_curr : (nullptr) The currently selected weapon or nullptr if item_curr is used.
+ * weap_last : (nullptr) The weapon selected in the last weapon selection or nullptr if none was
+ * selected. item_* and weap_* are used for weap_attempts, which are done
+ * findWeapAttempts times for each opp_attempts cycle.
+ * best_round_score : (NEUTRAL_ROUND_SCORE) A new target is always a complete new targetting round.
+ *
+ * To make this clear:
+ * - For each targeting attempt (tgt_attempts count to findTgtAttempts) up to findOppAttempts attempts are
+ * granted to find a suitable opponent.
+ * - For each opponent selection attempt (opp_attempts count to findOppAttempts) up to findWeapAttempts
+ * attempts are granted to select a suitable item or weapon.
+ * - For each weapon/item selection (weap_attempts count to findWeapAttempts) up to findRngAttempts attempts
+ * are granted to the AI to actually aim the selected weapon/item on the selected opponent.
+ *
+ * 2.2 Otherwise, if both weap_attempt is greater than 0 and mem_curr is not nullptr, the current target is kept
+ * and it is recorded that no new target was selected.
+ *
+ * 2.3 If the target selection was successful, the next item to use or weapon to fire can be selected.
+ * plStage : (PS_SELECT_WEAPON)
+ * weap_attempt : (+1) Raised by one when calling selectItem() below.
+ *
+ * 2.3.1 If the current target is new, the score lists for items and weapons are regenerated.
+ * 2.3.2 At this point selectDone is set to false.
+ * 2.3.3 While selectDone is false, and the AI can work, and weap_attempt is lower than findWeapAttempts,
+ * selectDone gets the return value of selectItem(bool is_last).
+ * Param 1 : is_last : This is set to true if the raised weap_attempt equals findWeapAttempts and is_last
+ * was already set to true in setupAttack() and needSuccess is true.
+ *
+ * 2.3.3.1 store item_curr in item_last and weap_curr in weap_last. This is needed for the regular walking
+ * down the ordered lists of weapons and items.
+ * 2.3.3.2 If the bot is shocked, or if it already has a best setup where the primary target was hit, a random
+ * weapon is chosen. This is done to give bots a wider range of opportunities to find an even better
+ * setup with weapons they would normally not chose in their current situation.
+ * And, on the other hand, a shocked bot does not really think but selects something random in panic.
+ * Reducers, Dirt weapons and weapons they do not have in stock are skipped.
+ * 2.3.3.3 Otherwise advance in the list of weapons. If no weapon was found (list exhausted), the first entry
+ * is preselected.
+ * 2.3.3.4 A shocked bot now ensures no item is selected, and if no weapon was available, the first one is
+ * chosen. Here the method ends and returns true.
+ * 2.3.3.5 Not shocked bots now rotate through their list of available items. If no items are left to try,
+ * item_curr is set to nullptr to indicate that no item selection is possible.
+ * 2.3.3.6 Unless the opponent has more than ten times the live points (including shields) than the bot,
+ * kamikaze selections are undone.
+ * 2.3.3.7 Unless the bot is buried or blocked, teleporter item and riot weapon selections are undone.
+ * 2.3.3.8 If both a weapon and an item was found, unselect the one with the lower score.
+ * 2.3.3.9 If neither weapon nor item was selected, but is_last is true, the small missile is selected.
+ *
+ * Eventually return true if either weapon or item is selected and false if both are nullptr.
+ *
+ * 2.3.4 If all weapon selection attempts were used, but there are opponent selections left, weap_attempt is
+ * reset to zero so another opponent can be selected.
+ *
+ * 2.4 If all weapon selection attempts for all opponent selection attempts were used up, breakUp is set to true
+ * to indicate that all that could be done was tried.
+ * 2.5 If everything failed and this is the very last setup attempt, an emergency plan kicks in:
+ * a) Try to teleport away.
+ * b) If the bot has no teleporter in stock and the currently selected opponent is not buried, try to select
+ * a swapper.
+ * c) If no swapper was available or the currently selected opponent is buried, try the mass teleport.
+ * d) If the bot has no mass teleport, select small missile.
+ * 2.6 If an item or weapon is chosen that shall be used to self destruct, check again whether this is really
+ * wanted. If so, set mem_curr to the own entry, otherwise the selection either failed, or, if this is the
+ * very last attempt and there was no successful setup, yet, revert to small missile.
+ * 2.7 If breakUp was set to true and this is not the last attempt, opp_attempt and weap_attempt are both reset
+ * to zero to trigger a new full targetting cylcle.
+ *
+ * 3 done = calcAttack(int32_t attempt) - called if setupAttack() returned true.
+ *
+ * Param 1 : Value of tgt_attempts
+ *
+ * Here the basic attack values are calculated, trying to find an angle and a power value to begin with. These are
+ * then checked and adapted according to the chosen item or weapon, the selected target, the situation of the bots
+ * tank and whether the current level has a ceiling ("boxed mode") or not.
+ *
+ * bool is_last : (checked) Set to true if attempt equals findTgtAttempts and no successful setup was
+ * found, yet.
+ * plStage : (PS_CALCULATE) AI enters the calculation stage.
+ * isBlocked : (false) Will be set to true if the aiming finds out that a hill blocks the path.
+ * needAim : (false) Will be set to true if a weapon is chosen that needs aiming.
+ * curr_overshoot : (MAX_OVERSHOOT) The overshoot for the current aiming round.
+ * offset_x : (0) Some weapons need a horizontal offset to aim at, like the napalm weapons.
+ * offset_y : (0) Some weapons need a vertical offset to aim at, like the driller.
+ *
+ * 3.1 If an item is chosen, the currently set angle and power are written into curr_angle and curr_power. There
+ * is no need for further investigation and aiming, so the method then returns true.
+ * 3.2 If the currently set opponent equals last_opp, and the currently set weapon is the same as what was used
+ * against the last opponent, copy back the angle and power used in the last round and be done.
+ * It is then tried to optimized the last attack.
+ * This is only done if the same weapon is used, as other weapons might need different approaches.
+ * 3.3 If a laser is chosen and the tank is not buried enough to be evaluated as buried or blocked, return the
+ * result of calcLaser(is_last is_last).
+ *
+ * Param 1 : True if is_last in calcAttack() is true and needSuccess is true.
+ *
+ * Set the angle to point directly at the selected opponent and see whether the opponent can be hit or not.
+ *
+ * int32_t old_angle : (curr_angle) Backup the currently used angle to write it back if the aiming fails.
+ * int32_t old_power : (curr_power) Backup the currently used power to write it back if the aiming fails.
+ * double drift : (calculated) Variation of the angle to simulate that an exact aiming needs skill.
+ *
+ * 3.3.1 Set curr_power to the current tanks power, so no power change is necessary if this becomes the
+ * attack that is performed.
+ * 3.3.2 Set curr_angle to an angle pointing directly at the selected target and add the value of 'drift'.
+ * 3.3.3 Follow the beam using a mind shot and calculate a hit score.
+ * 3.3.4 Use tank->shootClearance() to see whether the shot is blocked or crashes.
+ * If the shot does not reach the target, and is_last is true, the hit_score (might have hit someone else)
+ * is reduced. If this is not the very last attempt, curr_angle and curr_power are written back, needAim is
+ * set to true and false is returned.
+ *
+ * 3.4 If the tank is buried, return the result of calcUnbury(bool is_last).
+ *
+ * Param 1 : The value of is_last is simply transported.
+ *
+ * Make sure that whatever is chosen is appropriate and points into the right direction.
+ *
+ * 3.4.1 If either a riot bomb is chosen, or a non-shaped weapon is selected while a self destruct attempt is
+ * planned (*), the number of enemies on each side is counted to chose where to fire at.
+ * After setting curr_angle and curr_power to appropriate values, they get sanitized, and angle is set to
+ * curr_angle, power is set to curr_power, needAim is set to false as no further aiming is needed, and
+ * isBlocked is set to true as this is the situation.
+ * After that true is returned.
+ * 3.4.2 If a weapon is chosen and no self destruction is wanted, the shaped weapons are the only appropriate
+ * weapons to free the trank without damaging itself. The current angle is set to 180°, the current power
+ * is set to 10 plus some random variation according to the AI level. The current values then get
+ * sanitized, and angle is set to curr_angle, power is set to curr_power, needAim is set to false as no
+ * further aiming is needed, and isBlocked is set to true as this is the situation.
+ * After that true is returned.
+ * 3.4.3 If this all fails but is_last is set to true, useFreeingTool() is called for an emergency selection.
+ * As a last resort, the small missile is selected if useFreeingTool() did not succeed. The current angle
+ * is set to a value between 100° and 160° degrees to either the left or right side, according to which
+ * side is heavier buried. The current power is set to a value between 500 and 1,000. The current values
+ * then get sanitized, and angle is set to curr_angle, power is set to curr_power, needAim is set to false
+ * as no further aiming is needed, and isBlocked is set to true as this is the situation.
+ * After that true is returned.
+ * 3.4.4 If everything failed, false is returned.
+ *
+ * 3.5 If the currently selected target is the bot itself, this is a self destruct attempt. Return the result of
+ * calcKamikaze(bool is_last), then.
+ *
+ * Param 1 : The value of is_last is simply transported.
+ *
+ * As, at this point, a weapon is chosen, it must be checked whether it can be used and determined where to
+ * fire it.
+ *
+ * 3.5.1 If a horizontal shaped weapon is selected, a free flat spot of the same height as where the tanks stands
+ * on either the left or right side must be found.
+ * If such a spot can be found, curr_angle and curr_power are set to values trying to hit it.
+ * 3.5.2 If a napalm weapon is chosen, curr_angle is set to 135° to the left or right side, whichever side has
+ * head wind. curr_power is set to 100 plus an amount calculated using the current wind strength.
+ * 3.5.3 Otherwise fire the weapon upwards with more power the higher the spread value.
+ * 3.5.4 If this worked out, sanitize the current angle and power, write them into to angle and power, and return
+ * true.
+ * 3.5.5 If this didn't work out but this is the last attempt, go through all valid self destruct items and
+ * weapons until one is available or the small missile is selected. This is then just fired upwards and
+ * true is returned.
+ * 3.5.6 In all other cases false is returned.
+ *
+ * 3.6 Otherwise this is normal aiming, and calcStandard(bool is_last, bool allow_flip_shot) is used to generated
+ * the initial values to begin with.
+ *
+ * Param 1 : The value of is_last is simply transported.
+ * Param 2 : true, if the pre-incremented mem_curr->attempts is an even number, false otherwise.
+ * If set to true, the bot is allowed to shoot in the opposite direction. On steel walls, this
+ * parameter is ignored.
+ *
+ * This method does no aiming but sets needed offsets and generates an angle and a power value to begin with.
+ *
+ * 3.6.1 calculate the offsets for the x and y coordinate needed by the chosen weapon. This is done with the
+ * method calcOffset(bool is_last).
+ *
+ * Param 1 : The value of is_last is simply transported.
+ *
+ * This method calculate x and y offsets for weapons that need it. These offsets are stored in the AICore
+ * members offset_x and offset_y, as they are needed in multiple places.
+ *
+ * If the needed offset is off the screen, or makes no sense, the method returns false. But if is_last is
+ * set to true, insane offsets are tried to be fixed. The idea is, that the bot tries nevertheless out of
+ * pure desperation.
+ *
+ * 3.6.1.1 Napalm weapons need a horizontal offset where the wind blows the jellies over the opponent for
+ * greatest effect.
+ * 3.6.1.2 Shaped charges and their bigger versions need a horizontal offset that leads to a hit at either side
+ * of the opponent where the land height is equal enough to where the opponent stands, that the blast
+ * actually hits.
+ * 3.6.1.3 The driller must be placed above a buried tank, or under it if it is clinging to a steep hill side.
+ *
+ * 3.6.2 Calculate the starting angle, but limit it to:
+ * - 20° to 35° if the opponent is below the bot,
+ * - 40° to 55° if the opponent is at about the same height and
+ * - 60° to 75° if the opponent is above the bot.
+ * The angle is then modified according to the focusRate of the bot.
+ * 3.6.3 If a wrap wall is in place, check whether shooting through it results in a shorter shot, and flip the
+ * angle if it is.
+ * 3.6.4 If allow_flip_shot is true and the wall is something else than a steel wall, bots may flip the shot with
+ * a chance of 50% for a useless bot and 80% for a deadly bot.
+ * 3.6.5 Raise the angle until there is enough shooting clearance or the ceiling is hit.
+ * 3.6.6 If the angle becomes too steep, revert to the half way between the beginning angle and the currently
+ * raised one. If this means an obstacle is in the way, the bot might decide to remove it first.
+ * 3.6.7 Calculate starting power as a raw estimation using the simple distance.
+ * 3.6.8 If the shot is already known to be blocked, write back the current angle and power to the used angle
+ * and power and set needAim to false.
+ *
+ * 3.7 In boxed mode, the situation regarding the ceiling must be checked, but only if calcStandard() succeeded,
+ * the tank is not blocked and a weapon is chosen that needs aiming.
+ * This is done by calling calcBoxed(bool is_last).
+ *
+ * Param 1 : The value of is_last is simply transported.
+ *
+ * 3.7.1 If is_last is false, the bot might "forget" to check for ceiling hits. The chance is between 33% for the
+ * useless bot and 7% for the deadly+1 bot.
+ * 3.7.2 As long as the shot is regarded to be crashed, but the tracing was finished (not too many bounces/wraps)
+ * and either angle or power can be modified, traceShot() is used to see where the shot would end with the
+ * current angle and power.
+ *
+ * 3.7.2.1 If the shot ends in a steel wall or ceiling, or if the shot hits the floor bottom through a wrap
+ * ceiling but is not a digging weapon, the shot is regarded to be crashed.
+ * 3.7.2.2 If the shot crashed, either the current angle, power or both are modified by one step. The angle is
+ * changed to flatten the shot and the power is reduced.
+ * 3.7.2.3 If the angle reaches 90°/270°, it can no longer be modified.
+ * 3.7.2.4 If the power reaches MIN_POWER, it can no longer be modified.
+ *
+ * 3.7.3 If the shot has not crashed and the tracing was finished but the shot did not reach its target, the path
+ * is considered to be blocked. If is_last is true and the map has a steel or wrap wall and there was no
+ * positive setup score already, the path is tried to be cleared.
+ *
+ * 3.7.3.1 Decide whether to free the tank or to remove an obstacle.
+ * 3.7.3.2 Use calcUnbury() if the tank is to be freed.
+ * 3.7.3.3 Flatten the current angle if an obstacle is to be removed.
+ * 3.7.3.4 Set needAim to false and isBlocked to true.
+ * 3.7.3.5 Directly return true.
+ *
+ * 3.7.4 If no emergency freeing is possible, is_last is false, or its the wrong wall type or a positive setup
+ * has already been found, directly return false.
+ * 3.7.5 In all other cases return true if the last shot did not crash or if is_last is true, and false
+ * otherwise.
+ *
+ * 3.8 Eventually return the value of 'result'.
+ *
+ * 4 done = calcAim(bool is_last) - called if calcAttack() returned true, needAim is true and isBlocked is false.
+ *
+ * Param 1 : True if tgt_attempts equals findTgtAttempts and needSuccess is still true.
+ *
+ * Here the aiming is done for the selected weapon against the selected target.
+ *
+ * plStage : (PS_AIM) The AI enters the aiming stage.
+ * hill_detected : (false) Some situations indicate that a hill is between the tank and its target.
+ * best_score : (NEUTRAL_ROUND_SCORE) Used to record the best setup for the current aiming round.
+ * best_angle : (angle) Used to record the angle that achieved the best aiming round score.
+ * best_power : (power) Used to record the power that achieved the best aiming round score.
+ * best_prime_hit : (false) Used to record whether the best aiming round settings hit the primary
+ * target.
+ * best_overshoot : (MAX_OVERSHOO) Used to record the overshoot the best setup had.
+ * last_ang_mod : (0) Note down the used angle modifications, so the next aiming attempt knows
+ * the last modification to the angle.
+ * last_pow_mod : (0) Note down the used power modifications, so the next aiming attempt knows
+ * the last modification to the power.
+ * last_overshoot : (MAX_OVERSHOOT) Note down the achieved overshoot, so the next aiming attempt knows
+ * whether the shot gets nearer to the target or not.
+ * last_reverted : (false) Set to true if an aiming attempt reverts the previous modifications.
+ * last_score : (0) Note down the achieved score, so the next aiming attempt knows whether
+ * the new hit is really better or not.
+ * last_was_better: (false) Set to true if the previous score was better than the current one.
+ * reached_x : (x) Used as a short cut and memory of where the current attempt hits.
+ * reached_y : (y) Used as a short cut and memory of where the current attempt hits.
+ *
+ * The aiming is done as long as the number of aiming attempts have not reached findRngAttempts.
+ *
+ * 4.1 Generate an angle modifier in the interval [1;7], with 2 being the maximum for the useless and 7 the
+ * maximum for the deadly+1 AI, and a power modifier in the interval [10;340] with 220 being the maximum for
+ * useless and 340 being the maximum for deadly bots.
+ *
+ * 4.2 Use void traceWeapon(int32_t &has_crashed, int32_t &has_finished)
+ * to see where the used weapon using curr_angle and curr_power will end.
+ *
+ * Param 1: has_crashed is set to the number of projectiles that crashed into a steel wall or ceiling.
+ * If the level has a wrap wall ceiling and the projectile can not dig and thus would explode on
+ * the very bottom of the screen due to dirt being in the way, it is considered to have crashed,
+ * too.
+ * Param 2: has_finished is set to the number of projectiles that have been traced to the end. The bots can
+ * only trace maxBounce wall and ceiling bounces or wraps. If the number of actual bounces exceeds
+ * this limit, the projectile is considered unfinished and the tracing stops.
+ *
+ * Basically this method uses traceShot() for each spread projectile of the weapon. Non-spread weapons have a
+ * spread value of 1, so this can be done for every weapon.
+ * Weapons with submunition are then traced further using traceCluster(), which will, like traceWeapon() does
+ * on weapons without submunition, use calcHitDamage() to generate the damage values on each tank.
+ *
+ * The nearest hit to the primary target is recorded in curr_overshoot, reached_x and reached_y.
+ *
+ * 4.3 Use int32_t calcHitScore(bool is_last) to generate a score out of all damage dealt.
+ *
+ * Param 1: is_last is set to true if is_last in aim() is true and needSuccess is true.
+ *
+ * This method cycles through the opponents memory, and sums up the damage done with curr_weap to a total
+ * score according to a) how much damage over the opponents health (aka overkill) has been done and b) on
+ * which team they are compared to us.
+ *
+ * If the primary target was not hit, the score is ensured to be negative, as collateral damage is
+ * discouraged. However, this is only done if is_last is false, as collateral damage with a total positive
+ * score is better than nothing on the very last attempt.
+ *
+ * 4.4 If a new best_score is achieved, and either the primary target was hit, or hasn't been hit before, the
+ * best_* values are set to the current ones:
+ *
+ * best_angle = curr_angle
+ * best_overshoot = curr_overshoot
+ * best_power = curr_power
+ * best_prime_hit = curr_prime_hit
+ * best_score = hit_score
+ *
+ * 4.5 Basically there are four different situations that need different actions.
+ * If the shot, or some of the spread or cluster shots, did not finish, it must be tried to change the used
+ * angle and power so the next attempt does finish.
+ * With steel walls or a wrapped ceiling ceiling with dirt on the ground the shot can have crashed. This can
+ * be fixed by getting away from 45° and reducing power.
+ * The hit might be nearer than the last one. In this case the last modifications seem to have moved the angle
+ * and power into the right direction, which is a path to follow further.
+ * And finally the hit might be farther away. The last modifications might have been too strong or in the
+ * wrong direction.
+ *
+ * 4.5.1 Try to fix unfinished shots using void fixUnfinished(int32_t &ang_mod, int32_t &pow_mod).
+ *
+ * Param 1 : Reference to the angle modifier to adapt.
+ * Param 2 : Reference to the power modifier to adapt.
+ *
+ * If the angle is too steep, lower it, if it is too flat, raise it. The angle is, however, limited
+ * according to where the opponent is. If it is above the own tank, the angle is limited around 60°. If
+ * it is at an equal height in the range of +/- 100 pixels, the angle is limited around 40°. If it is
+ * below, the angle is limited around 20°.
+ *
+ * But if hill_detected is true, the angle modification is limited to 1° per aiming round.
+ *
+ * The power is reduced if it is greater than twice the x distance to the opponen, and raised if it is less
+ * then the x distance. If it is between the two, power is not changed.
+ *
+ * last_reverted : true if last_ang_mod is signed differently than the resulting ang_mod.
+ * last_was_better : false
+ *
+ * 4.5.2 Try to fix crashed shots using void fixCrashed(int32_t &ang_mod, int32_t &pow_mod).
+ *
+ * Param 1 : Reference to the angle modifier to adapt.
+ * Param 2 : Reference to the power modifier to adapt.
+ *
+ * Here the angle modifier is limited to 1° per aiming round if hill_detected is false. this is done,
+ * because if not trying to get over hill the crashes normally occur if the shot is just a bit too strong.
+ *
+ * If the angle is above 60° in boxed mode, it is reduced by a random amount between [1;7]° according
+ * to ai_level. In non-boxed mode it is reduced by 1° if it is between 10° and 45°. Otherwise the angle is
+ * left alone.
+ *
+ * If the power is greater than the simple x distance, it is reduced as well, even more if it is 50% and
+ * more greater than the x distance.
+ *
+ * Now if the hit_score is positive, halve it for every shot that crashed. The hit_score has of course
+ * been written into best_score already if it was better, but the modified version will be used for
+ * last_score later.
+ *
+ * last_reverted : true if last_ang_mod is signed differently than the resulting ang_mod.
+ * last_was_better : false
+ *
+ * 4.5.3 If the shot did finish and did not crash and hit nearer to the target than the last, a few adaptations
+ * might be needed.
+ *
+ * If the resulting hit_score is worse than last_score, it must be ensured, that ang_mod has the same sign
+ * as the previous one. The direction is correct, as the hit is nearer, but it hasn't been changed enough.
+ *
+ * To get even nearer, pow_mod is ensured to have the opposite sign of curr_overshoot. So if curr_overshoot
+ * is negative, the shot is still too short and pw_mod must be positive and vice versa.
+ *
+ * last_reverted : true if last_ang_mod is signed differently than the resulting ang_mod.
+ * last_was_better : true if hit_score is lower than last_score, false otherwise.
+ *
+ * 4.5.4 If the shot did finish and did not crash but hit farther away than the last, use
+ * void fixOvershoot(int32_t& ang_mod, int32_t& pow_mod, int32_t hit_score).
+ *
+ * Param 1 : Reference to the angle modifier to adapt.
+ * Param 2 : Reference to the power modifier to adapt.
+ * Param 3 : The hit_score achieved with the shot. This is local to AICore::aim() and must be submitted.
+ *
+ * bool angle_was_optimized : true if the last modification brought the angle nearer to 45° on its side,
+ * and false otherwise.
+ * hill_detected : false if both the overshoot and the hit_score are positive. Otherwise the
+ * current value is not changed.
+ *
+ * Here are some more possible (sub) situations to consider:
+ * 1) The current score is at least better than the last.
+ * This can happen if the shot does no longer hit team mates.
+ * The important situation is, if the overshoot is very small and a new best score is achieved.
+ * The bigger the weapon, the higher the probability that this might be the case.
+ *
+ * If the hit_score is lower than best_score, meaning no new best score was achieved, it is ensured
+ * that both ang_mod and pow_mod have the same sign than the last modifications had. Without a new
+ * best_score the higher one might only mean less collateral damage, and no adaptation is done.
+ *
+ * last_was_better : true
+ * hill_detected : false
+ *
+ * 2) Both the current and the last overshoot were negative, the angle was optimized towards 45° and the
+ * power was raised.
+ * Having a worse overshoot then can happen if the gun was lowered and the shot crashes into the side of
+ * a hill or mountain.
+ * The angle must then be brought towards 180° more than the last angle modification brought it away
+ * from it.
+ *
+ * last_was_better : false, no matter what, so this change won't get directly reverted again.
+ * hill_detected : true;
+ *
+ * 3) The current score is worse than the last score.
+ * a) The last score was better than the one before.
+ * The modifications might have been too strong, try values between the two.
+ * b) That was two worse tries in a row.
+ * The direction was wrong, and the last modifications must be reverted and strengthened by the
+ * current set modifications.
+ *
+ * last_was_better : false
+ *
+ * 4) No last score or the same.
+ * Just adapt the mods according to whether the shot was too short or too long.
+ *
+ * last_was_better : false
+ *
+ * last_reverted : true if last_ang_mod is signed differently than the resulting ang_mod.
+ *
+ * 4.6 If the current angle is 180°, so pointing straight up, ang_mod is zero and no hit_score greater than zero
+ * was achieved, a random ang_mod in the interval [2;6] towards the opponent is generated to fix this vertical
+ * shot. Vertical trick shots using wind are only accepted if the resulting hit_score is positive.
+ *
+ * 4.7 If the power modification according to the current overshoot and ang_mod is too low, it is strengthened
+ * using the difference of the overshoot and pow_mod divided by ang_mod and multiplied by the AI's focusRate.
+ *
+ * 4.8 Sanitize ang_mod and pow_mod, both applied must not lead to invalid values. Then apply both to curr_angle
+ * and curr_power.
+ *
+ * 4.9 Save the current values in the last_* members:
+ *
+ * last_ang_mod = ang_mod
+ * last_overshoot = curr_overshoot
+ * last_pow_mod = pow_mod
+ * last_score = hit_score
+ *
+ * After this, the loop ends and the work flow restarts at 4.1.
+ *
+ * 4.10 When all aiming attempts are used up, an emergency plan to free the tank or unblock its path might be
+ * triggered if all of the following conditions are true:
+ *
+ * - is_last and needSuccess are both true,
+ * - there was no best setup with a positive score, yet,
+ * - the current best round score will not create a new best setup with a positive score,
+ * - the overall best score is negative,
+ * - the best achieved overshoot is negative, indicating that the target was not yet reached and
+ * - either the overshoot is greater than the weapons radius without being a ceiling crash, or fixOvershoot()
+ * detected a hill in the path.
+ *
+ * 4.11 Eventually, if a new best_round_score is achieved, remember the current settings:
+ *
+ * best_round_score = best_score;
+ * curr_angle = best_angle;
+ * curr_power = best_power;
+ *
+ * 4.12 Return true if either best_round_score is larger than zero, or both is_last and needSuccess are true.
+ *
+ * 5 If the aiming was successful, a few more checks are made.
+ *
+ * 5.1 If best_round_score is greater than zero, and either more weapons have been tried than the ai_level is or
+ * all findOppAttempts have been used up, both the weap_attempts and opp_attempts are reset to zero so this
+ * targeting attempt is declared to be over.
+ *
+ * 5.2 If the primary target was hit, its score is added to best_round_score. The opponents score is divided by
+ * ten times the ai_level for this unless it is the revengee, in which case the opponent score is only divided
+ * by the simple ai_level. This is done to enhance scores for attacks where the primary target was hit over
+ * attempts, were only collateral damage was done, but counted positive due to last attempt behaviour.
+ * Further this makes shots against revengees more likely, even if they are imperfect, the lower the AI level
+ * is.
+ *
+ * 5.3 If a weapon with a single damage value over zero, so no dirt bomb or reducer, was chosen, its score divided
+ * by ten times the ai_level is added, too.
+ *
+ * 5.4 If a new best setup score was achieved, or the primary target was first hit, or a success must be enforced,
+ * the best setup data is remembered:
+ *
+ * best_setup_angle = curr_angle
+ * best_setup_item = item_curr
+ * best_setup_mem = mem_curr
+ * best_setup_overshoot = best_overshoot
+ * best_setup_power = curr_power
+ * best_setup_prime = best_prime_hit
+ * best_setup_weap = weap_curr
+ * best_setup_score = best_round_score
+ *
+ * As this targeting round is then definitely over, opp_attempts and weap_attempts are reset to zero to
+ * trigger a fresh new round.
+ *
+ * The main targeting loop ends here.
+ *
+ * 6 If a new revengee was set, or the old removed, save this in player->revenge.
+ *
+ * 7 Without a real setup with a positive score here, a last freeing attempt might be triggered. But only if the
+ * AI did not detect that it was blocked already, or a freeing attempt would have been setup already.
+ *
+ * 8 Write back the best attack setup for the tank to use.
+ *
+ * 9 Shout a retaliation phrase out if the revengee is attacked and the predicted damage is at least 50% of the
+ * opponents total health.
+ *
+ * 10 Otherwise, if a self destruct attempt is issued, shout out a good-bye-phrase.
+ *
+ * 11 If a different target than in the last round is attacked, add some "last-second-errors".
+**/
+class AICore
+{
+public:
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ explicit AICore();
+ ~AICore();
+
+ // No copying, no assignment
+ AICore(const AICore&) = delete;
+ AICore &operator=(const AICore&) = delete;
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ PLAYER* active_player() const;
+ void allowText ();
+ bool can_work () const;
+ void forbidText ();
+ bool hasExited () const;
+ bool start (PLAYER* player_);
+ bool status (int32_t &aItem, int32_t &aAngle,
+ int32_t &aPower, ePlayerStages &pl_stage);
+ void stop ();
+ void weapon_fired ();
+
+ void operator() ();
+
+
+ /* ----------------------
+ * --- Public members ---
+ * ----------------------
+ */
+
+private:
+
+ typedef ePlayerStages plStage_t;
+ typedef sItemListEntry itEntry_t;
+ typedef sOppMemEntry opEntry_t;
+ typedef sWeapListEntry weEntry_t;
+ typedef std::mutex mutex_t;
+ typedef std::condition_variable condv_t;
+ typedef std::lock_guard<mutex_t> lguard_t;
+ typedef std::unique_lock<mutex_t> luniq_t;
+
+
+ /* -----------------------
+ * --- Private methods ---
+ * -----------------------
+ */
+
+ bool aim (bool is_last);
+ bool calcAttack (int32_t attempt);
+ bool calcBoxed (bool is_last);
+ void calcHitDamage (int32_t hit_x, int32_t hit_y, double weap_rad,
+ double dmg, weaponType weapType);
+ int32_t calcHitScore (bool is_last);
+ bool calcKamikaze (bool is_last);
+ bool calcLaser (bool is_last);
+ bool calcOffset (bool is_last);
+ bool calcStandard (bool is_last, bool allow_flip_shot);
+ bool calcUnbury (bool is_last);
+ void checkItemMem ();
+ void checkOppMem ();
+ void checkWeapMem ();
+ void destroy ();
+ void flattenCurrAng ();
+ void fixCrashed (int32_t &ang_mod, int32_t &pow_mod);
+ void fixOvershoot (int32_t &ang_mod, int32_t &pow_mod,
+ int32_t hit_score);
+ void fixUnfinished (int32_t &ang_mod, int32_t &pow_mod);
+ const char* getLevelName (int32_t level) const;
+ bool getMemory ();
+ bool initialize ();
+ void sanitizeCurr ();
+ bool selectItem (bool is_last);
+ bool selectTarget (bool is_last);
+ bool setupAttack (bool is_last, int32_t &opp_attempt,
+ int32_t &weap_attempt);
+ void showFeedback (const char* const feedback, int32_t col,
+ double yv, eTextSway text_sway, int32_t dur);
+ void traceCluster (int32_t subType, int32_t subCount,
+ int32_t sub_x, int32_t sub_y,
+ double inh_xv, double inh_yv);
+ bool traceShot (int32_t trace_angle,
+ bool &finished, bool &top_wrapped,
+ int32_t &reached_x_, int32_t &reached_y_,
+ double &end_xv, double &end_yv);
+ void traceWeapon (int32_t &has_crashed, int32_t &has_finished);
+ void updateItemScore(itEntry_t* pItem);
+ void updateOppScore (opEntry_t* pOpp);
+ void updateWeapScore(weEntry_t* pWeap);
+ bool useFreeingTool (bool free_tank, bool is_last);
+ bool useItem (itemType item_type);
+ bool useItem (int32_t item_index);
+ bool useWeapon (weaponType weap_type);
+ bool useWeapon (int32_t weap_index);
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ // Internal values
+ mutex_t actionMutex;
+ condv_t actionCondition;
+ volatile
+ bool canWork = true;
+ int32_t curr_angle = 90; //!< The angle that is currently tested
+ int32_t curr_overshoot = 0; //!< Current calculated distance of hit versus opponent
+ int32_t curr_power = 0; //!< The power that is currently tested
+ bool curr_prime_hit = false; //!< Whether the primary target was hit.
+ double errorMultiplier = 0.; //!< Default error reduction according to AI level
+ int32_t findOppAttempts = 0; //!< Number of attempts to select a suitable opponent
+ int32_t findRngAttempts = 0; //!< Number of attempts to aim the current selection
+ int32_t findTgtAttempts = 0; //!< Number of attempts to come up with an attack plan
+ int32_t findWeapAttempts = 0; //!< Number of attempts to find a suitable item/weapon
+ double focusRate = 0.; //!< How good a bot can focus on a specific task
+ bool isBlocked = false; //!< Set to true if a shot can't get through
+ volatile
+ bool isFinished = false; //!< Set to true when operator() ends
+ bool isShocked = false;
+ volatile
+ bool isStopped = false;
+ volatile
+ bool isWorking = false;
+ int32_t maxBounce = 0; //!< How many wall bounces/wraps can be calculated
+ bool needAim = false; //!< true if this is a standard shot
+ bool needSuccess = true; //!< true unless a best score is achieved
+ int32_t offset_x = 0;
+ int32_t offset_y = 0;
+ plStage_t plStage = PS_AI_IS_IDLE;
+ sOpponent* revengee = nullptr; //!< If set, it is tried first as a target
+ sOpponent* shocker = nullptr; //!< The current fear shock winner
+ abool_t textAllowed; //!< Is new FLOATTEXT allowed?
+ int32_t weap_idx = SML_MIS;
+
+ // Values taken from the player and their tank
+ int32_t ai_level = 0; //!< To not having to cast from player type.
+ double ai_level_d = 0.; //!< To not having to cast from ai_level.
+ double ai_over_mod = 0.; //!< modifier for overkills and similar
+ double ai_type_mod = 0.; //!< modifier for important decisions
+ int32_t angle = 90; //!< The currently determined best angle
+ double blast_min = 0.; //!< Damage done by small missile
+ double blast_med = 0.; //!< Damage done by medium or large missile
+ double blast_big = 0.; //!< Damage done by small nuke or nuke
+ double blast_max = 0.; //!< Damage done by death head
+ int32_t buried = 0; //!< Full buried level
+ int32_t buried_l = 0; //!< left side buried level
+ int32_t buried_r = 0; //!< right side buried level
+ double currLife = 0.;
+ itEntry_t* item_curr = nullptr; //!< Currently selected entry
+ itEntry_t* item_head = nullptr; //!< Last selected entry
+ itEntry_t* item_last = nullptr; //!< Entry with highest score
+ int32_t last_ang = 0; //!< Angle used in last round
+ sOpponent* last_opp = nullptr; //!< The opponent attacked in the last round
+ int32_t last_pow = 0; //!< Power used in last round
+ int32_t last_weap = 0; //!< weapon used in the last round
+ int32_t maxLife = 100;
+ opEntry_t* mem_curr = nullptr; //!< Currently selected entry
+ opEntry_t* mem_head = nullptr; //!< Last selected entry
+ opEntry_t* mem_last = nullptr; //!< Entry with highest score
+ bool needMoney = false; //!< Might alter some decisions
+ PLAYER* player = nullptr;
+ int32_t power = 0; //!< The currently determined best power
+ TANK* tank = nullptr;
+ double type_mod = 1.;
+ weEntry_t* weap_curr = nullptr; //!< Currently selected entry
+ weEntry_t* weap_head = nullptr; //!< Last selected entry
+ weEntry_t* weap_last = nullptr; //!< Entry with highest score
+ double x = 0.;
+ double y = 0.;
+
+ // Values to remember settings and situations over aiming rounds
+ int32_t best_angle = 0;
+ int32_t best_overshoot = MAX_OVERSHOOT; //!< Overshoot value of currently best angle and power
+ int32_t best_power = 0;
+ bool best_prime_hit = false; //!< Whether the bes aiming round values hit the primary target.
+ int32_t best_score = NEUTRAL_ROUND_SCORE;
+ bool hill_detected = false;
+ int32_t last_ang_mod = 0;
+ int32_t last_overshoot = MAX_OVERSHOOT;
+ int32_t last_pow_mod = 0;
+ bool last_reverted = false;
+ int32_t last_score = 0;
+ bool last_was_better = false;
+ int32_t reached_x = x;
+ int32_t reached_y = y;
+
+ // Values used to memorize the best setup in a round
+ int32_t best_round_score = NEUTRAL_ROUND_SCORE;
+ int32_t best_setup_angle = 0;
+ itEntry_t* best_setup_item = nullptr;
+ opEntry_t* best_setup_mem = nullptr;
+ int32_t best_setup_overshoot = MAX_OVERSHOOT;
+ int32_t best_setup_power = 0;
+ bool best_setup_prime = false;
+ int32_t best_setup_score = 0;
+ weEntry_t* best_setup_weap = nullptr;
+};
+
+#endif // ATANKS_SRC_AICORE_H_INCLUDED
+
diff --git a/src/allegro.h b/src/allegro.h
deleted file mode 100644
index d5e7fc3..0000000
--- a/src/allegro.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/* ______ ___ ___
- * /\ _ \ /\_ \ /\_ \
- * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___
- * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\
- * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \
- * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
- * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
- * /\____/
- * \_/__/
- *
- * Main header file for the entire Allegro library.
- * (separate modules can be included from the allegro/ directory)
- *
- * By Shawn Hargreaves.
- *
- * Vincent Penquerc'h split the original allegro.h into separate headers.
- *
- * See readme.txt for copyright information.
- */
-
-
-#ifndef ALLEGRO_H
-#define ALLEGRO_H
-
-#include "allegro/base.h"
-
-#include "allegro/system.h"
-#include "allegro/debug.h"
-
-#include "allegro/unicode.h"
-
-#include "allegro/mouse.h"
-#include "allegro/timer.h"
-#include "allegro/keyboard.h"
-#include "allegro/joystick.h"
-
-#include "allegro/palette.h"
-#include "allegro/gfx.h"
-#include "allegro/color.h"
-#include "allegro/draw.h"
-#include "allegro/rle.h"
-#include "allegro/compiled.h"
-#include "allegro/text.h"
-#include "allegro/font.h"
-
-#include "allegro/fli.h"
-#include "allegro/config.h"
-#include "allegro/gui.h"
-
-#include "allegro/sound.h"
-
-#include "allegro/file.h"
-#include "allegro/lzss.h"
-#include "allegro/datafile.h"
-
-#include "allegro/fixed.h"
-#include "allegro/fmaths.h"
-#include "allegro/matrix.h"
-#include "allegro/quat.h"
-
-#include "allegro/3d.h"
-#include "allegro/3dmaths.h"
-
-
-#ifndef ALLEGRO_NO_COMPATIBILITY
- #include "allegro/alcompat.h"
-#endif
-
-#ifndef ALLEGRO_NO_FIX_CLASS
- #ifdef __cplusplus
- #include "allegro/fix.h"
- #endif
-#endif
-
-
-#ifdef ALLEGRO_EXTRA_HEADER
- #include ALLEGRO_EXTRA_HEADER
-#endif
-
-#endif /* ifndef ALLEGRO_H */
-
-
diff --git a/src/atanks.cpp b/src/atanks.cpp
index c434666..1d535eb 100644
--- a/src/atanks.cpp
+++ b/src/atanks.cpp
@@ -1,3 +1,5 @@
+#define ATANKS_SRC_ATANKS_CPP 1
+
/*
* atanks - obliterate each other with oversize weapons
* Copyright (C) 2002,2003 Thomas Hudson,Juraj Michalek
@@ -17,46 +19,27 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* */
-
-#include <dirent.h>
-
+#include "debug.h"
#include "globals.h"
-#include "menu.h"
+#include "optionscreens.h"
+#include "player.h"
#include "button.h"
-#include "team.h"
#include "files.h"
-#include "satellite.h"
#include "update.h"
-#include "network.h"
-#include "land.h"
-
-#include "floattext.h"
-#include "explosion.h"
+#include "tank.h"
#include "beam.h"
#include "missile.h"
-#include "decor.h"
-#include "teleport.h"
-#include "sky.h"
#include "gameloop.h"
+#include "clock.h"
-#ifdef THREADS
-#include <pthread.h>
-#endif
#ifdef NETWORK
-#include "client.h"
+# include <thread>
+# include "client.h"
#endif
-enum cmdTokens
-{
- ARGV_NOTHING_EXPECTED,
- ARGV_GFX_DEPTH,
- ARGV_SCREEN_WIDTH,
- ARGV_SCREEN_HEIGHT,
- ARGV_DATA_DIR,
- ARGV_CONFIG_DIR
-};
+#define HELP_REQUESTED -100
#define SWITCH_HELP "-h"
#define SWITCH_FULL_SCREEN "-fs"
#define SWITCH_WINDOWED "--windowed"
@@ -66,4969 +49,1353 @@ enum cmdTokens
#define SWITCH_NO_CONFIG "--noconfig"
-int screen_mode = GFX_AUTODETECT_WINDOWED;
-
-// BITMAP *create_gradient_strip (const gradient *gradient, int length);
-// int draw_circlesBG (GLOBALDATA *global, BITMAP *dest, int x, int y, int width, int height, bool image);
-
-using namespace std;
-
-void fpsadd()
-{
- fps = frames;
- frames = 0;
-}
-
-#ifdef THREADS
-pthread_mutex_t* cclock_lock;
-#endif
-void destroy_cclock_lock() {
- #ifdef THREADS
- if (cclock_lock)
- {
- int result = pthread_mutex_destroy(cclock_lock);
- switch (result)
- {
- case 0:
- //Successfully destroyed
- break;
- case EBUSY:
- //Some thread forgot to unlock result
- printf("%s:%i: Lock is still held.\n", __FILE__, __LINE__);
- break;
- case EINVAL:
- //Invalid lock
- printf("%s:%i: Lock is invalid.\n", __FILE__, __LINE__);
- break;
- default:
- printf("%s:%i: Unknown error code (%i) returned by pthread_mutex_destroy.\n", __FILE__, __LINE__, result);
- break;
- }
- free(cclock_lock);
- }
- #endif
-}
-void init_cclock_lock()
-{
- #ifdef THREADS
- cclock_lock = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t));
- if (cclock_lock == NULL)
- {
- printf("%s:%i: Could not allocate memory.\n", __FILE__, __LINE__);
- }
- int result = pthread_mutex_init(cclock_lock, NULL);
- switch (result)
- {
- case 0:
- //Succesfully initialized
- break;
- case EAGAIN:
- //Not enough resources
- printf("%s:%i: Not enough resources to create mutex.\n", __FILE__, __LINE__);
- break;
- case ENOMEM:
- printf("%s:%i: Not enough memory to create mutex.\n", __FILE__, __LINE__);
- break;
- case EPERM:
- printf("%s:%i: Not authorized.\n", __FILE__, __LINE__);
- break;
- case EBUSY:
- printf("%s:%i: The mutex is already initialized.\n", __FILE__, __LINE__);
- break;
- case EINVAL:
- printf("%s:%i: Invalid attribute.\n", __FILE__, __LINE__);
- break;
- default:
- printf("%s:%i: Unknown error code (%i) returned by pthread_mutex_init.\n", __FILE__, __LINE__, result);
- break;
- }
- atexit(destroy_cclock_lock);
- #endif
-}
-void lock_cclock()
-{
- #ifdef THREADS
- int result = pthread_mutex_lock(cclock_lock);
- switch (result)
- {
- case 0:
- //Got the lock.
- break;
- case EINVAL:
- //Priority too high
- printf("%s:%i: Either this thread's priority is higher than the mutex's priority, or the mutex is uninitialized.\n", __FILE__, __LINE__);
- break;
- case EAGAIN:
- printf("%s:%i: Too many locks on the mutex.\n", __FILE__, __LINE__);
- break;
- case EDEADLK:
- //We have the lock already
- printf("%s:%i: Already have lock.\n", __FILE__, __LINE__);
- break;
- default:
- //What error is this?
- printf("%s:%i: Unknown error code (%i) returned by pthread_mutex_lock.\n", __FILE__, __LINE__, result);
- break;
- }
- #endif
-}
-void unlock_cclock()
-{
- #ifdef THREADS
- int result = pthread_mutex_unlock(cclock_lock);
- switch (result)
- {
- case 0:
- //Released the lock
- break;
- case EPERM:
- //Forgot to get a lock on the mutex
- printf("%s:%i: Mutex isn't locked\n", __FILE__, __LINE__);
- break;
- case EINVAL:
- //Uninitialized mutex
- printf("%s:%i: cclock_lock is uninitialized.\n", __FILE__, __LINE__);
- break;
- default:
- //?
- printf("%s:%i: Unknown error code (%i) returned by pthread_mutex_unlock.\n", __FILE__, __LINE__, result);
- break;
- }
- #endif
-}
-int get_cclock()
-{
- lock_cclock();
- int c = cclock;
- unlock_cclock();
- return c;
-}
-void clockadd()
-{
- lock_cclock();
- cclock++;
- unlock_cclock();
-}
-END_OF_FUNCTION(clockadd)
-
-
-
-
-/*****************************************************************************
-drawMenuBackground
-
-Draws a 600x400 centered box, fills it with some random lines or circles.
-Someday, we should make this more generic; have it take the box dimensions
-as an input parameter.
-*****************************************************************************/
-void drawMenuBackground (GLOBALDATA *global, ENVIRONMENT *env, int itemType, int tOffset, int numItems)
-{
- rectfill (env->db, global->halfWidth - 300, global->menuBeginY, // 100,
- global->halfWidth + 300, global->menuEndY, // global->screenHeight - 100,
- makecol (0,79,0));
- rect (env->db, global->halfWidth - 300, global->menuBeginY, // 100,
- global->halfWidth + 300, global->menuEndY, // global->screenHeight - 100,
- makecol (128,255,128));
-
- drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
- env->current_drawing_mode = DRAW_MODE_TRANS;
- set_trans_blender (0, 0, 0, 15);
- for (int tCount = 0; tCount < numItems; tCount++)
- {
- int radius, xpos, ypos;
- switch (itemType)
- {
- case BACKGROUND_CIRCLE: // circles
- radius = (int)((perlin1DPoint (1.0, 5, (tOffset * 0.02) + tCount + 423346, 0.5, 8) + 1) / 2 * 40);
- xpos = global->halfWidth + (int)(perlin1DPoint (1.0, 3, (tOffset * 0.02) + tCount + 232662, 0.3, 6) * 250);
- ypos = global->halfHeight + (int)(perlin1DPoint (1.0, 2, (tOffset * 0.02) + tCount + 42397, 0.3, 6) * (global->halfHeight - 100));
- circlefill (env->db, xpos, ypos, radius,
- makecol (200,255,200));
- break;
- case BACKGROUND_LINE: // Horz lines
- radius = (int)((perlin1DPoint (1.0, 5, (tOffset * 0.02) + tCount + 423346, 0.5, 8) + 1) / 2 * 40);
- xpos = global->halfWidth + (int)(perlin1DPoint (1.0, 3, (tOffset * 0.02) + tCount + 232662, 0.3, 6) * 250);
- rectfill (env->db, xpos - radius / 2, 101,
- xpos + radius / 2, global->screenHeight - 101,
- makecol (200,255,200));
- break;
- case BACKGROUND_BLANK:
- default:
- break;
- }
-
- }
- solid_mode ();
- env->current_drawing_mode = DRAW_MODE_SOLID;
-}
-
-
-void initialisePlayers (GLOBALDATA *global)
-{
- int z;
-
- for (z = 0; z < global->numPlayers; z++)
- {
- global->players[z]->money = (int)global->startmoney;
- global->players[z]->score = 0;
- // global->players[z]->initialise ();
- // global->players[z]->type_saved = global->players[z]->type;
- if (((int)global->players[z]->type != HUMAN_PLAYER) &&
- (global->players[z]->preftype == PERPLAY_PREF))
- {
- global->players[z]->generatePreferences();
- }
- global->players[z]->initialise();
- global->players[z]->type_saved = global->players[z]->type;
- }
-}
-
-
-
-void wait_for_input()
-{
- do
- {
- LINUX_SLEEP;
- }
- while ((!keypressed()) && (!mouse_b));
-
- flush_inputs();
-
-}
-
-int pickColor (int left, int top, int width, int height, int x, int y)
-{
- int r, g, b;
- double value, saturation;
- double hue = ((double)(x - left) / width) * 360;
-
- double hPos = (double)(y - top) / height;
- if (hPos > 0.5)
- {
- value = 1.0 - ((hPos - 0.5) * 2);
- saturation = 1.0;
- }
- else
- {
- value = 1.0;
- saturation = hPos * 2;
- }
-
- hsv_to_rgb (hue, saturation, value, &r, &g, &b);
-
- return (makecol (r, g, b));
-}
-
-void colorBar (ENVIRONMENT *env, int left, int top, int width, int height)
-{
- int right = left + width;
- int bottom = top + height;
-
- for (int x = left; x < right; x++)
- {
- for (int y = top; y < bottom; y++)
- {
- putpixel (env->db, x, y, pickColor (left, top, width, height, x, y));
- }
- }
-}
-
-int textEntryBox (GLOBALDATA *global, ENVIRONMENT *env, int modify, int x, int y, char *text, unsigned int textLength)
-{
- int ke = 0;
- int fontWidth = text_length (font, (char *)"Z");
- int fontHeight = text_height (font);
- int leftX = x - (fontWidth * textLength / 2);
- int rightX = x + (fontWidth * textLength / 2);
- int boxWidth = fontWidth * textLength;
-// char tempText[textLength + 1];
- char * tempText;
- int flashCount = 0;
- int lx = mouse_x, ly = mouse_y;
-
- tempText = (char *)calloc(textLength + 1, sizeof(char));
-
- if (!tempText)
- {
- // Die hard!
- cerr << "ERROR: Unable to allocate " << (textLength + 1) << " bytes in textEntryBox() !!!" << endl;
- // exit (1);
- }
-
- if (! text) text = (char *)"";
- rectfill (env->db, leftX, y - 2, rightX, y + fontHeight + 2, WHITE);
- rect (env->db, leftX, y - 2, rightX, y + fontHeight + 2, BLACK);
- if (!modify)
- {
- textout_centre_ex (env->db, font, text, x, y, BLACK, -1);
- }
- env->make_update (leftX - 2, y - 4, fontWidth * textLength + 4, fontHeight + 6);
- env->do_updates ();
-
- if (!modify)
- {
- free(tempText);
- return (boxWidth);
- }
- strncpy (tempText, text, textLength + 1);
-
- while (((ke >> 8) != KEY_ENTER && (ke >> 8) != KEY_ESC && (ke >> 8) != KEY_ENTER_PAD)
- || strlen (tempText) < 1)
- {
- int tWidth = text_length (font, tempText);
-
- LINUX_SLEEP;
-
- rectfill (env->db, leftX, y - 2, rightX, y + fontHeight + 2, WHITE);
- rect (env->db, leftX, y - 2, rightX, y + fontHeight + 2, BLACK);
- //rectfill (screen, x - (tWidth / 2), y, x + (tWidth / 2) + 10, y + text_height (font), (flashCount < 5)?WHITE:BLACK);
- textout_centre_ex (env->db, font, tempText, x, y, BLACK, -1);
- rectfill (env->db, x + (tWidth / 2) + 2, y, x + (tWidth / 2) + 10, y + text_height (font), (flashCount < 25)?WHITE:BLACK);
- env->make_update (leftX - 2, y - 4, fontWidth * textLength + 4, fontHeight + 6);
- env->make_update (mouse_x, mouse_y, ((BITMAP *) (global->misc[0]))->w, ((BITMAP *) (global->misc[0]))->h);
- env->make_update (lx, ly, ((BITMAP *) (global->misc[0]))->w, ((BITMAP *) (global->misc[0]))->h);
- lx = mouse_x;
- ly = mouse_y;
- if (! global->os_mouse) show_mouse (NULL);
-
- if (keypressed ())
- {
- ke = readkey ();
- }
- else
- {
- ke = 0;
- }
-
- if ( ((ke >> 8) == KEY_BACKSPACE) && ( strlen(tempText) > 0 ) )
- {
- tempText[strlen (tempText) - 1] = 0;
- rectfill (screen, x - (tWidth / 2), y, x + (tWidth / 2) + 10, y + text_height (font), WHITE);
- env->make_update (x - (tWidth / 2) - 2, y - 2, tWidth + 14, text_height (font) + 4);
- }
- else if ((ke & 0xff) >= 32 && strlen (tempText) < textLength)
- {
- // tempText[strlen (tempText)] = ke & 0xff;
- tempText[strlen (tempText) + 1] = 0;
- tempText[strlen(tempText)] = ke & 0xff;
- //textprintf (screen, font, x + text_length (font, tempText), y, WHITE, (char *)"%c", ke & 0xff);
- }
- else
- env->do_updates ();
- if (! global->os_mouse) show_mouse (screen);
- rest (1);
- flashCount++;
- flashCount = flashCount % 50;
- }
- if ((ke >> 8) != KEY_ESC)
- strncpy (text, tempText, textLength);
-
- flush_inputs ();
-
- free(tempText);
-
- return (boxWidth);
-}
-
-
-void credits (GLOBALDATA *global, ENVIRONMENT *env)
+/*****************************
+*** static local variables ***
+*****************************/
+static bool allow_network = true;
+static char fullPath[PATH_MAX + 1] = { 0 };
+static eFullScreen full_screen = FULL_SCREEN_EITHER;
+static bool load_config_file = true;
+static int32_t screen_mode = GFX_AUTODETECT_WINDOWED;
+#ifdef NETWORK
+static int32_t client_socket = -1;
+#endif // NETWORK
+
+
+/*************************
+*** External variables ***
+*************************/
+extern WEAPON weapon[WEAPONS]; // from files.cpp
+extern WEAPON naturals[NATURALS]; // from files.cpp
+extern ITEM item[ITEMS]; // from files.cpp
+
+
+/*****************************
+*** static local functions ***
+*****************************/
+static void Change_Settings(bool old_sound, int32_t old_itech, int32_t old_wtech);
+static void close_button_handler(void);
+static void createConfig();
+static void credits();
+static
+const char* do_winner();
+static void endgame_cleanup();
+ void init_mouse_cursor();
+static void init_game_settings();
+static void initialisePlayers();
+static bool loadConfig();
+static bool loadPlayers(FILE* file);
+static int32_t menu();
+static void newgame();
+static int32_t parse_args(int32_t argc, char** argv);
+static void play_demo();
+static void play_local();
+static void play_networked();
+static void print_text_help();
+static void print_text_initmsg();
+static bool Save_Game_Settings(const char* path);
+static void show_options();
+static void title();
+
+
+/*****************************
+*** external functions ***
+*****************************/
+void draw_simple_bg(bool drawImage); // from shop.cpp
+void quickChange (bool clearerror); // from shop.cpp
+
+
+/*******************************
+*** Function implementations ***
+*******************************/
+
+/** @brief Take care of changed settings.
+ *
+ * This function detects changes to some environment settings and, if a
+ * change has happened, makes the required changes to the game environment.
+**/
+static void Change_Settings(bool old_sound, int32_t old_itech, int32_t old_wtech)
{
- char dataDir[2048];
- TEXTBLOCK *my_text;
-
- sprintf (dataDir, "%s/credits.txt", global->dataDir);
- my_text = new TEXTBLOCK(dataDir);
- scrollTextList (global, env, my_text );
- delete my_text;
+ // first, check for a change in the sound settings
+ if (old_sound != env.sound_enabled) {
+ if (env.sound_enabled) {
+ if (detect_digi_driver(DIGI_AUTODETECT)) {
+ if (install_sound (DIGI_AUTODETECT, MIDI_NONE, NULL) < 0)
+ fprintf (stderr, "install_sound: failed turning on sound\n");
+ } else
+ fprintf (stderr, "detect_digi_driver found no sound device\n");
+ } else
+ remove_sound();
+ } // End of sound checking
+
+ // Check for tech level changes
+ if ( (old_itech != env.itemtechLevel)
+ || (old_wtech != env.weapontechLevel) )
+ env.genItemsList();
}
-/*
- * Save all players to a text file.
-*/
-int savePlayers_Text(GLOBALDATA *global, FILE *my_file)
+/** @brief Close Button Handler
+ *
+ * This function catches the close command, usually given by the user pressing
+ * the close window button. We'll try to clean-up.
+**/
+static void close_button_handler(void)
{
- int count = 0;
-
- while (count < global->numPermanentPlayers)
- {
- global->allPlayers[count]->saveToFile_Text(my_file);
- count++;
- }
- return TRUE;
+ global.pressCloseButton();
}
-
-
-
-int loadPlayers_Text (GLOBALDATA *global, ENVIRONMENT *env, FILE *file)
+/// @brief Show the credits file in a text box
+static void credits ()
{
- int count, max;
- int status = TRUE;
- PLAYER *new_player;
-
- max = global->numPermanentPlayers;
- if (global->allPlayers) free(global->allPlayers); // avoid leak
- global->allPlayers = (PLAYER **) malloc (sizeof(PLAYER*) * max);
- if (! global->allPlayers)
- {
- perror("atanks.cc: Failed to allocate memory for allPlayers in loadPlayers_Text");
- return FALSE;
- }
+ snprintf (path_buf, PATH_MAX, "%s/credits.txt", env.dataDir);
- count = 0;
- while (status)
- {
- // global->allPlayers[count] = new PLAYER(global, env, file, true);
- new_player = new PLAYER(global, env);
- if (! new_player)
- {
- perror( (char *)"atanks.cc: Failed to allocate memory for players in loadPlayers_Text");
- return FALSE;
- }
- status = new_player->loadFromFile_Text(file);
- if (status)
- {
- global->allPlayers[count] = new_player;
- count++;
- if (count == max)
- {
- max += 5;
- global->allPlayers = (PLAYER**) realloc(global->allPlayers, sizeof(PLAYER *) * max);
- }
- }
- else
- // free(new_player);
- delete new_player;
- } // end of while status
-
- global->numPermanentPlayers = count;
- return TRUE;
+ TEXTBLOCK my_text(path_buf);
+ scrollTextList (&my_text);
}
-void newgame (GLOBALDATA *global, ENVIRONMENT *env)
+/// @brief create a fresh new config if loading was prohibited or failed
+static void createConfig()
{
- int objCount;
- TANK *tank;
-
- env->initialise ();
- global->initialise ();
-
- // if a game should be loaded, try it or deny loading of the game
- if ( (global->load_game) && (!Load_Game(global, env)) )
- global->load_game = false;
-
- // Now check back whether to load a game
- if (!global->load_game)
- initialisePlayers (global);
-
- // There must not be any tanks!
- for (objCount = 0; (tank = (TANK*)env->getNextOfClass (TANK_CLASS, &objCount)) && tank; objCount++)
- {
- tank->player = NULL; // To avoid violent death!
- delete(tank);
- }
-
- // This is always true here, as a newly started game is handled like a loaded one:
- global->bIsGameLoaded = true;
+ env.numPermanentPlayers = 0;
+
+ // Override full screen settings from command line
+ if ( (full_screen == FULL_SCREEN_TRUE)
+ || (full_screen == FULL_SCREEN_FALSE) )
+ env.full_screen = full_screen;
+
+ // Determine basic screen settings
+ env.temp_screenWidth = env.screenWidth;
+ env.temp_screenHeight = env.screenHeight;
+ env.halfWidth = env.screenWidth / 2;
+ env.halfHeight = env.screenHeight / 2;
+ env.menuBeginY = (env.screenHeight - 400) / 2;
+ if (env.menuBeginY < 0)
+ env.menuBeginY = 0;
+ env.menuEndY = env.screenHeight - env.menuBeginY;
+
+ // Perform game initialization
+ init_game_settings ();
+ env.load_text_files();
+ init_mouse_cursor();
+
+ // At least one human player must be created
+ PLAYER *tempPlayer = nullptr;
+ int32_t tempRes = PE_BACK; // ePlayerEdit, player_types.h
+ char noHumanMsg[200] = { 0 };
+
+ while (!(tempRes & PE_CONFIRM_NEW)) {
+ tempRes = new_player(&tempPlayer, 0);
+
+ if (tempPlayer) {
+ // Error case 1: The created player is an AI player
+ if (HUMAN_PLAYER != tempPlayer->type) {
+ snprintf(noHumanMsg, 199,
+ "The player \"%s\" is no human player!",
+ tempPlayer->getName());
+ errorMessage = noHumanMsg;
+ errorX = env.halfWidth - text_length(font, errorMessage) / 2;
+ errorY = env.menuBeginY + 15;
+ tempPlayer = nullptr; // It is saved already
+ tempRes = PE_BACK;
+ }
+ } else {
+ // error case 2: No player was created at all
+ strncpy(noHumanMsg, "Please create at least one human player!", 199);
+ errorMessage = noHumanMsg;
+ errorX = env.halfWidth - text_length(font, errorMessage) / 2;
+ errorY = env.menuBeginY + 15;
+ tempRes = PE_BACK;
+ }
+ } // End of force-creating a human player
+
+ // Default AI player names
+ const char* const defaultNames[] = {
+ "Caesar",
+ "Alex",
+ "Hatshepsut",
+ "Patton",
+ "Napoleon",
+ "Attila",
+ "Catherine",
+ "Hannibal",
+ "Stalin",
+ "Mao"
+ };
+
+ for (int32_t i = 0; i < 10; ++i) {
+ tempPlayer = env.createNewPlayer(defaultNames[i]);
+ tempPlayer->type = static_cast<playerType>(rand () % (LAST_PLAYER_TYPE - 1) + 1);
+ tempPlayer->generatePreferences();
+ }
}
-
-// This function draws the background for most screens
-int draw_circlesBG (GLOBALDATA *global, BITMAP *dest, int /*x*/, int /*y*/, int /*width*/, int /*height*/, bool image)
-{
- // int largestCircle, circleCount;
- // BITMAP *drawTo = dest;
-
- // try to prevent crashes on 64-bit systems by avoiding this function
- if (! global->draw_background)
- {
- rectfill(dest, 0, 0, global->screenWidth - 1, global->screenHeight - 1, BLACK);
- return 0;
- }
-
- if ( (image) && (global->misc[17]) )
- stretch_blit(global->misc[17], dest, 0, 0, global->misc[17]->w, global->misc[17]->h, 0, 0,
- global->screenWidth, global->screenHeight);
- else
- rectfill(dest, 0, 0, global->screenWidth - 1, global->screenHeight - 1, DARK_GREEN);
-
- /*
- if (global->cacheCirclesBG)
- {
- if (!global->gfxData.circlesBG)
- {
- global->gfxData.circlesBG = create_bitmap (width, height);
- drawTo = global->gfxData.circlesBG;
- }
- else
- {
- blit (global->gfxData.circlesBG, dest, 0, 0, 0, 0, width, height);
- return (0);
- }
- }
- else
- {
- drawTo = dest;
- }
-
- largestCircle = (int)(global->halfWidth * (4.0/3.0));
- if (largestCircle > 1000) largestCircle = 1000; // perhaps avoid crash on large screens
- global->gfxData.circle_gradient_strip = create_gradient_strip (circles_gradient, largestCircle);
- for (circleCount = largestCircle; circleCount > 0; circleCount -= 2)
- circlefill (drawTo, width/2, height/2, circleCount, getpixel (global->gfxData.circle_gradient_strip, 0, largestCircle - circleCount));
-
- if (global->cacheCirclesBG)
- draw_circlesBG (global, dest, x, y, width, height);
- */
- return (0);
-}
-
-ENVIRONMENT *init_game_settings (GLOBALDATA *global)
+/// @brief Draw the endgame screen and return the winner name
+/// or nullptr if no winner was found. The returned text is static
+/// and must *NOT* be freed.
+static const char* do_winner()
{
- int count, x, y, z;
- ENVIRONMENT *env;
- double expSize, disperseSize;
- // char dataDir[2048];
- int colour_theme = (int) global->colour_theme;
- int status;
-
- #ifdef WIN32
- if (global->full_screen == FULL_SCREEN_TRUE)
- global->os_mouse = FALSE;
- #endif
-
- status = allegro_init ();
- if (status)
- {
- printf("Unable to start Allegro.\n");
- exit(1);
- }
-
- set_window_title( "Atomic Tanks");
- // before we get started, make sure if we are using
- // full screen mode to ignore width and height settings
- if (global->full_screen == FULL_SCREEN_TRUE)
- {
- status = get_desktop_resolution(& (global->screenWidth), & (global->screenHeight) );
- if (status < 0)
- {
- global->screenWidth = 800;
- global->screenHeight = 600;
- }
- screen_mode = GFX_AUTODETECT_FULLSCREEN;
- }
- // check for X pressed on the window bar
- LOCK_FUNCTION(close_button_handler);
- set_close_button_callback(close_button_handler);
-
- if (! global->colourDepth)
- global->colourDepth = desktop_color_depth();
-
- if ( (global->colourDepth != 16) && (global->colourDepth != 32) )
- global->colourDepth = 16;
-
- set_color_depth (global->colourDepth);
- if (global->width_override)
- {
- global->screenWidth = global->width_override;
- global->halfWidth = global->screenWidth / 2;
- }
- if (global->height_override)
- {
- global->screenHeight = global->height_override;
- global->halfHeight = global->screenHeight / 2;
- }
- if (set_gfx_mode (screen_mode, global->screenWidth, global->screenHeight, 0, 0) < 0)
- {
- perror( "set_gfx_mode");
- // exit (1);
- status = set_gfx_mode(screen_mode, 800, 600, 0, 0);
- if ( status < 0 ) exit(1);
- global->screenWidth = 800;
- global->screenHeight = 600;
- global->halfWidth = 400;
- global->halfHeight = 300;
- }
-
-#ifdef WIN32
- if (global->full_screen == FULL_SCREEN_TRUE)
- set_display_switch_mode(SWITCH_BACKAMNESIA);
- else
- set_display_switch_mode(SWITCH_BACKGROUND);
-#endif
-
- if (install_keyboard () < 0)
- {
- perror ( "install_keyboard failed");
- exit (1);
- }
- if (install_timer () < 0)
- {
- perror ( "install_timer failed");
- exit (1);
- }
- if (install_mouse () < 0)
- {
- perror ( "install_mouse failed");
- // exit (1);
- }
-
- // check to see if we want sound
- if (global->sound > 0.0)
- {
- /*
- // don't stop program if no sound since the game can be played without
- if (install_sound (DIGI_AUTODETECT, MIDI_NONE, NULL) < 0)
- fprintf (stderr, "install_sound: %s", allegro_error);
- }
- */
- int sound_type = DIGI_AUTODETECT;
- #ifdef LINUX
- switch ( (int) global->sound_driver )
- {
- case SOUND_OSS: sound_type = DIGI_OSS; break;
- case SOUND_ESD: sound_type = DIGI_ESD; break;
- case SOUND_ARTS: sound_type = DIGI_ARTS; break;
- case SOUND_ALSA: sound_type = DIGI_ALSA; break;
- case SOUND_JACK: sound_type = DIGI_JACK; break;
- default: sound_type = DIGI_AUTODETECT; break;
- }
- #endif
- #ifdef UBUNTU
- if (sound_type == DIGI_AUTODETECT)
- sound_type = DIGI_OSS;
- #endif
- if (detect_digi_driver(sound_type))
- {
- if (install_sound (sound_type, MIDI_NONE, NULL) < 0)
- {
- fprintf (stderr, "install_sound: failed initialising sound\n");
- fprintf (stderr, "Please try selecting a different Sound Driver from the Options menu.\n");
- }
- }
- else
- fprintf (stderr, "detect_digi_driver detected no sound device\n");
- } // end of we want sound
-
- lock_cclock();
- LOCK_VARIABLE(cclock);
- unlock_cclock();
- LOCK_FUNCTION(clockadd);
- // if (install_int_ex (clockadd, BPS_TO_TIMER (FRAMES_PER_SECOND)) < 0) {
- if (install_int_ex (clockadd, BPS_TO_TIMER(global->frames_per_second)) < 0)
- {
- perror ( "install_int_ex");
- exit (1);
- }
- if (install_int (fpsadd, 1000) < 0)
- {
- perror ( "install_int");
- exit (1);
- }
-
- srand (time (0));
- WHITE = makecol (255, 255, 255);
- BLACK = makecol (0, 0, 0);
- PINK = makecol (255, 0, 255);
- RED = makecol (255, 0, 0);
- GREEN = makecol (0, 255, 0);
- DARK_GREEN = makecol(0, 80, 0);
- BLUE = makecol (0, 0, 255);
- PURPLE = makecol (200, 0, 200);
- YELLOW = makecol (255, 255, 0);
-
- global->Load_Bitmaps();
- global->Load_Fonts();
-
- if (! global->os_mouse) show_mouse(NULL);
- blit ((BITMAP *) global->title[0], screen, 0, 0, global->halfWidth - 320, global->halfHeight - 240, 640, 480);
- if (! global->os_mouse) show_mouse (screen);
-
- global->Load_Sounds();
-
- for (count = 0; count < ALL_LANDS; count++)
- global->gfxData.land_gradient_strips[count] = NULL;
- for (count = 0; count < ALL_SKIES; count++)
- global->gfxData.sky_gradient_strips[count] = NULL;
-
- global->gfxData.explosion_gradient_strip = create_gradient_strip (explosion_gradients[colour_theme], 200);
- // for (count = 0; count < 2; count++)
- // global->gfxData.explosion_gradient_strips[count] = create_gradient_strip (explosion_gradients[count], 200);
-
- expSize = 0;
- disperseSize = 0;
- for (count = 0; count < EXPLOSIONFRAMES; count++)
- {
- global->gfxData.explosions[count] = create_bitmap (214, 214);
- if (count == 0)
- {
- expSize = 25;
- disperseSize = 0;
- }
- else if (count < EXPLODEFRAMES - 4)
- expSize += (107 - expSize) / 3;
- else if (count < EXPLODEFRAMES)
- expSize--;
- else if (count == EXPLODEFRAMES)
- disperseSize = 25;
- else
- disperseSize += (107 - disperseSize) / 2;
-
- clear_to_color (global->gfxData.explosions[count], PINK);
- for (y = (int)expSize; y > disperseSize; y--)
- {
- double value;
- value = pow ((double)y / expSize, count / 4 + 1);
- circlefill (global->gfxData.explosions[count], 107, 107, y, getpixel (global->gfxData.explosion_gradient_strip, 0, (int)(value * 200)));
- }
- if (disperseSize)
- circlefill (global->gfxData.explosions[count], 107, 107, (int)disperseSize, PINK);
- }
-
- expSize = 0;
- disperseSize = 0;
- for (count = 0; count < EXPLOSIONFRAMES; count++)
- {
- global->gfxData.flameFront[count] = create_bitmap (600, 30);
- if (count == 0)
- {
- expSize = 10;
- disperseSize = 0;
- }
- else if (count < EXPLODEFRAMES - 4)
- expSize += (300 - expSize) / 3;
- else if (count < EXPLODEFRAMES)
- expSize--;
- else if (count == EXPLODEFRAMES)
- disperseSize = 10;
- else
- disperseSize += (300 - disperseSize) / 2;
-
- clear_to_color (global->gfxData.flameFront[count], PINK);
- for (y = (int)expSize; y > disperseSize; y--)
- {
- double value;
- value = pow ((double)y / expSize, count / 4 + 1);
- ellipsefill (global->gfxData.flameFront[count], 300, 15, y, y / 20, getpixel (global->gfxData.explosion_gradient_strip, 0, (int)(value * 200)));
- }
- if (disperseSize)
- ellipsefill (global->gfxData.flameFront[count], 300, 15, (int)disperseSize, (int)disperseSize / 16, PINK);
- }
-
- global->gfxData.topbar = create_bitmap (global->screenWidth, MENUHEIGHT);
- global->gfxData.topbar_gradient_strip = create_gradient_strip (topbar_gradient, 100);
- if (!global->ditherGradients)
- {
- for (count = 0; count < MENUHEIGHT; count++)
- {
- float adjCount = (100.0 / MENUHEIGHT) * count;
- line (global->gfxData.topbar, 0, count, global->screenWidth - 1, count, getpixel (global->gfxData.topbar_gradient_strip, 0, (int)adjCount));
- }
- }
- else
- {
- for (x = 0; x < global->screenWidth; x++)
- {
- for (y = 0; y < MENUHEIGHT; y++)
- {
- float adjY = (100.0 / MENUHEIGHT) * y;
- int offset;
- if ((adjY < 2) || (adjY > 100 - 2))
- offset = 0;
- else
- offset = rand () % 4 - 2;
- putpixel (global->gfxData.topbar, x, y, getpixel (global->gfxData.topbar_gradient_strip, 0, (int)adjY + offset));
- }
- }
- }
-
- global->gfxData.stuff_bar[0] = create_bitmap (STUFF_BAR_WIDTH, STUFF_BAR_HEIGHT);
- global->gfxData.stuff_bar[1] = create_bitmap (STUFF_BAR_WIDTH, STUFF_BAR_HEIGHT);
- global->gfxData.stuff_icon_base = create_bitmap (STUFF_BAR_WIDTH/10, STUFF_BAR_HEIGHT);
- clear_to_color (global->gfxData.stuff_bar[0], PINK);
- clear_to_color (global->gfxData.stuff_bar[1], PINK);
- clear_to_color (global->gfxData.stuff_icon_base, PINK);
- global->gfxData.stuff_bar_gradient_strip = create_gradient_strip (stuff_bar_gradient, STUFF_BAR_WIDTH);
- for (x = 0; x < STUFF_BAR_WIDTH; x++)
- {
- for (y = 0; y < STUFF_BAR_HEIGHT; y++)
- {
- double sides_dist = 0.1, circle_dist;
- circle_dist = vector_length_f ((float)x-(STUFF_BAR_WIDTH - 75), (float)y - (STUFF_BAR_HEIGHT/2 - 2), 0);
- if (circle_dist < 75)
- circle_dist = 1 - (circle_dist / 75.0);
+ static char return_string[257] = { 0 };
+
+ // Get the dimensions of the score board and the texts right:
+ int32_t lh = env.fontHeight + 3; // The line height.
+ int32_t pd = 10; // Padding. How much space to the board border.
+
+ // Find out longest player name and score length do determine the
+ // score board size and score entry positions
+ char head_name[5] = "Name";
+ char head_value[6] = "Value";
+ char head_score[30] = { 0 };
+ snprintf(head_score, 29, " %6s %6s %6s %6s", "Kills",
+ "Killed", "Diff", "Won");
+
+ int32_t namLen = text_length(font, head_name);
+ int32_t valLen = text_length(font, head_value);
+ int32_t scoLen = text_length(font, head_score);
+
+ // While checking for the winner, determine the real lengths needed
+ int32_t idx_jedi = -1; // Jedi Player with the highest score
+ int32_t idx_neutral = -1; // Neutral player with the highest score
+ int32_t idx_sith = -1; // SitH Player with the highest score
+ int32_t idx_winner = -1; // Player with the highest score
+ int32_t maxdiff = INT32_MIN;
+ int32_t maxscore = -1;
+ int32_t maxkills = -1;
+ int32_t minkilled = INT32_MAX;
+ bool multiwinner = false;
+ PLAYER** players = env.players; // short cut
+ int32_t pl_money[MAXPLAYERS] = { 0 };
+
+ for (int32_t z = 0; z < env.numGamePlayers; z++) {
+
+ // Check the length of the name
+ int32_t curLen = text_length(font, players[z]->getName());
+ if (curLen > namLen)
+ namLen = curLen;
+
+ // Sum up weapons worth
+ for (int32_t j = 0; j < WEAPONS; ++j) {
+ if (weapon[j].amt && players[z]->nm[j])
+ pl_money[z] += (weapon[j].cost / weapon[j].amt) * players[z]->nm[j];
+ }
+
+ // Sum up items worth
+ for (int32_t j = 0; j < ITEMS; ++j) {
+ if (item[j].amt && players[z]->ni[j])
+ pl_money[z] += (item[j].cost / item[j].amt) * players[z]->ni[j];
+ }
+
+ // Check value length
+ char valTxt[16] = { 0 };
+ snprintf(valTxt, 15, " %14s", Add_Comma(pl_money[z]));
+ curLen = text_length(font, valTxt);
+ if (curLen > valLen)
+ valLen = curLen;
+
+ // Check the length of the score
+ char scoTxt[30] = { 0 };
+ int32_t kill_diff = players[z]->kills - players[z]->killed;
+ snprintf(scoTxt, 29, " %6d %6d %6d %6d",
+ players[z]->kills,
+ players[z]->killed,
+ kill_diff,
+ players[z]->score);
+ curLen = text_length(font, scoTxt);
+ if (curLen > scoLen)
+ scoLen = curLen;
+
+ // Determine whether a new winner or a draw situation is found
+ if ( (players[z]->score == maxscore)
+ && (players[z]->kills == maxkills)
+ && (players[z]->killed == minkilled) ) {
+ multiwinner = true;
+ if (TEAM_NEUTRAL == players[z]->team)
+ idx_neutral = z;
+ } else if ( (players[z]->score > maxscore)
+ || ( (players[z]->score == maxscore)
+ && (kill_diff > maxdiff) )
+ || ( (players[z]->score == maxscore)
+ && (kill_diff == maxdiff)
+ && (players[z]->kills > maxkills) ) ) {
+ // Note: killed doesn't need to be checked. the same
+ // amount of kills with less killed score would mean
+ // a better diff anyway.
+ maxdiff = kill_diff;
+ maxkills = players[z]->kills;
+ maxscore = players[z]->score;
+ minkilled = players[z]->killed;
+ idx_winner = z;
+ multiwinner = false;
+ if (TEAM_NEUTRAL == players[z]->team)
+ idx_neutral = z;
+ }
+
+ if (TEAM_JEDI == players[z]->team)
+ idx_jedi = z;
+ if (TEAM_SITH == players[z]->team)
+ idx_sith = z;
+ } // end of checking players
+
+ // Now calculate the dimensions of our score board.
+ int32_t w = namLen + valLen + scoLen + (2 * pd);
+ int32_t h = ((env.numGamePlayers + 4) * lh) + (2 * pd);
+ int32_t x = env.halfWidth - (w / 2);
+ int32_t y = env.halfHeight - (h / 2);
+ int32_t qy = y + h + pd;
+ BOX qarea(x + pd, qy, w - (2 * pd), env.screenHeight - pd - qy);
+
+ //stop mouse during drawing
+ SHOW_MOUSE(nullptr)
+
+ global.make_update (x, y - pd - env.misc[9]->h, w, h + pd + env.misc[9]->h);
+
+ // Draw the winning bitmap, the background and the border
+ draw_simple_bg(false);
+ draw_sprite(global.canvas, env.misc[9],
+ env.halfWidth - (env.misc[9]->w / 2),
+ y - env.misc[9]->h - pd);
+ rectfill(global.canvas, x, y, x + w, y + h, BLACK);
+ rect (global.canvas, x, y, x + w, y + h, WHITE);
+ rect (global.canvas, x + 1, y + 1, x + w - 1, y + h - 1, GREY);
+
+ // Add the padding now, or it must be summed in everywhere!
+ x += pd;
+ y += pd;
+ w -= 2 * pd;
+ h -= 2 * pd;
+
+ // Draw winner names and info about all players
+ if (multiwinner) {
+ // check for team win
+ if ( TEAM_JEDI == players[idx_winner]->team ) {
+ if ( ( (idx_sith >= 0)
+ && (players[idx_sith]->score == players[idx_winner]->score) )
+ || ( (idx_neutral >= 0)
+ && (players[idx_neutral]->score == players[idx_winner]->score) ) )
+ snprintf(return_string, 256, "%s", env.ingame->Get_Line(48));
else
- circle_dist = 0;
-
- if (x < (STUFF_BAR_HEIGHT/2 - 2))
- sides_dist -= 0.1 - ((float)x / 150.0);
- else if (x > STUFF_BAR_WIDTH - (STUFF_BAR_HEIGHT/2 - 2))
- sides_dist -= ((float)(x - (STUFF_BAR_WIDTH - (STUFF_BAR_HEIGHT/2 - 2))) / 150.0);
-
- if (y < STUFF_BAR_HEIGHT/2 - 2)
- sides_dist -= 0.1 - ((float)(y) / 150.0);
+ snprintf(return_string, 256, "%s", env.ingame->Get_Line(45));
+ } else if ( TEAM_SITH == players[idx_winner]->team ) {
+ if ( ( (idx_jedi >= 0)
+ && (players[idx_jedi]->score == players[idx_winner]->score) )
+ || ( (idx_neutral >= 0)
+ && (players[idx_neutral]->score == players[idx_winner]->score) ) )
+ snprintf(return_string, 256, "%s", env.ingame->Get_Line(48));
else
- sides_dist -= ((float)(y - (STUFF_BAR_HEIGHT/2 - 2)) / 150.0);
-
- sides_dist -= circle_dist * circle_dist;
- if (sides_dist > ((double)x / 1000.0))
- sides_dist = ((double)x / 1000.0);
- if (sides_dist < 0)
- sides_dist = 0;
- if (circle_dist > 1)
- circle_dist = 1;
-
- if (x < STUFF_BAR_WIDTH/10)
- putpixel (global->gfxData.stuff_icon_base, x, y, getpixel (global->gfxData.stuff_bar_gradient_strip, 0, (int)((sides_dist + circle_dist) * (STUFF_BAR_WIDTH-1))));
-
- if (y < STUFF_BAR_HEIGHT - 5)
- {
- putpixel (global->gfxData.stuff_bar[0], x, y, getpixel (global->gfxData.stuff_bar_gradient_strip, 0, (int)((sides_dist + circle_dist) * (STUFF_BAR_WIDTH-1))));
- putpixel (global->gfxData.stuff_bar[1], x, y, getpixel (global->gfxData.stuff_bar_gradient_strip, 0, (int)((sides_dist + circle_dist + 0.05) * (STUFF_BAR_WIDTH-1))));
- }
- }
- }
-
- if (! global->os_mouse )
- {
- set_mouse_sprite ((BITMAP *) global->misc[0]);
- set_mouse_sprite_focus (0, 0);
- }
-
- global->window.x = 0;
- global->window.y = 0;
- global->window.w = 0;
- global->window.h = 0;
- for (z = 0; z < MAXUPDATES; z++)
- {
- global->updates[z].x = 0;
- global->updates[z].y = 0;
- global->updates[z].w = 0;
- global->updates[z].h = 0;
- }
-
- env = new ENVIRONMENT (global);
- if (!env)
- {
- perror ( "atanks.cc: Allocating env in init_game_settings");
- exit (1);
- }
-
- clear_to_color (env->db, BLACK);
- global->env = env;
-
- return (env);
+ snprintf(return_string, 256, "%s", env.ingame->Get_Line(46));
+ } else
+ snprintf(return_string, 256, "%s", env.ingame->Get_Line(48));
+ } else {
+ if ( TEAM_JEDI == players[idx_winner]->team )
+ snprintf(return_string, 256, "%s", env.ingame->Get_Line(45));
+ else if (TEAM_SITH == players[idx_winner]->team)
+ snprintf(return_string, 256, "%s", env.ingame->Get_Line(46));
+ else
+ snprintf(return_string, 256, "%s: %s", env.ingame->Get_Line(47),
+ players[idx_winner]->getName() );
+ }
+
+ // Print the title lines
+ textprintf_centre_ex(global.canvas, font, env.halfWidth, y,
+ players[idx_winner]->color, -1, "%s", return_string);
+
+ // to make the following easier, skip the two used lines
+ // (The title and one blank)
+ y += 2 * lh;
+
+ // Second title line, the score board header
+ int32_t valStart = x + namLen;
+ int32_t scoStart = valStart + valLen;
+ int32_t scoWidth = scoLen / 4;
+
+ textout_ex (global.canvas, font, "Name", x, y, WHITE, -1);
+ textprintf_right_ex (global.canvas, font, valStart + valLen, y, WHITE,
+ -1, " %14s", "$ Value");
+ textprintf_right_ex (global.canvas, font, scoStart + (1 * scoWidth),
+ y, GREEN, -1, " %6s", "Kills");
+ textprintf_right_ex (global.canvas, font, scoStart + (2 * scoWidth),
+ y, RED, -1, " %6s", "Killed");
+ textprintf_right_ex (global.canvas, font, scoStart + (3 * scoWidth),
+ y, WHITE, -1, " %6s", "Diff");
+ textprintf_right_ex (global.canvas, font, scoStart + (4 * scoWidth),
+ y, WHITE, -1, " %6s", "Won");
+
+ // Now get the score list:
+ sScore* score_array = sort_scores();
+
+ // And get the head entry:
+ sScore* score = score_array;
+ while (score->prev)
+ score = score->prev;
+
+
+ // Eventually the player scores can be displayed:
+ // (again skip the previous line for easier reading/doing below)
+ y += lh;
+ int32_t z = 0;
+ while (score) {
+ textout_ex (global.canvas, font, score->name,
+ x, y + (z * lh), score->color, -1);
+ textprintf_right_ex (global.canvas, font, valStart + valLen,
+ y + (z * lh), WHITE,
+ -1, " %14s", Add_Comma(pl_money[score->idx]));
+ textprintf_right_ex (global.canvas, font, scoStart + (1 * scoWidth),
+ y + (z * lh), GREEN, -1, " %6d", score->kills);
+ textprintf_right_ex (global.canvas, font, scoStart + (2 * scoWidth),
+ y + (z * lh), RED, -1, " %6d", score->killed);
+ textprintf_right_ex (global.canvas, font, scoStart + (3 * scoWidth),
+ y + (z * lh), score->diff < 0 ? RED : GREEN, -1,
+ " %6d", score->diff);
+ textprintf_right_ex (global.canvas, font, scoStart + (4 * scoWidth),
+ y + (z * lh), WHITE, -1, " %6d", score->score);
+ ++z;
+ score = score->next;
+ }
+ global.do_updates();
+
+ // Add a war quote:
+ const char* quote = env.war_quotes->Get_Random_Line();
+ if (quote)
+ draw_text_in_box(&qarea, quote, false);
+
+ // Clean up
+ delete [] score_array;
+
+ return return_string;
}
-int options (GLOBALDATA *global, ENVIRONMENT *env, MENUDESC *menu);
-int createNewPlayer (GLOBALDATA *global, ENVIRONMENT *env)
+static void endgame_cleanup()
{
- PLAYER *newPlayer = global->createNewPlayer (env);
- if (!newPlayer)
- {
- perror ( "atanks.cc: Failed allocating memory in createNewPlayer");
- // exit (1);
- }
- options (global, env, (MENUDESC*)newPlayer->menudesc);
- return (-1);
+ while (env.numGamePlayers > 0) {
+ if (env.players[0]->tank) {
+ delete env.players[0]->tank;
+ env.players[0]->tank = nullptr;
+ }
+
+ // make sure networked clients say good-bye and return
+ // to old AI level
+ if (env.players[0]->type >= NETWORK_CLIENT)
+ env.players[0]->type =
+ env.players[0]->previous_type;
+
+ env.removeGamePlayer(env.players[0]);
+ }
+
+ global.clear_objects();
}
-int destroyPlayer (GLOBALDATA *global, ENVIRONMENT *env, void *data)
-{
- int optionsRetVal;
- char sureMessage[200];
- PLAYER *tempPlayer = (PLAYER*)data;
- MENUDESC areYouSureMenu = { "Are You Sure?", 0, NULL, TRUE, TRUE};
-
- sprintf (sureMessage, "This player (%s) will be permanently deleted", tempPlayer->getName ());
- errorMessage = sureMessage;
- errorX = global->halfWidth - text_length (font, errorMessage) / 2;
- errorY = 170;
- optionsRetVal = options (global, env, &areYouSureMenu);
- if (optionsRetVal >> 8 != KEY_ESC)
- {
- global->destroyPlayer (tempPlayer);
- return (-2);
- }
- return (KEY_SPACE << 8);
-}
-int displayPlayerName (ENVIRONMENT *env, int x, int y, void *data)
+#if defined(ATANKS_IS_MSVC) && defined(ATANKS_DEBUG)
+// this removes the keyboard and mouse handler on break events,
+// hopefully making both operational in Visual Studio again if
+// a crash was caught. It does not work on breakpoints though,
+// that doesn't trigger the Handler.
+BOOL WINAPI ctrlHandler(DWORD CtrlType)
{
- PLAYER *player = (PLAYER*)data;
- char *name = player->getName ();
- int textHeight = text_height (font);
- int textLength = text_length (font, name);
-
- if ((int)player->type == HUMAN_PLAYER)
- {
- int radius = 5;
- circlefill (env->db, x - textLength - textHeight / 2 - 2,
- y + textHeight / 2,
- radius, makecol (200, 100, 255));
- circle (env->db, x - textLength - textHeight / 2 - 2,
- y + textHeight / 2,
- radius, BLACK);
- }
- else
- {
- rectfill (env->db,
- x - textLength - 2 - ((int)player->type * 3),
- y + textHeight - 10,
- x - textLength - 2,
- y + textHeight - 2,
- makecol (100, 255, 100));
- rect (env->db,
- x - textLength - 2 - ((int)player->type * 3),
- y + textHeight - 10,
- x - textLength - 2,
- y + textHeight - 2,
- BLACK);
- for (int lineCount = 1; lineCount < player->type; lineCount++)
- {
- vline (env->db,
- x - textLength - 2 - (lineCount * 3),
- y + textHeight - 2,
- y + textHeight - 10,
- BLACK);
- }
- }
- textout_ex (env->db, font, name, x - textLength, y, player->color, -1);
+ if ( ( CTRL_BREAK_EVENT == CtrlType )
+ || ( CTRL_C_EVENT == CtrlType ) ) {
+ remove_mouse();
+ remove_keyboard();
+ }
- return (0);
+ return FALSE;
}
+#endif // Microsoft Visual C++ and Debug
-int options (GLOBALDATA *global, ENVIRONMENT *env, MENUDESC *menu)
-{
- MENUENTRY *opts;
- char my_pointer[2];
- BUTTON *reset_button = NULL;
- int selected_index = 0, my_key = 0;
- int numEntries;
- const char *title;
- int ke, z;
- int mouseLeftPressed;
- char *reset_text, *back_text;
-#include "menucontent.h"
-
- if (global->language == LANGUAGE_GERMAN)
- {
- reset_text = "Alles zurucksetzen";
- back_text = "Zuruck";
- }
- else
- {
- reset_text = "Reset All";
- back_text = "Back";
- }
-
- if (!menu)
- {
- menu = &mainMenu;
- reset_button = new BUTTON(global, env, global->halfWidth - 5 - text_length (font, menu->title), global->menuBeginY + 70, // 170,
- reset_text, (BITMAP *) global->misc[7], (BITMAP *) global->misc[7],
- (BITMAP *) global->misc[8]);
- }
-
- opts = menu->entries;
- numEntries = menu->numEntries;
- title = menu->title;
-
- char name_buff[64] = {0x0};
- char format_buff[64] = {0x0};
- int done, lb;
- int stop = 0;
-
- int *updateoption;
-
- updateoption = (int *)calloc(numEntries, sizeof(int));
- if (!updateoption)
- {
- // Die hard!
- cerr << "ERROR: Unable to allocate " << numEntries << " bytes in options() !!!" << endl;
- // exit (1);
- }
-
- BUTTON *but_okay = NULL, *but_quit = NULL;
- if (menu->okayButton)
- {
- int xpos = global->halfWidth - 80;
- if (menu->quitButton)
- xpos -= 80;
- // but_okay = new BUTTON (global, env, xpos, global->halfHeight + 160, "Okay", (BITMAP*)global->misc[7], (BITMAP*)global->misc[7], (BITMAP*)global->misc[8]);
- but_okay = new BUTTON (global, env, xpos, global->menuBeginY + 360 , "Okay", (BITMAP*)global->misc[7], (BITMAP*)global->misc[7], (BITMAP*)global->misc[8]);
- if (!but_okay)
- {
- perror ( "atanks.cc: Failed allocating memory for but_okay in options");
- // exit (1);
- }
- }
- if (menu->quitButton)
- {
- int xpos = global->halfWidth - 80;
- if (menu->okayButton)
- xpos += 80;
- but_quit = new BUTTON (global, env, xpos, global->menuBeginY + 360, back_text, (BITMAP*)global->misc[7], (BITMAP*)global->misc[7], (BITMAP*)global->misc[8]);
- // but_quit = new BUTTON (global, env, xpos, global->halfHeight + 160, back_text, (BITMAP*)global->misc[7], (BITMAP*)global->misc[7], (BITMAP*)global->misc[8]);
- if (!but_quit)
- {
- perror ( "atanks.cc: Failed allocating memory for but_quit in options");
- // exit (1);
- }
- }
-
- lock_cclock();
- mouseLeftPressed = done = lb = env->mouseclock = cclock = 0;
- unlock_cclock();
- fi = 1;
-
- for (z = 0; z < numEntries; z++)
- {
- updateoption[z] = 1;
- }
-
- flush_inputs ();
-
- do
- {
- LINUX_SLEEP;
- while (get_cclock() > 0)
- {
- lock_cclock();
- cclock--;
- unlock_cclock();
- my_key = 0;
- if ( keypressed() )
- {
- my_key = readkey();
- my_key = my_key >> 8;
- if (my_key == KEY_DOWN)
- {
- selected_index++;
- if (selected_index >= numEntries)
- selected_index = 0;
- my_key = 0;
- }
- else if (my_key == KEY_UP)
- {
- selected_index--;
- if (selected_index < 0)
- selected_index = numEntries - 1;
- my_key = 0;
- }
- else if (my_key == KEY_ENTER_PAD)
- my_key = KEY_ENTER;
-
- for (z = 0; z < numEntries; z++)
- updateoption[z] = TRUE;
- fi = TRUE;
-
- } // end of a key was pressed
-
- if (!lb && mouse_b & 1)
- {
- env->mouseclock = 0;
- mouseLeftPressed = 1;
- }
- else
- {
- mouseLeftPressed = 0;
- }
- lb = (mouse_b & 1) ? 1 : 0;
- if ( ((mouse_b & 1 || mouse_b & 2) && !env->mouseclock) || (my_key) )
- {
- for (z = 0; z < numEntries; z++)
- {
- int midX = opts[z].x;
- int midY = opts[z].y;
- if (opts[z].type == OPTION_MENUTYPE)
- {
- sprintf (name_buff, "-> %s", opts[z].name);
- if ( ((!opts[z].viewonly) && mouse_x > midX - text_length (font, name_buff) && mouse_x < midX && mouse_y >= midY && mouse_y < midY + 10) ||
- ( (my_key == KEY_SPACE) && (selected_index == z)) )
- {
- int optsRetVal = options (global, env, (MENUDESC*)opts[z].value);
- if (optsRetVal < 0)
- {
- return (optsRetVal + 1);
- }
- fi = 1;
- for (z = 0; z < numEntries; z++)
- {
- updateoption[z] = 1;
- }
- my_key = selected_index = 0;
- }
- }
- else if (opts[z].type == OPTION_ACTIONTYPE)
- {
- sprintf (name_buff, "-> %s", opts[z].name);
- if ( ((!opts[z].viewonly) && mouse_x > midX - text_length (font, name_buff) && mouse_x < midX && mouse_y >= midY && mouse_y < midY + 10) ||
- ( (my_key == KEY_SPACE) && (selected_index == z) ) )
- {
- int (*action) (GLOBALDATA*, ENVIRONMENT*, void*)
- = (int (*)(GLOBALDATA*, ENVIRONMENT*, void*))opts[z].value;
- int actionRetVal = action (global, env, opts[z].data);
- if (actionRetVal)
- return (actionRetVal);
- }
- }
- else if (opts[z].type == OPTION_TEXTTYPE)
- {
- int my_text_length;
- strcmp(opts[z].name, "Name") ? my_text_length = 11 : my_text_length = NAME_LENGTH;
- int boxWidth;
- if (! strcmp(opts[z].name, "Server address") )
- my_text_length = ADDRESS_LENGTH;
-
- if (my_text_length == NAME_LENGTH)
- boxWidth = textEntryBox (global, env, FALSE, midX + 100, midY, (char*)opts[z].value, my_text_length);
- else if (my_text_length == ADDRESS_LENGTH)
- boxWidth = textEntryBox(global, env, FALSE, midX + 70, midY, (char*) opts[z].value, my_text_length);
- else
- boxWidth = textEntryBox (global, env, FALSE, midX + 50, midY, (char*)opts[z].value, my_text_length);
- if ( ((!opts[z].viewonly) && mouse_x > midX - text_length (font, name_buff) && mouse_x < midX + 50 + boxWidth && mouse_y >= midY && mouse_y < midY + 10) ||
- ( (selected_index == z) && (my_key == KEY_SPACE) ) )
- {
- if (my_text_length == NAME_LENGTH)
- textEntryBox (global, env, TRUE, midX + 100, midY, (char*)opts[z].value, my_text_length);
- else if (my_text_length == ADDRESS_LENGTH)
- textEntryBox(global, env, TRUE, midX + 70, midY, (char *)opts[z].value, my_text_length);
- else
- textEntryBox (global, env, TRUE, midX + 50, midY, (char*)opts[z].value, my_text_length);
- updateoption[z] = 1;
- }
- }
- else if (opts[z].type == OPTION_COLORTYPE)
- {
- if ((!opts[z].viewonly) && mouse_x > midX && mouse_x < midX + 100 && mouse_y >= midY && mouse_y < midY + 15)
- {
- *(int*)opts[z].value = pickColor (midX, midY, 100, 15, mouse_x, mouse_y);
- updateoption[z] = 1;
- }
- colorBar (env, midX, midY, 100, 15);
- rectfill (env->db, midX + 110, midY, midX + 130, midY + 10, *(int*)opts[z].value);
- rect (env->db, midX + 110, midY, midX + 130, midY + 10, BLACK);
- }
- else if (opts[z].type == OPTION_TOGGLETYPE)
- {
- int tlen = text_length (font, name_buff);
- int thgt = text_height (font);
- int temp_counter;
- if ( (mouseLeftPressed && (!opts[z].viewonly) && mouse_x > midX - tlen / 2 && mouse_x < midX + tlen / 2 && mouse_y >= midY && mouse_y < midY + thgt) ||
- ( (my_key == KEY_SPACE) && (selected_index == z) ) )
- {
- if (*opts[z].value == 0)
- *opts[z].value = 1;
- else
- *opts[z].value = 0;
- mouseLeftPressed = 1;
- // updateoption[z] = 1;
- // Crude, but hopefulyl useful
- for (temp_counter = 0; temp_counter < numEntries; temp_counter++)
- updateoption[temp_counter] = 1;
- }
- }
- else
- {
- if (!opts[z].viewonly)
- {
- if (mouse_x >= midX + 100 && mouse_x < midX + 110 && mouse_y >= midY && mouse_y < midY + 10)
- {
- if (mouse_b & 1)
- *opts[z].value -= opts[z].increment;
- else if (mouse_b & 2)
- *opts[z].value -= opts[z].increment * 10;
- updateoption[z] = 1;
- }
- if (mouse_x >= midX + 112 && mouse_x < midX + 122 && mouse_y >= midY && mouse_y < midY + 10)
- {
- if (mouse_b & 1)
- *opts[z].value += opts[z].increment;
- else if (mouse_b & 2)
- *opts[z].value += opts[z].increment * 10;
- updateoption[z] = 1;
- }
-
- if ( (my_key == KEY_RIGHT) && (selected_index == z) )
- {
- *opts[z].value += opts[z].increment;
- updateoption[z] = 1;
- }
- else if ( (my_key == KEY_LEFT) && (selected_index == z) )
- {
- *opts[z].value -= opts[z].increment;
- updateoption[z] = 1;
- }
-
- // This if block is a nasty hack to get the tank
- // styles to redraw on the Players menu.
- if (updateoption[z])
- {
- int my_counter;
- for (my_counter = 0; my_counter < numEntries; my_counter++)
- updateoption[my_counter] = TRUE;
- fi = TRUE;
- }
- /*if (mouse_x >= midX + 134 && mouse_x < midY + 154 && mouse_y >= midY && mouse_y < midY + 10) {
- *opts[z].value = opts[z].defaultv;
- updateoption[z] = 1;
- }*/
- if (*opts[z].value > opts[z].max)
- {
- *opts[z].value = opts[z].min;
- }
- if (*opts[z].value < opts[z].min)
- {
- *opts[z].value = opts[z].max;
- }
- }
- }
- }
- }
- env->mouseclock++;
- if (env->mouseclock > 10)
- {
- env->mouseclock = 0;
- }
- }
-
- env->make_update (mouse_x, mouse_y, ((BITMAP *) (global->misc[0]))->w, ((BITMAP *) (global->misc[0]))->h);
- env->make_update (lx, ly, ((BITMAP *) (global->misc[0]))->w, ((BITMAP *) (global->misc[0]))->h);
- lx = mouse_x;
- ly = mouse_y;
- if (! global->os_mouse) show_mouse (NULL);
-
- // draw menu stuff
- if (fi)
- {
- drawMenuBackground (global, env, BACKGROUND_BLANK, rand (), 400);
- textout_ex (env->db, font, title, global->halfWidth - 3 - text_length (font, title), global->menuBeginY + 50, BLACK, -1);
- textout_ex (env->db, font, title, global->halfWidth - 5 - text_length (font, title), global->menuBeginY + 50, WHITE, -1);
- for (z = 0; z < numEntries; z++)
- {
- int midX = opts[z].x;
- int midY = opts[z].y;
-
- if (z == selected_index)
- strcpy(my_pointer, "*");
- else
- my_pointer[0] = '\0';
-
- if (opts[z].type == OPTION_TOGGLETYPE)
- {
- int color = (*opts[z].value)?WHITE:BLACK;
- int radius = text_length(font, opts[z].name) / 2;
- int y_radius = text_height(font);
- if (y_radius > 8)
- y_radius = 8;
- ellipsefill (env->db, midX, midY + text_height (font) / 2, radius, y_radius, color);
- textout_ex(env->db, font, my_pointer, (midX - radius) - 40 , midY, WHITE, -1);
- }
-
- if (opts[z].displayFunc)
- {
- if (opts[z].type == OPTION_TOGGLETYPE)
- {
- opts[z].displayFunc (env,
- midX + text_length (font, opts[z].name) / 2, midY,
- opts[z].data);
- }
- else if (opts[z].type == OPTION_MENUTYPE)
- {
- opts[z].displayFunc (env,
- midX, midY,
- opts[z].data);
- sprintf(name_buff, "%s", my_pointer);
- textout_ex(env->db, font, name_buff, midX - 125, midY, WHITE, -1);
- }
- else if (opts[z].type == OPTION_SPECIALTYPE)
- {
- opts[z].displayFunc(env,
- midX + text_length(font, opts[z].name) / 2, midY,
- opts[z].value );
- sprintf (name_buff, "%s %s:", my_pointer, opts[z].name);
- textout_ex (env->db, font, name_buff, midX - text_length (font, name_buff),
- midY, opts[z].color, -1);
- if (!opts[z].viewonly)
- {
- draw_sprite_v_flip (env->db, (BITMAP *) global->misc[6], midX + 100, midY);
- draw_sprite (env->db, (BITMAP *) global->misc[6], midX + 112, midY);
- }
- }
- }
- else if (opts[z].type == OPTION_MENUTYPE)
- {
- sprintf (name_buff, "%s -> %s", my_pointer, opts[z].name);
- textout_ex (env->db, font, name_buff, midX - text_length (font, name_buff), midY, opts[z].color, -1);
- }
- else if (opts[z].type == OPTION_ACTIONTYPE)
- {
- sprintf (name_buff, "%s -> %s", my_pointer, opts[z].name);
- textout_ex (env->db, font, name_buff, midX - text_length (font, name_buff), midY, opts[z].color, -1);
- }
- else if (opts[z].type == OPTION_TEXTTYPE)
- {
- sprintf (name_buff, "%s %s:", my_pointer, opts[z].name);
- textout_ex (env->db, font, name_buff, midX - text_length (font, name_buff), midY, opts[z].color, -1);
- }
- else if (opts[z].type == OPTION_COLORTYPE)
- {
- sprintf (name_buff, "%s %s:", my_pointer, opts[z].name);
- textout_ex (env->db, font, name_buff, midX - text_length (font, name_buff), midY, opts[z].color, -1);
- }
- else if (opts[z].type == OPTION_TOGGLETYPE)
- {
- sprintf (name_buff, "%s %s", my_pointer, opts[z].name);
- textout_centre_ex (env->db, font, name_buff, midX, midY, opts[z].color, -1);
- }
- else
- {
- sprintf (name_buff, "%s %s:", my_pointer, opts[z].name);
- textout_ex (env->db, font, name_buff, midX - text_length (font, name_buff), midY, opts[z].color, -1);
- if (!opts[z].viewonly)
- {
- draw_sprite_v_flip (env->db, (BITMAP *) global->misc[6], midX + 100, midY);
- draw_sprite (env->db, (BITMAP *) global->misc[6], midX + 112, midY);
- }
- }
-
- if (my_pointer[0])
- env->make_update(midX - 200, midY - 20, 400, 40);
-
- } // end of for loop
- if (but_okay) but_okay->draw (env->db);
- if (but_quit) but_quit->draw (env->db);
- if (reset_button) reset_button->draw(env->db);
- } // end of if fi
-
- for (z = 0; z < numEntries; z++)
- {
- int midX = opts[z].x;
- int midY = opts[z].y;
- if (updateoption[z])
- {
- updateoption[z] = 0;
- if (opts[z].type == OPTION_TOGGLETYPE)
- {
- int color = (*opts[z].value)?WHITE:BLACK;
- int text_tall = text_height(font);
- if (text_tall > 8)
- text_tall = 8;
- ellipsefill (env->db, midX, midY + text_height (font) / 2, text_length (font, opts[z].name) / 2, text_tall, color);
- }
- if (opts[z].displayFunc)
- {
- if (opts[z].type == OPTION_TOGGLETYPE)
- {
- opts[z].displayFunc (env,
- midX + text_length (font, opts[z].name) / 2, midY,
- opts[z].data);
- }
- else if (opts[z].type == OPTION_MENUTYPE)
- {
- opts[z].displayFunc (env,
- midX, midY,
- opts[z].data);
- }
- env->make_update (midX - 200, midY - text_height (font), 450, 50);
- env->do_updates();
- }
- else if (opts[z].type != OPTION_MENUTYPE && opts[z].type != OPTION_ACTIONTYPE)
- {
- if (opts[z].type == OPTION_DOUBLETYPE)
- {
- sprintf (format_buff, opts[z].format, *opts[z].value);
- textEntryBox (global, env, FALSE, midX + 50, midY, format_buff, 11);
- }
- else if (opts[z].type == OPTION_TEXTTYPE)
- {
- textEntryBox (global, env, FALSE, midX + 100, midY, (char*)opts[z].value, NAME_LENGTH);
- }
- else if (opts[z].type == OPTION_COLORTYPE)
- {
- colorBar (env, midX, midY, 100, 15);
- rectfill (env->db, midX + 110, midY, midX + 130, midY + 10, *(int*)opts[z].value);
- rect (env->db, midX + 110, midY, midX + 130, midY + 10, BLACK);
- }
- else if (opts[z].type == OPTION_TOGGLETYPE)
- {
- sprintf (format_buff, "%s", opts[z].name);
- textout_centre_ex (env->db, font, format_buff, midX, midY, opts[z].color, -1);
- }
- else if (opts[z].specialOpts)
- {
- textEntryBox (global, env, FALSE, midX + 50, midY, opts[z].specialOpts[(int) *opts[z].value], 11);
- }
- env->make_update (midX - 100, midY - 2, 250, 20);
- }
- }
- }
-
- if (fi)
- {
- fi = 0;
- quickChange (global, env->db);
- }
-
- if ( (but_quit && but_quit->isPressed()) || (my_key == KEY_ESC) )
- {
- global->wr_lock_command();
- global->command = GLOBAL_COMMAND_MENU;
- global->unlock_command();
- stop = 1;
- }
- if ( (but_okay && but_okay->isPressed()) || (my_key == KEY_ENTER) )
- {
- stop = 2;
- }
-
- if ( (reset_button) && (reset_button->isPressed() ) )
- {
- // RESET all options!
- env->Reset_Options();
- global->Reset_Options();
- }
-
- if (but_okay) but_okay->draw (env->db);
- if (but_quit) but_quit->draw (env->db);
- if (reset_button) reset_button->draw(env->db);
-
- if (! global->os_mouse) show_mouse(env->db);
- if (global->close_button_pressed) stop = 1;
- env->do_updates ();
- }
- // while ((!keypressed ()) && (!stop) );
- while (! stop);
-
- if (!stop)
- ke = readkey ();
- else if (stop == 2)
- ke = KEY_ENTER << 8;
- else
- ke = KEY_ESC << 8;
-
- flush_inputs();
-
- if (but_quit)
- delete but_quit;
- if (but_okay)
- delete but_okay;
-
- if ( reset_button )
- delete reset_button;
-
- free(updateoption);
-
- return (ke);
-}
-
-int editPlayers (GLOBALDATA *global, ENVIRONMENT *env)
+void init_mouse_cursor()
{
- int optionsRetVal;
- // int rows = (global->screenHeight - 400) / 15;
- int rows = (global->screenHeight - 2 * global->menuBeginY - 200) / 15;
- int columns = (global->numPermanentPlayers / rows) + 1;
- rows = (rows / columns) + 1;
-
- MENUENTRY *playersOpts;
- MENUDESC playersMenu;
- // playersOpts = new MENUENTRY[1 + global->numPermanentPlayers];
- playersOpts = (MENUENTRY *) calloc( global->numPermanentPlayers + 1, sizeof(MENUENTRY));
- if (!playersOpts)
- {
- perror ( "atanks.cc: Failed allocating memory for playersOpts in editPlayers");
- return 0;
- // exit (1);
- }
- playersOpts[0].name = "Create New";
- playersOpts[0].displayFunc = NULL;
- playersOpts[0].color = WHITE;
- playersOpts[0].value = (double*)createNewPlayer;
- playersOpts[0].data = NULL;
- playersOpts[0].type = OPTION_ACTIONTYPE;
- playersOpts[0].viewonly = FALSE;
- playersOpts[0].x = global->halfWidth - 3;
- // playersOpts[0].y = global->halfHeight - 68 - 15;
- playersOpts[0].y = global->menuBeginY + 117;
-
- playersMenu.title = "Players";
- playersMenu.numEntries = 1 + global->numPermanentPlayers;
- playersMenu.entries = playersOpts;
- playersMenu.quitButton = TRUE;
- playersMenu.okayButton = FALSE;
-
- for (int count = 0; count < global->numPermanentPlayers; count++)
- {
- MENUENTRY *opt = &playersOpts[1 + count];
-
- opt->name = global->allPlayers[count]->getName ();
- opt->displayFunc = displayPlayerName;
- opt->data = global->allPlayers[count];
- opt->color = global->allPlayers[count]->color;
- opt->value = (double*)global->allPlayers[count]->menudesc;
- opt->type = OPTION_MENUTYPE;
- opt->viewonly = FALSE;
- opt->x = global->halfWidth - (((count % columns) - (columns / 2)) * 90) - (((columns + 1) % 2) * 45);
- // opt->y = global->halfHeight - 68 + ((count / columns) * 15);
- opt->y = global->menuBeginY + 132 + ((count / columns) * 15);
- }
- optionsRetVal = options (global, env, &playersMenu);
-
- // delete playersOpts;
- free(playersOpts);
-
- return (optionsRetVal);
+ if (env.osMouse )
+ show_os_cursor(MOUSE_CURSOR_ARROW);
+ else {
+ set_mouse_sprite (env.misc[0]);
+ set_mouse_sprite_focus (0, 0);
+ }
}
-int selectPlayers (GLOBALDATA *global, ENVIRONMENT *env)
-{
- MENUENTRY roundOpt = { global->ingame->complete_text[1], NULL, WHITE, (double*)&global->rounds, NULL, "%2.0f", 1, MAX_ROUNDS, 1, 5, NULL,
- OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->menuBeginY + 82
- };
- MENUENTRY gamename = { global->ingame->complete_text[2], NULL, WHITE, (double *) global->game_name, NULL, "%s", 0, 0, 0, 0, NULL,
- OPTION_TEXTTYPE, FALSE, global->halfWidth - 3, global->menuBeginY + 100
- };
- MENUENTRY loadgame, campaign;
- int save_game_exists;
-
- int optionsRetVal, z;
- // int rows = (global->screenHeight - 400) / 15;
- int rows = (global->screenHeight - 2 * global->menuBeginY - 200) / 15;
- int columns = (global->numPermanentPlayers / rows) + 1;
- int playerCount = 0;
-
- int number_saved_games = 0;
- struct dirent **saved_game_names;
- char **game_list = NULL;
- MENUENTRY *playersOpts;
- MENUDESC playersMenu;
- MENUENTRY load_game_entry;
-
- // find saved games
- saved_game_names = Find_Saved_Games(global, &number_saved_games);
- if ( (saved_game_names) && ( number_saved_games ) )
- {
- int count;
-
- // move the names into a char list
- game_list = (char **) calloc( number_saved_games, sizeof(char *) );
- for (count = 0; count < number_saved_games; count++)
- {
- game_list[count] = saved_game_names[count]->d_name;
- // clear trailign extension
- if ( strchr(game_list[count], '.') )
- strchr(game_list[count], '.')[0] = '\0';
- }
-
- global->saved_game_list = game_list;
- // set up menu for selecting saved games
- load_game_entry.name = global->ingame->complete_text[3];
- load_game_entry.displayFunc = NULL;
- load_game_entry.color = WHITE;
- load_game_entry.value = (double *) &global->saved_game_index;
- load_game_entry.data = NULL;
- load_game_entry.format = "%s";
- load_game_entry.min = 0;
- load_game_entry.max = number_saved_games - 1;
- load_game_entry.increment = 1;
- load_game_entry.defaultv = 0;
- load_game_entry.specialOpts = global->saved_game_list;
- load_game_entry.type = OPTION_SPECIALTYPE;
- load_game_entry.viewonly = FALSE;
- load_game_entry.x = global->halfWidth;
- load_game_entry.y = global->menuBeginY + 120;
- }
-
- rows = (rows / columns) + 1;
- // playersOpts = new MENUENTRY[global->numPermanentPlayers + 4];
- playersOpts = (MENUENTRY *) calloc( global->numPermanentPlayers + 5, sizeof(MENUENTRY) );
- if (!playersOpts)
- {
- perror ( "atanks.cc: Failed allocating memory for playersOpts in selectPlayers");
- // exit (1);
- }
-
- loadgame.name = global->ingame->complete_text[4];
- loadgame.displayFunc = NULL;
- loadgame.data = &global->load_game;
- loadgame.color = WHITE;
- loadgame.value = (double *) &global->load_game;
- loadgame.type = OPTION_TOGGLETYPE;
- loadgame.viewonly = FALSE;
- loadgame.x = global->halfWidth - 50;
- loadgame.y = global->menuBeginY + 140;
-
- campaign.name = global->ingame->complete_text[5];
- campaign.displayFunc = NULL;
- campaign.data = &global->campaign_mode;
- campaign.color = WHITE;
- campaign.value = (double *) &global->campaign_mode;
- campaign.type = OPTION_TOGGLETYPE;
- campaign.viewonly = FALSE;
- campaign.x = global->halfWidth + 50;
- campaign.y = global->menuBeginY + 140;
-
- playersMenu.title = global->ingame->complete_text[0];
- playersMenu.numEntries = global->numPermanentPlayers + 5;
- playersMenu.entries = playersOpts;
- playersMenu.quitButton = TRUE;
- playersMenu.okayButton = TRUE;
-
- for (int count = 0; count < global->numPermanentPlayers; count++)
- {
- MENUENTRY *opt = &playersOpts[count];
-
- opt->name = global->allPlayers[count]->getName ();
- opt->displayFunc = displayPlayerName;
- opt->data = global->allPlayers[count];
- opt->color = global->allPlayers[count]->color;
- opt->value = (double*)&global->allPlayers[count]->selected;
- opt->type = OPTION_TOGGLETYPE;
- opt->viewonly = FALSE;
- opt->x = global->halfWidth - (((count % columns) - (columns / 2)) * 90) - (((columns + 1) % 2) * 45);
- // opt->y = 265 + ( (count / columns) * 15 );
- opt->y = global->menuBeginY + 165 + ( (count / columns) * 15);
- }
- memcpy (&playersOpts[global->numPermanentPlayers], &roundOpt, sizeof (MENUENTRY));
- memcpy (&playersOpts[global->numPermanentPlayers + 1], &gamename, sizeof (MENUENTRY));
- memcpy (&playersOpts[global->numPermanentPlayers + 2], &loadgame, sizeof (MENUENTRY));
- memcpy (&playersOpts[global->numPermanentPlayers + 3], &campaign, sizeof (MENUENTRY));
- if ((number_saved_games) && (game_list) )
- memcpy (&playersOpts[global->numPermanentPlayers + 4], &load_game_entry, sizeof(MENUENTRY));
-
- do
- {
- optionsRetVal = options (global, env, &playersMenu);
- if ( global->load_game )
- {
- if ( ( global->saved_game_list) && ( global->saved_game_list[ (int) global->saved_game_index ][0] ) )
- {
- memset(global->game_name, '\0', GAME_NAME_LENGTH);
- strncpy(global->game_name, global->saved_game_list[ (int) global->saved_game_index ], 16);
- }
- }
-
- if (optionsRetVal >> 8 == KEY_ENTER)
- {
- if (! global->load_game ) // trying to play a game
- {
- playerCount = 0;
- global->numPlayers = 0;
- for (z = 0; z < global->numPermanentPlayers; z++)
- {
- if (global->allPlayers[z]->selected)
- {
- global->addPlayer (global->allPlayers[z]);
- playerCount++;
- }
- }
- if ((playerCount < 2) || (playerCount > MAXPLAYERS))
- {
- if (playerCount < 2)
- errorMessage = global->ingame->complete_text[8];
- else if (playerCount > MAXPLAYERS)
- errorMessage = global->ingame->complete_text[9];
- errorX = global->halfWidth - text_length (font, errorMessage) / 2;
- errorY = global->menuBeginY + 70;
- optionsRetVal = 0;
- }
- else
- {
- optionsRetVal = PLAY_GAME;
- }
- } // end of trying to start a new game
- else // start to load an existing game
- {
- save_game_exists = Check_For_Saved_Game(global);
- if (save_game_exists)
- optionsRetVal = LOAD_GAME;
- else
- {
- optionsRetVal = 0;
- errorMessage = global->ingame->complete_text[39];
- errorX = global->halfWidth - text_length(font, errorMessage) / 2;
- errorY = global->menuBeginY + 70;
- }
- }
- }
- // zero means an error occured.
- // keep running the loop until ESC is pressed or a non-zero value appears
- }
- // while (optionsRetVal == 0);
- while ( (optionsRetVal >> 8 != KEY_ESC) && (optionsRetVal != PLAY_GAME) &&
- (optionsRetVal != LOAD_GAME) );
-
- // delete playersOpts;
- free(playersOpts);
- if (optionsRetVal >> 8 == KEY_ESC)
- optionsRetVal = ESC_MENU;
-
- if (saved_game_names) free(saved_game_names);
-
- return (optionsRetVal);
-}
-void title (GLOBALDATA *global)
+static void init_game_settings()
{
- if (! global->os_mouse) show_mouse(NULL);
- blit ((BITMAP *) global->title[0], screen, 0, 0, global->halfWidth - 320, global->halfHeight - 240, 640, 480);
- if (! global->os_mouse) show_mouse (screen);
- clear_keybuf ();
+ if (env.full_screen == FULL_SCREEN_TRUE)
+ env.osMouse = false;
- //wait_for_input();
+ int32_t status = allegro_init();
- if (! global->os_mouse) show_mouse (NULL);
-}
-
-
-
-
-int menu (GLOBALDATA *global, ENVIRONMENT *env)
-{
- int ban, anclock, lb, updateplayers, done, updaterounds, z;
- int move_text;
- int seconds_idle = 0;
- int current_index = 0, max_index = 7;
- int my_key = 0;
- int bn = 0; // button number
- int shift_menu = global->halfHeight - 240;
- if (shift_menu < 0) shift_menu = -shift_menu;
- else shift_menu = 0;
-
- move_text = 75;
- if (global->language == LANGUAGE_RUSSIAN)
- bn += MENUBUTTONS * 2;
-
- BUTTON but_play(global, env, global->halfWidth - move_text, global->halfHeight - 235 + shift_menu, NULL, (BITMAP*)global->button[bn], (BITMAP*)global->button[bn], (BITMAP*)global->button[bn+1]);
- bn += 2;
- BUTTON but_help(global, env, global->halfWidth - move_text, global->halfHeight - 185 + shift_menu, NULL, (BITMAP*)global->button[bn], (BITMAP*)global->button[bn], (BITMAP*)global->button[bn+1]);
- bn += 2;
- BUTTON but_options(global, env, global->halfWidth - move_text, global->halfHeight - 135 + shift_menu, NULL, (BITMAP*)global->button[bn], (BITMAP*)global->button[bn], (BITMAP*)global->button[bn+1]);
- bn += 2;
- BUTTON but_players(global, env, global->halfWidth - move_text, global->halfHeight - 85 + shift_menu, NULL, (BITMAP*)global->button[bn], (BITMAP*)global->button[bn], (BITMAP*)global->button[bn+1]);
- bn += 2;
- BUTTON but_credits(global, env, global->halfWidth - move_text, global->halfHeight - 35 + shift_menu, NULL, (BITMAP*)global->button[bn], (BITMAP*)global->button[bn], (BITMAP*)global->button[bn+1]);
- bn += 2;
- BUTTON but_quit(global, env, global->halfWidth - move_text, global->halfHeight + 65 + shift_menu, NULL, (BITMAP*)global->button[bn], (BITMAP*)global->button[bn], (BITMAP*)global->button[bn+1]);
- bn += 2;
- BUTTON but_network(global, env, global->halfWidth - move_text, global->halfHeight + 15 + shift_menu, NULL, (BITMAP*)global->button[bn], (BITMAP*)global->button[bn], (BITMAP*)global->button[bn+1]);
-
- BUTTON *button[MENUBUTTONS] = {&but_play, &but_help, &but_options,
- &but_players, &but_credits, &but_network, &but_quit
- };
-
- ban = -1;
- lock_cclock();
- cclock = global->updateCount = lx = ly = anclock = env->mouseclock = 0;
- unlock_cclock();
- lb = updateplayers = done = updaterounds = 0;
- fi = global->stopwindow = 1;
- while (!done)
- {
- if ( global->Check_Time_Changed() )
- {
- seconds_idle++;
- if (seconds_idle > DEMO_WAIT_TIME)
- {
- done = 1;
- global->wr_lock_command();
- global->command = GLOBAL_COMMAND_DEMO;
- global->unlock_command();
- }
- }
-
- // LINUX_SLEEP;
- LINUX_REST;
- while (get_cclock() > 0)
- {
- lock_cclock();
- cclock--;
- unlock_cclock();
- for (z = 0; z < MENUBUTTONS; z++)
- {
- if (button[z]->isMouseOver ())
- {
- if (ban > -1 && ban != z)
- {
- button[z]->draw (env->db);
- //env->make_update (button[ban]->location.x, button[ban]->location.y, button[ban]->location.w, button[ban]->location.h);
- }
- ban = z;
- break;
- }
- }
- if (!lb && mouse_b & 1)
- env->mouseclock = 0;
- lb = (mouse_b & 1) ? 1 : 0;
- if (mouse_b & 1)
- {
- global->wr_lock_command();
- for (z = 0; z < MENUBUTTONS; z++)
- {
- if (button[z]->isPressed ())
- {
- if (z == 0)
- global->command = GLOBAL_COMMAND_PLAY;
- else if (z == 1)
- global->command = GLOBAL_COMMAND_HELP;
- else if (z == 2)
- global->command = GLOBAL_COMMAND_OPTIONS;
- else if (z == 3)
- global->command = GLOBAL_COMMAND_PLAYERS;
- else if (z == 4)
- global->command = GLOBAL_COMMAND_CREDITS;
- else if (z == 5)
- global->command = GLOBAL_COMMAND_NETWORK;
- else if (z == 6)
- global->command = GLOBAL_COMMAND_QUIT;
- done = 1;
- }
- }
- global->unlock_command();
- }
- env->mouseclock++;
- if (env->mouseclock > 10)
- env->mouseclock = 0;
- }
- if (updaterounds)
- {
- updaterounds = 0;
- env->make_update (global->halfWidth + 27, global->halfHeight + 198, 32, 32);
- }
- if (! global->os_mouse) show_mouse (NULL);
- if (fi)
- {
- draw_circlesBG (global, env->db, 0, 0, global->screenWidth, global->screenHeight, true);
- //textout (env->db, font, (char *)"Rounds: ", global->halfWidth - 45, global->halfHeight + 200, BLACK);
- //draw_sprite_v_flip (env->db, (BITMAP *) global->gfxData.M[6].dat, global->halfWidth - 60, global->halfHeight + 199);
- //draw_sprite (env->db, (BITMAP *) global->gfxData.M[6].dat, global->halfWidth + 64, global->halfHeight + 199);
- for (z = 0; z < MENUBUTTONS; z++)
- {
- button[z]->draw (env->db);
- if (z == current_index)
- {
- // int delta_width = (global->language == LANGUAGE_RUSSIAN) ? 20 : 0;
- // if (global->language == LANGUAGE_GERMAN) delta_width = 20;
- // draw rectangle around selected option
- rect(env->db, global->halfWidth - move_text - 8,
- global->halfHeight - 240 + (50 * current_index) + shift_menu,
- global->halfWidth + move_text + 8, // - delta_width,
- global->halfHeight - 190 + (50 * current_index) + shift_menu, YELLOW);
- }
- }
- }
- if (ban > -1)
- {
- button[ban]->draw (env->db);
- //env->make_update (button[ban]->location.x, button[ban]->location.y, button[ban]->location.w, button[ban]->location.h);
- }
- //rectfill (env->db, global->halfWidth + 27, global->halfHeight + 198, global->halfWidth + 59, global->halfHeight + 210, WHITE);
- //rect (env->db, global->halfWidth + 27, global->halfHeight + 198, global->halfWidth + 59, global->halfHeight + 210, BLACK);
- //textprintf_centre (env->db, font, global->halfWidth + 43, global->halfHeight + 200, BLACK, (char *)"%d", global->rounds);
- if (! global->os_mouse) show_mouse (env->db);
- env->make_update (mouse_x, mouse_y, ((BITMAP *) (global->misc[0]))->w, ((BITMAP *) (global->misc[0]))->h);
- env->make_update (lx, ly, ((BITMAP *) (global->misc[0]))->w, ((BITMAP *) (global->misc[0]))->h);
- lx = mouse_x;
- ly = mouse_y;
-
- // check for key press
- if ( keypressed() )
- {
- my_key = readkey();
- my_key = my_key >> 8;
- fi = 2;
- }
-
- if ( ( my_key == KEY_DOWN ) || (my_key == KEY_S) )
- {
- current_index++;
- if (current_index >= max_index)
- current_index = 0;
- }
- else if ( (my_key == KEY_UP) || (my_key == KEY_W) )
- {
- current_index--;
- if (current_index < 0)
- current_index = max_index - 1;
- }
- else if ( (my_key == KEY_ENTER) ||
- (my_key == KEY_ENTER_PAD) ||
- (my_key == KEY_SPACE) )
- {
- done = 1;
- global->wr_lock_command();
- if (current_index == 0)
- global->command = GLOBAL_COMMAND_PLAY;
- else if (current_index == 1)
- global->command = GLOBAL_COMMAND_HELP;
- else if (current_index == 2)
- global->command = GLOBAL_COMMAND_OPTIONS;
- else if (current_index == 3)
- global->command = GLOBAL_COMMAND_PLAYERS;
- else if (current_index == 4)
- global->command = GLOBAL_COMMAND_CREDITS;
- else if (current_index == 5)
- global->command = GLOBAL_COMMAND_NETWORK;
- else if (current_index == 6)
- global->command = GLOBAL_COMMAND_QUIT;
- global->unlock_command();
-
- }
- else if ( (my_key == KEY_Q) || (my_key == KEY_ESC))
- {
- return SIG_QUIT_GAME;
- }
- my_key = 0;
-
- if (fi > 0)
- {
- fi--;
- change (global, env->db);
- }
- if (global->close_button_pressed) return SIG_QUIT_GAME;
- if ( (global->update_string) && (global->update_string[0]) )
- {
- textout_centre_ex (env->db, font, global->update_string,
- global->halfWidth - 20, 500, WHITE, -1);
- env->make_update(50, 450, 300, 50);
- }
- if (global->client_message)
- {
- textout_centre_ex(env->db, font, global->client_message,
- global->halfWidth - 20, 520, WHITE, -1);
- env->make_update(50, 450, 300, 100);
- }
- env->do_updates ();
- }
- clear_keybuf ();
- return SIG_OK;
-}
-
-void draw_text_in_box (ENVIRONMENT *env, BOX *region, char *text)
-{
- char buffer[1024];
- unsigned int lineBegin;
- int lastSpace = 0;
- int lineCount;
- int charCount;
- int buffCount ;
-
- rectfill (env->db, region->x, region->y, region->w, region->h,
- makecol (0,0,128));
- rect (env->db, region->x, region->y, region->w, region->h,
- makecol (128,128,255));
-
- lineBegin = 0;
- lineCount = 0;
- while (lineBegin < strlen (text))
- {
- charCount = 0;
- buffCount = 0;
- do
- {
- buffer[buffCount] = text[lineBegin + charCount];
- buffer[buffCount+1] = 0;
- if (buffer[buffCount] == ' ')
- {
- lastSpace = 0;
- }
- else if (buffer[buffCount] == '\n')
- {
- lineCount++;
- charCount++;
- break;
- }
- lastSpace++;
- buffCount++;
- charCount++;
- }
- while (text[lineBegin + charCount] && (text_length (font, buffer) < region->w - 20));
- if ((lastSpace > 0) && (text_length (font, buffer) >= region->w - 20))
- {
- charCount -= lastSpace - 1;
- buffer[buffCount - lastSpace] = 0;
- }
- else
- {
- buffer[buffCount] = 0;
- }
- textout_ex (env->db, font, buffer, region->x + 5,
- region->y + (lineCount * text_height (font)) + 5, WHITE, -1);
- lineBegin = lineBegin + charCount;
- charCount = 0;
- lineCount++;
- }
- env->make_update (region->x, region->y, region->w, region->h);
-}
+ if (status) {
+ fprintf(stderr, "Unable to start Allegro.\nStatus %d", status);
+ exit(1);
+ }
-void draw_buystuff(GLOBALDATA *global, ENVIRONMENT *env, PLAYER *pl)
-{
- int z;
- env->make_update (0, 0, global->screenWidth, global->screenHeight);
- if (! global->os_mouse) show_mouse (NULL);
-
- draw_circlesBG (global, env->db, 0, 0, global->screenWidth, global->screenHeight, false);
- draw_sprite (env->db, (BITMAP *) global->misc[DONE_IMAGE], global->halfWidth - 100, global->screenHeight - 50);
- draw_sprite (env->db, (BITMAP *) global->misc[FAST_UP_ARROW_IMAGE], global->screenWidth - STUFF_BAR_WIDTH - 30, global->halfHeight - 50);
- draw_sprite (env->db, (BITMAP *) global->misc[UP_ARROW_IMAGE], global->screenWidth - STUFF_BAR_WIDTH - 30, global->halfHeight - 25);
- draw_sprite (env->db, (BITMAP *) global->misc[DOWN_ARROW_IMAGE], global->screenWidth - STUFF_BAR_WIDTH - 30, global->halfHeight);
- draw_sprite (env->db, (BITMAP *) global->misc[FAST_DOWN_ARROW_IMAGE], global->screenWidth - STUFF_BAR_WIDTH - 30, global->halfHeight + 25);
- drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
- env->current_drawing_mode = DRAW_MODE_TRANS;
-
- for (z = 0; z < global->halfWidth - 200; z++)
- {
- set_trans_blender (0, 0, 0, (int)((double)((double)z / (global->halfWidth - 200)) * 240) + 15);
- vline (env->db, z, 0, 29, pl->color);
- }
+ // Be sure no vsync is used:
+ const char* no_vsync = get_config_string("graphics", "disable_vsync", "no");
+ if ( strcasecmp("yes", no_vsync) )
+ set_config_string("graphics", "disable_vsync", "yes");
- for (z = 0; z < global->halfWidth - 200; z++)
- {
- set_trans_blender (0, 0, 0, (int)((double)((double)z / (global->halfWidth - 200)) * 240) + 15);
- vline (env->db, (global->screenWidth-1) - z, 0, 29, pl->color);
- }
+ set_window_title( "Atomic Tanks V" VERSION);
- solid_mode ();
- env->current_drawing_mode = DRAW_MODE_SOLID;
- textout_ex (env->db, font, global->ingame->complete_text[14], 20, 420, WHITE, -1);
- textout_ex(env->db, font, global->ingame->complete_text[15], 20, 450, WHITE, -1);
- textout_ex(env->db, font, global->ingame->complete_text[16], 20, 465, WHITE, -1);
+ // Before we get started make sure, that if we are using full
+ // screen mode, we have to ignore width and height settings.
+ if (env.full_screen == FULL_SCREEN_TRUE) {
+ status = get_desktop_resolution(&env.screenWidth, &env.screenHeight);
+ if (status < 0) {
+ env.screenWidth = 800;
+ env.screenHeight = 600;
+ }
+ screen_mode = GFX_AUTODETECT_FULLSCREEN;
+ }
-}
+ // check for X pressed on the window bar
+ LOCK_FUNCTION(close_button_handler);
+ set_close_button_callback(close_button_handler);
-int btps;
-void draw_weapon_list(GLOBALDATA *global, ENVIRONMENT *env, PLAYER *pl, int *trolley, int scroll, int pressed)
-{
- int slot, zzz;
- double tempbtps = (global->screenHeight - 55) / STUFF_BAR_HEIGHT;
- int font_diff;
-
- (font == global->unicode) ? font_diff = 8 : font_diff = 0;
-
- // To be sure it rounds down
- btps = (int)tempbtps;
- if (tempbtps < btps)
- btps--;
-
-
- // go through all items and draw them on the screen with
- // the amount of items in the trolly
- for (slot = 1, zzz = scroll; (slot < btps) && (zzz < env->numAvailable); zzz++)
- {
- int itemNum = env->availableItems[zzz];
- draw_sprite (env->db, (BITMAP *)global->gfxData.stuff_bar[(pressed == itemNum)?1:0], global->screenWidth - STUFF_BAR_WIDTH, slot * STUFF_BAR_HEIGHT);
-
- draw_sprite(env->db, (BITMAP *) global->gfxData.stuff_icon_base, global->screenWidth - STUFF_BAR_WIDTH, (slot * STUFF_BAR_HEIGHT));
- draw_sprite(env->db, (BITMAP *) global->stock[itemNum], global->screenWidth - STUFF_BAR_WIDTH, (slot * STUFF_BAR_HEIGHT) - 5);
- env->make_update (global->screenWidth - STUFF_BAR_WIDTH, slot * STUFF_BAR_HEIGHT, STUFF_BAR_WIDTH, STUFF_BAR_HEIGHT + 5);
-
- if (itemNum >= WEAPONS) /* Items part */
- {
-
- textout_ex (env->db, font,
- item[itemNum - WEAPONS].name, global->screenWidth - STUFF_BAR_WIDTH + 45, (slot * STUFF_BAR_HEIGHT) + 5 - font_diff, BLACK, -1);
- // Amount in inventory
- textprintf_ex (env->db, font, global->screenWidth - STUFF_BAR_WIDTH + 45, (slot * STUFF_BAR_HEIGHT) + (STUFF_BAR_HEIGHT/2) - font_diff, BLACK, -1, "%s: %d", global->ingame->complete_text[40], pl->ni[itemNum - WEAPONS]);
- // Anything in the trolley
- if (trolley[itemNum] != 0)
- {
- int textCol;
- if (trolley[itemNum] > 0)
- textCol = makecol (255,255,0);
- else
- textCol = makecol (176,0,0);
- textprintf_ex (env->db, font, global->screenWidth - STUFF_BAR_WIDTH + 45 + text_length (font, "Qty. in inventory: ddd"), (slot * STUFF_BAR_HEIGHT) + (STUFF_BAR_HEIGHT/2) - font_diff, textCol, -1, "%+d", trolley[itemNum]);
- }
- sprintf (buf, "$%s", Add_Comma( item[itemNum - WEAPONS].cost ) );
- textout_ex (env->db, font, buf,
- global->screenWidth - 45 - text_length (font, buf), (slot * STUFF_BAR_HEIGHT) + 5 - font_diff, BLACK, -1);
- sprintf (buf, "for %d", item[itemNum - WEAPONS].amt);
- textout_ex (env->db, font, buf,
- global->screenWidth - 45 - text_length (font, buf), (slot * STUFF_BAR_HEIGHT) + (STUFF_BAR_HEIGHT/2) - font_diff, BLACK, -1);
- }
- else /* Weapons part */
- {
-
- textout_ex (env->db, font, weapon[itemNum].name, global->screenWidth - STUFF_BAR_WIDTH + 45, (slot * STUFF_BAR_HEIGHT) + 5 - font_diff, BLACK, -1);
- textprintf_ex (env->db, font, global->screenWidth - STUFF_BAR_WIDTH + 45, (slot * STUFF_BAR_HEIGHT) + (STUFF_BAR_HEIGHT/2) - font_diff, BLACK, -1, "%s: %d", global->ingame->complete_text[40], pl->nm[itemNum]);
- // Anything in the trolley
- if (trolley[itemNum] != 0)
- {
- int textCol;
- if (trolley[itemNum] > 0)
- textCol = makecol (255,255,0);
- else
- textCol = makecol (176,0,0);
- textprintf_ex (env->db, font, global->screenWidth - STUFF_BAR_WIDTH + 45 + text_length (font, "Qty. in inventory: ddd"), (slot * STUFF_BAR_HEIGHT) + (STUFF_BAR_HEIGHT/2) - font_diff, textCol, -1, "%+d", weapon[itemNum].delay ? trolley[itemNum] / weapon[itemNum].delay : trolley[itemNum]);
- }
- sprintf (buf, "$%s", Add_Comma( weapon[itemNum].cost ));
- textout_ex (env->db, font, buf,
- global->screenWidth - 45 - text_length (font, buf), (slot * STUFF_BAR_HEIGHT) + 5 - font_diff, BLACK, -1);
- sprintf (buf, "for %d", weapon[itemNum].amt);
- textout_ex (env->db, font, buf, global->screenWidth - 45 - text_length (font, buf), (slot * STUFF_BAR_HEIGHT) + (STUFF_BAR_HEIGHT/2) - font_diff, BLACK, -1);
- }
- slot++;
- }
+ // Ensure sane colour depth
+ if (! env.colourDepth)
+ env.colourDepth = desktop_color_depth();
+ if ( (env.colourDepth != 16) && (env.colourDepth != 32) )
+ env.colourDepth = 16;
+ set_color_depth (env.colourDepth);
-}
+ // Now the screen mode can be set
+ if (set_gfx_mode(screen_mode, env.screenWidth, env.screenHeight, 0, 0) < 0) {
+ perror( "set_gfx_mode");
-bool buystuff (GLOBALDATA *global, ENVIRONMENT *env)
-{
- int pl, done;
- int updatename, pressed, scroll, lb, lastMouse_b;
- int hoverOver = 0, z, zz, zzz;
- char buf[50];
- int mouse_wheel_previous, mouse_wheel_current;
- int performed_save_game = FALSE;
- char description[1024];
- BOX area = {20, 60, 300, 400};
- int my_cclock;
- int item_index = 1;
- int my_key = 0;
- int cost, amt, inInv;
- int buy_count = 0;
-
- strcpy(description, " ");
- //avoid compiler warning via initalize
- lastMouse_b = 0;
-
- // check starting position of the mouse wheel
- mouse_wheel_previous = mouse_z;
-
- lock_cclock();
- global->updateCount = cclock = lb = env->mouseclock = 0;
- unlock_cclock();
- fi = global->stopwindow = updatename = scroll = 1;
-
- // before we do anything else, put a cap on money
- for (z = 0; z < global->numPlayers; z++)
- {
- if (global->players[z]->money > 1000000000)
- global->players[z]->money = 1000000000;
- if (global->players[z]->money < 0)
- global->players[z]->money = 0;
- }
-
-
- if (global->bIsGameLoaded)
- // after the first shopping loop the game isn't fresh anymore
-#ifdef DEBUG
- {
- cout << endl << "===========================================" << endl;
- cout << "== New or Loaded Game! ==" << endl;
- cout << "===========================================" << endl << endl;
-#endif // DEBUG
- global->bIsGameLoaded = false;
-#ifdef DEBUG
- }
-#endif // DEBUG
- else if (global->divide_money)
- {
- // This only applies if this is not a fresh/loaded game
- // And if players want to divide the money
- int iJediMoney = 0;
- int iJediCount = 0;
- int iSithMoney = 0;
- int iSithCount = 0;
- int iTeamFee = 0;
-
- for (z = 0; z < global->numPlayers; z++)
- {
- /*
- if (global->players[z]->money > 1000000000)
- global->players[z]->money = 1000000000;
- if (global->players[z]->money < 0)
- global->players[z]->money = 0;
- */
- // Sum up team money:
- if (global->players[z]->team == TEAM_JEDI)
- {
- iTeamFee = (int)((double)global->players[z]->money * 0.25);
- if (iTeamFee > MAX_TEAM_AMOUNT)
- iTeamFee = MAX_TEAM_AMOUNT;
- iJediMoney += iTeamFee;
- global->players[z]->money -= iTeamFee;
- iJediCount++;
- }
- if (global->players[z]->team == TEAM_SITH)
- {
- iTeamFee = (int)((double)global->players[z]->money * 0.25);
- if (iTeamFee > MAX_TEAM_AMOUNT)
- iTeamFee = MAX_TEAM_AMOUNT;
- iSithMoney += iTeamFee;
- global->players[z]->money -= iTeamFee;
- iSithCount++;
- }
- }
-#ifdef DEBUG
- cout << endl << "Jedi Count: " << iJediCount << " - SitH Count: " << iSithCount << endl;
-#endif // DEBUG
- // Now apply the team money:
- if (iJediCount || iSithCount)
- {
-#ifdef DEBUG
- if (iJediCount)
- printf( (char *)"The Jedi summed up a pool of %13d bucks!\n", iJediMoney);
- if (iSithCount)
- printf( (char *)"The Sith summed up a pool of %13d bucks!\n", iSithMoney);
-#endif // DEBUG
- if (iJediCount)
- iJediMoney = (int)(((double)iJediMoney * 0.90) / (double)iJediCount);
- if (iSithCount)
- iSithMoney = (int)(((double)iSithMoney * 0.90) / (double)iSithCount);
-#ifdef DEBUG
- if (iJediCount)
- printf( (char *)"Every Jedi will receive %10d credits out of the pool!\n", iJediMoney);
- if (iSithCount)
- printf( (char *)"Every Sith will receive %10d credits out of the pool!\n", iSithMoney);
-#endif // DEBUG
- for (z = 0; z < global->numPlayers; z++)
- {
- if (global->players[z]->team == TEAM_JEDI)
- global->players[z]->money += iJediMoney;
- if (global->players[z]->team == TEAM_SITH)
- global->players[z]->money += iSithMoney;
- }
- }
- }
+ status = set_gfx_mode(screen_mode, 800, 600, 0, 0);
- env->generateAvailableItemsList ();
- int iMaxBoost = 0;
- int iMaxScore = 0;
- for (pl = 0; pl < global->numPlayers; pl++)
- {
- int iCurrentBoostLevel = global->players[pl]->getBoostValue();
- if (iCurrentBoostLevel > iMaxBoost)
- iMaxBoost = iCurrentBoostLevel;
- if (global->players[pl]->score > iMaxScore)
- iMaxScore = global->players[pl]->score;
+ if ( status < 0 )
+ exit(1);
+ env.screenWidth = 800;
+ env.screenHeight = 600;
}
+ enable_triple_buffer();
- for (pl = 0; pl < global->numPlayers; pl++)
- {
- int money = global->players[pl]->money;
- int trolley[THINGS];
- memset (trolley, 0, sizeof (int) * THINGS);
-
- //have computer players buy stuff
- if ( (int) global->players[pl]->type != HUMAN_PLAYER)
- {
- int pressed = 0;
- int moneyToSave = 0; // How much money will the player save?
- int numDmgWeaps = 0; // How many damage dealing weapons apart from small missiles do they have=
- int numPara = 0; // how many parachutes do we have?
- int numProj = 0; // How many slick/dimpled projectiles do we have?
-#ifdef DEBUG
- cout << "(" << global->players[pl]->getName() << ") Starting to buy:" << endl;
- if (global->players[pl]->defensive < -0.9) cout << "(True Offensive)" << endl;
- if ((global->players[pl]->defensive >=-0.9) && (global->players[pl]->defensive < -0.75)) cout << "(Very Offensive)" << endl;
- if ((global->players[pl]->defensive >=-0.75) && (global->players[pl]->defensive < -0.25)) cout << "(Offensive)" << endl;
- if ((global->players[pl]->defensive >=-0.25) && (global->players[pl]->defensive < 0.00)) cout << "(Slightly Offensive)" << endl;
- if (global->players[pl]->defensive == 0.0) cout << "(Neutral)" << endl;
- if ((global->players[pl]->defensive >0.0) && (global->players[pl]->defensive <= 0.25)) cout << "(Slightly Defensive)"<< endl;
- if ((global->players[pl]->defensive >0.25) && (global->players[pl]->defensive <= 0.75)) cout << "(Defensive)" << endl;
- if ((global->players[pl]->defensive >0.75) && (global->players[pl]->defensive <= 0.9)) cout << "(Very Defensive)" << endl;
- if (global->players[pl]->defensive > 0.9) cout << "(True Defensive)" << endl;
- cout << "Inventory:" << endl << "----------" << endl;
- for (int i = 1; i < WEAPONS; i++)
- {
- if (global->players[pl]->nm[i])
- cout << global->players[pl]->nm[i] << " x " << weapon[i].name << endl;
- }
- cout << " - - - - - - -" << endl;
- for (int i = 1; i < ITEMS; i++)
- {
- if (global->players[pl]->ni[i])
- cout << global->players[pl]->ni[i] << " x " << item[i].name << endl;
- }
- cout << "----------" << endl;
-#endif // DEBUG
- // moneysaving will be made possible when:
- // 1. It's not the first three rounds
- // 2. It's not the last 5 rounds
- // and, dynamically:
- // 3. We have at least 10 parachutes or no gravity
- // 4. We have at least 5 damage dealing (not small missile) weapons
- // 5. We have a sum of 50 slick/dimpled projectiles
-
- if ( (global->currentround > 5)
- && ( ( (int) global->rounds - global->currentround) > 3))
- {
- moneyToSave = global->players[pl]->getMoneyToSave();
-#ifdef DEBUG
- cout << "Maximum Money to save: " << moneyToSave << " (I have: " << global->players[pl]->money << ")" << endl;
-#endif //DEBUG
- }
-#ifdef DEBUG
- else
- cout << "No money to save this round!!!" << endl;
-#endif // DEBUG
- // Check for a minimum of damage dealing weapons and parachutes, then buy until moneyToSave is reached
- buy_count = 0;
- do
- {
- numPara = global->players[pl]->ni[ITEM_PARACHUTE];
- numProj = global->players[pl]->ni[ITEM_SLICKP] + global->players[pl]->ni[ITEM_DIMPLEP];
- numDmgWeaps = 0;
-
- for (int i = 1; i < WEAPONS; i++)
- {
- // start from 1, as 0 is the small missile
- if (weapon[i].damage > 0)
- numDmgWeaps += global->players[pl]->nm[i];
- }
-
- if ( (global->players[pl]->money > moneyToSave)
- || ( (numPara < 10) && (env->landSlideType > LANDSLIDE_NONE))
- || (numDmgWeaps < 8)
- || (numProj < 50))
- {
- pressed = global->players[pl]->chooseItemToBuy (iMaxBoost);
- }
- else
- pressed = -1;
-
-#ifdef DEBUG
- if (pressed > -1)
- {
- cout << "I have bought: ";
- if (pressed < WEAPONS)
- cout << weapon[pressed].name;
- else
- cout << item[pressed - WEAPONS].name;
- cout << " (" << global->players[pl]->money << " bucks left)" << endl;
- }
- else
- cout << "Finished, with " << global->players[pl]->money << " Credits left!" << endl;
-#endif // DEBUG
- buy_count++;
- }
- while ( (pressed != -1) && (buy_count < 100) );
-
-#ifdef DEBUG
- cout << "============================================" << endl << endl;
-#endif //DEBUG
- continue; //go to next player
- } // end of AI player
-
- done = 0;
- updatename = scroll = 1;
- pressed = -1;
-
- draw_buystuff (global, env, global->players[pl]);
-
- while (!done)
- {
-
- LINUX_SLEEP;
- my_cclock = CLOCK_MAX;
- while (my_cclock > 0)
- {
- LINUX_SLEEP;
- if (global->close_button_pressed)
- {
- clear_keybuf();
- return false;
- }
- my_cclock--;
- if (!lb && mouse_b & 1 && mouse_x >= global->halfWidth - 100 && mouse_x < global->halfWidth + 100 && mouse_y >= global->screenHeight - 50 && mouse_y < global->screenHeight - 25)
- done = 1;
- if (!lb && mouse_b & 1)
- env->mouseclock = 0;
- lb = (mouse_b & 1) ? 1 : 0;
-
- my_key = 0;
- //Keyboard control
- if ( keypressed() )
- {
- my_key = readkey();
- my_key = my_key >> 8;
- }
-
- if ( my_key == KEY_UP || my_key == KEY_W) // && (scroll > 1)
- // && (!env->mouseclock))
- {
- if (item_index > 1)
- item_index--;
- if (scroll > 1)
- scroll--;
- }
- if (my_key == KEY_PGUP || my_key == KEY_R) // && (scroll > 1)
- // && (!env->mouseclock))
- {
- item_index -= btps / 2;
- if (item_index < 1)
- item_index = 1;
- scroll -= btps / 2;
- if (scroll < 1)
- scroll = 1;
- }
-
- if (my_key == KEY_DOWN || my_key == KEY_S)
- // && (scroll <= env->numAvailable - btps)
- // && (!env->mouseclock))
- {
- if (item_index < (env->numAvailable - 1))
- item_index++;
- if (scroll <= env->numAvailable - btps)
- scroll++;
- }
- if ((my_key == KEY_PGDN || my_key == KEY_F)
- && (scroll <= env->numAvailable - btps + 1) )
- // && (!env->mouseclock))
- {
- item_index += btps / 2;
- if (item_index > env->numAvailable - btps)
- item_index = env->numAvailable - btps;
- scroll += btps / 2;
- if (scroll > env->numAvailable - btps + 1)
- scroll = env->numAvailable - btps + 1;
- }
-
- // make sure the selected item is on the visible screen
- if (item_index < scroll)
- item_index = scroll;
- else if ( item_index > (scroll + btps + 3) )
- item_index = scroll + btps + 3;
-
- // buy or sell an item
- if (my_key == KEY_RIGHT || my_key == KEY_D)
- {
- pressed = env->availableItems[item_index];
- if (pressed >= WEAPONS)
- {
- cost = item[pressed - WEAPONS].cost;
- amt = item[pressed - WEAPONS].amt;
- inInv = global->players[pl]->ni[pressed - WEAPONS];
- }
- else
- {
- cost = weapon[pressed].cost;
- amt = weapon[pressed].amt;
- inInv = global->players[pl]->nm[pressed];
- }
- if ((money >= cost)
- && ( (inInv + trolley[pressed]) < (999 - amt)))
- {
- if (trolley[pressed] <= -amt)
- {
- if (global->sellpercent > 0.01)
- {
- money -= (int)(cost * global->sellpercent);
- trolley[pressed] += amt;
- updatename = 1;
- }
- }
- else
- {
- money -= cost;
- trolley[pressed] += amt;
- updatename = 1;
- if (inInv + trolley[pressed] > 999)
- trolley[pressed] = 999;
- }
- }
- pressed = -1;
- } // end of buying
-
- if ( my_key == KEY_LEFT || my_key == KEY_A)
- {
- pressed = env->availableItems[item_index];
- if (pressed >= WEAPONS)
- {
- cost = item[pressed - WEAPONS].cost;
- amt = item[pressed - WEAPONS].amt;
- inInv = global->players[pl]->ni[pressed - WEAPONS];
- }
- else
- {
- cost = weapon[pressed].cost;
- amt = weapon[pressed].amt;
- inInv = global->players[pl]->nm[pressed];
- }
- if (inInv + trolley[pressed] >= amt)
- {
- if (trolley[pressed] >= amt)
- {
- money += cost;
- trolley[pressed] -= amt;
- updatename = 1;
- }
- else
- {
- if (global->sellpercent > 0.01)
- {
- money += (int)(cost * global->sellpercent);
- trolley[pressed] -= amt;
- updatename = 1;
- }
- }
- }
- pressed = -1;
- } // end of selling
-
-
- // check for adding or removing rounds
- if ( (my_key == KEY_PLUS_PAD) || (my_key == KEY_EQUALS) )
- {
- if ( (global->rounds < 999) && (! env->mouseclock) )
- {
- global->rounds++;
- global->currentround++;
- updatename = 1;
- }
- }
- if ( (my_key == KEY_MINUS_PAD) || (my_key == KEY_MINUS) )
- {
- if ( (global->rounds > 1) && (global->currentround > 1)
- && (! env->mouseclock) )
- {
- global->rounds--;
- global->currentround--;
- updatename = 1;
- }
- }
-
- // check for saving the game
- if ( my_key == KEY_F10 )
- {
- int status;
- if (! performed_save_game)
- {
- status = Save_Game(global, env);
- performed_save_game = TRUE;
- if (status)
- snprintf(description, 64, "%s \"%s\".", global->ingame->complete_text[17], global->game_name);
- else
- strcpy(description, global->ingame->complete_text[41]);
- draw_text_in_box (env, &area, description);
- }
- }
-
- if ( my_key == KEY_ENTER)
- done = TRUE;
-
- // Mouse control
-
- // check mouse wheel
- mouse_wheel_current = mouse_z;
- if (mouse_wheel_current < mouse_wheel_previous)
- {
- scroll++;
- if (scroll > env->numAvailable - btps + 1)
- scroll = env->numAvailable - btps + 1;
- if (scroll > item_index)
- item_index = scroll;
- }
- else if (mouse_wheel_current > mouse_wheel_previous)
- {
- scroll--;
- if (scroll < 1)
- scroll = 1;
- if (item_index > scroll + btps)
- item_index = scroll + btps - 3;
- }
- mouse_wheel_previous = mouse_wheel_current;
-
-
- if (mouse_x >= global->screenWidth - STUFF_BAR_WIDTH && mouse_x < global->screenWidth)
- {
- int newlyOver;
- zz = 0;
- for (z = 1, zzz = scroll; z < btps; z++, zzz++)
- {
- if (mouse_y >= z * STUFF_BAR_HEIGHT && mouse_y < (z * STUFF_BAR_HEIGHT) + 30)
- {
- zz = 1;
- break;
- }
- }
- if (zz)
- newlyOver = env->availableItems[zzz];
- else
- newlyOver = -1;
- if (hoverOver != newlyOver)
- {
- // char description[1024];
- // BOX area = {20, 60, 300, 400};
-
- if (newlyOver > -1)
- {
- if (newlyOver < WEAPONS)
- {
- WEAPON *weap = &weapon[newlyOver];
- sprintf (description, "Radius: %d\nYield: %ld\n\n%s",
- weap->radius, calcTotalPotentialDamage (newlyOver) * weap->spread, weap->description);
- }
- else
- {
- int itemNum = newlyOver - WEAPONS;
- ITEM *it = &item[itemNum];
- if (itemNum >= ITEM_VENGEANCE && itemNum <= ITEM_FATAL_FURY)
- {
- sprintf (description, "Potential Damage: %ld\n\n%s",
- calcTotalPotentialDamage ((int)it->vals[0]) * (int)it->vals[1],
- it->description);
- }
- else
- {
- sprintf (description, "%s", it->description);
- }
- }
- }
- else
- {
- description[0] = 0;
- }
- hoverOver = newlyOver;
-
- draw_text_in_box (env, &area, description);
- }
- }
- if (mouse_b & 1 && !env->mouseclock)
- {
- int scrollArrowPos = global->screenWidth - STUFF_BAR_WIDTH - 30;
- if (mouse_x >= scrollArrowPos && mouse_x < scrollArrowPos + 24)
- {
- if ((mouse_y >= global->halfHeight - 50 && mouse_y < global->halfHeight - 25) && (scroll > 1))
- {
- scroll -= btps / 2;
- if (scroll < 1)
- scroll = 1;
- }
- if ((mouse_y >= global->halfHeight - 24 && mouse_y < global->halfHeight) && (scroll > 1))
- {
- scroll--;
- }
- if ((mouse_y >= global->halfHeight + 1 && mouse_y < global->halfHeight + 25) && (scroll <= env->numAvailable - btps))
- {
- scroll++;
- }
- if ((mouse_y >= global->halfHeight + 25 && mouse_y < global->halfHeight + 50) && (scroll <= env->numAvailable - btps + 1))
- {
- scroll += btps / 2;
- if (scroll > env->numAvailable - btps + 1)
- scroll = env->numAvailable - btps + 1;
- }
- }
- if (item_index < scroll)
- item_index = scroll;
- else if ( item_index > (scroll + btps) )
- item_index = scroll + btps - 3;
- }
- if (mouse_b & 1 || mouse_b & 2)
- {
- int itemButtonClicked = 0;
- for (int buttonCount = 1, currItem = scroll; buttonCount < btps; buttonCount++, currItem++)
- {
- if (mouse_x >= global->screenWidth - STUFF_BAR_WIDTH && mouse_x < global->screenWidth && mouse_y >= buttonCount * STUFF_BAR_HEIGHT && mouse_y < (buttonCount * STUFF_BAR_HEIGHT) + 30)
- {
- itemButtonClicked = 1;
- // Remember which button was pressed
- pressed = env->availableItems[currItem];
- }
- }
- if (!itemButtonClicked)
- {
- pressed = -1;
- }
- }
- if (pressed > -1 && !(mouse_b & 1 || mouse_b & 2))
- {
- // Cost, amount and in-inventory amount
- // of pressed item
- // int cost,amt,inInv;
- bool control_key = false;
-
- if ( ( key[KEY_LCONTROL] ) || ( key[KEY_RCONTROL] ) )
- control_key = true;
-
- if (pressed > WEAPONS - 1)
- {
- cost = item[pressed - WEAPONS].cost;
- amt = item[pressed - WEAPONS].amt;
- inInv = global->players[pl]->ni[pressed - WEAPONS];
- }
- else
- {
- cost = weapon[pressed].cost;
- amt = weapon[pressed].amt;
- inInv = global->players[pl]->nm[pressed];
- }
-
- if (control_key)
- {
- cost *= 10;
- amt *= 10;
- }
-
- if (lastMouse_b & 2)
- {
- if (inInv + trolley[pressed] >= amt)
- {
- if (trolley[pressed] >= amt)
- {
- money += cost;
- trolley[pressed] -= amt;
- updatename = 1;
- }
- else
- {
- if (global->sellpercent > 0.01)
- {
- money += (int)(cost * global->sellpercent);
- trolley[pressed] -= amt;
- updatename = 1;
- }
- }
- }
- }
- else
- {
- if ((money >= cost)
- && ( (inInv + trolley[pressed]) < (999 - amt)))
- {
- if (trolley[pressed] <= -amt)
- {
- if (global->sellpercent > 0.01)
- {
- money -= (int)(cost * global->sellpercent);
- trolley[pressed] += amt;
- updatename = 1;
- }
- }
- else
- {
- money -= cost;
- trolley[pressed] += amt;
- updatename = 1;
- if (inInv + trolley[pressed] > 999)
- trolley[pressed] = 999;
- }
- }
- }
- pressed = -1;
- }
- env->mouseclock++;
- if (env->mouseclock > 5)
- env->mouseclock = 0;
- lastMouse_b = mouse_b;
- }
- if (! global->os_mouse) show_mouse (NULL);
- if (updatename)
- {
- updatename = 0;
- // env->make_update (global->halfWidth - 315, 0, 400, 30);
- env->make_update (global->halfWidth - 315, 0, global->screenWidth - 1, 30);
- draw_sprite (env->db, (BITMAP *) global->gfxData.stuff_bar[0], global->halfWidth - 200, 0);
- textprintf_ex (env->db, font, global->halfWidth - 190, 5, BLACK, -1, "%s %d: %s", global->ingame->complete_text[10], pl + 1, global->players[pl]->getName ());
- // textprintf_ex (env->db, font, global->halfWidth - 190, 17, BLACK, -1, "Money: $%d", money);
- textprintf_ex(env->db, font, global->halfWidth - 190, 17, BLACK, -1, "%s: $%s", global->ingame->complete_text[11], Add_Comma(money));
- sprintf (buf, "%s: %d/%d", global->ingame->complete_text[12], (int)(global->rounds - global->currentround) + 1, (int)global->rounds);
- textout_ex (env->db, font, buf, global->halfWidth + 170 - text_length (font, buf), 5, BLACK, -1);
- sprintf (buf, "%s: %d", global->ingame->complete_text[13], global->players[pl]->score);
- textout_ex (env->db, font, buf, global->halfWidth + 155 - text_length (font, buf), 17, BLACK, -1);
- }
-
- draw_weapon_list(global, env, global->players[pl], trolley, scroll, (pressed < 0) ? env->availableItems[item_index] : pressed );
- env->make_update (mouse_x, mouse_y, ((BITMAP *) (global->misc[0]))->w, ((BITMAP *) (global->misc[0]))->h);
- env->make_update (lx, ly, ((BITMAP *) (global->misc[0]))->w, ((BITMAP *) (global->misc[0]))->h);
- lx = mouse_x;
- ly = mouse_y;
- if (! global->os_mouse) show_mouse (env->db);
- if (fi)
- {
- change (global, env->db);
- fi = 0;
- }
+ env.halfWidth = env.screenWidth / 2;
+ env.halfHeight = env.screenHeight / 2;
- else
- env->do_updates ();
- }
- for (int tItem = 0; tItem < WEAPONS; tItem++)
- global->players[pl]->nm[tItem] += trolley[tItem];
- for (int tItem = WEAPONS; tItem < THINGS; tItem++)
- global->players[pl]->ni[tItem - WEAPONS] += trolley[tItem];
- global->players[pl]->money = money;
- }
- // clear_keybuf ();
-
- for (z = 0; z < global->numPlayers; z++)
- {
- int iMoney = global->players[z]->money;
- int iInterest = 0;
- float fIntPerc= 0.0;
- int iLevel = 0;
- int iInterSum = 0; // The summed up interest
-#ifdef DEBUG
- cout << endl << "======================================================" << endl;
- printf( (char *)"%2d.: %s enters the bank to get interest:\n", (z+1), global->players[z]->getName());
- printf( (char *)" Starting Account: %10d\n", global->players[z]->money);
- cout << "------------------------------------------------------" << endl;
+#ifdef ATANKS_IS_MSVC
+# if defined(ATANKS_DEBUG)
+ SetConsoleCtrlHandler(ctrlHandler, TRUE);
#endif // DEBUG
- while (iMoney && (iLevel < 5))
- {
- // Enter next level
- iLevel++;
- fIntPerc = (global->interest - 1.0) / iLevel;
- iInterest = (int)((float)iMoney * fIntPerc);
- // The limit is only applicable on the first four levels, in the fifth level interest is fully applied!
- if ((iInterest > MAX_INTEREST_AMOUNT) && (iLevel < 5))
- iInterest = MAX_INTEREST_AMOUNT;
- // Now sum the interest up and substract the counted money!
- iInterSum += iInterest;
- iMoney -= (int)((float)iInterest / fIntPerc);
-#ifdef DEBUG
- printf( (char *)" Level %1d: %8d credits are rated,\n", iLevel, (int)(iInterest / fIntPerc));
- printf( (char *)" Interest: %8d credits. (%5.2f%%)\n", iInterest, (fIntPerc * 100));
-#endif // DEBUG
- // To get rid of (possible) rounding errors, add a security check:
- if ((iMoney < (4 * iLevel)) || (iInterest < 1))
- iMoney = 0; // With less there won't be any more interest anyway!
-#ifdef DEBUG
- printf( (char *)" Unrated : %8d credits left.\n", iMoney);
-#endif // DEBUG
- }
- // Now giv'em their money:
-#ifdef DEBUG
- printf( (char *)" Sum: %8d credits.\n", iInterSum);
- cout << "------------------------------------------------------" << endl;
-#endif // DEBUG
- global->players[z]->money += iInterSum;
-#ifdef DEBUG
- printf( (char *)" Final Account : %10d\n", global->players[z]->money);
- cout << "======================================================" << endl;
-#endif // DEBUG
- }
- return true;
-}
-
+ if ( env.full_screen == FULL_SCREEN_TRUE )
+ set_display_switch_mode(SWITCH_BACKAMNESIA);
+ else
+ set_display_switch_mode(SWITCH_BACKGROUND);
+#endif // ATANKS_IS_WINDOWS
+
+ if (install_keyboard () < 0) {
+ perror ( "install_keyboard failed");
+ exit (1);
+ }
+
+ if (install_mouse () < 0)
+ perror ( "install_mouse failed");
+
+ // check to see if we want sound
+ if (env.sound_enabled) {
+ int32_t sound_type = DIGI_AUTODETECT;
+
+# ifdef ATANKS_IS_LINUX
+ switch ( env.sound_driver ) {
+ case SD_OSS: sound_type = DIGI_OSS; break;
+ case SD_ESD: sound_type = DIGI_ESD; break;
+ case SD_ARTS: sound_type = DIGI_ARTS; break;
+ case SD_ALSA: sound_type = DIGI_ALSA; break;
+ case SD_JACK: sound_type = DIGI_JACK; break;
+ default: sound_type = DIGI_AUTODETECT; break;
+ }
+# ifdef UBUNTU
+ if (DIGI_AUTODETECT == sound_type)
+ sound_type = DIGI_OSS;
+# endif // UBUNTU
+# endif // ATANKS_IS_LINUX
+
+ int32_t channels = detect_digi_driver(sound_type);
+
+ if (!channels && (DIGI_AUTODETECT != sound_type)) {
+ sound_type = DIGI_AUTODETECT;
+ channels = detect_digi_driver(sound_type);
+ }
+
+ if (channels) {
+ env.voices = channels > 64 ? 32
+ : channels > 32 ? 16
+ : channels > 16 ? 8
+ : channels;
+
+ int32_t snd_installed = -1;
+
+ while ( (env.voices > 1) && (0 > snd_installed)) {
+ DEBUG_LOG("Sound Init", "Reserving %d / %d voices", env.voices, channels)
+ reserve_voices(env.voices, 0);
+ snd_installed = install_sound(sound_type, DIGI_NONE, nullptr);
+
+ // Instead of failing directly, reduces voices first
+ if (-1 == snd_installed) {
+ DEBUG_LOG("Sound Init", "Too many voices, halving...", 0)
+ env.voices /= 2;
+ }
+ }
+
+ // Now display an error message if it was not possible to succeed
+ if (0 > snd_installed) {
+ fprintf (stderr, "install_sound: failed initialising sound\n");
+ fprintf (stderr, "Please try selecting a different Sound Driver"
+ " from the Options menu.\n");
+ } else {
+ int32_t set_voices = get_mixer_voices();
+ DEBUG_LOG("Sound Init", "Mixer has %d voices", set_voices)
+
+ if (set_voices < env.voices)
+ env.voices = set_voices;
+
+ // Set the mixer quality:
+ int32_t mixq = get_mixer_quality();
+ if (mixq < 2) {
+ DEBUG_LOG("Sound Init", "Raising mixer quality from %d to 2",
+ mixq)
+ set_mixer_quality(2);
+ }
+
+ }
+ } else
+ fprintf (stderr, "detect_digi_driver detected no sound device\n");
+ } // End of sound initialization
+
+ srand (time (0));
+
+ // Colour initialization, must be done here when allegro is initialized.
+ BLACK = makecol (0x00, 0x00, 0x00);
+ BLUE = makecol (0x00, 0x00, 0xff);
+ DARK_GREEN = makecol (0x00, 0x50, 0x00);
+ DARK_GREY = makecol (0x40, 0x40, 0x40);
+ DARK_RED = makecol (0x80, 0x00, 0x00);
+ GREY = makecol (0x80, 0x80, 0x80);
+ GREEN = makecol (0x00, 0xff, 0x00);
+ LIGHT_GREEN = makecol (0x80, 0xff, 0x80);
+ LIME_GREEN = makecol (0xc8, 0xff, 0xc8);
+ ORANGE = makecol (0xfa, 0x96, 0x00);
+ PINK = makecol (0xff, 0x00, 0xff);
+ PURPLE = makecol (0xc8, 0x00, 0xc8);
+ RED = makecol (0xff, 0x00, 0x00);
+ SILVER = makecol (0xc0, 0xc0, 0xc0);
+ TURQUOISE = makecol (0x96, 0xc8, 0xff);
+ WHITE = makecol (0xff, 0xff, 0xff);
+ YELLOW = makecol (0xff, 0xff, 0x00);
+
+ // Start preparing environment
+ env.first_init(); // *MUST* be done before GLOBALDATA or
+ global.first_init(); // max_screen_updates is not correct!
+
+ // Prepare remaining environment
+ clear_to_color (global.canvas, BLACK);
+ env.loadBitmaps();
+ title();
+ env.loadSounds();
+
+ init_mouse_cursor();
+
+ env.loadFonts();
+
+ env.window.x = 0;
+ env.window.y = 0;
+ env.window.w = 0;
+ env.window.h = 0;
+
+ for (int32_t z = 0; z < env.max_screen_updates; z++) {
+ global.updates[z].x = 0;
+ global.updates[z].y = 0;
+ global.updates[z].w = 0;
+ global.updates[z].h = 0;
+ }
-
-#ifdef GETV_IS_EVER_USED
-double getv (int color)
-{
- float h, s, v;
- int r, g, b;
-
- r = getr (color);
- g = getg (color);
- b = getb (color);
- rgb_to_hsv (r, g, b, &h, &s, &v);
-
- return (v);
}
-#endif //GETV_IS_EVER_USED
-
-void set_level_settings (GLOBALDATA *global, ENVIRONMENT *env)
+static void initialisePlayers()
{
- int taken[MAXPLAYERS];
- BITMAP *sky_gradient_strip, *land_gradient_strip;
- int chosen = 0, chosenCount = 0, peak_height = 0;
- int z, zz;
- int objCount;
- TANK *ltank;
- int xoffset;
-
- // srand (time (NULL));
-
- if (! global->os_mouse) show_mouse (NULL);
- draw_sprite (screen, (BITMAP *) global->misc[1], global->halfWidth - 120, global->halfHeight + 115);
- textout_centre_ex (screen, font, global->ingame->complete_text[42], global->halfWidth, global->halfHeight + 120, WHITE, -1);
-
- // Choose appropriate gradients for sky and land
- while ((chosenCount < 60) && !chosen)
- {
- global->lock_curland();
- global->curland = rand () % LANDS;
- global->unlock_curland();
- if (global->colour_theme == COLOUR_THEME_CRISPY)
- {
- global->lock_curland();
- global->curland += LANDS;
- global->unlock_curland();
- }
- global->lock_curland();
- if (!global->gfxData.land_gradient_strips[global->curland])
- global->gfxData.land_gradient_strips[global->curland] = create_gradient_strip (land_gradients[global->curland], (global->screenHeight - MENUHEIGHT));
- land_gradient_strip = global->gfxData.land_gradient_strips[global->curland];
- global->unlock_curland();
-
- global->cursky = rand () % SKIES;
- if (global->colour_theme == COLOUR_THEME_CRISPY)
- global->cursky += SKIES;
- if (!global->gfxData.sky_gradient_strips[global->cursky])
- global->gfxData.sky_gradient_strips[global->cursky] = create_gradient_strip (sky_gradients[global->cursky], (global->screenHeight - MENUHEIGHT));
- sky_gradient_strip = global->gfxData.sky_gradient_strips[global->cursky];
-
- chosen = 1;
- for (z = 0; z < global->screenWidth; z++)
- if (peak_height < env->height[z])
- peak_height = (int)env->height[z];
- for (z = 0; z <= peak_height; z++)
- {
- int skyi, landi;
- double distance;
- skyi = getpixel (sky_gradient_strip, 0, (global->screenHeight - MENUHEIGHT - z));
- landi = getpixel (land_gradient_strip, 0, z);
-
- distance = colorDistance (skyi, landi);
- if (distance < 30)
- chosen = 0;
- }
- chosenCount++;
- }
-
- if (! global->os_mouse) show_mouse (NULL);
- draw_sprite (screen, (BITMAP *) global->misc[1], global->halfWidth - 120, global->halfHeight + 155);
- textout_centre_ex (screen, font, global->ingame->complete_text[43], global->halfWidth, global->halfHeight + 160, WHITE, -1);
- // It looks like we do not use this anymore xoffset = rand ();
- //generate_sky (global, env, xoffset, 0, global->screenWidth, global->screenHeight);
-
- if (env->sky)
- {
- destroy_bitmap(env->sky);
- env->sky = NULL;
- }
- // see if we want a custom background
- if ( (env->custom_background) && (env->bitmap_filenames) )
- {
- // if ( env->sky) destroy_bitmap(env->sky);
- env->sky = load_bitmap( env->bitmap_filenames[ rand() % env->number_of_bitmaps ], NULL);
- }
-
- // if we do not have a custom background (or do not want one) create a new background
- if ( (! env->custom_background) || (! env->sky) )
- {
- // if ( env->sky ) destroy_bitmap(env->sky);
-#ifdef THREADS
- // On Linux we will have a thread creating a sky for us in the background
- // to avoid wait times. If there is not a pre-created sky waiting for us
- // then fall back to generating one the regular way.
- if (env->get_waiting_sky())
- {
- env->sky = env->get_waiting_sky();
- env->lock(env->waiting_sky_lock);
- env->waiting_sky = NULL;
- env->unlock(env->waiting_sky_lock);
- }
- else
- {
-#endif
- env->sky = create_bitmap( global->screenWidth, global->screenHeight - MENUHEIGHT);
- generate_sky (global, env->sky, sky_gradients[global->cursky],
- (global->ditherGradients ? GENSKY_DITHERGRAD : 0 ) |
- (global->detailedSky ? GENSKY_DETAILED : 0 ) );
-#ifdef THREADS
- }
-#endif
- }
-
- if (! global->os_mouse) show_mouse (NULL);
- draw_sprite (screen, (BITMAP *) global->misc[1], global->halfWidth - 120, global->halfHeight + 195);
- textout_centre_ex (screen, font, global->ingame->complete_text[44], global->halfWidth, global->halfHeight + 200, WHITE, -1);
-
- #ifdef THREADS
- // we have threads, so check for pre-main terrain
- if (env->get_waiting_terrain())
- {
- // we have one waiting, so destroy the current one
- if (env->terrain) destroy_bitmap(env->terrain);
- env->terrain = env->get_waiting_terrain();
- env->lock(env->waiting_terrain_lock);
- env->waiting_terrain = NULL; // set this so a new one will be made
- env->unlock(env->waiting_terrain_lock);
- }
- else // one is not waiting, so we need to create one now
- {
- #endif
-
- clear_to_color (env->terrain, PINK);
-
- xoffset = rand ();
- generate_land (global, env, env->terrain, xoffset, 0, global->screenHeight);
- #ifdef THREADS
- } // end of else
- #endif
-
- for (z = 0; z < global->numTanks; z++)
- {
- taken[z] = 0;
- }
- for (objCount = 0; (ltank = (TANK*)env->getNextOfClass (TANK_CLASS, &objCount)) && ltank; objCount++)
- {
- for (zz = rand () % global->numTanks; taken[zz]; zz = rand () % global->numTanks) { }
- taken[zz] = objCount + 1;
- ltank->x = (zz + 1) * (global->screenWidth / (global->numTanks + 1));
- ltank->y = (global->screenHeight - (int)env->height[(int) ltank->x]) - (TANKHEIGHT - TANKSAG);
- ltank->newRound ();
- }
- for (z = 0; z < MAXPLAYERS; z++)
- env->order[z] = NULL;
- global->maxNumTanks = global->numTanks;
- for (objCount = 0; (ltank = (TANK*)env->getNextOfClass (TANK_CLASS, &objCount)) && ltank; objCount++)
- {
- for (z = rand () % global->numTanks; env->order[z]; z = rand () % global->numTanks) { }
- env->order[z] = ltank;
- }
- // if (global->turntype != TURN_RANDOM) {
- if ( (global->turntype != TURN_RANDOM) &&
- (global->turntype != TURN_SIMUL))
- {
- for (int index = 0; index < global->maxNumTanks - 1; index++)
- {
- int swap = FALSE;
- if (global->turntype == TURN_HIGH)
- {
- if (env->order[index]->player->score <
- env->order[index + 1]->player->score)
- {
- swap = TRUE;
- }
- }
- else if (global->turntype == TURN_LOW)
- {
- if (env->order[index]->player->score >
- env->order[index + 1]->player->score)
- {
- swap = TRUE;
- }
- }
- if (swap)
- {
- TANK *tempTank = env->order[index];
- env->order[index] =
- env->order[index + 1];
- env->order[index + 1] = tempTank;
- index = -1;
- }
- }
- }
+ for (int32_t z = 0; z < env.numGamePlayers; ++z) {
+ env.players[z]->money = env.startmoney;
+ env.players[z]->score = 0;
+ if ( (HUMAN_PLAYER != env.players[z]->type)
+ && (PERPLAY_PREF == env.players[z]->preftype))
+ env.players[z]->generatePreferences();
+ env.players[z]->initialise(false);
+ env.players[z]->type_saved = env.players[z]->type;
+ }
}
-char *do_winner (GLOBALDATA *global, ENVIRONMENT *env)
+static bool loadConfig()
{
- int maxscore = -1;
- int winindex = -1;
- int i, index, *order;
- bool multiwinner = false;
- int fonthgt = text_height(font)+10;
- int jedi_index = -1, sith_index = -1, neutral_index = -1;
- // char *my_quote;
- char *return_string = NULL;
-
- return_string = (char *) calloc(256, sizeof(char));
- if (! return_string)
- return NULL;
-
- //find the maxscore and print out winner
- for (i=0;i<global->numPlayers;i++)
- {
- if (global->players[i]->score == maxscore)
- {
- multiwinner=true;
- if (global->players[i]->team == TEAM_NEUTRAL)
- neutral_index = i;
- }
-
- else if (global->players[i]->score > maxscore)
- {
- maxscore = global->players[i]->score;
- winindex=i;
- multiwinner=false;
- if (global->players[i]->team == TEAM_NEUTRAL)
- neutral_index = i;
- }
- if (global->players[i]->team == TEAM_JEDI)
- jedi_index = i;
- else if (global->players[i]->team == TEAM_SITH)
- sith_index = i;
- }
-
- //stop mouse during drawing
- if (! global->os_mouse) show_mouse (NULL);
-
- //draw background and winner bitmap
- draw_circlesBG (global, env->db, 0, 0, global->screenWidth, global->screenHeight, false);
- draw_sprite (env->db, (BITMAP *) global->misc[9], global->halfWidth - 150, global->halfHeight - 150);
-
- //draw winner names and info about all players
- int boxtop = global->halfHeight-40;
- int boxleft = global->halfWidth-200;
- int boxright = global->halfWidth+280;
- int boxbottom = boxtop +4+(fonthgt*2)+(fonthgt*global->numPlayers);
-
- rectfill (env->db, boxleft, boxtop, boxright, boxbottom, BLACK);
- rect (env->db, boxleft, boxtop, boxright, boxbottom, WHITE);
- if (multiwinner)
- {
- // check for team win
- if ( global->players[winindex]->team == TEAM_JEDI )
- {
- if ( (sith_index >= 0) && ( (global->players[sith_index]->score == global->players[winindex]->score)) )
- snprintf(return_string, 256, "%s", global->ingame->complete_text[48]);
- else if ( (neutral_index >= 0) && ( (global->players[neutral_index]->score == global->players[winindex]->score) ) )
- snprintf(return_string, 256, "%s", global->ingame->complete_text[48]);
- else
- snprintf(return_string, 256, "%s", global->ingame->complete_text[45]);
- }
- else if ( global->players[winindex]->team == TEAM_SITH )
- {
- if ((jedi_index >= 0) && ((global->players[jedi_index]->score == global->players[winindex]->score)))
- snprintf(return_string, 256, "%s", global->ingame->complete_text[48]);
- else if ( (neutral_index >= 0) && ( (global->players[neutral_index]->score == global->players[winindex]->score) ) )
- snprintf(return_string, 256, "%s", global->ingame->complete_text[48]);
- else
- snprintf(return_string, 256, "%s", global->ingame->complete_text[46]);
- }
- else
- snprintf(return_string, 256, "%s", global->ingame->complete_text[48]);
- }
- else
- snprintf(return_string, 256, "%s: %s", global->ingame->complete_text[47],
- global->players[winindex]->getName() );
-
- textprintf_centre_ex(env->db, font, global->halfWidth, boxtop + 4,
- global->players[winindex]->color, -1,
- "%s", return_string);
-
- textout_centre_ex (env->db, font, global->ingame->complete_text[49], global->halfWidth, boxtop+4+fonthgt, WHITE, -1);
- order = Sort_Scores(global);
- for (index = 0; index < global->numPlayers; index++)
- {
- int i = order[index];
- int textypos = (index * 10) + boxtop+4+(fonthgt*2);
- int money;
-
- textprintf_ex (env->db, font, boxleft+10, textypos , global->players[i]->color, -1, "%s:", global->players[i]->getName ());
-
- money = 0;
- for (int weapNum = 0; weapNum < WEAPONS; weapNum++)
- {
- int individValue;
- if (weapon[weapNum].amt)
- individValue = weapon[weapNum].cost / weapon[weapNum].amt;
- else
- individValue = 0;
- money += (int)(individValue * global->players[i]->nm[weapNum]);
- }
- for (int itemNum = 0; itemNum < ITEMS; itemNum++)
- {
- int individValue;
- if (item[itemNum].amt)
- individValue = item[itemNum].cost / item[itemNum].amt;
- else
- individValue = 0;
- money += (int)(individValue * global->players[i]->ni[itemNum]);
- }
- textprintf_ex (env->db, font, boxleft+190, textypos, WHITE, -1, "%3d $%s %10d :%2d", global->players[i]->score, Add_Comma(money), global->players[i]->kills, global->players[i]->killed);
- }
+ bool result = false;
- /*
- my_quote = global->war_quotes->Get_Random_Line();
- if (my_quote)
- {
- char *little_string;
- int start_index = 0, to_index = 0;
- int total_length = strlen(my_quote);
- int quote_count = 1;
-
- little_string = (char *) calloc( total_length + 1, sizeof(char));
- if (little_string)
- {
- do
- {
- memset(little_string, '\0', total_length + 1);
- while ((( my_quote[start_index] != ' ' ) || (to_index < 50) ) && (start_index < total_length) )
- {
- little_string[to_index] = my_quote[start_index];
- to_index++;
- start_index++;
- }
-
- textprintf_ex(env->db,font,boxleft, boxbottom + (10 * quote_count), WHITE, -1, "%s", little_string);
- to_index = 0;
- quote_count++;
- while ( (start_index < total_length) && (my_quote[start_index] == ' ') )
- start_index++;
- } while (start_index < total_length);
- free(little_string);
- }
- }
- //do fade and wait for user keypress
- change (global, env->db);
- readkey ();
- // for (i = 0; i < global->numPlayers; i++)
- // global->players[i]->type = global->players[i]->type_saved;
- */
- return return_string;
-}
+ if (load_config_file) {
+ snprintf(fullPath, PATH_MAX, "%s/atanks-config.txt", env.configDir);
+ FILE* old_config_file = fopen(fullPath, "r");
+ if (old_config_file) {
+ env.load_from_file(old_config_file);
-void do_quote(GLOBALDATA *global, ENVIRONMENT *env)
-{
- char *my_quote;
- int fonthgt = text_height(font)+10;
- int boxleft = global->halfWidth-200;
- int boxtop = global->halfHeight-40;
- int boxbottom = boxtop +4+(fonthgt*2)+(fonthgt*global->numPlayers);
-
- my_quote = global->war_quotes->Get_Random_Line();
- if (my_quote)
- {
- char *little_string;
- int start_index = 0, to_index = 0;
- int total_length = strlen(my_quote);
- int quote_count = 1;
-
- little_string = (char *) calloc( total_length + 1, sizeof(char));
- if (little_string)
- {
- do
- {
- memset(little_string, '\0', total_length + 1);
- while ((( my_quote[start_index] != ' ' ) || (to_index < 50) ) && (start_index < total_length) )
- {
- little_string[to_index] = my_quote[start_index];
- to_index++;
- start_index++;
- }
-
- textprintf_ex(env->db,font,boxleft, boxbottom + (10 * quote_count), WHITE, -1, "%s", little_string);
- to_index = 0;
- quote_count++;
- while ( (start_index < total_length) && (my_quote[start_index] == ' ') )
- start_index++;
- } while (start_index < total_length);
- free(little_string);
- }
- }
-
- //do fade and wait for user keypress
- change (global, env->db);
- readkey ();
- for (int i = 0; i < global->numPlayers; i++)
- global->players[i]->type = global->players[i]->type_saved;
+ // over-ride full screen setting with command line
+ if ( (full_screen == FULL_SCREEN_TRUE)
+ || (full_screen == FULL_SCREEN_FALSE) )
+ env.full_screen = full_screen;
-}
+ // Initialize after loading
+ init_game_settings();
+ // Load texts first ...
+ env.load_text_files();
+ // ...then the players last
+ result = loadPlayers(old_config_file);
+ fclose(old_config_file);
+ }
+ } // End of loading old config file
-//draws indicaation bar
-void graph_bar (ENVIRONMENT *env, int x, int y, long int col, int actual, int max)
-{
- rect (env->db, x, y, x + max + 2, y + 8, BLACK);
- rectfill (env->db, x + 1, y + 1, x + 1 + actual, y + 7, col);
+ return result;
}
-//draws indication bar - centred
-void graph_bar_center (ENVIRONMENT *env, int x, int y, long int col, int actual, int max)
+static bool loadPlayers(FILE* file)
{
- rect (env->db, x, y, x + max + 2, y + 8, BLACK);
- rectfill (env->db, x + 1 + max / 2, y + 1, x + 1 + actual + max / 2, y + 7, col);
+ int32_t max_pl = env.numPermanentPlayers;
+
+ if (env.allPlayers) {
+ for (int32_t i = 0; i < env.numPermanentPlayers; ++i) {
+ if (env.allPlayers[i])
+ delete env.allPlayers[i];
+ env.allPlayers[i] = nullptr;
+ }
+ free(env.allPlayers);
+ env.allPlayers = nullptr;
+ }
+
+ env.allPlayers = (PLAYER **)malloc(sizeof(PLAYER*) * max_pl);
+
+ if (!env.allPlayers) {
+ fprintf(stderr, "%s:%d : Failed to allocate memory for allPlayers\n",
+ __FILE__, __LINE__);
+ return false;
+ }
+
+ for (int32_t i = 0; i < max_pl; ++i)
+ env.allPlayers[i] = nullptr;
+
+ int32_t pl_count = 0;
+ bool status = true;
+
+ while (status) {
+ PLAYER* player_new = nullptr;
+ try {
+ player_new = new PLAYER();
+ } catch (std::exception &e) {
+ fprintf(stderr, "%s:%d : Failed to allocate memory for player\n",
+ __FILE__, __LINE__);
+ status = false;
+ }
+
+ if (status)
+ status = player_new->load_from_file(file);
+
+ if (status) {
+ player_new->index = pl_count;
+ env.allPlayers[pl_count++] = player_new;
+ if (pl_count == max_pl) {
+ max_pl += 5;
+ env.allPlayers = (PLAYER**)realloc(env.allPlayers,
+ sizeof(PLAYER *) * max_pl);
+ for (int32_t i = pl_count; i < max_pl; ++i)
+ env.allPlayers[i] = nullptr;
+ }
+ } else if (player_new)
+ delete player_new;
+ } // end of while status
+
+ env.numPermanentPlayers = pl_count;
+
+ return true;
}
-//Some global parameters
-int ord;
-void loadshields (ENVIRONMENT *env)
-{
- TANK *tank;
- int objCount;
-
- for (objCount = 0; (tank = (TANK*)env->getNextOfClass (TANK_CLASS, &objCount)) && tank; objCount++)
- tank->reactivate_shield ();
-}
-
-void change_wind_strength (ENVIRONMENT *env)
+static int32_t menu()
{
- if (env->windvariation == 0.0 || (int)env->windstrength == 0)
- {
- return;
- }
- else
- {
- env->wind = env->lastwind + (double)(rand () % (int)(env->windvariation * 100)) / 100 - (env->windvariation / 2);
- if (env->wind > (env->windstrength / 2))
- {
- env->wind = env->windstrength / 2;
- }
- else if (env->wind < (-env->windstrength / 2))
- {
- env->wind = -env->windstrength / 2;
- }
-
- env->lastwind = env->wind;
- }
-
- // make sure game clients have up to date wind data
- #ifdef NETWORK
- char buffer[64];
- sprintf(buffer, "WIND %f", env->wind);
- env->_global->Send_To_Clients(buffer);
- #endif
-}
-
-TANK *nextturn (GLOBALDATA *global, ENVIRONMENT *env, bool skippingComputerPlay)
-{
- TANK *tank = NULL;
- int ordCurrently = ord;
- static int do_wind = 0;
- static int next_wind = 0;
- int index = 0, lowest_index = 0, lowest_shots = INT_MAX;
-
- // check whether there currently *are* active tanks first
- if (global->numTanks)
- {
- // find first tank with lowest number of shots fired
- while ( index < global->maxNumTanks )
- {
- if ( env->order[index] ) // make sure tank exists
- {
- if ( env->order[index]->shots_fired < lowest_shots )
- {
- lowest_shots = env->order[index]->shots_fired;
- lowest_index = index;
- }
- }
- index++;
- } // end of looking for low index
-
-
- do
- {
- ord++;
- // coming around to the next turn
- if (ord >= global->maxNumTanks)
- {
- ord = 0;
- doLaunch(global, env);
- // launch before we change the wind
- next_wind = 1;
- }
-
- }
- while ((!env->order[ord]) && (ord != ordCurrently));
- tank = env->order[ord];
- global->currTank = tank;
-
- if ( tank->shots_fired > lowest_shots )
- {
- tank = env->order[lowest_index];
- global->currTank = tank;
- }
-
- // Wind is blowing :-)
- // change_wind_strength (env);
- if ( (global->turntype != TURN_SIMUL) || (do_wind) )
- {
- change_wind_strength(env);
- do_wind = next_wind = 0;
- }
- else
- {
- do_wind = next_wind;
- next_wind = 0;
- }
- }
-
- if (tank)
- {
- if (!skippingComputerPlay)
- {
- env->make_fullUpdate();
- env->do_updates();
- }
- tank->reactivate_shield ();
- clear_keybuf();
- if (global->max_fire_time)
- {
- tank->player->time_left_to_fire = global->max_fire_time;
- tank->player->skip_me = false;
- }
- }
-
- return tank;
+ int32_t result = SIG_OK;
+ int32_t shift_menu = env.halfHeight < 240 ? 240 - env.halfHeight : 0;
+ int32_t move_btn = env.button[0]->w / 2;
+ int32_t bn = env.language == EL_RUSSIAN ? MENUBUTTONS * 2 : 0;
+
+ BUTTON but_play(env.halfWidth - move_btn,
+ env.halfHeight - 235 + shift_menu,
+ env.button[bn], env.button[bn], env.button[bn + 1]);
+ bn += 2;
+ BUTTON but_help(env.halfWidth - move_btn,
+ env.halfHeight - 185 + shift_menu,
+ env.button[bn], env.button[bn], env.button[bn + 1]);
+ bn += 2;
+ BUTTON but_options(env.halfWidth - move_btn,
+ env.halfHeight - 135 + shift_menu,
+ env.button[bn], env.button[bn], env.button[bn + 1]);
+ bn += 2;
+ BUTTON but_players(env.halfWidth - move_btn,
+ env.halfHeight - 85 + shift_menu,
+ env.button[bn], env.button[bn], env.button[bn + 1]);
+ bn += 2;
+ BUTTON but_credits(env.halfWidth - move_btn,
+ env.halfHeight - 35 + shift_menu,
+ env.button[bn], env.button[bn], env.button[bn + 1]);
+ bn += 2;
+ BUTTON but_quit(env.halfWidth - move_btn,
+ env.halfHeight + 65 + shift_menu,
+ env.button[bn], env.button[bn], env.button[bn + 1]);
+ bn += 2;
+ BUTTON but_network(env.halfWidth - move_btn,
+ env.halfHeight + 15 + shift_menu,
+ env.button[bn], env.button[bn], env.button[bn + 1]);
+
+ BUTTON *button[MENUBUTTONS] = { &but_play, &but_help, &but_options,
+ &but_players, &but_credits, &but_network,
+ &but_quit };
+
+ // Initialization of the menu
+ global.stopwindow = true;
+ fi = 1;
+ lx = 0;
+ ly = 0;
+ k = 0;
+ K = 0;
+
+ bool done = false;
+ int32_t seconds_idle = 0;
+ int32_t btn_over = -1;
+ int32_t currentindex = 0;
+ int32_t oldindex = 0;
+ int32_t maxindex = MENUBUTTONS;
+ int32_t lastmouse_x = 0;
+ int32_t lastmouse_y = 0;
+
+ // Clear key buffer and erase mouse button presses
+ while ( keypressed() )
+ readkey();
+ mouse_b = 0;
+
+ // Enable first background drawing:
+ bool need_draw = true;
+ draw_simple_bg(true);
+ global.make_fullUpdate();
+
+ while ( !done && (SIG_OK == result) ) {
+
+ // Extra loop to divide the handling and the drawing
+ while (!done && !need_draw) {
+ // Count seconds for demo mode to start after its wait time
+ if ( global.check_time_changed() ) {
+ if (++seconds_idle > DEMO_WAIT_TIME) {
+ done = true;
+ global.set_command(GLOBAL_COMMAND_DEMO);
+ }
+ }
+
+ // Detect mouse movement for custom cursors
+ if (!env.osMouse
+ && ( (lastmouse_x != mouse_x)
+ || (lastmouse_y != mouse_y) ) ) {
+ lastmouse_x = mouse_x;
+ lastmouse_y = mouse_y;
+ need_draw = true;
+ }
+
+ // See where the mouse is
+ for (int32_t z = 0; z < MENUBUTTONS; z++) {
+ if (button[z]->isMouseOver()) {
+ if ( (btn_over > -1) && (btn_over != z) ) {
+ button[z]->draw();
+ need_draw = true;
+ }
+
+ btn_over = z;
+ break;
+ }
+ }
+
+ // Handle mouse click
+ if (mouse_b & 1) {
+ for (int32_t z = 0; z < MENUBUTTONS; z++) {
+ if (button[z]->isPressed ()) {
+ need_draw = true;
+ done = true;
+ if (z == 0)
+ global.set_command(GLOBAL_COMMAND_PLAY);
+ else if (z == 1)
+ global.set_command(GLOBAL_COMMAND_HELP);
+ else if (z == 2)
+ global.set_command(GLOBAL_COMMAND_OPTIONS);
+ else if (z == 3)
+ global.set_command(GLOBAL_COMMAND_PLAYERS);
+ else if (z == 4)
+ global.set_command(GLOBAL_COMMAND_CREDITS);
+ else if (z == 5)
+ global.set_command(GLOBAL_COMMAND_NETWORK);
+ else if (z == 6) {
+ global.set_command(GLOBAL_COMMAND_QUIT);
+ result = SIG_QUIT_GAME;
+ }
+ }
+ }
+ } // End of mouse button pressed
+
+ // check for key press
+ if ( keypressed() ) {
+ k = readkey();
+ K = k >> 8;
+ fi = 2;
+ }
+
+ // Move selection down
+ if ( ( K == KEY_DOWN ) || (K == KEY_S) ) {
+ if (++currentindex >= maxindex)
+ currentindex = 0;
+ need_draw = true;
+ }
+
+ // Move selection up
+ else if ( (K == KEY_UP) || (K == KEY_W) ) {
+ if (--currentindex < 0)
+ currentindex = maxindex - 1;
+ need_draw = true;
+ }
+
+ // Activate selection
+ else if ( (KEY_ENTER == K)
+ || (KEY_ENTER_PAD == K)
+ || (KEY_SPACE == K) ) {
+ need_draw = true;
+ done = true;
+ if (currentindex == 0)
+ global.set_command(GLOBAL_COMMAND_PLAY);
+ else if (currentindex == 1)
+ global.set_command(GLOBAL_COMMAND_HELP);
+ else if (currentindex == 2)
+ global.set_command(GLOBAL_COMMAND_OPTIONS);
+ else if (currentindex == 3)
+ global.set_command(GLOBAL_COMMAND_PLAYERS);
+ else if (currentindex == 4)
+ global.set_command(GLOBAL_COMMAND_CREDITS);
+ else if (currentindex == 5)
+ global.set_command(GLOBAL_COMMAND_NETWORK);
+ else if (currentindex == 6)
+ global.set_command(GLOBAL_COMMAND_QUIT);
+ }
+
+ // Quick keys to exit and handle close button of the window
+ else if ( (KEY_Q == K)
+ || (KEY_ESC == K)) {
+ done = true;
+ result = SIG_QUIT_GAME;
+ }
+
+ // Erase key presses
+ K = 0;
+
+ // Print out update info if any
+ if ( (global.update_string) && (global.update_string[0]) ) {
+ textout_centre_ex (global.canvas, font, global.update_string,
+ env.halfWidth - 20, env.screenHeight - 50,
+ WHITE, -1);
+ global.make_update(50, 450, 300, 50);
+ need_draw = true;
+ }
+
+ // Print out client messages
+ if (global.client_message) {
+ textout_centre_ex(global.canvas, font, global.client_message,
+ env.halfWidth - 20, env.screenHeight - 25,
+ WHITE, -1);
+ global.make_update(50, 450, 300, 100);
+ need_draw = true;
+ }
+
+ // Sleep a bit if nothing happened
+ if (!done && !need_draw)
+ LINUX_SLEEP
+ } // End of while not needing to draw
+
+
+ // flip to front if needed
+ if (need_draw) {
+
+ // Draw the buttons
+ SHOW_MOUSE(nullptr)
+ draw_simple_bg(true);
+
+ for (int32_t z = 0; z < MENUBUTTONS; z++) {
+ button[z]->draw();
+
+ // draw a rectangle around the selected button
+ if ( (z == currentindex) || (z == oldindex) ) {
+ int32_t left = env.halfWidth - move_btn - 6;
+ int32_t top = env.halfHeight - 241 + (50 * currentindex)
+ + shift_menu;
+ int32_t right = env.halfWidth + move_btn + 5;
+ int32_t bottom = env.halfHeight - 192 + (50 * currentindex)
+ + shift_menu;
+ global.make_update(left, top, right - left, bottom - top);
+ if (z == currentindex)
+ rect(global.canvas, left, top, right, bottom, YELLOW);
+ }
+ } // end of looping buttons
+
+ // Show non-OS mouse
+ SHOW_MOUSE(global.canvas)
+
+ global.do_updates();
+ need_draw = false;
+ oldindex = currentindex;
+ } // End of if need_draw
+ } // End of menu loop
+
+ clear_keybuf ();
+
+ return result;
}
-void showRoundEndScoresAt (GLOBALDATA *global, ENVIRONMENT *env, BITMAP *bitmap, int x, int y, int winner)
-{
- int z;
-
- env->make_update (x - 150, y - 100, 301, 301);
- rectfill (bitmap, x - 150, y - 100, x + 100, y + 100, BLACK);
- rect (bitmap, x - 150, y - 100, x + 100, y + 100, WHITE);
- if (winner == JEDI_WIN)
- textout_centre_ex (bitmap, font, "Jedi", x - 20, y - 90, WHITE, -1);
- else if (winner == SITH_WIN)
- textout_centre_ex (bitmap, font, "Sith", x - 20, y - 90, WHITE, -1);
- else if (winner == -2)
- textout_centre_ex (bitmap, font, "Draw", x - 20, y - 90, WHITE, -1);
- else
- textprintf_centre_ex (bitmap, font, x - 30, y - 90, global->players[winner]->color, -1, "%s: %s", global->ingame->complete_text[47], global->players[winner]->getName ());
-
- textout_centre_ex (bitmap, font, global->ingame->complete_text[50], x - 30, y - 70, WHITE, -1);
- for (z = 0; z < global->numPlayers; z++)
- {
- textprintf_ex (bitmap, font, x - 140, (z * 10) + y - 50, global->players[z]->color, -1, "%s:", global->players[z]->getName ());
- textprintf_ex (bitmap, font, x + 60, (z * 10) + y - 50, WHITE, -1, "%d", global->players[z]->score);
- }
-}
-int setSlideColumnDimensions (GLOBALDATA *global, ENVIRONMENT *env, int x, bool reset)
+static void newgame()
{
- int pixelHeight;
- char *done = env->done;
- int *dropTo = env->dropTo;
- int *h = env->h;
- int *fp = env->fp;
- double *velocity = env->velocity;
- double *dropIncr = env->dropIncr;
-
- if (x < 0 || x > (global->screenWidth-1))
- {
- return (0);
- }
-
- if (reset)
- {
- h[x] = 0;
- fp[x] = 0;
- dropTo[x] = global->screenHeight - 1;
- }
- done[x] = 0;
-
- // Calc the top and bottom of the column to slide
-
- // Find top-most non-PINK pixel
- for (pixelHeight = h[x]; pixelHeight < dropTo[x]; pixelHeight++)
- if (getpixel (env->terrain, x, pixelHeight) != PINK)
- break;
- h[x] = pixelHeight;
- env->surface[x] = pixelHeight;
-
- // Find bottom-most PINK pixel
- for (pixelHeight = dropTo[x]; pixelHeight > h[x]; pixelHeight--)
- if (getpixel (env->terrain, x, pixelHeight) == PINK)
- break;
- dropTo[x] = pixelHeight;
-
- // Find bottom-most unsupported pixel
- for (; pixelHeight >= h[x]; pixelHeight--)
- if (getpixel (env->terrain, x, pixelHeight) != PINK)
- break;
-
- // If there's some processing to do
- if ((pixelHeight >= h[x]) && (h[x] < dropTo[x]))
- {
- fp[x] = pixelHeight - (int)h[x] + 1;
- return (0);
- }
- else
- {
- if (velocity[x])
- play_sample ((SAMPLE *) global->sounds[10], env->scaleVolume((velocity[x] / 10) * 255), (int)((double)(x - global->halfWidth) / global->halfWidth * 128 + 128), 1000 - (int)((double)fp[x] / global->screenHeight) * 1000, 0);
- h[x] = 0;
- fp[x] = 0;
- done[x] = 1;
- velocity[x] = 0;
- dropIncr[x] = 0;
- dropTo[x] = global->screenHeight - 1;
- return (1);
- }
- return (0);
+ env.initialise ();
+ global.initialise ();
+
+ // if a game should be loaded, try it or deny loading of the game
+ if ( (env.loadGame) && (!Load_Game()) )
+ env.loadGame = false;
+
+ // Now check back whether to load a game
+ if (!env.loadGame)
+ initialisePlayers ();
+
+ // There must not be any tanks!
+ TANK* tank = nullptr;
+ TANK* next_tank = nullptr;
+ global.getHeadOfClass(CLASS_TANK, &tank);
+ while (tank) {
+ tank->getNext(&next_tank);
+ tank->player = nullptr;
+ delete tank;
+ tank = next_tank;
+ }
+
+ // This is always true here, as a newly started game is handled like a loaded one:
+ env.isGameLoaded = true;
}
-int drawFracture (GLOBALDATA *global, ENVIRONMENT *env, BITMAP *dest, BOX *updateArea, int x, int y, int angle, int width, int segmentLength, int maxRecurse, int recurseDepth)
+/// @brief parse arguments and return EXIT_SUCCESS on success or EXIT_FAILURE
+/// if anything went wrong. If all is well but help was requested, return
+/// EXIT_HELP_SHOWN
+static int32_t parse_args(int32_t argc, char** argv)
{
- int branchCount;
- int x1, x2, x3;
- int y1, y2, y3;
-
- x1 = (int)(x + global->slope[angle][0] * width);
- y1 = (int)(y + global->slope[angle][1] * width);
- x2 = (int)(x - global->slope[angle][0] * width);
- y2 = (int)(y - global->slope[angle][1] * width);
- x3 = (int)(x + global->slope[angle][1] * segmentLength);
- y3 = (int)(y + global->slope[angle][0] * segmentLength);
- triangle (dest, x1, y1, x2, y2, x3, y3, PINK);
-
- if (recurseDepth == 0)
- {
- updateArea->x = x1;
- updateArea->y = y1;
- updateArea->w = x1;
- updateArea->h = y1;
- }
- updateArea->x = MIN (MIN (MIN (x1, x2), x3), updateArea->x);
- updateArea->y = MIN (MIN (MIN (y1, y2), y3), updateArea->y);
- updateArea->w = MAX (MAX (MAX (x1, x2), x3), updateArea->w);
- updateArea->h = MAX (MAX (MAX (y1, y2), y3), updateArea->h);
-
- if (recurseDepth < maxRecurse)
- {
- for (branchCount = 0; branchCount < 3; branchCount++)
- {
- if ((branchCount == 0) || (Noise (x + y + branchCount) < 0))
- {
- int newAngle, reduction;
- newAngle = (angle + (int)(Noise (x + y + 4) * 30));
- while (newAngle < 0)
- newAngle += 360;
- newAngle %= 360;
-
- reduction = 2;
- if (branchCount == 1)
- {
- newAngle = (int)(angle + 90 +
- (Noise (x + y + 25 + branchCount) * 22.5)) % 360;
- reduction = abs ((int)Noise (x + y + 1 + branchCount) * 4 + 3);
- }
- else if (branchCount == 2)
- {
- newAngle = (int)(angle + 270 +
- (Noise (x + y + 32 + branchCount) * 22.5)) % 360;
- reduction = abs ((int)Noise (x + y + 2 + branchCount) * 4 + 3);
- }
- drawFracture (global, env, dest, updateArea, x3, y3, newAngle, width / reduction, segmentLength / reduction, maxRecurse, recurseDepth + 1);
- }
- }
- }
-
- // Calculate width and height, previously right and bottom
- if (recurseDepth == 0)
- {
- updateArea->w -= updateArea->x;
- updateArea->h -= updateArea->y;
- }
-
- return (0);
-}
-
-/*
-void initSurface (GLOBALDATA *global, ENVIRONMENT *env)
-{
- int pixelHeight;
- for (int x = 0; x < global->screenWidth; x++)
- {
- for (pixelHeight = 0; pixelHeight < global->screenHeight; pixelHeight++)
- if (getpixel (env->terrain, x, pixelHeight) != PINK)
- break;
- env->surface[x] = pixelHeight;
- }
+ for (int32_t c = 1; c < argc; ++c) {
+ bool has_value = true;
+ std::string arg(argv[c]);
+
+ if ( (arg == SWITCH_HELP) || (arg == "--help") ) {
+ print_text_help();
+ return HELP_REQUESTED;
+ } else if (arg == SWITCH_FULL_SCREEN) {
+ screen_mode = GFX_AUTODETECT_FULLSCREEN;
+ full_screen = FULL_SCREEN_TRUE;
+ } else if (arg == SWITCH_WINDOWED) {
+ screen_mode = GFX_AUTODETECT_WINDOWED;
+ full_screen = FULL_SCREEN_FALSE;
+ } else if ( (arg == "-d") || (arg == "--depth") ) {
+ if ( (c < (argc - 1)) && (argv[c + 1][0] != '-') ) {
+ std::string next_arg(argv[++c]);
+ int32_t val = strtol (next_arg.c_str(), nullptr, 10);
+
+ if ( (16 == val) || (32 == val) )
+ env.colourDepth = val;
+ else {
+ cerr << "ERROR: Invalid graphics depth!\n"
+ << " Only 16 or 32 bit are supported!" << endl;
+ return EXIT_FAILURE;
+ }
+ } else
+ has_value = false;
+ } else if ( (arg == "-w") || (arg == "--width") ) {
+ if ( (c < (argc - 1)) && (argv[c + 1][0] != '-') ) {
+ std::string next_arg(argv[++c]);
+ int32_t val = strtol (next_arg.c_str(), nullptr, 10);
+
+ if ( 512 <= val ) {
+ env.screenWidth = val;
+ env.halfWidth = env.screenWidth / 2;
+ env.temp_screenWidth = env.screenWidth;
+ } else {
+ cerr << "ERROR: Width too small (minimum 512)\n" << endl;
+ return EXIT_FAILURE;
+ }
+ } else
+ has_value = false;
+ } else if ((arg == "-t") || (arg == "--tall") || (arg == "--height")) {
+ if ( (c < (argc - 1)) && (argv[c + 1][0] != '-') ) {
+ std::string next_arg(argv[++c]);
+ int32_t val = strtol (next_arg.c_str(), nullptr, 10);
+
+ if ( 320 <= val ) {
+ env.screenHeight = val;
+ env.halfHeight = env.screenHeight / 2;
+ env.temp_screenHeight = env.screenHeight;
+ } else {
+ cerr << "ERROR: Height too small (minimum 320)\n" << endl;
+ return EXIT_FAILURE;
+ }
+ } else
+ has_value = false;
+ } else if (arg == "--datadir") {
+ if ( (c < (argc - 1)) && (argv[c + 1][0] != '-') ) {
+ std::string next_arg(argv[++c]);
+
+ if ( next_arg.length() <= PATH_MAX )
+ strncpy(env.dataDir, next_arg.c_str(), PATH_MAX);
+ else {
+ cerr << "ERROR: Datadir path too long:\n"
+ << "\"" << next_arg << "\"\n\n"
+ << "Maximum length:" << PATH_MAX << " characters"
+ << endl;
+ return EXIT_FAILURE;
+ }
+ } else
+ has_value = false;
+ } else if (arg == "-c") {
+ if ( (c < (argc - 1)) && (argv[c + 1][0] != '-') ) {
+ std::string next_arg(argv[++c]);
+
+ if ( next_arg.length() <= PATH_MAX )
+ strncpy(env.configDir, next_arg.c_str(), PATH_MAX);
+ else {
+ cerr << "ERROR: Configuration path too long:\n"
+ << "\"" << next_arg << "\"\n\n"
+ << "Maximum length:" << PATH_MAX << " characters"
+ << endl;
+ return EXIT_FAILURE;
+ }
+ } else
+ has_value = false;
+ } else if (arg == "--noconfig")
+ load_config_file = false;
+ else if (arg == "--nosound")
+ env.sound_enabled = false;
+ else if (arg == "--noname")
+ env.nameAboveTank = false;
+ else if (arg == "--nonetwork")
+ allow_network = false;
+ else if (arg == "--nobackground")
+ env.drawBackground = false;
+ else if (arg == "--nothread")
+ cout << "--nothread is deprecated and will be ignored." << endl;
+ else if (arg == "--thread")
+ cout << "--thread is deprecated and will be ignored." << endl;
+
+ // If a required argument is missing, print out a message
+ if (!has_value) {
+ cerr << "ERROR: Missing argument for " << arg << endl;
+ return EXIT_FAILURE;
+ }
+ }
+
+ return EXIT_SUCCESS;
}
-*/
-
-
-int slideLand (GLOBALDATA *global, ENVIRONMENT *env)
-{
- char *done = env->done;
- int *dropTo = env->dropTo;
- int *h = env->h;
- int *fp = env->fp;
- double *velocity = env->velocity;
- double *dropIncr = env->dropIncr;
- int zz;
- double land_slide_type = LANDSLIDE_INSTANT;
-
- // land-slide, make it fall etc.
- int allDone = 1;
- if ( (env->landSlideType == LANDSLIDE_NONE) ||
- (env->landSlideType == LANDSLIDE_TANK_ONLY) )
- return (allDone);
-
- else if (env->landSlideType == LANDSLIDE_CARTOON)
- {
- if (env->time_to_fall > 0)
- land_slide_type = LANDSLIDE_CARTOON;
- else
- land_slide_type = LANDSLIDE_GRAVITY;
- }
-
- else if (env->landSlideType == LANDSLIDE_GRAVITY)
- land_slide_type = LANDSLIDE_GRAVITY;
-
- if (land_slide_type == LANDSLIDE_CARTOON)
- return (allDone);
-
- for (zz = 0; zz < global->screenWidth; zz++)
- {
- if (!done[zz])
- {
- allDone = 0;
- if (land_slide_type == LANDSLIDE_GRAVITY)
- {
- if (fp[zz] > 0)
- {
- velocity[zz] += env->gravity;
- dropIncr[zz] += velocity[zz];
- if (dropIncr[zz] >= 1)
- {
- if (dropIncr[zz] > dropTo[zz] - (h[zz] + fp[zz]))
- {
- dropIncr[zz] = dropTo[zz] - (h[zz] + fp[zz]) + 1;
- }
- blit (env->terrain, env->terrain, zz, h[zz] - (int)dropIncr[zz], zz, h[zz], 1, fp[zz] + (int)dropIncr[zz]);
- env->make_bgupdate (zz, h[zz] - (int)dropIncr[zz], 1, fp[zz] + ((int)dropIncr[zz] * 2) + 1);
- env->make_update (zz, h[zz] - (int)dropIncr[zz], 1, fp[zz] + ((int)dropIncr[zz] * 2) + 1);
- h[zz] += (int)dropIncr[zz];
- dropIncr[zz] -= (int)dropIncr[zz];
- }
- setSlideColumnDimensions (global, env, zz, FALSE);
- }
- else
- {
- setSlideColumnDimensions (global, env, zz, FALSE);
- }
- }
- else if (land_slide_type == LANDSLIDE_INSTANT)
- {
- if (fp[zz] > 0)
- {
- env->make_bgupdate (zz, h[zz], 1, dropTo[zz] - h[zz] + 1);
- env->make_update (zz, h[zz], 1, dropTo[zz] - h[zz] + 1);
- done[zz] = 1;
- blit (env->terrain, env->terrain, zz, h[zz], zz, dropTo[zz] - fp[zz] + 1, 1, fp[zz]);
- vline (env->terrain, zz, h[zz], dropTo[zz] - fp[zz], PINK);
- }
- setSlideColumnDimensions (global, env, zz, FALSE);
- }
- }
- }
- return (allDone);
-}
-void drawTopBar (GLOBALDATA *global, ENVIRONMENT *env, BITMAP *dest)
+static void play_demo()
{
- TANK *tank = global->currTank;
- char *name = "";
- int color = 0;
- int wind_col1 = 0, wind_col2 = 0;
- static int change_weapon_colour = RED;
- char *team_name = "";
- int time_to_fire = 0;
- int minus_font = (font == global->unicode) ? 9 : 0;
-
- if (tank)
- {
- name = global->currTank->player->getName ();
- color = global->currTank->player->color;
- team_name = global->currTank->player->Get_Team_Name();
- time_to_fire = tank->player->time_left_to_fire;
- }
-
- global->updateMenu = 0;
- blit (global->gfxData.topbar, dest, 0, 0, 0, 0, global->screenWidth, MENUHEIGHT);
-
- if (tank)
- {
- textout_ex (dest, font, name, 2, 2 - minus_font, BLACK, -1);
- textout_ex (dest, font, name, 1, 1 - minus_font, color, -1);
- textprintf_ex (dest, font, 1, 11 - minus_font, BLACK, -1, "%s", global->ingame->complete_text[18]);
- graph_bar_center (env, 50, 11, color, -(tank->a - 180) / 2, 180 / 2);
- // 0 is directly left, 180 points directly right
- textprintf_ex (dest, font, 150, 11 - minus_font, BLACK, -1, "%d", 180 - (tank->a - 90));
-
- textprintf_ex (dest, font, 1, 21 - minus_font, BLACK, -1, "%s", global->ingame->complete_text[19]);
- graph_bar (env, 50, 20, color, (tank->p) / (MAX_POWER/90), 90);
- textprintf_ex (dest, font, 150, 21 - minus_font, BLACK, -1, "%d", tank->p);
- textprintf_ex (dest, font, 200, 21 - minus_font, BLACK, -1, "%s: %s", global->ingame->complete_text[20], team_name);
- if (tank->cw < WEAPONS)
- {
- int weapon_amount;
-
- if (! weapon[tank->cw].delay )
- weapon_amount = tank->player->nm[tank->cw];
- else
- weapon_amount = tank->player->nm[tank->cw] / weapon[tank->cw].delay;
-
- if (tank->player->changed_weapon)
- {
- textprintf_ex (dest, font, 180, 1, change_weapon_colour, -1, "%s: %d",
- weapon[tank->cw].name, weapon_amount);
- // tank->player->nm[tank->cw]);
- if (change_weapon_colour == RED)
- change_weapon_colour = WHITE;
- else
- change_weapon_colour = RED;
-
-
- }
-
- else
- textprintf_ex (dest, font, 180, 1, BLACK, -1,"%s: %d",
- weapon[tank->cw].name, weapon_amount);
-
- } // end of less than WEAPONS
- else
- {
- textprintf_ex (dest, font, 180, 1, BLACK, -1, "%s: %d",
- item[tank->cw - WEAPONS].name, tank->player->ni[tank->cw - WEAPONS]);
- }
- draw_sprite (env->db, (BITMAP *) global->stock[ (tank->cw) ? tank->cw : 1], 700, 1);
- textprintf_ex (dest, font, 350, 1, BLACK, -1, "$%s", Add_Comma(tank->player->money));
- textprintf_ex (dest, font, 350, 12, BLACK, -1, "%s: %d", global->ingame->complete_text[21], tank->player->ni[ITEM_FUEL]);
- }
-
- textprintf_ex ( dest, font, 500, 1 - minus_font, BLACK, -1, "%s %d/%d",
- global->ingame->complete_text[12],
- (int)(global->rounds - global->currentround) + 1, (int)global->rounds);
-
- if (global->tank_status[0])
- textprintf_ex(dest, font, 350, 21, global->tank_status_colour, -1, "%s",
- global->tank_status);
-
- if (env->windstrength > 0)
- {
- textprintf_ex (dest, font, 500, 11 - minus_font, BLACK, -1, "%s", global->ingame->complete_text[22]);
- if (env->wind > 0)
- {
- wind_col1 = 1;
- wind_col2 = 0;
- }
- if (env->wind < 0)
- {
- wind_col1 = 0;
- wind_col2 = 1;
- }
- rect (dest, 540, 12, (int)(540 + env->windstrength * 4 + 2), 18, BLACK);
- rectfill (dest, (int)(541 + env->windstrength * 2), 13,
- (int) (541 + env->wind * 4 + env->windstrength * 2), 17,
- makecol (200 * wind_col1, 200 * wind_col2, 0));
- }
-
- if (global->max_fire_time)
- textprintf_ex( dest, font, 500, 20, BLACK, -1, "Time: %d", time_to_fire);
-
- global->stopwindow = 1;
- env->make_update (0, 0, global->screenWidth, MENUHEIGHT);
- global->stopwindow = 0;
+ int32_t old_skip = env.skipComputerPlay;
+ int32_t old_rounds = env.rounds;
+ bool old_music = env.play_music;
+
+ global.demo_mode = true;
+ env.loadGame = false;
+ env.play_music = false;
+
+ env.rounds = (rand() % 101) + (rand() % 101) + 50;
+ global.currentround = env.rounds - (rand() % env.rounds);
+
+ // Be sure to have at least 10 rounds left
+ if (global.currentround < 10)
+ global.currentround = 10;
+
+ // And at least 10 rounds must have been played, or it'll be a bit boring
+ if (global.currentround > (env.rounds - 10))
+ global.currentround = env.rounds - 10;
+
+ env.skipComputerPlay = SKIP_NONE;
+
+ // set up a bunch of players (non-human, less than 10)
+ int32_t playerCount = 0;
+ env.numGamePlayers = 0;
+ for (int32_t i = 0; i < env.numPermanentPlayers; ++i) {
+ if ( (env.allPlayers[i]->type > HUMAN_PLAYER)
+ && (i < MAXPLAYERS) ) {
+ env.addGamePlayer(env.allPlayers[i]);
+ playerCount++;
+ }
+ }
+
+ newgame();
+
+ for (int32_t i = 0; i < env.numGamePlayers; ++i) {
+ env.players[i]->newGame();
+
+ // give them money to spend:
+ env.players[i]->money += static_cast<int32_t>(env.players[i]->type)
+ * 25000 * (env.rounds - global.currentround);
+ }
+
+ while ( (global.currentround > 0) && (!global.isCloseBtnPressed()) ) {
+ game();
+ if ( (global.get_command() == GLOBAL_COMMAND_QUIT)
+ || (global.get_command() == GLOBAL_COMMAND_MENU) )
+ break;
+ }
+
+ endgame_cleanup();
+ global.demo_mode = false;
+ env.skipComputerPlay = old_skip;
+ env.play_music = old_music;
+ env.rounds = old_rounds;
}
-/*
- * Calculate the effective damage for a given weapon.
- * Recursively add the damage of sub-munitions, factor in chaos
- * and munition density.
- */
-long int calcTotalEffectiveDamage (int weapNum)
-{
- WEAPON *weap = &weapon[weapNum];
- long int total = 0;
-
- if (weap->submunition >= 0)
- {
- WEAPON *subm = &weapon[weap->submunition];
-
- // How chaotic is this weapon?
- double chaosVal=(weap->spreadVariation +
- weap->speedVariation +
- subm->countVariation) / 3;
- double coverage = (weap->numSubmunitions *
- subm->radius) /
- (double)weap->radius;
-
- total += calcTotalEffectiveDamage (weap->submunition) *
- weap->numSubmunitions;
- total = (long int)(total * coverage / weap->numSubmunitions);
- total -= (long int)((total / 2) * (1.0 - chaosVal));
- }
- else
- {
- total += weap->damage;
- }
-
- return (total);
-}
-/*
- * Calculate the potential damage for a given weapon.
- * Recursively add the damage of sub-munitions.
- */
-long int calcTotalPotentialDamage (int weapNum)
+static void play_local()
{
- WEAPON *weap = &weapon[weapNum];
- long int total = 0;
-
- if ( (weap->submunition >= 0) && (weap->numSubmunitions > 0) )
- total += calcTotalPotentialDamage (weap->submunition) *
- weap->numSubmunitions;
- else
- total += weap->damage;
-
- return (total);
+ if (selectPlayers() != MRC_Esc_Menu) {
+
+ // make sure the game has a name
+ if (!env.game_name[0])
+ strncpy(env.game_name, env.ingame->Get_Line(53), GAMENAMELEN);
+
+ newgame ();
+
+ if (!env.loadGame) {
+ global.currentround = env.rounds;
+ for (int32_t i = 0; i < env.numGamePlayers; ++i)
+ env.players[i]->newGame();
+ }
+
+ // play the game for the selected number of rounds
+ while ( (global.currentround > 0) && (!global.isCloseBtnPressed()) ) {
+ game (); // play a round
+
+ if (env.background_music) {
+ stop_sample(env.background_music);
+ }
+
+ if (global.isCloseBtnPressed())
+ global.set_command(GLOBAL_COMMAND_QUIT);
+
+ // if user selected to quit or return to main menu during game play
+ if ( (global.get_command() == GLOBAL_COMMAND_QUIT)
+ || (global.get_command() == GLOBAL_COMMAND_MENU) ) {
+ env.sendToClients("CLOSE");
+ break;
+ }
+
+ if (global.currentround != 0)
+ // end of the round
+ env.sendToClients("ROUNDEND");
+ }
+
+ // only show winner if finished all rounds and not broken off the last
+ // round by exiting or quitting
+ if ( (global.currentround == 0)
+ && (global.get_command() == GLOBAL_COMMAND_PLAY) ) {
+ char buffer[256] = { 0 };
+ const char* winner = do_winner();
+
+ if (winner)
+ snprintf(buffer, 255, "GAMEEND The game went to %s.", winner);
+ else
+ strncpy(buffer, "GAMEEND", 255);
+
+ env.sendToClients(buffer);
+
+ // Do fade and wait for user keypress
+ quickChange(true);
+ readkey ();
+
+ for (int i = 0; i < env.numGamePlayers; i++)
+ env.players[i]->type = env.players[i]->type_saved;
+ }
+ endgame_cleanup ();
+ } // end of start new game
}
-void doNaturals (GLOBALDATA *global, ENVIRONMENT *env)
-{
- int chance;
-
- if (env->naturals_since_last_shot >= 5)
- return;
-
- if (env->lightning)
- {
- chance = (int)(600 / env->lightning) + 100;
- if (!(rand () % chance))
- {
- BEAM *newbeam;
- int ca = ((rand () % 160) + (360 - 80)) % 360;
-
- newbeam = new BEAM (global, env,
- rand () % global->screenWidth, 0,
- ca, SML_LIGHTNING + (rand () % (int)env->lightning));
- if (newbeam)
- {
- newbeam->player = NULL;
- env->naturals_since_last_shot++;
- }
- else
- perror ( "atanks.cc: Failed allocating memory for newbeam in doNaturals");
- }
- } // end of lightning
-
- // only create meteors if we are not in aim mode on simul turn type
- if ( (global->turntype == TURN_SIMUL) && (env->stage == STAGE_AIM) )
- return;
-
- if (env->meteors)
- {
- chance = (int)(600 / env->meteors) + 100;
- if (!(rand () % chance))
- {
- MISSILE *newmis;
- int ca = ((rand () % 160) + (360 - 80)) % 360;
- double mxv = global->slope[ca][0] * 5;
- double myv = global->slope[ca][1] * 5;
-
- newmis = new MISSILE(global, env,
- rand () % global->screenWidth, 0,
- mxv, myv, SML_METEOR + (rand () % (int)env->meteors));
- if (newmis)
- {
- newmis->player = NULL;
- env->naturals_since_last_shot++;
- }
- else
- perror ( "atanks.cc: Failed allocating memory for newmis in doNaturals");
- }
- }
-
- if (env->falling_dirt_balls)
- {
- chance = (int) (600 / env->falling_dirt_balls) + 100;
- if (! (rand() % chance) )
- {
- MISSILE *newmis;
- int ca = ((rand() % 100) + (360 - 80) ) % 360;
- double mxv = global->slope[ca][0] * 5;
- double myv = global->slope[ca][1] * 5;
-
- newmis = new MISSILE(global, env,
- rand() % global->screenWidth, 0,
- mxv, myv, DIRT_BALL + ( rand() % (int) env->falling_dirt_balls) );
- if (newmis)
- {
- newmis->player = NULL;
- env->naturals_since_last_shot++;
- }
- else
- perror( "atanks.cc: Failed to allocate memory for falling dirt ball in doNaturals");
- }
- }
-}
-#ifdef OLD_GAMELOOP
-void game (GLOBALDATA *global, ENVIRONMENT *env)
+static void play_networked()
{
- int tanksfall;
- int tanklife, tlt, dclock;
- int lb, ca;
- int allDone, anyExploding, anyTeleporting, anyLaserFiring;
- int roundEndCount = 0;
- bool nextTankSelected = false;
- int humanPlayers = 0;
- int skippingComputerPlay = FALSE;
- int team_won = NO_WIN;
- bool bWinnerIsCredited = false;
- int my_class;
- VIRTUAL_OBJECT *my_object;
-
- TANK **tank, *ltank;
- MISSILE *missile;
- TELEPORT *teleport;
- DECOR *decor;
- BEAM *beam;
- EXPLOSION *explosion;
- FLOATTEXT *floattext;
- static SATELLITE *satellite = NULL;
-
- int bCount;
- int z, zz, z4;
- int objCount, count;
- int AI_clock = 0;
-
- global->computerPlayersOnly = FALSE;
-
- tank = &global->currTank;
-
- for (int doneCount = 0; doneCount < global->screenWidth; doneCount++)
- env->done[doneCount] = 0;
- // initSurface (global, env); // init surface[]
-
- env->newRound ();
- // set wall colour
- switch (env->current_wallType)
- {
- case WALL_RUBBER:
- env->wallColour = GREEN;
- break;
- case WALL_STEEL:
- env->wallColour = RED;
- break;
- case WALL_SPRING:
- env->wallColour = BLUE;
- break;
- case WALL_WRAP:
- env->wallColour = YELLOW;
- break;
- }
- if (env->dBoxedMode == 2.0)
- {
- if (rand() % 2)
- global->bIsBoxed = true;
- else
- global->bIsBoxed = false;
- }
- else if (env->dBoxedMode == 1.0)
- global->bIsBoxed = true;
- else if (env->dBoxedMode == 0.0)
- global->bIsBoxed = false;
- // Set Max velocity
- global->dMaxVelocity = (double)MAX_POWER * (100.0 / (double)global->frames_per_second) / 100.0;
- if ((env->current_wallType == WALL_SPRING) && !global->bIsBoxed)
- // In non-boxed the Spring Wall is allowed to have at least twice the normal velocity
- global->dMaxVelocity *= 2.0;
- if (global->bIsBoxed)
- // In boxed Mode, there is four times the normal max velocity allowed (or it won't be fun!)
- global->dMaxVelocity *= 4.0;
- for (count = 0; count < global->numPlayers; count++)
- global->players[count]->newRound ();
-
- /* Unfortunately, ENVIRONMENT can't call uppon FLOATTEXT::newRound due to circular dependencies.
- Thus we have to do that here: */
-
- for (int objCount = 0; objCount < MAX_OBJECTS; objCount++)
- {
- if (env->objects[count] && (env->objects[count]->isSubClass(FLOATTEXT_CLASS)))
- ((FLOATTEXT *)env->objects[count])->newRound();
- }
-
- buystuff (global, env);
- if (global->close_button_pressed)
- {
- global->wr_lock_command();
- global->command = GLOBAL_COMMAND_QUIT;
- global->unlock_command();
- return;
- }
-
- for (count = 0; count < global->numPlayers; count++)
- global->players[count]->exitShop ();
-
- set_level_settings (global, env);
-
- for (objCount = 0; (ltank = (TANK*)env->getNextOfClass (TANK_CLASS, &objCount)) && ltank; objCount++)
- {
- ltank->newRound ();
- if ((int)ltank->player->type == HUMAN_PLAYER)
- {
- humanPlayers++;
- }
- }
- if (!humanPlayers)
- {
- global->computerPlayersOnly = TRUE;
- //if ((int)global->skipComputerPlay >= SKIP_AUTOPLAY)
- // skippingComputerPlay = TRUE;
- }
- lock_cclock();
- cclock = lx = ly = tanksfall = 0;
- unlock_cclock();
- env->stage = STAGE_AIM;
- tlt = global->updateCount = dclock = global->stopwindow = 0;
- env->realm = env->am = 0;
- ca = 0;
- lb = env->mouseclock = env->pclock = 0;
- ord = 0;
-
- *tank = env->order[0];
- for (objCount = 0; (ltank = (TANK*)env->getNextOfClass (TANK_CLASS, &objCount)) && ltank; objCount++)
- {
- ltank->flashdamage = 0;
- ltank->boost_up_shield ();
- }
- winner = tanklife = -1;
- fi = global->updateMenu = 1;
- global->window.x = 0;
- global->window.y = 0;
- global->window.w = (global->screenWidth-1);
- global->window.h = (global->screenHeight-1);
- bCount = 0;
- if ((int)env->windstrength != 0)
- env->wind = (float)(rand () % (int)env->windstrength) - (env->windstrength / 2);
- else
- env->wind = 0;
- env->lastwind = env->wind;
- if ( (env->satellite) && (! satellite) )
- satellite = new SATELLITE(global, env);
- if (satellite)
- satellite->Init();
-
- global->iHumanLessRounds = -1;
-#ifdef DEBUG_AIM_SHOW
- global->bASD = false;
+#ifdef NETWORK
+ client_socket = Setup_Client_Socket(env.server_name, env.server_port);
+ if (client_socket >= 0) {
+ bool keep_playing = true;
+ cout << "Ready to play networked" << endl;
+
+ while (keep_playing)
+ keep_playing = Game_Client(client_socket);
+
+ Clean_Up_Client_Socket(client_socket);
+ } else
+ cerr << "ERROR: Unable to connect to server " << env.server_name
+ << ", port " << env.server_port << endl;
+#else
+ char noNetworkMsg[200] = { 0 };
+ snprintf(noNetworkMsg, 199,
+ "This version of Atanks is not compiled to"
+ " handle network games.");
+ errorMessage = noNetworkMsg;
+ errorX = env.halfWidth - text_length(font, errorMessage) / 2;
+ errorY = env.menuBeginY + 15;
+ cerr << "ERROR: " << noNetworkMsg << endl;
#endif
- global->background_music = global->Load_Background_Music();
- if (global->background_music)
- play_sample((SAMPLE *) global->background_music, 255, 128, 1000, TRUE);
-
- while (1)
- {
- LINUX_SLEEP;
- if (global->close_button_pressed)
- {
- global->wr_lock_command();
- global->command = GLOBAL_COMMAND_QUIT;
- global->wr_unlock_command();
- return;
- }
-
- while (get_cclock() > 0 || skippingComputerPlay)
- {
- lock_cclock();
- cclock--;
- unlock_cclock();
- if (!lb && mouse_b & 1)
- env->mouseclock = 0;
- lb = (mouse_b & 1) ? 1 : 0;
- bCount += 360/32;
- z4 = 0;
- anyExploding = 0;
- anyTeleporting = 0;
- anyLaserFiring = 0;
-
- env->am = 0;
- objCount = 0;
- my_object = env->objects[objCount];
- // keep track of how long we have been skipping AI
- if (skippingComputerPlay)
- {
- // advance clock
- if ( global->Check_Time_Changed() )
- AI_clock++;
- if (AI_clock > MAX_AI_TIME)
- {
- int player_index = 0;
- // kill all remaining tanks
- while (player_index < global->numPlayers)
- {
- if ( ( global->players[player_index] ) &&
- ( global->players[player_index]->tank ) )
- global->players[player_index]->tank->l = 0;
- player_index++;
- }
- }
- } // end of AI clock code
-
- while (objCount < MAX_OBJECTS)
- {
- if (my_object)
- {
- my_class = my_object->getClass();
- //for (objCount = 0; (decor = (DECOR*)env->getNextOfClass (DECOR_CLASS, &objCount)) && decor; objCount++)
- if (my_class == DECOR_CLASS)
- {
- decor = (DECOR *) my_object;
- decor->applyPhysics ();
- if (decor->destroy)
- {
- decor->requireUpdate ();
- decor->update ();
- delete decor;
- }
- }
- // for (objCount = 0; (explosion = (EXPLOSION*)env->getNextOfClass (EXPLOSION_CLASS, &objCount)) && explosion; objCount++)
- else if (my_class == EXPLOSION_CLASS)
- {
- explosion = (EXPLOSION *) my_object;
- if (explosion->bIsWeaponExplosion)
- anyExploding++;
- explosion->explode ();
- explosion->applyPhysics ();
- if (explosion->destroy)
- {
- explosion->requireUpdate ();
- explosion->update ();
- delete explosion;
- anyExploding--;
- }
- }
-
- // for (objCount = 0; (teleport = (TELEPORT*)env->getNextOfClass (TELEPORT_CLASS, &objCount)) && teleport; objCount++)
- else if (my_class == TELEPORT_CLASS)
- {
- teleport = (TELEPORT *) my_object;
- anyTeleporting++;
- teleport->applyPhysics ();
- if (teleport->destroy)
- {
- teleport->requireUpdate ();
- teleport->update ();
- delete teleport;
- anyTeleporting--; // It's done!
- tanksfall = 1;
- }
- }
- // env->am = 0;
- // for (objCount = 0; (missile = (MISSILE*)env->getNextOfClass (MISSILE_CLASS, &objCount)) && missile; objCount++)
- else if (my_class == MISSILE_CLASS)
- {
- TANK *shooting_tank = NULL;
- BEAM *defense_beam = NULL;
- int angle_to_fire;
-
- missile = (MISSILE *) my_object;
- env->am++;
- missile->hitSomething = 0;
- missile->applyPhysics ();
- missile->triggerTest ();
- shooting_tank = missile->Check_SDI(global);
- if (shooting_tank)
- {
- (shooting_tank->x < missile->x) ? angle_to_fire = 135 : angle_to_fire = 225;
- defense_beam = new BEAM(global, env, shooting_tank->x, shooting_tank->y - 10, angle_to_fire, SML_LAZER);
- missile->trigger();
- }
- if (missile->destroy)
- {
- missile->requireUpdate ();
- missile->update ();
- delete missile;
- tanksfall = 1;
- }
- }
- // for (objCount = 0; (beam = (BEAM*)env->getNextOfClass (BEAM_CLASS, &objCount)) && beam; objCount++)
- else if (my_class == BEAM_CLASS)
- {
- beam = (BEAM *) my_object;
- // As bots should not target while a laser is shot:
- anyLaserFiring ++;
- beam->applyPhysics ();
- if (beam->destroy)
- {
- beam->requireUpdate ();
- beam->update ();
- delete beam;
- anyLaserFiring--; // It's done!
- tanksfall = 1;
- }
- }
- // for (objCount = 0; (floattext = (FLOATTEXT*)env->getNextOfClass (FLOATTEXT_CLASS, &objCount)) && floattext; objCount++)
- else if (my_class == FLOATTEXT_CLASS)
- {
- floattext = (FLOATTEXT *) my_object;
- floattext->applyPhysics ();
- if (floattext->destroy)
- {
- floattext->requireUpdate();
- floattext->update();
- delete floattext;
- env->make_fullUpdate(); // ...kill remaining texts!
- }
- }
- } // end of if we have an object
- objCount++;
- my_object = env->objects[objCount];
- } // end of going through virtual objects
-
-
- #ifdef NETWORK
- for (int counter = 0; counter < global->numPlayers; counter++)
- {
- if (global->players[counter]->type == NETWORK_CLIENT)
- {
- global->players[counter]->Get_Network_Command();
- global->players[counter]->Execute_Network_Command(FALSE);
- }
- }
- #endif
-
- if (satellite)
- satellite->Move();
-
- if (!anyExploding)
- {
- doNaturals (global, env);
- if (satellite)
- satellite->Shoot();
- }
-
- allDone = slideLand (global, env);
- if (tanksfall && (env->stage != STAGE_ENDGAME))
- {
- for (objCount = 0; (ltank = (TANK*)env->getNextOfClass (TANK_CLASS, &objCount)) && ltank;
- count--, objCount++)
- {
- ltank->pen = 0;
- ltank->applyPhysics (global);
- if (ltank->l <= 0 && !anyExploding)
- {
- ltank->explode ();
- if (ltank->creditTo)
- {
- if (ltank->player != ltank->creditTo) //enemy destroyed
- {
- ltank->creditTo->money += (int)global->scoreUnitDestroyBonus;
- }
- else //self destroy - ugh foolish one :))
- {
- ltank->creditTo->money -= (int)global->scoreUnitSelfDestroy;
- if (ltank->creditTo->money < 0)
- ltank->creditTo->money = 0;
- }
- ltank->creditTo = NULL;
- }
- if (ltank->destroy)
- {
- if ((int)ltank->player->type == HUMAN_PLAYER)
- humanPlayers--;
-
- ltank->Destroy();
- delete(ltank);
-
- ltank = NULL; // should not be used anymore, setting NULL for later IF's to checks
- if ((!skippingComputerPlay) || (global->numTanks <= 1))
- env->make_fullUpdate();
- if (!humanPlayers)
- {
- int iMostIntelligentPlayer = 0;
- int iNumPlayers = 0;
-
- for (int i = 0; i < global->numPlayers; i++)
- {
- int iType = 0;
-
- if (global->players[i]->tank)
- {
- if (global->players[i]->tank->l > 0)
- {
- iType = (int) global->players[i]->type;
- iNumPlayers++;
- }
- }
- if (iType > iMostIntelligentPlayer)
- iMostIntelligentPlayer = iType;
- }
- // If the most intelligent player is more stupid than deadly, raise them all!
- if ( (iMostIntelligentPlayer < (int) DEADLY_PLAYER)
- && (iMostIntelligentPlayer >= (int) USELESS_PLAYER)
- && (iNumPlayers > 1))
- for (int i = 0; i < global->numPlayers; i++)
- {
- if (global->players[i]->tank)
- {
- if (global->players[i]->tank->l > 0)
- global->players[i]->setComputerValues ( (int) DEADLY_PLAYER - iMostIntelligentPlayer);
- }
- }
-
-#ifdef DEBUG
- if ((iMostIntelligentPlayer >= (int)USELESS_PLAYER) && (iNumPlayers > 1))
- {
- cout << endl << "===========================================================" << endl;
- cout << "Round without human players!" << endl;
- cout << "Most intelligent player has Skill level " << iMostIntelligentPlayer << endl;
- cout << "Raised all players by " << ((int)DEADLY_PLAYER - iMostIntelligentPlayer) << " Skill Level(s)" << endl;
- cout << "===========================================================" << endl << endl;
- }
-#endif //DEBUG
- // Initialize iHumanLessRounds if not done already
- if (global->iHumanLessRounds < 0)
- global->iHumanLessRounds = iNumPlayers * 16;
- }
- if (!*tank && global->numTanks)
- {
- *tank = nextturn (global, env, skippingComputerPlay);
- while (! *tank)
- *tank = nextturn (global, env, skippingComputerPlay);
- (*tank)->fs = 0;
- nextTankSelected = true;
- }
- team_won = Team_Won(global);
- if ( (global->numTanks <= 1) || ( team_won ) )
- {
- skippingComputerPlay = FALSE;
- lock_cclock();
- cclock = 0;
- unlock_cclock();
- env->stage = STAGE_ENDGAME;
- global->currTank = NULL;
- fi = 1;
- global->window.x = 0;
- global->window.y = 0;
- global->window.w = (global->screenWidth-1);
- global->window.h = (global->screenHeight-1);
- winner = -2;
- if (team_won)
- winner = team_won;
- else if (global->numTanks > 0)
- {
- for (z = 0; z < global->numPlayers; z++)
- {
- if (global->players[z]->tank)
- winner = z;
- }
- }
-
- for (objCount = 0; (floattext = (FLOATTEXT*)env->getNextOfClass (FLOATTEXT_CLASS, &objCount)) && floattext; objCount++)
- {
- floattext->newRound();
- }
-
- bCount = 0;
- global->updateMenu = 1;
- for (z = 0; z < global->numPlayers; z++)
- global->players[z]->played++;
- }
- }
- }
-
- // adjust chess style clock (only if tank wasn't destroyed)
- if ( ( global->max_fire_time > 0.0 ) &&
- ( ltank ) &&
- ( ltank->player->type == HUMAN_PLAYER ) &&
- (! env->stage ) )
- {
- if ( global->Check_Time_Changed() )
- {
- int ran_out_of_time;
- ran_out_of_time = ltank->player->Reduce_Time_Clock();
- if (ran_out_of_time && !nextTankSelected)
- {
- ltank->player->skip_me = true;
- *tank = nextturn (global, env, skippingComputerPlay);
- while (! *tank)
- *tank = nextturn (global, env, skippingComputerPlay);
- (*tank)->fs = 0;
- nextTankSelected = true;
- }
- global->updateMenu = 1;
- }
- }
-
- if ( ( ltank ) && ( ltank->fire_another_shot ) )
- {
- if (! (ltank->fire_another_shot % VOLLY_DELAY))
- ltank->activateCurrentSelection();
- ltank->fire_another_shot--;
- if (! ltank->fire_another_shot)
- env->stage = 0;
- }
-
- }
- }
- if (env->stage == 1)
- {
- if ((env->am == 0) && allDone && !anyExploding)
- {
- tanksfall = 0;
- env->stage = 2;
- }
- }
- if (env->stage == 2)
- {
- zz = 0;
-
- for (objCount = 0; (ltank = (TANK*)env->getNextOfClass (TANK_CLASS, &objCount)) && ltank; count--, objCount++)
- {
- zz += ltank->applyPhysics (global);
- }
- if (zz == global->numTanks)
- {
- tanksfall = 1;
- }
- zz = 0;
- if (tanksfall)
- {
- for (objCount = 0; (ltank = (TANK*)env->getNextOfClass (TANK_CLASS, &objCount)) && ltank; objCount++)
- {
- ltank->pen = 0;
- if (ltank->l > 0)
- zz++;
- }
- }
- if (zz == global->numTanks)
- {
- if (((int)global->skipComputerPlay > SKIP_NONE) &&
- (!humanPlayers) && (!global->computerPlayersOnly))
- {
- skippingComputerPlay = TRUE;
- #ifdef NETWORK
- int index = 0, network_clients = 0;
- while ( (index < global->numPlayers) && (! network_clients) )
- {
- if (global->players[index]->type == NETWORK_CLIENT)
- network_clients++;
- else
- index++;
- }
- if (network_clients)
- skippingComputerPlay = FALSE;
- #endif
- if (skippingComputerPlay)
- {
- draw_sprite (env->db, (BITMAP *) global->misc[1], global->halfWidth - 120, global->halfHeight + 155);
- textout_centre_ex (env->db, font, global->ingame->complete_text[51], global->halfWidth, global->halfHeight + 160, WHITE, -1);
- draw_sprite (screen, (BITMAP *) global->misc[1], global->halfWidth - 120, global->halfHeight + 155);
- textout_centre_ex (screen, font, global->ingame->complete_text[51], global->halfWidth, global->halfHeight + 160, WHITE, -1);
- }
- }
-
- env->stage = STAGE_AIM;
- winner = -2;
- for (z = 0; z < global->numPlayers; z++)
- {
- if (global->players[z]->tank && winner >= 0)
- winner = -1;
- if (global->players[z]->tank && winner == -2)
- winner = z;
- }
- if (winner >= 0)
- {
- global->players[winner]->score++;
- }
- if (winner == -1)
- {
- if ((!nextTankSelected || !tank) && global->numTanks)
- {
- *tank = nextturn (global, env, skippingComputerPlay);
- while (! *tank)
- *tank = nextturn (global, env, skippingComputerPlay);
- (*tank)->fs = 0;
- }
- nextTankSelected = false;
- }
- if ((!humanPlayers) && (!global->computerPlayersOnly))
- {
- global->iHumanLessRounds--;
-#ifdef DEBUG
- cout << endl << global->iHumanLessRounds << " Rounds left for the bots to play... " << endl;
-#endif // DEBUG
- if ((!global->iHumanLessRounds) && (winner < 1))
- {
-#ifdef DEBUG
- cout << endl << "=======================" << endl;
- cout << "Bots have FINISHED!... " << endl;
- cout << "=======================" << endl << endl;
-#endif // DEBUG
- global->iHumanLessRounds = -1;
- team_won = NO_WIN; // will be determined later
- skippingComputerPlay = FALSE;
- lock_cclock();
- cclock = 0;
- unlock_cclock();
- winner = -2;
- if (global->numTanks > 0)
- {
- // The most healthy player wins
- int iMaxHealth = 1;
- int iHealth = 0;
- for (z = 0; z < global->numPlayers; z++)
- {
-#ifdef DEBUG
- cout << z << ".: \"" << global->players[z]->getName() << "\" ";
-#endif // DEBUG
- if (global->players[z]->tank)
- {
- iHealth = global->players[z]->tank->l + global->players[z]->tank->sh;
- if (iHealth > iMaxHealth)
- {
-#ifdef DEBUG
- cout << "has most health! (" << iHealth << ")" << endl;
-#endif // DEBUG
- iMaxHealth = iHealth;
- winner = z;
- }
- else if (iHealth == iMaxHealth)
- winner = -2; // Equal Health == draw!
-#ifdef DEBUG
- else
- cout << "is too weak! (" << iHealth << " max: " << iMaxHealth << ")" << endl;
-#endif // DEBUG
- }
-#ifdef DEBUG
- else
- cout << "has no tank!" << endl;
-#endif //
- }
-#ifdef DEBUG
- cout << "winner is player " << winner << " - \"";
- if (winner > 0)
- cout << global->players[winner]->getName();
- else
- cout << "draw";
- cout << "\"" << endl;
-#endif // DEBUG
- if (winner > -1)
- {
- if (global->players[winner]->team == TEAM_JEDI)
- {
- team_won = JEDI_WIN;
- winner = JEDI_WIN;
- }
- if (global->players[winner]->team == TEAM_SITH)
- {
- team_won = SITH_WIN;
- winner = SITH_WIN;
- }
- }
- }
-#ifdef DEBUG
- cout << "Final Decision:" << endl;
- if (winner > -1)
- {
- cout << "\"";
- if (winner < 10)
- cout << global->players[winner]->getName();
- if (winner == JEDI_WIN)
- cout << "Team Jedi";
- if (winner == SITH_WIN)
- cout << "Team Sith";
- cout << "\" has won!" << endl;
- }
- else
- cout << "Round Draw!" << endl;
-#endif // DEBUG
- }
- }
- if (winner >= 0 || winner == -2)
- {
- env->stage = STAGE_ENDGAME;
- global->currTank = NULL;
- fi = 1;
- global->window.x = 0;
- global->window.y = 0;
- global->window.w = (global->screenWidth-1);
- global->window.h = (global->screenHeight-1);
- }
- bCount = 0;
- global->updateMenu = 1;
- }
- }
- dclock++;
- if (dclock > 2)
- {
- dclock = 0;
- for (objCount = 0; (ltank = (TANK*)env->getNextOfClass (TANK_CLASS, &objCount)) && ltank; objCount++)
- {
- if (ltank->flashdamage)
- {
- if (ltank->flashdamage > 25 || ltank->l < 1)
- {
- ltank->damage = 0;
- ltank->flashdamage = 0;
- ltank->requireUpdate ();
- }
- }
- }
- }
- env->pclock++;
- if (env->pclock > 10)
- env->pclock = 0;
- if (*tank && !anyTeleporting && !anyLaserFiring)
- {
- // if ((*tank)->player->controlTank() == -1)
- int status = (*tank)->player->controlTank();
- if (status == -1)
- return;
- else if ( (status == -2) && (!humanPlayers) )
- {
- skippingComputerPlay = TRUE;
- }
- }
- else if (global->computerPlayersOnly &&
- ((int)global->skipComputerPlay >= SKIP_HUMANS_DEAD))
- {
- if (env->stage == STAGE_ENDGAME)
- return;
- }
- else if ((keypressed () || mouse_b) && !fi)
- {
- if (keypressed ())
- k = readkey ();
- else
- k = 0;
- if ((env->stage == STAGE_ENDGAME) && (roundEndCount >= WAIT_AT_END_OF_ROUND) &&
- (mouse_b || k >> 8 == KEY_ENTER || k >> 8 == KEY_ESC || k >> 8 == KEY_SPACE))
- return;
- }
- env->mouseclock++;
- if (env->mouseclock > 10)
- env->mouseclock = 0;
- }
- frames++;
- global->stopwindow = 1;
- env->make_update (mouse_x, mouse_y, ((BITMAP *) (global->misc[0]))->w, ((BITMAP *) (global->misc[0]))->h);
- env->make_update (lx, ly, ((BITMAP *) (global->misc[0]))->w, ((BITMAP *) (global->misc[0]))->h);
- global->stopwindow = 0;
- lx = mouse_x;
- ly = mouse_y;
- set_clip_rect (env->db, 0, 0, (global->screenWidth-1), (global->screenHeight-1));
- if (! global->os_mouse) show_mouse (NULL);
- if (global->updateMenu)
- {
- set_clip_rect (env->db, 0, 0, (global->screenWidth-1), MENUHEIGHT - 1);
- drawTopBar (global, env, env->db);
- }
- set_clip_rect (env->db, 0, MENUHEIGHT, (global->screenWidth-1), (global->screenHeight-1));
- if (fi)
- {
- blit (env->sky, env->db, global->window.x, global->window.y - MENUHEIGHT, global->window.x, global->window.y, (global->window.w - global->window.x) + 1, (global->window.h - global->window.y) + 1);
- masked_blit (env->terrain, env->db, global->window.x, global->window.y, global->window.x, global->window.y, (global->window.w - global->window.x) + 1, (global->window.h - global->window.y) + 2);
- }
- else
- {
- env->replaceCanvas ();
- }
-
- for (objCount = 0, count = 0; (ltank = (TANK*)env->getNextOfClass (TANK_CLASS, &objCount)) && ltank; count++, objCount++)
- {
- if (env->stage < STAGE_ENDGAME)
- {
- if (*tank == ltank)
- {
- ltank->draw (env->db, (int)(global->slope[bCount % 360][0] * 4));
- ltank->requireUpdate ();
- }
- else
- {
- ltank->draw (env->db, 0);
- }
- ltank->update ();
- }
- ltank->framelyAccounting ();
- }
-
- objCount = 0;
- my_object = env->objects[objCount];
- while (objCount < MAX_OBJECTS)
- {
- if (my_object)
- {
- my_class = my_object->getClass();
- // for (objCount = 0; (missile = (MISSILE*)env->getNextOfClass (MISSILE_CLASS, &objCount)) && missile; objCount++)
- if (my_class == MISSILE_CLASS)
- {
- missile = (MISSILE *) my_object;
- missile->draw (env->db);
- missile->update ();
- }
- // for (objCount = 0; (beam = (BEAM*)env->getNextOfClass (BEAM_CLASS, &objCount)) && beam; objCount++)
- else if (my_class == BEAM_CLASS)
- {
- beam = (BEAM *) my_object;
- beam->draw (env->db);
- beam->update ();
- }
- // for (objCount = 0; (explosion = (EXPLOSION*)env->getNextOfClass (EXPLOSION_CLASS, &objCount)) && explosion; objCount++)
- else if (my_class == EXPLOSION_CLASS)
- {
- explosion = (EXPLOSION *) my_object;
- explosion->draw (env->db);
- explosion->update ();
- }
- // for (objCount = 0; (teleport = (TELEPORT*)env->getNextOfClass (TELEPORT_CLASS, &objCount)) && teleport; objCount++)
- else if (my_class == TELEPORT_CLASS)
- {
- teleport = (TELEPORT *) my_object;
- if (teleport->object)
- teleport->draw (env->db);
- teleport->update ();
- }
- // for (objCount = 0; (decor = (DECOR*)env->getNextOfClass (DECOR_CLASS, &objCount)) && decor; objCount++)
- else if (my_class == DECOR_CLASS)
- {
- decor = (DECOR *) my_object;
- decor->draw (env->db);
- decor->update ();
- }
- // for (objCount = 0; (floattext = (FLOATTEXT*)env->getNextOfClass (FLOATTEXT_CLASS, &objCount)) && floattext; objCount++)
- else if (my_class == FLOATTEXT_CLASS)
- {
- floattext = (FLOATTEXT *) my_object;
- floattext->draw (env->db);
- floattext->requireUpdate ();
- floattext->update ();
- }
- } // end of if we have an object
- objCount++;
- my_object = env->objects[objCount];
- } // end of going through objects
-
- if (satellite)
- satellite->Draw(env->db);
-
- if (env->stage == STAGE_ENDGAME)
- {
- if (roundEndCount < WAIT_AT_END_OF_ROUND + 1)
- roundEndCount++;
- if (roundEndCount >= WAIT_AT_END_OF_ROUND)
- {
- // check to see if the winner is still alive
- int tank_index = 0;
- int alive = false;
- while ( (! alive) && (tank_index < global->numPlayers) )
- {
- if ( ( global->players[tank_index]->tank )
- &&( global->players[tank_index]->tank->l > 0) )
- {
- alive = true;
- }
- tank_index++;
- }
-
- // moving this below so dead players lose credit
-// if (! alive)
-// {
-// winner = -2;
-// }
-
- if (! alive && bWinnerIsCredited)
- {
- // The score needs to be reduced, of course:
- int count;
- int iTeamCount = 0;
- int iTeamBonus = 0;
- if (winner == JEDI_WIN) // de-credit jedi team
- {
- for (count = 0; count < global->numPlayers; count++)
- {
- if (global->players[count]->team == TEAM_JEDI)
- {
- global->players[count]->score--;
- global->players[count]->won--;
- iTeamCount++;
- }
- }
- }
- else if (winner == SITH_WIN) // de-credit sith team
- {
- for (count = 0; count < global->numPlayers; count++)
- {
- if (global->players[count]->team == TEAM_SITH)
- {
- global->players[count]->score--;
- global->players[count]->won--;
- iTeamCount++;
- }
- }
-
- }
- else if (winner >= 0) // de-credit the (ex-)winner
- {
- global->players[winner]->score--;
- global->players[winner]->won--;
- global->players[winner]->money -= (int)global->scoreRoundWinBonus;
- }
- // If it's a team, take away their money now!
- if (iTeamCount)
- {
- iTeamBonus = (int)(global->scoreRoundWinBonus / iTeamCount);
- for (count = 0; count < global->numPlayers; count++)
- if ( ((winner==JEDI_WIN) && (global->players[count]->team == TEAM_JEDI))
- ||((winner==SITH_WIN) && (global->players[count]->team == TEAM_SITH)) )
- global->players[count]->money -= iTeamBonus;
- }
- winner = -2;
- bWinnerIsCredited = false; // Or it will be substracted on every loop...
- }
-
- if (! alive)
- {
- winner = -2;
- }
-
-
- // if we have a winner, give them credit
- if (alive && (winner >= 0) && (roundEndCount == WAIT_AT_END_OF_ROUND) && !bWinnerIsCredited)
- {
- int count;
- int iTeamCount = 0;
- int iTeamBonus = 0;
- if (winner == JEDI_WIN) // credit jedi team
- {
- for (count = 0; count < global->numPlayers; count++)
- {
- if (global->players[count]->team == TEAM_JEDI)
- {
- global->players[count]->score++;
- global->players[count]->won++;
- iTeamCount++;
- }
- }
- }
- else if (winner == SITH_WIN) // credit sith team
- {
- for (count = 0; count < global->numPlayers; count++)
- {
- if (global->players[count]->team == TEAM_SITH)
- {
- global->players[count]->score++;
- global->players[count]->won++;
- iTeamCount++;
- }
- }
-
- }
- else // credit the winner
- {
- global->players[winner]->score++;
- global->players[winner]->won++;
- global->players[winner]->money += (int)global->scoreRoundWinBonus;
- }
- // If it's a team, do give them their money now!
- if (iTeamCount)
- {
- iTeamBonus = (int)(global->scoreRoundWinBonus / iTeamCount);
- for (count = 0; count < global->numPlayers; count++)
- if ( ((winner==JEDI_WIN) && (global->players[count]->team == TEAM_JEDI))
- ||((winner==SITH_WIN) && (global->players[count]->team == TEAM_SITH)) )
- global->players[count]->money += iTeamBonus;
- }
- bWinnerIsCredited = true;
- }
-
- if ( roundEndCount >= WAIT_AT_END_OF_ROUND )
- showRoundEndScoresAt (global, env, env->db, global->screenWidth/2, global->screenHeight/2, winner);
- // when we get here and it is a demo, we should bail out
- if (global->demo_mode)
- return;
- }
- }
- // This four values are used to reduce access to global and increase readability (Easier to debug this way!)
- int iLeft = 0;
- int iRight = global->screenWidth - 1;
- int iTop = MENUHEIGHT;
- int iBottom = global->screenHeight - 1;
- set_clip_rect (env->db, 0, 0, iRight, iBottom);
- vline(env->db, iLeft, iTop, iBottom, env->wallColour); // Left edge
- vline(env->db, iRight, iTop, iBottom, env->wallColour); // right edge
- hline(env->db, iLeft, iBottom, iRight, env->wallColour);// bottom edge
- if (global->bIsBoxed)
- hline(env->db, iLeft, iTop, iRight, env->wallColour);// top edge
- if (! global->os_mouse) show_mouse (env->db);
- if (fi)
- {
- while (keypressed ())
- {
- readkey ();
- }
- fi = 0;
-
- // if (env->fog) {
- // clear_to_color (screen, makecol (128,128,128));
- // } else {
- quickChange (global, env->db);
- // }
- }
- else
- {
- env->do_updates ();
- global->window.x = global->screenWidth;
- global->window.y = global->screenHeight;
- global->window.w = -1;
- global->window.h = -1;
- }
-#ifdef DEBUG_AIM_SHOW
- if (!global->bASD)
- global->bASD = true; // Now it is allowed to be true
-#endif
- }
}
-#endif // old gameloop
-void print_text_help()
+static void print_text_help()
{
- cout << "-h\tThis screen\n"
- << "-fs\tFull screen\n"
- << "--windowed\tRun in a window\n"
- << "-w <width> or --width <width>\tSpecify the screen width in pixels\n"
- << "-t <height> --tall <height>\tSpecify the screen height in pixels\n"
- << "\tAdjust the screen size at your own risk (default is 800x600)\n"
- << "-d <depth> or --depth <depth>\tCurrently either 16 or 32\n"
- << "--datadir <data directory>\t Path to the data directory\n"
- << "-c <config directory>\t Path to config and saved game directory\n"
- << "--noconfig\t Do not load game settings from the config file.\n"
- << "--nosound\t Disable sound\n"
- << "--noname\t Do not show player name above tank\n"
- << "--nonetwork\t Do not allow the game to accept network connection.\n"
- << "--nobackground\t Do not display the green menu background.\n"
- << "--nothread\t Do not use threads to perform background tasks.\n"
- << "--thread\t Do use threads to perform background tasks.\n";
+ cout << "-h --help Show this help screen\n"
+ << "-fs Full screen\n"
+ << " --windowed Run in a window\n"
+ << "-w --width <width> Specify the screen width in pixels\n"
+ << "-t --tall <height> Specify the screen height in pixels\n"
+ << " Adjust the screen size at your own risk\n"
+ << " (default is 800x600)\n"
+ << "-d --depth <depth> Colour depths, currently either 16 or 32 bit\n"
+ << " --datadir <path> Path to the data directory\n"
+ << "-c <path> Path to config and saved game directory\n"
+ << " --noconfig Do not load game settings.\n"
+ << " --nosound Disable sound\n"
+ << " --noname Do not show player name above tank\n"
+ << " --nonetwork Disable network connections.\n"
+ << " --nobackground Do not display the green menu background."
+ << endl;
}
-void print_text_initmsg()
-{
- printf ( "Atomic Tanks Version %s (-h for help)\n", VERSION);
- printf ( "Authors: \tTom Hudson (rewrite, additions, improvements)\n");
- printf ( "\t\tStevante Software (original design)\n");
- printf ( "\t\tKota543 Software (fixes and updates)\n");
- printf ( "\t\tJesse Smith (additions, fixes and updates)\n");
- printf ( "\t\tSven Eden (ai rewrite, additions, fixes and updates)\n\n");
-
- // putchar ('\n');
-}
-void endgame_cleanup (GLOBALDATA *global, ENVIRONMENT *env)
+static void print_text_initmsg()
{
- while (global->numPlayers > 0)
- {
- if (global->players[0]->tank)
- delete(global->players[0]->tank);
- // make sure networked clients say good-bye and return to old AI level
- if (global->players[0]->type >= NETWORK_CLIENT)
- global->players[0]->type = global->players[0]->previous_type;
-
- global->players[0]->tank = NULL;
- global->removePlayer(global->players[0]);
- }
- for (int objCount = 0; objCount < MAX_OBJECTS; objCount++)
- {
- if (env->objects[objCount])
- {
- delete(env->objects[objCount]);
- env->objects[objCount] = NULL;
- }
- }
+ cout << "Atomic Tanks Version " << VERSION << " (-h for help)\n"
+ << "Authors: Tom Hudson (rewrite, additions, improvements)\n"
+ << " Stevante Software (original design)\n"
+ << " Kota543 Software (fixes and updates)\n"
+ << " Jesse Smith (additions, fixes and updates)\n"
+ << " Sven Eden (ai rewrite, additions, fixes and updates)\n\n"
+ << endl;
}
@@ -5039,1209 +1406,193 @@ the config file name.
The function returns TRUE on success and FALSE on failure.
-- Jesse
*/
-int Save_Game_Settings_Text(GLOBALDATA *global, ENVIRONMENT *env, char *text_file)
+static bool Save_Game_Settings(const char* path)
{
- FILE *my_file;
+ FILE* file = fopen(path, "w");
+ if (!file) {
+ perror ( "Error trying to open text file for writing.\n");
+ return false;
+ }
- my_file = fopen(text_file, (char *)"w");
- if (! my_file)
- {
- perror ( "Error trying to open text file for writing.\n");
- return FALSE;
- }
+ env.save_to_file (file);
- global->saveToFile_Text (my_file);
- env->saveToFile_Text (my_file);
- savePlayers_Text (global, my_file);
- fclose (my_file);
- return TRUE;
-}
+ for (int32_t i = 0; i < env.numPermanentPlayers; ++i)
+ env.allPlayers[i]->save_to_file(file);
-/*
-This function detects changes to the global settings (mouse and sound)
-and, if a change has happened, makes the required changes to the
-game environment.
-The function returns TRUE.
--- Jesse
-*/
-int Change_Settings(double old_mouse, double old_sound, double new_mouse, double new_sound, void *mouse_image)
-{
- BITMAP *my_mouse_image = (BITMAP *) mouse_image;
-
- // first, check for a change in the sound settings
- if (old_sound != new_sound)
- {
- if (new_sound > 0.0) // we turned ON sound
- {
- if (detect_digi_driver(DIGI_AUTODETECT))
- {
- if (install_sound (DIGI_AUTODETECT, MIDI_NONE, NULL) < 0)
- fprintf (stderr, "install_sound: failed turning on sound\n");
- }
- else
- fprintf (stderr, "detect_digi_driver found no sound device\n");
-
-// if (install_sound (DIGI_AUTODETECT, MIDI_NONE, NULL) < 0)
-// {
-// fprintf (stderr, "install_sound: %s", allegro_error);
-// }
- }
- else if (new_sound == 0.0) // we turned OFF sound
- {
- remove_sound();
- }
- }
-
- // check for a change in mouse settings
- if (old_mouse != new_mouse)
- {
- if (new_mouse > 0.0) // use OS cursor
- {
- set_mouse_sprite(NULL);
- show_os_cursor(MOUSE_CURSOR_ARROW);
- }
- else if (new_mouse == 0.0) // use Allgero cursor
- {
- set_mouse_sprite (my_mouse_image);
- set_mouse_sprite_focus (0, 0);
- }
- }
- return TRUE;
+ fclose (file);
+ return true;
}
-
-
-
-
-/*
-This function catches the close command, usually given by
-the user pressing the close window button. We'll
-try to clean-up.
-
-Note: This function causes the app to hang in Windows.
-Make this compile on non-Windows systems only.
-*/
-void close_button_handler(void)
+static void show_options()
{
- // allegro_exit();
- // exit(0);
- my_global->close_button_pressed = true;
-}
+ // save old settings
+ bool temp_sound = env.sound_enabled;
+ int32_t temp_itech = env.itemtechLevel;
+ int32_t temp_wtech = env.weapontechLevel;
-int main (int argc, char **argv)
-{
- int signal;
- int status;
- string tmp;
- ENVIRONMENT *env = NULL;
- GLOBALDATA *global = NULL;
- // ifstream configFile;
- char fullPath[2048];
- bool load_config_file = true;
- bool bLoadingSuccess = false;
- cmdTokens nextToken = ARGV_NOTHING_EXPECTED;
- double temp_mouse, temp_sound; // I wish I was not using these
- int menu_action;
- int playerCount, player_index;
- #ifdef NETWORK
- #ifdef THREADS
- SEND_RECEIVE_TYPE *send_receive = NULL;
- #endif
- int client_socket = -1;
- #endif
- bool allow_network = true, allow_thread = false;
- double music_place_holder;
- double full_screen = FULL_SCREEN_EITHER;
- init_cclock_lock();
-
- quit_right_now = false;
- global = new GLOBALDATA ();
- if (!global)
- {
- perror ( "Allocating global");
- exit (1);
- }
- my_global = global;
- print_text_initmsg();
-
- // try to find data dir
- if (! global->Find_Data_Dir() )
- printf("Could not find data dir.\n");
-
- if (argc >= 2) /* Parse command-line switches */
- {
- for (int argument = 1; argument < argc; argument++)
- {
- tmp = argv[argument];
-
- if (nextToken == ARGV_GFX_DEPTH)
- {
- global->colourDepth = strtol (tmp.c_str(), NULL, 10);
- if (global->colourDepth != 16 &&
- global->colourDepth != 32)
- {
- cout << "Invalid graphics depth, only 16 or 32 are valid\n";
- print_text_help();
- return 0;
- }
- nextToken = ARGV_NOTHING_EXPECTED;
- }
- else if (nextToken == ARGV_SCREEN_WIDTH)
- {
- global->screenWidth = strtol (tmp.c_str(), NULL, 10);
- if (global->screenWidth < 512)
- {
- cout << "Width too small (minimum 512)\n";
- return 0;
- }
- global->width_override = global->screenWidth;
- global->halfWidth = global->screenWidth / 2;
- nextToken = ARGV_NOTHING_EXPECTED;
- }
- else if (nextToken == ARGV_SCREEN_HEIGHT)
- {
- global->screenHeight = strtol (tmp.c_str(), NULL, 10);
- if (global->screenHeight < 320)
- {
- cout << "Height too small (minimum 320)\n";
- return 0;
- }
- global->height_override = global->screenHeight;
- global->halfHeight = global->screenHeight / 2;
- nextToken = ARGV_NOTHING_EXPECTED;
- }
- else if (nextToken == ARGV_DATA_DIR)
- {
- // Would use strndup, but the win compiler
- // doesn't know of it.
- if (strlen (tmp.c_str()) > 2048)
- {
- cout << "Datadir path too long:\n"
- << "\"" << tmp
- << "\"\n\n"
- << "Maximum length 2048 characters\n";
- return 0;
- }
- global->dataDir = strdup (tmp.c_str());
- nextToken = ARGV_NOTHING_EXPECTED;
- }
- else if (nextToken == ARGV_CONFIG_DIR)
- {
- if (strlen (tmp.c_str()) > 2048)
- {
- cout << "Configdir path too long:\n"
- << "\"" << tmp
- << "\"\n\n"
- << "Maximum length 2048 characters\n";
- return 0;
- }
- global->configDir = strdup ( tmp.c_str() );
- nextToken = ARGV_NOTHING_EXPECTED;
- }
- if ( (tmp == SWITCH_HELP) || (tmp == "--help") )
- {
- print_text_help();
- return 0;
- }
- else if (tmp == SWITCH_FULL_SCREEN)
- {
- screen_mode = GFX_AUTODETECT_FULLSCREEN;
- full_screen = FULL_SCREEN_TRUE;
- }
- else if (tmp == SWITCH_WINDOWED)
- {
- screen_mode = GFX_AUTODETECT_WINDOWED;
- full_screen = FULL_SCREEN_FALSE;
- }
- else if (tmp == "-d" || tmp == "--depth")
- {
- nextToken = ARGV_GFX_DEPTH;
- }
- else if (tmp == "-w" || tmp == "--width")
- {
- nextToken = ARGV_SCREEN_WIDTH;
- }
- else if (tmp == "-t" || tmp == "--tall")
- {
- nextToken = ARGV_SCREEN_HEIGHT;
- }
- else if (tmp == "--datadir")
- {
- nextToken = ARGV_DATA_DIR;
- }
- else if (tmp == "-c")
- {
- nextToken = ARGV_CONFIG_DIR;
- }
- else if (tmp == "--noconfig")
- {
- nextToken = ARGV_NOTHING_EXPECTED;
- load_config_file = false;
- }
- else if (tmp == "--nosound")
- {
- nextToken = ARGV_NOTHING_EXPECTED;
- global->sound = 0.0;
- }
- else if (tmp == "--noname")
- {
- nextToken = ARGV_NOTHING_EXPECTED;
- global->name_above_tank = FALSE;
- }
- else if (tmp == "--nonetwork")
- {
- allow_network = false;
- nextToken = ARGV_NOTHING_EXPECTED;
- }
- else if (tmp == "--nobackground")
- {
- global->draw_background = FALSE;
- nextToken = ARGV_NOTHING_EXPECTED;
- }
- else if (tmp == "--nothread")
- {
- allow_thread = false;
- nextToken = ARGV_NOTHING_EXPECTED;
- }
- else if (tmp == "--thread")
- {
- allow_thread = true;
- nextToken = ARGV_NOTHING_EXPECTED;
- }
+ optionsMenu();
- }
- if (nextToken != ARGV_NOTHING_EXPECTED)
- {
- cout << "Expecting an argument to follow " << tmp << endl;
- return 0;
- }
- }
+ if (!Save_Game_Settings(fullPath))
+ cerr << "atanks.cpp:" << __LINE__
+ << " Failed to save game settings from " << __FUNCTION__ << endl;
- if (! global->configDir)
- {
- global->configDir = global->Get_Config_Path();
-
- // copy the file over, if we did not yet
- if (!Copy_Config_File(global))
- {
- // If it did not work, look whether the directory already exists:
- DIR * pDestDir;
- pDestDir = opendir(global->configDir);
- if (!pDestDir)
- printf( "An error has occured trying to set up Atomic Tank folders.\n");
- else
- {
- closedir(pDestDir);
- pDestDir = NULL;
- }
- }
- } // end of no config file on the command line
-
-
-
- memset(fullPath, '\0', sizeof(fullPath));
- FILE *old_config_file = NULL;
- snprintf(fullPath, sizeof(fullPath) - 1, "%s/atanks-config.txt", global->configDir);
- if (load_config_file)
- old_config_file = fopen(fullPath, "r");
- else
- old_config_file = NULL;
- if (old_config_file)
- {
- global->loadFromFile_Text(old_config_file);
- // over-ride full screen setting with command line
- if ( (full_screen == FULL_SCREEN_TRUE) || (full_screen == FULL_SCREEN_FALSE) )
- global->full_screen = full_screen;
- env = init_game_settings(global);
- if (global->os_mouse)
- show_os_cursor(MOUSE_CURSOR_ARROW);
- global->Load_Text_Files();
- env->loadFromFile_Text(old_config_file);
- loadPlayers_Text(global, env, old_config_file);
- fclose(old_config_file);
- bLoadingSuccess = true;
- }
-
-
- if (!bLoadingSuccess) // no config file found or failed to load
- {
- global->numPermanentPlayers = 0;
- if ( (full_screen == FULL_SCREEN_TRUE) || (full_screen == FULL_SCREEN_FALSE) )
- global->full_screen = full_screen;
- env = init_game_settings (global);
- global->Load_Text_Files();
- if (global->os_mouse) show_os_cursor(MOUSE_CURSOR_ARROW);
- char *defaultNames[] =
- {
- "Caesar",
- "Alex",
- "Hatshepsut",
- "Patton",
- "Napoleon",
- "Attila",
- "Catherine",
- "Hannibal",
- "Stalin",
- "Mao"
- };
- PLAYER *tempPlayer;
- tempPlayer = global->createNewPlayer (env);
- tempPlayer->setName ( global->ingame->complete_text[52] );
- options (global, env, (MENUDESC*)tempPlayer->menudesc);
- for (int count = 0; count < 10; count++)
- {
- tempPlayer = global->createNewPlayer (env);
- tempPlayer->type = rand () % (LAST_PLAYER_TYPE - 1) + 1;
- tempPlayer->setName (defaultNames[count]);
- tempPlayer->generatePreferences();
- }
- }
-
- status = Load_Weapons_Text(global);
- if (! status)
- {
- printf( "An error occured trying to read weapons file.\n");
- exit(1);
- }
-
- snprintf(fullPath, sizeof(fullPath) - 1, "%s/atanks-config.txt", global->configDir);
- global->temp_screenWidth = global->screenWidth;
- global->temp_screenHeight = global->screenHeight;
- global->halfWidth = global->screenWidth / 2;
- global->halfHeight = global->screenHeight / 2;
- global->menuBeginY = (global->screenHeight - 400) / 2;
- if (global->menuBeginY < 0) global->menuBeginY = 0;
- global->menuEndY = global->screenHeight - global->menuBeginY;
-
- title (global);
- env->bitmap_filenames = Find_Bitmaps(global, & (env->number_of_bitmaps) );
- env->my_sky_gradients = (const gradient **) sky_gradients;
- env->my_land_gradients = (const gradient **) land_gradients;
- Create_Music_Folder(global);
-#ifdef THREADS
- if (allow_thread)
- {
- pthread_t sky_thread, terrain_thread;
- pthread_create( &sky_thread, NULL, Generate_Sky_In_Background, (void *) env);
- pthread_create( &terrain_thread, NULL, Generate_Land_In_Background, (void *) env);
- } // end of allowing threads
-#endif
-
- // new networking area
- #ifdef THREADS
- #ifdef NETWORK
- pthread_t network_thread;
- if (global->check_for_updates)
- {
- global->update_string = Check_For_Update("projects.sourceforge.net",
- "version.txt", "atanks.sourceforge.net", VERSION);
- }
-
- if ( (global->enable_network) && (allow_network) )
- {
- send_receive = (SEND_RECEIVE_TYPE *) calloc(1, sizeof(SEND_RECEIVE_TYPE));
- if (! send_receive)
- printf("Could not create networking data.\n");
- }
- else
- send_receive = NULL;
- if (send_receive)
- {
- send_receive->listening_port = (int) global->listen_port;
- send_receive->global = global;
- // quit option already cleared by calloc call
- pthread_create( &network_thread, NULL, Send_And_Receive, (void *) send_receive);
- }
- #endif
- #endif
-
- do
- {
- //show the main menu
- global->wr_lock_command();
- global->command = GLOBAL_COMMAND_MENU;
- global->unlock_command();
- signal = menu (global, env);
- if (global->client_message)
- {
- free(global->client_message);
- global->client_message = NULL;
- }
-
- // did the user signal to quit the game
- global->wr_lock_command();
- if (signal == SIG_QUIT_GAME) global->command = GLOBAL_COMMAND_QUIT;
- global->unlock_command();
-
- //determine which menu item is selected
- switch (global->get_command())
- {
- case GLOBAL_COMMAND_HELP:
- scrollTextList (global, env, global->instructions);
- break;
- case GLOBAL_COMMAND_OPTIONS:
- // save old settings
- temp_mouse = global->os_mouse;
- temp_sound = global->sound;
-
- options(global, env, NULL);
- if (! Save_Game_Settings_Text(global, env, fullPath))
- {
- perror ( "atanks.cpp: Failed to save game settings from atanks::main()!");
- }
-
- // check for changes to settings
- Change_Settings(temp_mouse, temp_sound, global->os_mouse, global->sound, global->misc[0]);
- global->Change_Font();
- global->Update_Player_Menu();
- break;
- case GLOBAL_COMMAND_PLAYERS:
- //loop until done editing players where return value is not ESC
- do
- {
- status = editPlayers(global, env);
- }
- while ( (status != KEY_ESC << 8) && (status != KEY_ENTER << 8) );
- break;
- case GLOBAL_COMMAND_CREDITS:
- credits(global, env);
- break;
- case GLOBAL_COMMAND_QUIT:
- break;
- case GLOBAL_COMMAND_NETWORK:
- #ifdef NETWORK
- client_socket = Setup_Client_Socket(global->server_name, global->server_port);
- if (client_socket >= 0)
- {
- int keep_playing = TRUE;
- printf("Ready to play networked\n");
- while (keep_playing)
- {
- keep_playing = Game_Client(global, env, client_socket);
- }
- Clean_Up_Client_Socket(client_socket);
- }
- else
- printf("Unable to connect to server %s, port %s.\n", global->server_name, global->server_port);
- #else
- printf("This version of Atanks is not compiled to handle network games.\n");
- #endif
- break;
- case GLOBAL_COMMAND_DEMO:
- global->demo_mode = true;
- global->load_game = false;
- music_place_holder = global->play_music;
- global->play_music = 0.0;
-
- // set up a bunch of players (non-human, less than 10)
- playerCount = 0;
- global->numPlayers = 0;
- for (player_index = 0; player_index < global->numPermanentPlayers; player_index++)
- {
- if ( (global->allPlayers[player_index]->type > HUMAN_PLAYER) && (playerCount < MAXPLAYERS) )
- {
- global->addPlayer (global->allPlayers[player_index]);
- playerCount++;
- }
- }
-
- player_index = (int) global->skipComputerPlay;
- global->skipComputerPlay = SKIP_NONE;
- global->currentround = 1; // might add more later
- while ( (global->currentround > 0) && (! global->close_button_pressed) )
- {
- game(global, env);
- if ((global->get_command() == GLOBAL_COMMAND_QUIT) || (global->get_command() == GLOBAL_COMMAND_MENU))
- break;
- global->currentround--;
- // skipping winner screen for now
- }
- endgame_cleanup(global, env);
- global->demo_mode = false;
- global->skipComputerPlay = player_index;
- global->play_music = music_place_holder;
- break;
-
- default: //must have commanded to play game
- menu_action = selectPlayers(global, env);
- if (menu_action == ESC_MENU)
- break;
-
- //selected players, start new game
- else
- {
- // make sure the game has a name
- if (! global->game_name[0])
- strcpy(global->game_name,global->ingame->complete_text[53] );
-
- newgame (global, env);
-
- // play the game for the selected number of rounds
- // for (global->currentround = (int)global->rounds; global->currentround > 0; global->currentround--)
- if (! global->load_game) global->currentround = (int) global->rounds;
- while ( (global->currentround > 0) && (! global->close_button_pressed) )
- {
- game (global, env); // play a round
- if (global->background_music)
- {
- stop_sample(global->background_music);
- destroy_sample(global->background_music);
- global->background_music = NULL;
- }
-
- // if user selected to quit or return to main menu during game play
- if ((global->get_command() == GLOBAL_COMMAND_QUIT) || (global->get_command() == GLOBAL_COMMAND_MENU))
- {
- #ifdef NETWORK
- global->Send_To_Clients("CLOSE");
- #endif
- break;
- }
-
- global->currentround--;
- #ifdef NETWORK
- if (global->currentround != 0) // end of the round
- global->Send_To_Clients("ROUNDEND");
- #endif
- }
-
- // only show winner if finished all rounds
- if ( (global->currentround == 0) && (! global->close_button_pressed) )
- {
- char buffer[256], *my_player;
-
- // strcpy(buffer, "GAMEEND");
- // global->Send_To_Clients(buffer);
- my_player = do_winner (global, env);
- if (my_player)
- {
- snprintf(buffer, 256, "GAMEEND The game went to %s.", my_player);
- free(my_player);
- }
- else
- strcpy(buffer, "GAMEEND");
- #ifdef NETWORK
- global->Send_To_Clients(buffer);
- #endif
- do_quote(global, env);
- }
-
- endgame_cleanup (global, env);
- } // end of start new game
-
- break;
-
- } // end of menu switch
-
- }
- while ( (global->get_command() != GLOBAL_COMMAND_QUIT) );
-
- // print out if there is an update
- if ( (global->update_string) && (global->update_string[0]) )
- {
- cout << global->update_string << endl;
- free(global->update_string);
- global->update_string = NULL;
- }
-
- #ifdef THREADS
- #ifdef NETWORK
- if (send_receive)
- {
- send_receive->shut_down = TRUE;
- // sleep(1);
- LINUX_REST;
- // we should probably wait and do a join here, but if the network thread does not
- // finish after a full second, then something has gone wrong anyway and we should move on....
- free(send_receive);
- }
- #endif
- #endif
-
- if (! Save_Game_Settings_Text(global, env, fullPath))
- {
- // This is a very critical issue, but as we are ending here, we just report it
- perror ( "atanks.cpp: Failed to save game settings from atanks::main()!");
- allegro_exit ();
- cout << "See http://atanks.sourceforge.net for the latest news and downloads." << endl;
-
- return(1);
- }
- else
- {
- Save_Game_Settings_Text(global, env, fullPath);
- allegro_exit ();
- cout << "See http://atanks.sourceforge.net for the latest news and downloads." << endl;
-
- return(0);
- }
+ // check for changes to settings
+ Change_Settings(temp_sound, temp_itech, temp_wtech);
}
-END_OF_MAIN ()
-
-
-
-
-/*
-Adding function here to avoid patch incompatibilties.
-This function should launch all items from all of
-the living tanks.
-*/
-void doLaunch(GLOBALDATA *global, ENVIRONMENT *env)
+static void title()
{
- // If we're in simultaneous mode, launch all selections
- int i;
- int savestage = env->stage;
- TANK *tank;
-
- if (global->turntype != TURN_SIMUL)
- return;
-
- for (i = 0; i < global->maxNumTanks; i++)
- {
- tank = env->order[i];
- if (tank)
- {
- if (! tank->player->skip_me)
- tank->activateCurrentSelection();
- else
- tank->player->skip_me = false;
- tank->player->time_left_to_fire = global->max_fire_time;
- }
- }
-
- env->stage = savestage;
+ SHOW_MOUSE(nullptr)
+ blit (env.title[0], screen, 0, 0,
+ env.halfWidth - (env.title[0]->w / 2),
+ env.halfHeight - (env.title[0]->h / 2),
+ env.title[0]->w, env.title[0]->h);
+ clear_keybuf ();
}
-// load a colour from a file
-bool popColo(int &aData, ifstream &ifsFile)
+int32_t main (int32_t argc, char** argv)
{
- bool bResult = false;
- if (ifsFile.is_open())
- {
- int iColor = 0;
- ifsFile >> iColor; // reads the next found number
- if (ifsFile.good())
- {
- // Now transform to color:
- aData = makecol((iColor & 0x00ff0000) >> 16,
- (iColor & 0x0000ff00) >> 8,
- iColor & 0x000000ff );
- bResult = true;
- }
- }
- return bResult;
-}
+ print_text_initmsg();
+ // Parse arguments and exit early if needed
+ int32_t result = parse_args(argc, argv);
-int *Sort_Scores(GLOBALDATA *global)
-{
- static int order[MAXPLAYERS];
- int counter;
- bool made_change = true;
- int temp;
-
- for (counter = 0; counter < global->numPlayers; counter++)
- order[counter] = counter;
-
- // bubble sort
- while (made_change)
- {
- made_change = false;
- counter = 0;
- // check for swap
- while (counter < (global->numPlayers - 1) )
- {
- if ( global->players[ order[counter] ]->score < global->players[ order[counter + 1] ]->score )
- {
- temp = order[counter];
- order[counter] = order[counter + 1];
- order[counter + 1] = temp;
- made_change = true;
- }
-
- counter++;
- } // end of check for swaps
- } // end of bubble sort
-
- return order;
-}
+ if (EXIT_FAILURE == result)
+ return EXIT_FAILURE;
+ if (HELP_REQUESTED == result)
+ return EXIT_SUCCESS;
+
+ // try to find data dir
+ if (! env.find_data_dir() ) {
+ cerr << "ERROR: Could not find data dir." << endl;
+ return EXIT_FAILURE;
+ }
+ // try to find config dir
+ env.find_config_dir();
+ // load or create a configuration
+ if (!loadConfig())
+ createConfig();
+ // Load game files
+ if (!env.loadGameFiles())
+ return EXIT_FAILURE; // message already out
-// Client version of the game
-// Really, this loop should do some basic things.
-// 1. Find out what the landscape should look like from the server.
-// 2. Place tanks on the battle field
-// 3. Create missiles, beam weapons and such when the server asks us to
-// 4. Get input from the player and forward it to the server.
-// 5. Clean up at the end of the round.
-//
-// Function return TRUE if everything went well or FALSE
-// if an error occured.
+ // new networking area
#ifdef NETWORK
+ SEND_RECEIVE_TYPE* send_receive = nullptr;
+ std::thread* network_thread = nullptr;
+
+ // Create the update checker thread:
+ update_data updateData("projects.sourceforge.net", "version.txt",
+ "atanks.sourceforge.net", VERSION);
+
+ std::thread updateThread(std::ref(updateData));
+ if (env.check_for_updates)
+ global.update_string = updateData.update_string;
+
+ // Initialize network if allowed and wanted
+ if ( env.network_enabled && allow_network ) {
+ send_receive = (SEND_RECEIVE_TYPE*)calloc(1, sizeof(SEND_RECEIVE_TYPE));
+ if (!send_receive)
+ cerr << "ERROR: Could not create networking data." << endl;
+ }
+
+ // If a SEND_RECEIVE_TYPE instance was created, start the networking thread
+ if (send_receive) {
+ send_receive->listening_port = env.network_port;
+
+ // quit option already cleared by calloc call
+ network_thread = new std::thread(Send_And_Receive, send_receive);
+ }
+#endif // NETWORK
+
+ /* ===============================
+ * === The real main main loop ===
+ * ===============================
+ */
+ do {
+
+ //show the main menu
+ global.set_command(GLOBAL_COMMAND_MENU);
+ int32_t signal = menu ();
+
+ // Ensure a clean client message
+ if (global.client_message) {
+ free(const_cast<char*>(global.client_message));
+ global.client_message = nullptr;
+ }
+
+ // did the user signal to quit the game?
+ if ( (signal == SIG_QUIT_GAME)
+ || global.isCloseBtnPressed() )
+ global.set_command(GLOBAL_COMMAND_QUIT);
+
+ // determine which menu item is selected
+ switch (global.get_command()) {
+ case GLOBAL_COMMAND_HELP:
+ scrollTextList (env.instructions);
+ break;
+ case GLOBAL_COMMAND_OPTIONS:
+ show_options();
+ break;
+ case GLOBAL_COMMAND_PLAYERS:
+ editPlayers();
+ break;
+ case GLOBAL_COMMAND_CREDITS:
+ credits();
+ break;
+ case GLOBAL_COMMAND_QUIT:
+ // Already handled by while condition below
+ break;
+ case GLOBAL_COMMAND_NETWORK:
+ play_networked();
+ break;
+ case GLOBAL_COMMAND_DEMO:
+ play_demo();
+ break;
+ default:
+ //must have commanded to play game
+ play_local();
+ break;
+ } // end of menu switch
+ } while ( (global.get_command() != GLOBAL_COMMAND_QUIT) );
+
+ // print out if there is an update
+ if ( (global.update_string) && (global.update_string[0]) ) {
+ cout << global.update_string << endl;
+ global.update_string = nullptr;
+ }
+
+ // Clean up network stuff
+#ifdef NETWORK
+ if (send_receive) {
+ send_receive->shut_down = TRUE;
+ LINUX_REST;
+ network_thread->join();
+ delete network_thread;
+ free(send_receive);
+ }
+ updateThread.join();
+#endif // NETWORK
-int Game_Client(GLOBALDATA *global, ENVIRONMENT *env, int socket_number)
-{
- int surface_x = 1, tank_position = 1, team_number = 1, name_number = 1;
- int weapon_number = 1, item_number = 1, tank_health = 1;
- int end_of_round = FALSE, keep_playing = FALSE;
- int game_stage = CLIENT_VERSION;
- char buffer[BUFFER_SIZE];
- int incoming;
- int my_key;
- int time_clock = 0;
- int screen_update = FALSE;
- int count; // generic counter
- int stuff_going_down = FALSE; // explosions, missiles etc on the screen
- VIRTUAL_OBJECT *my_object;
- BEAM *beam;
- EXPLOSION *explosion;
- MISSILE *missile;
- TELEPORT *teleport;
- FLOATTEXT *floattext;
- int my_class, object_count;
- bool fired = false;
-
-
- global->dMaxVelocity = (double)MAX_POWER * (100.0 / (double)global->frames_per_second) / 100.0;
- clear_to_color (env->terrain, PINK); // get terrain ready
- clear_to_color(env->db, BLACK);
-
- // clean up old text
- for (count = 0; (floattext = (FLOATTEXT*)env->getNextOfClass (FLOATTEXT_CLASS, &count)) && floattext; count++)
- {
- floattext->newRound();
- delete floattext;
+ if (! Save_Game_Settings(fullPath)) {
+ // This is a very critical issue, but as we are ending here, we just report it
+ cerr << "atanks.cpp: Failed to save game settings from atanks::main()!" << endl;
+ result = EXIT_FAILURE;
}
- Create_Sky(env, global); // so we have a background
- strcpy(buffer, "VERSION");
- write(socket_number, buffer, strlen(buffer));
-
- while (! end_of_round)
- {
- // check for waiting input from the server
- incoming = Check_For_Incoming_Data(socket_number);
- if (incoming)
- {
- int bytes_read;
-
- memset(buffer, '\0', BUFFER_SIZE);
- bytes_read = read(socket_number, buffer, BUFFER_SIZE);
- if (bytes_read > 0)
- {
- // do something with this input
- if (! strncmp(buffer, "CLOSE", 5) )
- {
- end_of_round = TRUE;
- keep_playing = FALSE;
- printf("Got close message.\n");
- global->client_message = global->ingame->complete_text[81];
- }
- else if (! strncmp(buffer, "NOROOM", 6) )
- {
- end_of_round = TRUE;
- keep_playing = FALSE;
- printf("The server is full or the game has not started. Please try again later.\n");
- global->client_message = global->ingame->complete_text[80];
- }
- else if (! strncmp(buffer, "GAMEEND", 7) )
- {
- end_of_round = TRUE;
- keep_playing = FALSE;
- printf("The game is over.\n");
- if ( strlen(buffer) > 7)
- global->client_message = strdup(& (buffer[8])) ;
- else
- global->client_message = strdup(global->ingame->complete_text[82]);
- }
- else if (! strncmp(buffer, "ROUNDEND", 8) )
- {
- end_of_round = TRUE;
- keep_playing = TRUE;
- printf("Round is over.\n");
- }
-
- else // not a special command, parse it
- {
- if ( Parse_Client_Data(global, env, buffer) )
- {
- if (game_stage < CLIENT_PLAYING)
- game_stage++;
-
- // Request more information
- if (game_stage < CLIENT_PLAYING)
- {
- switch (game_stage)
- {
- case CLIENT_SCREEN: strcpy(buffer, "SCREEN"); break;
- case CLIENT_WIND: strcpy(buffer, "WIND"); break;
- case CLIENT_NUMPLAYERS: strcpy(buffer, "NUMPLAYERS"); break;
- case CLIENT_TANK_POSITION: strcpy(buffer, "TANKPOSITION 0"); break;
- case CLIENT_SURFACE: strcpy(buffer, "SURFACE 0"); break;
- case CLIENT_WHOAMI: strcpy(buffer, "WHOAMI");
- screen_update = TRUE; break;
- case CLIENT_WEAPONS: strcpy(buffer, "WEAPON 0"); break;
- case CLIENT_ITEMS: strcpy(buffer, "ITEM 0"); break;
- case CLIENT_ROUNDS: strcpy(buffer, "ROUNDS"); break;
- case CLIENT_TEAMS: strcpy(buffer, "TEAMS 0");
- global->updateMenu = TRUE; break;
- case CLIENT_WALL_TYPE: strcpy(buffer, "WALLTYPE"); break;
- case CLIENT_BOXED: strcpy(buffer, "BOXED"); break;
- case CLIENT_NAME: strcpy(buffer, "PLAYERNAME 0"); break;
- case CLIENT_TANK_HEALTH: strcpy(buffer, "HEALTH 0"); break;
- default: buffer[0] = '\0';
- }
- write(socket_number, buffer, strlen(buffer));
- } // end of getting more info
- } // our game stage went up
- else // we got data, but our game stage did not go up
- {
- if (fired)
- {
- if ( (global->client_player) && (global->client_player->tank) )
- {
- fired = false;
- if (global->client_player->tank->cw < WEAPONS)
- sprintf(buffer, "WEAPON %d", global->client_player->tank->cw);
- else
- sprintf(buffer, "ITEM %d", global->client_player->tank->cw - WEAPONS);
- write(socket_number, buffer, strlen(buffer));
- }
- }
- else if (game_stage == CLIENT_SURFACE)
- {
- sprintf(buffer, "SURFACE %d", surface_x);
- write(socket_number, buffer, strlen(buffer));
- surface_x++;
- }
- else if (game_stage == CLIENT_ITEMS)
- {
- sprintf(buffer, "ITEM %d", item_number);
- write(socket_number, buffer, strlen(buffer));
- item_number++;
- }
- else if (game_stage == CLIENT_TANK_POSITION)
- {
- sprintf(buffer, "TANKPOSITION %d", tank_position);
- write(socket_number, buffer, strlen(buffer));
- tank_position++;
- if (tank_position >= global->numPlayers)
- tank_position = 0;
- }
- else if (game_stage == CLIENT_TANK_HEALTH)
- {
- sprintf(buffer, "HEALTH %d", tank_health);
- write(socket_number, buffer, strlen(buffer));
- tank_health++;
- if (tank_health >= global->numPlayers)
- tank_health = 0;
- }
- else if (game_stage == CLIENT_TEAMS)
- {
- sprintf(buffer, "TEAMS %d", team_number);
- write(socket_number, buffer, strlen(buffer));
- team_number++;
- }
- else if (game_stage == CLIENT_NAME)
- {
- sprintf(buffer, "PLAYERNAME %d", name_number);
- write(socket_number, buffer, strlen(buffer));
- name_number++;
- }
- else if (game_stage == CLIENT_WEAPONS)
- {
- sprintf(buffer, "WEAPON %d", weapon_number);
- write(socket_number, buffer, strlen(buffer));
- weapon_number++;
- }
- else if (game_stage == CLIENT_PLAYING)
- {
- time_clock++;
- if (time_clock > 1) // check positions every few inputs
- {
- time_clock = 0;
- if (surface_x < global->screenWidth)
- {
- game_stage = CLIENT_SURFACE;
- sprintf(buffer, "SURFACE %d", surface_x);
- write(socket_number, buffer, strlen(buffer));
- surface_x++;
- }
- else
- {
- game_stage = CLIENT_TANK_POSITION;
- tank_position = 1;
- strcpy(buffer, "TANKPOSITION 0");
- write(socket_number, buffer, strlen(buffer));
- } // game stage stuff
- }
- } // end of playing commands
- }
-
- } // end of we got something besides the close command
-
- }
- else // connection was broken
- {
- close(socket_number);
- printf("Server closed connection.\n");
- end_of_round = TRUE;
- }
- }
+ env.destroy();
+ global.destroy();
- object_count = 0;
- while (object_count < MAX_OBJECTS)
- {
- my_object = env->objects[object_count];
- if (my_object)
- {
- my_class = my_object->getClass();
- if (my_class == BEAM_CLASS)
- {
- beam = (BEAM *) my_object;
- beam->applyPhysics();
- if (beam->destroy)
- {
- beam->requireUpdate();
- beam->update();
- delete beam;
- }
- stuff_going_down = TRUE;
- }
- else if (my_class == MISSILE_CLASS)
- {
- missile = (MISSILE *) my_object;
- missile->hitSomething = 0;
- missile->applyPhysics();
- missile->triggerTest();
- if (missile->destroy)
- {
- missile->requireUpdate();
- missile->update();
- delete missile;
- }
- stuff_going_down = TRUE;
- }
- else if (my_class == EXPLOSION_CLASS)
- {
- explosion = (EXPLOSION *) my_object;
- explosion->explode ();
- explosion->applyPhysics ();
- if (explosion->destroy)
- {
- explosion->requireUpdate ();
- explosion->update ();
- delete explosion;
- }
- stuff_going_down = TRUE;
- }
- else if (my_class == TELEPORT_CLASS)
- {
- teleport = (TELEPORT *) my_object;
- teleport->applyPhysics ();
- if (teleport->destroy)
- {
- teleport->requireUpdate ();
- teleport->update ();
- delete teleport;
- time_clock = 2;
- }
- stuff_going_down = TRUE;
- }
- else if (my_class == FLOATTEXT_CLASS)
- {
- floattext = (FLOATTEXT *) my_object;
- floattext->applyPhysics ();
- if (floattext->destroy)
- {
- floattext->requireUpdate();
- floattext->update();
- delete floattext;
- }
- }
- else if (my_class == DECOR_CLASS)
- {
- DECOR *decor = (DECOR *) my_object;
- decor->applyPhysics ();
- if (decor->destroy)
- {
- decor->requireUpdate ();
- decor->update ();
- delete decor;
- }
- }
- } // end of got valid object
- object_count++;
- }
+ allegro_exit();
- slideLand(global, env);
+ cout << "See http://atanks.sourceforge.net for the latest news and downloads." << endl;
- // update everything on the screen
- if (global->updateMenu)
- {
- set_clip_rect (env->db, 0, 0, (global->screenWidth-1), MENUHEIGHT - 1);
- drawTopBar (global, env, env->db);
- }
-
- set_clip_rect (env->db, 0, MENUHEIGHT, (global->screenWidth-1), (global->screenHeight-1));
- if (screen_update)
- {
- blit (env->sky, env->db, global->window.x, global->window.y - MENUHEIGHT, global->window.x, global->window.y, (global->window.w - global->window.x) + 1, (global->window.h - global->window.y) + 1);
- masked_blit (env->terrain, env->db, global->window.x, global->window.y, global->window.x, global->window.y, (global->window.w - global->window.x) + 1, (global->window.h - global->window.y) + 2);
- drawTopBar(global, env, env->db);
- int iLeft = 0;
- int iRight = global->screenWidth - 1;
- int iTop = MENUHEIGHT;
- int iBottom = global->screenHeight - 1;
- set_clip_rect (env->db, 0, 0, iRight, iBottom);
- vline(env->db, iLeft, iTop, iBottom, env->wallColour); // Left edge
- vline(env->db, iRight, iTop, iBottom, env->wallColour); // right edge
- hline(env->db, iLeft, iBottom, iRight, env->wallColour);// bottom edge
- if (global->bIsBoxed)
- hline(env->db, iLeft, iTop, iRight, env->wallColour);// top edge
-
- env->make_update(0, 0, global->screenWidth, global->screenHeight);
- }
- else
- {
- env->replaceCanvas ();
- }
-
- for (count = 0; count < global->numPlayers; count++)
- {
- if ( (global->players[count]) && (global->players[count]->tank) )
- {
- global->players[count]->tank->drawTank(env->db, 0);
- global->players[count]->tank->update();
- }
- }
- // env->do_updates();
- screen_update = TRUE;
-
- object_count = 0;
- while (object_count < MAX_OBJECTS)
- {
- my_object = env->objects[object_count];
- if (my_object)
- {
- my_class = my_object->getClass();
-
- if (my_class == BEAM_CLASS)
- {
- beam = (BEAM *) my_object;
- beam->draw (env->db);
- beam->update ();
- }
- else if (my_class == MISSILE_CLASS)
- {
- missile = (MISSILE *) my_object;
- missile->draw(env->db);
- missile->update();
- }
- else if (my_class == EXPLOSION_CLASS)
- {
- explosion = (EXPLOSION *) my_object;
- explosion->draw (env->db);
- explosion->update ();
- }
- else if (my_class == TELEPORT_CLASS)
- {
- teleport = (TELEPORT *) my_object;
- if (teleport->object)
- teleport->draw (env->db);
- teleport->update ();
- }
- else if (my_class == DECOR_CLASS)
- {
- DECOR *decor = (DECOR *) my_object;
- decor->draw(env->db);
- decor->update();
- }
- else if (my_class == FLOATTEXT_CLASS)
- {
- floattext = (FLOATTEXT *) my_object;
- floattext->draw (env->db);
- floattext->requireUpdate ();
- floattext->update ();
- }
- } // end of valid object
- object_count++;
- } // end of while updating objects
- env->do_updates();
-
-
- // check for input from the user
- if ( keypressed() )
- {
- my_key = readkey();
- my_key = my_key >> 8;
- if (my_key == KEY_SPACE)
- {
- Client_Fire(global->client_player, socket_number);
- fired = true;
- }
- else if (my_key == KEY_ESC)
- {
- end_of_round = TRUE;
- close(socket_number);
- }
- else if (my_key == KEY_UP)
- {
- Client_Power(global->client_player, CLIENT_UP);
- }
- else if (my_key == KEY_DOWN)
- {
- Client_Power(global->client_player, CLIENT_DOWN);
- }
- else if (my_key == KEY_LEFT)
- {
- Client_Angle(global->client_player, CLIENT_LEFT);
- }
- else if (my_key == KEY_RIGHT)
- {
- Client_Angle(global->client_player, CLIENT_RIGHT);
- }
- else if ( (my_key == KEY_Z) || (my_key == KEY_BACKSPACE) )
- {
- Client_Cycle_Weapon(global->client_player, CYCLE_BACK);
- }
- else if ( (my_key == KEY_C) || (my_key == KEY_TAB) )
- {
- Client_Cycle_Weapon(global->client_player, CYCLE_FORWARD);
- global->updateMenu = TRUE;
- }
-
- screen_update = FALSE;
- global->updateMenu = TRUE;
- }
-
- // pause for a moment
- // if (game_stage < CLIENT_PLAYING)
- if (stuff_going_down)
- {
- LINUX_SLEEP;
- stuff_going_down = FALSE;
- }
- }
-
- // we should clean up here
- for (count = 0; count < global->numPlayers; count++)
- {
- if (global->players[count]->tank)
- {
- delete global->players[count]->tank;
- global->players[count]->tank = NULL;
- }
- }
-
- return keep_playing;
+ return result;
}
-
-#endif
-
+END_OF_MAIN()
diff --git a/src/atanks.rc b/src/atanks.rc
old mode 100644
new mode 100755
index 52a074d..96a03bf
--- a/src/atanks.rc
+++ b/src/atanks.rc
@@ -1,37 +1,96 @@
-/* THIS FILE WILL BE OVERWRITTEN BY DEV-C++ */
-/* DO NOT EDIT! */
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+/////////////////////////////////////////////////////////////////////////////
+// Deutsch (Deutschland) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DEU)
+LANGUAGE LANG_GERMAN, SUBLANG_GERMAN
+#pragma code_page(1252)
-#include <windows.h> // include for version info constants
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
-A ICON MOVEABLE PURE LOADONCALL DISCARDABLE "../atanks.ico"
-ALLEGRO_ICON ICON "../atanks.ico"
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+A ICON "../atanks.ico"
+ALLEGRO_ICON ICON "../atanks.ico"
+/////////////////////////////////////////////////////////////////////////////
//
-// TO CHANGE VERSION INFORMATION, EDIT PROJECT OPTIONS...
+// Version
//
+
1 VERSIONINFO
-FILEVERSION 3,2,0,24
-PRODUCTVERSION 3,2,0,24
-FILETYPE VFT_APP
-{
- BLOCK "StringFileInfo"
- {
- BLOCK "100904E4"
- {
- VALUE "CompanyName", ""
- VALUE "FileVersion", ""
- VALUE "FileDescription", "A fun tank game, which plays like Scorched Earth."
- VALUE "InternalName", ""
- VALUE "LegalCopyright", "See credits.txt and COPYING.txt"
- VALUE "LegalTrademarks", ""
- VALUE "OriginalFilename", "atanks.exe"
- VALUE "ProductName", "Atomic Tanks"
- VALUE "ProductVersion", ""
- }
- }
- BLOCK "VarFileInfo"
- {
- VALUE "Translation", 0x1009, 1252
- }
-}
+ FILEVERSION 6,1,2,0
+ PRODUCTVERSION 6,1,2,0
+ FILEFLAGSMASK 0x0L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "FileDescription", "Atomic Tanks"
+ VALUE "FileVersion", "6.1.2.0"
+ VALUE "LegalCopyright", "See credits.txt and COPYING.txt"
+ VALUE "OriginalFilename", "atanks.exe"
+ VALUE "ProductName", "Atomic Tanks"
+ VALUE "ProductVersion", "6.1.2.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // Deutsch (Deutschland) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
diff --git a/src/beam.cpp b/src/beam.cpp
index c7e768e..4f98566 100644
--- a/src/beam.cpp
+++ b/src/beam.cpp
@@ -24,263 +24,490 @@
#include "decor.h"
#include "tank.h"
#include "beam.h"
+#include "explosion.h"
+#include "sound.h"
+#include <cassert>
-BEAM::~BEAM ()
+
+// Static helper for the drawing methods
+static int32_t beamRadius = 1;
+static int32_t beamSeed = 0;
+
+// Helper methods for the drawing methods
+static void lazerPoint (BITMAP *dest, int32_t x1, int32_t y1, int32_t color);
+static void lightningPoint(BITMAP *dest, int32_t x1, int32_t y1, int32_t age);
+
+
+/// @brief BEAM constructor
+BEAM::BEAM (PLAYER* player_, double x_, double y_, int32_t fireAngle,
+ int32_t weaponType, eBeamType beam_type) :
+ PHYSICAL_OBJECT(BT_WEAPON == beam_type),
+ beamType (beam_type),
+ tgtRightX (env.screenWidth)
{
- requireUpdate();
- update();
- _env->make_bgupdate (_current.x, _current.y, _current.w, _current.h);
- _env->make_bgupdate (_old.x, _old.y, _old.w, _old.h);
- _env->removeObject (this);
- weap = NULL;
- _global = NULL;
- _env = NULL;
- delete [] points;
+ this->player = player_;
+ this->weapType = weaponType;
+
+ assert( ( ((weapType >= SML_LIGHTNING) && (weapType <= LRG_LIGHTNING))
+ ||((weapType >= SML_LAZER) && (weapType <= LRG_LAZER)) )
+ && "ERROR: BEAM ctor called with something else than Lightning or Laser!");
+
+#ifdef NETWORK
+ char buffer[256];
+ sprintf(buffer, "BEAM %d %d %d %d", (int) x_, (int) y_, fireAngle, weaponType);
+ env.sendToClients(buffer);
+#endif // NETWORK
+
+ x = x_;
+ y = y_;
+ angle = fireAngle % 360;
+ xv = env.slope[angle][0];
+ yv = env.slope[angle][1];
+
+
+ if (weapType < WEAPONS) weap = &(weapon[weapType]);
+ else weap = &(naturals[weapType - WEAPONS]);
+ radius = weap->radius;
+
+ /* All beams should have the same age, no matter what the FPS settings
+ * are.
+ * Based on the default of 60 frames per second, the following frame
+ * lengths are wanted:
+ * Small lightning : 5 frames ( 1/12 second)
+ * Medium lightning: 10 frames ( 1/ 6 second)
+ * Large lightning : 15 frames ( 3/12 second)
+ * Lightning increase : 5 frames per size = 1/12 FPS
+ * Small laser : 10 frames ( 1/6 second)
+ * Medium lightning: 20 frames ( 1/3 second)
+ * Large lightning : 30 frames ( 1/2 second)
+ * Laser increase : 10 frames per size = 1/6 FPS
+ */
+ int32_t age_per_size = env.frames_per_second / 12; // Doubled for laser
+ int32_t base_age = 5; // Doubled for laser
+ int32_t weap_size = 0; // aka "small"
+
+ if ( (weapType >= SML_LIGHTNING) && (weapType <= LRG_LIGHTNING) ) {
+ numPoints = 4 + (rand () % 9); // 4 - 12
+ weap_size = weapType - SML_LIGHTNING;
+ } else if ( (weapType >= SML_LAZER) && (weapType <= LRG_LAZER) ) {
+ base_age *= 2;
+ age_per_size *= 2;
+ numPoints = 2;
+ weap_size = weapType - SML_LAZER;
+ if (BT_SDI != beamType)
+ // The SDI constructor produces its own color
+ color = makecol(255 - ((weapType - SML_LAZER) * 64),
+ 128,
+ 64 + ((weapType - SML_LAZER) * 64));
+ if ( !global.skippingComputerPlay
+ && ( (BT_WEAPON == beamType) || (BT_SDI == beamType) ) )
+ play_fire_sound(weapType, x, 128 + (radius * 10), 1500 - (radius * 50));
+ }
+
+ maxAge = base_age + (age_per_size * weap_size);
+ damage = static_cast<double>(weap->damage) / static_cast<double>(maxAge);
+
+ // Set an offset seed
+ seed = rand() % std::max(env.screenWidth, env.screenHeight);
+
+ createBeamPath();
+
+ // Now that the points are clear, a lightning bolt can emit its thunder:
+ if ( !global.skippingComputerPlay
+ && (BT_NATURAL == beamType) )
+ play_natural_sound(weapType, (points[0].x + points[numPoints - 1].x) / 2,
+ 175 + (radius * 10), 1000);
+
+ // Add to the chain unless it is a mind shot:
+ if (BT_MIND_SHOT != beamType)
+ global.addObject(this);
}
-void tracePath (int *targetX, int *targetY, GLOBALDATA *global, ENVIRONMENT *env, int minRange, double xpos, double ypos, int angle)
+
+/// @brief special constructor for SDI lasers
+BEAM::BEAM(PLAYER* player_, double x_, double y_, double tx, double ty,
+ int32_t weaponType, bool is_burnt_out) :
+ BEAM(player_, x_, y_, GET_ANGLE(std::abs(ty - y_), tx - x_) + 90,
+ weaponType, BT_SDI)
{
- double dx = global->slope[angle][0];
- double dy = global->slope[angle][1];
- double tx = xpos, ty = ypos;
- int hitSomething = 0;
- int range = 0;
-
- while ((!hitSomething) && tx > -10 &&
- tx < global->screenWidth + 10 && ty > -10 &&
- ty < global->screenHeight &&
- (getpixel (env->terrain, (int)tx, (int)ty) == PINK))
- {
- TANK *ltank;
- for (int objCount = 0; (ltank = (TANK*)env->getNextOfClass (TANK_CLASS, &objCount)) && ltank; objCount++)
- {
- if (range > minRange && tx > ltank->x - TANKWIDTH && tx < ltank->x + TANKWIDTH && ty > ltank->y && ty < ltank->y + TANKHEIGHT && ltank->l > 0)
- {
- hitSomething = 1;
- ltank->requireUpdate ();
- }
- }
- tx += dx;
- ty += dy;
- range++;
- }
- *targetX = (int)tx;
- *targetY = (int)ty;
+ if (player)
+ ++player->sdiShots;
+
+ // SDI lasers are redder than normal, even more if burnt_out
+ color = makecol(is_burnt_out ? 255 : 240 - ((weapType - SML_LAZER) * 16),
+ is_burnt_out ? 32 : 64,
+ is_burnt_out ? (weapType - SML_LAZER) * 32 : 128);
+
+ // Limit the laser to the missiles coordinates
+ points[numPoints - 1].x = tx;
+ points[numPoints - 1].y = ty;
}
-BEAM::BEAM (GLOBALDATA *global, ENVIRONMENT *env, double xpos, double ypos, int fireAngle,
- int weaponType):PHYSICAL_OBJECT(),weap(NULL),points(NULL)
+
+
+/// @brief BEAM destructor
+BEAM::~BEAM ()
{
- player = NULL;
- drag = 0.00;
- mass = 0;
- clock = 500;
- age = 0;
- setEnvironment (env);
- _align = LEFT;
- _global = global;
- #ifdef NETWORK
- char buffer[256];
- sprintf(buffer, "BEAM %d %d %d %d", (int) xpos, (int) ypos, fireAngle, weaponType);
- global->Send_To_Clients(buffer);
- #endif
- x = xpos;
- y = ypos;
- angle = fireAngle % 360;
- type = weaponType;
- if (type < WEAPONS)
- weap = &(weapon[type]);
- else
- weap = &(naturals[type - WEAPONS]);
- radius = weap->radius;
-
-
- if (type >= SML_LIGHTNING && type <= LRG_LIGHTNING)
- {
- damage = (double)weap->damage / (10 * (type - SML_LIGHTNING + 1));
- maxAge = 10 * (type - SML_LIGHTNING + 1);
- numPoints = 4 + rand () % 10;
- color = WHITE;
- play_sample ((SAMPLE *) _global->sounds[LIGHTNING_SOUND], env->scaleVolume(128 + (radius * 10)), 128, 500 + (radius * 50), 0);
- }
- else
- {
- numPoints = 2;
- if (type >= SML_LAZER && type <= LRG_LAZER)
- damage = (double)weap->damage / (10 * (type - SML_LAZER + 1));
- maxAge = 10 * (type - SML_LAZER + 1);
- color = makecol (255 - (type - SML_LAZER) * 64, 128, 64 + (type - SML_LAZER) * 64);
- }
- tracePath (&targetX, &targetY, global, env, radius + 2, x, y, angle);
- xv = targetX - x;
- yv = targetY - y;
- points = new int[numPoints * 2];
- if (!points)
- {
- perror ( "beam.cc: Failed allocating memory for points in BEAM::BEAM");
- // exit (1);
- }
- setLightningPath ();
+ requireUpdate();
+ update();
+ weap = nullptr;
+ if (points)
+ delete [] points;
+ points = nullptr;
+
+ if (BT_MIND_SHOT != beamType) {
+ global.make_bgupdate (dim_cur.x, dim_cur.y, dim_cur.w, dim_cur.h);
+ global.make_bgupdate (dim_old.x, dim_old.y, dim_old.w, dim_old.h);
+
+ // Let the land slide where the beam burned through:
+ global.addLandSlide(tgtLeftX, tgtRightX, false);
+
+ // Apply damage to all hit tanks:
+ TANK* lt = nullptr;
+ global.getHeadOfClass(CLASS_TANK, <);
+ while (lt) {
+ lt->applyDamage();
+ lt->getNext(<);
+ }
+
+ // Take out of the chain:
+ global.removeObject(this);
+
+ // The player is allowed to fire one more SDI laser again:
+ if ((BT_SDI == beamType) && player)
+ --player->sdiShots;
+ }
}
-void BEAM::setLightningPath ()
+
+void BEAM::applyPhysics ()
{
- int point = 0;
- double x1 = x, y1 = y;
-
- do
- {
- double offX, offY;
- if (point > 0 && point < numPoints - 1)
- {
- offX = (perlin2DPoint (1.0, 20, 12746 + x1, y1, 0.3, 6) + 1) * (radius * 10);
- offY = (perlin2DPoint (1.0, 20, x1, 1273 + y1, 0.3, 6) + 1) * (radius * 10);
- }
- else
- {
- offX = 0;
- offY = 0;
- }
- points[point * 2] = (int)(x1 + offX);
- points[point * 2 + 1] = (int)(y1 + offY);
-
- x1 += xv / (numPoints - 1);
- y1 += yv / (numPoints - 1);
- point++;
- }
- while (point < numPoints);
+ if (++age > maxAge)
+ destroy = true;
+
+ if (BT_SDI != beamType) {
+ createBeamPath();
+ }
+
+ if (BT_MIND_SHOT != beamType) {
+ if ( !global.skippingComputerPlay
+ && !(rand() % (env.frames_per_second / 5)) ) {
+ try {
+ new DECOR ( points[numPoints-1].x,
+ points[numPoints-1].y,
+ (rand () % 7) - 3,
+ 1 - (rand () % 6), radius, DECOR_SMOKE, 0);
+ } catch (std::exception) {
+ perror ( "beam.cpp: Failed to allocate memory for decor in applyPhysics");
+ }
+ }
+
+ try {
+ new EXPLOSION(player, points[numPoints-1].x, points[numPoints-1].y,
+ points[numPoints-1].x - points[0].x,
+ points[numPoints-1].y - points[0].y,
+ weapType, damage, isWeaponFire);
+ } catch (std::exception) {
+ perror ( "beam.cpp: Failed to allocate memory for explosion in applyPhysics");
+ }
+ }
}
-void BEAM::initialise ()
+
+void BEAM::draw()
{
- PHYSICAL_OBJECT::initialise ();
- drag = 0.00;
- mass = 0;
- clock = 500;
- age = 0;
+ // never draw mind shots!
+ if (BT_MIND_SHOT == beamType)
+ return;
+
+ int32_t oldDrawingMode = global.current_drawing_mode;
+
+ drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
+ global.current_drawing_mode = DRAW_MODE_TRANS;
+ set_trans_blender (0, 0, 0, 50);
+
+ beamRadius = radius;
+ beamSeed = seed;
+
+ for (int32_t i = 1; i < numPoints; ++i) {
+ int32_t left = std::min(points[i - 1].x, points[i].x);
+ int32_t top = std::min(points[i - 1].y, points[i].y);
+ int32_t right = std::max(points[i - 1].x, points[i].x);
+ int32_t bottom = std::max(points[i - 1].y, points[i].y);
+
+ if ( (weapType >= SML_LIGHTNING) && (weapType <= LRG_LIGHTNING) )
+ do_line (global.canvas,
+ points[i - 1].x, points[i - 1].y,
+ points[i ].x, points[i ].y,
+ age, lightningPoint);
+ else if ( (weapType >= SML_LAZER) && (weapType <= LRG_LAZER) )
+ do_line (global.canvas,
+ points[i - 1].x, points[i - 1].y,
+ points[i ].x, points[i ].y,
+ color, lazerPoint);
+
+ addUpdateArea (left - radius, top - radius,
+ right - left + (2 * radius),
+ bottom - top + (2 * radius) );
+ }
+
+ drawing_mode(oldDrawingMode, NULL, 0, 0);
+ global.current_drawing_mode = oldDrawingMode;
+
+ requireUpdate ();
}
-int BEAM::applyPhysics ()
+
+// === Private method implementations ===
+// ======================================
+
+/// @brief Create the basic points array with path tracing
+void BEAM::createBeamPath()
{
- TANK *ltank;
- DECOR *decor;
-
- age++;
- circlefill (_env->terrain, targetX, targetY, radius, PINK);
- for (int col = targetX - radius - 1; col < targetX + radius + 1; col++)
- setSlideColumnDimensions (_global, _env, col, true);
-
- if (age > maxAge)
- destroy = TRUE;
-
- for (int objCount = 0; (ltank = (TANK*)_env->getNextOfClass (TANK_CLASS, &objCount)) && ltank; objCount++)
- {
- if (targetX > ltank->x - TANKWIDTH - radius && targetX < ltank->x + TANKWIDTH + radius && targetY > ltank->y - radius && targetY < ltank->y + TANKHEIGHT + radius && ltank->l > 0)
- {
- // hitSomething = 1;
- ltank->requireUpdate ();
-
- // make sure a player is involved
- if (player)
- ltank->damage += damage * player->damageMultiplier;
- else
- ltank->damage += damage;
-
- ltank->creditTo = player;
- if (destroy)
- ltank->applyDamage ();
- }
- }
- decor = new DECOR (_global, _env, targetX, targetY, rand () % 6 - 3, rand () % 6 - 3, radius, DECOR_SMOKE);
- if (!decor)
- {
- perror ( "beam.cc: Failed allocating memory for decor in applyPhysics");
- // exit (1);
- }
- tracePath (&targetX, &targetY, _global, _env, radius + 2, x, y, angle);
- setLightningPath ();
-
- return (hitSomething);
+ if (nullptr == points) {
+ try {
+ points = new POINT_t[numPoints];
+ } catch (std::exception) {
+ perror ( "beam.cpp: Failed to allocate memory for points in BEAM::createBeamPath()");
+ }
+ }
+
+ // First determine the direct target - where does the beam end?
+ double tx = x, ty = y;
+ hitSomething = false;
+
+ // If this is not the first call, use the already known endpoints
+ if ( ( points[0].x || points[0].y
+ || points[numPoints - 1].x || points[numPoints - 1].y)
+ && !global.isDirtInBox( points[0].x, points[0].y,
+ points[numPoints - 1].x,
+ points[numPoints - 1].y) ) {
+ tx = points[numPoints - 1].x;
+ ty = points[numPoints - 1].y;
+ } else {
+ // The first point is the starting point, the last will become the target
+ points[0].x = x;
+ points[0].y = y;
+ }
+
+ while ( !hitSomething
+ && (tx > -radius)
+ && (tx < (env.screenWidth + radius))
+ && (ty > -radius)
+ && (ty < (env.screenHeight + radius)) ) {
+
+ // Assume PINK for off screen pixels
+ int32_t col = PINK;
+
+ if ( (tx > 0)
+ && (tx < (env.screenWidth - 1))
+ && (ty > MENUHEIGHT)
+ && (ty < (env.screenHeight - 1)) )
+ col = getpixel (global.terrain, tx, ty);
+
+ if (PINK == col) {
+ tx += xv;
+ ty += yv;
+ } else
+ hitSomething = true;
+ } // End of tracing pixels
+
+ // tx and ty now result in the first obstacle (or screen border)
+ // on a direct path.
+ points[numPoints - 1].x = tx;
+ points[numPoints - 1].y = ty;
+
+ // If this is a lightning strike, points between the first and last
+ // have to be (re-)generated.
+ makeLightningPath();
+
+ // Generate new lightning offsets checking for collisions:
+ traceBeamPath();
+
+ // If this is a mind_shot, it is immediately destroyed
+ if (BT_MIND_SHOT == beamType)
+ destroy = true;
}
-int beamRadius;
-void lazerPoint (BITMAP *dest, int x1, int y1, int color)
+
+/// @brief get the end of a mind shot laser
+void BEAM::getEndPoint(int32_t& x, int32_t& y)
{
- circlefill (dest, x1, y1, beamRadius, color);
+ x = points[numPoints - 1].x;
+ y = points[numPoints - 1].y;
}
-void lightningPoint (BITMAP *dest, int x1, int y1, int age)
+
+/// @brief create the lightning steps between the beginning and the end
+void BEAM::makeLightningPath()
{
- double pointRad = (perlin2DPoint (1.0, 2, x1 + age, y1, 0.3, 6) + 1) / 2 * beamRadius + 1;
- double offX = (perlin2DPoint (1.0, 20, 127846 + x1, y1 + age, 0.3, 6) + 1) * beamRadius;
- double offY = (perlin2DPoint (1.0, 20, x1 + age, 12973 + y1, 0.3, 6) + 1) * beamRadius;
+ if ( (numPoints > 2)
+ && (weapType >= SML_LIGHTNING)
+ && (weapType <= LRG_LIGHTNING) ) {
+ int32_t maxP = numPoints - 1;
+ double stepping = FABSDISTANCE2(points[0].x, points[0].y,
+ points[maxP].x, points[maxP].y) / maxP;
+
+ for (int32_t i = 1; i < maxP; ++i) {
+ points[i].x = x
+ + (xv * (static_cast<double>(i) * stepping))
+ + (perlin2DPoint (1.0, 10. * radius,
+ points[i].x + seed, points[i].y,
+ 0.3, 6) * radius * 10.);
+ points[i].y = y
+ + (yv * (static_cast<double>(i) * stepping))
+ + (perlin2DPoint (1.0, 10. * radius,
+ points[i].x, points[i].y + seed,
+ 0.3, 6) * radius * 10.);
+ }
+ } // End of lightning preparation
+}
- circlefill (dest, x1 + (int)offX, y1 + (int)offY, (int)pointRad, WHITE);
+
+/// @brief this method is used by the satellite to move the beam with itself.
+void BEAM::moveStart(double x_, double y_)
+{
+ x = x_;
+ y = y_;
+ if (points) {
+ points[0].x = x;
+ points[0].y = y;
+ }
}
-void BEAM::draw (BITMAP *dest)
+
+/// @brief walk through the beam points and check whether anything is hit
+void BEAM::traceBeamPath()
{
- int x1, y1, x2, y2;
- setUpdateArea ((int)x - radius, (int)y - radius, (int)x + radius * 2, (int)y + radius * 2);
-
- drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
- _env->current_drawing_mode = DRAW_MODE_TRANS;
- set_trans_blender (0, 0, 0, (int)50);
- if (type >= SML_LIGHTNING && type <= LRG_LIGHTNING)
- {
- beamRadius = radius;
- for (int point = 1; point < numPoints; point++)
- {
-
- x1 = points[(point - 1) * 2];
- y1 = points[(point - 1) * 2 + 1];
- x2 = points[point * 2];
- y2 = points[point * 2 + 1];
-
- do_line (dest, x1, y1, x2, y2, age, lightningPoint);
- addUpdateArea (MIN(x1,x2) - radius * 6,
- MIN(y1,y2) - radius * 6,
- MAX(x1,x2) + radius * 2 * 6,
- MAX(y1,y2) + radius * 2 * 6);
- }
- }
- else if (type >= SML_LAZER && type <= LRG_LAZER)
- {
- beamRadius = radius;
- for (int point = 1; point < numPoints; point++)
- {
-
- x1 = points[(point - 1) * 2];
- y1 = points[(point - 1) * 2 + 1];
- x2 = points[point * 2];
- y2 = points[point * 2 + 1];
-
- do_line (dest, x1, y1, x2, y2, color, lazerPoint);
- //do_line (dest, x1, y1, x2, y2, color, lightningPoint);
- addUpdateArea (MIN(x1,x2) - radius * 2,
- MIN(y1,y2) - radius * 2,
- MAX(x1,x2) + radius * 2 * 2,
- MAX(y1,y2) + radius * 2 * 2);
- }
- }
- drawing_mode(DRAW_MODE_SOLID, NULL, 0, 0);
- _env->current_drawing_mode = DRAW_MODE_SOLID;
- requireUpdate ();
+ int32_t minRange = radius + 2;
+ bool canHit = false;
+
+ hitSomething = false;
+
+ for (int32_t i = 1; !hitSomething && (i < numPoints); ++i) {
+ double startX = points[i-1].x;
+ double startY = points[i-1].y;
+ double endX = points[i].x;
+ double endY = points[i].y;
+ bool chkTanks = (BT_SDI == beamType)
+ ? false
+ : global.areTanksInBox(startX, startY, endX, endY);
+ bool chkDirt = global.isDirtInBox (startX, startY, endX, endY);
+
+ // Break this if there is nothing possibly in between
+ if (!(chkTanks || chkDirt) )
+ continue;
+
+ int32_t range = 0;
+ double distX = endX - startX;
+ double distY = endY - startY;
+ double absX = std::abs(distX);
+ double absY = std::abs(distY);
+ double moveX = distX / (absX > absY ? absX : absY);
+ double moveY = distY / (absY > absX ? absY : absX);
+ int32_t toMove = ROUND(std::max(absX, absY));
+
+ // Now wander along the path:
+ while ( !hitSomething
+ && (range < toMove)
+ && (startX > 0)
+ && (startX < (env.screenWidth - 1))
+ && (startY > MENUHEIGHT)
+ && (startY < (env.screenHeight - 1))
+ && ( !chkDirt
+ || (PINK == getpixel (global.terrain, startX, startY))) ) {
+
+ // Only check for tanks if the total range is large enough
+ // and if there are tanks in the path
+ if ((range >= minRange) && !canHit)
+ canHit = true;
+ if (canHit && chkTanks) {
+ TANK* lt = nullptr;
+ global.getHeadOfClass(CLASS_TANK, <);
+ while (lt) {
+ // Tank found, is it hit?
+ if (!lt->destroy
+ && lt->isInBox(startX - radius, startY - radius,
+ startX + radius, startY + radius) ) {
+ hitSomething = true;
+ lt->requireUpdate();
+
+ // 'Lock' the beam end on the tank:
+ if (startY < (lt->y + radius) )
+ startY = lt->y + radius;
+ if (startY > (lt->y + (2 * radius)) )
+ startY = lt->y + (2 * radius);
+ if (startX < (lt->x - (radius / 2)))
+ startX = lt->x - (radius / 2);
+ if (startX > (lt->x + (radius / 2)))
+ startX = lt->x + (radius / 2);
+
+ // Get the in_rates
+ double in_rate_x, in_rate_y;
+ if ( (BT_MIND_SHOT != beamType)
+ && lt->isInEllipse(startX, startY, radius, radius,
+ in_rate_x, in_rate_y) ) {
+ double in_rate = in_rate_x * in_rate_y;
+ if (in_rate < 0.9)
+ // Beams do not 'splash'.
+ in_rate = 0.9;
+
+ lt->addDamage(player, static_cast<double>(damage)
+ * in_rate
+ * (player ? player->damageMultiplier : 1.) );
+ }
+ // That's it
+ lt = nullptr;
+ moveX = 0.;
+ moveY = 0.;
+ } // End of having a tank
+ else
+ lt->getNext(<);
+ } // End of looping tanks
+ }
+
+ // Advance startX/Y
+ startX += moveX;
+ startY += moveY;
+ ++range;
+ } // End of regular check
+
+ // If dirt was hit, hitSomething must be adapted
+ if ((PINK != getpixel (global.terrain, startX, startY)))
+ hitSomething = true;
+
+ if (hitSomething
+ && ( (ROUND(startX) != points[numPoints - 1].x)
+ || (ROUND(startY) != points[numPoints - 1].y) ) ) {
+ // Reset the points to the new circumstances:
+ points[numPoints - 1].x = ROUND(startX);
+ points[numPoints - 1].y = ROUND(startY);
+ makeLightningPath();
+
+ // Note down x position for dirt slide on destruction:
+ if ((startX - radius - 1) < tgtLeftX) tgtLeftX = startX - radius - 1;
+ if ((startX + radius + 1) > tgtRightX) tgtRightX = startX + radius + 1;
+ } // End of checking pixels
+ } // End of looping points
}
-/*void BEAM::update ()
+
+// === static helper methods ===
+// =============================
+static void lazerPoint (BITMAP *dest, int32_t x1, int32_t y1, int32_t color)
{
- _env->combineUpdates = FALSE;
- VIRTUAL_OBJECT::update ();
- _env->combineUpdates = TRUE;
-}*/
+ circlefill (dest, x1, y1, beamRadius, color);
+}
-int BEAM::isSubClass (int classNum)
+static void lightningPoint (BITMAP *dest, int32_t x1, int32_t y1, int32_t age)
{
- if (classNum == BEAM_CLASS)
- return (TRUE);
- else
- return (FALSE);
- //return (PHYSICAL_OBJECT::isSubClass (classNum));
+ double pRad = (perlin2DPoint (1.0, 2, x1 + age, y1 + beamSeed, 0.3, 6) + 1)
+ / 2 * beamRadius + 1;
+ double offX = (perlin2DPoint (1.0, 10 * pRad, x1 + age + beamSeed, y1 + age, 0.3, 6) + 1)
+ * pRad / 2.;
+ double offY = (perlin2DPoint (1.0, 10 * pRad, x1 + age, y1 + age + beamSeed, 0.3, 6) + 1)
+ * pRad / 2.;
+
+ circlefill (dest, x1 + offX, y1 + offY, pRad, WHITE);
}
diff --git a/src/beam.h b/src/beam.h
index 7bdfaa7..3f50828 100644
--- a/src/beam.h
+++ b/src/beam.h
@@ -21,54 +21,80 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "main.h"
-#include "virtobj.h"
#include "physobj.h"
-#define LIGHTNING_SOUND 12
-enum beamType
+/** @enum eBeamType
+ * @brief Determines what kind of beam is generated
+**/
+enum eBeamType
{
- LIGHTNING_BEAM,
- LAZER_BEAM
+ BT_WEAPON = 0, //!< Normal weapon, nothing special
+ BT_SDI, //!< Not a weapon but an SDI laser
+ BT_NATURAL, //!< Fired by natural disaster, like lightning.
+ BT_MIND_SHOT //!< AI thinking.
};
+
class BEAM: public PHYSICAL_OBJECT
- {
- public:
- int radius;
- int length;
- double damage;
- int clock;
- int type;
- WEAPON *weap;
- int *points; // Allows jagged lines
- int numPoints;
- int color;
- int targetX;
- int targetY;
-
- virtual ~BEAM ();
- //BEAM (GLOBALDATA *global, ENVIRONMENT *env, double tX, double tY, double tXv, double tYv, int weaponType);
- BEAM (GLOBALDATA *global, ENVIRONMENT *env, double xpos, double ypos, int angle, int weaponType);
- void setLightningPath();
- void initialise ();
- void draw (BITMAP *dest);
- //void update ();
- int applyPhysics ();
- virtual int isSubClass (int classNum);
- inline virtual int getClass ()
- {
- return (BEAM_CLASS);
- }
- inline virtual void setEnvironment(ENVIRONMENT *env)
- {
- if (!_env || (_env != env))
- {
- _env = env;
- _index = _env->addObject (this);
- }
- }
- };
+{
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ explicit BEAM ( PLAYER* player_, double x_, double y_,
+ int32_t fireAngle, int32_t weaponType,
+ eBeamType beam_type);
+ BEAM ( PLAYER* player_, double x_, double y_,
+ double tx, double ty, int32_t weaponType,
+ bool is_burnt_out);
+ ~BEAM ();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ void applyPhysics();
+ void draw ();
+ void getEndPoint (int32_t &x, int32_t &y); // For mind shots to fetch
+ void moveStart (double x_, double y_); // For the satellite
+
+ eClasses getClass() { return CLASS_BEAM; }
+
+
+private:
+
+ /* -----------------------
+ * --- Private methods ---
+ * -----------------------
+ */
+
+ void createBeamPath();
+ void makeLightningPath();
+ void traceBeamPath ();
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ eBeamType beamType = BT_WEAPON;
+ int32_t color = WHITE;
+ double damage = 0.;
+ int32_t numPoints = 0;
+ POINT_t* points = nullptr;
+ int32_t radius = 0;
+ int32_t seed = 0;
+ int32_t tgtLeftX = 0;
+ int32_t tgtRightX = 0;
+ WEAPON* weap = nullptr;
+};
#endif
diff --git a/src/button.cpp b/src/button.cpp
index ff845ad..8207016 100644
--- a/src/button.cpp
+++ b/src/button.cpp
@@ -2,66 +2,138 @@
This file contains funcions for the BUTTON class. These
are being moved out of the atanks.cc file.
-- Jesse
+
+Updated to be more variable and self managing
+-- Sven
*/
#include "button.h"
+#include "sound.h"
+
-BUTTON::BUTTON(GLOBALDATA *global, ENVIRONMENT *env, int x1, int y1,
- char *text1, BITMAP *bmp1, BITMAP *hover1, BITMAP *depressed1):_global(NULL),_env(NULL),text(NULL),
- bmp(NULL),hover(NULL),depressed(NULL),click(NULL)
+/** @brief Create a graphical button with no optional title and default sound
+ *
+ * Note: if @a click_ is nullptr, global_->sounds[8] is used.
+ */
+BUTTON::BUTTON(int32_t left_, int32_t top_,
+ BITMAP* bmp_, BITMAP* hover_, BITMAP* depressed_) :
+ bmp (bmp_ ? bmp_ : hover_ ? hover_ : depressed_ ? depressed_ : nullptr),
+ depressed(depressed_ ? depressed_ : hover_ ? hover_ : bmp_ ? bmp_ : nullptr),
+ hover (hover_ ? hover_ : bmp_ ? bmp_ : depressed_ ? depressed_ : nullptr)
{
- _global = global;
- _env = env;
- location.x = x1;
- location.y = y1;
- text = text1;
- click = (SAMPLE *)_global->sounds[8];
- bmp = bmp1;
- hover = hover1;
- depressed = depressed1;
- location.w = bmp->w;
- location.h = bmp->h;
- xl = location.x + bmp->w;
- yl = location.y + bmp->h;
+ location.x = left_;
+ location.y = top_;
+ location.w = bmp ? bmp->w : 0;
+ location.h = bmp ? bmp->h : 0;
+ x1 = location.x;
+ y1 = location.y;
+ x2 = location.w + x1;
+ y2 = location.h + y1;
+ x3 = x1 + (location.w / 2);
+ y3 = y1 + (location.h / 2);
}
+/** @brief Create a graphical button
+ *
+ * Note: if @a click_ is nullptr, global_->sounds[8] is used.
+ */
+BUTTON::BUTTON(const char* text_, bool text_only_,
+ int32_t left_, int32_t top_,
+ BITMAP* bmp_, BITMAP* hover_, BITMAP* depressed_) :
+ BUTTON(left_, top_, bmp_, hover_, depressed_)
+{
+ text_only = text_only_;
+ text = text_;
+ text_width = text ? text_length(font, text) : 0;
+}
+
-void BUTTON::draw (BITMAP *dest)
+/** @brief Create a button with manual drawing
+ */
+BUTTON::BUTTON(const char* text_, bool text_only_,
+ int32_t left_, int32_t top_, int32_t width_, int32_t height_) :
+ BUTTON(text_, text_only_, left_, top_,
+ nullptr, nullptr, nullptr)
{
- draw_sprite (dest, (BITMAP *)(isMouseOver()?(isPressed()?depressed:hover):bmp), location.x, location.y);
- if (text)
- textout_centre_ex (dest, font, text, location.x+75, location.y + 6, WHITE, -1);
- _env->make_update (location.x, location.y, xl, yl);
+ location.w = width_;
+ location.h = height_;
+ x2 = location.w + x1;
+ y2 = location.h + y1;
+ x3 = x1 + (location.w / 2);
+ y3 = y1 + (location.h / 2);
}
-int BUTTON::isMouseOver ()
+
+
+void BUTTON::draw()
{
- if ((mouse_x >= location.x) &&
- (mouse_y >= location.y) &&
- (mouse_x <= xl) &&
- (mouse_y <= yl))
- {
- return (1);
- }
- else
- {
- return (0);
- }
+ bool mouse_over = isMouseOver();
+ bool pressed = isPressed();
+
+ if (!text_only) {
+ if (bmp)
+ draw_sprite (global.canvas,
+ mouse_over ? (pressed ? depressed : hover) : bmp,
+ x1, y1);
+ else {
+ int32_t fg = mouse_over ? (pressed ? SILVER : GREY) : WHITE;
+ int32_t lc = mouse_over ? (pressed ? GREY : WHITE) : SILVER;
+ int32_t dc = mouse_over ? (pressed ? WHITE : SILVER) : GREY;
+
+ rect (global.canvas, x1 , y1 , x2 , y2 , BLACK);
+ rectfill (global.canvas, x1 + 1, y1 + 1, x2 - 1, y2 - 1, fg);
+ line (global.canvas, x2 - 2, y2 - 2, x1 + 2, y2 - 2, dc);
+ line (global.canvas, x2 - 3, y2 - 3, x1 + 3, y2 - 3, dc);
+ line (global.canvas, x2 - 2, y2 - 2, x2 - 2, y1 + 2, dc);
+ line (global.canvas, x2 - 3, y2 - 3, x2 - 3, y1 + 3, dc);
+ line (global.canvas, x1 + 2, y1 + 2, x2 - 2, y1 + 2, lc);
+ line (global.canvas, x1 + 3, y1 + 3, x2 - 3, y1 + 3, lc);
+ line (global.canvas, x1 + 2, y1 + 2, x1 + 2, y2 - 2, lc);
+ line (global.canvas, x1 + 3, y1 + 3, x1 + 3, y2 - 3, lc);
+ rect (global.canvas, x1 + 4, y1 + 4, x2 - 4, y2 - 4, BLACK);
+ }
+ }
+
+ if (text)
+ textout_centre_ex (global.canvas, font, text, x3, y3 - (text_height(font) / 2),
+ text_only ? BLACK : WHITE, -1);
+
+ global.make_update (x1 - 5, y1 - 5, x2 + 5, y2 + 5);
}
-int BUTTON::isPressed()
+void BUTTON::getLocation(int32_t &x,int32_t &y,int32_t &w,int32_t &h)
{
- if ((mouse_b == 1) && isMouseOver ())
- {
- play_sample (click, _env->scaleVolume(128), 128, 1000, 0);
- return 1;
- }
- else
- {
- return 0;
- }
+ x = location.x;
+ y = location.y;
+ w = location.w;
+ h = location.h;
}
+
+bool BUTTON::isMouseOver ()
+{
+ if ( (mouse_x >= x1)
+ && (mouse_y >= y1)
+ && (mouse_x <= x2)
+ && (mouse_y <= y2) )
+ return true;
+ return false;
+}
+
+
+bool BUTTON::isPressed()
+{
+ if ((mouse_b & 3) && isMouseOver ()) {
+ play_interface_sound(SND_INTE_BUTTON_CLICK);
+ return true;
+ }
+ return false;
+}
+
+void BUTTON::setText(const char* text_)
+{
+ text = text_;
+}
diff --git a/src/button.h b/src/button.h
index 6dfe362..1c04d7b 100644
--- a/src/button.h
+++ b/src/button.h
@@ -1,30 +1,59 @@
#ifndef BUTTON_HEADER_
#define BUTTON_HEADER_
-#include "globaldata.h"
-#include "environment.h"
+#include "main.h"
class BUTTON
- {
- GLOBALDATA *_global;
- ENVIRONMENT *_env;
-
- public:
- BOX location;
- int xl, yl;
- char *text;
- BITMAP *bmp;
- BITMAP *hover;
- BITMAP *depressed;
- SAMPLE *click;
-
- BUTTON (GLOBALDATA *global, ENVIRONMENT *env, int x1, int y1, char *text1,
- BITMAP *bmp1, BITMAP *hover1, BITMAP *depressed1);
-
- int isPressed (); //if button pressed returns 1
- int isMouseOver (); //Cursor is over button
- void draw (BITMAP *dest);
- };
+{
+public:
+
+ /* --------------------
+ * --- constructors ---
+ * --------------------
+ */
+
+ // Minimum ctor without text
+ explicit
+ BUTTON (int32_t left_, int32_t top_,
+ BITMAP* bmp_, BITMAP* hover_, BITMAP* depressed_);
+
+ // ctor for using a bitmap.
+ BUTTON (const char* text_, bool text_only_,
+ int32_t left_, int32_t top_,
+ BITMAP* bmp_, BITMAP* hover_, BITMAP* depressed_);
+
+ // ctor for drawing a manual box.
+ BUTTON (const char* text_, bool text_only_,
+ int32_t left_, int32_t top_, int32_t width_, int32_t height_);
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ void draw ();
+ void getLocation(int32_t &x,int32_t &y,int32_t &w,int32_t &h);
+ bool isMouseOver ();
+ bool isPressed ();
+ void setText(const char* text_);
+
+private:
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ BITMAP* bmp = nullptr;
+ BITMAP* depressed = nullptr;
+ BITMAP* hover = nullptr;
+ BOX location; //!< is {0, 0, 0, 0} by default
+ const char* text = nullptr;
+ bool text_only = false; //!< If set to true, only the title is displayed.
+ int32_t text_width = 0; //!< must not be recalculated over and over again...
+ int32_t x1, y1, x2, y2, x3, y3; //!< Shortcuts, as those stay fixed.
+};
#endif
diff --git a/src/client.cpp b/src/client.cpp
index bd2eb68..e1a2e4a 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -1,8 +1,4 @@
-#ifdef NETWORK
-
-#include "menu.h"
#include "button.h"
-#include "team.h"
#include "files.h"
#include "satellite.h"
#include "update.h"
@@ -13,51 +9,52 @@
#include "missile.h"
#include "teleport.h"
#include "floattext.h"
+#include "player.h"
+#include "tank.h"
#include "sky.h"
-#ifdef THREADS
-#include <pthread.h>
-#endif
-
-
+// Note: Don't guard everything. Empty compilation units are invalid.
+#ifdef NETWORK
+// From gameloop.cpp:
+void draw_top_bar();
// Here we try to match the buffer with an action. We then attempt to
// perform the action. Remember, this is a command from the server, so
// it is either giving us some info or telling us to create something.
-int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
+int Parse_Client_Data(char *buffer)
{
char args[CLIENT_ARGS][BUFFER_SIZE];
char letter;
int dest_string;
int line_length = strlen(buffer);
- int source_index = 0, dest_index = 0;
-
+ int sourceindex = 0, destindex = 0;
+
// clear buffers
for (dest_string = 0; dest_string < CLIENT_ARGS; dest_string++)
memset(args[dest_string], '\0', BUFFER_SIZE);
- dest_string = 0;
+ dest_string = 0;
// copy buffer into cmd and argument variables
- while ( ( source_index < line_length ) && (dest_string < CLIENT_ARGS) )
- {
- letter = buffer[source_index];
+ while ( ( sourceindex < line_length ) && (dest_string < CLIENT_ARGS) )
+ {
+ letter = buffer[sourceindex];
if ( letter == ' ' )
{
letter = '\0';
- args[dest_string][dest_index] = letter;
- dest_index = 0;
+ args[dest_string][destindex] = letter;
+ destindex = 0;
dest_string++;
}
else
{
- args[dest_string][dest_index] = letter;
- dest_index++;
+ args[dest_string][destindex] = letter;
+ destindex++;
}
- source_index++;
+ sourceindex++;
}
-
+
// let us see what we have
if (! strcmp(args[0], "SERVERVERSION") )
{
@@ -70,10 +67,10 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
}
else if (! strcmp(args[0], "CURRENTPOSITION") )
{
- if ( (global->client_player) && (global->client_player->tank) )
+ if ( (global.client_player) && (global.client_player->tank) )
{
- sscanf(args[1], "%lf", &(global->client_player->tank->x));
- sscanf(args[2], "%lf", &(global->client_player->tank->y));
+ sscanf(args[1], "%lf", &(global.client_player->tank->x));
+ sscanf(args[2], "%lf", &(global.client_player->tank->y));
}
}
else if (! strcmp(args[0], "BEAM") )
@@ -84,16 +81,16 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
sscanf(args[2], "%lf", &my_y);
sscanf(args[3], "%d", &my_angle);
sscanf(args[4], "%d", &my_type);
- new BEAM(global, env, my_x, my_y, my_angle, my_type);
+ new BEAM(nullptr, my_x, my_y, my_angle, my_type, BT_WEAPON);
}
else if (! strcmp(args[0], "BOXED"))
{
int got_box;
sscanf(args[1], "%d", &got_box);
if (got_box)
- global->bIsBoxed = true;
+ env.isBoxed = true;
else
- global->bIsBoxed = false;
+ env.isBoxed = false;
return TRUE;
}
else if (! strcmp(args[0], "EXPLOSION") )
@@ -103,53 +100,53 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
sscanf(args[1], "%lf", &my_x);
sscanf(args[2], "%lf", &my_y);
sscanf(args[3], "%d", &my_type);
- new EXPLOSION(global, env, my_x, my_y, my_type);
+ new EXPLOSION(nullptr, my_x, my_y, 0., 0., my_type, true);
return FALSE;
}
else if (! strcmp(args[0], "ITEM"))
{
- int item_index, amount;
- sscanf(args[1], "%d", &item_index);
+ int itemindex, amount;
+ sscanf(args[1], "%d", &itemindex);
sscanf(args[2], "%d", &amount);
- if ( (item_index >= 0) && (item_index < ITEMS) &&
+ if ( (itemindex >= 0) && (itemindex < ITEMS) &&
(amount >= 0) && (amount <= 99) )
{
- global->client_player->ni[item_index] = amount;
+ global.client_player->ni[itemindex] = amount;
}
- if (item_index == (ITEMS - 1) )
+ if (itemindex == (ITEMS - 1) )
return TRUE;
}
else if (! strcmp(args[0], "HEALTH") )
{
- int tank_index;
+ int tankindex;
int health, shield, shield_type;
char some_text[32];
- sscanf(args[1], "%d", &tank_index);
- if (tank_index >= 0)
+ sscanf(args[1], "%d", &tankindex);
+ if (tankindex >= 0)
{
sscanf(args[2], "%d", &health );
sscanf(args[3], "%d", &shield );
sscanf(args[4], "%d", &shield_type);
- global->players[tank_index]->tank->l = health;
- global->players[tank_index]->tank->sh = shield;
- global->players[tank_index]->tank->sht = shield_type;
+ env.players[tankindex]->tank->l = health;
+ env.players[tankindex]->tank->sh = shield;
+ env.players[tankindex]->tank->sht = shield_type;
// set the text over the tank
sprintf(some_text, "%d", health);
- global->players[tank_index]->tank->healthText->set_text(some_text);
- global->players[tank_index]->tank->healthText->set_color(global->players[tank_index]->color);
+ env.players[tankindex]->tank->healthText.set_text(some_text);
+ env.players[tankindex]->tank->healthText.set_color(env.players[tankindex]->color);
sprintf(some_text, "%d", shield);
- global->players[tank_index]->tank->shieldText->set_text(some_text);
- global->players[tank_index]->tank->healthText->set_color(global->players[tank_index]->color);
+ env.players[tankindex]->tank->shieldText.set_text(some_text);
+ env.players[tankindex]->tank->healthText.set_color(env.players[tankindex]->color);
}
- if (tank_index == (global->numPlayers - 1) )
+ if (tankindex == (env.numGamePlayers - 1) )
return TRUE;
else
return FALSE;
}
else if (! strcmp(args[0], "WIND") )
{
- sscanf(args[1], "%lf", & (env->wind) );
+ sscanf(args[1], "%lf", & (global.wind) );
return TRUE;
}
else if (! strcmp(args[0], "MISSILE") )
@@ -162,7 +159,8 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
sscanf(args[3], "%lf", &delta_x);
sscanf(args[4], "%lf", &delta_y);
sscanf(args[5], "%d", &my_type);
- missile = new MISSILE(global, env, my_x, my_y, delta_x, delta_y, my_type);
+ missile = new MISSILE(nullptr, my_x, my_y, delta_x, delta_y, my_type,
+ MT_WEAPON, 1);
if (! missile)
printf("Attempted to create missile failed in client code.\n");
return FALSE;
@@ -170,15 +168,14 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
else if (! strcmp(args[0], "NUMPLAYERS") )
{
int counter;
- sscanf(args[1], "%d", & (global->numPlayers) );
+ sscanf(args[1], "%d", & (env.numGamePlayers) );
// create the players in question
- for (counter = 0; counter < global->numPlayers; counter++)
+ for (counter = 0; counter < env.numGamePlayers; counter++)
{
- global->players[counter] = new PLAYER(global, env);
- global->players[counter]->tank = new TANK(global, env);
- global->players[counter]->tank->player = global->players[counter];
- global->players[counter]->tank->initialise();
- global->players[counter]->tank->nameText->set_text("");
+ env.players[counter] = new PLAYER();
+ env.players[counter]->tank = new TANK();
+ env.players[counter]->tank->player = env.players[counter];
+ env.players[counter]->tank->nameText.set_text(nullptr);
}
return TRUE;
}
@@ -192,29 +189,29 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
{
int number;
sscanf(args[1], "%d", &number);
- if ( (number < global->numPlayers) && (number >= 0) )
- global->players[number]->setName(args[2]);
- if (number == (global->numPlayers - 1) )
- return TRUE;
+ if ( (number < env.numGamePlayers) && (number >= 0) )
+ env.players[number]->setName(args[2]);
+ if (number == (env.numGamePlayers - 1) )
+ return TRUE;
}
else if (! strcmp(args[0], "REMOVETANK") )
{
int index;
sscanf(args[1], "%d", &index);
- if ( (index >= 0) && (index < global->numPlayers) )
+ if ( (index >= 0) && (index < env.numGamePlayers) )
{
// make sure this tank exists before we get rid of it
- if ( global->players[index]->tank )
+ if ( env.players[index]->tank )
{
- delete global->players[index]->tank;
- global->players[index]->tank = NULL;
+ delete env.players[index]->tank;
+ env.players[index]->tank = NULL;
}
}
}
else if (! strcmp(args[0], "ROUNDS") )
{
- sscanf(args[1], "%lf", &global->rounds);
- sscanf(args[2], "%d", &global->currentround);
+ sscanf(args[1], "%u", &env.rounds);
+ sscanf(args[2], "%u", &global.currentround);
return TRUE;
}
else if (! strcmp(args[0], "SURFACE") )
@@ -227,13 +224,13 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
sscanf(args[1], "%d", &x);
sscanf(args[2], "%d", &y);
- env->surface[x] = y;
- my_height = global->screenHeight - y;
+ global.surface[x].store(y);
+ my_height = env.screenHeight - y;
my_height = my_height / 50; // ratio of change
// fill in terrain...
- for (index = y; index < global->screenHeight; index++)
+ for (index = y; index < env.screenHeight; index++)
{
- putpixel(env->terrain, x, index, makecol(0, green, 0));
+ putpixel(global.terrain, x, index, makecol(0, green, 0));
colour_change++;
if (colour_change >= my_height)
{
@@ -241,7 +238,7 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
green--;
}
}
- if (x >= (global->screenWidth - 1) )
+ if (x >= (env.screenWidth - 1) )
return TRUE;
}
else if (! strcmp(args[0], "SCREEN") )
@@ -250,14 +247,14 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
sscanf(args[1], "%d", &width);
sscanf(args[2], "%d", &height);
- if ( (width == global->screenWidth) &&
- (height == global->screenHeight) )
+ if ( (width == env.screenWidth) &&
+ (height == env.screenHeight) )
printf("Host's screen resolution matches ours.\n");
else
{
printf("Host's screen resolution is %d by %d.\n", width, height);
printf("Ours is %d by %d. This is going to cause problems!\n",
- global->screenWidth, global->screenHeight);
+ env.screenWidth, env.screenHeight);
}
return TRUE;
}
@@ -267,7 +264,7 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
PLAYER *my_player;
sscanf(args[1], "%d", &player_number);
- my_player = global->players[player_number];
+ my_player = env.players[player_number];
if ( (my_player) && (my_player->tank) )
{
sscanf(args[2], "%d", &x);
@@ -275,30 +272,32 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
my_player->tank->x = x;
my_player->tank->y = y;
}
- if (player_number == (global->numPlayers - 1))
+ if (player_number == (env.numGamePlayers - 1))
return TRUE;
}
else if (! strcmp(args[0], "TEAM") )
{
- int player_number, colour;
- double the_team;
+ int32_t player_number = 0;
+ int32_t colour = BLACK;
+ int the_team;
sscanf(args[1], "%d", &player_number);
- sscanf(args[2], "%lf", & the_team);
- if ( (the_team < global->numPlayers) && (the_team >= 0) )
+ sscanf(args[2], "%d", & the_team);
+ if ( (the_team < env.numGamePlayers) && (the_team >= 0) )
{
- global->players[player_number]->team = the_team;
+ env.players[player_number]->team =
+ static_cast<eTeamTypes>(the_team);
if (the_team == TEAM_JEDI)
colour = makecol(0, 255, 0);
else if (the_team == TEAM_SITH)
colour = makecol(255, 0, 255);
else if (the_team == TEAM_NEUTRAL)
colour = makecol(0, 0, 255);
- if (global->players[player_number] == global->client_player)
+ if (env.players[player_number] == global.client_player)
colour = makecol(255, 0, 0);
- global->players[player_number]->color = colour;
+ env.players[player_number]->color = colour;
}
- if (player_number == (global->numPlayers - 1) )
- return TRUE;
+ if (player_number == (env.numGamePlayers - 1) )
+ return TRUE;
}
else if (! strcmp(args[0], "TELEPORT") )
{
@@ -308,55 +307,55 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
sscanf(args[1], "%d", &player_num);
sscanf(args[2], "%d", &new_x);
sscanf(args[3], "%d", &new_y);
- if ( (player_num >= 0) && (player_num < global->numPlayers) &&
- (global->players[player_num]->tank) )
+ if ( (player_num >= 0) && (player_num < env.numGamePlayers) &&
+ (env.players[player_num]->tank) )
{
- new TELEPORT(global, env, global->players[player_num]->tank,
- new_x, new_y, TANKHEIGHT * 4 + GUNLENGTH, 120);
+ TANK* lt = env.players[player_num]->tank;
+ new TELEPORT(lt, new_x, new_y, lt->getDiameter(), 120, ITEM_TELEPORT);
}
-
+
}
else if (! strcmp(args[0], "WALLTYPE"))
{
- sscanf(args[1], "%d", &(env->current_wallType));
- switch (env->current_wallType)
+ sscanf(args[1], "%d", &(env.current_wallType));
+ switch (env.current_wallType)
{
case WALL_RUBBER:
- env->wallColour = makecol(0, 255, 0); // GREEN;
+ env.wallColour = makecol(0, 255, 0); // GREEN;
break;
case WALL_STEEL:
- env->wallColour = makecol(255, 0, 0); // RED;
+ env.wallColour = makecol(255, 0, 0); // RED;
break;
case WALL_SPRING:
- env->wallColour = makecol(0, 0, 255); //BLUE;
+ env.wallColour = makecol(0, 0, 255); //BLUE;
break;
case WALL_WRAP:
- env->wallColour = makecol(255, 255, 0); // YELLOW;
+ env.wallColour = makecol(255, 255, 0); // YELLOW;
break;
}
return TRUE;
}
else if (! strcmp(args[0], "WEAPON") )
{
- int weapon_index, amount;
- sscanf(args[1], "%d", &weapon_index);
+ int weaponindex, amount;
+ sscanf(args[1], "%d", &weaponindex);
sscanf(args[2], "%d", &amount);
- if ( (weapon_index >= 0) && (weapon_index < WEAPONS) &&
+ if ( (weaponindex >= 0) && (weaponindex < WEAPONS) &&
(amount >= 0) && (amount <= 99) )
{
- global->client_player->nm[weapon_index] = amount;
+ global.client_player->nm[weaponindex] = amount;
}
- if (weapon_index == (WEAPONS - 1))
+ if (weaponindex == (WEAPONS - 1))
return TRUE;
}
else if (! strcmp(args[0], "YOUARE") )
{
int index;
sscanf(args[1], "%d", &index );
- if ( (index >= 0) && (index < global->numPlayers) )
+ if ( (index >= 0) && (index < env.numGamePlayers) )
{
- global->client_player = global->players[index];
- global->currTank = global->client_player->tank;
+ global.client_player = env.players[index];
+ global.set_curr_tank(global.client_player->tank);
}
return TRUE;
}
@@ -366,39 +365,31 @@ int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer)
-
-void Create_Sky(ENVIRONMENT *env, GLOBALDATA *global)
+void Create_Sky()
{
- if ( (env->custom_background) && (env->bitmap_filenames) )
- {
- if ( env->sky) destroy_bitmap(env->sky);
- env->sky = load_bitmap( env->bitmap_filenames[ rand() % env->number_of_bitmaps ], NULL);
- }
-
- if ( (! env->custom_background) || (! env->sky) )
- {
- if ( env->sky ) destroy_bitmap(env->sky);
-#ifdef THREADS
- if (env->get_waiting_sky())
- {
- env->sky = env->get_waiting_sky();
- env->lock(env->waiting_sky_lock);
- env->waiting_sky = NULL;
- env->unlock(env->waiting_sky_lock);
- }
- else
- {
-#endif
- env->sky = create_bitmap( global->screenWidth, global->screenHeight - MENUHEIGHT);
- generate_sky (global, env->sky, sky_gradients[global->cursky],
- (global->ditherGradients ? GENSKY_DITHERGRAD : 0 ) |
- (global->detailedSky ? GENSKY_DETAILED : 0 ) );
-#ifdef THREADS
- }
-#endif
- }
-
-} // end of create sky function
+ if (env.custom_background && env.bitmap_filenames) {
+ if (env.sky)
+ destroy_bitmap(env.sky);
+ env.sky = load_bitmap(
+ env.bitmap_filenames[ rand() % env.number_of_bitmaps ], nullptr);
+ }
+
+ if (!env.custom_background || !env.sky) {
+ if (env.sky
+ && ( (env.sky->w != env.screenWidth)
+ || (env.sky->h != (env.screenHeight - MENUHEIGHT) ) ) ) {
+ destroy_bitmap(env.sky);
+ env.sky = nullptr;
+ }
+
+ if (!env.sky)
+ env.sky = create_bitmap(env.screenWidth,
+ env.screenHeight - MENUHEIGHT);
+ generate_sky (nullptr, sky_gradients[global.cursky],
+ (env.ditherGradients ? GENSKY_DITHERGRAD : 0 )
+ | (env.detailedSky ? GENSKY_DETAILED : 0 ) );
+ }
+} // end of create sky function
@@ -410,9 +401,11 @@ int Client_Fire(PLAYER *my_player, int my_socket)
if (!my_player) return FALSE;
if (! my_player->tank) return FALSE;
- sprintf(buffer, "FIRE %d %d %d", my_player->tank->cw, my_player->tank->a,
- my_player->tank->p);
- write(my_socket, buffer, strlen(buffer));
+ int32_t towrite, written;
+
+ SAFE_WRITE(my_socket, "FIRE %d %d %d", my_player->tank->cw,
+ my_player->tank->a, my_player->tank->p)
+
return TRUE;
}
@@ -457,7 +450,7 @@ int Client_Cycle_Weapon(PLAYER *my_player, int forward_or_back)
{
if (forward_or_back == CYCLE_FORWARD)
my_player->tank->cw++;
- else
+ else
my_player->tank->cw--;
if (my_player->tank->cw >= THINGS)
@@ -488,28 +481,367 @@ int Client_Cycle_Weapon(PLAYER *my_player, int forward_or_back)
// On success, a pointer to char is returned.
// On failure, a NULL is returned.
// The returned pointer does NOT need to be freed.
-char *Explain_Error(GLOBALDATA *global, int error_code)
+const char* Explain_Error(int32_t error_code)
{
- char *my_message = NULL;
+ switch (error_code) {
+ case CLIENT_ERROR_VERSION:
+ return env.ingame->Get_Line(77);
+ break;
+ case CLIENT_ERROR_SCREENSIZE:
+ return env.ingame->Get_Line(78);
+ break;
+ case CLIENT_ERROR_DISCONNECT:
+ return env.ingame->Get_Line(79);
+ break;
+ }
+
+ return nullptr;
+}
- switch (error_code)
+
+// Client version of the game
+// Really, this loop should do some basic things.
+// 1. Find out what the landscape should look like from the server.
+// 2. Place tanks on the battle field
+// 3. Create missiles, beam weapons and such when the server asks us to
+// 4. Get input from the player and forward it to the server.
+// 5. Clean up at the end of the round.
+//
+// Function return TRUE if everything went well or FALSE
+// if an error occured.
+int Game_Client(int socket_number)
+{
+ int surface_x = 1, tank_position = 1, team_number = 1, name_number = 1;
+ int weapon_number = 1, item_number = 1, tank_health = 1;
+ int end_of_round = FALSE, keep_playing = FALSE;
+ int game_stage = CLIENT_VERSION;
+ char buffer[BUFFER_SIZE];
+ int incoming;
+ int my_key;
+ int time_clock = 0;
+ bool screen_update = false;
+ int count; // generic counter
+ int stuff_going_down = FALSE; // explosions, missiles etc on the screen
+ VIRTUAL_OBJECT *my_object, *next_obj;
+ int32_t class_ = 0;
+ bool fired = false;
+ int32_t towrite, written;
+
+
+ clear_to_color (global.terrain, PINK); // get terrain ready
+ clear_to_color(global.canvas, BLACK);
+
+ // clean up old text
+ global.getHeadOfClass(CLASS_FLOATTEXT, &my_object);
+ while (my_object) {
+ my_object->getNext(&next_obj);
+ static_cast<FLOATTEXT*>(my_object)->newRound();
+ delete my_object;
+ }
+
+ Create_Sky(); // so we have a background
+ SAFE_WRITE(socket_number, "%s", "VERSION")
+
+ while (! end_of_round)
{
- case CLIENT_ERROR_VERSION:
- my_message = global->ingame->complete_text[77];
- break;
- case CLIENT_ERROR_SCREENSIZE:
- my_message = global->ingame->complete_text[78];
- break;
- case CLIENT_ERROR_DISCONNECT:
- my_message = global->ingame->complete_text[79];
- break;
- }
+ // check for waiting input from the server
+ incoming = Check_For_Incoming_Data(socket_number);
+ if (incoming)
+ {
+ int bytes_read;
- return my_message;
-}
+ memset(buffer, '\0', BUFFER_SIZE);
+ bytes_read = read(socket_number, buffer, BUFFER_SIZE);
+ if (bytes_read > 0)
+ {
+ // do something with this input
+ if (! strncmp(buffer, "CLOSE", 5) )
+ {
+ end_of_round = TRUE;
+ keep_playing = FALSE;
+ printf("Got close message.\n");
+ global.client_message = strdup(env.ingame->Get_Line(81));
+ }
+ else if (! strncmp(buffer, "NOROOM", 6) )
+ {
+ end_of_round = TRUE;
+ keep_playing = FALSE;
+ printf("The server is full or the game has not started. Please try again later.\n");
+ global.client_message = strdup(env.ingame->Get_Line(80));
+ }
+ else if (! strncmp(buffer, "GAMEEND", 7) )
+ {
+ end_of_round = TRUE;
+ keep_playing = FALSE;
+ printf("The game is over.\n");
+ if ( strlen(buffer) > 7)
+ global.client_message = strdup(& (buffer[8])) ;
+ else
+ global.client_message = strdup(env.ingame->Get_Line(82));
+ }
+ else if (! strncmp(buffer, "ROUNDEND", 8) )
+ {
+ end_of_round = TRUE;
+ keep_playing = TRUE;
+ printf("Round is over.\n");
+ }
+
+ else // not a special command, parse it
+ {
+ if ( Parse_Client_Data(buffer) )
+ {
+ if (game_stage < CLIENT_PLAYING)
+ game_stage++;
+
+ // Request more information
+ if (game_stage < CLIENT_PLAYING)
+ {
+ switch (game_stage)
+ {
+ case CLIENT_SCREEN: strcpy(buffer, "SCREEN"); break;
+ case CLIENT_WIND: strcpy(buffer, "WIND"); break;
+ case CLIENT_NUMPLAYERS: strcpy(buffer, "NUMPLAYERS"); break;
+ case CLIENT_TANK_POSITION: strcpy(buffer, "TANKPOSITION 0"); break;
+ case CLIENT_SURFACE: strcpy(buffer, "SURFACE 0"); break;
+ case CLIENT_WHOAMI: strcpy(buffer, "WHOAMI"); break;
+ case CLIENT_WEAPONS: strcpy(buffer, "WEAPON 0"); break;
+ case CLIENT_ITEMS: strcpy(buffer, "ITEM 0"); break;
+ case CLIENT_ROUNDS: strcpy(buffer, "ROUNDS"); break;
+ case CLIENT_TEAMS: strcpy(buffer, "TEAMS 0");
+ global.updateMenu = TRUE; break;
+ case CLIENT_WALL_TYPE: strcpy(buffer, "WALLTYPE"); break;
+ case CLIENT_BOXED: strcpy(buffer, "BOXED"); break;
+ case CLIENT_NAME: strcpy(buffer, "PLAYERNAME 0"); break;
+ case CLIENT_TANK_HEALTH: strcpy(buffer, "HEALTH 0"); break;
+ default: buffer[0] = '\0';
+ }
+ towrite = strlen(buffer);
+ written = write(socket_number, buffer, strlen(buffer));
+ if (written < towrite)
+ fprintf(stderr,
+ "%s:%d: Warning: Only %d/%d bytes sent to server\n",
+ __FILE__, __LINE__, written, towrite);
+ } // end of getting more info
+ } // our game stage went up
+ else // we got data, but our game stage did not go up
+ {
+ if (fired)
+ {
+ if ( (global.client_player) && (global.client_player->tank) )
+ {
+ fired = false;
+ if (global.client_player->tank->cw < WEAPONS)
+ SAFE_WRITE(socket_number, "WEAPON %d",
+ global.client_player->tank->cw)
+ else
+ SAFE_WRITE(socket_number, "ITEM %d",
+ global.client_player->tank->cw - WEAPONS)
+ }
+ }
+ else if (game_stage == CLIENT_SURFACE)
+ {
+ SAFE_WRITE(socket_number, "SURFACE %d", surface_x)
+ surface_x++;
+ }
+ else if (game_stage == CLIENT_ITEMS)
+ {
+ SAFE_WRITE(socket_number, "ITEM %d", item_number)
+ item_number++;
+ }
+ else if (game_stage == CLIENT_TANK_POSITION)
+ {
+ SAFE_WRITE(socket_number, "TANKPOSITION %d", tank_position)
+ tank_position++;
+ if (tank_position >= env.numGamePlayers)
+ tank_position = 0;
+ }
+ else if (game_stage == CLIENT_TANK_HEALTH)
+ {
+ SAFE_WRITE(socket_number, "HEALTH %d", tank_health)
+ tank_health++;
+ if (tank_health >= env.numGamePlayers)
+ tank_health = 0;
+ }
+ else if (game_stage == CLIENT_TEAMS)
+ {
+ SAFE_WRITE(socket_number, "TEAMS %d", team_number)
+ team_number++;
+ }
+ else if (game_stage == CLIENT_NAME)
+ {
+ SAFE_WRITE(socket_number, "PLAYERNAME %d", name_number)
+ name_number++;
+ }
+ else if (game_stage == CLIENT_WEAPONS)
+ {
+ SAFE_WRITE(socket_number, "WEAPON %d", weapon_number)
+ weapon_number++;
+ }
+ else if (game_stage == CLIENT_PLAYING)
+ {
+ time_clock++;
+ if (time_clock > 1) // check positions every few inputs
+ {
+ time_clock = 0;
+ if (surface_x < env.screenWidth)
+ {
+ game_stage = CLIENT_SURFACE;
+ SAFE_WRITE(socket_number, "SURFACE %d", surface_x)
+ surface_x++;
+ }
+ else
+ {
+ game_stage = CLIENT_TANK_POSITION;
+ tank_position = 1;
+ SAFE_WRITE(socket_number, "TANKPOSITION %d", 0)
+ } // game stage stuff
+ }
+ } // end of playing commands
+ }
+
+ } // end of we got something besides the close command
+
+ }
+ else // connection was broken
+ {
+ close(socket_number);
+ printf("Server closed connection.\n");
+ end_of_round = TRUE;
+ }
+ }
+
+ class_ = 0;
+ while (class_ < CLASS_COUNT) {
+ if (CLASS_TANK == class_) {
+ ++class_;
+ continue;
+ }
+
+ global.getHeadOfClass(static_cast<eClasses>(class_), &my_object);
+ while(my_object) {
+ my_object->getNext(&next_obj);
+
+ if (CLASS_EXPLOSION == class_)
+ static_cast<EXPLOSION*>(my_object)->explode();
+
+ my_object->applyPhysics();
+
+ if (my_object->destroy) {
+ my_object->requireUpdate();
+ my_object->update();
+ delete my_object;
+ if (CLASS_TELEPORT == class_)
+ time_clock = 2;
+ }
+
+ if ( (CLASS_BEAM == class_)
+ || (CLASS_MISSILE == class_)
+ || (CLASS_EXPLOSION == class_)
+ || (CLASS_TELEPORT == class_) )
+ stuff_going_down = TRUE;
+
+ my_object = next_obj;
+ }
+ ++class_;
+ }
+
+ global.slideLand();
+
+ // update everything on the screen
+ if (global.updateMenu)
+ draw_top_bar ();
+
+ if (screen_update) {
+ screen_update = false;
+ global.make_fullUpdate();
+ }
+ global.replace_canvas ();
+
+ screen_update = true;
+
+ class_ = 0;
+ while (class_ < CLASS_COUNT) {
+ global.getHeadOfClass(static_cast<eClasses>(class_), &my_object);
+ while(my_object) {
+ my_object->draw();
+ if (CLASS_FLOATTEXT == class_)
+ my_object->requireUpdate();
+ my_object->update();
+ my_object->getNext(&my_object);
+ }
+ ++class_;
+ }
+
+ global.do_updates();
+
+
+ // check for input from the user
+ if ( keypressed() )
+ {
+ my_key = readkey();
+ my_key = my_key >> 8;
+ if (my_key == KEY_SPACE)
+ {
+ Client_Fire(global.client_player, socket_number);
+ fired = true;
+ }
+ else if (my_key == KEY_ESC)
+ {
+ end_of_round = TRUE;
+ close(socket_number);
+ }
+ else if (my_key == KEY_UP)
+ {
+ Client_Power(global.client_player, CLIENT_UP);
+ }
+ else if (my_key == KEY_DOWN)
+ {
+ Client_Power(global.client_player, CLIENT_DOWN);
+ }
+ else if (my_key == KEY_LEFT)
+ {
+ Client_Angle(global.client_player, CLIENT_LEFT);
+ }
+ else if (my_key == KEY_RIGHT)
+ {
+ Client_Angle(global.client_player, CLIENT_RIGHT);
+ }
+ else if ( (my_key == KEY_Z) || (my_key == KEY_BACKSPACE) )
+ {
+ Client_Cycle_Weapon(global.client_player, CYCLE_BACK);
+ }
+ else if ( (my_key == KEY_C) || (my_key == KEY_TAB) )
+ {
+ Client_Cycle_Weapon(global.client_player, CYCLE_FORWARD);
+ global.updateMenu = TRUE;
+ }
+ screen_update = false;
+ global.updateMenu = TRUE;
+ }
+ // pause for a moment
+ // if (game_stage < CLIENT_PLAYING)
+ if (stuff_going_down)
+ {
+ LINUX_SLEEP;
+ stuff_going_down = FALSE;
+ }
+ }
+
+ // we should clean up here
+ for (count = 0; count < env.numGamePlayers; count++)
+ {
+ if (env.players[count]->tank)
+ {
+ delete env.players[count]->tank;
+ env.players[count]->tank = NULL;
+ }
+ }
+
+ return keep_playing;
+}
-#endif
+#endif // NETWORK
diff --git a/src/client.h b/src/client.h
index 488ed37..9c2946f 100644
--- a/src/client.h
+++ b/src/client.h
@@ -1,9 +1,6 @@
#ifndef CLIENT_HEADER_FILE__
#define CLIENT_HEADER_FILE__
-#include "globaldata.h"
-#include "environment.h"
-
#ifdef NETWORK
#define CLIENT_VERSION 1
@@ -42,26 +39,25 @@
// This function takes some data from the server
// and tries to figure out what to do with it.
// The game stage is returned.
-int Parse_Client_Data(GLOBALDATA *global, ENVIRONMENT *env, char *buffer);
+int Parse_Client_Data(char *buffer);
// Draws a background
-void Create_Sky(ENVIRONMENT *env, GLOBALDATA *global);
+void Create_Sky();
// Sends fire command to the server
// Message must be in format "FIRE item angle power"
int Client_Fire(PLAYER *my_player, int my_socket);
-
-
int Client_Power(PLAYER *my_player, int up_or_down);
-
int Client_Angle(PLAYER *my_player, int left_or_right);
-
int Client_Cycle_Weapon(PLAYER *my_player, int forward_or_back);
// Take an error code and return a string with readable info.
// The returning string should NOT be freed after use.
-char *Explain_Error(GLOBALDATA *global, int error_code);
+// Note: This is nowhere used. ( REMOVEME ??? )
+const char* Explain_Error(int32_t error_code);
+
+int Game_Client(int socket_number);
#endif
diff --git a/src/clock.cpp b/src/clock.cpp
new file mode 100644
index 0000000..e733ad2
--- /dev/null
+++ b/src/clock.cpp
@@ -0,0 +1,66 @@
+#include "clock.h"
+
+#include <chrono>
+
+/// === Used clocks and time granularity ===
+using atanks_clock_t = std::chrono::steady_clock;
+
+#if defined(ATANKS_IS_MSVC) && !defined(ATANKS_IS_AT_LEAST_MSVC13)
+ // Note: this is a bug in vc12, that is fixed in vc13.
+ // See: https://connect.microsoft.com/VisualStudio/feedback/details/858357/steady-clock-now-returning-the-wrong-type
+# include "winclock.h"
+ using time_point_t = std::chrono::time_point<std::chrono::system_clock>;
+#else
+ using time_point_t = std::chrono::time_point<atanks_clock_t>;
+#endif // MSVC++ 2013 bug
+
+using clock_ms_t = std::chrono::milliseconds;
+using clock_us_t = std::chrono::microseconds;
+
+/// === Helper macros to not have ridiculously long lines ===
+#define CLOCK_NOW atanks_clock_t::now()
+#define MS_CAST(x) static_cast<int32_t>(std::chrono::duration_cast<clock_ms_t>(x).count())
+#define US_CAST(x) static_cast<int32_t>(std::chrono::duration_cast<clock_us_t>(x).count())
+
+/// === Internal values only used here ===
+static time_point_t game_us_end = CLOCK_NOW;
+static time_point_t game_us_start = CLOCK_NOW;
+static time_point_t menu_ms_end = CLOCK_NOW;
+static time_point_t menu_ms_start = CLOCK_NOW;
+
+
+/// === Function implementations ===
+
+/// REMOVE_VS12_WORKAROUND
+#if !defined(ATANKS_IS_MSVC) || defined(ATANKS_IS_AT_LEAST_MSVC13)
+int32_t game_us_get()
+{
+ game_us_end = CLOCK_NOW;
+ int32_t used_us = US_CAST(game_us_end - game_us_start);
+ game_us_start = game_us_end;
+ return used_us > 0 ? used_us : 0;
+}
+
+
+void game_us_reset()
+{
+ game_us_end = CLOCK_NOW;
+ game_us_start = game_us_end;
+}
+
+
+int32_t menu_ms_get()
+{
+ menu_ms_end = CLOCK_NOW;
+ int32_t used_us = MS_CAST(menu_ms_end - menu_ms_start);
+ menu_ms_start = menu_ms_end;
+ return used_us > 0 ? used_us : 0;
+}
+
+
+void menu_ms_reset()
+{
+ menu_ms_end = CLOCK_NOW;
+ menu_ms_start = menu_ms_end;
+}
+#endif // !ATANKS_IS_MSVC
diff --git a/src/clock.h b/src/clock.h
new file mode 100644
index 0000000..bbf4e7b
--- /dev/null
+++ b/src/clock.h
@@ -0,0 +1,25 @@
+#pragma once
+#ifndef ATANKS_SRC_CLOCK_H_INCLUDED
+#define ATANKS_SRC_CLOCK_H_INCLUDED
+
+#include "debug.h"
+#include <cstdint>
+
+int32_t game_us_get();
+void game_us_reset();
+int32_t menu_ms_get();
+void menu_ms_reset();
+
+/// REMOVE_VS12_WORKAROUND
+#if defined(ATANKS_IS_MSVC) && !defined(ATANKS_IS_AT_LEAST_MSVC13)
+void win_clock_deinit();
+void win_clock_init();
+#define WIN_CLOCK_INIT win_clock_init();
+#define WIN_CLOCK_REMOVE win_clock_deinit();
+#else
+#define WIN_CLOCK_INIT {}
+#define WIN_CLOCK_REMOVE {}
+#endif
+
+#endif // ATANKS_SRC_CLOCK_H_INCLUDED
+
diff --git a/src/debris_pool.cpp b/src/debris_pool.cpp
new file mode 100644
index 0000000..667a171
--- /dev/null
+++ b/src/debris_pool.cpp
@@ -0,0 +1,238 @@
+#include "debris_pool.h"
+#include "main.h"
+
+#include <cassert>
+
+/// @brief sDebrisItem default constructor
+sDebrisItem::sDebrisItem(int32_t diameter_, sDebrisItem* next_) :
+ idx( (diameter_ / 2) - 1),
+ next(next_)
+{
+ if (diameter_ && (idx >= 0) && (idx < 5)) {
+ bmp = create_bitmap(diameter_ + 1, diameter_ + 1);
+ }
+
+ if (bmp && next) {
+ prev = next->prev;
+ next->prev = this;
+ if (prev)
+ prev->next = this;
+ }
+
+ if (bmp) {
+ clear_to_color(bmp, PINK);
+ } else
+ // unusable...
+ delete this;
+}
+
+
+/// @brief take this out of the list and free bmp data
+sDebrisItem::~sDebrisItem()
+{
+ if (bmp)
+ destroy_bitmap(bmp);
+ bmp = nullptr;
+
+ if (prev)
+ prev->next = next;
+ if (next)
+ next->prev = prev;
+ prev = nullptr;
+ next = nullptr;
+}
+
+
+/// @brief create an empty pool with a set limit
+sDebrisPool::sDebrisPool(int32_t limit_) :
+ limit(limit_ * 5) // The limit is per debris size
+{
+ DEBUG_LOG_OBJ("Debris Pool", "Pool created with limit %d", limit);
+
+ // memset initialization, Visual C++ 2013 can not do array initialization
+ memset(avail, 0, sizeof(int32_t) * 5);
+ memset(counts, 0, sizeof(int32_t) * 5);
+ memset(heads, 0, sizeof(item_t*) * 5);
+ memset(tails, 0, sizeof(item_t*) * 5);
+
+ // Pre-create a third of the possible pool elements now,
+ // so an evenly distributed base is given at any time.
+ int32_t max_items = limit / 3 / 5;
+
+ for (int32_t r = 1; r < 6; ++r) {
+ for (int32_t i = 0; i < max_items; ++i)
+ create_item(r);
+ }
+}
+
+
+/// @brief destroy all pool items
+sDebrisPool::~sDebrisPool()
+{
+ for (int32_t r = 0; r < 5; ++r) {
+ while (heads[r]) {
+ item_t* curr = heads[r];
+ heads[r] = curr->next;
+ delete curr;
+ --counts[r];
+ --count_all;
+ }
+
+ if (counts[r]) {
+ cerr << "ERROR: debris pool count for radius " << (r + 1);
+ cerr << " should be zero but is " << counts[r] << " !" << endl;
+ }
+ }
+ if (count_all) {
+ cerr << "ERROR: total debris pool count should be zero but is ";
+ cerr << count_all << " !" << endl;
+ }
+}
+
+
+/// @brief centralized creation, so it can be used by both the ctor and get_item()
+sDebrisItem* sDebrisPool::create_item(int32_t radius)
+{
+ item_t* res = nullptr;
+
+ if (count_all < limit) {
+
+ int32_t idx = radius - 1;
+
+ res = new sDebrisItem(radius * 2, heads[idx]);
+
+ if (res) {
+ // Insert item as new head
+ heads[idx] = res;
+ if (nullptr == tails[idx])
+ tails[idx] = res;
+
+ // Count the item
+ ++count_all;
+ ++counts[idx];
+ ++avail[idx];
+ DEBUG_LOG_OBJ("Debris", "New maximum number: %d", count_all)
+ }
+
+ }
+
+ return res;
+}
+
+
+/** @brief mark one item as unused.
+ * Do not do this yourself, although it is a struct.
+ * The pool needs to keep track of how many items are available.
+**/
+void sDebrisPool::free_item(item_t* item)
+{
+ if (item && !item->is_free) {
+ // Reset item to PINK
+ if (item->bmp)
+ clear_to_color(item->bmp, PINK);
+
+ // Mark item as being free
+ item->is_free = true;
+ ++avail[item->idx];
+ }
+}
+
+
+/** @brief get the next free item with the specified radius.
+ *
+ * If no item is available, a new one will be created unless the
+ * current limit is reached.
+**/
+sDebrisItem* sDebrisPool::get_item (int32_t radius)
+{
+ assert( (radius > 0) && (radius < 6)
+ && "ERROR: Only radius in the range [1;5] supported!");
+
+ if ( (radius < 1) || (radius > 5) )
+ return nullptr;
+
+ int32_t idx = radius - 1;
+
+ // See whether an item is available:
+ if (avail[idx]) {
+ item_t* curr = heads[idx];
+ while (curr && !curr->is_free)
+ curr = curr->next;
+
+ assert(curr && "ERROR: avail[idx] is not 0, but no item available!");
+
+ if (curr) {
+ --avail[idx];
+ curr->is_free = false;
+ return curr;
+ } else {
+ cerr << "No item for radius " << radius << " found, but ";
+ cerr << avail[idx] << " should be available!" << endl;
+ avail[idx] = 0;
+ }
+ }
+
+ /* --- If this failed, or no items are available, create a new item --- */
+
+ // Check whether the limit is hit already
+ if (count_all >= limit) {
+ /* If the limit is hit, this is the strategy:
+ * a) look which index has the most items
+ * b) Take the first free item and delete it
+ * -> Voilà, another item is available!
+ * <- no item free? Doesn't matter, we are busy now!
+ * ( Note : If the list with the most items has none free,
+ * then it is safe to assume that none has. )
+ */
+
+ DEBUG_LOG_OBJ("Debris Limit", "The limit of %d was reached", limit)
+
+ int32_t hasMaxIdx = 0;
+
+ for (int32_t i = 0; i < 5; ++i) {
+ if ( (i != idx)
+ && avail[i]
+ && ( (counts[i] > counts[hasMaxIdx])
+ || !avail[hasMaxIdx]) )
+ hasMaxIdx = i;
+ }
+
+ if (avail[hasMaxIdx]) {
+ item_t* curr = heads[hasMaxIdx];
+ while (curr && !curr->is_free)
+ curr = curr->next;
+
+ // If a free item is found, delete it!
+ if (curr) {
+ // fix head/tail if affected
+ if (heads[hasMaxIdx] == curr)
+ heads[hasMaxIdx] = curr->next;
+ if (tails[hasMaxIdx] == curr)
+ tails[hasMaxIdx] = curr->prev;
+ delete curr;
+ --avail[hasMaxIdx];
+ --counts[hasMaxIdx];
+ --count_all;
+ } else {
+ cerr << "No item for radius " << (hasMaxIdx + 1) << " found, but ";
+ cerr << avail[hasMaxIdx] << " should be available!" << endl;
+ avail[hasMaxIdx] = 0;
+ }
+ }
+
+ } // End of limit reached resolving strategy
+
+ // If the limit is not reached (or relieved) at this point,
+ // a new item can be created
+ item_t* new_item = create_item(radius);
+
+ if (new_item) {
+ // Note: create_item() has already raised counts[idx],
+ // count_all and avail[idx].
+ --avail[idx];
+ new_item->is_free = false;
+ }
+
+ return new_item;
+}
+
diff --git a/src/debris_pool.h b/src/debris_pool.h
new file mode 100644
index 0000000..f4df087
--- /dev/null
+++ b/src/debris_pool.h
@@ -0,0 +1,73 @@
+#pragma once
+#ifndef ATANKS_SRC_DEBRIS_POOL_H_INCLUDED
+#define ATANKS_SRC_DEBRIS_POOL_H_INCLUDED
+
+/*
+ * atanks - obliterate each other with oversize weapons
+ * Copyright (C) 2003 Thomas Hudson
+ *
+ * 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.
+ *
+ */
+
+#include "main.h"
+
+/** @struct sDebrisItem
+ * @brief represent one entry in the debris pool
+**/
+struct sDebrisItem
+{
+ BITMAP* bmp = nullptr;
+ int32_t idx = 0; //!< calculated index from diameter (aka radius/2-1)
+ bool is_free = true;
+ sDebrisItem* next = nullptr;
+ sDebrisItem* prev = nullptr;
+
+ explicit sDebrisItem(int32_t diameter_, sDebrisItem* next_);
+ ~sDebrisItem();
+};
+
+
+/** @struct sDebrisPool
+ * @brief A pool of bitmaps used to throw around dirt.
+ *
+ * Note: Currently the pool is limited to radius [1;5] the
+ * debris can have. That is five series of bitmaps.
+**/
+struct sDebrisPool
+{
+ typedef sDebrisItem item_t;
+
+ explicit sDebrisPool(int32_t limit_);
+ ~sDebrisPool();
+
+ void free_item(item_t* item);
+ item_t* get_item (int32_t radius);
+
+private:
+
+ item_t* create_item(int32_t radius);
+
+ int32_t avail[5]; //!< How many items are available for which radius.
+ int32_t count_all = 0; //!< Sum of all created items.
+ int32_t counts[5]; //!< How many items are used for which radius.
+ int32_t limit = 0; //!< The limit of the pool, set on pool creation.
+ item_t* heads[5];
+ item_t* tails[5];
+};
+
+
+#endif // ATANKS_SRC_DEBRIS_POOL_H_INCLUDED
+
diff --git a/src/debug.cpp b/src/debug.cpp
new file mode 100644
index 0000000..0499a11
--- /dev/null
+++ b/src/debug.cpp
@@ -0,0 +1,64 @@
+#include "debug.h"
+
+#include <ctime>
+#include <cstdarg>
+#include <iostream>
+#include <mutex>
+
+#if defined(ATANKS_DEBUG)
+
+#if defined(ATANKS_IS_WINDOWS) || defined(ATANKS_DEBUG_LOGTOFILE)
+# include <cstdio>
+#endif
+
+
+// Log Mutex
+std::mutex log_lock;
+
+
+/// @brief use with DEBUG_LOG to get debug dependent positional information for free!
+void debug_log(const char* moduleName, const char* title, const char* message, ...)
+{
+ char timebuf[21];
+ time_t t;
+ struct tm tm_;
+ char xMsg[512];
+
+ // Create timestamp
+ atanks_tzset();
+ t = time(NULL);
+ atanks_localtime(&tm_, &t);
+ atanks_snprintf(timebuf, 20, "%04d.%02d.%02d %02d:%02d:%02d",
+ tm_.tm_year + 1900, tm_.tm_mon + 1, tm_.tm_mday,
+ tm_.tm_hour, tm_.tm_min, tm_.tm_sec);
+ timebuf[20] = 0x0;
+
+ // Create message
+ va_list vl;
+ va_start(vl, message);
+ vsprintf_s(xMsg, 512, message, vl);
+ va_end(vl);
+
+ log_lock.lock();
+
+#if defined(ATANKS_IS_WINDOWS) || defined(ATANKS_DEBUG_LOGTOFILE)
+ // Unfortunately, for everything to work right,
+ // a WinApp must be created. So write the log msg
+ // to atanks.log instead.
+ FILE* out = fopen("atanks.log", "a");
+ if (out) {
+ fprintf(out, "%s : %s : \"%s\" - %s\n",
+ timebuf, moduleName, title, xMsg);
+ fclose(out);
+ }
+#endif // MSVC or explicit logging to atanks.log
+#if !defined(ATANKS_IS_WINDOWS)
+ fprintf(stdout, "%s : %s : \"%s\" - %s\n",
+ timebuf, moduleName, title, xMsg);
+#endif // !Windows
+
+
+ log_lock.unlock();
+}
+
+#endif
diff --git a/src/debug.h b/src/debug.h
new file mode 100644
index 0000000..d86b1df
--- /dev/null
+++ b/src/debug.h
@@ -0,0 +1,168 @@
+#pragma once
+#ifndef ATANKS_SRC_DEBUG_H_INCLUDED
+#define ATANKS_SRC_DEBUG_H_INCLUDED
+
+#include <cstdio>
+
+/********************************************************
+ * Determine whether we build for BSD, Linux or Windows *
+ *******************************************************/
+#if defined(_WIN32) || defined(__WIN32__)
+# define ATANKS_IS_WINDOWS
+# if defined(_MSC_VER)
+# define ATANKS_IS_MSVC
+ // See whether the chrono bug is fixed:
+# if (_MSC_VER >= 1900)
+# define ATANKS_IS_AT_LEAST_MSVC13
+# endif // VS 2015
+# endif // _MSVC_VER
+#endif // Win 32
+
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__)
+# define ATANKS_IS_BSD
+#endif // BSD
+
+#if defined(__linux__) || defined(__linux) || defined(linux) || defined(ATANKS_IS_BSD)
+# define ATANKS_IS_LINUX
+#endif // Linux
+
+#if !defined(ATANKS_IS_WINDOWS) && !defined(ATANKS_IS_LINUX)
+# error "Only Windows, Linux and BSD are supported at the moment"
+#endif // Windows versus Linux
+
+
+/**********************************************************
+ * Define some macros to work around compiler differences *
+ *********************************************************/
+#if defined(ATANKS_IS_MSVC)
+# define atanks_snprintf(target, char_count, fmt, ...) \
+ _snprintf_s(target, char_count + 1, char_count, fmt, __VA_ARGS__)
+# define atanks_tzset() _tzset()
+# define atanks_localtime(tm_p, time_p) localtime_s(tm_p, time_p)
+#else
+# define atanks_snprintf(target, char_count, fmt, ...) \
+ snprintf(target, char_count, fmt, __VA_ARGS__)
+# define atanks_tzset() tzset()
+# define atanks_localtime(tm_p, time_p) localtime_r(time_p, tm_p)
+# define vsprintf_s(tgt, size, fmt, listvar) vsprintf(tgt, fmt, listvar)
+#endif // MSVC++ versus GCC/Clang
+
+
+// Windows special definitions
+#if defined(ATANKS_IS_WINDOWS)
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
+
+# ifndef WIN32_EXTRA_LEAN
+# define WIN32_EXTRA_LEAN
+# endif
+
+# ifndef VC_EXTRALEAN
+# define VC_EXTRALEAN
+# endif
+
+# define NOMINMAX
+
+#endif // IS_WINDOWS
+
+
+/// ==============================================================
+/// === The following is only used if ATANKS_DEBUG is defined. ===
+/// === Otherwise the macro DEBUG_LOG() does nothing. ===
+/// ==============================================================
+
+
+#if defined(ATANKS_DEBUG)
+
+// ATANKS_GET_FILE - This macro simply sets target to the current filename without a path
+#ifdef ATANKS_IS_MSVC
+# define ATANKS_GET_FILE(target) { \
+ char _atanks_fname_info[64]; \
+ char _atanks_extension[8]; \
+ _splitpath_s(__FILE__, NULL, 0, NULL, 0, _atanks_fname_info, 63, _atanks_extension, 7); \
+ atanks_snprintf(target, 255, "%s%s", _atanks_fname_info, \
+ _atanks_extension); \
+ }
+#else
+# define ATANKS_GET_FILE(target) { \
+ atanks_snprintf(target, 255, "%s", basename(__FILE__)); \
+ }
+#endif
+
+
+// ATANKS_GET_POS - This macro gathers positional information
+#ifdef ATANKS_IS_MSVC
+# define ATANKS_GET_POS(target) { \
+ ATANKS_GET_FILE(target) \
+ atanks_snprintf(target, 255, "%-10s:%4d %-24s", target, __LINE__, __FUNCTION__); \
+ }
+#else
+# define ATANKS_GET_POS(target) { \
+ atanks_snprintf(target, 255, "%-10s:%4d %-24s", basename(__FILE__), __LINE__, __FUNCTION__); \
+ }
+#endif
+
+
+// DEBUG_LOG - This macro is a wrapper for debug_log
+#define DEBUG_LOG(Title, Msg, ...) { \
+ char _atanks_trace_info[256]; \
+ ATANKS_GET_POS(_atanks_trace_info) \
+ debug_log(_atanks_trace_info, Title, Msg, __VA_ARGS__); \
+ }
+
+// declaration if debug_log
+void debug_log(const char* moduleName, const char* title, const char* message, ...);
+
+
+#else
+
+#define DEBUG_LOG(...) {}
+
+#endif // defined(ATANKS_DEBUG)
+
+// Enable / Disable specific debug message flavours
+// Those do nothing, even if enabled, if ATANKS_DEBUG is not defined
+
+#ifdef ATANKS_DEBUG_AIMING
+# define DEBUG_LOG_AIM(Title, Msg, ...) DEBUG_LOG(Title, Msg, __VA_ARGS__)
+#else
+# define DEBUG_LOG_AIM(...) {}
+#endif // ATANKS_DEBUG_AIMING
+
+#ifdef ATANKS_DEBUG_EMOTIONS
+# define DEBUG_LOG_EMO(Title, Msg, ...) DEBUG_LOG(Title, Msg, __VA_ARGS__)
+#else
+# define DEBUG_LOG_EMO(...) {}
+#endif // ATANKS_DEBUG_EMOTIONS
+
+// The next is a helper so those messages can show up whenever
+// either aiming or emotions shall be logged.
+#if defined(ATANKS_DEBUG_AIMING) || defined(ATANKS_DEBUG_EMOTIONS)
+# define DEBUG_LOG_AI(Title, Msg, ...) DEBUG_LOG(Title, Msg, __VA_ARGS__)
+#else
+# define DEBUG_LOG_AI(...) {}
+#endif // ATANKS_DEBUG_AIMING || ATANKS_DEBUG_EMOTIONS
+
+
+#ifdef ATANKS_DEBUG_FINANCE
+# define DEBUG_LOG_FIN(Title, Msg, ...) DEBUG_LOG(Title, Msg, __VA_ARGS__)
+#else
+# define DEBUG_LOG_FIN(...) {}
+#endif // ATANKS_DEBUG_FINANCE
+
+#ifdef ATANKS_DEBUG_OBJECTS
+# define DEBUG_LOG_OBJ(Title, Msg, ...) DEBUG_LOG(Title, Msg, __VA_ARGS__)
+#else
+# define DEBUG_LOG_OBJ(...) {}
+#endif // ATANKS_DEBUG_OBJECTS
+
+#ifdef ATANKS_DEBUG_PHYSICS
+# define DEBUG_LOG_PHY(Title, Msg, ...) DEBUG_LOG(Title, Msg, __VA_ARGS__)
+#else
+# define DEBUG_LOG_PHY(...) {}
+#endif // ATANKS_DEBUG_OBJECTS
+
+
+#endif // ATANKS_SRC_DEBUG_H_INCLUDED
+
diff --git a/src/decor.cpp b/src/decor.cpp
new file mode 100644
index 0000000..ee37d46
--- /dev/null
+++ b/src/decor.cpp
@@ -0,0 +1,441 @@
+#include "decor.h"
+#include "sound.h"
+#include "tank.h"
+
+#include <cassert>
+
+/// @brief Default constructor
+DECOR::DECOR(double x_, double y_, double xv_, double yv_,
+ int32_t maxRadius, int32_t type_, int32_t delay_) :
+ PHYSICAL_OBJECT(false),
+ curWind(global.wind),
+ delay(delay_),
+ maxGravAccel(-4. * env.gravity * env.FPS_mod),
+ maxWind(env.windstrength),
+ maxWindAccel(global.wind * env.FPS_mod),
+ radius(maxRadius),
+ type(type_)
+{
+ x = x_;
+ y = y_;
+ xv = xv_;
+ yv = yv_;
+
+ if (DECOR_DIRT == type) {
+ // The core data is taken from the meteors.
+ weapType = SML_METEOR
+ + (maxRadius / 2); // results in (0, 1, 1, 2, 2) for radius [1;5]
+ mass = naturals[weapType - WEAPONS].mass;
+ drag = naturals[weapType - WEAPONS].drag / 5.;
+
+ // Special physics for dirt debris:
+ physType = PT_DIRTBOUNCE;
+
+ // Only keep dirt alive while it is really moving,
+ // if it becomes too slow, only keep it for 2 seconds
+ maxAge = 2 * env.frames_per_second;
+
+ // The diameter is just used so it does not have to
+ // be calculated each time updateDirt() is called.
+ diameter = radius * 2;
+
+ // Calculate how many pixels are needed per call to updateDirt()
+ grabPerCall = ((diameter + 1) * (diameter + 1)) / (delay > 1 ? delay : 1);
+ } else if (DECOR_SMOKE == type) {
+ int32_t tempCol = 128 + (rand () % 64);
+
+ if (maxRadius <= 3)
+ radius = 3;
+ else
+ radius = 3 + (rand () % (maxRadius - 2));
+
+ color = makecol (tempCol, tempCol, tempCol);
+ mass = 1.0 + (static_cast<double>(rand () % 5) / 10.);
+ drag = 0.9 + (static_cast<double>(rand () % 90) / 100.);
+
+ // maximum age depends on the maximum radius and the real radius,
+ // plus 0 to 2 extra seconds.
+ maxAge = ( (maxRadius - (maxRadius - radius)) / 3) + (rand() % 3);
+ maxAge *= env.frames_per_second;
+
+ // Special physics for smoke, only for repulsion
+ physType = PT_SMOKE;
+
+ // Smoke does not need the dirt grabber
+ ready = true;
+ } else
+ destroy = true;
+
+ maxVel = env.maxVelocity * (1.20 + (mass / (.01 * MAX_POWER)));
+
+ // Add to the chain:
+ global.addObject(this);
+}
+
+
+/// @brief Constructor with bitmap
+DECOR::DECOR(double x_, double y_, double xv_, double yv_,
+ int32_t maxRadius, int32_t type_, int32_t delay_,
+ sDebrisItem* deb_item, sDebrisItem* met_item) :
+ DECOR(x_, y_, xv_, yv_, maxRadius, type_, delay_)
+{
+ // Everything done in delegated ctor, only img to set
+ dirt = deb_item;
+ setBitmap(dirt ? dirt->bmp : nullptr);
+ // Note: It is safe to distribute dirt->bmp, because bitmap normally holds
+ // global graphics and must not be destroyed.
+ meteor = met_item;
+
+ if ( (nullptr == dirt) || !hasBitmap() )
+ // Can't work without...
+ destroy = true;
+}
+
+
+/// @brief default destructor
+DECOR::~DECOR()
+{
+ if (DECOR_DIRT == type) {
+ // Draw dirt on terrain and add land slide
+ rotate_sprite (global.terrain, dirt->bmp, x - radius, y - radius, itofix (angle));
+ global.addLandSlide(x - radius - 1, x + radius + 1, false);
+ }
+
+ if (dirt) {
+ global.free_debris_item(dirt);
+ dirt = nullptr;
+ }
+
+ if (meteor) {
+ global.free_debris_item(meteor);
+ meteor = nullptr;
+ }
+
+ // Update the last drawing area
+ int32_t calcRadius = radius;
+
+ if (DECOR_DIRT == type)
+ ++calcRadius;
+ else if (DECOR_SMOKE == type)
+ // The older, the larger...
+ calcRadius = static_cast<int32_t>(radius * (4.0 * age / maxAge));
+
+ setUpdateArea ( x - calcRadius - 1, y - calcRadius - 1,
+ (calcRadius * 2) + 2, (calcRadius * 2) + 2);
+ requireUpdate ();
+ this->update();
+
+ // Take out of the chain:
+ global.removeObject(this);
+}
+
+
+/// @brief let smoke drift and disperse with the wind
+void DECOR::applyPhysics ()
+{
+ if (destroy)
+ return;
+
+ if (delay > 0) {
+ --delay;
+ return;
+ }
+
+ // for detecting bounces
+ double old_yv = yv;
+
+ if (DECOR_DIRT == type) {
+
+ // Check whether movement ended
+ double movement = FABSDISTANCE2(xv, yv, 0, 0);
+ bool on_floor = isOnFloor(); // Needed again below.
+
+ if ( on_floor
+ && ( (hitSomething && (movement < 0.8))
+ || (movement < 0.2) ) ) {
+
+ // It ended!
+
+ // fix y:
+ int32_t dirt_bottom = y + dirt->bmp->h;
+ if ( ( (y - radius) > MENUHEIGHT)
+ && ( dirt_bottom < env.screenHeight)
+ && (PINK != getpixel(global.terrain, x, dirt_bottom)) )
+ --y;
+
+ xv = 0.;
+ yv = 0.;
+ destroy = true;
+ requireUpdate();
+
+ } else {
+ hitSomething = false; // Enable checking.
+
+ // Now apply physics
+ repulseDecor();
+ PHYSICAL_OBJECT::applyPhysics();
+
+ // Be sure x/y values are sane (Can drift into walls
+ // on rare wind conditions.)
+ if (x < 2) x = 2;
+ if (x > (env.screenWidth - 2)) x = env.screenWidth - 2;
+ if (y > (env.screenHeight - 2)) y = env.screenHeight - 2;
+
+ // Maybe play a sound on bounce
+ if ( !global.skippingComputerPlay
+ && (old_yv > .5)
+ && (yv < -0.1) )
+ play_natural_sound(DIRT_FRAGMENT, x, radius * 32,
+ 1200 - (radius * 50));
+ }
+
+ // raise age if movement is below 0.5
+ if ( ( on_floor || (FABSDISTANCE2(xv, yv, 0, 0) < .5) )
+ && (++age > maxAge) )
+ destroy = true;
+
+ } else if (DECOR_SMOKE == type) {
+ // Apply wind first
+ int32_t ageMod = ROUND(std::abs(curWind / (maxWind / 2.0))) + 1;
+
+ /* This produces: (with max wind = 8)
+ * wind = 0 : round(0 / (8 / 2)) + 1 = round(0 / 4) + 1 = 0 + 1 = 1 <-- normal aging
+ * wind = 1 : round(1 / (8 / 2)) + 1 = round(1 / 4) + 1 = 0 + 1 = 1 <-- normal aging
+ * wind = 4 : round(4 / (8 / 2)) + 1 = round(4 / 4) + 1 = 1 + 1 = 2 <-- raised aging
+ * wind = 6 : round(6 / (8 / 2)) + 1 = round(6 / 4) + 1 = 2 + 1 = 3 <-- fast aging
+ * wind = 8 : round(8 / (8 / 2)) + 1 = round(8 / 4) + 1 = 2 + 1 = 3 <-- fast aging
+ */
+ age += ageMod;
+
+ // Set further values
+ // Try to reach half distance to the maximum values per second
+ double xaccel = ((xv + maxWindAccel) / 2)
+ / static_cast<double>(env.frames_per_second);
+ double yaccel = ((yv + maxGravAccel) / 2)
+ / static_cast<double>(env.frames_per_second / 10.);
+
+ // Apply current acceleration
+ xv += xaccel;
+ yv += yaccel;
+
+ // Add repulsion:
+ repulseDecor();
+
+ // Be sure that neither xv outruns wind nor yv is
+ // higher than reverse gravity
+ if (std::abs(xv) > std::abs(curWind))
+ xv = curWind;
+ if (yv < maxGravAccel)
+ yv = maxGravAccel;
+
+ // Don't push through the floor
+ if ( (y + yv) >= env.screenHeight) {
+ yv *= -0.5;
+ xv *= 0.95;
+ }
+
+ // The faster the smoke is blown by the wind, the less it rises:
+ if ( (yv < -1.) && (std::abs(xv) > 1.) )
+ yv = (yv + (yv / std::abs(xv))) / 2.;
+ // and if the smoke is going down, halve yv
+ else if (yv > 0.)
+ yv /= 2.;
+
+ // Now the velocity can be applied.
+ x += xv;
+ y += yv;
+
+ // Destroy the smoke if it goes off-screen or is diffused
+ int32_t calcRadius = static_cast<int32_t>(radius * (4.0 * age / maxAge));
+
+ if ( (x < (1 - calcRadius))
+ || (x >= (env.screenWidth + calcRadius))
+ || (y < (MENUHEIGHT - calcRadius))
+ || (age > maxAge) )
+ destroy = true;
+ }
+}
+
+
+/// @brief draw decor according to current settings and type.
+void DECOR::draw()
+{
+ if (!ready && !destroy) {
+ updateDirt();
+ if (ready) {
+ // finished! See whether there are enough pixels
+ if (gotPixels <= radius)
+ // nope.
+ destroy = true;
+ }
+ }
+
+ if (destroy || (delay > 0))
+ return;
+
+ int32_t calcRadius = radius;
+
+ if (DECOR_DIRT == type) {
+ // Rotate according to xv and yv
+ angle += yv + ((SIGNd(xv) * 5.) - xv);
+
+ // Be sure the angle is in order:
+ if (angle < 0) angle += 360;
+ if (angle > 360) angle -= 360;
+
+ // And draw it:
+ if (y > MENUHEIGHT) {
+ VIRTUAL_OBJECT::draw();
+ ++calcRadius;
+ }
+ } else if (DECOR_SMOKE == type) {
+ // The older, the larger...
+ calcRadius = static_cast<int32_t>(radius * (4.0 * age / maxAge));
+
+ drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
+ set_trans_blender (0, 0, 0, 255 - (255 * age / maxAge));
+ circlefill (global.canvas, x, y, calcRadius, color);
+ }
+
+ drawing_mode (global.current_drawing_mode, NULL, 0, 0);
+
+ setUpdateArea ( x - calcRadius - 1, y - calcRadius - 1,
+ (calcRadius * 2) + 2, (calcRadius * 2) + 2);
+ requireUpdate ();
+}
+
+
+/// In case of too much decor for the machine, allow forced ageing
+void DECOR::force_aging(int32_t frames)
+{
+ age += frames;
+ if (age > maxAge)
+ destroy = true;
+}
+
+
+/// return true if a dirt debris item "lies" on the floor, or is squeezed in a
+/// dirt slide.
+bool DECOR::isOnFloor()
+{
+ int32_t scr_r = env.screenWidth - 2; // shortcut;
+ int32_t scr_b = env.screenHeight - 2; // ditto;
+
+ // If the debris is above the screen or directly on the floor,
+ // return at once:
+ if (y <= MENUHEIGHT)
+ return false;
+ if (y >= (scr_b - radius))
+ return true;
+
+ // Use safe values:
+ int32_t round_x = ROUND(x);
+ int32_t round_y = ROUND(y);
+
+ // sanitize x value:
+ if (round_x < 1) round_x = 1;
+ else if (round_x > scr_r) round_x = scr_r;
+
+ // rounded boundaries, clipped to the screen:
+ int32_t left = std::max(1, round_x - radius);
+ int32_t top = std::max(MENUHEIGHT, round_y - radius);
+ int32_t right = std::min(scr_r, round_x + radius);
+ int32_t bottom = std::min(scr_b, round_y + radius);
+
+ // Go from left to right and check whether the surface is above the bottom.
+ int32_t surf_hits = 0;
+ bool on_floor = false;
+ for (int32_t i = left; !on_floor && (i <= right); ++i) {
+ if (global.surface[i].load(ATOMIC_READ) <= bottom) {
+ // Actually this could mean that the debris is within
+ // a hole in a mountain that hasn't been slid down, yet.
+ bool in_dirt = false;
+
+ for (int32_t j = bottom; !in_dirt && (j >= top) ; --j) {
+ if (PINK != getpixel(global.terrain, i, j))
+ in_dirt = true;
+ }
+
+ if (in_dirt && (++surf_hits >= radius) )
+ on_floor = true;
+ }
+ }
+
+ return on_floor;
+}
+
+
+/// DIRT and Smoke (somewhat) can be repulsed, too
+void DECOR::repulseDecor()
+{
+ TANK* lt = nullptr;
+ double xaccel = 0;
+ double yaccel = 0;
+
+ global.getHeadOfClass(CLASS_TANK, <);
+
+ while (lt) {
+ if (!lt->destroy) {
+
+ if (lt->repulse (x + xv, y + yv, &xaccel, &yaccel, physType)) {
+ xv += xaccel;
+ yv += yaccel;
+ }
+ }
+ lt->getNext(<);
+ }
+}
+
+
+/// Small scale dirt grabber
+void DECOR::updateDirt()
+{
+ int32_t togo = grabPerCall + 1;
+ double deb_rad = static_cast<double>(radius);
+
+ while (togo) {
+ double deb_dist = FABSDISTANCE2(static_cast<double>(grab_x),
+ static_cast<double>(grab_y),
+ deb_rad,
+ deb_rad);
+ if (deb_dist <= deb_rad) {
+ int32_t tcol = getpixel(dirt->bmp, grab_x, grab_y);
+
+ // If this is a meteor and the terrain had no pixel
+ // there, take one out of the rock instead.
+ if ( (PINK == tcol) && meteor)
+ tcol = getpixel(meteor->bmp, grab_x, grab_y);
+
+ // If this is valid, scorch the colour and put it back.
+ if (PINK != tcol) {
+ double deb_mod = deb_dist / deb_rad;
+ int32_t new_r = getr(tcol) / (1.25 + deb_mod);
+ int32_t new_g = getg(tcol) / (1.66 + deb_mod);
+ int32_t new_b = getb(tcol) / (1.33 + deb_mod);
+ putpixel(dirt->bmp, grab_x, grab_y, makecol(new_r, new_g, new_b));
+ ++gotPixels;
+ }
+ } // End of position in range
+
+ else
+ // If the position is not in range, erase the surplus pixel
+ putpixel(dirt->bmp, grab_x, grab_y, PINK);
+
+ // This point is done
+ --togo;
+
+ // Advance coordinates
+ if (++grab_x > diameter) {
+ grab_x = 0;
+ if (++grab_y > diameter) {
+ // end of work
+ togo = 0;
+ ready = true;
+ if (meteor) {
+ global.free_debris_item(meteor);
+ meteor = nullptr;
+ }
+ }
+ }
+ } // End of having pixels to grab
+}
diff --git a/src/decor.h b/src/decor.h
index ff29a2f..eaaa83a 100644
--- a/src/decor.h
+++ b/src/decor.h
@@ -21,149 +21,88 @@
* */
-#include "main.h"
#include "physobj.h"
-#include "environment.h"
-#include "globaldata.h"
+#include "debris_pool.h"
-enum decorTypes
+enum decorTypes
{
- DECOR_SMOKE
+ DECOR_SMOKE = 0,
+ DECOR_DIRT
};
class DECOR: public PHYSICAL_OBJECT
- {
- public:
- int type;
- int radius;
- int color;
-
- /* --- constructor --- */
- inline DECOR (GLOBALDATA *global, ENVIRONMENT *env, double xpos, double ypos, double xvel, double yvel,
- int maxRadius, int decorType):PHYSICAL_OBJECT()
- {
- type = decorType;
- initialise ();
- setEnvironment (env);
- player = NULL;
- _align = LEFT;
- _global = global;
- x = xpos;
- y = ypos;
- xv = xvel;
- yv = yvel;
- if (maxRadius <= 3)
- radius = 3;
- else
- radius = 3 + (rand () % (maxRadius - 3));
- }
-
- /* --- destructor --- */
- inline virtual ~DECOR ()
- {
- _env->removeObject (this);
- _global = NULL;
- _env = NULL;
- }
-
- /* --- non-inline methods --- */
-
- /* --- inline methods --- */
-
- inline virtual void draw (BITMAP *dest)
- {
- if (!destroy)
- {
- int iCalcRadius = radius;
- if (type == DECOR_SMOKE) iCalcRadius = (int)(iCalcRadius * (4.0 * age / maxAge)); // The older, the larger...
- drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
- set_trans_blender (0, 0, 0, 255 - (255 * age / maxAge));
- circlefill (dest, (int)x, (int)y, iCalcRadius, color);
- drawing_mode (DRAW_MODE_SOLID, NULL, 0, 0);
- setUpdateArea ( (int)x - (iCalcRadius * 2) - 1,
- (int)y - (iCalcRadius * 2) - 1,
- iCalcRadius * 4 + 2,
- iCalcRadius * 4 + 2);
- requireUpdate ();
- }
- }
-
- inline virtual int applyPhysics ()
- {
- /** Update: Smoke will age faster, if the wind is stronger! **/
- int iAgeMod = 1;
- if (_env->wind && (type == DECOR_SMOKE))
- iAgeMod = (int)round(fabs(_env->wind / (_env->windstrength / 2.0))) + 1;
- /* This produces: (with max wind = 8)
- * wind = 1 : round(1 / (8 / 2)) + 1 = round(1 / 4) + 1 = 0 + 1 = 1 <-- normal aging
- * wind = 4 : round(4 / (8 / 2)) + 1 = round(4 / 4) + 1 = 1 + 1 = 2 <-- raised aging
- * wind = 6 : round(6 / (8 / 2)) + 1 = round(6 / 4) + 1 = 2 + 1 = 3 <-- fast aging
- * wind = 8 : round(8 / (8 / 2)) + 1 = round(8 / 4) + 1 = 2 + 1 = 3 <-- fast aging */
- age += iAgeMod;
-
- if (!physics)
- {
- double xaccel = (_env->wind - xv) / mass * drag * _env->viscosity;
- xv += xaccel;
- x += xv;
- if (x < 1 || x > (_global->screenWidth-1))
- {
- destroy = TRUE;
- }
- double yaccel = (-1 - yv) / mass * drag * _env->viscosity;
- yv += yaccel;
- if (y + yv >= _global->screenHeight)
- {
- yv = -yv * 0.5;
- xv *= 0.95;
- }
- y += yv;
- }
- if (age > maxAge)
- {
- destroy = TRUE;
- }
-
- return (hitSomething);
- }
-
- inline virtual void initialise ()
- {
- PHYSICAL_OBJECT::initialise ();
- physics = 0;
- age = 0;
- if (type == DECOR_SMOKE)
- {
- int tempCol = 128 + (rand () % 64);
- color = makecol (tempCol, tempCol, tempCol);
- mass = 1 + ((double)(rand () % 5) / 10);
- drag = 0.9 + ((double)(rand () % 90) / 100);
- maxAge = 20 + (rand () % 90);
- }
- }
-
- inline virtual int isSubClass (int classNum)
- {
- if (classNum == DECOR_CLASS)
- return (TRUE);
- else
- return (FALSE);
- }
-
- inline virtual int getClass ()
- {
- return (DECOR_CLASS);
- }
-
- inline virtual void setEnvironment(ENVIRONMENT *env)
- {
- if (!_env || (_env != env))
- {
- _env = env;
- _index = _env->addObject (this);
- }
- }
-
- };
+{
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ // ctor without bitmap
+ explicit
+ DECOR (double x_, double y_, double xv_, double yv_,
+ int32_t maxRadius, int32_t type_, int32_t delay_);
+
+ // ctor with bitmap
+ DECOR (double x_, double y_, double xv_, double yv_,
+ int32_t maxRadius, int32_t type_, int32_t delay_,
+ sDebrisItem* deb_item, sDebrisItem* met_item);
+
+
+ ~DECOR();
+
+
+ /* -----------------------------------
+ * --- Public methods ---
+ * -----------------------------------
+ */
+
+ void applyPhysics ();
+ void draw ();
+ void force_aging (int32_t frames); // Helper to work against FPS drops.
+ eClasses getClass () { return (DECOR_SMOKE == type
+ ? CLASS_DECOR_SMOKE
+ : CLASS_DECOR_DIRT); }
+ bool isSmoke () { return DECOR_SMOKE == type; }
+
+
+private:
+
+ typedef sDebrisItem item_t;
+
+
+ /* -----------------------------------
+ * --- Private methods ---
+ * -----------------------------------
+ */
+
+ bool isOnFloor ();
+ void repulseDecor();
+ void updateDirt ();
+
+
+ /* -----------------------------------
+ * --- Private members ---
+ * -----------------------------------
+ */
+
+ int32_t color = BLACK;
+ double curWind = 0.; //!< shortcut to help physics calculations.
+ int32_t delay = -1; //!< How long until debris must be on its way.
+ int32_t diameter = 10; //!< Pre-calculated shortcut for debris items.
+ item_t* dirt = nullptr; //!< The debris item to throw around if not smoke.
+ int32_t gotPixels = 0; //!< Helper for phased debris creation.
+ int32_t grab_x = 0; //!< Helper for phased debris creation.
+ int32_t grab_y = 0; //!< Helper for phased debris creation.
+ int32_t grabPerCall = 0; //!< Helper for phased debris creation.
+ double maxGravAccel = 1.; //!< Pre-calculated physics helper.
+ double maxWind = 8; //!< env.windstrength cast to double.
+ double maxWindAccel = 1.; //!< Pre-calculated physics helper.
+ item_t* meteor = nullptr; //!< Metor data if not enough dirt was found, but a meteor stroke.
+ int32_t radius = 5;
+ bool ready = false; //!< Whether a debris item is finished or not.
+ int32_t type = DECOR_SMOKE;
+};
#endif
diff --git a/src/environment.cpp b/src/environment.cpp
index 2a96f50..22a5ba9 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -18,1096 +18,1310 @@
* */
+#include "main.h"
#include "environment.h"
#include "globaldata.h"
-#include "virtobj.h"
#include "missile.h"
#include "tank.h"
#include "files.h"
+#include "sound.h"
+#include "player.h"
-ENVIRONMENT::ENVIRONMENT (GLOBALDATA *global):_global(global),done(NULL),fp(NULL),h(NULL),surface(NULL),dropTo(NULL),
- velocity(NULL),dropIncr(NULL),height(NULL),db(NULL),terrain(NULL),sky(NULL)
+#include <cassert>
+
+ENVIRONMENT::ENVIRONMENT ()
{
- gravity = 0.150;
- viscosity = 0.5;
- landSlideType = LANDSLIDE_GRAVITY;
- landSlideDelay = MAX_GRAVITY_DELAY;
- windstrength = 8;
- windvariation = 1;
- weapontechLevel = itemtechLevel = 5;
- landType = LANDTYPE_RANDOM;
- meteors = 0;
- lightning = 0;
- satellite = 0.0;
- falling_dirt_balls = 0.0;
- fog = 0;
- wallType = 0.0;
- dBoxedMode = 0.0; // default is 0 = off
- dFadingText = 0.0; // defaults to 0 for backwards compatibilities sake
- dShadowedText = 0.0; // defaults to 0 for backwards compatibilities sake
- current_wallType = 0;
- custom_background = 0.0;
- bitmap_filenames = NULL;
- number_of_bitmaps = 0;
- current_drawing_mode = DRAW_MODE_SOLID;
-
- done = new char[global->screenWidth];
- fp = new int[global->screenWidth];
- h = new int[global->screenWidth];
- surface = new int[global->screenWidth];
- dropTo = new int[global->screenWidth];
- velocity = new double[global->screenWidth];
- dropIncr = new double[global->screenWidth];
- height = new double[global->screenWidth];
-
- db = create_bitmap (global->screenWidth, global->screenHeight);
- if (!db)
- cout << "Failed to create db bitmap: " << allegro_error;
- terrain = create_bitmap (global->screenWidth, global->screenHeight);
- if (!terrain)
- cout << "Failed to create terrain bitmap: " << allegro_error;
- sky = create_bitmap (global->screenWidth, global->screenHeight - MENUHEIGHT);
- if (!sky)
- cout << "Failed to create sky bitmap: " << allegro_error;
- waiting_sky = NULL;
- waiting_terrain = NULL;
- _global = global;
- global_tank_index = 0;
- volume_factor = MAX_VOLUME_FACTOR;
-
- initialise ();
- #ifdef THREADS
- waiting_sky_lock = init_lock(waiting_sky_lock);
- waiting_terrain_lock = init_lock(waiting_terrain_lock);
- #endif
+ // Unfortunately Visual C++ can not initialize arrays using an initialization list
+ // although it is part of C++11. Gcc and clang do it fine btw...
+ memset(availableItems, 0, sizeof(int32_t) * THINGS);
+ memset(configDir, 0, sizeof(char) * (PATH_MAX + 1));
+ memset(dataDir, 0, sizeof(char) * (PATH_MAX + 1));
+ memset(game_name, 0, sizeof(char) * GAMENAMELEN);
+ memset(playerOrder, 0, sizeof(PLAYER*) * MAXPLAYERS);
+ memset(server_name, 0, sizeof(char) * 129);
+ memset(server_port, 0, sizeof(char) * 129);
+ memset(slope, 0, sizeof(double) * 720);
+
+
+
+ set_fps(60); // rock solid default.
+
+ fontHeight = 10; // Initial value
+
+ strncpy(server_name, "127.0.0.1", 127);
+ strncpy(server_port, "25645", 127);
+
+ // Reserve space for the players array:
+ // Note: The allPlayers array is dynamically (re-)allocated while loading
+ // stored players from the configuration.
+ if ( (players = (PLAYER **) calloc( MAXPLAYERS, sizeof(PLAYER *) ) ) == nullptr)
+ perror ( "environment.cpp: Failed allocating memory for players");
+
+ // sin/cos short-cuts, With only 1° granularity the arrays are always
+ // faster than live calculations.
+ for (int32_t i = 0; i < 360; i++) {
+ slope[i][0] = std::sin(DEG2RAD(i));
+ slope[i][1] = std::cos(DEG2RAD(i));
+ }
}
-
-/** @brief ~ENVIRONMENT default dtor
- * * *
- * * * Cleanly remove created objects
- * * */
+/** @brief default dtor
+ * Cleanly remove created objects
+ **/
ENVIRONMENT::~ENVIRONMENT()
{
- int count;
-
- #ifdef THREADS
- destroy_lock(waiting_sky_lock);
- destroy_lock(waiting_terrain_lock);
- #endif
-
- if (db) destroy_bitmap(db); db = NULL;
- if (sky) destroy_bitmap(sky); sky = NULL;
- if (waiting_sky) destroy_bitmap(sky); waiting_sky = NULL;
- if (terrain) destroy_bitmap(terrain); terrain = NULL;
-
- _global = NULL;
-
- if (done) delete [] (done); done = NULL;
- if (fp) delete [] (fp); fp = NULL;
- if (h) delete [] (h); h = NULL;
- if (surface) delete [] (surface); surface = NULL;
- if (dropTo) delete [] (dropTo); dropTo = NULL;
- if (velocity) delete [] (velocity); velocity = NULL;
- if (dropIncr) delete [] (dropIncr); dropIncr = NULL;
- if (height) delete [] (height); height = NULL;
- if (bitmap_filenames)
- {
- for (count = 0; count < number_of_bitmaps; count++)
- {
- if (bitmap_filenames[count])
- free(bitmap_filenames[count]);
- }
- free(bitmap_filenames);
- }
-}
-#ifdef THREADS
-void ENVIRONMENT::destroy_lock(pthread_mutex_t* lock) {
- if (lock)
- {
- int result = pthread_mutex_destroy(lock);
- switch (result)
- {
- case 0:
- //Successfully destroyed
- break;
- case EBUSY:
- //Some thread forgot to unlock result
- printf("%s:%i: Lock is still held.\n", __FILE__, __LINE__);
- break;
- case EINVAL:
- //Invalid lock
- printf("%s:%i: Lock is invalid.\n", __FILE__, __LINE__);
- break;
- default:
- printf("%s:%i: Unknown error code (%i) returned by pthread_mutex_destroy.\n", __FILE__, __LINE__, result);
- break;
- }
- free(lock);
- }
-}
-#endif
-#ifdef THREADS
-pthread_mutex_t* ENVIRONMENT::init_lock(pthread_mutex_t* lock) {
- lock = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t));
- if (!lock)
- {
- printf("%s:%i: Could not allocate memory.\n", __FILE__, __LINE__);
- }
- int result = pthread_mutex_init(lock, NULL);
- switch (result)
- {
- case 0:
- //Succesfully initialized
- break;
- case EAGAIN:
- //Not enough resources
- printf("%s:%i: Not enough resources to create mutex.\n", __FILE__, __LINE__);
- break;
- case ENOMEM:
- printf("%s:%i: Not enough memory to create mutex.\n", __FILE__, __LINE__);
- break;
- case EPERM:
- printf("%s:%i: Not authorized.\n", __FILE__, __LINE__);
- break;
- case EBUSY:
- printf("%s:%i: The mutex is already initialized.\n", __FILE__, __LINE__);
- break;
- case EINVAL:
- printf("%s:%i: Invalid attribute.\n", __FILE__, __LINE__);
- break;
- default:
- printf("%s:%i: Unknown error code (%i) returned by pthread_mutex_init.\n", __FILE__, __LINE__, result);
- break;
- }
- return lock;
-}
-#endif
-#ifdef THREADS
-void ENVIRONMENT::lock(pthread_mutex_t* lock)
-{
- int result = pthread_mutex_lock(lock);
- switch (result)
- {
- case 0:
- //Got the lock.
- break;
- case EINVAL:
- //Priority too high
- printf("%s:%i: Either this thread's priority is higher than the mutex's priority, or the mutex is uninitialized.\n", __FILE__, __LINE__);
- break;
- case EAGAIN:
- printf("%s:%i: Too many locks on the mutex.\n", __FILE__, __LINE__);
- break;
- case EDEADLK:
- //We have the lock already
- printf("%s:%i: Already have lock.\n", __FILE__, __LINE__);
- break;
- default:
- //What error is this?
- printf("%s:%i: Unknown error code (%i) returned by pthread_mutex_lock.\n", __FILE__, __LINE__, result);
- break;
- }
-}
-#endif
-#ifdef THREADS
-void ENVIRONMENT::unlock(pthread_mutex_t* lock)
-{
- int result = pthread_mutex_unlock(lock);
- switch (result)
- {
- case 0:
- //Released the lock
- break;
- case EPERM:
- //Forgot to get a lock on the mutex
- printf("%s:%i: Mutex isn't locked\n", __FILE__, __LINE__);
- break;
- case EINVAL:
- //Uninitialized mutex
- printf("%s:%i: waiting_sky_lock is uninitialized.\n", __FILE__, __LINE__);
- break;
- default:
- //?
- printf("%s:%i: Unknown error code (%i) returned by pthread_mutex_unlock.\n", __FILE__, __LINE__, result);
- break;
- }
-}
-#endif
-BITMAP* ENVIRONMENT::get_waiting_sky()
-{
- #ifdef THREADS
- lock(waiting_sky_lock);
- #endif
- BITMAP* w = waiting_sky;
- #ifdef THREADS
- unlock(waiting_sky_lock);
- #endif
- return w;
-}
-BITMAP* ENVIRONMENT::get_waiting_terrain()
-{
- #ifdef THREADS
- lock(waiting_terrain_lock);
- #endif
- BITMAP* w = waiting_terrain;
- #ifdef THREADS
- unlock(waiting_terrain_lock);
- #endif
- return w;
+ this->destroy();
}
-/*
-This function saves the environment settings to a text
-file. Each line has the format
-name=value\n
-The function returns TRUE on success and FALSE on failure.
--- Jesse
-*/
-int ENVIRONMENT::saveToFile_Text (FILE *file)
+
+/// @brief add a player to the players[] array that will take part in the next game
+void ENVIRONMENT::addGamePlayer(PLAYER* player_)
{
- if (! file) return FALSE;
-
- fprintf (file, "*ENV*\n");
-
- fprintf (file, "WINDSTRENGTH=%f\n", windstrength);
- fprintf (file, "WINDVARIATION=%f\n", windvariation);
- fprintf (file, "VISCOSITY=%f\n", viscosity);
- fprintf (file, "GRAVITY=%f\n", gravity);
- fprintf (file, "WEAPONTECHLEVEL=%f\n", weapontechLevel);
- fprintf (file, "ITEMTECHLEVEL=%f\n", itemtechLevel);
- fprintf (file, "METEORS=%f\n", meteors);
- fprintf (file, "LIGHTNING=%f\n", lightning);
- fprintf (file, "SATELLITE=%f\n", satellite);
- fprintf (file, "FOG=%f\n", fog);
- fprintf (file, "LANDTYPE=%f\n", landType);
- fprintf (file, "LANDSLIDETYPE=%f\n", landSlideType);
- fprintf (file, "WALLTYPE=%f\n", wallType);
- fprintf (file, "BOXMODE=%f\n", dBoxedMode);
- fprintf (file, "TEXTFADE=%f\n", dFadingText);
- fprintf (file, "TEXTSHADOW=%f\n", dShadowedText);
- fprintf (file, "LANDSLIDEDELAY=%f\n", landSlideDelay);
- fprintf (file, "FALLINGDIRTBALLS=%f\n", falling_dirt_balls);
- fprintf (file, "CUSTOMBACKGROUND=%f\n", custom_background);
- fprintf (file, "***\n");
- return TRUE;
+ if (player_ && (numGamePlayers < MAXPLAYERS) ) {
+
+ // Ensure the player isn't already there:
+ for (int32_t i = 0; i < numGamePlayers; ++i) {
+ if (player_ == players[i])
+ return;
+ }
+
+ players[numGamePlayers++] = player_;
+
+ if (HUMAN_PLAYER == player_->type)
+ numHumanPlayers++;
+ }
}
-/*
-This function loads environment settings from a text
-file. The function returns TRUE on success and FALSE if
-any erors are encountered.
--- Jesse
-*/
-int ENVIRONMENT::loadFromFile_Text (FILE *file)
+
+/// @brief create a new player or return nullptr if an error occurred
+PLAYER* ENVIRONMENT::createNewPlayer (const char* player_name)
{
- char line[MAX_CONFIG_LINE];
- int equal_position, line_length;
- char field[MAX_CONFIG_LINE], value[MAX_CONFIG_LINE];
- char *result = NULL;
- bool done = false;
-
- // read until we hit line (char *)"*ENV*" or "***" or EOF
- do
- {
- result = fgets(line, MAX_CONFIG_LINE, file);
- if (! result) // eof
- return FALSE;
- if (! strncmp(line, "***", 3) ) // end of record
- return FALSE;
- }
- while ( strncmp(line, "*ENV*", 5) ); // read until we hit new record
+ PLAYER** reallocatedPlayers = nullptr;
+ PLAYER* player = nullptr;
- while ( (result) && (!done) )
- {
- // read a line
- memset(line, '\0', MAX_CONFIG_LINE);
- result = fgets(line, MAX_CONFIG_LINE, file);
- if (result)
- {
- // if we hit end of the record, stop
- if (! strncmp(line, "***", 3) ) return TRUE;
- // find equal sign
- line_length = strlen(line);
- // strip newline character
- if ( line[line_length - 1] == '\n')
- {
- line[line_length - 1] = '\0';
- line_length--;
- }
- equal_position = 1;
- while ( ( equal_position < line_length) && (line[equal_position] != '=') )
- equal_position++;
- // make sure we have valid equal sign
-
- if ( equal_position <= line_length )
- {
- // seperate field from value
- memset(field, '\0', MAX_CONFIG_LINE);
- memset(value, '\0', MAX_CONFIG_LINE);
- strncpy(field, line, equal_position);
- strcpy(value, & (line[equal_position + 1]));
- // check for fields and values
- if (! strcasecmp(field, "windstrength") )
- sscanf(value, "%lf", &windstrength);
- else if (! strcasecmp(field, "windvariation") )
- sscanf(value, "%lf", &windvariation);
- else if (! strcasecmp(field, "viscosity") )
- sscanf(value, "%lf", &viscosity);
- else if (! strcasecmp(field, "gravity") )
- sscanf(value, "%lf", &gravity);
- else if (! strcasecmp(field, "techlevel"))
- {
- sscanf(value, "%lf", &weapontechLevel);
- itemtechLevel = weapontechLevel; // for backward compatibility
- }
- else if (! strcasecmp(field, "weapontechlevel") )
- sscanf(value, "%lf", &weapontechLevel);
- else if (! strcasecmp(field, "itemtechlevel") )
- sscanf(value, "%lf", &itemtechLevel);
- else if (! strcasecmp(field, "meteors"))
- sscanf(value, "%lf", &meteors);
- else if (! strcasecmp(field, "lightning") )
- sscanf(value, "%lf", &lightning);
- else if (! strcasecmp(field, "satellite") )
- sscanf(value, "%lf", &satellite);
- else if (! strcasecmp(field, "fog") )
- sscanf(value, "%lf", &fog);
- else if (! strcasecmp(field, "landtype"))
- sscanf(value, "%lf", &landType);
- else if (! strcasecmp(field, "landslidetype"))
- sscanf(value, "%lf", &landSlideType);
- else if (! strcasecmp(field, "walltype"))
- sscanf(value, "%lf", &wallType);
- else if (! strcasecmp(field, "boxmode"))
- sscanf(value, "%lf", &dBoxedMode);
- else if (! strcasecmp(field, "textfade"))
- sscanf(value, "%lf", &dFadingText);
- else if (! strcasecmp(field, "textshadow"))
- sscanf(value, "%lf", &dShadowedText);
- else if (! strcasecmp(field, "landslidedelay"))
- sscanf(value, "%lf", &landSlideDelay);
- else if (! strcasecmp(field, "fallingdirtballs") )
- sscanf(value, "%lf", &falling_dirt_balls);
- else if (! strcasecmp(field, "custombackground") )
- sscanf(value, "%lf", &custom_background);
-
- } // end of found field=value line
-
- } // end of read a line properly
- } // end of while not done
-
- return TRUE;
-}
+ assert (player_name && "ERROR: player_name is nullptr!");
+ if (nullptr == player_name)
+ return nullptr;
+ if (getPlayerByName(player_name) > -1)
+ return nullptr;
-void ENVIRONMENT::initialise ()
-{
- for (int count = 0; count < _global->screenWidth; count++)
- {
- h[count] = 0;
- fp[count] = 0;
- done[count] = 1;
- dropTo[count] = _global->screenHeight - 1;
- surface[count] = 0;
- }
- for (int count = 0; count < MAX_OBJECTS; count++)
- objects[count] = NULL;
- for (int count = 0; count < MAXPLAYERS; count++)
- order[count] = NULL;
+ reallocatedPlayers = (PLAYER**)realloc (allPlayers,
+ sizeof (PLAYER*) * (numPermanentPlayers + 1));
- clear_to_color (sky, PINK);
- clear_to_color (db, WHITE);
- clear_to_color (terrain, PINK);
+ if (reallocatedPlayers)
+ allPlayers = reallocatedPlayers;
+ else
+ perror ("environment.cpp: Failed allocating memory for reallocatedPlayers in ENVIRONMENT::createNewPlayer");
- // oldFogX = 0;
- // oldFogY = 0;
+ player = new PLAYER ();
+ if (!player)
+ perror ("environment.cpp: Failed allocating memory for player in ENVIRONMENT::createNewPlayer");
- combineUpdates = TRUE;
+ player->index = numPermanentPlayers;
+ player->setName(player_name);
+ allPlayers[numPermanentPlayers++] = player;
+
+ return player;
}
-int ENVIRONMENT::isItemAvailable (int itemNum)
+
+/// @brief This function gives credits, score and money to the winner(s).
+void ENVIRONMENT::creditWinners(int32_t winner)
{
- if (itemNum < WEAPONS)
- {
- if ((weapon[itemNum].warhead) ||
- (weapon[itemNum].techLevel > weapontechLevel))
- return (FALSE);
- }
- else if (item[itemNum - WEAPONS].techLevel > itemtechLevel)
- {
- return (FALSE);
- }
- return (TRUE);
+ if (winner == WINNER_DRAW) // no winner
+ return;
+
+ int32_t team_members = 0;
+
+ if (winner == WINNER_JEDI) {
+ for (int32_t i = 0; i < numGamePlayers; ++i) {
+ if (TEAM_JEDI == players[i]->team) {
+ players[i]->score++;
+ players[i]->won++;
+ team_members++;
+ }
+ }
+ } else if (winner == WINNER_SITH) {
+ for (int32_t i = 0; i < numGamePlayers; ++i) {
+ if (TEAM_SITH == players[i]->team) {
+ players[i]->score++;
+ players[i]->won++;
+ team_members++;
+ }
+ }
+ } else if (winner < WINNER_NO_WIN) {
+ players[winner]->score++;
+ players[winner]->won++;
+ players[winner]->money += scoreRoundWinBonus;
+ }
+
+ // team gets their money too
+ if (team_members) {
+ int32_t team_bonus = scoreRoundWinBonus / team_members;
+ for (int32_t i = 0; i < numGamePlayers; ++i) {
+ if ( ((winner == WINNER_JEDI) && (players[i]->team == TEAM_JEDI) )
+ || ((winner == WINNER_SITH) && (players[i]->team == TEAM_SITH) ) )
+ players[i]->money += team_bonus;
+ }
+ }
+}
+
+
+void ENVIRONMENT::decreaseVolume()
+{
+ if (volume_factor > 0)
+ --volume_factor;
}
-void ENVIRONMENT::generateAvailableItemsList ()
+
+/// @brief Remove one of the players, then gone for good.
+void ENVIRONMENT::deletePermPlayer (PLAYER* player_)
{
- int slot = 0;
- for (int itemNum = 0; itemNum < THINGS; itemNum++)
- {
- if (!isItemAvailable (itemNum))
- continue;
- availableItems[slot] = itemNum;
- slot++;
- }
- numAvailable = slot;
+ int32_t toCount = 0;
+
+ for (int32_t fromCount = 0; fromCount < numPermanentPlayers; fromCount++) {
+ if (allPlayers[fromCount] != player_) {
+ if (allPlayers[toCount] != allPlayers[fromCount]) {
+ allPlayers[toCount] = allPlayers[fromCount];
+ allPlayers[toCount]->index = toCount;
+ }
+ toCount++;
+ }
+ }
+ numPermanentPlayers--;
+
+ delete player_;
}
-int ENVIRONMENT::addObject (VIRTUAL_OBJECT *object)
+
+/** @brief Free all allocated memory.
+ *
+ * Important: This MUST be called *before* allegro shuts down!
+**/
+void ENVIRONMENT::destroy()
{
- int objCount = 0;
+ if (sky) {
+ destroy_bitmap(sky);
+ sky = nullptr;
+ }
+
+ if (bitmap_filenames) {
+ for (int32_t count = 0; count < number_of_bitmaps; ++count) {
+ if (bitmap_filenames[count])
+ free(bitmap_filenames[count]);
+ }
+ free(bitmap_filenames);
+ bitmap_filenames = nullptr;
+ }
+
+ if (saved_game_list_size && saved_game_list) {
+ for (uint32_t i = 0; i < saved_game_list_size; ++i) {
+ if (saved_game_list[i])
+ free (const_cast<char*>(saved_game_list[i]));
+ saved_game_list[i] = nullptr;
+ }
+ free (saved_game_list);
+ saved_game_list = nullptr;
+ saved_game_list_size = 0;
+ }
+
+ if (music_dir) {
+ closedir(music_dir);
+ music_dir = nullptr;
+ }
+
+ if (background_music) {
+ destroy_sample(background_music);
+ background_music = nullptr;
+ }
+
+ if (sounds) {
+ int32_t index = 0;
+ while (sounds[index])
+ destroy_sample(sounds[index++]);
+ free(sounds);
+ sounds = nullptr;
+ }
+
+ if (title) {
+ int32_t index = 0;
+ while (title[index])
+ destroy_bitmap(title[index++]);
+ free(title);
+ title = nullptr;
+ }
+
+ if (button) {
+ int32_t index = 0;
+ while (button[index])
+ destroy_bitmap(button[index++]);
+ free(button);
+ button = nullptr;
+ }
+
+ if (misc) {
+ int32_t index = 0;
+ while (misc[index])
+ destroy_bitmap(misc[index++]);
+ free(misc);
+ misc = nullptr;
+ }
+
+ if (missile) {
+ int32_t index = 0;
+ while (missile[index])
+ destroy_bitmap(missile[index++]);
+ free(missile);
+ missile = nullptr;
+ }
+
+ if (stock) {
+ int32_t index = 0;
+ while (stock[index])
+ destroy_bitmap(stock[index++]);
+ free(stock);
+ stock = nullptr;
+ }
+
+ if (tank) {
+ int32_t index = 0;
+ while (tank[index])
+ destroy_bitmap(tank[index++]);
+ free(tank);
+ tank = nullptr;
+ }
+
+ if (tankgun) {
+ int32_t index = 0;
+ while (tankgun[index])
+ destroy_bitmap(tankgun[index++]);
+ free(tankgun);
+ tankgun = nullptr;
+ }
+
+ if (gloat) { delete gloat; gloat = nullptr; }
+ if (ingame) { delete ingame; ingame = nullptr; }
+ if (instructions) { delete instructions; instructions = nullptr; }
+ if (panic) { delete panic; panic = nullptr; }
+ if (kamikaze) { delete kamikaze; kamikaze = nullptr; }
+ if (retaliation) { delete retaliation; retaliation = nullptr; }
+ if (revenge) { delete revenge; revenge = nullptr; }
+ if (suicide) { delete suicide; suicide = nullptr; }
+ if (war_quotes) { delete war_quotes; war_quotes = nullptr; }
+
+ if (allPlayers) {
+ for (int32_t i = 0; i < numPermanentPlayers; ++i) {
+ if (allPlayers[i])
+ delete allPlayers[i];
+ allPlayers[i] = nullptr;
+ }
+ free(allPlayers);
+ allPlayers = nullptr;
+ }
+
+ if (players) {
+ for (int32_t i = 0; i < MAXPLAYERS; ++i)
+ players[i] = nullptr;
+ free(players);
+ players = nullptr;
+ }
+
+ if (main_font) {
+ destroy_font(main_font);
+ main_font = nullptr;
+ }
+
+ gfxData.destroy();
+}
- while ((objects[objCount] != NULL) && (objCount < MAX_OBJECTS))
- objCount++;
- if (objCount < MAX_OBJECTS)
- objects[objCount] = object;
- return (objCount);
+/// @brief Sets configDir to the path to the config directory used by atanks
+void ENVIRONMENT::find_config_dir()
+{
+ // If no config dir was given on the command line, try to find a valid one
+ if (!configDir[0]) {
+ // figure out file name
+ char* homedir = getenv(HOME_DIR);
+ snprintf(configDir, PATH_MAX, "%s/.atanks", homedir ? homedir : ".");
+
+ // copy the file over, if we did not yet
+ if (!Copy_Config_File()) {
+ // If it did not work, look whether the directory already exists:
+ DIR * pDestDir = opendir(env.configDir);
+ if (!pDestDir)
+ cerr << "ERROR: An error has occurred trying to set up"
+ << " Atomic Tanks folders." << endl;
+ else {
+ closedir(pDestDir);
+ pDestDir = nullptr;
+ }
+ }
+ } // end of no config file on the command line
}
-int ENVIRONMENT::removeObject (VIRTUAL_OBJECT *object)
+
+/// @brief Sets dataDir to the path 'unicode.dat' can be found in.
+bool ENVIRONMENT::find_data_dir()
{
- int objCount = object->getIndex();
- int objClass = object->getClass();
- object->requireUpdate();
- object->update();
+ // If the datadir set by command line options, try that first
+ if (dataDir[0]) {
+ snprintf(path_buf, PATH_MAX, "%s/%s", DATA_DIR, "unicode.dat");
+ if (!access(path_buf, R_OK))
+ return true;
+ else {
+ cerr << "ERROR: The given datadir \"" << dataDir << "\""
+ << " is invalid!" << endl;
+ memset(dataDir, 0, sizeof(char) * (PATH_MAX + 1));
+ }
+ }
+
+ // Try the set directory from the build
+ snprintf(path_buf, PATH_MAX, "%s/%s", DATA_DIR, "unicode.dat");
+ if (!access(path_buf, R_OK))
+ strncpy(dataDir, DATA_DIR, PATH_MAX);
+ else {
+ // This was not successful, try the current directory if not tried, yet.
+
+ if (strncmp(DATA_DIR, ".", 1) && strncmp(DATA_DIR, "./", 2)) {
+ strncpy(path_buf, "./unicode.dat", PATH_MAX);
+
+ // Try again and reset if unsuccessful
+ if (!access(path_buf, R_OK))
+ strncpy(dataDir, ".", PATH_MAX);
+ }
+ }
- if (objCount < MAX_OBJECTS)
- {
- objects[objCount] = NULL;
- if (objClass == TANK_CLASS)
- for (int ordCount = 0; ordCount < MAXPLAYERS; ordCount++)
- if (order[ordCount] == object)
- order[ordCount] = NULL;
- }
- else
- {
- return (objCount);
- }
+ // If dataDir is set, now, this was a success.
+ if (strlen(dataDir))
+ return true;
- return (0);
+ return false;
}
-VIRTUAL_OBJECT *ENVIRONMENT::getNextOfClass (int classNum, int *objCount)
+
+/// @brief Must be called before GLOBALDATA::first_init() is called!
+void ENVIRONMENT::first_init()
{
- for (;*objCount < MAX_OBJECTS; (*objCount)++)
- {
- if ((objects[*objCount] != NULL) &&
- ((classNum == ANY_CLASS) || (classNum == objects[*objCount]->getClass ())))
- break;
- }
+ // Determine maximum number of updates before doing
+ // a full update:
+ max_screen_updates = ROUND(std::sqrt(ROUND(screenWidth / 8)
+ * ROUND(screenHeight / 8)));
+ /* This is:
+ * 800 x 600 => sqrt(100 * 75) => sqrt( 7500) = 87
+ * 1280 x 1024 => sqrt(160 * 28) => sqrt(20480) = 143
+ * 1600 x 900 => sqrt(200 * 113) => sqrt(22600) = 150
+ * 1920 x 1080 => sqrt(240 * 135) => sqrt(32400) = 180
+ */
+
+ // Get memory ...
+ if (!sky)
+ sky = create_bitmap (screenWidth, screenHeight - MENUHEIGHT);
+ if (!sky) {
+ cout << "Failed to create sky bitmap: " << allegro_error << endl;
+ exit(1);
+ }
+
+ initialise ();
+
+ menuBeginY = (screenHeight - 400) / 2;
+ if (menuBeginY < 0)
+ menuBeginY = 0;
+ menuEndY = screenHeight - menuBeginY;
+
+ gfxData.first_init();
+}
- if (*objCount < MAX_OBJECTS)
- return (objects[*objCount]);
- else
- return (NULL);
+
+/// @brief Fill availableItems array with everything buyable with current settings.
+void ENVIRONMENT::genItemsList ()
+{
+ int32_t slot = 0;
+ for (int32_t i = 0; i < THINGS; ++i) {
+ if (isItemAvailable(i))
+ availableItems[slot++] = i;
+ }
+ numAvailable = slot;
}
-void ENVIRONMENT::newRound ()
+
+/// @brief return the index of the player with @a player_name or -1 if not found
+int32_t ENVIRONMENT::getPlayerByName(const char* player_name)
{
- for (int objCount = 0; objCount < MAX_OBJECTS; objCount++)
- {
- if (objects[objCount])
- {
- if ((!objects[objCount]->isSubClass (TANK_CLASS)) &&
- (!objects[objCount]->isSubClass (FLOATTEXT_CLASS)))
- {
- delete objects[objCount];
- objects[objCount] = NULL;
- }
- else if (objects[objCount]->isSubClass (TANK_CLASS))
- ((TANK *)objects[objCount])->newRound();
- }
- }
+ int32_t result = -1;
- naturals_since_last_shot = 0;
- // set wall type
- if (wallType == WALL_RANDOM)
- current_wallType = rand() % 4;
- else
- current_wallType = (int) wallType;
+ assert (player_name && "ERROR: player_name is nullptr!");
- // time_to_fall = (rand() % MAX_GRAVITY_DELAY) + 1;
- time_to_fall = (rand() & (int)landSlideDelay) + 1;
- global_tank_index = 0;
-}
+ if (nullptr == player_name)
+ return result;
-int ENVIRONMENT::ingamemenu ()
-{
- int button[INGAMEBUTTONS];
- int pressed = -1;
- int updatew[INGAMEBUTTONS];
- char *buttext[INGAMEBUTTONS];
- buttext[0] = _global->ingame->complete_text[69];
- buttext[1] = _global->ingame->complete_text[70];
- buttext[2] = _global->ingame->complete_text[71];
- buttext[3] = _global->ingame->complete_text[72];
- // char buttext_de[INGAMEBUTTONS][32] = { "Zuruck", "Hauptmenu", "Verlassen", "Skip AI" };
- int z, zz;
-
- // Set/calcualte button size and positions
- int button_width = 150;
- int button_height = 20;
- int button_space = 5;
- int button_halfwidth = button_width / 2;
- // int button_halfheight = button_height / 2;
- int dialog_width = 200;
- int dialog_height = ((INGAMEBUTTONS + 2) * button_height) + ((INGAMEBUTTONS + 1) * button_space);
- int dialog_halfwidth = dialog_width / 2;
- int dialog_halfheight = dialog_height / 2;
-
- // Calculate button y values and set all button status to 0
- int y = -dialog_halfheight + button_height + button_space;
-
- for (z = 0; z < INGAMEBUTTONS; z++)
- {
- updatew[z] = 0;
- button[z] = y;
- y += button_height + button_space;
- }
-
- if (! _global->os_mouse) show_mouse (NULL);
- make_update (_global->halfWidth - dialog_halfwidth, _global->halfHeight - dialog_halfheight, dialog_width, dialog_height);
- rectfill (db, _global->halfWidth - dialog_halfwidth, _global->halfHeight - dialog_halfheight, _global->halfWidth + dialog_halfwidth - 1, _global->halfHeight + dialog_halfheight - 1, makecol (128, 128, 128));
- rect (db, _global->halfWidth - dialog_halfwidth, _global->halfHeight - dialog_halfheight, _global->halfWidth + dialog_halfwidth - 1, _global->halfHeight + dialog_halfheight - 1, BLACK);
-
-
- while (1)
- {
- LINUX_SLEEP;
- if (keypressed ())
- {
- k = readkey ();
- if ( (k >> 8 == KEY_ESC) || (k >> 8 == KEY_P) )
- return (0);
- }
+ for (int32_t i = 0; (-1 == result) && (i < numPermanentPlayers); ++i) {
+ if (!strcmp(player_name, allPlayers[i]->getName()))
+ result = i;
+ }
- if (mouse_b & 1)
- {
- zz = 0;
- for (z = 0; z < INGAMEBUTTONS; z++)
- {
- if (mouse_x >= _global->halfWidth - button_halfwidth && mouse_x < _global->halfWidth + button_halfwidth && mouse_y >= button[z] + _global->halfHeight
- && mouse_y < button[z] + button_height + _global->halfHeight)
- {
- zz = 1;
- if (pressed > -1)
- updatew[pressed] = 1;
- pressed = z;
- updatew[z] = 1;
- }
- }
- if (!zz)
- {
- if (pressed > -1)
- updatew[pressed] = 1;
- pressed = -1;
- }
- }
- if (pressed > -1 && !mouse_b & 1)
- return (pressed);
- for (z = 0; z < INGAMEBUTTONS; z++)
- {
- if (updatew[z])
- {
- updatew[z] = 0;
- make_update (_global->halfWidth - button_halfwidth, _global->halfHeight + button[z], button_width, button_height);
- }
- }
- make_update (mouse_x, mouse_y, ((BITMAP *) (_global->misc[0]))->w, ((BITMAP *) (_global->misc[0]))->h);
- make_update (lx, ly, ((BITMAP *) (_global->misc[0]))->w, ((BITMAP *) (_global->misc[0]))->h);
- lx = mouse_x;
- ly = mouse_y;
- if (! _global->os_mouse) show_mouse (NULL);
- for (z = 0; z < INGAMEBUTTONS; z++)
- {
- draw_sprite (db, (BITMAP *) _global->misc[(pressed == z) ? 8 : 7], _global->halfWidth - button_halfwidth, _global->halfHeight + button[z]);
- textout_centre_ex (db, font, buttext[z], _global->halfWidth, _global->halfHeight + button[z] + 6, WHITE, -1);
- }
- if (! _global->os_mouse) show_mouse (db);
- do_updates ();
- }
+ return result;
}
-void ENVIRONMENT::do_updates ()
-{
- int count;
- bool bIsBgUpdNeeded = false;
- if (_global->lastUpdatesCount)
- bIsBgUpdNeeded = true;
+void ENVIRONMENT::increaseVolume()
+{
+ if (volume_factor < MAX_VOLUME_FACTOR)
+ ++volume_factor;
+}
- if (_global->updateCount >= MAXUPDATES)
- make_fullUpdate();
- else
- {
- for (count = 0; count < _global->updateCount && count < MAXUPDATES; count++)
- {
- blit (db, screen, _global->updates[count].x, _global->updates[count].y, _global->updates[count].x, _global->updates[count].y, _global->updates[count].w, _global->updates[count].h);
- // Debug rectangle
- //rect (screen, _global->updates[count].x, _global->updates[count].y, _global->updates[count].x + _global->updates[count].w, _global->updates[count].y + _global->updates[count].h, WHITE);
- if (bIsBgUpdNeeded)
- make_bgupdate(_global->updates[count].x, _global->updates[count].y, _global->updates[count].w, _global->updates[count].h);
- }
- if (!bIsBgUpdNeeded)
- {
- _global->lastUpdatesCount = _global->updateCount;
- memcpy (_global->lastUpdates, _global->updates, sizeof (BOX) * _global->updateCount);
- }
- _global->updateCount = 0;
- }
+int32_t ENVIRONMENT::ingamemenu ()
+{
+ int32_t pressed = -1;
+ bool need_draw = true;
+ int32_t button[INGAMEBUTTONS];
+ bool updatew[INGAMEBUTTONS];
+ const char* buttext[INGAMEBUTTONS] = {
+ ingame->Get_Line(69),
+ ingame->Get_Line(70),
+ ingame->Get_Line(71),
+ ingame->Get_Line(72),
+ };
+
+ // Set/calculate button size and positions
+ int32_t b_width = 150;
+ int32_t b_height = 20;
+ int32_t b_space = 5;
+ int32_t b_half_w = b_width / 2;
+ int32_t b_left = halfWidth - b_half_w;
+ int32_t b_right = halfWidth + b_half_w - 1;
+
+ int32_t d_width = 200;
+ int32_t d_height = ((INGAMEBUTTONS + 2) * b_height)
+ + ((INGAMEBUTTONS + 1) * b_space);
+ int32_t d_half_w = d_width / 2;
+ int32_t d_half_h = d_height / 2;
+
+ int32_t d_left = halfWidth - d_half_w;
+ int32_t d_right = halfWidth + d_half_w - 1;
+ int32_t d_top = halfHeight - d_half_h;
+ int32_t d_bottom = halfHeight + d_half_h - 1;
+
+ // store last mouse coordinates for movement detection
+ int32_t lastMouse_x = 0;
+ int32_t lastMouse_y = 0;
+
+ // Calculate button y values and set all button status to 0
+ int32_t y = -d_half_h + b_height + b_space;
+
+ for (int32_t i = 0; i < INGAMEBUTTONS; ++i) {
+ updatew[i] = false;
+ button[i] = y;
+ y += b_height + b_space;
+ }
+
+ SHOW_MOUSE(nullptr);
+ k = 0;
+ K = 0;
+
+ global.make_update (d_left, d_top, d_width, d_height);
+ rectfill (global.canvas, d_left, d_top, d_right, d_bottom, GREY);
+ rect (global.canvas, d_left, d_top, d_right, d_bottom, BLACK);
+
+ while (-1 == pressed) {
+ LINUX_REST;
+ if (keypressed ()) {
+ k = readkey ();
+ K = k >> 8;
+ }
+
+ // look for keyboard exit
+ if ( ( K == KEY_ESC)
+ || ( K == KEY_P) ) {
+ pressed = -2;
+ continue;
+ }
+
+ // Check mouse movement
+ if ( !env.osMouse
+ && ( (lastMouse_x != mouse_x)
+ || (lastMouse_y != mouse_y) ) ) {
+ lastMouse_x = mouse_x;
+ lastMouse_y = mouse_y;
+ need_draw = true;
+ }
+
+ if (mouse_b & 1) {
+ bool is_hit = false;
+ for (int32_t i = 0; !is_hit && (i < INGAMEBUTTONS); ++i) {
+ if ( (mouse_x >= b_left)
+ && (mouse_x < b_right)
+ && (mouse_y >= (button[i] + halfHeight))
+ && (mouse_y < (button[i] + b_height + halfHeight)) ) {
+
+ is_hit = true;
+
+ if (pressed > -1)
+ updatew[pressed] = true;
+ pressed = i;
+ updatew[i] = true;
+ }
+ }
+
+ if (!is_hit) {
+ if (pressed > -1)
+ updatew[pressed] = true;
+ pressed = -1;
+ }
+ }
+
+ // Update buttons
+ for (int32_t i = 0; i < INGAMEBUTTONS; ++i) {
+ if (updatew[i]) {
+ updatew[i] = false;
+ global.make_update (b_left, halfHeight + button[i],
+ b_width, b_height);
+ }
+ }
+
+ // Draw buttons
+ if (need_draw) {
+ SHOW_MOUSE(nullptr)
+
+ for (int32_t i = 0; i < INGAMEBUTTONS; ++i) {
+ draw_sprite (global.canvas, misc[(pressed == i) ? 8 : 7],
+ b_left, halfHeight + button[i]);
+ textout_centre_ex (global.canvas, font, buttext[i], halfWidth,
+ halfHeight + button[i] + 1, WHITE, -1);
+ }
+
+ // Update non-OS mouse movements
+ SHOW_MOUSE(global.canvas)
+
+ global.do_updates ();
+ need_draw = false;
+ }
+ } // end of menu loop
+
+ return pressed;
}
-void ENVIRONMENT::replaceCanvas ()
+
+void ENVIRONMENT::initialise ()
{
- int count;
+ campaign_rounds = static_cast<double>(rounds) / 5.;
+ if (campaign_rounds < 1.)
+ campaign_rounds = 1.;
- if (_global->lastUpdatesCount >= MAXUPDATES)
- make_fullUpdate();
- else
- {
- for (count = 0; count < _global->lastUpdatesCount && count < MAXUPDATES; count++)
- {
- blit (sky, db, _global->lastUpdates[count].x, _global->lastUpdates[count].y - MENUHEIGHT, _global->lastUpdates[count].x, _global->lastUpdates[count].y, _global->lastUpdates[count].w, _global->lastUpdates[count].h);
- masked_blit (terrain, db, _global->lastUpdates[count].x, _global->lastUpdates[count].y, _global->lastUpdates[count].x, _global->lastUpdates[count].y, _global->lastUpdates[count].w, _global->lastUpdates[count].h);
- }
- #ifdef NEW_GAMELOOP
- int iLeft = 0;
- int iRight = _global->screenWidth - 1;
- int iTop = MENUHEIGHT;
- int iBottom = _global->screenHeight - 1;
-
- vline(db, iLeft, iTop, iBottom, wallColour); // Left edge
- vline(db, iLeft + 1, iTop, iBottom, wallColour); // Left edge
- vline(db, iRight, iTop, iBottom, wallColour); // right edge
- vline(db, iRight - 1, iTop, iBottom, wallColour); // right edge
- hline(db, iLeft, iBottom, iRight, wallColour);// bottom edge
- if (_global->bIsBoxed)
- hline(db, iLeft, iTop, iRight, wallColour);// top edge
- #endif
- _global->lastUpdatesCount = 0;
- // The menu needs to be redrawn:
- make_update(0, 0, _global->screenWidth - 1, MENUHEIGHT);
- }
+ nextCampaignRound = static_cast<double>(rounds) - campaign_rounds;
}
-void ENVIRONMENT::make_update (int x, int y, int w, int h)
+
+/// @return true if the items tech level is not too high and if it is not a warhead.
+bool ENVIRONMENT::isItemAvailable (int32_t itemNum)
{
- int combined = 0;
- if (combineUpdates && _global->updateCount && _global->updateCount < MAXUPDATES)
- {
- BOX prev, next;
- prev.x = _global->updates[_global->updateCount - 1].x;
- prev.y = _global->updates[_global->updateCount - 1].y;
- // .w = .x + .w = x2 (aka renamed to be the right x-position)
- prev.w = _global->updates[_global->updateCount - 1].x + _global->updates[_global->updateCount - 1].w;
- // .h = .y + .h = y2 (aka renamed to be the bottom y-position)
- prev.h = _global->updates[_global->updateCount - 1].y + _global->updates[_global->updateCount - 1].h;
-
- next.x = x;
- next.y = y;
- next.w = x + w; // same as above, becoming x2 and not the width!
- next.h = y + h; // same as above, becoming y2 and not the height!
-
- if (((next.w > prev.x - 3) && (prev.w > next.x - 3)) &&
- ((next.h > prev.y - 3) && (prev.h > next.y - 3)))
- {
- next.x = (next.x < prev.x)?next.x:prev.x;
- next.y = (next.y < prev.y)?next.y:prev.y;
- next.w = (next.w > prev.w)?next.w:prev.w;
- next.h = (next.h > prev.h)?next.h:prev.h;
- _global->updates[_global->updateCount - 1].x = next.x;
- _global->updates[_global->updateCount - 1].y = next.y;
- _global->updates[_global->updateCount - 1].w = next.w - next.x;
- _global->updates[_global->updateCount - 1].h = next.h - next.y;
- combined = 1;
- }
- }
- if (!combined)
- {
- _global->updates[_global->updateCount].x = x;
- _global->updates[_global->updateCount].y = y;
- _global->updates[_global->updateCount].w = w;
- _global->updates[_global->updateCount].h = h;
- if (_global->updateCount < MAXUPDATES)
- _global->updateCount++;
- }
+ if (itemNum < WEAPONS) {
+ if ( (weapon[itemNum].warhead)
+ || (weapon[itemNum].techLevel > weapontechLevel) )
+ return false;
+ } else if (item[itemNum - WEAPONS].techLevel > itemtechLevel)
+ return false;
+ return true;
+}
- if (_global->updateCount >= MAXUPDATES)
- make_fullUpdate();
- else if (!_global->stopwindow)
- {
- if (x < _global->window.x)
- _global->window.x = x;
- if (y < _global->window.y)
- _global->window.y = y;
- if (x + w > _global->window.w)
- _global->window.w = (x + w) - 1;
- if (y + h > _global->window.h)
- _global->window.h = (y + h) - 1;
- if (_global->window.x < 0)
- _global->window.x = 0;
- if (_global->window.y < MENUHEIGHT)
- _global->window.y = MENUHEIGHT;
- if (_global->window.w > (_global->screenWidth-1))
- _global->window.w = (_global->screenWidth-1);
- if (_global->window.h > (_global->screenHeight-1))
- _global->window.h = (_global->screenHeight-1);
- }
+
+/*
+This function loads environment settings from a text
+file. The function returns TRUE on success and FALSE if
+any erors are encountered.
+-- Jesse
+*/
+/// @todo : This should be changed to streams. It's C++ and we have formatted
+/// text files, so formatted input/output should be far more efficient in
+/// maintenance.
+void ENVIRONMENT::load_from_file (FILE *file)
+{
+ char line[MAX_CONFIG_LINE + 1] = { 0 };
+ char field[MAX_CONFIG_LINE + 1] = { 0 };
+ char value[MAX_CONFIG_LINE + 1] = { 0 };
+ char* result = nullptr;
+ bool done = false;
+ bool sound_bookmark = sound_enabled; // To disable by command line
+
+ // read until we hit line "*ENV*" or "***" or EOF
+ do {
+ result = fgets(line, MAX_CONFIG_LINE, file);
+ if (!result || !strncmp(line, "***", 3) )
+ // eof or end of record
+ return;
+ else if (!strncmp(line, "*GLOBAL*", 8)) {
+ // Old style config/save file
+ rewind(file);
+ global.load_from_file(file);
+ }
+ } while ( strncmp(line, "*ENV*", 5) );
+ // read until we hit new record
+
+ while ( (result) && (!done) ) {
+ // read a line
+ memset(line, 0, MAX_CONFIG_LINE);
+ result = fgets(line, MAX_CONFIG_LINE, file);
+
+ // if we hit end of the record, stop
+ if (! strncmp(line, "***", 3) )
+ done = true;
+
+ if (result && !done) {
+
+ // strip newline character
+ int32_t line_length = strlen(line);
+ while ( line[line_length - 1] == '\n') {
+ line[line_length - 1] = '\0';
+ line_length--;
+ }
+
+ // find equal sign
+ int32_t equal_position = 1;
+ while ( ( equal_position < line_length )
+ && ( line[equal_position] != '=' ) )
+ equal_position++;
+
+ // make sure the equal sign position is valid
+ if (line[equal_position] != '=')
+ continue; // Go to next line
+
+ // seperate field from value
+ memset(field, '\0', MAX_CONFIG_LINE);
+ memset(value, '\0', MAX_CONFIG_LINE);
+ strncpy(field, line, equal_position);
+ strncpy(value, & (line[equal_position + 1]), 127);
+
+ // check for fields and values
+ if (!strcasecmp(field, "acceleratedai")) {
+ sscanf(value, "%d", &skipComputerPlay);
+ if (skipComputerPlay > SKIP_HUMANS_DEAD)
+ skipComputerPlay = SKIP_HUMANS_DEAD;
+ } else if (!strcasecmp(field, "checkupdates")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ check_for_updates = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "colourtheme") ) {
+ sscanf(value, "%d", &colourTheme);
+ if (colourTheme < CT_REGULAR) colourTheme = CT_REGULAR;
+ if (colourTheme > CT_CRISPY) colourTheme = CT_CRISPY;
+ } else if (!strcasecmp(field, "debrislevel") )
+ sscanf(value, "%d", &debris_level);
+ else if (!strcasecmp(field, "detailedland")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ detailedLandscape = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "detailedsky")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ detailedSky = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "dither")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ ditherGradients = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "dividemoney") ) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ divide_money = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "doboxwrap") ) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ do_box_wrap = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "dynamicmenubg") ) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ dynamicMenuBg = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "frames") ) {
+ int32_t new_fps = 0;
+ sscanf(value, "%d", &new_fps);
+ set_fps(new_fps);
+ } else if (!strcasecmp(field, "fullscreen"))
+ sscanf(value, "%d", &full_screen);
+ else if (!strcasecmp(field, "interest"))
+ sscanf(value, "%lf", &interest);
+ else if (!strcasecmp(field, "language") ) {
+ uint32_t stored_lang = 0;
+ sscanf(value, "%u", &stored_lang);
+ language = static_cast<eLanguages>(stored_lang);
+ } else if (!strcasecmp(field, "maxfiretime") )
+ sscanf(value, "%d", &maxFireTime);
+ else if (!strcasecmp(field, "networking")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ network_enabled = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "networkport"))
+ sscanf(value, "%d", &network_port);
+ else if (!strcasecmp(field, "numpermanentplayers"))
+ sscanf(value, "%d", &numPermanentPlayers);
+ else if (!strcasecmp(field, "osmouse")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ osMouse = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "playmusic")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ play_music = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "rounds") )
+ sscanf(value, "%u", &rounds);
+ else if (!strcasecmp(field, "scorehitunit"))
+ sscanf(value, "%d", &scoreHitUnit);
+ else if (!strcasecmp(field, "scoreroundwinbonus"))
+ sscanf(value, "%d", &scoreRoundWinBonus);
+ else if (!strcasecmp(field, "scoreselfhit"))
+ sscanf(value, "%d", &scoreSelfHit);
+ else if (!strcasecmp(field, "scoreteamhit"))
+ sscanf(value, "%d", &scoreTeamHit);
+ else if (!strcasecmp(field, "scoreunitdestroybonus"))
+ sscanf(value, "%d", &scoreUnitDestroyBonus);
+ else if (!strcasecmp(field, "scoreunitselfdestroy"))
+ sscanf(value, "%d", &scoreUnitSelfDestroy);
+ else if (!strcasecmp(field, "sellpercent"))
+ sscanf(value, "%lf", &sellpercent);
+#ifdef NETWORK
+ else if ( !strcasecmp(field, "servername") )
+ sscanf(value, "%*[']%[^']%*[']", server_name);
+ else if ( !strcasecmp(field, "serverport") )
+ sscanf(value, "%*[']%[^']%*[']", server_port);
+#endif // NETWORK
+ else if ( !strcasecmp(field, "showaifeedback") ) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ showAIFeedback = val > 0 ? true : false;
+ } else if ( !strcasecmp(field, "showfps") ) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ showFPS = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "soundenabled")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ sound_enabled = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "sounddriver"))
+ sscanf(value, "%d", &sound_driver);
+ else if (!strcasecmp(field, "startmoney"))
+ sscanf(value, "%d", &startmoney);
+ else if (!strcasecmp(field, "turntype"))
+ sscanf(value, "%d", &turntype);
+ else if (!strcasecmp(field, "violentdeath") )
+ sscanf(value, "%d", &violent_death);
+ else if (!strcasecmp(field, "windstrength") )
+ sscanf(value, "%d", &windstrength);
+ else if (!strcasecmp(field, "windvariation") )
+ sscanf(value, "%d", &windvariation);
+ else if (!strcasecmp(field, "viscosity") ) {
+ sscanf(value, "%lf", &viscosity);
+ if (viscosity < 0.25)
+ viscosity = 0.5;
+ } else if (!strcasecmp(field, "gravity") ) {
+ sscanf(value, "%lf", &gravity);
+ if (gravity < 0.025)
+ gravity = 0.15;
+ } else if (!strcasecmp(field, "techlevel")) {
+ sscanf(value, "%d", &weapontechLevel);
+ itemtechLevel = weapontechLevel; // for backward compatibility
+ } else if (!strcasecmp(field, "weapontechlevel") )
+ sscanf(value, "%d", &weapontechLevel);
+ else if (!strcasecmp(field, "itemtechlevel") )
+ sscanf(value, "%d", &itemtechLevel);
+ else if (!strcasecmp(field, "meteors"))
+ sscanf(value, "%d", &meteors);
+ else if (!strcasecmp(field, "lightning") )
+ sscanf(value, "%d", &lightning);
+ else if (!strcasecmp(field, "satellite") )
+ sscanf(value, "%d", &satellite);
+ else if (!strcasecmp(field, "fog") )
+ sscanf(value, "%d", &fog);
+ else if (!strcasecmp(field, "landtype"))
+ sscanf(value, "%d", &landType);
+ else if (!strcasecmp(field, "landslidetype"))
+ sscanf(value, "%d", &landSlideType);
+ else if (!strcasecmp(field, "walltype"))
+ sscanf(value, "%d", &wallType);
+ else if (!strcasecmp(field, "boxmode"))
+ sscanf(value, "%d", &boxedMode);
+ else if (!strcasecmp(field, "textfade")) {
+ int32_t res = 0;
+ sscanf(value, "%d", &res);
+ fadingText = res ? true : false;
+ } else if (!strcasecmp(field, "textshadow")) {
+ int32_t res = 0;
+ sscanf(value, "%d", &res);
+ shadowedText = res ? true : false;
+ } else if (!strcasecmp(field, "textsway")) {
+ int32_t res = 0;
+ sscanf(value, "%d", &res);
+ swayingText = res ? true : false;
+ } else if (!strcasecmp(field, "landslidedelay"))
+ sscanf(value, "%d", &landSlideDelay);
+ else if (!strcasecmp(field, "fallingdirtballs") ) {
+ sscanf(value, "%d", &falling_dirt_balls);
+ if (falling_dirt_balls < 0) falling_dirt_balls = 0;
+ if (falling_dirt_balls > 3) falling_dirt_balls = 3;
+ } else if (!strcasecmp(field, "custombackground") )
+ sscanf(value, "%d", &custom_background);
+ else if (!strcasecmp(field, "volumefactor") )
+ sscanf(value, "%d", &volume_factor);
+ else if (!strcasecmp(field, "volleydelay") )
+ sscanf(value, "%d", &volley_delay);
+ else if (!strcasecmp(field, "screenwidth"))
+ sscanf(value, "%d", &screenWidth);
+ else if (!strcasecmp(field, "screenheight"))
+ sscanf(value, "%d", &screenHeight);
+ } // end of read a line properly
+ } // end of while not done
+
+ // If values were set on the command line, override
+ // configuration values:
+ if (temp_screenHeight)
+ screenHeight = temp_screenHeight;
+ else
+ temp_screenHeight = screenHeight;
+ if (temp_screenWidth)
+ screenWidth = temp_screenWidth;
+ else
+ temp_screenWidth = screenWidth;
+
+ // The resolution must not be below 800x600:
+ if (screenHeight < 600)
+ screenHeight = 600;
+ if (screenWidth < 800)
+ screenWidth = 800;
+
+ // The screen resolution values must be copied back into
+ // the temp variables, which are then used by the menu,
+ // or changing the resolution will make the menu exit crash.
+ // The resolution is set only once on game start, so
+ // these changes go into temp and are stored back from them.
+ temp_screenHeight = screenHeight;
+ temp_screenWidth = screenWidth;
+
+ if (! sound_bookmark)
+ sound_enabled = false;
+
+ halfWidth = screenWidth / 2;
+ halfHeight = screenHeight / 2;
+
+ menuBeginY = (screenHeight - 400) / 2;
+ if (menuBeginY < 0) menuBeginY = 0;
+ menuEndY = screenHeight - menuBeginY;
+
+ return;
}
-void ENVIRONMENT::make_bgupdate (int x, int y, int w, int h)
+
+#define LOAD_TEXT_BLOCK(var, file) try { \
+ snprintf(path_buf, PATH_MAX, "%s/text/%s%s", dataDir, file, suffix); \
+ TEXTBLOCK* new_##var = new TEXTBLOCK(path_buf); \
+ if (var) \
+ delete var; \
+ var = new_##var; \
+} catch (...) { }
+
+/** @brief load text files according to set language
+ * This function loads all needed text files, based on
+ * language, into memory. If a previous text was loaded, it is
+ * removed from memory first.
+**/
+void ENVIRONMENT::load_text_files()
{
- int combined = 0;
- if (_global && combineUpdates && _global->lastUpdatesCount && (_global->lastUpdatesCount < MAXUPDATES))
- {
- BOX prev, next;
- prev.x = _global->lastUpdates[_global->lastUpdatesCount - 1].x;
- prev.y = _global->lastUpdates[_global->lastUpdatesCount - 1].y;
- prev.w = _global->lastUpdates[_global->lastUpdatesCount - 1].x + _global->lastUpdates[_global->lastUpdatesCount - 1].w;
- prev.h = _global->lastUpdates[_global->lastUpdatesCount - 1].y + _global->lastUpdates[_global->lastUpdatesCount - 1].h;
-
- next.x = x;
- next.y = y;
- next.w = x + w;
- next.h = y + h;
-
- if (((next.w > prev.x - 3) && (prev.w > next.x - 3)) &&
- ((next.h > prev.y - 3) && (prev.h > next.y - 3)))
- {
- next.x = (next.x < prev.x)?next.x:prev.x;
- next.y = (next.y < prev.y)?next.y:prev.y;
- next.w = (next.w > prev.w)?next.w:prev.w;
- next.h = (next.h > prev.h)?next.h:prev.h;
- _global->lastUpdates[_global->lastUpdatesCount - 1].x = next.x;
- _global->lastUpdates[_global->lastUpdatesCount - 1].y = next.y;
- _global->lastUpdates[_global->lastUpdatesCount - 1].w = next.w - next.x;
- _global->lastUpdates[_global->lastUpdatesCount - 1].h = next.h - next.y;
- combined = 1;
- }
- }
- if (_global && !combined)
- {
- _global->lastUpdates[_global->lastUpdatesCount].x = x;
- _global->lastUpdates[_global->lastUpdatesCount].y = y;
- _global->lastUpdates[_global->lastUpdatesCount].w = w;
- _global->lastUpdates[_global->lastUpdatesCount].h = h;
- if (_global->lastUpdatesCount < MAXUPDATES)
- _global->lastUpdatesCount++;
- }
+ char suffix[12] = { 0 };
+
+ switch (language) {
+ case EL_FRENCH:
+ strncpy(suffix, "_fr.txt", 11);
+ snprintf(path_buf, PATH_MAX, "%s/text/war_quotes.txt", dataDir);
+ break;
+ case EL_GERMAN:
+ strncpy(suffix, "_de.txt", 11);
+ snprintf(path_buf, PATH_MAX, "%s/text/war_quotes.txt", dataDir);
+ break;
+ case EL_ITALIAN:
+ strncpy(suffix, "_it.txt", 11);
+ snprintf(path_buf, PATH_MAX, "%s/text/war_quotes_it.txt", dataDir);
+ break;
+ case EL_PORTUGUESE:
+ strncpy(suffix, ".pt_BR.txt", 11);
+ snprintf(path_buf, PATH_MAX, "%s/text/war_quotes.txt", dataDir);
+ break;
+ case EL_RUSSIAN:
+ strncpy(suffix, "_ru.txt", 11);
+ snprintf(path_buf, PATH_MAX, "%s/text/war_quotes_ru.txt", dataDir);
+ break;
+ case EL_SLOVAK:
+ strncpy(suffix, "_sk.txt", 11);
+ snprintf(path_buf, PATH_MAX, "%s/text/war_quotes.txt", dataDir);
+ break;
+ case EL_SPANISH:
+ strncpy(suffix, "_ES.txt", 11);
+ snprintf(path_buf, PATH_MAX, "%s/text/war_quotes_ES.txt", dataDir);
+ break;
+ case EL_ENGLISH:
+ default:
+ strncpy(suffix, ".txt", 11); // default to english
+ snprintf(path_buf, PATH_MAX, "%s/text/war_quotes.txt", dataDir);
+ break;
+ }
+
+ try {
+ TEXTBLOCK* new_war_quotes = new TEXTBLOCK(path_buf);
+ if (war_quotes)
+ delete war_quotes;
+ war_quotes = new_war_quotes;
+ } catch (...) { /* can't do anything helpful here anyway */ }
+
+
+ LOAD_TEXT_BLOCK(gloat, "gloat")
+ LOAD_TEXT_BLOCK(ingame, "ingame")
+ LOAD_TEXT_BLOCK(instructions, "instr")
+ LOAD_TEXT_BLOCK(panic, "panic")
+ LOAD_TEXT_BLOCK(kamikaze, "kamikaze")
+ LOAD_TEXT_BLOCK(retaliation, "retaliation")
+ LOAD_TEXT_BLOCK(revenge, "revenge")
+ LOAD_TEXT_BLOCK(suicide, "suicide")
+}
+
- if (_global && _global->lastUpdatesCount >= MAXUPDATES)
- make_fullUpdate();
+/** @brief load a background music file.
+ *
+ * This function loads a music file (if there is one available.)
+ * If a current sample is set, it will be released.
+ *
+ * @return true if a sample is loaded, false otherwise.
+**/
+bool ENVIRONMENT::loadBackgroundMusic()
+{
+ static bool isSecondTry = false;
+
+ // see if we should bother
+ if (!play_music)
+ return false;
+
+ SAMPLE* newStream = nullptr;
+ dirent* folder_entry = nullptr;
+
+
+ // see if we have the music folder open
+ if (! music_dir) {
+ snprintf(path_buf, PATH_MAX, "%s/music", configDir);
+ music_dir = opendir(path_buf);
+ if (!music_dir)
+ return false;
+ }
+
+
+ // at this point we should have an open music folder
+ // the music folder is closed by global's deconstructor
+ // search for files ending in .wav
+ folder_entry = readdir(music_dir);
+ while (folder_entry && !newStream) {
+ // we have something, see if it is a wav file
+ if ( strstr(folder_entry->d_name, ".wav") ) {
+ snprintf(path_buf, PATH_MAX, "%s/music/%s",
+ configDir, folder_entry->d_name);
+ newStream = load_sample(path_buf);
+ }
+ if (!newStream)
+ folder_entry = readdir(music_dir);
+ }
+
+ if (!folder_entry) {
+ // hit end of folder
+ closedir(music_dir);
+ music_dir = nullptr;
+
+ // If there is a current background music file loaded, then the
+ // directory is just gone through completely. In that case a
+ // recursive call re-opens the directory and starts anew.
+ if (!isSecondTry && background_music) {
+ isSecondTry = true;
+ return loadBackgroundMusic();
+ } else {
+ // Otherwise there is either an error or there are no
+ // files in that directory
+ if (background_music) {
+ // Okay, this is odd.
+ destroy_sample(background_music);
+ background_music = nullptr;
+ }
+ play_music = false;
+ return false;
+ }
+ }
+
+ if (background_music)
+ destroy_sample(background_music);
+ background_music = newStream;
+ isSecondTry = false;
+
+ return true;
}
-void ENVIRONMENT::make_fullUpdate()
+
+/*
+ * This function loads all the bitmaps needed by the game.
+ * Bitmaps are found in a series of sub-folders under the
+ * data directory. The function returns true on success and
+ * false if an error occurs.
+*/
+bool ENVIRONMENT::loadBitmaps()
{
- // Replace Updates with a full screen update:
- int iOldCombUpd = combineUpdates;
- combineUpdates = 0;
-
- // They are splitted into 4 x 2 updates:
- int iQuartWidth = _global->halfWidth / 2;
- int iHalfHeight = _global->halfHeight;
-
- _global->updateCount = 0;
- for (int x = 0; x < 4; x++)
- for (int y = 0; y < 2; y++)
- make_update(iQuartWidth * x , iHalfHeight * y,
- iQuartWidth * (x+1) , iHalfHeight * (y+1));
-
- _global->lastUpdatesCount = 0;
- for (int x = 0; x < 4; x++)
- for (int y = 0; y < 2; y++)
- make_bgupdate(iQuartWidth * x , iHalfHeight * y,
- iQuartWidth * (x+1) , iHalfHeight * (y+1));
-
- combineUpdates = iOldCombUpd;
+ int32_t file_group = 0;
+ char sub_folder[9] = { 0 };
+ BITMAP* newbitmap = nullptr;
+ BITMAP** bitmap_array = nullptr;
+
+ while (file_group < 7) {
+ // set the folder we're looking at
+ switch (file_group) {
+ case 0: strncpy(sub_folder, "title", 8); break;
+ case 1: strncpy(sub_folder, "button", 8); break;
+ case 2: strncpy(sub_folder, "misc", 8); break;
+ case 3: strncpy(sub_folder, "missile", 8); break;
+ case 4: strncpy(sub_folder, "stock", 8); break;
+ case 5: strncpy(sub_folder, "tank", 8); break;
+ case 6: strncpy(sub_folder, "tankgun", 8); break;
+ }
+
+ // set up empty array
+ int32_t array_size = 10;
+ bitmap_array = (BITMAP **) calloc(10, sizeof(BITMAP *) );
+ if (! bitmap_array) {
+ printf("Ran out of memory, loading bitmaps.\n");
+ return false;
+ }
+
+ // search for files
+ int32_t file_count = 0;
+ snprintf(path_buf, PATH_MAX, "%s/%s/%d.bmp", dataDir,
+ sub_folder, file_count);
+ while ( !access(path_buf, F_OK | R_OK) && bitmap_array ) {
+ newbitmap = load_bitmap(path_buf, nullptr);
+ if (! newbitmap)
+ printf("An error occured loading bitmap %s\n", path_buf);
+
+ // Crop tank bitmaps for unification
+ if (newbitmap && (5 == file_group)) {
+ int32_t left = 0;
+ int32_t right = newbitmap->w;
+ int32_t top = 0;
+ int32_t bottom = newbitmap->h;
+
+ // Find real left edge
+ bool hasPix = false;
+ while (!hasPix && (left < right)) {
+ for (int32_t y = top; !hasPix && (y < bottom); ++y) {
+ if (PINK != getpixel(newbitmap, left, y))
+ hasPix = true;
+ }
+ if (!hasPix)
+ ++left;
+ }
+
+ // Find real right edge
+ hasPix = false;
+ while (!hasPix && (right > left)) {
+ for (int32_t y = top; !hasPix && (y < bottom); ++y) {
+ if (PINK != getpixel(newbitmap, right, y))
+ hasPix = true;
+ }
+ if (!hasPix)
+ --right;
+ }
+
+ // Find real top edge
+ hasPix = false;
+ while (!hasPix && (top < bottom)) {
+ for (int32_t x = left; !hasPix && (x < right); ++x) {
+ if (PINK != getpixel(newbitmap, x, top))
+ hasPix = true;
+ }
+ if (!hasPix)
+ ++top;
+ }
+
+ // Find real bottom edge
+ hasPix = false;
+ while (!hasPix && (bottom > top)) {
+ for (int32_t x = left; !hasPix && (x < right); ++x) {
+ if (PINK != getpixel(newbitmap, x, bottom))
+ hasPix = true;
+ }
+ if (!hasPix)
+ --bottom;
+ }
+
+ // Now create the real bitmap
+ bitmap_array[file_count] = create_bitmap(right - left,
+ bottom - top);
+ blit(newbitmap, bitmap_array[file_count], left, top, 0, 0,
+ right - left, bottom - top);
+
+ destroy_bitmap(newbitmap);
+ } // End of cropping tank bitmap
+
+ // otherwise just copy the bitmap pointer
+ else {
+ bitmap_array[file_count] = newbitmap;
+ }
+
+ file_count++;
+
+ // make sure array is large enough
+ if ( file_count >= array_size) {
+ array_size += 10;
+ bitmap_array = (BITMAP **) realloc(bitmap_array,
+ sizeof(BITMAP *) * (array_size + 1) );
+ if (! bitmap_array) {
+ printf("Unable to increase array size while loading bitmaps.\n");
+ return false;
+ } else
+ memset(bitmap_array + file_count, 0,
+ sizeof(BITMAP*) * (array_size - file_count));
+ }
+
+ // get next file
+ snprintf(path_buf, PATH_MAX, "%s/%s/%d.bmp", dataDir,
+ sub_folder, file_count);
+ }
+
+ // save the new array
+ switch (file_group) {
+ case 0: title = bitmap_array; break;
+ case 1: button = bitmap_array; break;
+ case 2: misc = bitmap_array; break;
+ case 3: missile = bitmap_array; break;
+ case 4: stock = bitmap_array; break;
+ case 5: tank = bitmap_array; break;
+ case 6: tankgun = bitmap_array; break;
+ }
+
+ file_group++;
+ }
+
+ return true;
}
+// This file loads in extra fonts the game requires.
+// Fonts should be stored in the datafolder. On
+// success the function returns true. When an
+// error occurs, it returns false.
+bool ENVIRONMENT::loadFonts()
+{
+ snprintf(path_buf, PATH_MAX, "%s/unicode.dat", dataDir);
+ main_font = load_font(path_buf, nullptr, nullptr);
+ if (main_font)
+ font = main_font;
+ else
+ printf("Unable to load font %s\n", path_buf);
-int ENVIRONMENT::getAvgBgColor(int aTopLeftX, int aTopLeftY, int aBotRightX, int aBotRightY, double aVelX, double aVelY)
- {
- // Coordinate sets:
- int iLeftX = aTopLeftX;
- int iRightX = aBotRightX;
- int iCentX; // Will be calculated later
- int iTopY = aTopLeftY;
- int iBottomY= aBotRightY;
- int iCentY; // Will be calculated later!
-
- // Colors:
- int iColorToLe, iColorToCe, iColorToRi; // top row
- int iColorCeLe, iColorCeCe, iColorCeRi; // center row
- int iColorBoLe, iColorBoCe, iColorBoRi; // bottom row
- int iRed = 0;
- int iGreen = 0;
- int iBlue = 0;
-
- // Get the coordinates into range:
- if (aTopLeftX < 1) iLeftX = 1;
- if (aTopLeftX > (_global->screenWidth - 2)) iLeftX = _global->screenWidth - 2;
- if (aBotRightX < 1) iRightX= 1;
- if (aBotRightX > (_global->screenWidth - 2)) iRightX= _global->screenWidth - 2;
- iCentX = (iLeftX + iRightX) / 2;
-
- if (aTopLeftY < (MENUHEIGHT + 1)) iTopY = MENUHEIGHT + 1;
- if (aTopLeftY > (_global->screenHeight - 2)) iTopY = _global->screenHeight - 2;
- if (aBotRightY < (MENUHEIGHT + 1)) iBottomY = MENUHEIGHT + 1;
- if (aBotRightY > (_global->screenHeight - 2)) iBottomY = _global->screenHeight - 2;
- iCentY = (iTopY + iBottomY) / 2;
-
- // Get Sky color or bg color, whatever fits:
- /*---------------------
- --- Left side ---
- ---------------------*/
- if (surface[iLeftX] <= iTopY)
- {
- // All infront of the surface:
- iColorBoLe = getpixel(terrain, iLeftX, iBottomY);
- iColorCeLe = getpixel(terrain, iLeftX, iCentY);
- iColorToLe = getpixel(terrain, iLeftX, iTopY);
- }
- else
- {
- // look where we are going...
- if (surface[iLeftX] > iBottomY)
- {
- // Left part is guaranteed to be in front of the sky, get all three colors at once:
- iColorBoLe = getpixel(sky, iLeftX, iBottomY - MENUHEIGHT);
- iColorCeLe = getpixel(sky, iLeftX, iCentY - MENUHEIGHT);
- iColorToLe = getpixel(sky, iLeftX, iTopY - MENUHEIGHT);
- }
- else
- {
- // At least the bottom color is infront of the terrain
- iColorBoLe = getpixel(terrain, iLeftX, iBottomY);
- if (surface[iLeftX] > iCentY)
- {
- // ...but the other two are in front of the sky:
- iColorCeLe = getpixel(sky, iLeftX, iCentY - MENUHEIGHT);
- iColorToLe = getpixel(sky, iLeftX, iTopY - MENUHEIGHT);
- }
- else
- {
- // Nope, they are in front of the terrain
- iColorCeLe = getpixel(terrain, iLeftX, iCentY);
- iColorToLe = getpixel(terrain, iLeftX, iTopY);
- }
- }
- }
- /*---------------------
- --- The Center ---
- ---------------------*/
- if (surface[iCentX] <= iTopY)
- {
- // All infront of the surface:
- iColorBoCe = getpixel(terrain, iCentX, iBottomY);
- iColorCeCe = getpixel(terrain, iCentX, iCentY);
- iColorToCe = getpixel(terrain, iCentX, iTopY);
- }
- else
- {
- // look where we are going...
- if (surface[iCentX] > iBottomY)
- {
- // Left part is guaranteed to be in front of the sky, get all three colors at once:
- iColorBoCe = getpixel(sky, iCentX, iBottomY - MENUHEIGHT);
- iColorCeCe = getpixel(sky, iCentX, iCentY - MENUHEIGHT);
- iColorToCe = getpixel(sky, iCentX, iTopY - MENUHEIGHT);
- }
- else
- {
- // At least the bottom color is infront of the terrain
- iColorBoCe = getpixel(terrain, iCentX, iBottomY);
- if (surface[iCentX] > iCentY)
- {
- // ...but the other two are in front of the sky:
- iColorCeCe = getpixel(sky, iCentX, iCentY - MENUHEIGHT);
- iColorToCe = getpixel(sky, iCentX, iTopY - MENUHEIGHT);
- }
- else
- {
- // Nope, they are in front of the terrain
- iColorCeCe = getpixel(terrain, iCentX, iCentY);
- iColorToCe = getpixel(terrain, iCentX, iTopY);
- }
- }
- }
- /*----------------------
- --- Right side ---
- ----------------------*/
- if (surface[iRightX] <= iTopY)
- {
- // All infront of the surface:
- iColorBoRi = getpixel(terrain, iRightX, iBottomY);
- iColorCeRi = getpixel(terrain, iRightX, iCentY);
- iColorToRi = getpixel(terrain, iRightX, iTopY);
- }
- else
- {
- // look where we are going...
- if (surface[iRightX] > iBottomY)
- {
- // Left part is guaranteed to be in front of the sky, get all three colors at once:
- iColorBoRi = getpixel(sky, iRightX, iBottomY - MENUHEIGHT);
- iColorCeRi = getpixel(sky, iRightX, iCentY - MENUHEIGHT);
- iColorToRi = getpixel(sky, iRightX, iTopY - MENUHEIGHT);
- }
- else
- {
- // At least the bottom color is infront of the terrain
- iColorBoRi = getpixel(terrain, iRightX, iBottomY);
- if (surface[iRightX] > iCentY)
- {
- // ...but the other two are in front of the sky:
- iColorCeRi = getpixel(sky, iRightX, iCentY - MENUHEIGHT);
- iColorToRi = getpixel(sky, iRightX, iTopY - MENUHEIGHT);
- }
- else
- {
- // Nope, they are in front of the terrain
- iColorCeRi = getpixel(terrain, iRightX, iCentY);
- iColorToRi = getpixel(terrain, iRightX, iTopY);
- }
- }
- }
+ // Store font height
+ if (main_font)
+ fontHeight = text_height(main_font);
- // Fetch the rgb parts, according to movement:
- /* --- X-Movement --- */
- if (aVelX < 0.0)
- {
- // Movement to the left, weight left side color twice
- iRed += (_GET_R(iColorBoLe) * 2) + (_GET_R(iColorCeLe) * 2) + (_GET_R(iColorToLe) * 2);
- iGreen += (_GET_G(iColorBoLe) * 2) + (_GET_G(iColorCeLe) * 2) + (_GET_G(iColorToLe) * 2);
- iBlue += (_GET_B(iColorBoLe) * 2) + (_GET_B(iColorCeLe) * 2) + (_GET_B(iColorToLe) * 2);
- // The others are counted once
- iRed += _GET_R(iColorBoCe) + _GET_R(iColorCeCe) + _GET_R(iColorToCe) + _GET_R(iColorBoRi) + _GET_R(iColorCeRi) + _GET_R(iColorToRi);
- iGreen += _GET_G(iColorBoCe) + _GET_G(iColorCeCe) + _GET_G(iColorToCe) + _GET_G(iColorBoRi) + _GET_G(iColorCeRi) + _GET_G(iColorToRi);
- iBlue += _GET_B(iColorBoCe) + _GET_B(iColorCeCe) + _GET_B(iColorToCe) + _GET_B(iColorBoRi) + _GET_B(iColorCeRi) + _GET_B(iColorToRi);
- }
- if (aVelX == 0.0)
- {
- // No X-Movement, weight center twice
- iRed += (_GET_R(iColorBoCe) * 2) + (_GET_R(iColorCeCe) * 2) + (_GET_R(iColorToCe) * 2);
- iGreen += (_GET_G(iColorBoCe) * 2) + (_GET_G(iColorCeCe) * 2) + (_GET_G(iColorToCe) * 2);
- iBlue += (_GET_B(iColorBoCe) * 2) + (_GET_B(iColorCeCe) * 2) + (_GET_B(iColorToCe) * 2);
- // The others are counted once
- iRed += _GET_R(iColorBoLe) + _GET_R(iColorCeLe) + _GET_R(iColorToLe) + _GET_R(iColorBoRi) + _GET_R(iColorCeRi) + _GET_R(iColorToRi);
- iGreen += _GET_G(iColorBoLe) + _GET_G(iColorCeLe) + _GET_G(iColorToLe) + _GET_G(iColorBoRi) + _GET_G(iColorCeRi) + _GET_G(iColorToRi);
- iBlue += _GET_B(iColorBoLe) + _GET_B(iColorCeLe) + _GET_B(iColorToLe) + _GET_B(iColorBoRi) + _GET_B(iColorCeRi) + _GET_B(iColorToRi);
- }
- if (aVelX > 0.0)
- {
- // Movement to the right, weight right side color twice
- iRed += (_GET_R(iColorBoRi) * 2) + (_GET_R(iColorCeRi) * 2) + (_GET_R(iColorToRi) * 2);
- iGreen += (_GET_G(iColorBoRi) * 2) + (_GET_G(iColorCeRi) * 2) + (_GET_G(iColorToRi) * 2);
- iBlue += (_GET_B(iColorBoRi) * 2) + (_GET_B(iColorCeRi) * 2) + (_GET_B(iColorToRi) * 2);
- // The others are counted once
- iRed += _GET_R(iColorBoCe) + _GET_R(iColorCeCe) + _GET_R(iColorToCe) + _GET_R(iColorBoLe) + _GET_R(iColorCeLe) + _GET_R(iColorToLe);
- iGreen += _GET_G(iColorBoCe) + _GET_G(iColorCeCe) + _GET_G(iColorToCe) + _GET_G(iColorBoLe) + _GET_G(iColorCeLe) + _GET_G(iColorToLe);
- iBlue += _GET_B(iColorBoCe) + _GET_B(iColorCeCe) + _GET_B(iColorToCe) + _GET_B(iColorBoLe) + _GET_B(iColorCeLe) + _GET_B(iColorToLe);
- }
- /* --- Y-Movement --- */
- if (aVelY < 0.0)
- {
- // Movement upwards, weight upper side color twice
- iRed += (_GET_R(iColorToLe) * 2) + (_GET_R(iColorToCe) * 2) + (_GET_R(iColorToRi) * 2);
- iGreen += (_GET_G(iColorToLe) * 2) + (_GET_G(iColorToCe) * 2) + (_GET_G(iColorToRi) * 2);
- iBlue += (_GET_B(iColorToLe) * 2) + (_GET_B(iColorToCe) * 2) + (_GET_B(iColorToRi) * 2);
- // The others are counted once
- iRed += _GET_R(iColorBoLe) + _GET_R(iColorCeLe) + _GET_R(iColorBoCe) + _GET_R(iColorCeCe) + _GET_R(iColorBoRi) + _GET_R(iColorCeRi);
- iGreen += _GET_G(iColorBoLe) + _GET_G(iColorCeLe) + _GET_G(iColorBoCe) + _GET_G(iColorCeCe) + _GET_G(iColorBoRi) + _GET_G(iColorCeRi);
- iBlue += _GET_B(iColorBoLe) + _GET_B(iColorCeLe) + _GET_B(iColorBoCe) + _GET_B(iColorCeCe) + _GET_B(iColorBoRi) + _GET_B(iColorCeRi);
- }
- if (aVelY == 0.0)
- {
- // No Y-Movement, weight center twice
- iRed += (_GET_R(iColorCeLe) * 2) + (_GET_R(iColorCeCe) * 2) + (_GET_R(iColorCeRi) * 2);
- iGreen += (_GET_G(iColorCeLe) * 2) + (_GET_G(iColorCeCe) * 2) + (_GET_G(iColorCeRi) * 2);
- iBlue += (_GET_B(iColorCeLe) * 2) + (_GET_B(iColorCeCe) * 2) + (_GET_B(iColorCeRi) * 2);
- // The others are counted once
- iRed += _GET_R(iColorBoLe) + _GET_R(iColorToLe) + _GET_R(iColorBoCe) + _GET_R(iColorToCe) + _GET_R(iColorBoRi) + _GET_R(iColorToRi);
- iGreen += _GET_G(iColorBoLe) + _GET_G(iColorToLe) + _GET_G(iColorBoCe) + _GET_G(iColorToCe) + _GET_G(iColorBoRi) + _GET_G(iColorToRi);
- iBlue += _GET_B(iColorBoLe) + _GET_B(iColorToLe) + _GET_B(iColorBoCe) + _GET_B(iColorToCe) + _GET_B(iColorBoRi) + _GET_B(iColorToRi);
- }
- if (aVelY > 0.0)
- {
- // Movement downwards, weight lower side color twice
- iRed += (_GET_R(iColorBoRi) * 2) + (_GET_R(iColorBoCe) * 2) + (_GET_R(iColorBoLe) * 2);
- iGreen += (_GET_G(iColorBoRi) * 2) + (_GET_G(iColorBoCe) * 2) + (_GET_G(iColorBoLe) * 2);
- iBlue += (_GET_B(iColorBoRi) * 2) + (_GET_B(iColorBoCe) * 2) + (_GET_B(iColorBoLe) * 2);
- // The others are counted once
- iRed += _GET_R(iColorToLe) + _GET_R(iColorCeLe) + _GET_R(iColorToCe) + _GET_R(iColorCeCe) + _GET_R(iColorToRi) + _GET_R(iColorCeRi);
- iGreen += _GET_G(iColorToLe) + _GET_G(iColorCeLe) + _GET_G(iColorToCe) + _GET_G(iColorCeCe) + _GET_G(iColorToRi) + _GET_G(iColorCeRi);
- iBlue += _GET_B(iColorToLe) + _GET_B(iColorCeLe) + _GET_B(iColorToCe) + _GET_B(iColorCeCe) + _GET_B(iColorToRi) + _GET_B(iColorCeRi);
- }
- /* I know this looks weird, but what we now have is some kind of summed matrix, which is always the same:
- * Let's assume that dVelX and dVelY are both 0.0, so no movement is happening. The result is: (In counted times)
- * 2|3|2 ( = 7)
- * -+-+-
- * 3|4|3 ( = 10)
- * -+-+-
- * 2|3|2 ( = 7)
- * = 24
- * And it is always 24, no matter which movement combination you try. */
- iRed /= 24;
- if (iRed > 0xff) iRed = 0xff;
- iGreen/= 24;
- if (iGreen> 0xff) iGreen= 0xff;
- iBlue /= 24;
- if (iBlue > 0xff) iBlue = 0xff;
-
- return(makecol(iRed, iGreen, iBlue));
- }
+ return main_font ? true : false;
+}
+/// @brief collection of all game text file loadings.
+bool ENVIRONMENT::loadGameFiles()
+{
+ // Before the (language specific) weapons texts can be loaded,
+ // the english one must be pre-loaded to get the weapons data.
+ // all other files only hold the texts.
+ bool status = true;
+ if (EL_ENGLISH != language) {
+ eLanguages cur_lang = language;
+ language = EL_ENGLISH;
+ status = Load_Weapons_Text();
+ language = cur_lang;
+ }
+
+ if (status)
+ status = Load_Weapons_Text();
+
+ if (!status) {
+ cerr << "ERROR: An error occurred trying to read weapons file." << endl;
+ return status;
+ }
+ // Note: If english is chosen, the first load is not done.
+ // If english is not chosen, the first load pre-loads english
+ // Thus the second load is always necessary.
+
+
+ bitmap_filenames = Find_Bitmaps(&number_of_bitmaps);
+
+ // If no bitmaps where found, a custom background is futile.
+ if ( custom_background
+ && !bitmap_filenames)
+ custom_background = 0;
+
+ Create_Music_Folder();
+ genItemsList ();
+
+ return status;
+}
-/*
- * This function puts all the of the environment settings back
- * to their default values. These are settings which get written
- * to the config file.
- * -- Jesse
- * */
-void ENVIRONMENT::Reset_Options()
+/** @brief load all needed sounds
+ * This function loads all sounds from the data folder and saves them
+ * in an array.
+ * @return true on success or false if an error happens.
+**/
+bool ENVIRONMENT::loadSounds()
{
- windstrength = 8;
- windvariation = 1;
- viscosity = 0.5;
- gravity = 0.150;
- weapontechLevel = 5;
- itemtechLevel = 5;
- meteors = 0;
- lightning = 0;
- satellite = 0.0;
- fog = 0;
- landType = LANDTYPE_RANDOM;
- landSlideType = LANDSLIDE_GRAVITY;
- landSlideDelay = MAX_GRAVITY_DELAY;
- falling_dirt_balls = 0.0;
- wallType = 0.0;
- dBoxedMode = 0.0;
- dFadingText = 0.0;
- dShadowedText = 0.0;
- custom_background = 0.0;
+ SAMPLE *temp_sample = nullptr;
+
+ // allocate space for sound samples
+ sounds = (SAMPLE **) calloc(SND_COUNT, sizeof(SAMPLE *) );
+ if (! sounds) {
+ printf("Unable to create sound array.\n");
+ return false;
+ }
+
+ // read from directory
+ for (int32_t i = 0; i < SND_COUNT; ++i) {
+ snprintf(path_buf, PATH_MAX, "%s/sound/%02d.wav", dataDir, i);
+ if (!access(path_buf, R_OK)) {
+ temp_sample = load_sample(path_buf);
+ if (temp_sample)
+ sounds[i] = temp_sample;
+ else
+ fprintf(stderr, "An error occured loading sound file %s\n", path_buf);
+ }
+ // No else, because the sound enum has free slots.
+ }
+
+ return true;
}
-/*
-This function checks to see which wall type we are using
-and sets the appropriate colour.
-*/
-void ENVIRONMENT::Set_Wall_Colour()
+void ENVIRONMENT::newRound ()
{
+ // set wall type
+ if (wallType == WALL_RANDOM)
+ current_wallType = rand() % 4;
+ else
+ current_wallType = wallType;
+
+ time_to_fall = (rand() & landSlideDelay) + 1;
+
+ // Set boxed mode
+ if (BM_RANDOM == boxedMode) {
+ if (rand() % 2)
+ isBoxed = true;
+ else
+ isBoxed = false;
+ } else if (BM_ON == boxedMode)
+ isBoxed = true;
+ else
+ isBoxed = false;
+
+ // Set wall colour
switch (current_wallType)
{
case WALL_RUBBER:
@@ -1119,80 +1333,237 @@ void ENVIRONMENT::Set_Wall_Colour()
case WALL_WRAP:
wallColour = makecol(255, 255, 0); break;
}
-}
+ // Init player array
+ for (int32_t i = 0; i < MAXPLAYERS; ++i)
+ playerOrder[i] = nullptr;
+}
-bool ENVIRONMENT::Get_Boxed_Mode(GLOBALDATA *global)
+void ENVIRONMENT::removeGamePlayer(PLAYER* player_)
{
- if (dBoxedMode == 2.0)
- {
- if (rand() % 2)
- global->bIsBoxed = true;
- else
- global->bIsBoxed = false;
- }
- else if (dBoxedMode == 1.0)
- global->bIsBoxed = true;
- else if (dBoxedMode == 0.0)
- global->bIsBoxed = false;
- return global->bIsBoxed;
-}
-
+ int32_t fromCount = 0;
+ int32_t toCount = -1;
+
+ if (HUMAN_PLAYER == player_->type)
+ numHumanPlayers--;
+
+ while (fromCount < numGamePlayers) {
+ if (player_ != players[fromCount]) {
+ if ((toCount >= 0) && (fromCount > toCount)) {
+ players[toCount] = players[fromCount];
+ players[fromCount] = nullptr;
+ toCount++;
+ }
+ } else
+ // Position found, now move the remaining players down!
+ toCount = fromCount;
+ fromCount++;
+ }
+ numGamePlayers--;
+}
-TANK *ENVIRONMENT::Get_Next_Tank(int *wrapped_around)
+/*
+ * This function puts all the of the environment settings back
+ * to their default values. These are settings which get written
+ * to the config file.
+ * -- Jesse
+ * */
+void ENVIRONMENT::Reset_Options()
{
- int index;
- int found = FALSE;
- int old_index;
- int wrapped = 0;
-
- index = global_tank_index;
- old_index = index;
- index++;
- while (!found)
- {
- if (index >= MAXPLAYERS)
- {
- index = 0;
- *wrapped_around = TRUE;
- wrapped++;
- }
- if ( (order[index]) && (index != old_index) )
- {
- found = TRUE;
- }
- else
- index++;
- if (wrapped > 1)
- break;
- }
-
- /*
- if (! found)
- *wrapped_around = TRUE;
- else
- *wrapped_around = FALSE;
- */
- global_tank_index = index;
- return order[index];
+ boxedMode = BM_OFF;
+ check_for_updates = true;
+ colourTheme = CT_CRISPY;
+ custom_background = 0;
+ debris_level = 1;
+ detailedLandscape = false;
+ detailedSky = false;
+ ditherGradients = true;
+ divide_money = false;
+ fadingText = false;
+ falling_dirt_balls = 0;
+ fog = 0;
+ set_fps(60);
+ gravity = 0.15;
+ interest = 1.25;
+ itemtechLevel = 5;
+ landSlideDelay = MAX_GRAVITY_DELAY;
+ landSlideType = SLIDE_GRAVITY;
+ landType = LAND_RANDOM;
+ language = EL_ENGLISH;
+ lightning = 0;
+ maxFireTime = 0;
+ meteors = 0;
+ network_enabled = false;
+ network_port = DEFAULT_NETWORK_PORT;
+ osMouse = true;
+ play_music = 1.0;
+ satellite = 0;
+ scoreHitUnit = 75;
+ scoreRoundWinBonus = 10000;
+ scoreSelfHit = 25;
+ scoreTeamHit = 10;
+ scoreUnitDestroyBonus = 5000;
+ scoreUnitSelfDestroy = 0;
+ sellpercent = 0.80;
+ shadowedText = true;
+ skipComputerPlay = SKIP_HUMANS_DEAD;
+ sound_driver = SD_AUTODETECT;
+ sound_enabled = true;
+ startmoney = 15000;
+ swayingText = true;
+ temp_screenHeight = DEFAULT_SCREEN_HEIGHT;
+ temp_screenWidth = DEFAULT_SCREEN_WIDTH;
+ turntype = TURN_RANDOM;
+ viscosity = 0.5;
+ violent_death = 0;
+ volley_delay = 10;
+ volume_factor = MAX_VOLUME_FACTOR;
+ wallType = WALL_RUBBER;
+ weapontechLevel = 5;
+ windstrength = 8;
+ windvariation = 1;
+
+ strncpy(server_name, "127.0.0.1", 127);
+ strncpy(server_port, "25645", 127);
}
-void ENVIRONMENT::increaseVolume()
+
+/** @brief Save environment settings to a text file
+ *
+ * This function saves the environment settings to a text file. Each line has
+ * the format name=value.\n
+ *
+ * @return true on success and false on failure.
+*/
+bool ENVIRONMENT::save_to_file (FILE *file)
{
- if (volume_factor < MAX_VOLUME_FACTOR)
- ++volume_factor;
+ if (!file)
+ return false;
+
+ fprintf (file, "*ENV*\n");
+
+ fprintf (file, "ACCELERATEDAI=%d\n", skipComputerPlay);
+ fprintf (file, "BOXMODE=%d\n", boxedMode);
+ fprintf (file, "CHECKUPDATES=%d\n", check_for_updates ? 1 : 0);
+ fprintf (file, "COLOURTHEME=%d\n", colourTheme);
+ fprintf (file, "CUSTOMBACKGROUND=%d\n", custom_background);
+ fprintf (file, "DEBRISLEVEL=%d\n", debris_level);
+ fprintf (file, "DETAILEDLAND=%d\n", detailedLandscape ? 1 : 0);
+ fprintf (file, "DETAILEDSKY=%d\n", detailedSky ? 1 : 0);
+ fprintf (file, "DITHER=%d\n", ditherGradients ? 1 : 0);
+ fprintf (file, "DIVIDEMONEY=%d\n", divide_money);
+ fprintf (file, "DOBOXWRAP=%d\n", do_box_wrap);
+ fprintf (file, "DYNAMICMENUBG=%d\n", dynamicMenuBg ? 1 : 0);
+ fprintf (file, "FALLINGDIRTBALLS=%d\n", falling_dirt_balls);
+ fprintf (file, "FOG=%d\n", fog);
+ fprintf (file, "FRAMES=%d\n", frames_per_second);
+ fprintf (file, "FULLSCREEN=%d\n", full_screen);
+ fprintf (file, "GRAVITY=%f\n", gravity);
+ fprintf (file, "INTEREST=%f\n", interest);
+ fprintf (file, "ITEMTECHLEVEL=%d\n", itemtechLevel);
+ fprintf (file, "LANDSLIDEDELAY=%d\n", landSlideDelay);
+ fprintf (file, "LANDSLIDETYPE=%d\n", landSlideType);
+ fprintf (file, "LANDTYPE=%d\n", landType);
+ fprintf (file, "LANGUAGE=%u\n", static_cast<uint32_t>(language));
+ fprintf (file, "LIGHTNING=%d\n", lightning);
+ fprintf (file, "MAXFIRETIME=%d\n", maxFireTime);
+ fprintf (file, "METEORS=%d\n", meteors);
+ fprintf (file, "NETWORKING=%d\n", network_enabled ? 1 : 0);
+ fprintf (file, "NETWORKPORT=%d\n", network_port);
+ fprintf (file, "NUMPERMANENTPLAYERS=%d\n", numPermanentPlayers);
+ fprintf (file, "OSMOUSE=%d\n", osMouse);
+ fprintf (file, "PLAYMUSIC=%d\n", play_music ? 1 : 0);
+ fprintf (file, "ROUNDS=%d\n", rounds);
+ fprintf (file, "SATELLITE=%d\n", satellite);
+ fprintf (file, "SCOREHITUNIT=%d\n", scoreHitUnit);
+ fprintf (file, "SCOREROUNDWINBONUS=%d\n", scoreRoundWinBonus);
+ fprintf (file, "SCORESELFHIT=%d\n", scoreSelfHit);
+ fprintf (file, "SCORETEAMHIT=%d\n", scoreTeamHit);
+ fprintf (file, "SCOREUNITDESTROYBONUS=%d\n", scoreUnitDestroyBonus);
+ fprintf (file, "SCOREUNITSELFDESTROY=%d\n", scoreUnitSelfDestroy);
+ fprintf (file, "SCREENHEIGHT=%d\n", temp_screenHeight);
+ fprintf (file, "SCREENWIDTH=%d\n", temp_screenWidth);
+ fprintf (file, "SELLPERCENT=%f\n", sellpercent);
+ fprintf (file, "SERVERNAME='%s'\n", server_name);
+ fprintf (file, "SERVERPORT='%s'\n", server_port);
+ fprintf (file, "SHOWAIFEEDBACK=%d\n", showAIFeedback ? 1 : 0);
+ fprintf (file, "SHOWFPS=%d\n", showFPS ? 1 : 0);
+ fprintf (file, "SOUNDDRIVER=%d\n", sound_driver);
+ fprintf (file, "SOUNDENABLED=%d\n", sound_enabled ? 1 : 0);
+ fprintf (file, "STARTMONEY=%d\n", startmoney);
+ fprintf (file, "TEXTFADE=%d\n", fadingText ? 1 : 0);
+ fprintf (file, "TEXTSHADOW=%d\n", shadowedText ? 1 : 0);
+ fprintf (file, "TEXTSWAY=%d\n", swayingText ? 1 : 0);
+ fprintf (file, "TURNTYPE=%d\n", turntype);
+ fprintf (file, "VISCOSITY=%f\n", viscosity);
+ fprintf (file, "VIOLENTDEATH=%d\n", violent_death);
+ fprintf (file, "VOLLEYDELAY=%d\n", volley_delay);
+ fprintf (file, "VOLUMEFACTOR=%d\n", volume_factor);
+ fprintf (file, "WALLTYPE=%d\n", wallType);
+ fprintf (file, "WEAPONTECHLEVEL=%d\n", weapontechLevel);
+ fprintf (file, "WINDSTRENGTH=%d\n", windstrength);
+ fprintf (file, "WINDVARIATION=%d\n", windvariation);
+ fprintf (file, "***\n");
+
+ return true;
}
-void ENVIRONMENT::decreaseVolume()
+
+/// @brief This function sends a message to all connected game clients.
+/// @return true on success or false if the message could not be sent
+bool ENVIRONMENT::sendToClients(const char* message)
{
- if (volume_factor > 0)
- --volume_factor;
+ if (! message) return false;
+
+#ifdef NETWORK
+ int32_t written = 0;
+ int32_t message_length = strlen(message);
+
+ for (int32_t index = 0; index < numGamePlayers; index++) {
+ if ( (players[index]) && (players[index]->type == NETWORK_CLIENT) ) {
+ written = write(players[index]->server_socket,
+ message, message_length);
+ if (written < message_length)
+ fprintf(stderr, "%s:%d: Warning: Only %d/%d bytes sent to player %d\n",
+ __FILE__, __LINE__, written, message_length, index);
+ }
+ } // done all players
+#endif // NETWORK
+ return true;
}
-int ENVIRONMENT::scaleVolume(int vol) const
+
+
+/// @brief set new frames per second if valid and calculate dependent values.
+void ENVIRONMENT::set_fps(int32_t new_FPS)
{
- return (vol * volume_factor) / MAX_VOLUME_FACTOR;
+ if (!new_FPS || ((new_FPS > 0) && (new_FPS != frames_per_second)) ) {
+ if (new_FPS)
+ frames_per_second = new_FPS;
+ FPS_mod = 100. / static_cast<double>(frames_per_second);
+ maxVelocity = static_cast<double>(MAX_POWER) * FPS_mod / 100.;
+ }
}
+
+void ENVIRONMENT::window_update(int32_t x, int32_t y, int32_t w, int32_t h)
+{
+ if (x < window.x)
+ window.x = x;
+ if (y < window.y)
+ window.y = y;
+ if (x + w > window.w)
+ window.w = (x + w) - 1;
+ if (y + h > window.h)
+ window.h = (y + h) - 1;
+ if (window.x < 0)
+ window.x = 0;
+ if (window.y < MENUHEIGHT)
+ window.y = MENUHEIGHT;
+ if (window.w > (screenWidth-1))
+ window.w = (screenWidth-1);
+ if (window.h > (screenHeight-1))
+ window.h = (screenHeight-1);
+}
diff --git a/src/environment.h b/src/environment.h
index f4fe553..5934ee3 100644
--- a/src/environment.h
+++ b/src/environment.h
@@ -22,169 +22,263 @@
#include "main.h"
+#include "network.h"
+#include "gfxData.h"
+#include "text.h"
-enum landscapeTypes
-{
- LANDTYPE_RANDOM,
- LANDTYPE_CANYONS,
- LANDTYPE_MOUNTAINS,
- LANDTYPE_VALLEYS,
- LANDTYPE_HILLS,
- LANDTYPE_FOOTHILLS,
- LANDTYPE_PLAIN,
- LANDTYPE_NONE
-};
-enum landSlideTypes
-{
- LANDSLIDE_NONE, // gravity does not exist
- LANDSLIDE_TANK_ONLY, // dirt falls, tank does not
- LANDSLIDE_INSTANT, // dirt falls without you seeing it
- LANDSLIDE_GRAVITY, // normal
- LANDSLIDE_CARTOON // gravity is delayed
-};
+// As everything depends on environment.h, PLAYER, TANK and VIRTUAL_OBJECT
+// Must be forwarded here, and included before the ENVIRONMENT definition
+class VIRTUAL_OBJECT;
+class TANK;
+class PLAYER;
+
+
+#ifndef HAS_DIRENT
+# if defined(ATANKS_IS_MSVC)
+# include "extern/dirent.h"
+# else
+# include <dirent.h>
+# endif // Linux
+# define HAS_DIRENT 1
+#endif //HAS_DIRENT
+
#ifndef MAX_GRAVITY_DELAY
-#define GRAVITY_DELAY 200
-#define MAX_GRAVITY_DELAY 3
+# define GRAVITY_DELAY 200
+# define MAX_GRAVITY_DELAY 3
#endif
-enum wallTypes
-{
- WALL_RUBBER,
- WALL_STEEL,
- WALL_SPRING,
- WALL_WRAP,
- WALL_RANDOM
-};
+#define SPRING_CHANGE 1.25
+#define BOUNCE_CHANGE 0.90
+#define GET_R(x) ((x & 0xff0000) >> 16)
+#define GET_G(x) ((x & 0x00ff00) >> 8)
+#define GET_B(x) ( x & 0x0000ff)
-// stages
-#define STAGE_AIM 0
-#define STAGE_FIRE 1
-#define STAGE_ENDGAME 3
+// Something from externs.h can not be used via include
+// due to circular dependencies.
+extern int32_t GREY;
+extern int32_t GREEN;
+
+// Defined in sound.cpp:
+extern int32_t MAX_VOLUME_FACTOR;
-#define SPRING_CHANGE 1.25
-// size of satellite laser
-#define LASER_NONE 0.0
-#define LASER_WEAK 1.0
-#define LASER_STRONG 2.0
-#define LASER_SUPER 3.0
-
-#define _GET_R(x) ((x & 0xff0000) >> 16)
-#define _GET_G(x) ((x & 0x00ff00) >> 8)
-#define _GET_B(x) (x & 0x0000ff)
-
-// max volume factor: means that the interval 0% -> 100% is splitted in 5
-#define MAX_VOLUME_FACTOR 5
-
-#include "tank.h"
-//class TANK;
-//class MISSILE;
-//class FLOATTEXT;
-//class GLOBALDATA;
-//class VIRTUAL_OBJECT;
+/** @class ENVIRONMENT
+ * @brief Fixed values of the current environment the game takes place in.
+ *
+ * This class holds all values and the corresponding methods that define
+ * the gaming environment.
+ *
+ * This means that all values in here must be set on game round start and
+ * must not change until the game round ends.
+ *
+ * So basically this class consolidates everything set up with the options
+ * menu and by the game round initialization.
+ *
+ * Everything that can change between the game round start and the game round
+ * end has to be managed by GLOBALDATA.
+**/
class ENVIRONMENT
- {
- public:
- GLOBALDATA *_global;
- TANK *order[MAXPLAYERS * 3];
- VIRTUAL_OBJECT *objects[MAX_OBJECTS];
- int availableItems[THINGS];
- int numAvailable;
- int realm, am;
- double wind, lastwind;
- double windstrength, windvariation;
- double viscosity;
- double gravity;
- double weapontechLevel, itemtechLevel;
- double meteors;
- double lightning;
- double falling_dirt_balls;
- double fog;
- // int oldFogX, oldFogY;
- double landType;
- double landSlideType;
- double landSlideDelay;
- char *done;
- int *fp;
- int *h;
- int *surface;
- int *dropTo;
- double *velocity;
- double *dropIncr;
- double *height;
- BITMAP *db, *terrain, *sky;
- BITMAP *waiting_sky, *waiting_terrain; // sky saved in background
- #ifdef THREADS
- pthread_mutex_t* waiting_terrain_lock;
- pthread_mutex_t* waiting_sky_lock;
- void destroy_lock(pthread_mutex_t* lock);
- pthread_mutex_t* init_lock(pthread_mutex_t* lock);
- void lock(pthread_mutex_t* lock);
- void unlock(pthread_mutex_t* lock);
- #endif
- BITMAP* get_waiting_sky();
- BITMAP* get_waiting_terrain();
- const gradient **my_sky_gradients, **my_land_gradients;
- int pclock, mouseclock;
- int stage;
- int combineUpdates;
- double wallType;
- double dBoxedMode; // Can be 0.0 = off, 1.0 = on, 2.0 = random
- double dFadingText; // 0.0 = off, 1.0 = on
- double dShadowedText; // 0.0 = off, 1.0 = on
- int current_wallType, wallColour;
- int time_to_fall; // amount of time dirt will hover
- double satellite;
- int naturals_since_last_shot;
- double custom_background;
- char **bitmap_filenames;
- int number_of_bitmaps;
- int current_drawing_mode;
- int global_tank_index;
- int volume_factor; // from 0 (no volume) to MAX_VOLUME_FACTOR
-
-
- ENVIRONMENT (GLOBALDATA *global);
- ~ENVIRONMENT ();
- void initialise ();
- void setGlobaldata (GLOBALDATA *global)
- {
- _global = global;
- }
- GLOBALDATA *Get_Globaldata()
- {
- return _global;
- }
- void generateAvailableItemsList ();
- int isItemAvailable (int itemNum);
- int addObject (VIRTUAL_OBJECT *object);
- int removeObject (VIRTUAL_OBJECT *object);
- VIRTUAL_OBJECT *getNextOfClass (int classNum, int *index);
- void newRound ();
- int ingamemenu ();
- void make_fullUpdate();
- int getAvgBgColor(int, int, int, int, double, double);
- void do_updates ();
- void replaceCanvas ();
- void make_update (int x, int y, int w, int h);
- void make_bgupdate (int x, int y, int w, int h);
-
- int saveToFile_Text (FILE *file);
- int loadFromFile_Text (FILE *file);
- int loadFromFile (ifstream &ifsFile);
-
- void Reset_Options();
- void Set_Wall_Colour();
- bool Get_Boxed_Mode(GLOBALDATA *global);
- TANK *Get_Next_Tank(int *wrapped_around);
- void increaseVolume();
- void decreaseVolume();
- int scaleVolume(int vol) const;
- };
+{
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+ explicit ENVIRONMENT ();
+ ~ENVIRONMENT ();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+ void addGamePlayer (PLAYER* player_);
+ PLAYER* createNewPlayer (const char* player_name);
+ void creditWinners (int32_t winner);
+ void decreaseVolume ();
+ void deletePermPlayer (PLAYER* player_);
+ void destroy (); // Must be called before allegro shutdown
+ void find_config_dir ();
+ bool find_data_dir ();
+ void first_init (); // Used for the first init after creation
+ void genItemsList ();
+ int32_t getPlayerByName (const char* player_name);
+ void increaseVolume ();
+ int32_t ingamemenu ();
+ void initialise (); // Does a regular initialization
+ bool isItemAvailable (int32_t itemNum);
+ bool loadBackgroundMusic();
+ bool loadBitmaps ();
+ bool loadFonts ();
+ bool loadGameFiles ();
+ void load_from_file (FILE *file);
+ bool loadSounds ();
+ void load_text_files ();
+ void newRound ();
+ void removeGamePlayer (PLAYER* player_);
+ void Reset_Options ();
+ bool save_to_file (FILE *file);
+ bool sendToClients (const char* message); // send a short message to all network clients
+ void set_fps (int32_t new_FPS);
+ void window_update (int32_t x, int32_t y, int32_t w, int32_t h);
+
+
+ /* ----------------------
+ * --- Public members ---
+ * ----------------------
+ */
+
+ PLAYER** allPlayers = nullptr;
+ int32_t availableItems[THINGS];
+ SAMPLE* background_music = nullptr;
+ char** bitmap_filenames = nullptr;
+ int32_t boxedMode = BM_OFF;
+ BITMAP** button = nullptr;
+ bool campaign_mode = false;
+ double campaign_rounds = 0.; // 20% of the total round number
+ bool check_for_updates = true;
+ int32_t colourDepth = 0;
+ int32_t colourTheme = CT_CRISPY; // land and sky gradiant theme
+ char configDir[PATH_MAX + 1];
+ int32_t current_wallType = 0;
+ int32_t custom_background = 0;
+ char dataDir[PATH_MAX + 1];
+ int32_t debris_level = 1;
+ bool detailedLandscape = false;
+ bool detailedSky = false;
+ bool ditherGradients = true;
+ bool divide_money = false;
+ bool do_box_wrap = false;
+ bool drawBackground = true;
+ bool dynamicMenuBg = true;
+ bool fadingText = false;
+ int32_t falling_dirt_balls = 0;
+ int32_t fog = 0;
+ int32_t fontHeight = 0; // Fixed in ctor, no calls to text_height(font) needed.
+ double FPS_mod = 0.; // Pre-calculated, used in many places.
+ int32_t frames_per_second = 0;
+ int32_t full_screen = FULL_SCREEN_FALSE;
+ char game_name[GAMENAMELEN + 1];
+ sGfxData gfxData;
+ double gravity = 0.15;
+ int32_t halfHeight = DEFAULT_SCREEN_HEIGHT / 2;
+ int32_t halfWidth = DEFAULT_SCREEN_WIDTH / 2;
+ double interest = 1.25;
+ int32_t itemtechLevel = 5;
+ bool isBoxed = false;
+ bool isGameLoaded = true;
+ int32_t landSlideDelay = MAX_GRAVITY_DELAY;
+ int32_t landSlideType = SLIDE_GRAVITY;
+ int32_t landType = LAND_RANDOM;
+ eLanguages language = EL_ENGLISH;
+ int32_t lightning = 0;
+ bool loadGame = false;
+ FONT* main_font = nullptr;
+ int32_t maxFireTime = 0;
+ int32_t maxNumTanks = 0;
+ double maxVelocity = 0.;
+ int32_t max_screen_updates = 64;
+ int32_t menuBeginY = 0;
+ int32_t menuEndY = 0;
+ int32_t meteors = 0;
+ BITMAP** misc = nullptr;
+ BITMAP** missile = nullptr;
+ int32_t mouseclock = 0;
+ bool nameAboveTank = true;
+ bool network_enabled = false;
+ int32_t network_port = DEFAULT_NETWORK_PORT;
+ double nextCampaignRound = 0; // When AI players will be raised next
+ int32_t numAvailable = 0;
+ int32_t numGamePlayers = 0;
+ int32_t numHumanPlayers = 0;
+ int32_t numPermanentPlayers = 0;
+ int32_t number_of_bitmaps = 0;
+ bool osMouse = true; // whether we should use the OS or custom mouse
+ bool play_music = true;
+ PLAYER** players = nullptr;
+ PLAYER* playerOrder[MAXPLAYERS];
+ uint32_t rounds = 5;
+ int32_t satellite = 0;
+ uint32_t saved_gameindex = 0;
+ const char** saved_game_list = nullptr;
+ uint32_t saved_game_list_size = 0;
+ int32_t scoreHitUnit = 75;
+ int32_t scoreRoundWinBonus = 10000;
+ int32_t scoreSelfHit = 25;
+ int32_t scoreTeamHit = 10;
+ int32_t scoreUnitDestroyBonus = 5000;
+ int32_t scoreUnitSelfDestroy = 2500;
+ int32_t screenHeight = DEFAULT_SCREEN_HEIGHT;
+ int32_t screenWidth = DEFAULT_SCREEN_WIDTH;
+ double sellpercent = 0.80;
+ char server_name[129];
+ char server_port[129];
+ bool shadowedText = true;
+ bool showAIFeedback = true;
+ bool showFPS = false;
+ int32_t skipComputerPlay = SKIP_HUMANS_DEAD;
+ BITMAP* sky = nullptr;
+ double slope[360][2];
+ int32_t sound_driver = SD_AUTODETECT;
+ bool sound_enabled = true;
+ SAMPLE** sounds = nullptr;
+ int32_t startmoney = 15000;
+ BITMAP** stock = nullptr;
+ bool swayingText = true;
+ BITMAP** tank = nullptr;
+ BITMAP** tankgun = nullptr;
+ int32_t temp_screenHeight = 0; // 0 to detect command line arguments
+ int32_t temp_screenWidth = 0; // versus loaded configuration.
+ int32_t time_to_fall = 0; // amount of time dirt will hover
+ BITMAP** title = nullptr;
+ int32_t turntype = TURN_RANDOM;
+ double viscosity = 0.5;
+ int32_t violent_death = 0;
+ int32_t voices = 0;
+ int32_t volley_delay = 10; // Delay factor for volley shots, 5-50
+ int32_t volume_factor = MAX_VOLUME_FACTOR;
+ int32_t wallColour = GREEN;
+ int32_t wallType = WALL_RUBBER;
+ int32_t weapontechLevel = 5;
+ BOX window;
+ int32_t windstrength = 8;
+ int32_t windvariation = 1;
+
+ // Text structures holding (translated) lines of in game text
+ TEXTBLOCK* gloat = nullptr;
+ TEXTBLOCK* ingame = nullptr;
+ TEXTBLOCK* instructions = nullptr;
+ TEXTBLOCK* panic = nullptr;
+ TEXTBLOCK* kamikaze = nullptr;
+ TEXTBLOCK* retaliation = nullptr;
+ TEXTBLOCK* revenge = nullptr;
+ TEXTBLOCK* suicide = nullptr;
+ TEXTBLOCK* war_quotes = nullptr;
+
+
+private:
+
+ /* -----------------------
+ * --- Private methods ---
+ * -----------------------
+ */
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ DIR* music_dir = nullptr;
+};
+
+
+#define HAS_ENVIRONMENT 1
#endif
diff --git a/src/explosion.cpp b/src/explosion.cpp
index 29b02e9..ed44eda 100644
--- a/src/explosion.cpp
+++ b/src/explosion.cpp
@@ -15,7 +15,11 @@
* 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.
- * */
+ */
+
+#include "main.h"
+
+#include <cassert>
#include "environment.h"
#include "globaldata.h"
@@ -26,414 +30,949 @@
#include "player.h"
-EXPLOSION::~EXPLOSION ()
+/// @brief constructor for all detonations that are not caused by BEAMs
+EXPLOSION::EXPLOSION (PLAYER* player_, double x_, double y_,
+ double xv_, double yv_, int32_t type, bool is_weapon) :
+ PHYSICAL_OBJECT(is_weapon),
+ impact_xv(xv_),
+ impact_yv(yv_)
{
- /* the immediate destruction of the background creates a need for a second dirt slide,
- or there are holes left
- (important for chain missiles to work properly!) */
- for (int z = 0; z <= (radius + 2); z++)
- {
- setSlideColumnDimensions (_global, _env, x + z, true);
- setSlideColumnDimensions (_global, _env, x - z, true);
- }
- _env->removeObject (this);
- weap = NULL;
- _global = NULL;
- _env = NULL;
+ xv = xv_;
+ yv = yv_;
+
+ if ( (TREMOR <= type) && (TECTONIC >= type) )
+ angle = GET_ANGLE(xv, yv);
+ else
+ angle = GET_SAFE_ANGLE(xv, yv, 0);
+
+ player = player_;
+ drag = 0.95;
+ mass = 3;
+ x = x_;
+ y = y_;
+ maxVel = env.maxVelocity * (1.20 + (mass / (.01 * MAX_POWER)));
+
+ WEAPON* weap = nullptr;
+ weapType = type;
+ if (weapType < WEAPONS)
+ weap = &weapon[weapType];
+ else
+ weap = &naturals[weapType - WEAPONS];
+
+ radius = weap->radius;
+ etime = weap->etime;
+ damage = weap->damage;
+
+ if ( ( (SHAPED_CHARGE <= weapType) && (CUTTER >= weapType) )
+ || ( DRILLER == weapType) ) {
+ // Uses FlameFront
+ explo_w = FLAME_W;
+ explo_h = FLAME_H;
+ centre_x = FLAME_CX;
+ centre_y = FLAME_CY;
+ flame_w = static_cast<float>(radius) * 2.f;
+ flame_h = static_cast<float>(radius) / 10.f;
+ } else {
+ // explo_* and centre_* are already set
+ flame_w = static_cast<float>(radius) * 2.f;
+ flame_h = static_cast<float>(radius) * 2.f;
+ }
+ scale = static_cast<float>(radius) / centre_x;
+
+ // make sure dirt appears on the screen, not above the playing area,
+ // and all other explosions at least reach into the area:
+ int32_t minHeightMiss = MENUHEIGHT + (env.isBoxed ? 1 : 0);
+ int32_t minHeightDirt = minHeightMiss + radius;
+ if ( (weapType >= DIRT_BALL)
+ && (weapType <= SMALL_DIRT_SPREAD) ) {
+ if (y < minHeightDirt)
+ y = minHeightDirt;
+ } else if ( (y < minHeightMiss)
+ && ( !env.isBoxed
+ || !env.do_box_wrap
+ || (WALL_WRAP != env.current_wallType) ) )
+ y = minHeightMiss;
+
+ // For all others
+ // Some weapons have no damage to apply:
+ // Note: Napalm Jellies deal damage over time and not at once.
+ if ( (NAPALM_JELLY == weapType)
+ || ( (weapType >= RIOT_BOMB) && (weapType <= CLUSTER_MIRV) ) ) {
+ apply_damage = false;
+ // And those wouldn't throw debris or clear terrain either
+ hasThrown = true;
+ hasCleared = true;
+ } else if (env.debris_level > 0) {
+ maxDebris = (radius * 2) / (8 - (2 * env.debris_level));
+ maxFrame = 2 + (2 * env.debris_level);
+
+ // Tremor, Shockwave and Tectonic Shift must not throw debris around
+ if ( (TREMOR <= weapType) && (TECTONIC >= weapType))
+ maxDebris = 0;
+ else if (maxDebris < 5)
+ maxDebris = 5;
+ }
+
+ // Lasers and beams do only issue a tiny explosion to add the
+ // dirt throw effect and some boom bang.
+ if ( ( (weapType >= SML_LAZER) && (weapType <= LRG_LAZER) )
+ || ( (weapType >= SML_LIGHTNING) && (weapType <= LRG_LIGHTNING) ) ) {
+ apply_damage = false;
+ etime = 0;
+
+ if (radius < 2)
+ radius = 2;
+
+ maxDebris /= radius;
+
+ if (maxDebris < 3)
+ maxDebris = 3;
+ else if (maxDebris > 6)
+ maxDebris = 6;
+ }
+
+ // Napalm Jellies need a bit more variation:
+ if (NAPALM_JELLY == weapType)
+ curFrame = ( (rand() % (2 * env.frames_per_second))
+ - env.frames_per_second) / etime;
+
+ // Unless this is a napalm jelly, that does not clear away any dirt,
+ // lock our field of devastation so no sliding into the explosion occurs
+ else
+ global.addLandSlide(x - radius - 1, x + radius + 1, true);
+
+ // Add to the chain:
+ global.addObject(this);
}
-EXPLOSION::EXPLOSION (GLOBALDATA *global, ENVIRONMENT *env, double xpos, double ypos,
- int weaponType):PHYSICAL_OBJECT(),weap(NULL)
+
+/// This one is just for the beam and lightning dirt mills.
+EXPLOSION::EXPLOSION (PLAYER* player_, double x_, double y_,
+ double xv_, double yv_, int32_t type, double damage_,
+ bool is_weapon) :
+ // delegate base settings
+ EXPLOSION(player_, x_, y_, xv_, yv_, type, is_weapon)
{
- drag = 0.95;
- mass = 3;
- a = 1;
- peaked = 0;
- exclock = 500;
- bIsWeaponExplosion = true; // Exploding tanks set them to false themselves!
- setEnvironment (env);
- player = NULL;
- _align = LEFT;
- _global = global;
- #ifdef NETWORK
- /*
- char buffer[256];
- sprintf(buffer, "EXPLOSION %d %d %d", (int) xpos, (int) ypos, weaponType);
- global->Send_To_Clients(buffer);
- */
- #endif
- x = xpos;
- y = ypos;
- type = weaponType;
- if (type < WEAPONS)
- weap = &weapon[type];
- else
- weap = &naturals[type - WEAPONS];
- radius = weap->radius;
- etime = weap->etime;
- damage = weap->damage;
- eframes = weap->eframes;
-
- // make sure dirt appears on the screen, not above the playing area
- if ((type >= DIRT_BALL) && (type <= SMALL_DIRT_SPREAD))
- {
- if (y < DIRT_CEILING)
- y = DIRT_CEILING + radius;
- }
+ damage = damage_;
+}
+
+
+/// @brief default dtor
+EXPLOSION::~EXPLOSION ()
+{
+ // If this is a tremor, the land slide has to be released
+ if ( (TREMOR <= weapType) && (TECTONIC >= weapType))
+ global.unlockLandSlide(dim_cur.x, dim_cur.x + dim_cur.w);
+
+ // Take out of the chain:
+ global.removeObject(this);
}
-EXPLOSION::EXPLOSION (GLOBALDATA *global, ENVIRONMENT *env, double xpos, double ypos, double xvel, double yvel,
- int weaponType):PHYSICAL_OBJECT(),weap(NULL)
+
+/// @brief Physics for the Napalm Jelly, the only explosion that can 'move'
+void EXPLOSION::applyPhysics ()
{
- initialise ();
- setEnvironment (env);
- _align = LEFT;
- _global = global;
- x = xpos;
- y = ypos;
- xv = xvel;
- yv = yvel;
- angle = (int)(atan2(xv, yv) / PI * 180);
- while (angle < 0)
- angle += 360;
- while (angle > 360)
- angle -= 360;
-
- type = weaponType;
- if (type < WEAPONS)
- weap = &weapon[type];
- else
- weap = &naturals[type - WEAPONS];
- radius = weap->radius;
- etime = weap->etime;
- damage = weap->damage;
- eframes = weap->eframes;
+ if (NAPALM_JELLY == weapType) {
+ if ( !global.skippingComputerPlay
+ && !(rand() % (env.frames_per_second / 2)) ) {
+ try {
+ new DECOR (x, y, 0, -2. * env.gravity * env.FPS_mod,
+ radius / 2, DECOR_SMOKE, 0);
+ } catch (std::exception) { /* No reason to fuss, its just smoke. */ }
+ }
+
+ // Instead of the radius, the current blob size is used
+ // for hit and damage calculation. Thus a tank that is only
+ // grazed by the full blob receives a lot less damage.
+ double blobSize = radius - (
+ static_cast<double>(curFrame)
+ / static_cast<double>(EXPLOSIONFRAMES)
+ * radius) + 1.;
+ if ( blobSize < 1.)
+ blobSize = 1.;
+
+ // Stop all movement if dirt is hit:
+ bool can_move = (y < env.screenHeight);
+
+ if (can_move && (y < (env.screenHeight - 1))) {
+ if (PINK != getpixel (global.terrain, x, y + 1))
+ can_move = false;
+ }
+
+ if (can_move) {
+ // If the dirt below is falling away, napalm can fall, too:
+ PHYSICAL_OBJECT::applyPhysics();
+
+ // And falling napalm can be repulsed
+ TANK* lt = nullptr;
+ double xaccel = 0;
+ double yaccel = 0;
+
+ global.getHeadOfClass(CLASS_TANK, <);
+
+ while (lt) {
+ if (!lt->destroy) {
+
+ if (lt->repulse (x + xv, y + yv, &xaccel, &yaccel, physType)) {
+ xv += xaccel;
+ yv += yaccel;
+ }
+ }
+ lt->getNext(<);
+ }
+ } else {
+ xv = 0.;
+ yv = 0.;
+ } // End of normal physics
+
+ // Enable next round checking, because dirt can fall
+ // away so the jelly might follow.
+ hitSomething = false;
+
+ // Napalm keeps burning, check all tanks
+ double in_rate_x = 0.;
+ double in_rate_y = 0.;
+ TANK* lt = nullptr;
+ double damage_mod = blobSize / static_cast<double>(radius);
+
+ global.getHeadOfClass(CLASS_TANK, <);
+
+ while (lt) {
+
+ if ( !lt->destroy
+ && lt->isInEllipse(x, y, blobSize, blobSize, in_rate_x, in_rate_y) ) {
+ double full_rate = in_rate_x * in_rate_y;
+ // If the tank is hit enough for the Napalm to "attach",
+ // stop all movement:
+ if ( (full_rate > 0.9) && (y >= lt->y) ) {
+ xv = 0.;
+ yv = 0.;
+ hitSomething = true;
+ // Note: When the blob shrinks, it can "fall off".
+ }
+
+ // Napalm is *HOT*. Never do less than 50% damage
+ if (full_rate < 0.5)
+ full_rate = 0.5;
+ // Apply damage, but do it per frame
+ lt->addDamage(player,
+ ( static_cast<double>(damage)
+ * damage_mod * full_rate
+ * (player ? player->damageMultiplier : 1.) )
+ / static_cast<double>(env.frames_per_second) );
+ }
+
+ lt->getNext(<);
+ } // End of looping tanks
+ } // End of NAPALM_JELLY special physics
}
-void EXPLOSION::initialise ()
+
+/// @brief draw the explosions according to weapon type and shape
+void EXPLOSION::draw()
{
- PHYSICAL_OBJECT::initialise ();
- drag = 0.95;
- mass = 3;
- a = 1;
- peaked = 0;
- exclock = 500;
- bIsWeaponExplosion = true; // Exploding tanks set them to false themselves!
+ if ( (curFrame > 1) && (curFrame <= (EXPLOSIONFRAMES + 1))
+ && (NAPALM_JELLY != weapType) ) {
+ /* This group includes:
+ * - all regular explosives,
+ * - all items and naturals,
+ * - tremor, shockwave and tectonic shift.
+ */
+ int32_t flameIdx = curFrame - 2;
+ int32_t rad = (radius * curFrame) / EXPLODEFRAMES;
+
+ switch (weapType) {
+ case SHAPED_CHARGE:
+ case WIDE_BOY:
+ case CUTTER:
+ rotate_scaled_sprite (global.canvas, env.gfxData.flameFront[flameIdx],
+ x - radius,
+ y - (radius / 20),
+ itofix (0),
+ ftofix (static_cast<double>(radius) / 300.) );
+
+ setUpdateArea (x - radius - 1, y - (radius / 20) - 1,
+ (radius + 1) * 2, ((radius / 20) + 1) * 2);
+ break;
+ case DRILLER:
+ rotate_scaled_sprite(global.canvas, env.gfxData.flameFront[flameIdx],
+ x - radius,
+ y - (radius / 20),
+ itofix(192),
+ ftofix(static_cast<double>(radius) / 300.) );
+ setUpdateArea(x - (radius / 20) - 1, y - radius - 1,
+ ((radius / 20) + 1) * 2, (radius + 1) * 2);
+ break;
+ case TREMOR:
+ case SHOCKWAVE:
+ case TECTONIC:
+ if (curFrame <= (EXPLODEFRAMES + 1) ) {
+ double tst_width = static_cast<double>(curFrame)
+ / static_cast<double>(EXPLODEFRAMES)
+ * static_cast<double>(radius) / 3.;
+ drawFracture (x, y, angle,
+ static_cast<int32_t>(.75 + tst_width),
+ radius * 1.75,
+ (weapType - TREMOR + 1) * 3, 0);
+ global.addLandSlide(dim_cur.x, dim_cur.x + dim_cur.w, true);
+ }
+ break;
+ case RIOT_BOMB:
+ case HVY_RIOT_BOMB:
+ if (curFrame <= EXPLODEFRAMES) {
+ int32_t colour = player ? player->color : BLUE;
+
+ circlefill (global.terrain, x, y, rad, PINK);
+ circle (global.canvas, x, y, rad, colour);
+
+ setUpdateArea(x - radius - 1, y - radius - 1,
+ (radius + 1) * 2, (radius + 1) * 2);
+ } else if (!peaked) {
+ // Do it here or the slide has an ugly delay.
+ peaked = true;
+ do_clear();
+ }
+ break;
+ case RIOT_CHARGE:
+ case RIOT_BLAST:
+ if (curFrame <= EXPLODEFRAMES) {
+ double sx = x - env.slope[angle][0] * 15;
+ double sy = y - env.slope[angle][1] * 15;
+ int32_t x1 = sx + env.slope[(angle + 45) % 360][0] * rad;
+ int32_t y1 = sy + env.slope[(angle + 45) % 360][1] * rad;
+ int32_t x2 = sx + env.slope[(angle + 315) % 360][0] * rad;
+ int32_t y2 = sy + env.slope[(angle + 315) % 360][1] * rad;
+
+ triangle (global.canvas, sx, sy, x1, y1, x2, y2, player ? player->color : BLUE);
+ triangle (global.terrain, sx, sy, x1, y1, x2, y2, PINK);
+
+ setUpdateArea (sx - rad - 1, sy - rad - 1,
+ (rad + 1) * 2, (rad + 1) * 2);
+ global.addLandSlide(sx - rad - 1, sx + rad + 1, true);
+ } else if (!peaked) {
+ // Do it here or the slide has an ugly delay.
+ peaked = true;
+ do_clear();
+ }
+ break;
+ case SML_LAZER:
+ case MED_LAZER:
+ case LRG_LAZER:
+ case SML_LIGHTNING:
+ case MED_LIGHTNING:
+ case LRG_LIGHTNING:
+ setUpdateArea(x - radius - 1, y - radius - 1,
+ (radius + 1) * 2, (radius + 1) * 2);
+ break;
+ case PERCENT_BOMB:
+ default:
+ if ( (weapType <= LAST_EXPLOSIVE)
+ || (weapType >= WEAPONS)
+ || (weapType == PERCENT_BOMB) ) {
+ rotate_scaled_sprite(global.canvas, env.gfxData.explosions[flameIdx],
+ x - radius, y - radius,
+ itofix (0),
+ ftofix (static_cast<double>(radius) / 107.) );
+ setUpdateArea(x - radius - 1, y - radius - 1,
+ (radius + 1) * 2, (radius + 1) * 2);
+ } else if (curFrame <= EXPLODEFRAMES) {
+ if ( !peaked
+ && (weapType >= DIRT_BALL)
+ && (weapType <= SMALL_DIRT_SPREAD) ) {
+ BITMAP* tmp = create_bitmap(rad * 2, rad * 2); // for mixing
+ int32_t colour = player ? player->color : GREEN;
+
+ clear_to_color(tmp, PINK);
+
+ circlefill(tmp, rad, rad, rad - 1, colour);
+
+ // copy terrain over explosion
+ masked_blit(global.terrain, tmp,
+ x - rad, y - rad, 0, 0,
+ rad * 2, rad * 2);
+
+ // blit back exploded terrain
+ masked_blit(tmp, global.terrain,
+ 0, 0, x - rad, y - rad,
+ rad * 2, rad * 2);
+ destroy_bitmap(tmp);
+ setUpdateArea (x - rad - 1, y - rad - 1,
+ (rad + 1) * 2, (rad + 1) * 2);
+ } else if (REDUCER == weapType) {
+ int32_t col_front = player ? player->color : SILVER;
+ int32_t col_back = GetShadeColor(PURPLE, false, col_front);
+
+ int32_t col_mid = makecol ( (getr(col_front) + getr(col_back)) / 2,
+ (getg(col_front) + getg(col_back)) / 2,
+ (getb(col_front) + getb(col_back)) / 2);
+ circlefill (global.canvas, x, y, rad, col_front);
+
+ for (int32_t i = 1 + (curFrame % 2); i < rad; i += 2)
+ circle(global.canvas, x, y, i, i < (rad / 2) ? col_mid : col_back);
+ setUpdateArea (x - rad - 1, y - rad - 1,
+ (rad + 1) * 2, (rad + 1) * 2);
+ } else {
+ // This is something else. But what?
+ fprintf(stderr, "EXPLOSION::draw() Unknown weapon type %d\n",
+ weapType);
+ circlefill (global.canvas, x, y, rad, RED);
+ setUpdateArea (x - rad - 1, y - rad - 1,
+ (rad + 1) * 2, (rad + 1) * 2);
+ }
+ } else if (!peaked && (curFrame > EXPLODEFRAMES)
+ && (DIRT_BALL <= weapType)
+ && (SUP_DIRT_BALL >= weapType) ) {
+ // Do it here or the slide has an ugly delay.
+ peaked = true;
+ do_clear();
+ }
+ break;
+ }
+ } else if (NAPALM_JELLY == weapType) {
+ // The Jelly does not use flameFront and is drawn individually.
+ int32_t blobSize = curFrame > 0
+ ? static_cast<int32_t>(radius
+ - ( static_cast<double>(curFrame)
+ / static_cast<double>(EXPLOSIONFRAMES)
+ * radius)) + 1
+ : radius;
+ if ( (blobSize > 0) && (curFrame <= (EXPLOSIONFRAMES + 1)) ) {
+ if (blobSize < 2)
+ // avoid circle size crash
+ blobSize = 2;
+ draw_Napalm_Blob(this, x, y, blobSize, curFrame);
+ }
+ }
+
+ /// === Allow clearing once peaked on high enough frame ===
+ ///---------------------------------------------------------
+ if (!peaked && (curFrame > EXPLODEFRAMES) && (NAPALM_JELLY != weapType)) {
+ peaked = true;
+ do_clear();
+ }
+
+ /// === Throw if the frame is in range and it has not thrown, yet ===
+ ///-------------------------------------------------------------------
+ if ( !hasThrown
+ && (curFrame > 1)
+ && (curFrame <= EXPLOSIONFRAMES)
+ && ( (weapType <= TECTONIC) || (weapType > REDUCER) )
+ && (NAPALM_JELLY != weapType) ) {
+
+ do_throw();
+
+ // The tremor types do not need clearing!
+ if ( (TREMOR > weapType) || (TECTONIC < weapType) )
+ do_clear();
+ }
}
-int EXPLOSION::applyPhysics ()
+
+/// @brief Draw recursive fractures
+void EXPLOSION::drawFracture(int32_t x, int32_t y, int32_t frac_angle,
+ int32_t width, int32_t segmentLength,
+ int32_t maxRecurse, int32_t recurseDepth)
{
- if (type == NAPALM_JELLY)
- {
- if (rand () % 300 == 0)
- {
- DECOR *decor;
- decor = new DECOR (_global, _env, x, y, xv, yv, radius / 2, DECOR_SMOKE);
- if (!decor)
- {
- perror ( "explosion.cc: Failed allocating memory for decor in applyPhysics");
- // exit (1);
- }
- // If the dirt below is falling away, napalm can fall, too:
- if (getpixel(_env->terrain, x, y + 1) == PINK)
- {
- // yv = _env->gravity * (100.0 / FRAMES_PER_SECOND);
- yv = _env->gravity * (100.0 / _global->frames_per_second);
- PHYSICAL_OBJECT::applyPhysics();
- }
- else
- yv = 0.0;
- }
- }
- /* if (dispersing)
- {
- double accel = (_env->wind - xv) / mass * drag * _env->viscosity;
- xv += accel;
- x += xv;
- y += yv;
- }
- */
- return (hitSomething);
+ double xLen = env.slope[frac_angle][1] * width;
+ double yLen = env.slope[frac_angle][0] * width;
+ int32_t x1 = x + xLen;
+ int32_t y1 = y + yLen;
+ int32_t x2 = x - xLen;
+ int32_t y2 = y - yLen;
+ int32_t x3 = x + (env.slope[frac_angle][0] * segmentLength);
+ int32_t y3 = y + (env.slope[frac_angle][1] * segmentLength);
+
+ triangle (global.terrain, x1, y1, x2, y2, x3, y3, PINK);
+
+ if (!recurseDepth) {
+ dim_cur.x = x1;
+ dim_cur.y = y1;
+ dim_cur.w = x1;
+ dim_cur.h = y1;
+ } else {
+ dim_cur.x = std::min(std::min(std::min(x1, x2), x3), dim_cur.x);
+ dim_cur.y = std::min(std::min(std::min(y1, y2), y3), dim_cur.y);
+ dim_cur.w = std::max(std::max(std::max(x1, x2), x3), dim_cur.w);
+ dim_cur.h = std::max(std::max(std::max(y1, y2), y3), dim_cur.h);
+ }
+
+ if (recurseDepth < maxRecurse) {
+ for (int32_t branchCount = 0; branchCount < 3; ++branchCount) {
+ if ( branchCount || (Noise(x + y + branchCount) < 0)) {
+ int32_t reduction = 2;
+ int32_t newAngle = frac_angle;
+
+ switch(branchCount) {
+ case 1:
+ newAngle += 90 + (Noise (x + y + 25 + branchCount) * 22.5);
+ reduction = ROUNDu(Noise(x + y + 1 + branchCount) * 4) + 3;
+ break;
+ case 2:
+ newAngle += 270 + (Noise (x + y + 32 + branchCount) * 22.5);
+ reduction = ROUNDu(Noise (x + y + 2 + branchCount) * 4) + 3;
+ break;
+ case 0:
+ default:
+ newAngle += Noise(x + y + 4) * 30;
+ break;
+ }
+
+ while (newAngle < 0)
+ newAngle += 360;
+ newAngle %= 360;
+
+ if (reduction < 2)
+ reduction = 2;
+
+ drawFracture (x3, y3, newAngle, width / reduction,
+ segmentLength / reduction, maxRecurse,
+ recurseDepth + 1);
+ }
+ }
+ }
+
+ // Calculate width and height, previously right and bottom
+ if (!recurseDepth) {
+ dim_cur.w -= dim_cur.x;
+ dim_cur.h -= dim_cur.y;
+ }
}
void EXPLOSION::explode ()
{
- int calcblow;
- double distance;
- int z;
- calcblow = 1;
-
- /* to allow synchronous tank explosions, explosions need to know what they are */
- if (player && player->tank && (type < WEAPONS))
- bIsWeaponExplosion = true;
- else
- bIsWeaponExplosion = false;
-
- if (peaked == 1 && a <= EXPLOSIONFRAMES + 1)
- {
- exclock++;
- if (exclock > weap->etime)
- {
- exclock = 0;
- a++;
- requireUpdate ();
- }
- if (a > EXPLOSIONFRAMES + 1)
- {
- destroy = TRUE;
- }
- }
- if (a <= EXPLODEFRAMES + 1)
- {
- TANK *tank;
- if (a == 1)
- {
- for (int index = 0; (tank = (TANK*)_env->getNextOfClass (TANK_CLASS, &index)) && tank; index++)
- {
- // is tank directly above explosion?
- if ((fabs (x - tank->x) < radius) && (y - tank->y >= 0))
- tank->creditTo = player;
- }
- }
- if ((a == 1) && (type <= TECTONIC || type >= WEAPONS || type == PERCENT_BOMB || type == REDUCER))
- {
- for (int index = 0; (tank = (TANK*)_env->getNextOfClass (TANK_CLASS, &index)) && tank; index++)
- {
- if (type >= SHAPED_CHARGE && type <= CUTTER)
- {
- double dXDistance = fabs (x - tank->x);
- double dYDistance;
- if (y > tank->y)
- dYDistance = fabs (y - tank->y) - (TANKHEIGHT * (3.0 / 4.0)); // To include the bottom of the tank
- else
- dYDistance = fabs (y - tank->y) - (TANKHEIGHT * (3.0 / 8.0)); // The top has to be hit, but not only brushed
-
- if (dYDistance < 0) dYDistance = 0;
-
- if ( (dYDistance <= (radius / 20))
- && (dXDistance >= (TANKWIDTH / 2)))
- distance = ABSDISTANCE(dXDistance, dYDistance, 0, 0);
- else
- distance = 2 * radius; // clear outside the explosion!
-#ifdef DEBUG
- if ((dXDistance < (radius + TANKHEIGHT / 2)) && (dYDistance < 25))
- {
- cout << endl << "Shape: " << radius << " x " << (radius / 20) << endl;
- printf( "Tank X = % 4d, Tank Y = % 4d\n", (int)tank->x, (int)tank->y);
- printf( "Explo X = % 4d, Explo Y = % 4d\n", (int)x, (int)y);
- printf( "Dist X = % 4d, Dist Y = % 4d\n", (int)dXDistance, (int)dYDistance);
- cout << "Distance: " << distance << endl;
- }
-#endif // DEBUG
- }
- else
- distance = ABSDISTANCE(x, y, tank->x, tank->y);
-
- if (distance <= (radius + TANKHEIGHT/2) && tank->l > 0)
- {
- _global->updateMenu = 1;
-
- if (type == PERCENT_BOMB)
- tank->damage = (int) ( (tank->l + tank->sh) / 2) + 1;
- else if ( type == REDUCER )
- {
- if (tank->player->damageMultiplier > 0.1)
- tank->player->damageMultiplier *= 0.75;
- }
- else if (player)
- tank->damage = (int) ((float) damage * ((float) 1 - ((fabs (distance) / (float)radius) / 2)) * player->damageMultiplier);
- // player is not used in weather attacks
- else
- tank->damage = (int) (float) damage * ((float) 1 - ((fabs (distance) / (float)radius) / 2));
- tank->creditTo = player;
- tank->applyDamage ();
- }
- }
- if (type >= TREMOR && type <= TECTONIC)
- {
- angle = (int)(atan2 (yv, xv) / PI * 180);
- if (angle < 0)
- angle = angle + 360;
- angle = angle % 360;
- }
- }
- exclock++;
- if (exclock > weap->etime)
- {
- exclock = 0;
- a++;
- requireUpdate ();
- }
- if (a >= EXPLODEFRAMES + 1 && !peaked)
- {
- calcblow = 0;
- }
- }
- if (!calcblow)
- {
- if (type >= SHAPED_CHARGE && type <= CUTTER)
- {
- ellipsefill (_env->terrain, (int)x, (int)y, radius, radius / 20, PINK);
- setUpdateArea ((int)x - (radius + 1), (int)y - (radius / 20 + 1), (radius + 1) * 2, (radius / 20 + 1) * 2);
-
- }
-
- else if (type == DRILLER)
- {
- ellipsefill (_env->terrain, (int) x, (int) y, radius / 20, radius, PINK);
- setUpdateArea( (int) x - (radius + 1), (int) y - (radius + 1), (radius + 1) * 2, (radius + 1) * 2);
- }
-
- else if (type <= LAST_EXPLOSIVE || type >= WEAPONS || type == PERCENT_BOMB)
- {
-
- if (type != NAPALM_JELLY)
- {
- circlefill (_env->terrain, (int)x, (int)y, radius, PINK);
-// too much? setUpdateArea ((int)x - (radius + 1), (int)y - (radius + 1), (radius + 1) * 2, (radius + 1) * 2);
-// still too much! setUpdateArea ((int)x - radius, (int)y - radius, radius * 2, radius * 2);
- setUpdateArea ((int)x - radius, (int)y - radius, (radius * 2) - 2, (radius * 2) - 2);
- }
- }
- else if ((type >= RIOT_BOMB) && (type <= HVY_RIOT_BOMB))
- {
- circlefill (_env->terrain, (int)x, (int)y, radius, PINK);
- setUpdateArea ((int)x - (radius + 1), (int)y - (radius + 1), (radius + 1) * 2, (radius + 1) * 2);
- }
- else if ((type >= RIOT_CHARGE) && (type <= RIOT_BLAST))
- {
- double sx = x - _global->slope[angle][0] * 15;
- double sy = y - _global->slope[angle][1] * 15;
- triangle (_env->terrain,
- (int)sx,
- (int)sy,
- (int)(sx + _global->slope[(angle + 45) % 360][0] * radius),
- (int)(sy + _global->slope[(angle + 45) % 360][1] * radius),
- (int)(sx + _global->slope[(angle + 315) % 360][0] * radius),
- (int)(sy + _global->slope[(angle + 315) % 360][1] * radius), PINK);
- setUpdateArea ((int)sx - (radius + 1), (int)sy - (radius + 1), (radius + 1) * 2, (radius + 1) * 2);
- }
- else if ((type >= DIRT_BALL) && (type <= SMALL_DIRT_SPREAD))
- {
- BITMAP *tmp; //for mixing
-
- tmp = create_bitmap(radius * 2, radius * 2);
- clear_to_color(tmp, PINK);
- for (int count = 0; count < radius ; count++)
- {
- circle (tmp, radius, radius, count, (player)?player->color: makecol (0, 255, 0) );
- }
- setUpdateArea ((int)x - (radius + 1), (int)y - (radius + 1), (radius + 1) * 2, (radius + 1) * 2);
-
- //copy terrain over explosion
- masked_blit(_env->terrain, tmp, (int)x - radius, (int)y - radius, 0, 0, radius*2, radius*2);
-
- //blit back exploded terrain
- masked_blit(tmp, _env->terrain, 0, 0,(int)x - radius, (int)y - radius, radius*2, radius*2);
-
- destroy_bitmap(tmp);
- }
-
- for (z = 0; z < (_current.w + 2); z++)
- setSlideColumnDimensions (_global, _env, _current.x + z, TRUE);
- calcblow = 1;
- peaked = 1;
- dispersing = 1;
- }
+ /// === Time and frame advancement ===
+ ///------------------------------------
+ if (curFrame <= (EXPLOSIONFRAMES + 1) ) {
+ if (++exclock > etime) {
+ exclock = 0;
+ ++curFrame;
+ requireUpdate();
+
+ }
+ } // End of time and frame advancement
+
+ // Check whether the explosion ends:
+ if (curFrame > (EXPLOSIONFRAMES + 1))
+ destroy = true;
+
+ /// === Apply Damage if not done, yet ===
+ ///---------------------------------------
+ if (apply_damage) {
+ // In this case the affected tanks must be checked first
+ TANK* lt = nullptr;
+ weaponType wType = static_cast<weaponType>(weapType);
+
+ // But do not check dirt balls, they deal no damage
+ if ( (DIRT_BALL > weapType)
+ || (SUP_DIRT_BALL < weapType) ) {
+
+ global.getHeadOfClass(CLASS_TANK, <);
+
+ while (lt) {
+ double dmg = get_hit_damage(lt, wType, x, y);
+
+ if (dmg > 0.) {
+ if (PERCENT_BOMB == weapType)
+ lt->addDamage(player, dmg); // already set, no multiplier
+ else if (REDUCER == weapType) {
+ lt->player->damageMultiplier *= 0.75; // already checked
+ } else
+ lt->addDamage(player, dmg
+ * (player ? player->damageMultiplier : 1.) );
+ } // End of having damage to deal
+
+ lt->getNext(<);
+ } // End of looping tanks
+ } // end of having no dirt ball
+ apply_damage = false;
+ } // End of handling damage
+}
+
+
+// ======================================
+// === Private method implementations ===
+// ======================================
+
+/// @brief Clear the background (Display must be locked!)
+void EXPLOSION::do_clear()
+{
+ if (hasCleared && hasSlid)
+ return;
+
+ // Use a calculated radius for pre-mature clearing
+ int32_t rad = (radius * curFrame) / EXPLODEFRAMES;
+
+ // If the radius is below 3, the early clearing would jeopardize
+ // debris throwing, so opt out if the radius is much larger
+ if ( (rad < 3) && (radius > 5) )
+ return;
+
+ // Do not clear/slide more than the real radius
+ if (rad > radius)
+ rad = radius;
+
+ // Raise rad so no flood of "(rad + 1)" calculations is needed.
+ int32_t area_rad = rad + 1;
+
+ if (!hasCleared) {
+
+ // Now clear according to weapon type
+ if ( (weapType >= SHAPED_CHARGE) && (weapType <= CUTTER) ) {
+ int32_t yrad = (rad - 1) / 20;
+ ellipsefill (global.terrain, x, y, rad, yrad, PINK);
+ global.addLandSlide(x - area_rad, x + area_rad, true);
+ addUpdateArea (x - area_rad, y - (yrad + 1),
+ area_rad * 2, (yrad + 1) * 2);
+ } else if (weapType == DRILLER) {
+ int32_t xrad = rad / 20;
+ ellipsefill (global.terrain, x, y, xrad, rad, PINK);
+ global.addLandSlide(x - xrad - 1, x + xrad + 1, true);
+ addUpdateArea (x - (xrad + 1), y - area_rad,
+ (xrad + 1) * 2, area_rad * 2);
+ } else if (( (weapType <= LAST_EXPLOSIVE)
+ || (weapType >= WEAPONS)
+ || (weapType == PERCENT_BOMB)
+ || ( (weapType >= SML_LAZER) && (weapType <= LRG_LAZER) )
+ || ( (weapType >= SML_LIGHTNING) && (weapType <= LRG_LIGHTNING) ) )
+ && (NAPALM_JELLY != weapType) ) {
+ circlefill (global.terrain, x, y, rad, PINK);
+ global.addLandSlide(x - area_rad, x + area_rad, true);
+ addUpdateArea (x - area_rad, y - area_rad,
+ area_rad * 2, area_rad * 2);
+ }
+
+ } // End of clearing
+
+ // If rad did not reach radius (yet), this isn't done
+ if ( (rad >= radius) || peaked) {
+
+ if (!hasSlid) {
+ // Allow the land slide to happen:
+ int32_t area_rad = 1 + ((weapType == DRILLER) ? rad / 20 : rad);
+ global.unlockLandSlide(x - area_rad, x + area_rad);
+ hasSlid = true;
+ }
+
+ if (!hasCleared)
+ hasCleared = true;
+ }
+}
+
+
+/// @brief Throw some debris
+void EXPLOSION::do_throw()
+{
+ // Do never throw when skipping AI play, and
+ // opt out if debris generation is forbidden
+ if (!hasThrown
+ && ( global.skippingComputerPlay
+ || (hasDebris >= maxDebris)
+ || (curFrame > maxFrame ) ) )
+ hasThrown = true;
+
+ // Early out if this is already done or no further deco is allowed
+ if (hasThrown || global.hasTooMuchDeco)
+ return;
+
+ // The delay used for smoke and debris.
+ int32_t delay_dirt = (etime * (maxFrame - curFrame)) - exclock;
+ int32_t delay_smoke = (etime * (EXPLODEFRAMES - curFrame)) - exclock;
+ // Note: DECOR checks against delay>0, thus a negative delay
+ // is no problem here.
+
+ // The radius is not always the radius, so use shortcuts:
+ int32_t rad = (radius * maxFrame) / EXPLODEFRAMES;
+ int32_t xrad = DRILLER == weapType ? rad / 20 : rad;
+ int32_t yrad = ( (SHAPED_CHARGE <= weapType)
+ && (CUTTER >= weapType) ) ? rad / 20 : rad;
+
+ // A minimum radius of 1 is needed for the debris seek to make sense:
+ if (rad < 1)
+ return;
+
+ // Use limited rounds for debris creation to ensure no
+ // endless loops are created if there is no terrain to throw.
+ int32_t deb_round = 0;
+ int32_t max_rounds = 3 * env.debris_level;
+
+ // Initial velocity is modified by damage:
+ double damage_mod = 1. + (static_cast<double>(damage)
+ / static_cast<double>(weapon[DTH_HEAD].damage)
+ / 2. );
+
+ // If this is a meteor, the damage mod is tweaked or the debris more
+ // looks like a collapsing rock than anything thrown.
+ bool isMeteor = ((SML_METEOR <= weapType) && (LRG_METEOR >= weapType));
+ BITMAP* meteor = nullptr;
+ if (isMeteor) {
+ meteor = env.missile[naturals[weapType - WEAPONS].picpoint];
+ damage_mod *= 2. * static_cast<double>(weapType - SML_METEOR + 1);
+ }
+
+ // Now move through the x-axis and create debris and smoke.
+ double xpos = 0;
+ double ypos = 0;
+ double bottom = env.screenHeight;
+ double alpha = 0.;
+ double minX = x - xrad;
+ double maxY = 0.;
+ int32_t round = 1;
+ int32_t deb_rad, diameter, seek_area = hasDebris % 3;
+ double max_x_vel, max_y_vel, mod_x_vel, mod_y_vel;
+
+ do {
+ deb_rad = 1 + (rad > 1 ? (rand() % std::min(5, rad)) : 0);
+ diameter = 2 * deb_rad;
+ max_x_vel = 16. - (static_cast<double>(deb_rad) * 2.0);
+ max_y_vel = -17. + (static_cast<double>(deb_rad) * 1.5);
+
+ // The mods are lower than the max, as the velocity values
+ // can be modified up by damage and position.
+ mod_x_vel = max_x_vel * 0.66;
+ mod_y_vel = max_y_vel * -0.50; // Make positive
+
+ // If this isn't weapon fire, reduce the maximum velocities
+ if (!isWeaponFire) {
+ max_x_vel *= 0.75;
+ max_y_vel *= 0.66;
+ }
+
+
+ // Were the routine looks for dirt is determined by the
+ // current amount of debris found:
+ xpos = ( seek_area
+ ? 1 == seek_area
+ ? minX // left
+ : x // right
+ : x - (xrad / 2) // centre
+ ) + (rand() % xrad);
+
+ /* Circle Coordinates:
+ * X = cos(alpha) * radius (xrad)
+ * Y = sin(alpha) * radius (yrad)
+ * alpha is the angle between x and y.
+ * cos(alpha) is x / xrad
+ * sin(alpha) is y / yrad
+ * So if we have alpha, we can produce Y.
+ */
+ alpha = std::acos((xpos - x) / xrad);
+ // Note: This results in radians, but that is okay,
+ // as sin() needs radians anyway.
+ ypos = y - ROUND(std::sin(alpha) * yrad);
+ maxY = std::min(y + (y - ypos), bottom - deb_rad);
+
+ // find first earth pixel:
+ if ( (ypos < y) && (maxY > ypos)
+ && checkPixelsBetweenTwoPoints(&xpos, &ypos, xpos, maxY)) {
+
+ // Try to get a free debris pool item
+ sDebrisItem* deb_item = global.get_debris_item(deb_rad);
+
+ if (nullptr == deb_item) {
+ // pool is full and at its limit.
+ hasDebris = maxDebris;
+ break;
+ }
+
+ // Try to get another free item if this is a meteor
+ sDebrisItem* met_item = isMeteor
+ ? global.get_debris_item(deb_rad)
+ : nullptr;
+ // Note: It is not a problem if a meteor got no met_item.
+
+ // Move down a bit...
+ ypos += 1 + (rand() % deb_rad);
+ // ... but do not end up below maxY
+ if (ypos > maxY)
+ ypos = maxY;
+
+
+ // Extract the pixels around xpos/ypos
+ int32_t left = xpos - deb_rad;
+ int32_t top = ypos - deb_rad;
+
+ // Blit in terrain
+ blit(global.terrain, deb_item->bmp, left, top,
+ 0, 0, diameter + 1, diameter + 1);
+
+ // Blit in meteor if needed
+ if (isMeteor && meteor && met_item)
+ blit(meteor, met_item->bmp,
+ left % ( meteor->w - diameter),
+ top % (meteor->h - diameter),
+ 0, 0, diameter + 1, diameter + 1);
+
+ // Now the distance from the lowest centre point can be used
+ // to determine the initial velocity of the debris.
+ double dyp = y + radius + diameter;
+ double dxv = ((xpos - x ) / xrad ) * mod_x_vel * damage_mod;
+ double dyv = ((ypos - dyp) / radius) * mod_y_vel * damage_mod;
+
+ assert((dyv < 0.) && "Check it: Dirt shall be thrown down?");
+
+ // Modify x and y velocity a bit randomly for more variance
+ dxv *= 1. + (static_cast<double>((rand() % 9) - 4) / 10.);
+ dyv *= 1. + (static_cast<double>((rand() % 7) - 3) / 10.);
+
+ // Apply impact velocity
+ dxv -= impact_xv / ( (std::abs(dxv) * .75) + 1.5);
+ dyv -= impact_yv / ( (std::abs(dyv) * .75) + 1.5);
+
+ // Maximum x and y velocity depends on the radius of the debris:
+ if (std::abs(dxv) > max_x_vel)
+ dxv = SIGNd(dxv) * max_x_vel;
+ if (dyv < max_y_vel)
+ dyv = max_y_vel;
+
+ // Move the decoration out to the rim of the final explosion:
+ double rimx = x + ROUND(std::cos(alpha) * xrad);
+ double rimy = y - ROUND(std::sin(alpha) * yrad);
+
+ try {
+ new DECOR(rimx, rimy, dxv, dyv,
+ deb_rad, DECOR_DIRT, delay_dirt, deb_item, met_item);
+ // Clear the source to not take these pixels again.
+ circlefill (global.terrain, xpos, ypos, deb_rad, PINK);
+ addUpdateArea(xpos - deb_rad, ypos - deb_rad, diameter, diameter);
+ addUpdateArea(rimx - deb_rad, rimy - deb_rad, diameter, diameter);
+ } catch (...) {
+ // As the decor was not created, the item can be released again
+ global.free_debris_item(deb_item);
+ if (met_item)
+ global.free_debris_item(met_item);
+ }
+
+ // Every throw needs a smoke... ;)
+ try {
+ new DECOR(xpos, ypos, dxv, dyv,
+ deb_rad * 3, DECOR_SMOKE, delay_smoke);
+ } catch (...) { /* nothing.. really... it doesn't matter */ }
+
+ // Advance hasDebris and get new seeking area
+ seek_area = ++hasDebris % 3;
+ } // End of having hit a pixel
+
+ // Count tries to advance rounds (if needed)
+ if (++deb_round >= maxDebris) {
+ deb_round = 0;
+ ++round;
+ }
+ } while ( (round < max_rounds) && (hasDebris < maxDebris) );
+
+ // If the calculated radius did not reach radius (yet), and hasDebris
+ // has not reached maxDebris, yet, the throwing is not finished, yet.
+ if ( (rad >= radius) || (hasDebris >= maxDebris))
+ hasThrown = true;
+
}
-void EXPLOSION::draw (BITMAP *dest)
+
+// =======================
+// === Global helpers: ===
+// =======================
+
+
+/// @brief draw one blob of Napalm (display be locked!)
+void draw_Napalm_Blob(VIRTUAL_OBJECT* blob, int32_t x, int32_t y,
+ int32_t radius, int32_t frame)
{
- if (type >= SHAPED_CHARGE && type <= CUTTER)
- {
- if (a > 1 && a <= EXPLOSIONFRAMES + 1)
- {
- rotate_scaled_sprite (dest, (BITMAP *) _global->gfxData.flameFront[(a + (EXPLOSIONFRAMES * weap->eframes)) - 2],
- (int)x - radius, (int)y - (radius / 20), itofix (0), ftofix ((double) radius / 300));
- setUpdateArea ((int)x - (radius + 1), (int)y - (radius / 20 + 1), (radius + 1) * 2, (radius / 20 + 1) * 2);
- }
- }
- else if (type == DRILLER)
- {
- if (a > 1 && a <= EXPLOSIONFRAMES + 1)
- {
- rotate_scaled_sprite(dest, (BITMAP *) _global->gfxData.flameFront[(a + (EXPLOSIONFRAMES * weap->eframes)) - 2], (int) x - (radius), (int) y - (radius / 20), itofix(192), ftofix(radius / 300));
- // rotate_scaled_sprite (dest, (BITMAP *) _global->gfxData.flameFront[(a + (EXPLOSIONFRAMES * weap->eframes)) - 2], (int)x - radius, (int)y - (radius / 20), itofix (0), ftofix ((double) radius / 300));
-
- setUpdateArea( (int) x - (radius + 1), (int) y - (radius + 1), (radius + 1) * 2, (radius + 1) * 2);
- }
- }
- else if (type == NAPALM_JELLY)
- {
- int blobSize = (int)(radius - ((double)a / EXPLOSIONFRAMES) * radius + 1);
- if (blobSize < 2) blobSize = 2; // avoid circle size crash
- circlefill (_env->db, (int)x, (int)y, blobSize - 2, makecol (255, 0, 0));
- circle(_env->db, (int)x, (int)y, blobSize - 1, makecol(255, 150, 0));
- circle(_env->db, (int)x, (int)y, blobSize, makecol(255, 150, 0));
- setUpdateArea ((int)x - (radius + 1), (int)y - (radius + 1), (radius + 1) * 2, (radius + 1) * 2);
- }
- else if (type <= LAST_EXPLOSIVE || type >= WEAPONS || type == PERCENT_BOMB)
- {
- if (a > 1 && a <= EXPLOSIONFRAMES + 1)
- {
- /* - background needs to be cleard immediately to allow chain missiles to work
- (and horizontal spreads look *far* better, too! ;) )
- Note: Shaped charges are always shot alone, and they live off their visual effect.
- Adding the immediate destruction on them, too, would cut off some of the effect... */
- if (a <= EXPLODEFRAMES)
- circlefill (_env->terrain, (int)x, (int)y, (int)((radius / EXPLODEFRAMES) * a), PINK);
- rotate_scaled_sprite(dest, (BITMAP *) _global->gfxData.explosions[(a + (EXPLOSIONFRAMES * weap->eframes)) - 2],
- (int)x - radius, (int)y - radius, itofix (0), ftofix ((double) radius / 107));
- setUpdateArea ((int)x - (radius + 1), (int)y - (radius + 1), (radius + 1) * 2, (radius + 1) * 2);
- }
- }
- else if ( (type <= TECTONIC) && (type >= TREMOR) )
- {
- if (a > 1 && a <= EXPLODEFRAMES + 1)
- {
- drawFracture (_global, _env, _env->terrain, &_current, (int)x, (int)y, angle, (int)(((double)a / EXPLODEFRAMES) * (radius / 4)), radius, 5, 0);
- }
- }
- else if ((type >= RIOT_BOMB) && (type <= HVY_RIOT_BOMB))
- {
- if (a > 1 && a <= EXPLODEFRAMES + 1)
- {
- int startCirc = (radius / EXPLODEFRAMES) * a;
- int colour = (player) ? player->color : makecol(0, 0, 255);
- circlefill (_env->terrain, (int)x, (int)y, startCirc, PINK);
- circle (dest, (int)x, (int)y, startCirc, colour);
- setUpdateArea ((int)x - (radius + 1), (int)y - (radius + 1), (radius + 1) * 2, (radius + 1) * 2);
- }
- }
- else if ((type >= RIOT_CHARGE) && (type <= RIOT_BLAST))
- {
- if (a > 1 && a <= EXPLODEFRAMES + 1)
- {
- double sx = x - _global->slope[angle][0] * 15;
- double sy = y - _global->slope[angle][1] * 15;
- int startCirc = (radius / EXPLODEFRAMES) * a;
- triangle (dest, (int)sx, (int)sy, (int)(sx + _global->slope[(angle + 45) % 360][0] * startCirc), (int)(sy + _global->slope[(angle + 45) % 360][1] * startCirc),(int)(sx + _global->slope[(angle + 315) % 360][0] * startCirc),(int)(sy + _global->slope[(angle + 315) % 360][1] * startCirc), player->color);
- setUpdateArea ((int)sx - (startCirc + 1), (int)sy - (startCirc + 1), (startCirc + 1) * 2, (startCirc + 1) * 2);
- }
- }
- else
- {
- if (a > 1 && a <= EXPLODEFRAMES + 1)
- {
- int startCirc = (radius / EXPLODEFRAMES) * a;
- circlefill (dest, (int)x, (int)y, startCirc, (player)?player->color: makecol (0, 255, 0) );
- startCirc += (radius / EXPLODEFRAMES) * 2;
- setUpdateArea ((int)x - startCirc, (int)y - startCirc, startCirc * 2, startCirc * 2);
- }
- }
+ if (nullptr == blob)
+ return;
+
+ int32_t phase = std::abs(frame) % 4;
+ int32_t lo_mod = phase % 2;
+ int32_t hi_mod = (phase + 1) % 2;
+
+ circlefill (global.canvas, x, y, radius, RED);
+
+ for (int32_t i = 1; i < radius; i += 3) {
+ int32_t r = 255 - (20 * phase);
+ int32_t g = 25 * phase;
+ int32_t b = 10 * phase;
+ circle(global.canvas, x, y, i + lo_mod, makecol(r, g, 10 + b));
+ circle(global.canvas, x, y, i + hi_mod, makecol(r, 25 + g, b));
+ }
+
+ blob->setUpdateArea (x - radius - 1, y - radius - 1, (radius + 1) * 2, (radius + 1) * 2);
+ blob->requireUpdate ();
}
-int EXPLOSION::isSubClass (int classNum)
+
+/** @brief return the damage of a weapon against a tank
+ * @param[in] tank Pointer to the tank to check
+ * @param[in] type The weapon used
+ * @param[in] hit_x X coordinate of the impact
+ * @param[in] hit_y Y coordinate of the impact
+ * @return The part damage of the weapon without player modification
+**/
+double get_hit_damage(TANK* tank, weaponType type, int32_t hit_x, int32_t hit_y)
{
- if (classNum == EXPLOSION_CLASS)
- return (TRUE);
- else
- return (FALSE);
- //return (PHYSICAL_OBJECT::isSubClass (classNum));
+ if ( (nullptr == tank) || (tank->destroy) )
+ return 0.;
+
+ double weap_rad = weapon[type].radius;
+ double xrad = DRILLER == type ? weap_rad / 20. : weap_rad;
+ double yrad = ( (SHAPED_CHARGE <= type)
+ && (CUTTER >= type) ) ? weap_rad / 20. : weap_rad;
+ double in_rate_x = 0.;
+ double in_rate_y = 0.;
+ double dmg = 0.;
+
+ if (tank->isInEllipse(hit_x, hit_y, xrad, yrad, in_rate_x, in_rate_y) ) {
+ if (PERCENT_BOMB == type)
+ dmg = ((tank->l + tank->sh) / 2) + 1;
+ else if ( (REDUCER == type)
+ && (tank->player->damageMultiplier > 0.1) )
+ dmg = 1.; // So result > 0 can be checked
+
+ // Shaped charges and drillers have a minimum distance under which they
+ // deal no damage:
+ else if ( ( ( (SHAPED_CHARGE > type) || (CUTTER < type) ) // ( Not shaped
+ || (std::abs(tank->x - hit_x) > yrad) ) // or x distance okay )
+ && ( (DRILLER != type) // and (not driller
+ || (std::abs(tank->y - hit_y) > xrad) ) ) { // or y distance okay )
+ /* Note: The radii are reversed as the opposite radius is the
+ * minimum distance needed for the main blast radius.
+ * Note: The above is built from the following formula:
+ * Be a = Weapon is a shaped charge, wide boy or cutter,
+ * b = x distance is greater than the weapons x range minimum,
+ * c = Weapon is the driller,
+ * d = y distance is greater than the weapons y range minimum.
+ * Then the following term must be true to be allowed to enter this block:
+ * (~a v b) ^ (~c v d) which reads like:
+ * (Not a shaped weapon OR the x distance is in order)
+ * AND (Not the driller OR the y distance is in order)
+ * The above if block actually is exactly that from bottom to top.
+ */
+
+ dmg = weapon[type].damage;
+
+ // Some weapons have minimum rates on axis ratings
+ if (DRILLER == type) {
+ // The driller has its force focused vertically:
+ if (in_rate_y < 0.95)
+ in_rate_y = 0.95;
+ } else if ( (SHAPED_CHARGE <= type)
+ && (CUTTER >= type) ) {
+ // The shaped ones have their force on the horizontal axis
+ if (in_rate_x < 0.95)
+ in_rate_x = 0.95;
+ } else if ( (TREMOR <= type)
+ && (TECTONIC >= type) ) {
+ if (in_rate_x < 0.25)
+ in_rate_x = 0.25;
+ if (in_rate_y < 0.25)
+ in_rate_y = 0.25;
+ }
+
+ // The full in_rate must not be lower than 10% on any weapon.
+ double in_rate = in_rate_x * in_rate_y;
+ if (in_rate < 0.1)
+ in_rate = 0.1;
+ dmg *= in_rate;
+ } // End of having damage to deal
+ } // End of tank in ellipse
+
+ return dmg;
}
diff --git a/src/explosion.h b/src/explosion.h
index f7c9b19..2bcf4d4 100644
--- a/src/explosion.h
+++ b/src/explosion.h
@@ -24,45 +24,85 @@
#include "main.h"
#include "physobj.h"
-#define DIRT_CEILING 50
+class EXPLOSION: public PHYSICAL_OBJECT
+{
+public:
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
-class EXPLOSION: public PHYSICAL_OBJECT
- {
- public:
- int etime;
- int damage;
- int eframes;
- int radius;
- int sound;
- int exclock;
- int type, a;
- int peaked;
- int dispersing;
- WEAPON *weap;
- /* to allow synchronous tank explosions, explosions need to know what they are */
- bool bIsWeaponExplosion;
-
- virtual ~EXPLOSION ();
- EXPLOSION (GLOBALDATA *global, ENVIRONMENT *env, double xpos, double ypos, int weaponType);
- EXPLOSION (GLOBALDATA *global, ENVIRONMENT *env, double xpos, double ypos, double xvel, double yvel, int weaponType);
- void draw (BITMAP *dest);
- void initialise ();
- int applyPhysics ();
- void explode ();
- int isSubClass (int classNum);
- inline virtual int getClass ()
- {
- return (EXPLOSION_CLASS);
- }
- inline virtual void setEnvironment(ENVIRONMENT *env)
- {
- if (!_env || (_env != env))
- {
- _env = env;
- _index = _env->addObject (this);
- }
- }
- };
+ // default ctor for all non-BEAM explosions
+ explicit
+ EXPLOSION (PLAYER* player_, double x_, double y_, double xv_, double yv_,
+ int32_t type, bool is_weapon);
+ // Special ctor for BEAM:
+ EXPLOSION (PLAYER* player_, double x_, double y_, double xv_, double yv_,
+ int32_t type, double damage_, bool is_weapon);
+ ~EXPLOSION ();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ void applyPhysics();
+ void draw ();
+ void explode ();
+
+ eClasses getClass() { return CLASS_EXPLOSION; }
+
+
+private:
+
+ /* -----------------------
+ * --- Private methods ---
+ * -----------------------
+ */
+
+ void do_clear();
+ void do_throw();
+ void drawFracture (int32_t x, int32_t y, int32_t frac_angle, int32_t width,
+ int32_t segmentLength, int32_t maxRecurse,
+ int32_t recurseDepth);
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ bool apply_damage = true;
+ int32_t curFrame = 1;
+ float centre_x = EXPLO_CX;
+ float centre_y = EXPLO_CY;
+ int32_t damage = 0;
+ int32_t etime = 0;
+ int32_t exclock = 0;
+ float explo_h = EXPLO_H;
+ float explo_w = EXPLO_W;
+ float flame_w = 0.f;
+ float flame_h = 0.f;
+ bool hasCleared = false;
+ int32_t hasDebris = 0;
+ bool hasSlid = false;
+ bool hasThrown = false;
+ double impact_xv = 0.;
+ double impact_yv = 0.;
+ int32_t maxDebris = 0;
+ int32_t maxFrame = 0;
+ bool peaked = false;
+ int32_t radius = 10;
+ float scale = 0.;
+};
+
+
+// Global helpers:
+void draw_Napalm_Blob(VIRTUAL_OBJECT* blob, int32_t x, int32_t y,
+ int32_t radius, int32_t frame);
+double get_hit_damage (TANK* tank, weaponType type, int32_t hit_x,
+ int32_t hit_y);
#endif
diff --git a/src/extern/dirent.c b/src/extern/dirent.c
new file mode 100755
index 0000000..3f8d8ee
--- /dev/null
+++ b/src/extern/dirent.c
@@ -0,0 +1,152 @@
+/*
+
+ Implementation of POSIX directory browsing functions and types for Win32.
+
+ Author: Kevlin Henney (kevlin at acm.org, kevlin at curbralan.com)
+ History: Created March 1997. Updated June 2003 and July 2012.
+ Rights: See end of file.
+
+*/
+
+#include "dirent.h"
+#include <errno.h>
+#include <io.h> /* _findfirst and _findnext set errno iff they return -1 */
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef ptrdiff_t handle_type; /* C99's intptr_t not sufficiently portable */
+
+struct DIR
+{
+ handle_type handle; /* -1 for failed rewind */
+ struct _finddata_t info;
+ struct dirent result; /* d_name null iff first time */
+ char *name; /* null-terminated char string */
+};
+
+DIR *opendir(const char *name)
+{
+ DIR *dir = 0;
+
+ if(name && name[0])
+ {
+ size_t base_length = strlen(name);
+ size_t full_length = base_length;
+ const char *all = /* search pattern must end with suitable wildcard */
+ strchr("/\\", name[base_length - 1]) ? "*" : "/*";
+
+ full_length += strlen(all) + 1;
+
+ if((dir = (DIR *) malloc(sizeof *dir)) != 0 &&
+ ( dir->name = (char *)malloc(full_length) ) != 0 )
+ {
+ strcpy_s(dir->name, full_length, name);
+ strcat_s(dir->name, full_length, all);
+
+ if((dir->handle =
+ (handle_type) _findfirst(dir->name, &dir->info)) != -1)
+ {
+ dir->result.d_name = 0;
+ }
+ else /* rollback */
+ {
+ free(dir->name);
+ free(dir);
+ dir = 0;
+ }
+ }
+ else /* rollback */
+ {
+ free(dir);
+ dir = 0;
+ errno = ENOMEM;
+ }
+ }
+ else
+ {
+ errno = EINVAL;
+ }
+
+ return dir;
+}
+
+int closedir(DIR *dir)
+{
+ int result = -1;
+
+ if(dir)
+ {
+ if(dir->handle != -1)
+ {
+ result = _findclose(dir->handle);
+ }
+
+ free(dir->name);
+ free(dir);
+ }
+
+ if(result == -1) /* map all errors to EBADF */
+ {
+ errno = EBADF;
+ }
+
+ return result;
+}
+
+struct dirent *readdir(DIR *dir)
+{
+ struct dirent *result = 0;
+
+ if(dir && dir->handle != -1)
+ {
+ if(!dir->result.d_name || _findnext(dir->handle, &dir->info) != -1)
+ {
+ result = &dir->result;
+ result->d_name = dir->info.name;
+ }
+ }
+ else
+ {
+ errno = EBADF;
+ }
+
+ return result;
+}
+
+void rewinddir(DIR *dir)
+{
+ if(dir && dir->handle != -1)
+ {
+ _findclose(dir->handle);
+ dir->handle = (handle_type) _findfirst(dir->name, &dir->info);
+ dir->result.d_name = 0;
+ }
+ else
+ {
+ errno = EBADF;
+ }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+/*
+
+ Copyright Kevlin Henney, 1997, 2003, 2012. All rights reserved.
+
+ Permission to use, copy, modify, and distribute this software and its
+ documentation for any purpose is hereby granted without fee, provided
+ that this copyright and permissions notice appear in all copies and
+ derivatives.
+
+ This software is supplied "as is" without express or implied warranty.
+
+ But that said, if there are any problems please get in touch.
+
+*/
diff --git a/src/extern/dirent.h b/src/extern/dirent.h
new file mode 100755
index 0000000..a02a0d8
--- /dev/null
+++ b/src/extern/dirent.h
@@ -0,0 +1,50 @@
+#ifndef DIRENT_INCLUDED
+#define DIRENT_INCLUDED
+
+/*
+
+ Declaration of POSIX directory browsing functions and types for Win32.
+
+ Author: Kevlin Henney (kevlin at acm.org, kevlin at curbralan.com)
+ History: Created March 1997. Updated June 2003.
+ Rights: See end of file.
+
+*/
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef struct DIR DIR;
+
+struct dirent
+{
+ char *d_name;
+};
+
+DIR *opendir(const char *);
+int closedir(DIR *);
+struct dirent *readdir(DIR *);
+void rewinddir(DIR *);
+
+/*
+
+ Copyright Kevlin Henney, 1997, 2003. All rights reserved.
+
+ Permission to use, copy, modify, and distribute this software and its
+ documentation for any purpose is hereby granted without fee, provided
+ that this copyright and permissions notice appear in all copies and
+ derivatives.
+
+ This software is supplied "as is" without express or implied warranty.
+
+ But that said, if there are any problems please get in touch.
+
+*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/externs.h b/src/externs.h
index 2a6a692..cbec34e 100644
--- a/src/externs.h
+++ b/src/externs.h
@@ -1,5 +1,6 @@
-#ifndef EXTERNS_DEFINE
-#define EXTERNS_DEFINE
+#pragma once
+#ifndef ATANKS_SRC_EXTERNS_H_INCLUDED
+#define ATANKS_SRC_EXTERNS_H_INCLUDED
/*
* atanks - obliterate each other with oversize weapons
@@ -20,40 +21,50 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* */
+#include "globaldata.h"
+#ifndef HAS_GLOBALDATA
+class GLOBALDATA;
+#endif // HAS_GLOBALDATA
-extern char *errorMessage;
-extern int errorX, errorY;
+#include "environment.h"
+#ifndef HAS_ENVIRONMENT
+class ENVIRONMENT;
+#endif // HAS_ENVIRONMENT
-extern int WHITE, BLACK, PINK, COLOR[];
+#define CLOCK_MAX 10
-extern int k;
-extern int ctrlUsedUp;
+#ifndef ATANKS_SRC_ATANKS_CPP
-extern int cclock,
- fps, frames,
- fi, lx, ly,
- order[];
+// === The two most important things in the game: ;) ===
+extern GLOBALDATA global;
+extern ENVIRONMENT env;
-//extern char cacheCirclesBG;
-//extern char ditherGradients;
-//extern int startmoney;
-//extern int turntype;
-extern double height[];
-//extern int landtable[];
+// === Defined colours used everywhere ===
+extern int32_t BLACK, BLUE, DARK_GREEN, DARK_GREY, DARK_RED, GREY, GREEN,
+ LIGHT_GREEN, LIME_GREEN, ORANGE, PINK, PURPLE, RED, SILVER,
+ TURQUOISE, WHITE, YELLOW;
-//extern int steep, mheight, mbase;
-//extern double msteep, smooth;
-//extern double gravity;
-//extern double windstrength, windvariation;
-//extern double interest;
-//extern char name[][11];
-extern int winner;
+// === General values that are globally used ===
+extern char buf[100];
+extern const char* errorMessage;
+extern int32_t errorX, errorY;
+extern int32_t k, K;
+extern int32_t fi, lx, ly;
+extern WEAPON weapon[WEAPONS]; // from files.cpp
+extern WEAPON naturals[NATURALS]; // from files.cpp
+extern ITEM item[ITEMS]; // from files.cpp
-extern WEAPON weapon[];
-extern WEAPON naturals[];
-extern ITEM item[];
-#endif
+// === Gradients ===
+extern gradient topbar_gradient[4];
+extern gradient stuff_bar_gradient[11];
+extern gradient circles_gradient[4];
+extern gradient explosion_gradient1[3];
+extern gradient explosion_gradient2[3];
+extern gradient* explosion_gradients[2];
+
+#endif // ATANKS_SRC_ATANKS_CPP
+#endif // ATANKS_SRC_EXTERNS_H_INCLUDED
diff --git a/src/fade.cpp b/src/fade.cpp
deleted file mode 100644
index eb6da5a..0000000
--- a/src/fade.cpp
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * atanks - obliterate each other with oversize weapons
- * Copyright (C) 2003 Thomas Hudson
- *
- * 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.
- * */
-
-/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
-fade.cc
-
-Provides graphical transitions via change and quickChange, as prototyped in
-main.h
-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/
-
-/*
-TODO
- + Add more faders
- + Improve/complete documentation
-*/
-
-
-
-#include "globaldata.h"
-#include "main.h"
-
-
-
-/*****************************************************************************
-Internal fades
-
-The following section of code defines the internal fade routines. These
-routines are used by the change function to perform transitions. To add a
-new fader, simply implement a function as seen below and then add it to
-change's function pointer array.
-*****************************************************************************/
-/*
-static void spiralPixelFade (GLOBALDATA *global, BITMAP *target)
-{
- int blockSize = 8;
- int spiralSize = 20;
- int length = spiralSize, x = 0, y = 0;
- int stepSize = (spiralSize + 1) * blockSize;
- int x2, y2;
- int count;
-
- for (count = 0, y = stepSize - blockSize; count < length; count++, y -= blockSize)
- for (x2 = 0; x2 <= global->screenWidth; x2 += stepSize)
- for (y2 = 0; y2 <= global->screenHeight; y2 += stepSize)
- blit (target, screen, x2+x, y2+y, x2+x, y2+y, blockSize, blockSize);
- while (length)
- {
- for (count = 0; count < length; count++, x += blockSize)
- for (x2 = 0; x2 <= global->screenWidth; x2 += stepSize)
- for (y2 = 0; y2 <= global->screenHeight; y2 += stepSize)
- blit (target, screen, x2+x, y2+y, x2+x, y2+y, blockSize, blockSize);
- for (count = 0; count < length; count++, y += blockSize)
- for (x2 = 0; x2 <= global->screenWidth; x2 += stepSize)
- for (y2 = 0; y2 <= global->screenHeight; y2 += stepSize)
- blit (target, screen, x2+x, y2+y, x2+x, y2+y, blockSize, blockSize);
- length--;
- for (count = 0; count < length; count++, x -= blockSize)
- for (x2 = 0; x2 <= global->screenWidth; x2 += stepSize)
- for (y2 = 0; y2 <= global->screenHeight; y2 += stepSize)
- blit (target, screen, x2+x, y2+y, x2+x, y2+y, blockSize, blockSize);
- for (count = 0; count < length; count++, y -= blockSize)
- for (x2 = 0; x2 <= global->screenWidth; x2 += stepSize)
- for (y2 = 0; y2 <= global->screenHeight; y2 += stepSize)
- blit (target, screen, x2+x, y2+y, x2+x, y2+y, blockSize, blockSize);
- length--;
- }
- for (x2 = 0; x2 <= global->screenWidth; x2 += stepSize)
- for (y2 = 0; y2 <= global->screenHeight; y2 += stepSize)
- blit (target, screen, x2+x, y2+y, x2+x, y2+y, blockSize, blockSize);
-}
-
-static void topDownMultiWipeFade (GLOBALDATA *global, BITMAP *target)
-{
- int z, zz ;
-
- for (zz = 0; zz < 8; zz++)
- {
- int lineCount = 0;
- for (z = zz; z < global->screenHeight; z += 8)
- {
- blit (target, screen, 0, z, 0, z, global->screenWidth, 1);
- if (lineCount == 10)
- {
- lineCount = 0;
- rest (1);
- }
- lineCount++;
- }
- }
-}
-
-static void topDownWipeFade (GLOBALDATA *global, BITMAP *target)
-{
- int z;
-
- for (z = 0; z <= global->screenHeight / 8; z++)
- {
- blit (target, screen, 0, z * 8, 0, z * 8, global->screenWidth, 8);
- rest (1);
- }
-}
-
-static void bottomUpWipeFade (GLOBALDATA *global, BITMAP *target)
-{
- int z;
-
- for (z = global->screenHeight / 8; z >= 0; z--)
- {
- blit (target, screen, 0, z * 8, 0, z * 8, global->screenWidth, 8);
- rest (1);
- }
-}
-
-static void rightLeftWipeFade (GLOBALDATA *global, BITMAP *target)
-{
- int z;
-
- for (z = global->screenWidth / 8; z >= 0; z--)
- {
- blit (target, screen, z * 8, 0, z * 8, 0, 8, global->screenHeight);
- rest (1);
- }
-}
-
-static void leftRightWipeFade (GLOBALDATA *global, BITMAP *target)
-{
- int z;
-
- for (z = 0; z <= global->screenWidth / 8; z++)
- {
- blit (target, screen, z * 8, 0, z * 8, 0, 8, global->screenHeight);
- rest (1);
- }
-}
-
-static void crossLinesFade (GLOBALDATA *global, BITMAP *target)
-{
- int z;
-
- for (z = 0; z < (global->screenWidth/4+4) / 4; z++)
- {
- blit (target, screen, z * 8, 0, z * 8, 0, 4, global->screenHeight);
- blit (target, screen, (global->screenWidth-1) - (z * 8), 0, (global->screenWidth-1) - (z * 8), 0, 4, global->screenHeight);
- blit (target, screen, 0, (z * 6), 0, (z * 6), global->screenWidth, 4);
- blit (target, screen, 0, (global->screenHeight - (z * 6)), 0, (global->screenHeight - (z * 6)), global->screenWidth, 4);
- rest (1);
- }
- for (z = (global->screenWidth/4+4) / 4; z != 0; z--)
- {
- blit (target, screen, z * 8 + 4, 0, z * 8 + 4, 0, 4, global->screenHeight);
- blit (target, screen, (global->screenWidth-1) - (z * 8) + 4, 0, (global->screenWidth-1) - (z * 8) + 4, 0, 4, global->screenHeight);
- blit (target, screen, 0, (z * 6) + 4, 0, (z * 6) + 4, global->screenWidth, 4);
- blit (target, screen, 0, (global->screenHeight - (z * 6)) + 4, 0, (global->screenHeight - (z * 6)) + 4, global->screenWidth, 4);
- rest (1);
- }
-}
-
-*/
-
-/*****************************************************************************
-changeHandleError
-
-Used internally by change and quickChange. Prints out error messages.
-*****************************************************************************/
-static void changeHandleError (GLOBALDATA *global, BITMAP *target)
-{
- if (! global->os_mouse) show_mouse (NULL);
- if (errorMessage)
- {
- textout_ex (target, font, errorMessage, errorX, errorY, makecol (255, 0, 0), -1);
- errorMessage = NULL;
- }
-}
-
-
-/*****************************************************************************
-change
-
-Selects and executes a random transition.
-*****************************************************************************/
-/*
-void change(GLOBALDATA *global, BITMAP *target)
-{
- static void (* const procs[])(GLOBALDATA*,BITMAP*) =
- {
- crossLinesFade, spiralPixelFade,
- leftRightWipeFade, rightLeftWipeFade,
- bottomUpWipeFade, topDownWipeFade,
- topDownMultiWipeFade,
- } ;
- static const int cprocs = sizeof( procs ) / sizeof( procs[0] );
-
- if (! global->os_mouse) show_mouse (NULL);
- changeHandleError (global, target);
-
- (procs[rand() % cprocs]) (global, target);
-
- clear_keybuf ();
-}
-*/
-
-void change(GLOBALDATA *global, BITMAP *target)
-{
- quickChange(global, target);
-}
-
-/*****************************************************************************
-quickChange
-
-Executes a fast and simple transition.
-
-*****************************************************************************/
-void quickChange (GLOBALDATA *global, BITMAP *target)
-{
- changeHandleError (global, target);
- blit (target, screen, 0, 0, 0, 0, global->screenWidth, global->screenHeight);
- rest (1);
-}
-
diff --git a/src/files.cpp b/src/files.cpp
index cca9dc4..bb3842f 100644
--- a/src/files.cpp
+++ b/src/files.cpp
@@ -1,91 +1,103 @@
+#include "main.h"
+
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <dirent.h>
+#include <cassert>
// basically all UNIX-like OSes should use stat
#ifndef WIN32
-#include <sys/stat.h>
+# include <sys/stat.h>
#endif
#include "player.h"
#include "files.h"
-#include "main.h"
#include "text.h"
-/*
-This function saves the game in progress.
-All data is saved in a text file for flexiblity.
-Returns TRUE on success and FALSE on failure.
-*/
-int Save_Game(GLOBALDATA *global, ENVIRONMENT *env)
+
+// They are filled here, declaring them here prevents the linker
+// from 'optimizing' them away.
+WEAPON weapon[WEAPONS];
+WEAPON naturals[NATURALS];
+ITEM item[ITEMS];
+
+
+
+/* Update: Instead of using constantly allocated/deallocated
+ * char strings for file path generation, one string buffer
+ * used everywhere that does not need *alloc/free is faster
+ * and a lot more secure.
+ * - Sven
+ */
+char path_buf[PATH_MAX + 1];
+// Note: Any consumer of this buffer has to include files.h or
+// to define an extern for it.
+
+
+/** @brief Save the current game in progress
+ * This function saves the game in progress.
+ * All data is saved in a text file for flexibility.
+ * @return true on success and false on failure.
+**/
+bool Save_Game()
{
- FILE *game_file = NULL;
- char *file_path = NULL;
- int player_count = 0;
- PLAYER *my_player = NULL;
- int count;
- int global_player_number;
-
- // figure out file name
- file_path = (char *) calloc( strlen(global->configDir) +
- strlen(global->game_name) + 24,
- sizeof(char) );
- if (! file_path)
- return FALSE;
-
- sprintf(file_path, "%s/%s.sav", global->configDir, global->game_name);
-
- game_file = fopen(file_path, "w");
- free(file_path);
- if (!game_file)
- return FALSE;
-
- // write global data
- fprintf(game_file, "GLOBAL\n");
- fprintf(game_file, "ROUNDS=%f\n", global->rounds);
- fprintf(game_file, "CURRENTROUND=%d\n", global->currentround);
- fprintf(game_file, "CAMPAIGNMODE=%f\n", global->campaign_mode);
- fprintf(game_file, "***\n");
-
- // write envrionment data
- fprintf(game_file, "ENVIRONMENT\n");
- fprintf(game_file, "***\n");
-
- // write player data
- fprintf(game_file, "PLAYERS\n");
- while ( player_count < global->numPlayers )
- {
- my_player = global->players[player_count];
- global_player_number = 0;
- while ( strcmp(global->allPlayers[global_player_number]->getName(), my_player->getName() ) )
- global_player_number++;
- fprintf(game_file, "PLAYERNUMBER=%d\n", global_player_number);
- fprintf(game_file, "SCORE=%d\n", my_player->score);
- fprintf(game_file, "MONEY=%d\n", my_player->money);
- fprintf(game_file, "TYPE=%f\n", my_player->type);
- fprintf(game_file, "TYPESAVED=%f\n", my_player->type_saved);
- for (count = 0; count < WEAPONS; count++)
- fprintf(game_file, "WEAPON=%d %d\n", count, my_player->nm[count]);
- for (count = 0; count < ITEMS; count++)
- fprintf(game_file, "ITEM=%d %d\n", count, my_player->ni[count]);
- fprintf(game_file, "***\n");
- player_count++;
- }
-
- fclose(game_file);
-
- // save the current config
- file_path = (char *) calloc( strlen(global->configDir) +
- strlen(global->game_name) + 24,
- sizeof(char) );
- if (! file_path)
- return FALSE;
- sprintf(file_path, "%s/%s.txt", global->configDir, global->game_name);
- Save_Game_Settings_Text(global, env, file_path);
- free(file_path);
-
- return TRUE;
+ snprintf(path_buf, PATH_MAX, "%s/%s.sav", env.configDir, env.game_name);
+
+ FILE* game_file = fopen(path_buf, "w");
+ if (!game_file)
+ return false;
+
+ // write global data
+ fprintf(game_file, "GLOBAL\n");
+ fprintf(game_file, "CURRENTROUND=%d\n", global.currentround + 1);
+ // Note: When the game is saved, the round already has been decreased. Thus,
+ // to not loose rounds when saving a game, returning to main and load it
+ // again, this has to be increased here.
+ fprintf(game_file, "SCOREBOARD=%d\n", global.showScoreBoard ? 1 : 0);
+ fprintf(game_file, "***\n");
+
+ // write environment data
+ fprintf(game_file, "ENVIRONMENT\n");
+ fprintf(game_file, "CAMPAIGNMODE=%d\n", env.campaign_mode ? 1 : 0);
+ fprintf(game_file, "CAMPAIGNROUNDS=%lf\n", env.campaign_rounds);
+ fprintf(game_file, "NEXTCAMPROUND=%lf\n", env.nextCampaignRound);
+ fprintf(game_file, "ROUNDS=%u\n", env.rounds);
+ fprintf(game_file, "***\n");
+
+ // write player data
+ fprintf(game_file, "PLAYERS\n");
+ for (int32_t i = 0; i < env.numGamePlayers; ++i) {
+ PLAYER* my_player = env.players[i];
+
+ if (my_player->index > -1) {
+ // Note: This line is needed to know which player to load
+ fprintf(game_file, "PLAYERNUMBER=%d\n", my_player->index);
+ my_player->save_game_data(game_file);
+ }
+ }
+
+ fprintf(game_file, "***EOF***\n");
+
+ fclose(game_file);
+
+ /* atanks always saved the current configuration in a file
+ * alongside the save game, but this environment file was
+ * never read. It makes no sense anyway, as it would change
+ * the current settings without re-loading the main config
+ * file.
+ * However, I find this very good for testing reasons, and
+ * if a game becomes boring, why not allow the player to
+ * enable some weather effects before loading a game?
+ * So it won't be added that the environment file is loaded,
+ * but if an old one is laying around, we should delete our
+ * own garbage:
+ */
+ snprintf(path_buf, PATH_MAX, "%s/%s.txt",
+ env.configDir, env.game_name);
+ if (!access(path_buf, F_OK) && !access(path_buf, W_OK))
+ unlink(path_buf);
+
+ return true;
}
@@ -94,139 +106,183 @@ int Save_Game(GLOBALDATA *global, ENVIRONMENT *env)
This function attempts to load a saved
game.
The function returns TRUE on success and
-FALSE is an error occures.
+FALSE if an error occurs.
-- Jesse
*/
-int Load_Game(GLOBALDATA *global, ENVIRONMENT * /*env*/)
+bool Load_Game()
{
- char line[512];
- char *field, *value;
- int index, amount, player_count = 0;
- FILE *my_file;
- char *file_path;
- int stage = NO_STAGE;
- char *got_line;
- int global_player_number;
-
- // load config settings
- /*
- file_path = (char *) calloc( strlen(homedir) +
- strlen(global->game_name) + 24,
- sizeof(char) );
- if (! file_path)
- return FALSE;
- sprintf(file_path, "%s/.atanks/%s.txt", homedir, global->game_name);
- my_file = fopen(file_path, "r");
- free(file_path);
- if (! my_file)
- return FALSE;
- global->loadFromFile_Text(my_file);
- env->loadFromFile_Text(my_file);
- fclose(my_file);
- */
-
- file_path = (char *) calloc( strlen(global->configDir) +
- strlen(global->game_name) + 24,
- sizeof(char) );
- if (! file_path)
- return FALSE;
-
- sprintf(file_path, "%s/%s.sav", global->configDir, global->game_name);
-
- my_file = fopen(file_path, "r");
- free(file_path);
- if (! my_file)
- return FALSE;
-
- // read a line from the file
- got_line = fgets(line, 512, my_file);
- // keep reading until we hit EOF or error
- while ( (got_line) && (player_count < MAXPLAYERS) )
- {
- // clear end of line
- if ( strchr(line, '\n') )
- strchr(line, '\n')[0] = '\0';
- if ( strchr(line, '\r') )
- strchr(line, '\r')[0] = '\0';
-
- // check to see if we found a new stage
- if (! strcasecmp(line, "global") )
- stage = GLOBAL_STAGE;
- else if (! strcasecmp(line, "environment") )
- stage = ENVIRONMENT_STAGE;
- else if (! strcasecmp(line, "players") )
- stage = PLAYER_STAGE;
-
- // not a new stage, seperate field and value
- else
- {
- value = strchr(line, '=');
- if (value) // valid line
- {
- field = line;
- value[0] = '\0'; // seperate field and value;
- value++; // go to first place after =
- // interpret data
- switch (stage)
- {
- case GLOBAL_STAGE:
- if (! strcasecmp(field, "rounds") )
- sscanf(value, "%lf", &global->rounds);
- else if (! strcasecmp(field, "currentround") )
- sscanf(value, "%d", &global->currentround);
- else if (! strcasecmp(field, "campaignmode") )
- sscanf(value, "%lf", &global->campaign_mode);
-
- break;
- case ENVIRONMENT_STAGE:
-
- break;
- case PLAYER_STAGE:
- if (! strcasecmp(field, "playernumber") )
- {
- sscanf(value, "%d", &global_player_number);
- global->addPlayer( global->allPlayers[global_player_number] );
- /* the loading of the preferences is (unfortunately) disabled and I do not know why.
- As long as they *are* disabled, PerPlay-Config bots have to get a new config here,
- or they use the global config, rendering PerPlay-Config useless. */
- if ( (global->players[player_count]->preftype == PERPLAY_PREF)
- && (global->players[player_count]->type != HUMAN_PLAYER))
- global->players[player_count]->generatePreferences();
- global->players[player_count]->initialise();
- }
- else if (! strcasecmp(field, "score") )
- sscanf(value, "%d", &(global->players[player_count]->score) );
- else if (! strcasecmp(field, "money") )
- sscanf(value, "%d", &(global->players[player_count]->money) );
- else if (! strcasecmp(field, "type") )
- sscanf(value, "%lf", &(global->players[player_count]->type) );
- else if (! strcasecmp(field, "typesaved") )
- sscanf(value, "%lf", &(global->players[player_count]->type_saved) );
- else if (! strcasecmp(field, "weapon") )
- {
- sscanf(value, "%d %d", &index, &amount);
- global->players[player_count]->nm[index] = amount;
- }
- else if (! strcasecmp(field, "item") )
- {
- sscanf(value, "%d %d", &index, &amount);
- global->players[player_count]->ni[index] = amount;
- }
- break;
- } // end of stage
-
- } // end of valid line
- else if ( (! strcmp(line, "***") ) && (stage == PLAYER_STAGE) )
- player_count++;
-
- } // end of field value area
-
- // read next line of file
- got_line = fgets(line, 512, my_file);
-
- } // end of reading file
- fclose(my_file);
- return TRUE;
+ char line[ MAX_CONFIG_LINE + 1] = { 0 };
+ char field[MAX_CONFIG_LINE + 1] = { 0 };
+ char value[MAX_CONFIG_LINE + 1] = { 0 };
+ char* result = nullptr;
+ int32_t player_count = 0;
+ int32_t line_num = 0;
+ int32_t player_idx = -1;
+ bool done = false;
+
+ // Be sure that numbers are understood right:
+ const char* cur_lc_numeric = setlocale(LC_NUMERIC, "C");
+
+ // Ensure backward compatibility:
+ env.nextCampaignRound = -1.;
+ env.campaign_rounds = -1.;
+
+ // Open game file
+ snprintf(path_buf, PATH_MAX, "%s/%s.sav", env.configDir, env.game_name);
+ FILE* game_file = fopen(path_buf, "r");
+ if (!game_file)
+ return false;
+
+ // Now read until the file is finished loading
+ eSaveGameStage stage = SGS_NONE;
+ do {
+ // read a line
+ memset(line, 0, MAX_CONFIG_LINE);
+ if ( ( result = fgets(line, MAX_CONFIG_LINE, game_file) ) ) {
+ ++line_num;
+
+ // if we hit end of the file, stop
+ if (! strncmp(line, "***EOF***", 9) ) {
+ done = true;
+ continue; // This exits the loop as well
+ }
+
+ // strip newline character
+ int32_t line_length = strlen(line);
+ while ( line[line_length - 1] == '\n') {
+ line[line_length - 1] = '\0';
+ line_length--;
+ }
+
+ // check to see if we found a new stage
+ if (!strcasecmp(line, "GLOBAL") )
+ stage = SGS_GLOBAL;
+ else if (!strcasecmp(line, "ENVIRONMENT") )
+ stage = SGS_ENVIRONMENT;
+ else if (!strcasecmp(line, "PLAYERS") )
+ stage = SGS_PLAYERS;
+ else {
+ // Not a new stage, keep loading.
+
+ // find equal sign
+ int32_t equal_position = 1;
+ while ( ( equal_position < line_length )
+ && ( line[equal_position] != '=' ) )
+ equal_position++;
+
+ // make sure the equal sign position is valid
+ if (line[equal_position] != '=')
+ continue; // Go to next line
+
+ // separate field from value
+ memset(field, '\0', MAX_CONFIG_LINE);
+ memset(value, '\0', MAX_CONFIG_LINE);
+ strncpy(field, line, equal_position);
+ strncpy(value, &( line[equal_position + 1] ), MAX_CONFIG_LINE);
+
+ switch (stage) {
+ case SGS_ENVIRONMENT:
+ if (!strcasecmp(field, "CAMPAIGNMODE") ) {
+ int32_t cm = 0;
+ sscanf(value, "%d", &cm);
+ env.campaign_mode = !cm ? false : true;
+ } else if (!strcasecmp(field, "CAMPAIGNROUNDS") )
+ sscanf(value, "%lf", &env.campaign_rounds);
+ else if (!strcasecmp(field, "ROUNDS") )
+ sscanf(value, "%u", &env.rounds);
+ else if (!strcasecmp(field, "NEXTCAMPROUND") )
+ sscanf(value, "%lf", &env.nextCampaignRound);
+ else {
+ cerr << path_buf << ":" << line_num << " : Ignored line\n";
+ cerr << "The line \"" << line << "\"";
+ cerr << " is ignored, it does not belong to ENV" << endl;
+ }
+
+ break;
+ case SGS_GLOBAL:
+ if (!strcasecmp(field, "CURRENTROUND") )
+ sscanf(value, "%u", &global.currentround);
+
+ // The following are kept for backwards compatibility:
+
+ else if (!strcasecmp(field, "NEXTCAMPROUND") )
+ sscanf(value, "%lf", &env.nextCampaignRound);
+ else if (!strcasecmp(field, "CAMPAIGNMODE") ) {
+ int32_t cm = 0;
+ sscanf(value, "%d", &cm);
+ env.campaign_mode = !cm ? false : true;
+ } else if (!strcasecmp(field, "ROUNDS") )
+ sscanf(value, "%u", &env.rounds);
+ else if (!strcasecmp(field, "SCOREBOARD") ) {
+ int32_t enabled = 0;
+ sscanf(value, "%d", &enabled);
+ global.showScoreBoard = (enabled != 0);
+ } else {
+ cerr << path_buf << ":" << line_num << " : Ignored line\n";
+ cerr << "The line \"" << line << "\"";
+ cerr << " is ignored, it does not belong to GLOBAL" << endl;
+ }
+
+ break;
+ case SGS_PLAYERS:
+ if (!strcasecmp(field, "PLAYERNUMBER") ) {
+ sscanf(value, "%d", &player_idx);
+ if ( (player_idx > -1)
+ && (player_idx < env.numPermanentPlayers)
+ && (player_count < MAXPLAYERS) ) {
+ env.addGamePlayer( env.allPlayers[player_idx] );
+ env.players[player_count++]->initialise(true);
+ } else
+ player_idx = -1;
+ }
+
+ // Now let the player load itself
+ if (player_idx > -1)
+ env.allPlayers[player_idx]->load_game_data(game_file);
+ else {
+ cerr << path_buf << ":" << line_num << " : Ignored line\n";
+ cerr << "The line \"" << line << "\"";
+ cerr << " is ignored, as player idx is " << player_idx << endl;
+ }
+
+ break;
+ case SGS_NONE:
+ default:
+ cerr << path_buf << ":" << line_num << " : Wrong line\n";
+ cerr << "The line \"" << line << "\"";
+ cerr << " does not belong to any stage!" << endl;
+ break;
+ } // end of switching stage
+ } // End of loading line / player record
+ } // end of having read a line
+ } while (result && !done);
+ // End of being not done
+
+ fclose(game_file);
+
+ // See if the campaign state values have to be recalculated
+ // because an old save game was loaded.
+ if (env.campaign_rounds < 0.)
+ env.campaign_rounds = static_cast<double>(env.rounds) / 5.;
+ if (env.nextCampaignRound < 0.) {
+ env.nextCampaignRound = static_cast<double>(env.rounds)
+ - env.campaign_rounds;
+ while (global.currentround < env.nextCampaignRound)
+ env.nextCampaignRound -= env.campaign_rounds;
+ }
+
+ // To ensure backwards compatibility, all players have to check
+ // their opponents memory. Old save files do not provide any.
+ for (int32_t i = 0; i < env.numGamePlayers; ++i)
+ env.players[i]->checkOppMem();
+
+ // Revert locale settings
+ if (cur_lc_numeric)
+ setlocale(LC_NUMERIC, cur_lc_numeric);
+ else
+ setlocale(LC_NUMERIC, "");
+
+ return true;
}
@@ -234,488 +290,410 @@ int Load_Game(GLOBALDATA *global, ENVIRONMENT * /*env*/)
/*
Check to see if a saved game exists with the given name.
*/
-int Check_For_Saved_Game(GLOBALDATA *global)
+bool Check_For_Saved_Game()
{
- FILE *my_file;
- char *path_name;
-
- path_name = (char *) calloc( strlen(global->configDir) + strlen(global->game_name) + 24,
- sizeof(char) );
- if (! path_name)
- return FALSE;
-
- sprintf(path_name, "%s/%s.sav", global->configDir, global->game_name);
-
- my_file = fopen(path_name, "r");
- free(path_name);
- if (my_file)
- {
- fclose(my_file);
- return TRUE;
- }
- else
- return FALSE;
+ snprintf(path_buf, PATH_MAX, "%s/%s.sav", env.configDir, env.game_name);
+ if (!access(path_buf, R_OK))
+ return true;
+
+ return false;
}
-/*
-This function copies the atanks config file
-from the HOME_DIR folder to HOME_DIR/.atanks
-If the .atanks folder does not exist, this
-function will create it.
-Returns TRUE on success and FALSE on error.
+/** @brief Copy atanks config to safe location.
+ *
+ * This function copies the atanks config file from the HOME_DIR folder to
+ * HOME_DIR/.atanks
+ * If the .atanks folder does not exist, this function will create it.
+ *
+ * @return true on success, false otherwise
*/
-
-int Copy_Config_File(GLOBALDATA *global)
+bool Copy_Config_File()
{
- FILE *source_file, *dest_file;
- char source_path[1024], dest_path[1024];
- char buffer[1024];
- char *my_home_folder;
- int status;
-
- // figure out where home is
- my_home_folder = getenv(HOME_DIR);
- if (! my_home_folder)
- my_home_folder = (char *)".";
-
- // check to see if the config file has already been copied
- snprintf(source_path, 1024, "%s/atanks-config.txt", global->configDir);
- source_file = fopen(source_path, "r");
- if (source_file) // config file is in the right place
- {
- fclose(source_file);
- return TRUE;
- }
-
- /*
- // check to make sure we have a source file
- snprintf(source_path, 1024, "%s/.atanks-config.txt", my_home_folder);
- source_file = fopen(source_path, "r");
- if (! source_file)
- return TRUE;
- */
-
- // file not copied yet, create the required directory
- snprintf(buffer, 1024, "%s/.atanks", my_home_folder);
-#ifdef WIN32
- status = mkdir(buffer);
+ static char xHere[2] = ".";
+ FILE* source_file = nullptr;
+ FILE* dest_file = nullptr;
+ char source_path[PATH_MAX + 1] = { 0 };
+ char dest_path[PATH_MAX + 1] = { 0 };
+ char buffer[PATH_MAX + 1] = { 0 };
+
+ // check to see if the config file has already been copied
+ snprintf(dest_path, PATH_MAX, "%s/atanks-config.txt", env.configDir);
+ if (!access(dest_path, R_OK | W_OK))
+ return true;
+
+ char* my_home_folder = getenv(HOME_DIR);
+ int status = 0;
+
+ // figure out where home is
+ if (! my_home_folder)
+ my_home_folder = xHere;
+
+
+ // file not copied yet, create the required directory
+ snprintf(buffer, PATH_MAX, "%s/.atanks", my_home_folder);
+#ifdef ATANKS_IS_WINDOWS
+ status = mkdir(buffer);
#else
- status = mkdir(buffer, 0700);
-#endif
- if (status == -1)
- {
- printf( (char *)"Error occured. Unable to create sub directory.\n");
- return FALSE;
- }
-
- // check to make sure we have a source file
- snprintf(source_path, 1024, "%s/.atanks-config.txt", my_home_folder);
- source_file = fopen(source_path, "r");
- if (! source_file)
- return TRUE;
-
- // we already have an open source file, create destination file
- snprintf(dest_path, 1024, "%s/atanks-config.txt", global->configDir);
- dest_file = fopen(dest_path, "wb");
- if (! dest_file)
- {
- printf( (char *)"Unable to create destination file.\n");
- fclose(source_file);
- return FALSE;
- }
-
- // we have open files, let's copy
- status = fread(buffer, 1, 1024, source_file);
- while (status)
- {
- status = fwrite(buffer, 1, 1024, dest_file);
- status = fread(buffer, 1, 1024, source_file);
- }
-
- fclose(source_file);
- fclose(dest_file);
- return TRUE;
+ status = mkdir(buffer, 0700);
+#endif // ATANKS_IS_WINDOWS
+
+ if (status == -1) {
+ printf( "Error occured. Unable to create sub directory.\n");
+ return false;
+ }
+
+ // check to make sure we have a source file
+ snprintf(source_path, PATH_MAX, "%s/.atanks-config.txt", my_home_folder);
+ source_file = fopen(source_path, "r");
+ if (! source_file)
+ return true;
+
+ // we already have an open source file, create destination file
+ dest_file = fopen(dest_path, "wb");
+ if (! dest_file) {
+ printf( "Unable to create destination file.\n");
+ fclose(source_file);
+ return false;
+ }
+
+ // we have open files, let's copy
+ status = fread(buffer, 1, PATH_MAX, source_file);
+ while (status) {
+ status = fwrite(buffer, 1, PATH_MAX, dest_file);
+ status = fread(buffer, 1, PATH_MAX, source_file);
+ }
+
+ fclose(source_file);
+ fclose(dest_file);
+ return true;
}
-// Make sure we have a music folder
-// Returns TRUE on success or FALSE
-// if an error occures.
-int Create_Music_Folder(GLOBALDATA *global)
+/** @brief Make sure we have a music folder
+ * @return true on success or false if an error occures.
+**/
+bool Create_Music_Folder()
{
- DIR *music_folder;
- char *folder_path;
-
- folder_path = (char *) calloc( strlen(global->configDir) + 32, sizeof(char) );
- if (! folder_path)
- return FALSE;
-
- sprintf(folder_path, "%s/music", global->configDir);
- music_folder = opendir(folder_path);
- if (! music_folder)
- {
- #ifdef WIN32
- mkdir(folder_path);
- #else
- mkdir(folder_path, 0700);
- #endif
- }
- else // it already exists
- closedir(music_folder);
-
- free(folder_path);
- return TRUE;
-}
+ snprintf(path_buf, PATH_MAX, "%s/music", env.configDir);
+ DIR* music_folder = opendir(path_buf);
-
-
-
-
-/*
-Displays lines of text.
-*/
-void renderTextLines (GLOBALDATA *global, ENVIRONMENT *env,
- TEXTBLOCK *lines, int scrollOffset,
- const FONT* fnt, const int spacing )
-{
- const int textheight = text_height (fnt) ;
- const int textheight_s = textheight * spacing ;
- int yOffset = scrollOffset;
- char *text;
- int count;
-
- for (count = 0;
- (count < lines->total_lines) && (yOffset < global->screenHeight);
- count++)
- {
- text = lines->complete_text[count];
-
- textout_centre_ex (env->db, fnt, text,
- global->halfWidth + 2, global->halfHeight + yOffset + 2, BLACK, -1);
- textout_centre_ex (env->db, fnt, text,
- global->halfWidth, global->halfHeight + yOffset, WHITE, -1);
- yOffset += textheight_s;
- }
+ if (! music_folder) {
+#ifdef ATANKS_IS_WINDOWS
+ if (mkdir(path_buf))
+#else
+ if (mkdir(path_buf, 0700))
+#endif // ATANKS_IS_WINDOWS
+ return false;
+ } else
+ // it already exists
+ closedir(music_folder);
+
+ return true;
}
-
-
/*
Scroll text in a box
*/
-void scrollTextList (GLOBALDATA *global, ENVIRONMENT *env,
- TEXTBLOCK *lines)
+void scrollTextList (TEXTBLOCK* lines)
{
- /* Justin says: this function, along with renderTextLines, aren't
- exactly efficient. I think they could use rewrite. */
- static const int numItemsSrc[] = { 100, 30 } ;
-
- // DATAFILE *dffont ;
- const FONT* fnt = font;
- int spacing = 2;
- int tOffset = rand ();
- int itemType = rand () / (RAND_MAX/2 + 1) ;
- int numItems = numItemsSrc[itemType];
- int my_key, done = FALSE;
- int moving = TRUE;
-
- draw_circlesBG (global, env->db, 0, 0, global->screenWidth, global->screenHeight, false);
- drawMenuBackground (global, env, BACKGROUND_BLANK, abs (tOffset), numItems);
- quickChange (global, env->db);
- //set_clip_rect (env->db, global->halfWidth - 300 + 1, 100 + 1,
- // global->halfWidth + 300 - 1, global->screenHeight - 100 - 1);
- set_clip_rect(env->db, global->halfWidth - 300 + 1, global->menuBeginY + 1,
- global->halfWidth + 300 - 1, global->menuEndY - 1);
- int scrollOffset = 0;
- flush_inputs ();
- if (! global->os_mouse) show_mouse (NULL);
-
- // lines->Display_All(TRUE);
- do
- {
- drawMenuBackground (global, env, BACKGROUND_BLANK, abs (tOffset), numItems);
-
- renderTextLines (global, env, lines, scrollOffset,
- fnt, spacing );
- //blit (env->db, screen, global->halfWidth - 300, 100, global->halfWidth - 300, 100,
- // 601, global->screenHeight - 199);
- blit(env->db, screen, global->halfWidth - 300, global->menuBeginY,
- global->halfWidth - 300, global->menuBeginY, 601,
- global->screenHeight - 2 * global->menuBeginY);
- LINUX_REST;
- if (moving)
- {
- tOffset++;
- scrollOffset--;
- }
-
- if (scrollOffset < -(global->halfHeight - 100 + lines->total_lines * 30))
- scrollOffset = global->halfHeight - 100;
- if (global->close_button_pressed)
- break;
- if ( keypressed() )
- {
- my_key = (readkey()) >> 8;
- switch (my_key)
- {
- case KEY_ESC: done = TRUE;
- break;
- case KEY_SPACE:
- moving = TRUE;
- break;
- case KEY_UP:
- tOffset--;
- scrollOffset++;
- moving = FALSE;
- break;
- case KEY_DOWN:
- tOffset++;
- scrollOffset--;
- moving = FALSE;
- break;
- } // end of switch
- } // end of key pressed
- }
- while ( (! done) && (!mouse_b) );
-
- if (! global->os_mouse) show_mouse (screen);
- set_clip_rect (env->db, 0, 0, (global->screenWidth-1), (global->screenHeight-1));
- flush_inputs ();
+ int32_t spacing = 2;
+ int32_t tOffset = (RAND_MAX / 4) + (rand() % (RAND_MAX / 4));
+ int32_t numItems = (rand() % 100) + 20;
+ int32_t key = 0;
+ bool done = false;
+ bool moving = true;
+
+ eBackgroundTypes bgType = env.dynamicMenuBg
+ ? static_cast<eBackgroundTypes>(rand() % BACKGROUND_COUNT)
+ : BACKGROUND_BLANK;
+
+ drawMenuBackground (bgType, tOffset, numItems);
+ quickChange(true);
+
+ int32_t clip_l = env.halfWidth - 299;
+ int32_t clip_t = env.menuBeginY + 1;
+ int32_t clip_b = env.menuEndY - 1;
+ int32_t clip_r = env.halfWidth + 299;
+ set_clip_rect(global.canvas, clip_l, clip_t, clip_r, clip_b);
+
+ int32_t scrollOffset = clip_b - env.halfHeight - 14;
+ flush_inputs ();
+ SHOW_MOUSE(nullptr)
+
+ do {
+ if (global.isCloseBtnPressed())
+ done = true;
+
+ if (++tOffset >= INT_MAX)
+ tOffset = 0;
+ if (moving)
+ scrollOffset--;
+
+ if (scrollOffset < -(env.halfHeight - 100 + lines->Lines() * 30) )
+ scrollOffset = env.halfHeight - 100;
+ else if (scrollOffset > (clip_b - env.halfHeight - 14))
+ scrollOffset = clip_b - env.halfHeight - 14;
+
+ drawMenuBackground (bgType, tOffset, numItems);
+ lines->Render_Lines(scrollOffset, spacing, clip_t, clip_b);
+ global.make_update(env.halfWidth - 300, env.menuBeginY,
+ 601, env.screenHeight - 2 * env.menuBeginY);
+ global.do_updates();
+ LINUX_REST;
+
+ if ( keypressed() ) {
+ key = (readkey()) >> 8;
+ switch (key) {
+ case KEY_ESC:
+ done = true;
+ break;
+ case KEY_SPACE:
+ moving = true;
+ break;
+ case KEY_UP:
+ scrollOffset += 2;
+ moving = false;
+ break;
+ case KEY_DOWN:
+ scrollOffset -= 2;
+ moving = false;
+ break;
+ } // end of switch
+ } // end of key pressed
+
+ if (mouse_b)
+ done = true;
+ } while (!done);
+
+ SHOW_MOUSE(global.canvas)
+ set_clip_rect (global.canvas, 0, 0, (env.screenWidth-1), (env.screenHeight-1));
+ flush_inputs ();
}
-
/* Flush key buffer and waits for button releases */
void flush_inputs()
{
- do { }
- while (mouse_b);
- clear_keybuf();
+ do { std::this_thread::yield(); } while (mouse_b);
+ clear_keybuf();
}
-
// This file loads weapons, naturals and items
// from a text file
// Returns TRUE on success and FALSE on failure
-int Load_Weapons_Text(GLOBALDATA *global)
+bool Load_Weapons_Text()
{
- FILE *wfile;
- char *path_to_file;
- char *status;
- char line[512];
- int file_stage = 0; // weapons, natruals, items
- int data_stage = 0; // name, descrption, data
- int item_count = 0, weapon_count = 0, natural_count = 0;
-
- setlocale(LC_NUMERIC, "C");
- // get path name
- path_to_file = (char *) calloc( strlen(global->dataDir) + 64, sizeof(char) );
- if (! path_to_file)
- {
- printf( "Memory error occured while loading weapons.\n");
- return FALSE;
- }
-
- if (global->language == LANGUAGE_ENGLISH)
- sprintf(path_to_file, "%s/text/weapons.txt", global->dataDir);
- else if (global->language == LANGUAGE_PORTUGUESE)
- sprintf(path_to_file, "%s/text/weapons.pt_BR.txt", global->dataDir);
- else if (global->language == LANGUAGE_FRENCH)
- sprintf(path_to_file, "%s/text/weapons_fr.txt", global->dataDir);
- else if (global->language == LANGUAGE_GERMAN)
- sprintf(path_to_file, "%s/text/weapons_de.txt", global->dataDir);
- else if (global->language == LANGUAGE_SLOVAK)
- sprintf(path_to_file, "%s/text/weapons_sk.txt", global->dataDir);
- else if (global->language == LANGUAGE_RUSSIAN)
- sprintf(path_to_file, "%s/text/weapons_ru.txt", global->dataDir);
- else if (global->language == LANGUAGE_SPANISH)
- sprintf(path_to_file, "%s/text/weapons_ES.txt", global->dataDir);
- else if (global->language == LANGUAGE_ITALIAN)
- sprintf(path_to_file, "%s/text/weapons_it.txt", global->dataDir);
-
- // open file
- wfile = fopen(path_to_file, "r");
- // free memory
- // free(path_to_file);
- if (! wfile)
- {
- printf( "Unable to open weapons file. (%s)\n", path_to_file);
- free(path_to_file);
- return FALSE;
- }
- free(path_to_file);
-
- Clear_Weapons(); // make sure arrays are cleared before loading data
- // read line
- status = fgets(line, 512, wfile);
- while (status)
- {
- // clear end of line
- if ( strchr(line, '\n') )
- strchr(line, '\n')[0] = '\0';
- if ( strchr(line, '\r') )
- strchr(line, '\r')[0] = '\0';
-
- // skip # and empty lines
- if ( (! (line[0] == '#') ) && ( strlen(line) > 2 ) )
- {
- // check for header
- if (! strcasecmp(line, "*WEAPONS*") )
- {
- file_stage = 0;
- data_stage = 0;
- }
- else if (! strcasecmp(line, "*NATURALS*") )
- {
- file_stage = 1;
- data_stage = 0;
- }
- else if (! strcasecmp(line, "*ITEMS*") )
- {
- file_stage = 2;
- data_stage = 0;
- }
-
- // not a special line, let's read some data
- else
- {
- // weapon
- if ( (file_stage == 0) && (weapon_count < WEAPONS) )
- {
- if (data_stage == 0) // name
- strcpy(weapon[weapon_count].name, line);
- else if (data_stage == 1)
- strcpy(weapon[weapon_count].description, line);
- else if (data_stage == 2)
- {
- sscanf(line, "%d %d %lf %lf %d %d %d %d %d %d %d %d %d %d %d %d %d %lf %d %lf %lf %lf %d %lf",
- &(weapon[weapon_count].cost),
- &(weapon[weapon_count].amt),
- &(weapon[weapon_count].mass),
- &(weapon[weapon_count].drag),
- &(weapon[weapon_count].radius),
- &(weapon[weapon_count].sound),
- &(weapon[weapon_count].etime),
- &(weapon[weapon_count].damage),
- &(weapon[weapon_count].eframes),
- &(weapon[weapon_count].picpoint),
- &(weapon[weapon_count].spread),
- &(weapon[weapon_count].delay),
- &(weapon[weapon_count].noimpact),
- &(weapon[weapon_count].techLevel),
- &(weapon[weapon_count].warhead),
- &(weapon[weapon_count].numSubmunitions),
- &(weapon[weapon_count].submunition),
- &(weapon[weapon_count].impartVelocity),
- &(weapon[weapon_count].divergence),
- &(weapon[weapon_count].spreadVariation),
- &(weapon[weapon_count].launchSpeed),
- &(weapon[weapon_count].speedVariation),
- &(weapon[weapon_count].countdown),
- &(weapon[weapon_count].countVariation) );
- }
- data_stage++;
- if (data_stage > 2)
- {
- data_stage = 0;
- weapon_count++;
- }
- } // end of weapon section
-
- // naturals
- else if ( (file_stage == 1) && (natural_count < NATURALS) )
- {
- if (data_stage == 0) // name
- strcpy(naturals[natural_count].name, line);
- else if (data_stage == 1)
- strcpy(naturals[natural_count].description, line);
- else if (data_stage == 2)
- {
- sscanf(line, "%d %d %lf %lf %d %d %d %d %d %d %d %d %d %d %d %d %d %lf %d %lf %lf %lf %d %lf",
- &(naturals[natural_count].cost),
- &(naturals[natural_count].amt),
- &(naturals[natural_count].mass),
- &(naturals[natural_count].drag),
- &(naturals[natural_count].radius),
- &(naturals[natural_count].sound),
- &(naturals[natural_count].etime),
- &(naturals[natural_count].damage),
- &(naturals[natural_count].eframes),
- &(naturals[natural_count].picpoint),
- &(naturals[natural_count].spread),
- &(naturals[natural_count].delay),
- &(naturals[natural_count].noimpact),
- &(naturals[natural_count].techLevel),
- &(naturals[natural_count].warhead),
- &(naturals[natural_count].numSubmunitions),
- &(naturals[natural_count].submunition),
- &(naturals[natural_count].impartVelocity),
- &(naturals[natural_count].divergence),
- &(naturals[natural_count].spreadVariation),
- &(naturals[natural_count].launchSpeed),
- &(naturals[natural_count].speedVariation),
- &(naturals[natural_count].countdown),
- &(naturals[natural_count].countVariation) );
- }
-
- data_stage++;
- if (data_stage > 2)
- {
- data_stage = 0;
- natural_count++;
- }
-
- } // end of naturals
-
- // item section
- else if ( (file_stage == 2) && (item_count < ITEMS) )
- {
- if (data_stage == 0) // name
- strcpy(item[item_count].name, line);
- else if (data_stage == 1)
- strcpy(item[item_count].description, line);
- else if (data_stage == 2)
- {
- sscanf(line, "%d %d %d %d %d %lf %lf %lf %lf %lf %lf",
- &(item[item_count].cost),
- &(item[item_count].amt),
- &(item[item_count].selectable),
- &(item[item_count].techLevel),
- &(item[item_count].sound),
- &(item[item_count].vals[0]),
- &(item[item_count].vals[1]),
- &(item[item_count].vals[2]),
- &(item[item_count].vals[3]),
- &(item[item_count].vals[4]),
- &(item[item_count].vals[5]) );
- }
-
- data_stage++;
- if (data_stage > 2)
- {
- data_stage = 0;
- item_count++;
- }
- } // end of items
-
- } // end of reading data from a valid line
-
- } // end of valid line
-
- // read in data
- status = fgets(line, 512, wfile);
- }
-
- // close file
- fclose(wfile);
- // go home
- return TRUE;
+ // Be sure that numbers are understood right:
+ const char* cur_lc_numeric = setlocale(LC_NUMERIC, "C");
+
+ // get path name
+ if (env.language == EL_ENGLISH)
+ snprintf(path_buf, PATH_MAX, "%s/text/weapons.txt", env.dataDir);
+ else if (env.language == EL_PORTUGUESE)
+ snprintf(path_buf, PATH_MAX, "%s/text/weapons.pt_BR.txt", env.dataDir);
+ else if (env.language == EL_FRENCH)
+ snprintf(path_buf, PATH_MAX, "%s/text/weapons_fr.txt", env.dataDir);
+ else if (env.language == EL_GERMAN)
+ snprintf(path_buf, PATH_MAX, "%s/text/weapons_de.txt", env.dataDir);
+ else if (env.language == EL_SLOVAK)
+ snprintf(path_buf, PATH_MAX, "%s/text/weapons_sk.txt", env.dataDir);
+ else if (env.language == EL_RUSSIAN)
+ snprintf(path_buf, PATH_MAX, "%s/text/weapons_ru.txt", env.dataDir);
+ else if (env.language == EL_SPANISH)
+ snprintf(path_buf, PATH_MAX, "%s/text/weapons_ES.txt", env.dataDir);
+ else if (env.language == EL_ITALIAN)
+ snprintf(path_buf, PATH_MAX, "%s/text/weapons_it.txt", env.dataDir);
+
+ // open file
+ FILE* wfile = fopen(path_buf, "r");
+
+ if (! wfile) {
+ printf( "Unable to open weapons file. (%s)\n", path_buf);
+ return false;
+ }
+
+ // read line
+ char line[512] = { 0 };
+ char* status = fgets(line, 512, wfile);
+ eFileStage file_stage = FS_WEAPONS; // weapons, naturals, items
+ eDataStage data_stage = DS_NAME; // name, description, data
+ int32_t item_count = 0;
+ int32_t weapon_count = 0;
+ int32_t natural_count = 0;
+
+ while (status) {
+ // clear end of line
+ if ( strchr(line, '\n') )
+ strchr(line, '\n')[0] = '\0';
+ if ( strchr(line, '\r') )
+ strchr(line, '\r')[0] = '\0';
+
+ // skip # and empty lines
+ if ( (! (line[0] == '#') ) && ( strlen(line) > 2 ) ) {
+
+ // check for header
+ if (!strcasecmp(line, "*WEAPONS*") ) {
+ file_stage = FS_WEAPONS;
+ data_stage = DS_NAME;
+ } else if (!strcasecmp(line, "*NATURALS*") ) {
+ file_stage = FS_NATURALS;
+ data_stage = DS_NAME;
+ } else if (!strcasecmp(line, "*ITEMS*") ) {
+ file_stage = FS_ITEMS;
+ data_stage = DS_NAME;
+ }
+
+ // not a special line, let's read some data
+ else {
+ // =============
+ // == Weapons ==
+ // =============
+ if ( (FS_WEAPONS == file_stage) && (weapon_count < WEAPONS) ) {
+ if (DS_NAME == data_stage)
+ weapon[weapon_count].setName(line);
+ else if (DS_DESC == data_stage)
+ weapon[weapon_count].setDesc(line);
+ else if (DS_DATA == data_stage) {
+ sscanf(line, "%d %d %lf %lf %d %d %d %d %d %d %d %d %d %d %d %d %lf %d %lf %lf %lf %d %lf",
+ &(weapon[weapon_count].cost),
+ &(weapon[weapon_count].amt),
+ &(weapon[weapon_count].mass),
+ &(weapon[weapon_count].drag),
+ &(weapon[weapon_count].radius),
+ &(weapon[weapon_count].sound),
+ &(weapon[weapon_count].etime),
+ &(weapon[weapon_count].damage),
+ &(weapon[weapon_count].picpoint),
+ &(weapon[weapon_count].spread),
+ &(weapon[weapon_count].delay),
+ &(weapon[weapon_count].noimpact),
+ &(weapon[weapon_count].techLevel),
+ &(weapon[weapon_count].warhead),
+ &(weapon[weapon_count].numSubmunitions),
+ &(weapon[weapon_count].submunition),
+ &(weapon[weapon_count].impartVelocity),
+ &(weapon[weapon_count].divergence),
+ &(weapon[weapon_count].spreadVariation),
+ &(weapon[weapon_count].launchSpeed),
+ &(weapon[weapon_count].speedVariation),
+ &(weapon[weapon_count].countdown),
+ &(weapon[weapon_count].countVariation) );
+ }
+
+ // Advance data stage
+ ++data_stage;
+ if ( (DS_NAME == data_stage) // flipped
+ || ( (DS_DATA == data_stage)
+ && (EL_ENGLISH != env.language)) ) {
+ data_stage = DS_NAME;
+ weapon_count++;
+ }
+ } // end of weapon section
+
+ // ==============
+ // == Naturals ==
+ // ==============
+ else if ( (FS_NATURALS == file_stage) && (natural_count < NATURALS) ) {
+ if (DS_NAME == data_stage)
+ naturals[natural_count].setName(line);
+ else if (DS_DESC == data_stage)
+ naturals[natural_count].setDesc(line);
+ else if (DS_DATA == data_stage) {
+ sscanf(line, "%d %d %lf %lf %d %d %d %d %d %d %d %d %d %d %d %d %lf %d %lf %lf %lf %d %lf",
+ &(naturals[natural_count].cost),
+ &(naturals[natural_count].amt),
+ &(naturals[natural_count].mass),
+ &(naturals[natural_count].drag),
+ &(naturals[natural_count].radius),
+ &(naturals[natural_count].sound),
+ &(naturals[natural_count].etime),
+ &(naturals[natural_count].damage),
+ &(naturals[natural_count].picpoint),
+ &(naturals[natural_count].spread),
+ &(naturals[natural_count].delay),
+ &(naturals[natural_count].noimpact),
+ &(naturals[natural_count].techLevel),
+ &(naturals[natural_count].warhead),
+ &(naturals[natural_count].numSubmunitions),
+ &(naturals[natural_count].submunition),
+ &(naturals[natural_count].impartVelocity),
+ &(naturals[natural_count].divergence),
+ &(naturals[natural_count].spreadVariation),
+ &(naturals[natural_count].launchSpeed),
+ &(naturals[natural_count].speedVariation),
+ &(naturals[natural_count].countdown),
+ &(naturals[natural_count].countVariation) );
+ }
+
+ // Advance data stage
+ ++data_stage;
+ if ( (DS_NAME == data_stage) // flipped
+ || ( (DS_DATA == data_stage)
+ && (EL_ENGLISH != env.language)) ) {
+ data_stage = DS_NAME;
+ natural_count++;
+ }
+
+ } // end of naturals
+
+ // ==============
+ // == Items ==
+ // ==============
+ else if ( (FS_ITEMS == file_stage) && (item_count < ITEMS) ) {
+ if (DS_NAME == data_stage)
+ item[item_count].setName(line);
+ else if (DS_DESC == data_stage)
+ item[item_count].setDesc(line);
+ else if (DS_DATA == data_stage) {
+ sscanf(line, "%d %d %d %d %d %lf %lf %lf %lf %lf %lf",
+ &(item[item_count].cost),
+ &(item[item_count].amt),
+ &(item[item_count].selectable),
+ &(item[item_count].techLevel),
+ &(item[item_count].sound),
+ &(item[item_count].vals[0]),
+ &(item[item_count].vals[1]),
+ &(item[item_count].vals[2]),
+ &(item[item_count].vals[3]),
+ &(item[item_count].vals[4]),
+ &(item[item_count].vals[5]) );
+ }
+
+ // Advance data stage
+ ++data_stage;
+ if ( (DS_NAME == data_stage) // flipped
+ || ( (DS_DATA == data_stage)
+ && (EL_ENGLISH != env.language)) ) {
+ data_stage = DS_NAME;
+ item_count++;
+ }
+ } // end of items
+ } // end of reading data from a valid line
+ } // end of valid line
+
+ // read in data
+ status = fgets(line, 512, wfile);
+ } // end while(status)
+
+
+ // close file
+ if (wfile)
+ fclose(wfile);
+
+
+ // Revert locale settings
+ if (cur_lc_numeric)
+ setlocale(LC_NUMERIC, cur_lc_numeric);
+ else
+ setlocale(LC_NUMERIC, "");
+
+ return true;
}
@@ -730,36 +708,35 @@ int Filter_File( const struct dirent *my_file )
int Filter_File( struct dirent *my_file )
#endif
{
- if ( strstr(my_file->d_name, ".sav") )
- return TRUE;
- else
- return FALSE;
+ if ( strstr(my_file->d_name, ".sav") )
+ return true;
+ else
+ return false;
}
/*
This function finds a list of saved games on your profile.
On error, NULL is returned. If all goes well, a list of file names
are returned.
-After use, the return value should be freed.
+After use, the return value *must* be freed.
*/
-#ifndef WIN32
-struct dirent ** Find_Saved_Games(GLOBALDATA *global, int *num_files_found)
- {
- struct dirent **my_list;
- int status;
-
- status = scandir(global->configDir, &my_list, Filter_File, alphasort);
- if (status < 0)
- {
- printf( (char *)"Error trying to find saved games.\n");
- return NULL;
- }
-
- *num_files_found = status;
- return my_list;
- }
-#endif
+#if defined(ATANKS_IS_LINUX)
+dirent** Find_Saved_Games(uint32_t &num_files_found)
+{
+ dirent** my_list = nullptr;
+ int32_t status = 0;
+ status = scandir(env.configDir, &my_list, Filter_File, alphasort);
+ if (status < 0) {
+ printf("Error trying to find saved games.\n");
+ return nullptr;
+ }
+
+ num_files_found = status;
+
+ return my_list;
+}
+#endif //Linux
/*
@@ -767,128 +744,40 @@ This function hunts for saved games. If games are found, the
function returns an array of filenames. If an error occures
or no files are found, NULL is returned.
*/
-#ifdef WIN32
-struct dirent ** Find_Saved_Games(GLOBALDATA *global, int *num_files_found)
- {
- struct dirent **my_list;
- struct dirent *one_file;
- int file_count = 0;
- DIR *game_dir;
-
- my_list = (struct dirent **) calloc(256, sizeof(struct dirent *));
- if (! my_list)
- return NULL;
-
- game_dir = opendir(global->configDir);
- if (! game_dir)
- {
- free(my_list);
- return NULL;
- }
-
- one_file = readdir(game_dir);
- while ( (one_file) && (file_count < 256) )
- {
- // check to see if this is a save game file
- if ( strstr( one_file->d_name, ".sav" ) )
- {
- my_list[file_count] = (struct dirent *) calloc(1, sizeof(struct dirent) );
- if ( my_list[file_count] )
- {
- memcpy(my_list[file_count], one_file, sizeof(struct dirent) );
- file_count++;
- }
- }
-
- one_file = readdir(game_dir);
- }
-
- closedir(game_dir);
- *num_files_found = file_count;
- return my_list;
- }
-#endif
-
-
-
-
-/*
- * This function grabs a random quote from the war_quotes.txt file.
- * This file is held in the data directory (dataDir) and contains
- * one-line quotes.
- * The function returns a pointer to the quote on success and a NULL
- * on failure. The line should be free()ed after use.
- * -- Jesse
- * */
-/*
-char *Get_Random_Quote(GLOBALDATA *global)
+#if defined(ATANKS_IS_WINDOWS)
+dirent** Find_Saved_Games(uint32_t &num_files_found)
{
- char *path_to_file = NULL;
- char *line, *result;
- FILE *quote_file = NULL;
- int line_count = 0;
- int index = 0;
- int random_number;
-
- line = (char *) calloc(1024, sizeof(char));
- if (! line)
- return NULL;
- path_to_file = (char *) calloc( strlen(global->dataDir) + 32, sizeof(char) );
- if (! path_to_file)
- {
- free(line);
- return NULL;
- }
-
- if (global->language == LANGUAGE_RUSSIAN)
- sprintf(path_to_file, "%s/war_quotes_ru.txt", global->dataDir);
- else
- sprintf(path_to_file, "%s/war_quotes.txt", global->dataDir);
- quote_file = fopen(path_to_file, "r");
- free(path_to_file);
- if (! quote_file)
- {
- free(line);
- return NULL;
- }
-
- // search through file getting the number of lines
- result = fgets(line, 1024, quote_file);
- while (result)
- {
- line_count++;
- result = fgets(line, 1024, quote_file);
- }
-
- // return to the start of the file
- rewind(quote_file);
-
- // generate a random number based on the size of the file
- random_number = rand() % line_count;
-
- // search through and find the line we want
- result = fgets(line, 1024, quote_file);
- while ( (result) && (index < random_number) )
- {
- index++;
- result = fgets(line, 1024, quote_file);
- }
-
- fclose(quote_file);
- if (! result)
- {
- free(line);
- return NULL;
- }
-
- // trim trailing newline character
- if ( strchr(line, '\n') )
- strchr(line, '\n')[0] = '\0';
- return line;
+ dirent** my_list = (dirent**)calloc(256, sizeof(dirent*));
+ dirent* one_file = nullptr;
+ uint32_t file_count = 0;
+ DIR* game_dir = nullptr;
+
+ if (!my_list)
+ return nullptr;
+
+ game_dir = opendir(env.configDir);
+ if (!game_dir) {
+ free(my_list);
+ return nullptr;
+ }
+
+ while ( (one_file = readdir(game_dir)) && (file_count < 256) ) {
+ // check to see if this is a save game file
+ if ( strstr( one_file->d_name, ".sav" ) ) {
+ my_list[file_count] = (dirent*)calloc(1, sizeof(dirent) );
+ if ( my_list[file_count] ) {
+ memcpy(my_list[file_count], one_file, sizeof(dirent));
+ my_list[file_count]->d_name = strdup(one_file->d_name);
+ file_count++;
+ }
+ }
+ }
+
+ closedir(game_dir);
+ num_files_found = file_count;
+ return my_list;
}
-*/
-
-
+#endif // ATANKS_IS_WINDOWS
/*
@@ -896,214 +785,52 @@ char *Get_Random_Quote(GLOBALDATA *global)
* The function returns an array of bitmap file names. If no files
* are found, or an error occures, then NULL is returned.
* */
-char ** Find_Bitmaps(GLOBALDATA *global, int *bitmaps_found)
+char** Find_Bitmaps(int32_t* bitmaps_found)
{
- char **my_list;
- struct dirent *one_file;
- int file_count = 0;
- DIR *game_dir;
-
- my_list = (char **) calloc(256, sizeof(struct dirent *));
- if (! my_list)
- return NULL;
-
-
- game_dir = opendir(global->configDir);
- if (! game_dir)
- {
- free(my_list);
- return NULL;
- }
-
- one_file = readdir(game_dir);
- while ( (one_file) && (file_count < 256) )
- {
- // check to see if this is a save game file
- #ifdef LINUX
- if ( strcasestr( one_file->d_name, ".bmp" ) )
- #else
- if ( (strstr(one_file->d_name, ".bmp")) || (strstr(one_file->d_name, ".BMP") ) )
- #endif
- {
- my_list[file_count] = (char *) calloc( strlen(global->configDir) + strlen(one_file->d_name) + 16, sizeof(char) );
- if ( my_list[file_count] )
- {
- sprintf(my_list[file_count], "%s/%s", global->configDir, one_file->d_name);
- file_count++;
- }
- }
-
- one_file = readdir(game_dir);
- }
-
- closedir(game_dir);
- *bitmaps_found = file_count;
- if (file_count < 1)
- {
- free(my_list);
- my_list = NULL;
- }
- return my_list;
-}
-
-
-
-
-
-BITMAP *create_gradient_strip (const gradient *grad, int length)
-{
- BITMAP *strip;
- int color;
- int currLine;
-
- strip = create_bitmap (1, length);
- if (! strip)
- return NULL;
-
- clear_to_color (strip, BLACK);
-
- for (currLine = 0;currLine < length; currLine++)
- {
- color = gradientColorPoint (grad, length, currLine);
- putpixel (strip, 0, currLine, color);
- }
-
- return (strip);
-}
-
-
-
-
-int gradientColorPoint (const gradient *grad, double length, double line)
-{
- int pointCount = 0;
- double point = line / length;
- int color;
-
- for (pointCount = 0; (point >= grad[pointCount].point) && (grad[pointCount].point != -1); pointCount++) { }
- pointCount--;
-
- if (pointCount == -1)
- {
- color = makecol (grad[0].color.r, grad[0].color.g, grad[0].color.b);
- }
- else if (grad[pointCount + 1].point == -1)
- {
- color = makecol (grad[pointCount].color.r, grad[pointCount].color.g, grad[pointCount].color.b);
- }
- else
- {
- double i = (point - grad[pointCount].point) / (grad[pointCount + 1].point - grad[pointCount].point);
- int r = (int)(interpolate (grad[pointCount].color.r, grad[pointCount + 1].color.r, i));
- int g = (int)(interpolate (grad[pointCount].color.g, grad[pointCount + 1].color.g, i));
- int b = (int)(interpolate (grad[pointCount].color.b, grad[pointCount + 1].color.b, i));
-
- color = makecol (r, g, b);
- }
-
- return (color);
-}
-
-
-
-
-
-/*****************************************************************************
- * colorDistance
- *
- * Treat two color values as 3D vectors of the form <r,g,b>.
- * Compute the scalar size of the difference between the two vectors.
- * *****************************************************************************/
-double colorDistance (int col1, int col2)
-{
- double distance;
- int col1r, col1g, col1b;
- int col2r, col2g, col2b;
-
- col1r = getr (col1);
- col1g = getg (col1);
- col1b = getb (col1);
- col2r = getr (col2);
- col2g = getg (col2);
- col2b = getb (col2);
-
- // Treat the colour-cube as a space
- distance = vector_length_f ((float)(col1r - col2r), (float)(col1g - col2g), (float)(col1b - col2b));
-
- return (distance);
-}
-
-
-
-
-void Clear_Weapons()
-{
- memset(weapon, 0, WEAPONS * sizeof(WEAPON));
- memset(naturals, 0, NATURALS * sizeof(WEAPON));
- memset(item, 0, ITEMS * sizeof(ITEM));
-
-}
-
-
-
-int Display_Tank_Bitmap(ENVIRONMENT *env, int xpos, int ypos, void *image_number)
-{
- double *temp_number = (double *) image_number;
- int my_number = (int) *temp_number;
- int use_tank_bitmap, use_turret_bitmap;
- BITMAP *dest = env->db;
- GLOBALDATA *global = env->_global;
-
- switch (my_number)
- {
- case CLASSIC_TANK:
- use_tank_bitmap = 8;
- use_turret_bitmap = 1;
- break;
- case BIGGREY_TANK:
- use_tank_bitmap = 9;
- use_turret_bitmap = 2;
- break;
- case T34_TANK:
- use_tank_bitmap = 10;
- use_turret_bitmap = 3;
- break;
- case HEAVY_TANK:
- use_tank_bitmap = 11;
- use_turret_bitmap = 4;
- break;
- case FUTURE_TANK:
- use_tank_bitmap = 12;
- use_turret_bitmap = 5;
- break;
- case UFO_TANK:
- use_tank_bitmap = 13;
- use_turret_bitmap = 6;
- break;
- case SPIDER_TANK:
- use_tank_bitmap = 14;
- use_turret_bitmap = 7;
- break;
- case BIGFOOT_TANK:
- use_tank_bitmap = 15;
- use_turret_bitmap = 8;
- break;
- case MINI_TANK:
- use_tank_bitmap = 16;
- use_turret_bitmap = 8;
- break;
- default:
- use_tank_bitmap = 0;
- use_turret_bitmap = 0;
- break;
-
- }
-
- draw_sprite(dest, global->tank[use_tank_bitmap], xpos, ypos);
- draw_sprite(dest, global->tankgun[use_turret_bitmap], xpos, ypos - TANKHEIGHT + 5);
-
- return TRUE;
-
-
+ char** my_list;
+ struct dirent* one_file;
+ int32_t file_count = 0;
+ DIR* game_dir;
+
+ my_list = (char**)calloc(256, sizeof(char*));
+ if (! my_list)
+ return nullptr;
+
+
+ game_dir = opendir(env.configDir);
+ if (! game_dir) {
+ free(my_list);
+ return nullptr;
+ }
+
+ one_file = readdir(game_dir);
+ while ( (one_file) && (file_count < 256) ) {
+ // check to see if this is a save game file
+#ifdef ATANKS_IS_LINUX
+ if ( strcasestr( one_file->d_name, ".bmp" ) ) {
+#else
+ if ( (strstr(one_file->d_name, ".bmp"))
+ || (strstr(one_file->d_name, ".BMP")) ) {
+#endif // ATANKS_IS_LINUX
+ size_t nLen = strlen(env.configDir)
+ + strlen(one_file->d_name) + 16;
+ my_list[file_count] = (char*)calloc(nLen + 1, sizeof(char) );
+ if ( my_list[file_count] ) {
+ snprintf(my_list[file_count], nLen, "%s/%s", env.configDir, one_file->d_name);
+ file_count++;
+ }
+ }
+
+ one_file = readdir(game_dir);
+ }
+
+ closedir(game_dir);
+ *bitmaps_found = file_count;
+ if (file_count < 1) {
+ free(my_list);
+ my_list = nullptr;
+ }
+
+ return my_list;
}
diff --git a/src/files.h b/src/files.h
index 5fbd485..a71dd23 100644
--- a/src/files.h
+++ b/src/files.h
@@ -2,89 +2,48 @@
#define FILE_HANDLING_HEADER_
#define MAX_CONFIG_LINE 128
-#define MAX_INSULT_LINE 256
+#define MAX_INSULAND_LINE 256
-#define NO_STAGE 0
-#define GLOBAL_STAGE 1
-#define ENVIRONMENT_STAGE 2
-#define PLAYER_STAGE 3
+#include "debug.h"
+
+#ifndef HAS_DIRENT
+# if defined(ATANKS_IS_MSVC)
+# include "extern/dirent.h"
+# else
+# include <dirent.h>
+# endif // Linux
+# define HAS_DIRENT 1
+#endif //HAS_DIRENT
-#include <dirent.h>
#include "globaldata.h"
#include "environment.h"
#include "text.h"
+/* Global path buffer */
+extern char path_buf[PATH_MAX + 1];
-int Save_Game(GLOBALDATA *global, ENVIRONMENT *env);
-int Load_Game(GLOBALDATA *global, ENVIRONMENT *env);
-int Check_For_Saved_Game(GLOBALDATA *global);
-/*
-Copy the atanks config file from HOME_DIR to
-HOME_DIR/.atanks
-*/
-int Copy_Config_File(GLOBALDATA *global);
-
-// Make sure there is a music folder in .atanks
-int Create_Music_Folder(GLOBALDATA *global);
-
-
-void renderTextLines (GLOBALDATA *global, ENVIRONMENT *env,
- TEXTBLOCK *lines, int scrollOffset,
- const FONT* fnt, const int spacing );
-
-void scrollTextList (GLOBALDATA *global, ENVIRONMENT *env,
- TEXTBLOCK *lines);
-int draw_circlesBG (GLOBALDATA *global, BITMAP *dest, int x, int y, int width, int height, bool image);
+bool Save_Game();
+bool Load_Game();
+bool Check_For_Saved_Game();
+bool Copy_Config_File();
-void drawMenuBackground (GLOBALDATA *global, ENVIRONMENT *env, int itemType, int tOffset, int numItems);
+// Make sure there is a music folder in .atanks
+bool Create_Music_Folder();
+void scrollTextList(TEXTBLOCK* lines);
void flush_inputs();
+bool Load_Weapons_Text();
-int Load_Weapons_Text(GLOBALDATA *global);
-
-// char *Get_Random_Quote(GLOBALDATA *global);
#ifdef MACOSX
-int Filter_File( struct dirent *my_file );
+ int Filter_File( struct dirent *my_file );
#else
-int Filter_File( const struct dirent *my_file );
+ int Filter_File( const struct dirent *my_file );
#endif
-struct dirent ** Find_Saved_Games(GLOBALDATA *global, int *num_files_found);
-
-
-
-char ** Find_Bitmaps(GLOBALDATA *global, int *bitmaps_found);
-
-BITMAP *create_gradient_strip (const gradient *gradient, int length);
-
-int gradientColorPoint (const gradient *grad, double length, double line);
-
-double colorDistance(int col1, int col2);
-
-
-// This function removes all weapon, natural and item data.
-// Should be called before re-loading weapon file.
-void Clear_Weapons();
-
-
-// Draw a tank bitmap on the screen at the given location'
-int Display_Tank_Bitmap(ENVIRONMENT *env, int xpos, int ypos, void *image_number);
-
-// cause natural events to happen
-void doNaturals(GLOBALDATA *global, ENVIRONMENT *env);
-
-// give people the chance to buy items
-bool buystuff (GLOBALDATA *global, ENVIRONMENT *env);
-
-// display the bar at the top of the game screen
-void drawTopBar(GLOBALDATA *global, ENVIRONMENT *env, BITMAP *dest);
-
-int slideLand(GLOBALDATA *global, ENVIRONMENT *env);
-
-void set_level_settings(GLOBALDATA *global, ENVIRONMENT *env);
+dirent** Find_Saved_Games(uint32_t &num_files_found);
-void showRoundEndScoresAt (GLOBALDATA *global, ENVIRONMENT *env, BITMAP *bitmap, int x, int y, int winner);
+char** Find_Bitmaps(int32_t* bitmaps_found);
#endif
diff --git a/src/floattext.cpp b/src/floattext.cpp
index 2c15452..fcdffdd 100644
--- a/src/floattext.cpp
+++ b/src/floattext.cpp
@@ -1,268 +1,389 @@
#include "floattext.h"
-FLOATTEXT::FLOATTEXT (GLOBALDATA *global, ENVIRONMENT *env, char *text, int xpos, int ypos, int color, alignType alignment)
+FLOATTEXT::FLOATTEXT (const char* text_, int32_t xpos, int32_t ypos,
+ double xv_, double yv_, int32_t color_,
+ alignType alignment, eTextSway sway_type,
+ int32_t max_age) :
+ VIRTUAL_OBJECT(),
+ color(color_),
+ pos_x(xpos),
+ pos_y(ypos)
{
- initialise ();
- setEnvironment (env);
- _text = NULL;
- x = (double)xpos;
- y = (double)ypos;
- _current.x = (int)x;
- _current.y = (int)y;
- _current.w = 0;
- _current.h = 0;
- set_pos (xpos, ypos);
- if (text)
- set_text (text);
- else
- set_text(NULL);
- set_color (color);
- _align = alignment;
- _global = global;
- _halfColor = color;
- sway = NO_SWAY;
- original_x = xpos;
- delta_x = 0;
+ int32_t sky_col = TURQUOISE;
+ if ( (pos_x > -1) && (pos_y > MENUHEIGHT)
+ && (pos_x < env.screenWidth) && (pos_y < env.screenWidth) ) {
+ sky_col = getpixel(env.sky, pos_x, pos_y - MENUHEIGHT);
+ }
+
+ halfColor = GetShadeColor(color, true, sky_col);
+ align = alignment;
+ maxAge = max_age;
+
+ if (text_)
+ set_text(text_);
+
+ // The font and thus its height is fixed:
+ dim_cur.h = env.fontHeight + (env.fontHeight % 2);
+
+ x = xpos;
+ y = ypos;
+ dim_cur.x = ROUND(pos_x);
+ dim_cur.y = ROUND(pos_y);
+ set_sway_type(sway_type);
+ set_speed(xv_, yv_);
+
+ // Add to the chain:
+ global.addObject(this);
}
FLOATTEXT::~FLOATTEXT()
{
- if (_env)
- {
- /* To be sure to really remove the full text (might fail due to font differences),
- we "enlarge" the text by 25%: */
- if (_current.w)
- {
- _current.x -= (int)((double)_current.w * 1.125);
- _current.w = (int)((double)_current.w * 1.250);
- }
- if (_current.h)
- {
- _current.y -= (int)((double)_current.h * 1.125);
- _current.h = (int)((double)_current.h * 1.250);
- }
-
- switch (_align)
- {
- case LEFT:
- _env->make_bgupdate (_current.x, _current.y, _current.w, _current.h);
- _env->make_bgupdate (_old.x, _old.y, _old.w, _old.h);
- break;
- case RIGHT:
- _env->make_bgupdate (_current.x - _current.w, _current.y - _current.h, _current.w, _current.h);
- _env->make_bgupdate (_old.x - _old.w, _old.y - _old.h, _old.w, _old.h);
- break;
- case CENTRE:
- _env->make_bgupdate (_current.x - (_current.w / 2), _current.y - (_current.h / 2), _current.w + 2, _current.h + 2);
- _env->make_bgupdate (_old.x - (_old.w / 2), _old.y - (_old.h / 2), _old.w + 2, _old.h + 2);
- break;
- default:
- _env->make_bgupdate (_current.x - _current.w, _current.y - _current.h, 2 * _current.w, 2 * _current.h);
- _env->make_bgupdate (_old.x - _old.w, _old.y - _old.h, 2 * _old.w, 2 * _old.h);
- }
- _env->removeObject (this);
- }
- _global = NULL;
- _env = NULL;
- if (_text)
- {
- free(_text);
- _text = NULL;
- }
+ requireUpdate ();
+ this->update();
+
+ // Only do the final update if the dimensions have been set
+ if ( dim_cur.w > 0 ) {
+ // Update current position
+ int32_t left = LEFT == align ? dim_cur.x
+ : RIGHT == align ? dim_cur.x - dim_cur.w
+ : dim_cur.x - (dim_cur.w / 2);
+ int32_t top = LEFT == align ? dim_cur.y
+ : RIGHT == align ? dim_cur.y - dim_cur.h
+ : dim_cur.y - (dim_cur.h / 2);
+ int32_t right = std::min(env.screenWidth, left + dim_cur.w + 1);
+ int32_t bottom = std::min(env.screenHeight, top + dim_cur.h + 1);
+
+ global.make_bgupdate(left, top, right - left, bottom - top);
+
+ // Update previous position
+ left = LEFT == align ? dim_old.x
+ : RIGHT == align ? dim_old.x - dim_old.w
+ : dim_old.x - (dim_old.w / 2);
+ top = LEFT == align ? dim_old.y
+ : RIGHT == align ? dim_old.y - dim_old.h
+ : dim_old.y - (dim_old.h / 2);
+ right = std::min(env.screenWidth, left + dim_old.w + 1);
+ bottom = std::min(env.screenHeight, top + dim_old.h + 1);
+
+ global.make_bgupdate(left, top, right - left, bottom - top);
+ }
+
+ // Free allocated text
+ if (text) {
+ free(text);
+ text = nullptr;
+ }
+
+ // Take out of the chain:
+ global.removeObject(this);
}
-int FLOATTEXT::applyPhysics()
+void FLOATTEXT::applyPhysics()
{
- VIRTUAL_OBJECT::applyPhysics ();
-
- if ( (!delta_x) && (sway) )
- delta_x = -1;
+ // Opt out early if there is no text to be drawn.
+ if ( (nullptr == text) || (dim_cur.w < 1) )
+ return;
+
+ if (TS_HORIZONTAL == sway) {
+ double x_dist = pos_x - x;
+ double rel_xv = static_cast<double>(sway - std::abs(x_dist))
+ / static_cast<double>(sway) * SIGNd(x_dist); // [-1:+1]
+ if (std::abs(rel_xv) < 0.15)
+ // Only reverse to not stop it.
+ xv *= -1.;
+ else if (SIGN(xv) == SIGN(rel_xv))
+ xv = rel_xv;
+ else
+ xv = -1. * rel_xv;
+ } else if (TS_VERTICAL == sway) {
+ double y_dist = pos_y - y;
+ double rel_yv = static_cast<double>(sway - std::abs(y_dist))
+ / static_cast<double>(sway) * SIGNd(y_dist); // [-1:+1]
+ if (std::abs(rel_yv) < 0.33)
+ // Only reverse to not stop it.
+ yv *= -1.;
+ else if (SIGN(yv) == SIGN(rel_yv))
+ yv = rel_yv;
+ else
+ yv = -1. * rel_yv;
+ }
+ pos_x += xv;
+ pos_y += yv;
+
+ dim_cur.x = ROUND(pos_x);
+ dim_cur.y = ROUND(pos_y);
+
+ requireUpdate();
+
+ if ( (maxAge != -1) && (++age > maxAge) )
+ destroy = true;
+}
- x += delta_x;
- if (x < original_x - sway)
- delta_x = -delta_x;
- else if (x > original_x + sway)
- delta_x = -delta_x;
- set_pos ((int)x, (int)y);
+/// Little Helper, move it somewhere else if it makes sense to be used elsewhere
+#define SAFE_MAKECOL(r_, g_, b_) makecol( \
+ r_ < 0 ? 0 : r_ > 255 ? 255 : r_, \
+ g_ < 0 ? 0 : g_ > 255 ? 255 : g_, \
+ b_ < 0 ? 0 : b_ > 255 ? 255 : b_)
- age++;
- if ( (maxAge != -1) && (age > maxAge) )
- destroy = TRUE;
- return (0);
+void FLOATTEXT::draw()
+{
+ // Opt out early if there is no text to be drawn.
+ if ( (nullptr == text) || !dim_cur.w)
+ return;
+
+ // If the width is not known, yet, determine it
+ if (1 > dim_cur.w) {
+ dim_cur.w = text_length(font, text) + 3;
+ dim_cur.w += dim_cur.w % 2;
+ }
+
+ // Current position according to alignment:
+ int32_t left = LEFT == align ? dim_cur.x
+ : RIGHT == align ? dim_cur.x - dim_cur.w
+ : dim_cur.x - (dim_cur.w / 2);
+ int32_t top = LEFT == align ? dim_cur.y
+ : RIGHT == align ? dim_cur.y - dim_cur.h
+ : dim_cur.y - (dim_cur.h / 2);
+
+ double shadeFade = 0.75;
+ int32_t frontCol = color;
+ int32_t shadeCol = halfColor;
+ int32_t backCol = color;
+
+ // If either shadowed or fading text is enabled, a background
+ // average colour is needed.
+ if ( (env.shadowedText || env.fadingText)
+ && !global.skippingComputerPlay) {
+ backCol = global.get_avg_bgcolor(left, top, left + dim_cur.w,
+ top + dim_cur.h, xv, yv);
+
+ // If fading text is activated, the front colour must be calculated as well
+ if ( env.fadingText
+ && (maxAge > 0)
+ && (age >= (maxAge / 2)) ) {
+ double calcMax = maxAge / 2;
+ double calcAge = age - calcMax;
+ double frontFade = 1.0 - (calcAge / calcMax);
+
+ shadeFade -= 0.75 * (calcAge / calcMax);
+
+ if (frontFade < 0.) frontFade = 0.;
+ if (shadeFade < 0.) shadeFade = 0.;
+
+ double backFade = 1.0 - frontFade;
+ if (backFade < 0.) backFade = 0.;
+
+ int32_t r = ROUND( (getr(frontCol) * frontFade) + (getr(backCol) * backFade) );
+ int32_t g = ROUND( (getg(frontCol) * frontFade) + (getg(backCol) * backFade) );
+ int32_t b = ROUND( (getb(frontCol) * frontFade) + (getb(backCol) * backFade) );
+ frontCol = SAFE_MAKECOL(r, g, b);
+ } // end of calculating fading values
+
+ // The now current values must be applied to the shadow colour if needed
+ if (env.shadowedText) {
+ double backFade = 1.0 - shadeFade;
+
+ if (backFade < 0.) backFade = 0.;
+
+ int32_t r = ROUND( (getr(shadeCol) * shadeFade) + (getr(backCol) * backFade) );
+ int32_t g = ROUND( (getg(shadeCol) * shadeFade) + (getg(backCol) * backFade) );
+ int32_t b = ROUND( (getb(shadeCol) * shadeFade) + (getb(backCol) * backFade) );
+
+ shadeCol = SAFE_MAKECOL(r, g, b);
+ } // End of calculating shadow values
+ } // End of fading / shadow preparations
+
+ // Eventually print out the text:
+ if (env.shadowedText && !global.skippingComputerPlay)
+ textout_ex (global.canvas, font, text, left + 1, top + 1, shadeCol, -1);
+ textout_ex (global.canvas, font, text, left, top, frontCol, -1);
}
-void FLOATTEXT::setEnvironment(ENVIRONMENT *env)
+void FLOATTEXT::newRound()
{
- if (!_env || (_env != env))
- {
- _env = env;
- _index = _env->addObject (this);
- }
-}
-
+ if (maxAge > 0)
+ age = maxAge + 1;
+}
-void FLOATTEXT::draw(BITMAP *dest)
+// Reset movement to begin neutrally if the text is swaying
+void FLOATTEXT::reset_sway()
{
- // If there is no text then we have nothing to draw
- if (! _text)
- return;
-
- if (_current.w)
- {
- double dFrontFade = 1.0;
- double dShadFade = 0.75;
- int iFrontCol = _color;
- int iShadCol = _halfColor;
- int iBackCol = _color;
-
- // get Background color:
- if ( (_env->dShadowedText > 0.0) || (_env->dFadingText > 0.0) )
- {
- switch (_align)
- {
- case LEFT:
- iBackCol = _env->getAvgBgColor( _current.x, _current.y,
- _current.x + _current.w, _current.y + _current.h,
- xv, yv);
- break;
- case RIGHT:
- iBackCol = _env->getAvgBgColor( _current.x - _current.w, _current.y - _current.h,
- _current.x, _current.y,
- xv, yv);
- break;
- case CENTRE:
- iBackCol = _env->getAvgBgColor( _current.x - (_current.w / 2), _current.y - (_current.h / 2),
- _current.x + (_current.w / 2) + 2,_current.y + (_current.h / 2) + 2,
- xv, yv);
- break;
- }
- }
- if ((maxAge > 0) && (age >= (maxAge / 2)) && (_env->dFadingText > 0.0))
- {
- double dCalcAge = (double)age - (double)(maxAge / 2);
- double dCalcMax = (double)(maxAge / 2);
- dFrontFade = 1.0 - (dCalcAge / dCalcMax);
- dShadFade = 0.75 - (0.75 * (dCalcAge / dCalcMax));
- if (dFrontFade < 0.0) dFrontFade = 0.0;
- if (dFrontFade > 1.0) dFrontFade = 1.0;
- if (dShadFade < 0.0) dShadFade = 0.0;
- if (dShadFade > 1.0) dShadFade = 1.0;
- iFrontCol = newmakecol((getr(iFrontCol) * dFrontFade) + (getr(iBackCol) * (1.0 - dFrontFade)),
- (getg(iFrontCol) * dFrontFade) + (getg(iBackCol) * (1.0 - dFrontFade)),
- (getb(iFrontCol) * dFrontFade) + (getb(iBackCol) * (1.0 - dFrontFade)));
- }
- if (_env->dShadowedText > 0.0)
- iShadCol = newmakecol( (getr(iShadCol) * dShadFade) + (getr(iBackCol) * (1.0 - dShadFade)),
- (getg(iShadCol) * dShadFade) + (getg(iBackCol) * (1.0 - dShadFade)),
- (getb(iShadCol) * dShadFade) + (getb(iBackCol) * (1.0 - dShadFade)));
- if (_align == LEFT)
- {
- if (_env->dShadowedText > 0.0)
- textout_ex (dest, font, _text,
- _current.x + 1, _current.y + 1, iShadCol, -1);
- textout_ex (dest, font, _text,
- _current.x, _current.y, iFrontCol, -1);
- }
- else if (_align == RIGHT)
- {
- if (_env->dShadowedText > 0.0)
- textout_ex (dest, font, _text,
- _current.x - _current.w + 1,
- _current.y - _current.h + 1, iShadCol, -1);
- textout_ex (dest, font, _text,
- _current.x - _current.w,
- _current.y - _current.h, iFrontCol, -1);
- }
- else
- {
- if (_env->dShadowedText > 0.0)
- textout_centre_ex (dest, font, _text,
- _current.x + 1, _current.y + 1, iShadCol, -1);
- textout_centre_ex (dest, font, _text,
- _current.x, _current.y, iFrontCol, -1);
- }
- }
+ xv = 0.;
+ yv = 0.;
+ pos_x = x;
+ pos_y = y;
+ if (TS_HORIZONTAL == sway) {
+ pos_x = x + ((1 + (rand() % (sway / 2))) * (rand() % 2 ? -1 : 1));
+ xv = SIGNd(pos_x - x);
+ } else if (TS_VERTICAL == sway) {
+ pos_y = y;
+ yv = -1.;
+ }
+ dim_cur.x = pos_x;
+ dim_cur.y = pos_y;
}
-
-// Returns TRUE on success or FALSE on failure.
-int FLOATTEXT::set_text(char *text)
+void FLOATTEXT::set_color(int32_t color_)
{
- // int my_length = strlen(text);
- int my_length;
-
- // clean up old text
- if (_text)
- {
- free(_text);
- _text = NULL;
- }
- if (! text)
- return TRUE;
-
- my_length = strlen(text);
- _text = (char *) calloc( my_length + 1, sizeof(char));
- if (! _text)
- return FALSE;
-
- strcpy(_text, text);
- if (my_length)
- _current.w = text_length(font, _text) + 3;
- else
- _current.w = 0;
- _current.h = text_height(font) + 15;
- return TRUE;
+ if (color != color_)
+ color = color_;
+
+ int32_t left = LEFT == align ? dim_cur.x + (dim_cur.w / 2)
+ : dim_cur.x - (dim_cur.w / 2);
+ int32_t top = LEFT == align ? dim_cur.y + (dim_cur.h / 2)
+ : dim_cur.y - (dim_cur.h / 2);
+
+ int32_t sky_col = TURQUOISE;
+ if ( (left > -1) && (top > MENUHEIGHT)
+ && (left < env.screenWidth) && (top < env.screenWidth) ) {
+ sky_col = getpixel(env.sky, left, top - MENUHEIGHT);
+ }
+
+ halfColor = GetShadeColor(color, true, sky_col);
}
-void FLOATTEXT::set_color(int color)
+void FLOATTEXT::set_pos(int32_t xpos, int32_t ypos)
{
- int red = getr(color);
- int green = getg(color);
- int blue = getb(color);
- int iAverage = (red + green + blue) / 3;
-
- _color = color;
-
- if (!(red & 0xc0) && !(green & 0xc0) && !(blue & 0xc0))
- _halfColor = makecol(red | 0x80, green | 0x80, blue | 0x80); // Color is too dark, shadow should be bright
- else
- _halfColor = makecol( red > iAverage?(red / 4):(red / 6),
- green > iAverage?(green / 4):(green / 6),
- blue > iAverage?(blue / 4):(blue / 6));
+ if ( (xpos != x) || (ypos !=y) ) {
+ x = xpos;
+ y = ypos;
+ reset_sway();
+ }
}
-void FLOATTEXT::newRound()
+void FLOATTEXT::set_speed(double xv_, double yv_)
{
- age = maxAge + 1;
+ reset_sway();
+
+ if (TS_HORIZONTAL != sway)
+ xv = xv_;
+
+ if (TS_VERTICAL != sway) {
+ if (yv_ < 0.) {
+ // avoid over-lapping text
+ double mix_it_up = ((rand() % 6) - 3.) / 10.; // [-.3;+.2]
+
+ yv = yv_ + mix_it_up;
+
+ // avoid text that does not move up
+ if (yv > -0.1)
+ yv = -0.1;
+ } else
+ yv = yv_;
+ }
}
-void FLOATTEXT::set_pos(int x, int y)
+
+void FLOATTEXT::set_sway_type(eTextSway sway_type)
{
- _current.x = x;
- _current.y = y;
+ if (sway_type != sway) {
+ sway = sway_type;
+ reset_sway();
+ }
}
+void FLOATTEXT::set_text(const char* text_)
+{
+ if (text && text_ && !strcmp(text, text_))
+ return;
+
+ int new_len = text_ ? strlen(text_) : 0;
+ int old_len = text ? strlen(text) : 0;
+
+ // clean up old text
+ if (text && old_len)
+ memset(text, 0, old_len * sizeof(char));
+
+ // reallocate new text
+ if (!text || (new_len > old_len)) {
+ char* new_text = (char*)realloc(text, (new_len + 1) * sizeof(char));
+ if (new_text) {
+ text = new_text;
+ memset(text, 0, (new_len + 1) * sizeof(char));
+ } else {
+ cerr << "Unable to allocate " << ( (new_len + 1) * sizeof(char));
+ cerr << " bytes for new text" << endl;
+ return;
+ }
+ }
+
+ // Copy new text if any. If not, _text is { 0x0 } already.
+ if (new_len) {
+ strncpy(text, text_, new_len);
+ dim_cur.w = -1; // draw() must determine width
+ } else
+ // Width must be reset
+ dim_cur.w = 0;
+}
+
-void FLOATTEXT::set_speed(double x_speed, double y_speed)
+/// @param[in] do_lighten If true, the colour is made brighter if the shade colour
+/// would be too dark to make a difference.
+/// @param[in] bg_colour If not PINK, the background colour is taken into account
+/// and the result darkened or lightened more according to @a do_lighten
+int32_t GetShadeColor(int32_t colour, bool do_lighten, int32_t bg_colour)
{
- double mix_it_up; // avoid over-lapping text
-
- mix_it_up = rand() % SPEED_RANGE; // (0-5)
- mix_it_up -= 3.0; // -3 to +2
- mix_it_up /= 10.0; // -0.3 to + 0.2
- xv = x_speed;
- yv = y_speed + mix_it_up;
- if ( (yv == 0.0) && (y_speed < 0.0) )
- yv = -0.1; // avoid text that does not move
+ int32_t r = getr(colour), g = getg(colour), b = getb(colour);
+ float h, s, v;
+
+ if (do_lighten) {
+ // Be sure something can be done with near black colours
+ if ((r < 0x20) && (g < 0x20) && (b < 0x20)) {
+ r = 0x28;
+ g = 0x28;
+ b = 0x28;
+ }
+ }
+
+ rgb_to_hsv(r, g, b, &h, &s, &v);
+
+ if (do_lighten) {
+ if (s < 0.10) s = 0.10;
+ if (v < 0.25) v = 0.25;
+ }
+
+ int32_t rn, gn, bn;
+ double s_mod = s > 0.5 ? (s > 0.9 ? 0.33 : 0.5) : (do_lighten ? 1.75 : .75);
+ double v_mod = v > 0.5 ? (v > 0.9 ? 0.33 : 0.5) : (do_lighten ? 1.75 : .75);
+ hsv_to_rgb(h, s * s_mod * (v > 0.75 ? 0.5 : 1.),
+ v * v_mod * (s > 0.75 ? 0.5 : 1.),
+ &rn, &gn, &bn);
+
+ // check to see if this all fits with a possible background
+ if (bg_colour != PINK) {
+ int32_t rb = getr(bg_colour), gb = getg(bg_colour), bb = getb(bg_colour);
+ int32_t rm = (rn + r) / 2, gm = (gn + g) / 2, bm = (bn + b) / 2;
+
+ if (ABSDISTANCE3(rb, gb, bb, rm, gm, bm) < 0x20) {
+
+ // The middle between the colour and its shade is too near
+ // to the background. Here s_mod/v_mod can be reused:
+ rn = std::round( static_cast<double>(rn)
+ * (rn > r ? std::max(s_mod, v_mod)
+ : std::min(s_mod, v_mod)));
+ gn = std::round( static_cast<double>(gn)
+ * (gn > g ? std::max(s_mod, v_mod)
+ : std::min(s_mod, v_mod)));
+ bn = std::round( static_cast<double>(bn)
+ * (bn > b ? std::max(s_mod, v_mod)
+ : std::min(s_mod, v_mod)));
+ // Do not overflow:
+ if (rn > 255) rn = 255;
+ if (gn > 255) gn = 255;
+ if (bn > 255) bn = 255;
+ }
+ }
+
+ return makecol(rn, gn, bn);
}
diff --git a/src/floattext.h b/src/floattext.h
index 027dbe6..67db9e4 100644
--- a/src/floattext.h
+++ b/src/floattext.h
@@ -21,75 +21,82 @@
* */
-#include "virtobj.h"
#include "main.h"
#include "environment.h"
+#include "virtobj.h"
-#define newmakecol(r,g,b) makecol((int)(r),(int)(g),(int)(b))
-#define NO_SWAY 0
-#define NORMAL_SWAY 42
+/// @enum eTextSway
+/// @brief Type of text swaying
+enum eTextSway
+{
+ TS_NO_SWAY = 0, //!< Static text that is moving normally
+ TS_VERTICAL = 15, //!< Vertical "bouncing" text like tank health.
+ TS_HORIZONTAL = 22 //!< Horizontal swaying text, if turned on, used for damage and money.
+};
-#define SPEED_RANGE 6
-#ifndef TRUE
-#define TRUE 1
-#endif
-#ifndef FALSE
-#define FALSE 0
-#endif
class FLOATTEXT: public VIRTUAL_OBJECT
- {
- private:
- // empty ctor, copy-ctor and assign operator are private, so the compiler won't create implicit ones!
- // inline const FLOATTEXT& operator= (const FLOATTEXT &sourceText) { return(sourceText); }
-
- char *_text;
- int _color;
- int _halfColor; // for shadowd text!
- int original_x;
- int delta_x;
-
- public:
- int sway;
-
- /* --- constructor --- */
- FLOATTEXT (GLOBALDATA *global, ENVIRONMENT *env, char *text, int xpos, int ypos, int color, alignType alignment);
-
- /* --- destructor --- */
- ~FLOATTEXT ();
- void setEnvironment(ENVIRONMENT *env);
-
- inline virtual void initialise ()
- {
- VIRTUAL_OBJECT::initialise ();
- }
-
- int applyPhysics ();
- void draw (BITMAP *dest);
- int set_text (char * text);
- void set_pos (int x, int y);
- void set_color (int color);
- void set_speed(double x_speed, double y_speed);
-
-
- inline virtual int isSubClass (int classNum)
- {
- if (classNum != FLOATTEXT_CLASS)
- return (FALSE);
- //return (VIRTUAL_OBJECT::isSubClass (classNum));
- else
- return (TRUE);
- }
-
- inline virtual int getClass ()
- {
- return (FLOATTEXT_CLASS);
- }
-
- void newRound();
-
- };
+{
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ explicit FLOATTEXT (const char* text_, int32_t xpos, int32_t ypos,
+ double xv_, double yv_, int32_t color_,
+ alignType alignment, eTextSway sway_type,
+ int32_t max_age);
+ ~FLOATTEXT ();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ void applyPhysics ();
+ void draw ();
+ void newRound ();
+ void set_color (int32_t color_);
+ void set_pos (int32_t xpos, int32_t ypos);
+ void set_sway_type(eTextSway sway_type);
+ void set_text (const char* text_);
+
+ eClasses getClass() { return CLASS_FLOATTEXT; }
+
+
+private:
+
+ /* -----------------------
+ * --- Private methods ---
+ * -----------------------
+ */
+
+ void reset_sway ();
+ void set_speed (double xv_, double yv_);
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ int32_t color = SILVER; //!< Foreground colour
+ int32_t halfColor = GREY; //!< Shadow colour
+ double pos_x = 0.;
+ double pos_y = 0.;
+ eTextSway sway = TS_NO_SWAY;
+ char* text = nullptr;
+};
+
+
+// This function returns a shade colour, which
+// is either brighter or darker depending on
+// the given colour and options.
+int32_t GetShadeColor(int32_t colour, bool do_lighten, int32_t bg_colour);
#endif
diff --git a/src/gameloop.cpp b/src/gameloop.cpp
index 943debc..3b6314a 100644
--- a/src/gameloop.cpp
+++ b/src/gameloop.cpp
@@ -1,589 +1,2185 @@
-#include <stdio.h>
+#include <cstdio>
+#include <thread>
+#include <condition_variable>
+#include <cassert>
+
#include "main.h"
-#include "team.h"
#include "files.h"
#include "satellite.h"
#include "update.h"
#include "network.h"
#include "land.h"
+#include "clock.h"
#include "floattext.h"
+#include "tank.h"
#include "explosion.h"
#include "beam.h"
#include "missile.h"
#include "decor.h"
#include "teleport.h"
#include "sky.h"
+#include "sound.h"
#include "gameloop.h"
+#include "player.h"
+#include "aicore.h"
-#ifdef NEW_GAMELOOP
-
-int game (GLOBALDATA *global, ENVIRONMENT *env)
-{
- int humanPlayers = 0;
- int skippingComputerPlay = FALSE;
- int team_won = NO_WIN;
- int my_class;
- int screen_update = TRUE;
- int text_bounce = 0, text_delta = 1;
- VIRTUAL_OBJECT *my_object;
- TANK *my_tank, *a_tank;
- MISSILE *missile;
- TELEPORT *teleport;
- DECOR *decor;
- BEAM *beam;
- EXPLOSION *explosion;
- FLOATTEXT *floattext;
- SATELLITE *satellite = NULL;
- int count;
- int winner = -1, done = FALSE;
- int stuff_happening = FALSE;
- int AI_clock = 0;
- int fire = FALSE;
- int roundEndCount = 0;
- int game_speed = (int) global->frames_per_second * GAME_SPEED / 60;
- int explosion_in_progress = FALSE;
-
- // adjust for Windows
- #ifdef WIN32
- game_speed /= 1000;
- #endif
-
- global->computerPlayersOnly = FALSE;
- // clear surface
- for (count = 0; count < global->screenWidth; count++)
- env->done[count] = 0;
-
- env->newRound();
- env->Set_Wall_Colour();
- global->bIsBoxed = env->Get_Boxed_Mode(global);
- global->dMaxVelocity = global->Calc_Max_Velocity();
-
- for (count = 0; count <global->numPlayers; count++)
- global->players[count]->newRound();
+#include "shop.h"
- // clear floating text
- for (count = 0; count < MAX_OBJECTS; count++)
- {
- if (env->objects[count] && (env->objects[count]->isSubClass(FLOATTEXT_CLASS)))
- ((FLOATTEXT *)env->objects[count])->newRound();
- }
-
- // everyone gets to buy stuff
- buystuff(global, env);
- set_level_settings(global, env);
-
- for (count = 0; count < global->numPlayers; count++)
- {
- global->players[count]->exitShop();
- if ( (global->players[count]->type == HUMAN_PLAYER) ||
- (global->players[count]->type == NETWORK_CLIENT) )
- humanPlayers++;
- if (global->players[count]->tank)
- {
- // global->players[count]->tank->newRound();
- global->players[count]->tank->flashdamage = 0;
- global->players[count]->tank->boost_up_shield();
- }
-
- }
-
- // set_level_settings(global, env);
- env->stage = STAGE_AIM;
- fi = global->updateMenu = TRUE;
- global->window.x = 0;
- global->window.y = 0;
- global->window.w = global->screenWidth - 1;
- global->window.h = global->screenHeight - 1;
+/// Forwarding for function arguments
+class ObjectUpdater;
- // set wind
- if ( (int) env->windstrength != 0)
- env->wind = (float) (rand() % (int)env->windstrength) - (env->windstrength / 2);
- else
- env->wind = 0;
- env->lastwind = env->wind;
-
- // create satellite
- if (env->satellite)
- satellite = new SATELLITE(global, env);
- if (satellite)
- satellite->Init();
-
- // get some mood music
- global->background_music = global->Load_Background_Music();
- if (global->background_music)
- play_sample( (SAMPLE *) global->background_music, 255, 128, 1000, TRUE);
-
- my_tank = env->order[0];
- // init stuff complete. Get down to playing
- while ( (! done) && (global->get_command() != GLOBAL_COMMAND_QUIT) )
- {
- // see how long we have been skipping
- if (skippingComputerPlay)
- {
- // advance clock
- if (global->Check_Time_Changed() )
- AI_clock++;
- if ( (AI_clock > MAX_AI_TIME) && (winner == -1) )
- {
- // in over-time, kill all tanks
- count = 0;
- while (count < global->numPlayers)
- {
- if ( (global->players[count]) &&
- (global->players[count]->tank) )
- global->players[count]->tank->l = 0;
- count++;
- }
- }
- }
-
-
- global->currTank = my_tank;
- // move through all objects, updating them
- explosion_in_progress = FALSE;
- count = 0;
- while (count < MAX_OBJECTS)
- {
- my_object = env->objects[count];
- if (my_object)
- {
- my_class = my_object->getClass();
- if (my_class == DECOR_CLASS)
- {
- decor = (DECOR *) my_object;
- decor->applyPhysics ();
- if (decor->destroy)
- {
- decor->requireUpdate ();
- decor->update ();
- delete decor;
- }
- }
- else if (my_class == EXPLOSION_CLASS)
- {
- explosion = (EXPLOSION *) my_object;
- if (explosion->bIsWeaponExplosion)
- stuff_happening = TRUE;
- explosion->explode ();
- explosion_in_progress = TRUE;
- explosion->applyPhysics ();
- if (explosion->destroy)
- {
- explosion->requireUpdate ();
- explosion->update ();
- delete explosion;
- }
- }
-
- else if (my_class == TELEPORT_CLASS)
- {
- teleport = (TELEPORT *) my_object;
- stuff_happening = TRUE;
- teleport->applyPhysics ();
- if (teleport->destroy)
- {
- teleport->requireUpdate ();
- teleport->update ();
- delete teleport;
- }
- }
- else if (my_class == MISSILE_CLASS)
- {
- TANK *shooting_tank = NULL;
- int angle_to_fire;
-
- missile = (MISSILE *) my_object;
- stuff_happening = TRUE;
- env->am++;
- missile->hitSomething = 0;
- missile->applyPhysics ();
- missile->triggerTest ();
- shooting_tank = missile->Check_SDI(global);
- if (shooting_tank)
- {
- // (shooting_tank->x < missile->x) ? angle_to_fire = 135 : angle_to_fire = 225;
- angle_to_fire = atan2(labs(shooting_tank->y - 10 - missile->y), (missile->x - shooting_tank->x)) * 180 / 3.14159265 + 90;
- new BEAM(global, env, shooting_tank->x, shooting_tank->y - 10, angle_to_fire, SML_LAZER);
- missile->trigger();
- }
- if (missile->destroy)
- {
- missile->requireUpdate ();
- missile->update ();
- delete missile;
- }
- }
- else if (my_class == BEAM_CLASS)
- {
- beam = (BEAM *) my_object;
- // As bots should not target while a laser is shot:
- stuff_happening = TRUE;
- beam->applyPhysics ();
- if (beam->destroy)
- {
- beam->requireUpdate ();
- beam->update ();
- delete beam;
- }
- }
- else if (my_class == FLOATTEXT_CLASS)
- {
- floattext = (FLOATTEXT *) my_object;
- floattext->applyPhysics ();
- if (floattext->destroy)
- {
- floattext->requireUpdate();
- floattext->update();
- delete floattext;
- env->make_fullUpdate(); // ...kill remaining texts!
- }
- }
- } // end of if we have an object
-
- count++;
- } // end of updating envirnoment objects
+/// === Helper functions ===
+static inline bool advance_tank ();
+static inline void change_wind_strength();
+static inline void check_fps (ObjectUpdater* upd);
+static inline void check_overtime (AICore &aicore);
+static inline void check_skiptime ();
+static inline void clear_voices ();
+static inline void check_winner ();
+static inline double colorDistance (int32_t col1, int32_t col2);
+static inline void delete_destroyed (AICore &aicore);
+static inline void do_naturals ();
+static inline void draw_FPS_Counter ();
+static inline void draw_objects (AICore &aicore);
+static inline void draw_eor_scoreboard (); // The [e]nd-[o]f-[r]ound score board
+static inline void draw_mini_scoreboard(); // The ingame mini score board
+ void draw_top_bar ();
+static inline bool explode_tanks ();
+static inline void fire_weapon ();
+static inline void graph_bar (int32_t x, int32_t y, int32_t col,
+ int32_t actual, int32_t max);
+static inline void graph_bar_center (int32_t x, int32_t y, int32_t col,
+ int32_t actual, int32_t max);
+static inline void init_new_round ();
+static inline bool manage_input (AICore &aicore);
+static inline void set_level_settings (LevelCreator* lcr);
+static inline void set_tank_settings ();
+static inline void update_display ();
+static inline void update_objects (ObjectUpdater* upd);
- if (satellite)
- satellite->Move();
-
- // move land
- slideLand(global, env);
-
- // seems like a good place to update tanks
- for (count = 0; count < global->numPlayers; count++)
- {
- a_tank = global->players[count]->tank;
- if (a_tank)
- {
- // see if we are doing a volly
- if (a_tank->fire_another_shot)
- {
- if (! (a_tank->fire_another_shot % VOLLY_DELAY) )
- a_tank->activateCurrentSelection();
- a_tank->fire_another_shot--;
- }
-
- if (a_tank->flashdamage)
- {
- if (a_tank->flashdamage > 25 || a_tank->l < 1)
- {
- a_tank->damage = 0;
- a_tank->flashdamage = 0;
- a_tank->requireUpdate();
- }
- }
-
- // update position
- a_tank->pen = 0;
- a_tank->applyPhysics();
-
- if ( (a_tank->l <= 0) && (! explosion_in_progress) ) // dead tank
- {
- a_tank->explode();
- a_tank->Give_Credit(global);
- if (a_tank->destroy)
- {
- if ( (a_tank->player) &&
- ( (a_tank->player->type == HUMAN_PLAYER) ||
- (a_tank->player->type == NETWORK_CLIENT) ) )
- {
- humanPlayers--;
- if ( (! humanPlayers) && (global->skipComputerPlay) )
- skippingComputerPlay = TRUE;
- }
-
- a_tank->Destroy();
- if (my_tank == a_tank)
- my_tank = NULL;
- delete a_tank;
- a_tank = NULL;
- }
- }
-
- // if the tank is still alive, adjust its chess-style clock
- if ( (global->max_fire_time > 0.0) &&
- (a_tank == my_tank) &&
- (a_tank) && (a_tank->player->type == HUMAN_PLAYER) &&
- (! env->stage) )
- {
- if (global->Check_Time_Changed() )
- {
- int ran_out_of_time;
- ran_out_of_time = a_tank->player->Reduce_Time_Clock();
- if (ran_out_of_time)
- {
- a_tank->player->skip_me = true;
- a_tank = NULL;
- fire = FALSE;
- my_tank = env->Get_Next_Tank(&fire);
- }
- global->updateMenu = TRUE;
- }
- }
-
- }
- }
-
- #ifdef NETWORK
- // check for input from network
- for (count = 0; count < global->numPlayers; count++)
- {
- if (global->players[count]->type == NETWORK_CLIENT)
- {
- global->players[count]->Get_Network_Command();
- global->players[count]->Execute_Network_Command(FALSE);
- }
- }
- #endif
-
- // drop some naturals
- if (! stuff_happening)
- {
- env->stage = STAGE_AIM;
- doNaturals(global, env);
- if (satellite)
- satellite->Shoot();
- }
-
- // draw top bar
- if (global->updateMenu)
- {
- set_clip_rect(env->db, 0, 0, global->screenWidth - 1,
- MENUHEIGHT - 1);
- drawTopBar(global, env, env->db);
- global->updateMenu = FALSE;
- }
-
-
- set_clip_rect (env->db, 0, MENUHEIGHT,
- (global->screenWidth-1), (global->screenHeight-1));
- if (screen_update)
- {
- blit (env->sky, env->db, global->window.x, global->window.y - MENUHEIGHT, global->window.x, global->window.y, (global->window.w - global->window.x) + 1, (global->window.h - global->window.y) + 1);
- masked_blit (env->terrain, env->db, global->window.x, global->window.y, global->window.x, global->window.y, (global->window.w - global->window.x) + 1, (global->window.h - global->window.y) + 2);
- // drawTopBar(global, env, env->db);
- int iLeft = 0;
- int iRight = global->screenWidth - 1;
- int iTop = MENUHEIGHT;
- int iBottom = global->screenHeight - 1;
- set_clip_rect (env->db, 0, 0, iRight, iBottom);
- vline(env->db, iLeft, iTop, iBottom, env->wallColour); // Left edge
- vline(env->db, iLeft + 1, iTop, iBottom, env->wallColour); // Left edge
- vline(env->db, iRight, iTop, iBottom, env->wallColour); // right edge
- vline(env->db, iRight - 1, iTop, iBottom, env->wallColour); // right edge
- hline(env->db, iLeft, iBottom, iRight, env->wallColour);// bottom edge
- if (global->bIsBoxed)
- hline(env->db, iLeft, iTop, iRight, env->wallColour);// top edge
-
- env->make_update(0, 0, global->screenWidth, global->screenHeight);
- }
- else
- {
- env->replaceCanvas ();
- screen_update = TRUE;
- }
- for (count = 0; count < global->numPlayers; count++)
- {
- if ( (global->players[count]) && (global->players[count]->tank) )
- {
- global->players[count]->tank->framelyAccounting();
- if ( my_tank == global->players[count]->tank )
- {
- text_bounce += text_delta;
- global->players[count]->tank->drawTank(env->db, text_bounce / 4);
- if ( (text_bounce > MAX_TEXT_BOUNCE) ||
- (text_bounce < 1) )
- text_delta = -text_delta;
- }
-
- else
- global->players[count]->tank->drawTank(env->db, 0);
- global->players[count]->tank->update();
- }
- }
-
-
- // draw all this cool stuff
- count = 0;
- while (count < MAX_OBJECTS)
- {
- my_object = env->objects[count];
- if (my_object)
- {
- my_class = my_object->getClass();
- if (my_class == MISSILE_CLASS)
- {
- missile = (MISSILE *) my_object;
- missile->draw (env->db);
- missile->update ();
- }
- else if (my_class == BEAM_CLASS)
- {
- beam = (BEAM *) my_object;
- beam->draw (env->db);
- beam->update ();
- }
- else if (my_class == EXPLOSION_CLASS)
- {
- explosion = (EXPLOSION *) my_object;
- explosion->draw (env->db);
- explosion->update ();
- }
- else if (my_class == TELEPORT_CLASS)
- {
- teleport = (TELEPORT *) my_object;
- if (teleport->object)
- teleport->draw (env->db);
- teleport->update ();
- }
- else if (my_class == DECOR_CLASS)
- {
- decor = (DECOR *) my_object;
- decor->draw (env->db);
- decor->update ();
- }
- else if (my_class == FLOATTEXT_CLASS)
- {
- floattext = (FLOATTEXT *) my_object;
- floattext->draw (env->db);
- floattext->requireUpdate ();
- floattext->update ();
- }
- } // end of if we have an object
- count++;
- } // end of while drawing objects
- if (satellite)
- satellite->Draw(env->db);
- env->do_updates();
-
- // it is possible our tank is dead, make sure we have one
- if (! my_tank)
- {
- my_tank = env->Get_Next_Tank(&fire);
- fire = FALSE;
- }
-
- // get user input
- if ( (my_tank) && (env->stage < STAGE_ENDGAME) )
- {
- PLAYER *my_player;
- int control_result;
-
- global->updateMenu = FALSE;
- my_player = my_tank->player;
- // my_tank->reactivate_shield();
- control_result = my_player->controlTank();
- if (control_result == CONTROL_QUIT)
- done = TRUE;
- else if (control_result == CONTROL_FIRE)
- {
- my_tank = env->Get_Next_Tank(&fire);
- if (global->turntype != TURN_SIMUL)
- fire = TRUE;
- }
- else if ( (control_result == CONTROL_SKIP) && (! humanPlayers) )
- skippingComputerPlay = TRUE;
-
- // if ( (fire) && (env->stage == STAGE_AIM) )
- if (fire)
- {
- env->stage = STAGE_FIRE;
- if (my_tank)
- my_tank->reactivate_shield();
- doLaunch(global, env);
- fire = FALSE;
- // screen_update = TRUE;
- }
- // else
- // screen_update = FALSE;
-
- screen_update = FALSE;
- if (control_result)
- global->updateMenu = TRUE;
- } // end of controling my tank
-
- if ( (! skippingComputerPlay) && (env->stage != STAGE_ENDGAME) )
- {
- #ifdef LINUX
- usleep(game_speed);
- #endif
- #ifdef MACOSX
- usleep(game_speed);
- #endif
- #ifdef WIN32
- Sleep(game_speed);
- #endif
- }
- stuff_happening = FALSE;
- // check for winner
- if (env->stage < STAGE_ENDGAME)
- {
- team_won = Team_Won(global);
- if (team_won == NO_WIN)
- winner = global->Check_For_Winner();
-
- if ( (winner >= 0) || (winner == -2) || (team_won != NO_WIN) )
- env->stage = STAGE_ENDGAME;
- }
-
- if (env->stage == STAGE_ENDGAME)
- {
- roundEndCount++;
- if (roundEndCount >= WAIT_AT_END_OF_ROUND)
- done = TRUE;
- screen_update = FALSE;
- }
- if (global->close_button_pressed)
- done = TRUE;
- } // end of game loop
-
- // credit winners
- if (team_won != NO_WIN)
- winner = team_won;
- global->Credit_Winners(winner);
-
- // display winning results
- while ( keypressed() )
- readkey(); // clear the buffer
-
- // check to see if we have a winner or we just bailed out early
- if ( (winner != -1) && (! global->demo_mode) &&
- (! global->close_button_pressed) )
- {
- showRoundEndScoresAt(global, env, env->db, global->screenWidth / 2,
- global->screenHeight / 2, winner);
- env->do_updates();
-
- while ( (! keypressed() ) && (! global->demo_mode) )
- LINUX_SLEEP;
- readkey();
- }
- // else
- // {
- global->window.x = global->window.y = 0;
- global->window.w = global->screenWidth - 1;
- global->window.h = global->screenHeight - 1;
- set_clip_rect(env->db, 0, 0, global->window.w, global->window.h);
- // }
+
+/// === Static helper values ===
+static int32_t AI_time_change = 0;
+static TANK* curr_tank = nullptr;
+static bool death_substitute= false;
+static bool fire = false;
+static int32_t FPS_counter = 0;
+static int32_t FPS_last = 0;
+static int32_t FPS_pos = 0;
+static int32_t game_us_needed = 0;
+volatile
+static abool_t has_action = ATOMIC_VAR_INIT(false);
+volatile
+static abool_t has_deco = ATOMIC_VAR_INIT(false);
+volatile
+static abool_t has_explosion = ATOMIC_VAR_INIT(false);
+static int32_t human_players = 0;
+static TANK* next_tank = nullptr;
+static bool order_wrapped = false;
+static int32_t score_name_pos = 0;
+static int32_t score_money_pos = 0;
+volatile
+static bool second_passed = false;
+volatile
+static bool show_frame = true;
+static int32_t skip_health = 0;
+static int32_t smkIdx = -1;
+static bool update_screen = true;
+static int32_t us_per_frame = 0;
+volatile
+static int32_t winner = WINNER_NO_WIN;
+
+
+// Note: Here mutexes must be used, condition_variable
+// can not use anything else.
+std::mutex updMutex;
+std::condition_variable updCondition;
+
+
+/// Helper Class to multi-thread object updating
+class ObjectUpdater
+{
+ eClasses class_ = CLASS_COUNT;
+ abool_t doExit;
+ abool_t doStart;
+ abool_t isDone;
+ abool_t isExited;
+ int32_t force_age = 0; // Only used for CLASS_DECOR_SMOKE to force more aging
+
+public:
+
+ explicit ObjectUpdater() :
+ doExit (ATOMIC_VAR_INIT(false)),
+ doStart (ATOMIC_VAR_INIT(false)),
+ isDone (ATOMIC_VAR_INIT(false)),
+ isExited(ATOMIC_VAR_INIT(false))
+ { /* nothing to see here */ }
+
+ /// @brief the main thread handler.
+ void operator()()
+ {
+ vobj_t* next_obj = nullptr;
+ vobj_t* obj = nullptr;
+ TANK* tmp_tank = nullptr;
+
+
+ // The thread is valid until someone tells it to exit
+ while (!doExit.load()) {
+
+ // Sleep until called
+ std::unique_lock<std::mutex> updLock(updMutex);
+ updCondition.wait(updLock, [this]{
+ return (doStart.load(ATOMIC_READ) || doExit.load(ATOMIC_READ));
+ } );
+
+ // Early quit if this is a call to do so:
+ if (doExit.load())
+ continue;
+
+ // If this is the TANK class, yield once if
+ // there is no known explosion, yet.
+ if ((CLASS_TANK == class_) && (false == has_explosion.load()) )
+ // Note: No argument to load(), use the most strict default!
+ std::this_thread::yield();
+
+
+ // Okay, do the updating for this class:
+ global.getHeadOfClass(static_cast<eClasses>(class_), &obj);
+
+ // If this is the floating text class, lock it, or AI
+ // feedback might lead to data races.
+ if (CLASS_FLOATTEXT == class_)
+ global.lockClass(class_);
+
+ while (obj) {
+
+ // Explosions must be known at once:
+ if ( (false == has_explosion.load(ATOMIC_READ))
+ && (CLASS_EXPLOSION == class_) ) {
+ has_explosion.store(true);
+ has_action.store(true);
+ }
+
+ // Make sure we know when stuff is happening!
+ if ( (false == has_action.load(ATOMIC_READ))
+ && ( ( ( (CLASS_BEAM == class_)
+ || (CLASS_MISSILE == class_) )
+ && static_cast<PHYSICAL_OBJECT*>(obj)->isWeapon() )
+ || (CLASS_TELEPORT == class_) ) )
+ has_action.store(true);
+
+ obj->getNext(&next_obj);
+
+ // Trigger Explosion progress
+ if (CLASS_EXPLOSION == class_)
+ static_cast<EXPLOSION*>(obj)->explode();
+
+ // Apply forced smoke ageing
+ if ( (CLASS_DECOR_SMOKE == class_) && force_age)
+ static_cast<DECOR*>(obj)->force_aging(force_age);
+
+ // Do tank special handling
+ if (CLASS_TANK == class_) {
+ tmp_tank = static_cast<TANK*>(obj);
+
+ if (!tmp_tank->destroy) {
+ // Activate next volley shot if applicable
+ if (tmp_tank->fire_another_shot) {
+ if (! (tmp_tank->fire_another_shot % env.volley_delay) ) {
+ has_action.store(true);
+ tmp_tank->activateCurrentSelection();
+ }
+ tmp_tank->fire_another_shot--;
+ }
+
+ // Move and possibly apply pending damage
+ tmp_tank->applyPhysics();
+ tmp_tank->resetFlashDamage();
+ if (tmp_tank->isFlying())
+ has_action.store(true);
+ }
+
+
+ // If the tank is still alive, adjust its chess-style clock
+ if ( !tmp_tank->destroy
+ && (env.maxFireTime > 0)
+ && (tmp_tank == curr_tank)
+ && (HUMAN_PLAYER == tmp_tank->player->type)
+ && (STAGE_AIM == global.stage)
+ && second_passed
+ && tmp_tank->player->reduceClock() ) {
+ tmp_tank->player->skip_me = true;
+ tmp_tank = nullptr;
+ fire = false;
+ curr_tank = next_tank ? next_tank : global.get_next_tank(&order_wrapped);
+ next_tank = nullptr;
+ global.set_curr_tank(curr_tank);
+ }
+
+ tmp_tank = nullptr;
+ } else
+ // All others need to apply physics
+ obj->applyPhysics();
+
+ obj = next_obj;
+ } // End of looping objects of one class
+
+ // If this is the floating text class, unlock it again.
+ if (CLASS_FLOATTEXT == class_)
+ global.unlockClass(class_);
+
+ // All done
+ isDone.store( true, ATOMIC_WRITE);
+ doStart.store(false, ATOMIC_WRITE);
+ } // End of while not exiting
+
+ isExited.store(true, ATOMIC_WRITE);
+ }
+
+ void finish () { doExit.store(true); }
+ bool hasDone () const { return isDone.load(ATOMIC_READ); }
+ bool hasExited () const { return isExited.load(ATOMIC_READ); }
+ void setClass (eClasses aClass_) { class_ = aClass_; }
+ void setForceAge(int32_t ageing_) { force_age = ageing_; }
+ void start () { isDone.store(false, ATOMIC_WRITE);
+ doStart.store(true, ATOMIC_WRITE); }
+};
+
+
+/// The main game loop. Everything happens here.
+void game ()
+{
+ volatile
+ bool done = false;
+ volatile
+ int32_t round_end_count = 0;
+ SATELLITE* satellite = nullptr;
+ const
+ int32_t EndOfRoundFrames = env.frames_per_second * WAIT_AT_END_OF_ROUND;
+ AICore aicore;
+
+ // Check whether the AI Core is in any state to do work:
+ if (!aicore.can_work()) {
+ perror("The AI core could not be initialized");
+ global.set_command(GLOBAL_COMMAND_QUIT);
+ return;
+ }
+
+ // Initialize the new round
+ init_new_round();
+
+ // Only prepare the game if the player did not close
+ // the window in the buy screen
+ if ( (global.get_command() == GLOBAL_COMMAND_QUIT)
+ || (global.isCloseBtnPressed()) )
+ return;
+
+ // Now that everybody has done their shopping, initialize the tanks
+ set_tank_settings();
+
+ // Create the AI Core thread
+ std::thread aithread(std::ref(aicore));
+
+ // Create one updater thread per object class:
+ ObjectUpdater updater[CLASS_COUNT];
+ std::thread* threads[CLASS_COUNT];
+
+ // Set each updater to an individual class, but note the index
+ // of the SMOKE. The smoke decoration index is used to force faster
+ // ageing when rendering a frame takes too long.
+ for (int32_t class_ = 0; class_ < CLASS_COUNT; ++class_) {
+ if (CLASS_DECOR_SMOKE == class_)
+ smkIdx = class_;
+
+ updater[class_].setClass(static_cast<eClasses>(class_));
+ threads[class_] = new std::thread(std::ref(updater[class_]));
+ }
+
+ // create satellite
+ if (env.satellite)
+ satellite = new SATELLITE();
+
+ // get some mood music
+ play_music();
+
+ // Final round preparation
+ global.AI_clock = -1;
+ global.skippingComputerPlay = false;
+ curr_tank = global.order[0];
+ next_tank = nullptr;
+ death_substitute = false;
+ global.set_curr_tank(curr_tank);
+
+ WIN_CLOCK_INIT
+ game_us_reset();
+
+
+ /* ==============================================================
+ * ==== init stuff complete. Get down to playing ====
+ * ==============================================================
+ */
+
+ while ( !done && (global.get_command() != GLOBAL_COMMAND_QUIT) ) {
+
+ // No action and no explosions, yet.
+ has_action.store(false);
+ has_explosion.store(false);
+
+ // For the chess-style clock and the AI clock in skipping computer
+ // play it is necessary to know when a second has passed.
+ second_passed = global.check_time_changed();
+
+
+ // Check overtime and do frame display flipping
+ if (global.skippingComputerPlay) {
+ if (winner != WINNER_DRAW)
+ check_overtime(aicore);
+
+ // End skipping play if the game is over:
+ if (global.stage >= STAGE_SCOREBOARD) {
+ global.skippingComputerPlay = false;
+ show_frame = true;
+ }
+ }
+
+
+ // free used voices if possible.
+ clear_voices();
+
+
+ // Check whether the system is good with the amount of work
+ // for the last frame:
+ check_fps(updater);
+
+
+ // Update all objects
+ update_objects(updater);
+
+
+ // Move that flying saucer
+ if (satellite)
+ satellite->move();
+
+
+ // move land
+ global.slideLand();
+
+
+ // Delete everything that was destroyed
+ delete_destroyed(aicore);
+
+
+ // Drop some naturals if applicable
+ if ( (false == has_action.load(ATOMIC_READ))
+ && !global.skippingComputerPlay
+ && (winner == WINNER_NO_WIN) ) {
+ do_naturals();
+
+ if (satellite)
+ satellite->shoot();
+ }
+
+ // Now update / prepare the display for drawing
+ update_display();
+
+
+ // draw top bar
+ if (global.updateMenu)
+ draw_top_bar();
+
+
+ // draw all this cool stuff
+ draw_objects(aicore);
+
+
+ if (satellite)
+ satellite->draw();
+
+
+ // If requested, show the mini scoreboard
+ if (global.showScoreBoard)
+ draw_mini_scoreboard();
+
+
+ // If wanted, an FPS Counter is shown
+ if (env.showFPS)
+ draw_FPS_Counter();
+
+ // Show the end of round scoreboard if it is needed
+ if ( !global.skippingComputerPlay
+ && (STAGE_SCOREBOARD == global.stage)
+ && (global.get_command() != GLOBAL_COMMAND_QUIT) )
+ draw_eor_scoreboard();
+
+
+ // Now update what has been drawn
+ if (show_frame) {
+ // Draw custom mouse cursor
+ SHOW_MOUSE(global.canvas);
+ global.do_updates();
+ }
+
+ // Let bots do their thinking and perform synchronized input reaction
+ if (curr_tank || (STAGE_SCOREBOARD > global.stage))
+ done = manage_input(aicore);
+
+
+ // Fire selected stuff (if any) and check skipping time
+ if (fire && (STAGE_AIM == global.stage)) {
+ fire_weapon();
+
+ if (global.skippingComputerPlay && order_wrapped)
+ check_skiptime();
+ }
+
+#ifdef NETWORK
+ // check for input from network
+ for (int32_t i = 0; i < env.numGamePlayers; ++i) {
+ if (env.players[i]->type == NETWORK_CLIENT) {
+ env.players[i]->getNetCmd();
+ env.players[i]->executeNetCmd(false, &aicore);
+ }
+ }
+#endif // NETWORK
+
+
+ // Advance to next tank ?
+ if (!advance_tank()
+ && (STAGE_ENDGAME > global.stage) )
+ // In this case at least check for exploding tanks.
+ explode_tanks();
+
+
+ // check for winner
+ if ( (false == has_explosion.load(ATOMIC_READ))
+ && (global.stage < STAGE_ENDGAME) )
+ check_winner();
+
+
+ // manage the end of the round
+ if (global.stage == STAGE_SCOREBOARD) {
+ if ( !explode_tanks()
+ && (false == has_explosion.load())
+ && (false == has_action.load())
+ && ( (++round_end_count >= EndOfRoundFrames)
+ || (WINNER_DRAW == winner) ) ) {
+ if (false == has_deco.load(ATOMIC_READ)) {
+ done = true;
+ global.stage = STAGE_ENDGAME;
+ }
+ } else if ( has_explosion.load() )
+ // recheck for winner
+ check_winner();
+ else if ( has_action.load() )
+ round_end_count = 0;
+ }
+
+
+ // Possibly enter AI skipping mode
+ if ( !human_players && env.skipComputerPlay
+ && !global.skippingComputerPlay
+ && (false == has_action.load())
+ && (global.numTanks > 1)
+ && (STAGE_SCOREBOARD > global.stage)
+ && (WINNER_NO_WIN == winner) ) {
+ global.skippingComputerPlay = true;
+ global.AI_clock = 0;
+ }
+
+
+ // Quit if the close button was pressed
+ if (global.isCloseBtnPressed())
+ done = true;
+ }
+
+ /* ==========================
+ * ==== end of game loop ====
+ * ==========================
+ */
+
+
+ // Stop the AI thread
+ aicore.stop();
+
+ // Stop the updater threads:
+ for (int32_t class_ = 0; class_ < CLASS_COUNT; ++class_)
+ updater[class_].finish();
+ updMutex.lock();
+ updCondition.notify_all(); // <-- That's hilariously simple, isn't it?
+ updMutex.unlock();
+
+ // Wait and join all threads
+ bool has_thread = true;
+ bool has_aicore = true;
+
+ while (has_thread) {
+
+ has_thread = false;
+
+ // AI Core:
+ if (has_aicore) {
+ if (aicore.hasExited()) {
+ aithread.join();
+ has_aicore = false;
+ } else
+ has_thread = true;
+ }
+
+ // Object updaters:
+ for (int32_t class_ = 0; class_ < CLASS_COUNT; ++class_) {
+ if (threads[class_]) {
+ if (updater[class_].hasExited()) {
+ threads[class_]->join();
+ delete threads[class_];
+ threads[class_] = nullptr;
+ } else
+ has_thread = true;
+ }
+ }
+ if (has_thread)
+ std::this_thread::yield();
+ } // end of waiting for all threads to join
+
+
+ // Show end of round score board and credit winner(s)
+ if ((global.get_command() != GLOBAL_COMMAND_QUIT))
+ draw_eor_scoreboard();
+
+ // Ensure full window clipping rectangle
+ set_clip_rect(global.canvas, 0, 0, env.window.w, env.window.h);
// clean up
+
if (satellite)
delete satellite;
- // remove existing tanks etc
- for (count = 0; count < global->numPlayers; count++)
- {
- if (global->players[count]->tank)
- {
- global->players[count]->Reclaim_Shield();
- delete global->players[count]->tank;
- }
- }
+ // remove existing tanks etc
+ for (int32_t i = 0; i < env.numGamePlayers; ++i) {
+ if (env.players[i]->tank) {
+ env.players[i]->reclaimShield();
+ delete env.players[i]->tank;
+ }
+ }
+
+ WIN_CLOCK_REMOVE
+}
+
- return done;
+
+/// ==========================
+/// === Helper functions ===
+/// ==========================
+
+
+static inline bool advance_tank()
+{
+ /* The whole finding of a next tank to have their shot is needed in
+ * two situations during the fire stage:
+ * a) When all action has ceased, because then the turn is over.
+ * b) The current tanks has exploded, a substitute must be found.
+ */
+
+ if ( ( (false == has_action.load(ATOMIC_READ))
+ && (STAGE_FIRE == global.stage) )
+ || ( (global.stage < STAGE_SCOREBOARD)
+ && (!curr_tank || !curr_tank->player || curr_tank->destroy) ) ) {
+
+ bool need_update = false;
+
+ if ( (STAGE_FIRE == global.stage)
+ && !explode_tanks()
+ && (false == has_explosion.load(ATOMIC_READ))
+ && (false == has_action.load(ATOMIC_READ)) ) {
+ // Note: has_action is checked last, so dead tanks can
+ // explode even if others are still falling/flying
+ // around.
+
+ // Normal tank advancement after firing stage
+ global.stage = STAGE_AIM;
+ order_wrapped = false;
+ change_wind_strength();
+
+ if ( !death_substitute ) {
+ if ( next_tank && !next_tank->destroy
+ && (next_tank != curr_tank))
+ curr_tank = next_tank;
+ else
+ curr_tank = global.get_next_tank(&order_wrapped);
+ }
+
+ death_substitute = false;
+ next_tank = nullptr;
+ need_update = true;
+ } else if (!curr_tank || !curr_tank->player || curr_tank->destroy) {
+ // We need a death substitute
+ if (next_tank && !next_tank->destroy)
+ curr_tank = next_tank;
+ else
+ curr_tank = global.get_next_tank(&order_wrapped);
+ death_substitute = true;
+ next_tank = nullptr;
+ need_update = true;
+ }
+
+ if (need_update) {
+ update_screen = true;
+ fire = false;
+ global.updateMenu = true;
+ global.set_curr_tank(curr_tank);
+ return true;
+ }
+ } // End of checking for tank advancement
+
+ return false;
+}
+
+
+static inline void change_wind_strength ()
+{
+ if (!env.windvariation || !env.windstrength)
+ return;
+ else {
+ global.wind = global.lastwind
+ + static_cast<double>(rand () % (env.windvariation * 100)) / 100
+ - static_cast<double>(env.windvariation) / 2.;
+ if (global.wind > (env.windstrength / 2))
+ global.wind = static_cast<double>(env.windstrength) / 2.;
+ else if (global.wind < (-env.windstrength / 2))
+ global.wind = static_cast<double>(env.windstrength) / -2.;
+
+ global.lastwind = global.wind;
+ }
+
+ // make sure game clients have up to date wind data
+# ifdef NETWORK
+ char buffer[64];
+ sprintf(buffer, "WIND %f", global.wind);
+ env.sendToClients(buffer);
+# endif // NETWORK
+}
+
+
+// See whether decorations must be reduced or time can be wasted
+// to achieve the set FPS.
+static inline void check_fps(ObjectUpdater* upd)
+{
+ if ( !global.skippingComputerPlay ) {
+ game_us_needed = game_us_get();
+ int32_t us_unused = us_per_frame - game_us_needed;
+
+ if ( us_unused < 500 ) {
+ // Stop adding decoration:
+ if (!global.hasTooMuchDeco)
+ global.hasTooMuchDeco = true;
+
+ // If more us where needed than available, there is already
+ // too much deco on the screen.
+ // All smoke is forwarded in aging now, so they would get
+ // deleted sooner.
+ if (us_unused < 0) {
+ int32_t agemod = (us_unused / -1000) + 1;
+
+ // Don't age more than 5 frames:
+ if (agemod > 5)
+ agemod = 5;
+ upd[smkIdx].setForceAge(agemod);
+ }
+ } else if ( global.hasTooMuchDeco && (us_unused > 1000) ) {
+ // (Re-)enable deco
+ global.hasTooMuchDeco = false;
+
+ // Normal smoke ageing
+ upd[smkIdx].setForceAge(0);
+ }
+
+ // Sleep what is unused
+ if ( us_unused > 0 ) {
+ USLEEP(us_unused)
+ }
+
+ // Add what has been used until now:
+ game_us_needed += game_us_get();
+ }
+}
+
+
+// Check whether the AI time is up and force a draw if it is.
+// This method does not check whether it is needed and must not
+// be called if global.skippingComputerPlay is false!
+static inline void check_overtime(AICore &aicore)
+{
+ // Check every second whether the AI clock
+ // should be changed:
+ if (second_passed) {
+ global.AI_clock += SIGN(AI_time_change);
+ AI_time_change = 0;
+ global.updateMenu = true;
+ }
+
+ // Skip every other frame
+ show_frame = !show_frame;
+
+ // Kill all tanks if skipping time is over
+ if ( (global.AI_clock >= MAX_AI_TIME)
+ && (winner == WINNER_NO_WIN) ) {
+
+ // Stop the ai first:
+ aicore.stop();
+ while (!aicore.hasExited())
+ std::this_thread::yield();
+
+ // in over-time, kill all tanks
+ TANK* tank = nullptr;
+ global.getHeadOfClass(CLASS_TANK, &tank);
+ while (tank) {
+ // reclaim shield. This is fair, because technically
+ // the bots survive the battle. The tank destruction
+ // is only "for the effect".
+ // And if they bought vengeance items, they'll loose
+ // one now. Expensive enough.
+ if (tank->player)
+ tank->player->reclaimShield();
+ tank->addDamage(nullptr, tank->sh + tank->l + 1);
+ tank->applyDamage();
+ tank->resetFlashDamage();
+ tank->getNext(&tank);
+ }
+
+ global.skippingComputerPlay = false;
+ show_frame = true;
+ global.stage = STAGE_SCOREBOARD;
+ winner = WINNER_DRAW;
+
+ // All action is false, or they won't explode
+ has_action.store( false, ATOMIC_WRITE);
+ has_explosion.store(false, ATOMIC_WRITE);
+
+ // Now make them go bye bye
+ explode_tanks();
+ }
}
-#endif
+static inline void check_skiptime()
+{
+ // Check whether to reset the AI_clock
+ int32_t cur_health = 0;
+ int32_t bots_alive = 0;
+
+ for (int32_t i = 0; i < env.numGamePlayers; ++i) {
+ if ( (env.players[i])
+ && (env.players[i]->tank) ) {
+ cur_health += env.players[i]->tank->l
+ + env.players[i]->tank->sh;
+ ++bots_alive;
+ }
+ }
+
+ int32_t health_delta = skip_health - cur_health;
+
+ if (!skip_health || (health_delta < (bots_alive * 5)))
+ // No (real) damage done, raise AI_Clock
+ ++AI_time_change;
+ else if (health_delta > (bots_alive * 25))
+ // Lots of damage, halve the clock
+ global.AI_clock /= 2;
+ else if (global.AI_clock && (health_delta > (bots_alive * 10)) )
+ // Moderate damage, decrease the AI_clock
+ --AI_time_change;
+ // No else, it would be a little damage and that means
+ // do not change the AI_clock at all.
+ skip_health = cur_health;
+}
+
+
+static inline void clear_voices()
+{
+ // Assume one voice per 2 ms to be usable again
+ /// @todo : This must be substituted by a real direct
+ /// control over the voices used. The auto-mixing
+ /// of allegro 4 is just too inefficient.
+ /// However, this way it is a lot better than without any control...
+ if (game_us_needed >= 1000)
+ global.used_voices -= game_us_needed / 500;
+ else
+ --global.used_voices;
+ if (global.used_voices < 0)
+ global.used_voices = 0;
+}
+
+
+static inline void check_winner()
+{
+ bool all_jedi = true;
+ bool all_sith = true;
+ int32_t player_count = 0;
+ int32_t last_alive = -1;
+
+ for (int32_t i = 0; i < env.numGamePlayers; ++i) {
+ TANK* tank = env.players[i]->tank;
+ if ( tank && tank->l && !tank->destroy && tank->player ) {
+ eTeamTypes team = tank->player->team;
+ if (TEAM_SITH != team) all_sith = false;
+ if (TEAM_JEDI != team) all_jedi = false;
+
+ last_alive = i;
+ player_count++;
+ }
+ }
+
+ if (!player_count) winner = WINNER_DRAW;
+ else if (all_jedi) winner = WINNER_JEDI;
+ else if (all_sith) winner = WINNER_SITH;
+ else if (1 == player_count) winner = last_alive;
+ else winner = WINNER_NO_WIN;
+
+ // End skipping play if a winner is known:
+ if (WINNER_NO_WIN != winner) {
+ if (global.stage < STAGE_SCOREBOARD)
+ global.stage = STAGE_SCOREBOARD;
+ global.skippingComputerPlay = false;
+ show_frame = true;
+ }
+}
+
+
+/*****************************************************************************
+ * colorDistance
+ *
+ * Treat two color values as 3D vectors of the form <r,g,b>.
+ * Compute the scalar size of the difference between the two vectors.
+ * *****************************************************************************/
+double colorDistance (int32_t col1, int32_t col2)
+{
+ int32_t r1 = getr (col1);
+ int32_t g1 = getg (col1);
+ int32_t b1 = getb (col1);
+ int32_t r2 = getr (col2);
+ int32_t g2 = getg (col2);
+ int32_t b2 = getb (col2);
+
+ // Treat the colour-cube as a space
+ return FABSDISTANCE3(r1, g1, b1, r2, g2, b2);
+}
+
+
+static inline void delete_destroyed(AICore &aicore)
+{
+ vobj_t* next_obj = nullptr;
+ vobj_t* obj = nullptr;
+
+ // do not create new FLOATTEXT instance while deletion is in progress
+ aicore.forbidText();
+
+ // Now loop classes and delete destroyed objects
+ for (int32_t class_ = 0; class_ < CLASS_COUNT; ++class_) {
+ // Skip tank class, the tanks must be deleted in their special
+ // function explode_tanks() as there is more to do.
+ if (CLASS_TANK == class_)
+ continue;
+
+ eClasses e_class = static_cast<eClasses>(class_);
+
+ global.getHeadOfClass(e_class, &obj);
+ global.lockClass(e_class);
+
+ while(obj) {
+ obj->getNext(&next_obj);
+
+ // Update object if it is destroyed
+ if (obj->destroy) {
+ obj->requireUpdate();
+ obj->update();
+
+ // For deleting the object, the class must be unlocked,
+ // or we'll hit a deadlock with global.removeObject().
+ global.unlockClass(e_class);
+ delete obj;
+ global.lockClass(e_class);
+ }
+ obj = next_obj;
+ } // End of looping objects of one class
+
+ // Finished:
+ global.unlockClass(e_class);
+ } // End of looping classes
+
+ // Eventually re-allow AICore to create FLOATTEXT instances again
+ aicore.allowText();
+}
+
+
+void do_naturals()
+{
+ if (global.naturals_activated >= 5)
+ return;
+
+ if (env.lightning) {
+ int32_t chance = (600 / env.lightning) + 100;
+
+ if (!(rand () % chance)) {
+ try {
+ new BEAM (nullptr, 1 + (rand () % (env.screenWidth - 2)),
+ MENUHEIGHT + (env.isBoxed ? 1 : 0),
+ ((rand () % 160) + (360 - 80)) % 360,
+ SML_LIGHTNING + (rand () % env.lightning), BT_NATURAL);
+ global.naturals_activated++;
+ return;
+ } catch (...) { /* can't do anything here */ }
+ }
+ } // end of lightning
+
+ // only create meteors and dirt balls if we are not in aim mode on simul turn type
+ if ( (env.turntype == TURN_SIMUL) && (global.stage == STAGE_AIM) )
+ return;
+
+ if (env.meteors) {
+ int32_t chance = (600 / env.meteors) + 100;
+
+ if (!(rand () % chance)) {
+ int32_t ca = ((rand () % 160) + (360 - 80)) % 360;
+ double mxv = env.slope[ca][0] * 5;
+ double myv = env.slope[ca][1] * 5;
+
+ try {
+ new MISSILE(nullptr, 1 + (rand () % (env.screenWidth - 2)),
+ MENUHEIGHT + (env.isBoxed ? 1 : 0), mxv, myv,
+ SML_METEOR + (rand () % env.meteors), MT_NATURAL, 1);
+ global.naturals_activated++;
+ return;
+ } catch (...) { /* can't do anything here */ }
+ }
+ }
+
+ if (env.falling_dirt_balls) {
+ int32_t chance = (600 / env.falling_dirt_balls) + 100;
+
+ if (! (rand() % chance) ) {
+ int ca = ((rand() % 100) + (360 - 80) ) % 360;
+ double mxv = env.slope[ca][0] * 5;
+ double myv = env.slope[ca][1] * 5;
+
+ try {
+ new MISSILE(nullptr, 1 + (rand () % (env.screenWidth - 2)),
+ MENUHEIGHT + (env.isBoxed ? 1 : 0), mxv, myv,
+ DIRT_BALL + ( rand() % env.falling_dirt_balls),
+ MT_NATURAL, 1);
+ global.naturals_activated++;
+ } catch (...) { /* can't do anything here */ }
+ }
+ }
+}
+
+
+static inline void draw_FPS_Counter()
+{
+ ++FPS_counter;
+
+ if (second_passed) {
+ FPS_last = FPS_counter;
+ FPS_counter = 0;
+ }
+
+ int32_t fc = BLUE;
+ int32_t bc = GREY;
+ if (FPS_last < (env.frames_per_second - 5)) {
+ fc = DARK_RED;
+ bc = DARK_GREEN;
+ } else if (FPS_last > (env.frames_per_second + 5)) {
+ fc = TURQUOISE;
+ bc = SILVER;
+ }
+
+ textprintf_ex (global.canvas, font, FPS_pos + 1, MENUHEIGHT + 11,
+ bc, -1, "% 4d FPS", FPS_last);
+ textprintf_ex (global.canvas, font, FPS_pos, MENUHEIGHT + 10,
+ fc, -1, "% 4d FPS", FPS_last);
+
+ global.make_update(FPS_pos - 1, MENUHEIGHT + 5,
+ FPS_pos + 60, MENUHEIGHT + 20);
+
+}
+
+
+static inline void draw_objects(AICore &aicore)
+{
+ vobj_t* obj = nullptr;
+
+ has_deco.store(false, ATOMIC_WRITE);
+ set_clip_rect (global.canvas, 0, MENUHEIGHT,
+ (env.screenWidth-1), (env.screenHeight-1));
+
+ // do not create new FLOATTEXT instance while drawing is in progress
+ aicore.forbidText();
+
+ for (int32_t class_ = 0; class_ < CLASS_COUNT; ++class_) {
+
+ global.getHeadOfClass(static_cast<eClasses>(class_), &obj);
+ while(obj) {
+
+ if (show_frame) {
+ obj->draw();
+ obj->update();
+ }
+
+ if ( (false == has_deco.load(ATOMIC_READ))
+ && ( (CLASS_DECOR_DIRT == class_)
+ || (CLASS_DECOR_SMOKE == class_) ) )
+ has_deco.store(true, ATOMIC_WRITE);
+
+ obj->getNext(&obj);
+ } // End of looping objects
+
+ } // End of looping classes
+
+ // Eventually re-allow AICore to create FLOATTEXT instances again
+ aicore.allowText();
+}
+
+
+/// @brief the ingame mini score board
+static inline void draw_mini_scoreboard()
+{
+ int32_t line = MENUHEIGHT + 2;
+
+ for (int i = 0; i < env.maxNumTanks; ++i) {
+ PLAYER *player = env.playerOrder[i];
+
+ assert(player && "ERROR: player in playerOrder is nullptr!");
+
+ if (player) {
+ int32_t color = player->color;
+ const char* money = Add_Comma(player->money);
+ const char* name = player->getName();
+ const char* team = player->getTeamName();
+ int32_t mid_y = line + (env.fontHeight / 2) + 1;
+
+ // Strike through dead players (BLACK background *before* the name)
+ if (!player->tank || player->tank->destroy)
+ hline(global.canvas, 17, mid_y + 1, 276, BLACK);
+
+ // Display team
+ textprintf_ex (global.canvas, font, 16, line + 1, BLACK, -1,
+ "(%-7s)", team);
+ textprintf_ex (global.canvas, font, 15, line, color, -1,
+ "(%-7s)", team);
+
+ // Display player indicator
+ player->drawIndicator(score_name_pos - env.fontHeight - 2,
+ line + 1, env.fontHeight - 3);
+
+ // Display name
+ textprintf_ex (global.canvas, font, score_name_pos + 1, line + 1,
+ BLACK, -1, "%s", name);
+ textprintf_ex (global.canvas, font, score_name_pos, line,
+ color, -1, "%s", name);
+
+ // Display money
+ textprintf_ex (global.canvas, font, score_money_pos + 1, line + 1,
+ BLACK, -1, "$%s", money);
+ textprintf_ex (global.canvas, font, score_money_pos, line,
+ color, -1, "$%s", money);
+
+ // Draw an arrow indicating the current player
+ if (curr_tank == player->tank) {
+ hline(global.canvas, 4, mid_y, 12, color);
+ hline(global.canvas, 5, mid_y + 1, 13, BLACK);
+ }
+
+ // Strike through dead players (color)
+ if (!player->tank || player->tank->destroy)
+ hline(global.canvas, 16, mid_y, 275, color);
+
+ line += env.fontHeight;
+ }
+ }
+
+ global.make_update(0, MENUHEIGHT, 300, line);
+}
+
+
+/// @brief This method draws the top bar with all current information
+void draw_top_bar ()
+{
+ TANK* tank = global.get_curr_tank();
+ PLAYER* player = tank ? tank->player : nullptr;
+ const char* name = player ? player->getName () : nullptr;
+ const char* team_name = player ? player->getTeamName() : nullptr;
+ int32_t color = player ? player->color : BLACK;
+ int32_t time_to_fire = player ? player->time_left_to_fire : 0;
+ int32_t y1 = 0;
+ int32_t y2 = 13;
+ int32_t y3 = 26;
+ static
+ int32_t change_colour = RED;
+
+ // Copy empty top bar background
+ global.updateMenu = false;
+
+ // copy backdrop:
+ set_clip_rect(global.canvas, 0, 0, env.screenWidth - 1, MENUHEIGHT - 1);
+ blit (env.gfxData.topbar, global.canvas, 0, 0, 0, 0, env.screenWidth, MENUHEIGHT);
+
+ // Fill in player info if possible :
+ if (player) {
+ // name is first, as always
+ textout_ex (global.canvas, font, name, 2, y1 + 1,
+ GetShadeColor(color, true, PINK), -1);
+ textout_ex (global.canvas, font, name, 1, y1,
+ color, -1);
+ textprintf_ex(global.canvas, font, 1, y2, BLACK, -1,
+ "%s", env.ingame->Get_Line(18));
+
+ // Display set angle. 0 is directly left, 180 points directly right
+ graph_bar_center (50, y2 + 4, color, -(tank->a - 180) / 2, 180 / 2);
+ textprintf_ex(global.canvas, font, 150, y2, BLACK, -1,
+ "%d", GET_DISP_ANGLE(tank->a));
+
+ // Display set power
+ graph_bar (50, y3 + 4, color, (tank->p) / (MAX_POWER/90), 90);
+ textprintf_ex(global.canvas, font, 1, y3, BLACK, -1,
+ "%s", env.ingame->Get_Line(19));
+ textprintf_ex(global.canvas, font, 150, y3, BLACK, -1,
+ "%d", tank->p);
+
+ // Display the team name
+ textprintf_ex(global.canvas, font, 200, y3, BLACK, -1,
+ "%s: %s", env.ingame->Get_Line(20), team_name);
+
+ // Display weapon if chosen
+ if (tank->cw < WEAPONS) {
+ int32_t amt = tank->player->nm[tank->cw]
+ / weapon[tank->cw].getDelayDiv();
+ int32_t col = BLACK;
+
+ // Forcibly changed weapons (previous out of ammo) flash red/white
+ // on each update.
+ if (tank->player->changed_weapon) {
+ col = change_colour;
+
+ if (RED == change_colour)
+ change_colour = WHITE;
+ else
+ change_colour = RED;
+ }
+ textprintf_ex(global.canvas, font, 180, y1, col, -1,
+ "%s: %d", weapon[tank->cw].getName(), amt);
+ } else
+ textprintf_ex(global.canvas, font, 180, y1, BLACK, -1,
+ "%s: %d", item[tank->cw - WEAPONS].getName(),
+ tank->player->ni[tank->cw - WEAPONS]);
+
+ // Show the weapon / item icon
+ draw_sprite(global.canvas, env.stock[ (tank->cw > 0) ? tank->cw : 1], 700, 1);
+
+ // Eventually print out money and Fuel
+ textprintf_ex(global.canvas, font, 386, y1, BLACK, -1,
+ "$%s", Add_Comma(tank->player->money));
+ textprintf_ex(global.canvas, font, 386, y2, BLACK, -1,
+ "%s: %d", env.ingame->Get_Line(21),
+ tank->player->ni[ITEM_FUEL]);
+ } // End of displaying player info
+
+
+ // Display round information
+ textprintf_ex(global.canvas, font, 500, y1, BLACK, -1,
+ "%s %d/%d", env.ingame->Get_Line(12),
+ env.rounds - global.currentround, env.rounds);
+
+ // If a tank status is set, display it
+ if (global.tank_status[0])
+ textprintf_ex(global.canvas, font, 350, y3, global.tank_status_colour,
+ -1, "%s", global.tank_status);
+
+ // Show the wind blowing (if configured)
+ if (env.windstrength > 0) {
+ textprintf_ex(global.canvas, font, 500, y2, BLACK, -1,
+ "%s", env.ingame->Get_Line(22));
+
+ int32_t wcol1 = global.wind > 0 ? 1 : 0;
+ int32_t wcol2 = global.wind < 0 ? 1 : 0;
+
+ rect (global.canvas, 540, y2 + 4,
+ 542 + (env.windstrength * 4), y2 + 12, BLACK);
+ rectfill(global.canvas, 541 + (env.windstrength * 2), y2 + 5,
+ 541 + (global.wind * 4) + (env.windstrength * 2), y2 + 11,
+ makecol(200 * wcol1, 200 * wcol2, 0) );
+ }
+
+ // Print AI Skip time or chess style clock if set
+ if ( (global.AI_clock > -1) && (global.AI_clock <= MAX_AI_TIME) )
+ textprintf_ex(global.canvas, font, 500, y3, BLACK, -1,
+ "AI Time: %d", MAX_AI_TIME - global.AI_clock);
+ else if (env.maxFireTime)
+ textprintf_ex(global.canvas, font, 500, y3, BLACK, -1,
+ "Time: %d", time_to_fire);
+
+ // Update and be done
+ global.stopwindow = 1;
+ global.make_update (0, 0, env.screenWidth, MENUHEIGHT);
+ global.stopwindow = 0;
+}
+
+
+/// Let all tanks explode that are destroyed
+/// @return true if at least one tank goes bye bye
+static inline bool explode_tanks()
+{
+ // return if something is exploding already
+ if (has_explosion.load(ATOMIC_READ))
+ return true; // true, because an explosion is present.
+
+ TANK* tank = nullptr;
+ TANK* tmp = nullptr;
+ bool res = false;
+ bool tanks_left = false;
+
+ // Check how many tanks are still alive and whether they are from
+ // different teams
+ bool all_jedi = true;
+ bool all_sith = true;
+ bool all_jedi_alive = true;
+ bool all_sith_alive = true;
+ bool do_explode = false;
+
+ global.getHeadOfClass(CLASS_TANK, &tank);
+
+ while (tank) {
+ // Look for teams for any tanks including exploding ones
+ if (tank->player && (TEAM_JEDI != tank->player->team) )
+ all_jedi = false;
+ if (tank->player && (TEAM_SITH != tank->player->team) )
+ all_sith = false;
+
+ // Look for alive tanks
+ if ( (tank->l > 0) && !tank->destroy) {
+ tanks_left = true;
+
+ // Note down if alive tanks are from other teams
+ if (tank->player && (TEAM_JEDI != tank->player->team) )
+ all_jedi_alive = false;
+ if (tank->player && (TEAM_SITH != tank->player->team) )
+ all_sith_alive = false;
+
+ } else
+ do_explode = true;
+
+ tank->getNext(&tank);
+ }
+
+ // Return if no tank is about to explode:
+ if (!do_explode)
+ return false;
+
+ // If tanks are left that are only jedi or sith, vengeance is disallowed:
+ bool allow_vengeance = (tanks_left && !all_jedi && !all_sith);
+
+ // Now explode what has to go
+ global.getHeadOfClass(CLASS_TANK, &tank);
+ while (tank) {
+
+ tank->getNext(&tmp);
+
+ // If the tank is now destroyed, let it explode
+ if (tank->destroy) {
+
+ /* Second level of vengeance allowance:
+ * If, for example, three tanks are left, one neutral, and two of
+ * the same team. And the neutral and one team tank are about to
+ * explode. Then the team tank must not trigger vengeance, because
+ * their team already won. However, the neutral tank might surely
+ * detonate in a firestorm and maybe force a draw.
+ */
+ bool do_vengeance = allow_vengeance;
+ if ( do_vengeance
+ && ( (all_jedi_alive && (TEAM_JEDI == tank->player->team))
+ || (all_sith_alive && (TEAM_SITH == tank->player->team)) ) )
+ do_vengeance = false;
+
+ tank->explode(do_vengeance);
+
+ has_action.store( true, ATOMIC_WRITE); // For sure.
+ has_explosion.store(true, ATOMIC_WRITE); // It'll go now.
+ res = true;
+
+ // count human player reduction
+ if ( (tank->player)
+ && ( (HUMAN_PLAYER == tank->player->type)
+ || (NETWORK_CLIENT == tank->player->type) ) )
+ --human_players;
+
+ // Now the tank has to be removed, but take care
+ // of the current and next tank if they are this
+ if (curr_tank == tank)
+ curr_tank = nullptr;
+ if (next_tank == tank)
+ next_tank = nullptr;
+
+ // Remove from order array
+ global.removeTank(tank);
+
+ delete tank;
+ tank = nullptr;
+ } // End of handling tank destruction
+
+ tank = tmp;
+ tmp = nullptr;
+ }
+
+ return res;
+}
+
+
+/// @brief Wrapper to automatically trigger firing in simultaneous mode, too.
+static inline void fire_weapon()
+{
+ assert( (STAGE_AIM == global.stage)
+ && " ERROR: fire_weapon() called, but not STAGE_AIM!");
+ global.stage = STAGE_FIRE;
+
+ if (curr_tank && !curr_tank->destroy) {
+ has_action.store(true);
+ curr_tank->reactivate_shield();
+ curr_tank->simActivateCurrentSelection();
+ }
+
+ // Have everything launched in simultaneous mode
+ if (TURN_SIMUL == env.turntype) {
+ TANK* tank = nullptr;
+
+ global.getHeadOfClass(CLASS_TANK, &tank);
+ while (tank) {
+ if (tank->player->skip_me)
+ tank->player->skip_me = false;
+ else {
+ has_action.store(true);
+ tank->activateCurrentSelection();
+ }
+ tank->player->time_left_to_fire = env.maxFireTime;
+ tank->getNext(&tank);
+ }
+
+ assert( (STAGE_FIRE == global.stage)
+ && "ERROR: global.stage changed illegally!");
+ }
+
+ fire = false;
+}
+
+
+// Draws indication bar
+static inline void graph_bar(int32_t x, int32_t y, int32_t col, int32_t actual,
+ int32_t max_)
+{
+ rect (global.canvas, x, y, x + max_ + 2, y + 8, BLACK);
+ rectfill (global.canvas, x + 1, y + 1, x + 1 + actual, y + 7, col);
+}
+
+
+// Draws indication bar - centered
+static inline void graph_bar_center(int32_t x, int32_t y, int32_t col,
+ int32_t actual, int32_t max_)
+{
+ rect (global.canvas, x, y,
+ x + max_ + 2, y + 8, BLACK);
+ rectfill (global.canvas, x + 1 + max_ / 2, y + 1,
+ x + 1 + actual + max_ / 2, y + 7, col);
+}
+
+
+// do new round preparations
+static inline void init_new_round()
+{
+ // First env,
+ env.newRound();
+
+ // then the players in case the campaign mode rise kicks in
+ for (int32_t i = 0; i < env.numGamePlayers; ++i)
+ env.players[i]->newRound();
+
+ // finally global, so campaign mode round is changed after the players.
+ global.newRound();
+
+ // clear floating text
+ FLOATTEXT* txt = nullptr;
+ global.getHeadOfClass(CLASS_FLOATTEXT, &txt);
+ while (txt) {
+ txt->newRound();
+ txt->getNext(&txt);
+ }
+
+ // Initialize the static inline global values, so no old data from a previous
+ // run is carried over. (Unless this is wanted, of course)
+ AI_time_change = 0;
+ curr_tank = nullptr;
+ fire = false;
+ human_players = 0;
+ us_per_frame = 1000000 / env.frames_per_second;
+ next_tank = nullptr;
+ order_wrapped = false;
+ second_passed = false;
+ show_frame = true;
+ skip_health = 0;
+ update_screen = true;
+ winner = WINNER_NO_WIN;
+ game_us_reset();
+
+ // everyone gets to buy stuff. While the (human)
+ // player(s) are at it, generate the next level
+ // in the background.
+ LevelCreator lvlCreator;
+ std::thread gen_land_thread(std::ref(lvlCreator));
+ shop(&lvlCreator);
+ gen_land_thread.join();
+
+ // End each players shopping and count the number of human players
+ for (int32_t i = 0; i < env.numGamePlayers; ++i) {
+ env.players[i]->exitShop();
+ if ( (env.players[i]->type == HUMAN_PLAYER)
+ || (env.players[i]->type == NETWORK_CLIENT) )
+ human_players++;
+ }
+
+ // set wind
+ if (env.windstrength)
+ global.wind = (rand() % env.windstrength) - (env.windstrength / 2);
+ else
+ global.wind = 0;
+ global.lastwind = global.wind;
+
+ // finalize preparation
+ fi = 1;
+ global.stage = STAGE_AIM;
+ global.updateMenu = true;
+ env.window = BOX(0, 0, env.screenWidth - 1, env.screenHeight - 1);
+}
+
+
+/// @brief Wrapper to combine both human input and AI actions.
+static inline bool manage_input(AICore &aicore)
+{
+ bool done = false;
+
+ if ( curr_tank && curr_tank->player ) {
+ global.updateMenu = false;
+ PLAYER* player = curr_tank->player;
+ bool can_fire = !( has_action.load(ATOMIC_READ)
+ || has_explosion.load(ATOMIC_READ) );
+ int32_t result = player->controlTank(&aicore, can_fire);
+
+ if (CONTROL_QUIT == result)
+ done = true;
+ else if (CONTROL_FIRE == result) {
+ has_action.store(true);
+ next_tank = global.get_next_tank(&order_wrapped);
+
+ if (order_wrapped || (env.turntype != TURN_SIMUL) )
+ fire = true;
+ } else if ( (CONTROL_SKIP == result)
+ && !human_players
+ && env.skipComputerPlay
+ && !global.skippingComputerPlay
+ && (STAGE_SCOREBOARD > global.stage)
+ && (WINNER_NO_WIN == winner) ) {
+ global.skippingComputerPlay = true;
+ global.AI_clock = 0;
+ }
+
+ update_screen = false;
+ if (result)
+ global.updateMenu = true;
+ }
+
+ return done;
+}
+
+
+/** @brief Set up the next level to play
+ *
+ * This must work in parallel with the shop(), so any drawing must
+ * lock the land, do the drawing and unlock it again.
+**/
+static inline void set_level_settings(LevelCreator* lcr)
+{
+ // -------------------------
+ // === Choosing colours ===
+ //=============================
+ lcr->working_on(1);
+
+ // Choose first gradients for sky and land
+
+ // First the land:
+ if (lcr->can_work()) {
+ global.curland = (rand () % LANDS)
+ + (CT_CRISPY == env.colourTheme ? LANDS : 0);
+ if (!env.gfxData.land_gradient_strips[global.curland]) {
+
+ global.lockLand();
+
+ env.gfxData.land_gradient_strips[global.curland]
+ = create_gradient_strip (land_gradients[global.curland],
+ (env.screenHeight - MENUHEIGHT));
+
+ global.unlockLand();
+ }
+ }
+
+ // Then the sky
+ if (lcr->can_work()) {
+ global.cursky = (rand () % SKIES)
+ + (CT_CRISPY == env.colourTheme ? SKIES : 0);
+
+ if (!env.gfxData.sky_gradient_strips[global.cursky]) {
+ global.lockLand();
+
+ env.gfxData.sky_gradient_strips[global.cursky]
+ = create_gradient_strip (sky_gradients[global.cursky],
+ (env.screenHeight - MENUHEIGHT));
+
+ global.unlockLand();
+ }
+ }
+ BITMAP* sky_gradient_strip = env.gfxData.sky_gradient_strips[global.cursky];
+
+ // -------------------------
+ // === Rendering Landscape ===
+ //=============================
+ lcr->working_on(2);
+ if (lcr->can_work())
+ generate_land (lcr, rand (), 0, env.screenHeight);
+
+
+ // -------------------------
+ // === Check Colours ===
+ //=============================
+ if (lcr->can_work()) {
+ int32_t peak_height = env.screenHeight;
+ for (int32_t z = 0; lcr->can_work() && (z < env.screenWidth); ++z) {
+ if (peak_height > global.surface[z].load())
+ peak_height = global.surface[z].load(ATOMIC_READ);
+ }
+
+ int32_t min_dist = 128; // start with this colour distance wanted
+ int32_t max_tries = 16; // These many tries before lowering the distance
+ int32_t cur_try = 1;
+ int32_t bottom = env.screenHeight - MENUHEIGHT;
+ int32_t max_y = std::min(env.screenHeight - peak_height,
+ env.screenHeight - MENUHEIGHT);
+ bool has_colours = false;
+
+ while (!has_colours && lcr->can_work()) {
+ has_colours = true;
+
+ for (int32_t y = 2; lcr->can_work() && has_colours && (y < max_y); ++y) {
+ if (colorDistance(
+ getpixel(sky_gradient_strip, 0, bottom - y),
+ getpixel(global.terrain, 0, env.screenHeight - y) )
+ < min_dist)
+ has_colours = false;
+ }
+
+ if (!has_colours && lcr->can_work()) {
+ // Create new strip:
+ global.cursky = (rand () % SKIES)
+ + (CT_CRISPY == env.colourTheme ? SKIES : 0);
+
+ if (!env.gfxData.sky_gradient_strips[global.cursky]) {
+ global.lockLand();
+
+ env.gfxData.sky_gradient_strips[global.cursky]
+ = create_gradient_strip (sky_gradients[global.cursky],
+ (env.screenHeight - MENUHEIGHT));
+
+ global.unlockLand();
+ }
+ sky_gradient_strip = env.gfxData.sky_gradient_strips[global.cursky];
+
+ // Advance try and check:
+ if (++cur_try > max_tries) {
+ cur_try = 1;
+ min_dist /= 2;
+
+ // Break if min_dist is reduced to 1:
+ if (min_dist < 2)
+ has_colours = true;
+ } // end of advancing tries
+ } // end of handling wrong colours
+ } // End of searching suitable sky colours
+ } // end of thread not to be killed
+
+ // -------------------------
+ // === Rendering Sky ===
+ //=============================
+ lcr->working_on(3);
+
+ if (lcr->can_work()) {
+ if (env.sky
+ && ( (env.sky->w != env.screenWidth)
+ || (env.sky->h != (env.screenHeight - MENUHEIGHT) ) ) ) {
+ destroy_bitmap(env.sky);
+ env.sky = nullptr;
+ }
+
+ // see if we want a custom background
+ if (env.custom_background && env.bitmap_filenames) {
+ global.lockLand();
+ if (env.sky)
+ destroy_bitmap(env.sky);
+ env.sky = load_bitmap(env.bitmap_filenames[
+ rand() % env.number_of_bitmaps ], nullptr);
+ global.unlockLand();
+ }
+
+ // if we do not have a custom background (or do not want one) create a new background
+ if (!env.custom_background || !env.sky) {
+ global.lockLand();
+
+ if (!env.sky)
+ env.sky = create_bitmap(env.screenWidth,
+ env.screenHeight - MENUHEIGHT);
+
+ global.unlockLand();
+
+ generate_sky (lcr, sky_gradients[global.cursky],
+ (env.ditherGradients ? GENSKY_DITHERGRAD : 0 ) |
+ (env.detailedSky ? GENSKY_DETAILED : 0 ) );
+ }
+ }
+
+ lcr->working_on(4);
+}
+
+
+/// @brief tank placement and player ordering
+static inline void set_tank_settings()
+{
+ // -------------------------
+ // === Tank Placement ===
+ //=============================
+
+ // Distribute tanks over the landscape
+ abool_t taken[MAXPLAYERS] = { ATOMIC_VAR_INIT(false) };
+ int32_t middle = global.numTanks / 2;
+
+ global.getHeadOfClass(CLASS_TANK, &curr_tank);
+ while (curr_tank) {
+ int32_t x = rand () % global.numTanks;
+ while (taken[x]){
+ bool go_up = x < middle ? true : false;
+ while (taken[x] && (x > 0) && (x < (global.numTanks - 1)) )
+ x += go_up ? 1 : -1;
+ if (taken[x])
+ x = rand () % global.numTanks;
+ }
+
+ /* Note: this is a lot faster than the previous approach, because
+ * the chance to fail if only one place is left is very high, while
+ * the chance to go into the direction of the last free spot is 50%.
+ */
+
+ if (!taken[x]) {
+ taken[x] = true;
+
+ int32_t tx = (x + 1) * (env.screenWidth / (global.numTanks + 1));
+ int32_t ty = env.screenHeight - global.surface[tx].load();
+
+ curr_tank->newRound (tx, ty);
+ curr_tank->getNext(&curr_tank);
+ }
+ }
+
+ // --------------------------
+ // === Determine Tank Order ===
+ //==============================
+
+ for (int32_t z = 0; z < MAXPLAYERS; z++) {
+ global.order[z] = nullptr;
+ env.playerOrder[z] = nullptr;
+ }
+ env.maxNumTanks = global.numTanks;
+
+ // Distribute tanks in the order array
+ int32_t place = 0;
+ global.getHeadOfClass(CLASS_TANK, &curr_tank);
+ while (curr_tank) {
+ global.order[place++] = curr_tank;
+ curr_tank->getNext(&curr_tank);
+ }
+
+ // Mix up the order if it is wanted to be randomized
+ if ( (env.turntype == TURN_RANDOM)
+ || (env.turntype == TURN_SIMUL)) {
+ for (int32_t index = 0; index < env.maxNumTanks; ++index) {
+ for (int32_t round = 0; round < middle; ++round) {
+ int32_t target = rand () % global.numTanks;
+ if (target != index) {
+ TANK* tmp_tank = global.order[index];
+ global.order[index] = global.order[target];
+ global.order[target] = tmp_tank;
+ }
+ }
+ }
+ }
+
+ // Otherwise sort the order array
+ else {
+ bool sorted = false;
+ while (!sorted) {
+ sorted = true;
+ for (int32_t index = 0; index < env.maxNumTanks - 1; ++index) {
+ bool swap = false;
+ if (env.turntype == TURN_HIGH) {
+ if (global.order[index ]->player->score
+ < global.order[index + 1]->player->score)
+ swap = true;
+ } else if (env.turntype == TURN_LOW) {
+ if (global.order[index ]->player->score
+ > global.order[index + 1]->player->score)
+ swap = true;
+ }
+ if (swap) {
+ TANK *tempTank = global.order[index];
+ global.order[index] = global.order[index + 1];
+ global.order[index + 1] = tempTank;
+ sorted = false;
+ }
+ }
+ }
+ }
+
+ // Create the ordered list of players
+ // Since tanks get deleted as they are destroyed, we loose the information
+ // about their order, but the player order list will be complete through the
+ // entire round.
+ int32_t max_name_len = 0;
+ int32_t max_team_len = 0;
+
+ for (int i = 0; i < env.maxNumTanks; ++i) {
+ env.playerOrder[i] = global.order[i]->player;
+
+ int32_t name_len = text_length(font, env.playerOrder[i]->getName());
+ int32_t team_len = text_length(font, env.playerOrder[i]->getTeamName());
+
+ if (name_len > max_name_len)
+ max_name_len = name_len;
+ if (team_len > max_team_len)
+ max_team_len = team_len;
+
+ // Reset tank flash damage and activate their first shields:
+ if (env.playerOrder[i]->tank) {
+ env.playerOrder[i]->tank->resetFlashDamage();
+ env.playerOrder[i]->tank->reactivate_shield();
+ }
+ }
+
+ // Set name and money position according to the maximum lengths
+ score_name_pos = 16 + (2 * env.fontHeight) + max_team_len;
+ score_money_pos = score_name_pos + (2 * env.fontHeight) + max_name_len;
+
+ // FPS is shown on the right top corner:
+ FPS_pos = env.screenWidth - text_length(font, "XXXX FPS ");
+}
+
+
+/// @brief the [e]nd [o]f [r]ound score board
+/// If the stage is STAGE_ENDGAME, credits and points are awarded and the
+/// function waits for user input before it returns
+static inline void draw_eor_scoreboard()
+{
+ // Clear key buffer
+ if (STAGE_ENDGAME == global.stage) {
+ while ( keypressed() )
+ readkey();
+ }
+
+ // check to see if we have a winner or we just got out early
+ if ( (winner != WINNER_NO_WIN)
+ && !global.demo_mode
+ && !global.isCloseBtnPressed() ) {
+
+ // Re-Check for winner - This might have changed due to
+ // dying wrath devices that went off - should not happen. Really.
+ if (STAGE_ENDGAME == global.stage) {
+ check_winner();
+
+ // Eventually credit winner(s)
+ env.creditWinners(winner);
+ }
+
+ // Now the scores can be displayed
+ int32_t lh = 14; // The line height. If you need to change it, do it here.
+ int32_t pd = 10; // Padding. How much space to the board border.
+
+ // Find out longest player name and score length do determine the
+ // score board size and score entry positions
+ char head_name[5] = "Name";
+ char head_score[30] = { 0 };
+ snprintf(head_score, 29, " %6s %6s %6s %6s", "Kills", "Killed", "Diff", "Won");
+
+ int32_t namLen = text_length(font, head_name);
+ int32_t scoLen = text_length(font, head_score);
+
+ for (int32_t z = 0; z < env.numGamePlayers; z++) {
+ int32_t curLen = text_length(font, env.players[z]->getName());
+ if (curLen > namLen)
+ namLen = curLen;
+ char scoTxt[30] = { 0 };
+ snprintf(scoTxt, 29, " %6d %6d %6d %6d",
+ env.players[z]->kills,
+ env.players[z]->killed,
+ env.players[z]->killed - env.players[z]->kills,
+ env.players[z]->score);
+ curLen = text_length(font, scoTxt);
+ if (curLen > scoLen)
+ scoLen = curLen;
+ }
+
+ // Now calculate the dimensions of our score board.
+ int32_t w = namLen + scoLen + (2 * pd);
+ int32_t h = ((env.numGamePlayers + 4) * lh) + (2 * pd);
+ int32_t x = env.halfWidth - (w / 2);
+ int32_t y = env.halfHeight - (h / 2);
+
+ // Draw the background and the border
+ global.make_update (x, y, w, h);
+
+ rectfill(global.canvas, x, y, x + w, y + h, BLACK);
+ rect (global.canvas, x, y, x + w, y + h, WHITE);
+ rect (global.canvas, x + 1, y + 1, x + w - 1, y + h - 1, GREY);
+
+ // Show a hint to press a key if this is the endgame board
+ if (STAGE_ENDGAME == global.stage) {
+ global.make_update (x, y + h, w, env.fontHeight + 6);
+ textout_centre_ex(global.canvas, font, "Press any key to exit",
+ x + (w / 2) + 2, y + h + 6, BLACK, -1);
+ textout_centre_ex(global.canvas, font, "Press any key to exit",
+ x + (w / 2), y + h + 4, SILVER, -1);
+ }
+
+ // Add the padding now, or it must be summed in everywhere!
+ x += pd;
+ y += pd;
+ w -= 2 * pd;
+ h -= 2 * pd;
+
+ // First title line, the winner
+ if (winner == WINNER_JEDI)
+ textout_centre_ex (global.canvas, font, "Jedi Win!",
+ env.halfWidth, y, WHITE, -1);
+ else if (winner == WINNER_SITH)
+ textout_centre_ex (global.canvas, font, "Sith Win!",
+ env.halfWidth, y, WHITE, -1);
+ else if (winner == WINNER_DRAW)
+ textout_centre_ex (global.canvas, font, "Draw",
+ env.halfWidth, y, WHITE, -1);
+ else
+ textprintf_centre_ex (global.canvas, font,
+ env.halfWidth, y,
+ env.players[winner]->color, -1, "%s: %s",
+ env.ingame->Get_Line(47),
+ env.players[winner]->getName());
+
+ // Second title line: The score is to follow. (Is this needed?)
+ textout_right_ex (global.canvas, font, env.ingame->Get_Line(50),
+ env.halfWidth, y + (2 * lh), WHITE, -1);
+
+ // to make the following easier, skip the three used lines
+ // (two titles, one blank)
+ y += 3 * lh;
+
+ // Third title line, the score board header
+ int32_t scoStart = x + namLen;
+ int32_t scoWidth = scoLen / 4;
+
+ textout_ex (global.canvas, font, "Name", x, y, WHITE, -1);
+ textprintf_right_ex (global.canvas, font, scoStart + (1 * scoWidth),
+ y, GREEN, -1, " %6s", "Kills");
+ textprintf_right_ex (global.canvas, font, scoStart + (2 * scoWidth),
+ y, RED, -1, " %6s", "Killed");
+ textprintf_right_ex (global.canvas, font, scoStart + (3 * scoWidth),
+ y, WHITE, -1, " %6s", "Diff");
+ textprintf_right_ex (global.canvas, font, scoStart + (4 * scoWidth),
+ y, WHITE, -1, " %6s", "Won");
+
+
+ // Create the score order.
+ // The scores are ordered by score->diff->kills->killed->name
+ sScore* score_array = sort_scores();
+
+ // And get the head entry:
+ sScore* score = score_array;
+ while (score->prev)
+ score = score->prev;
+
+
+ // Eventually the player scores can be displayed:
+ // (again skip the previous line for easier reading/doing below)
+ y += lh;
+ int32_t z = 0;
+ while (score) {
+ textout_ex (global.canvas, font, score->name,
+ x, y + (z * lh), score->color, -1);
+ textprintf_right_ex (global.canvas, font, scoStart + (1 * scoWidth),
+ y + (z * lh), GREEN, -1, " %6d", score->kills);
+ textprintf_right_ex (global.canvas, font, scoStart + (2 * scoWidth),
+ y + (z * lh), RED, -1, " %6d", score->killed);
+ textprintf_right_ex (global.canvas, font, scoStart + (3 * scoWidth),
+ y + (z * lh), score->diff < 0 ? RED : GREEN, -1,
+ " %6d", score->diff);
+ textprintf_right_ex (global.canvas, font, scoStart + (4 * scoWidth),
+ y + (z * lh), WHITE, -1, " %6d", score->score);
+ ++z;
+ score = score->next;
+ }
+
+ // If this is the end-board, display here and wait for user input:
+ if (STAGE_ENDGAME == global.stage) {
+ global.do_updates();
+
+ // Wait until a key is pressed
+ while (!keypressed() && !mouse_b)
+ LINUX_REST;
+
+ // Clear key buffer
+ while ( keypressed() )
+ readkey();
+ }
+
+ // Clean up
+ delete [] score_array;
+ } // End of handling winner display
+}
+
+
+/** @brief sort players by scores.
+ *
+ * The return value is the pointer to the allocated array, users
+ * must use its prev() pointer to find the head entry.
+ *
+ * @return a pointer to the scores array. This must be deleted.
+**/
+sScore* sort_scores()
+{
+ sScore* scores = new sScore[env.numGamePlayers];
+ sScore* score_head = scores;
+ sScore* score_tail = scores;
+ sScore* curr = nullptr;
+
+ for (int32_t z = 0; z < env.numGamePlayers; z++) {
+ curr = score_head;
+ scores[z] = *(env.players[z]);
+ scores[z].idx = z; // The game index is needed.
+
+ // Walk to find a lower score:
+ while (curr
+ && (curr->score > scores[z].score))
+ curr = curr->next;
+
+ // Walk to find a lower diff:
+ while (curr
+ && (curr->score == scores[z].score)
+ && (curr->diff > scores[z].diff))
+ curr = curr->next;
+
+ // Walk to find a lower kills value:
+ while (curr
+ && (curr->score == scores[z].score)
+ && (curr->diff == scores[z].diff)
+ && (curr->kills > scores[z].kills))
+ curr = curr->next;
+
+ // Walk to find a higher killed value:
+ while (curr
+ && (curr->score == scores[z].score)
+ && (curr->diff == scores[z].diff)
+ && (curr->kills == scores[z].kills)
+ && (curr->killed < scores[z].killed))
+ curr = curr->next;
+
+ // Walk to find a higher name value:
+ while (curr
+ && (curr->score == scores[z].score)
+ && (curr->diff == scores[z].diff)
+ && (curr->kills == scores[z].kills)
+ && (curr->killed == scores[z].killed)
+ && (strcmp(curr->name, scores[z].name) < 0))
+ curr = curr->next;
+
+ // If there is a curr, sort the new score before it.
+ if (curr && (curr != &scores[z])) {
+ scores[z].prev = curr->prev;
+ scores[z].next = curr;
+ if (scores[z].prev)
+ scores[z].prev->next = &scores[z];
+ curr->prev = &scores[z];
+ if (score_head == curr)
+ score_head = &scores[z];
+ }
+
+ // Otherwise this is the new tail:
+ else if (score_tail != &scores[z]) {
+ scores[z].prev = score_tail;
+ score_tail->next = &scores[z];
+ score_tail = &scores[z];
+ }
+ } // End of sorting scores
+
+ return scores;
+}
+
+
+static inline void update_display()
+{
+ if (show_frame) {
+ // do not show custom mouse cursor while drawing
+ SHOW_MOUSE(nullptr)
+
+ set_clip_rect(global.canvas, 0, 0,
+ env.screenWidth - 1, env.screenHeight - 1);
+
+ if (update_screen) {
+ update_screen = false;
+ global.make_fullUpdate();
+ global.updateMenu = true;
+ }
+ global.replace_canvas();
+ }
+}
+
+
+
+
+static inline void update_objects(ObjectUpdater* upd)
+{
+ // Start all updater threads
+ for (int32_t class_ = 0; class_ < CLASS_COUNT; ++class_)
+ upd[class_].start();
+
+ // Wakeup all at once:
+ updMutex.lock();
+ updCondition.notify_all();
+ updMutex.unlock();
+
+ // Wait for the threads to finish
+ bool has_thread = true;
+ while (has_thread) {
+ has_thread = false;
+ for (int32_t class_ = 0; class_ < CLASS_COUNT; ++class_) {
+ if (!upd[class_].hasDone())
+ has_thread = true;
+ }
+ if (has_thread)
+ std::this_thread::yield();
+ }
+
+ // Reset SDI shot status on all tanks
+ TANK* lt = nullptr;
+ global.getHeadOfClass(CLASS_TANK, <);
+ while (lt) {
+ if (lt->player)
+ lt->player->sdi_has_fired.store(false, ATOMIC_WRITE);
+ lt->getNext(<);
+ }
+}
+
+
+/// Level Creator Methods implementation
+LevelCreator::LevelCreator()
+{
+ for (int32_t i = 0; i < 4; ++i)
+ in_progress[i] = false;
+}
+
+
+/// The operator is just a wrapper.
+void LevelCreator::operator()()
+{
+ fiVal = 1;
+ set_level_settings(this);
+ fiVal = 0;
+}
+
+void LevelCreator::add_fi()
+{
+ fiLock.lock();
+ ++fiVal;
+ fiLock.unlock();
+}
+
+bool LevelCreator::can_work() const
+{
+ return !i_shall_die;
+}
+
+void LevelCreator::die_now()
+{
+ i_shall_die = true;
+}
+
+bool LevelCreator::has_progress()
+{
+ fiLock.lock();
+ bool result = (fiVal > 0);
+ fiVal = 0;
+ fiLock.unlock();
+
+ return result;
+}
+
+bool LevelCreator::is_finished() const
+{
+ return in_progress[3];
+}
+
+void LevelCreator::print_state() const
+{
+ if (in_progress[0]) {
+ draw_sprite (global.canvas, env.misc[1], env.halfWidth - 120, env.halfHeight + 115);
+ textout_centre_ex(global.canvas, font, env.ingame->Get_Line(42),
+ env.halfWidth, env.halfHeight + 116, WHITE, -1);
+ global.make_update(env.halfWidth - 120, env.halfHeight + 115,
+ env.misc[1]->w, env.misc[1]->h);
+ }
+
+ if (in_progress[1]) {
+ draw_sprite(global.canvas, env.misc[1], env.halfWidth - 120, env.halfHeight + 155);
+ textout_centre_ex(global.canvas, font, env.ingame->Get_Line(44),
+ env.halfWidth, env.halfHeight + 156, WHITE, -1);
+ global.make_update(env.halfWidth - 120, env.halfHeight + 155,
+ env.misc[1]->w, env.misc[1]->h);
+ }
+
+
+ if (in_progress[2]) {
+ draw_sprite(global.canvas, env.misc[1], env.halfWidth - 120, env.halfHeight + 195);
+ textout_centre_ex(global.canvas, font, env.ingame->Get_Line(43),
+ env.halfWidth, env.halfHeight + 196, WHITE, -1);
+ global.make_update(env.halfWidth - 120, env.halfHeight + 195,
+ env.misc[1]->w, env.misc[1]->h);
+ }
+}
+
+/// @brief Tell the LevelCreator that it does not need to yield any more
+void LevelCreator::work_alone()
+{
+ i_must_yield = false;
+}
+
+
+void LevelCreator::working_on(int32_t what)
+{
+ if ( (what > 0) && (what < 5) ) {
+ add_fi();
+ in_progress[what - 1] = true;
+ }
+}
+
+
+/// @brief yield if it is not working alone
+void LevelCreator::yield()
+{
+ if (i_must_yield)
+ std::this_thread::yield();
+}
diff --git a/src/gameloop.h b/src/gameloop.h
index 934f118..ddcc442 100644
--- a/src/gameloop.h
+++ b/src/gameloop.h
@@ -1,16 +1,67 @@
#ifndef GAMELOOP_HEADER_FILE__
#define GAMELOOP_HEADER_FILE__
-#include "globaldata.h"
-#include "environment.h"
-
#define MAX_TEXT_BOUNCE 40
-// The massive game loop, re-wrrite here for
+#include "player.h"
+
+/// Helper Class to background level creation
+class LevelCreator
+{
+ volatile bool in_progress[4];
+ volatile bool i_must_yield = true;
+ volatile bool i_shall_die = false;
+ CSpinLock fiLock;
+ int32_t fiVal = 0;
+ void add_fi();
+public:
+ explicit LevelCreator();
+ void operator()();
+ bool can_work() const;
+ void die_now();
+ bool has_progress();
+ bool is_finished() const;
+ void print_state() const;
+ void work_alone();
+ void working_on(int32_t what);
+ void yield();
+};
+
+
+/// @brief small struct to order the score board, used by the end-of-game-sorting, too
+struct sScore
+{
+ int32_t color = BLACK;
+ int32_t diff = 0;
+ int32_t idx = -1;
+ int32_t killed = 0;
+ int32_t kills = 0;
+ const char* name = nullptr;
+ sScore* next = nullptr;
+ sScore* prev = nullptr;
+ int32_t score = 0;
+
+ sScore &operator=(PLAYER &rhs)
+ {
+ color = rhs.color;
+ idx = rhs.index;
+ killed = rhs.killed;
+ kills = rhs.kills;
+ name = rhs.getName();
+ score = rhs.score;
+ diff = kills - killed;
+ return *this;
+ }
+};
+
+
+// The massive game loop, rewritten here for
// all sorts of reasons.
-#ifdef NEW_GAMELOOP
-int game(GLOBALDATA *global, ENVIRONMENT *env);
-#endif
+void game();
+
+// sort players. The returned scores point to an array that must be deleted.
+sScore* sort_scores();
+
#endif
diff --git a/src/gfxData.cpp b/src/gfxData.cpp
new file mode 100644
index 0000000..8fdfbe3
--- /dev/null
+++ b/src/gfxData.cpp
@@ -0,0 +1,295 @@
+#include "main.h"
+#include "gfxData.h"
+
+/** @brief explicit constructor, because Visual C++ needs one.
+**/
+sGfxData::sGfxData()
+{
+ memset(sky_gradient_strips, 0, sizeof(BITMAP*) * ALL_SKIES);
+ memset(land_gradient_strips, 0, sizeof(BITMAP*) * ALL_LANDS);
+ memset(stuff_bar, 0, sizeof(BITMAP*) * 2);
+ memset(explosions, 0, sizeof(BITMAP*) * EXPLOSIONFRAMES);
+ memset(flameFront, 0, sizeof(BITMAP*) * EXPLOSIONFRAMES);
+}
+
+
+/** @brief sGfxData destructor - clean everything up
+ */
+sGfxData::~sGfxData()
+{
+ this->destroy();
+}
+
+
+/// @brief should be called from ENVIRONMENT::destroy();
+void sGfxData::destroy()
+{
+ if (initDone) {
+ if (topbar)
+ destroy_bitmap(topbar);
+ if (topbar_gradient_strip)
+ destroy_bitmap(topbar_gradient_strip);
+ if (stuff_bar[0])
+ destroy_bitmap(stuff_bar[0]);
+ if (stuff_bar[1])
+ destroy_bitmap(stuff_bar[1]);
+ if (stuff_icon_base)
+ destroy_bitmap(stuff_icon_base);
+ if (stuff_bar_gradient_strip)
+ destroy_bitmap(stuff_bar_gradient_strip);
+ if (explosion_gradient_strip)
+ destroy_bitmap(explosion_gradient_strip);
+
+ topbar = nullptr;
+ topbar_gradient_strip = nullptr;
+ stuff_bar[0] = nullptr;
+ stuff_bar[1] = nullptr;
+ stuff_icon_base = nullptr;
+ stuff_bar_gradient_strip = nullptr;
+ explosion_gradient_strip = nullptr;
+
+ if (sky_gradient_strips) {
+ for (int32_t i = 0; i < ALL_SKIES; ++i) {
+ if ( sky_gradient_strips[i] ) {
+ destroy_bitmap(sky_gradient_strips[i]);
+ sky_gradient_strips[i] = nullptr;
+ }
+ }
+ }
+
+ if (land_gradient_strips) {
+ for (int32_t i = 0; i < ALL_LANDS; ++i) {
+ if ( land_gradient_strips[i] ) {
+ destroy_bitmap(land_gradient_strips[i]);
+ land_gradient_strips[i] = nullptr;
+ }
+ }
+ }
+
+ if (explosions) {
+ for (int32_t i = 0; i < EXPLOSIONFRAMES; ++i) {
+ if ( explosions[i] ) {
+ destroy_bitmap(explosions[i]);
+ explosions[i] = nullptr;
+ }
+ }
+ }
+
+ if (flameFront) {
+ for (int32_t i = 0; i < EXPLOSIONFRAMES; ++i) {
+ if ( flameFront[i] ) {
+ destroy_bitmap(flameFront[i]);
+ flameFront[i] = nullptr;
+ }
+ }
+ }
+
+ initDone = false;
+ }
+}
+
+
+/// @brief create the gfx data to hold
+void sGfxData::first_init()
+{
+ // Note: This method is mostly uncommented, because the original
+ // function that did this was uncommented.
+
+ if (initDone)
+ return;
+
+ int32_t colour_theme = static_cast<int32_t>(env.colourTheme);
+ explosion_gradient_strip = create_gradient_strip(explosion_gradients[colour_theme], 200);
+ double expSize = 25.;
+ double flmSize = 10.;
+ double expDisperse = 0.;
+ double flmDisperse = 0.;
+
+ for (int32_t i = 0; i < EXPLOSIONFRAMES; ++i) {
+ explosions[i] = create_bitmap (214, 214);
+ flameFront[i] = create_bitmap (600, 30);
+ if (i < EXPLODEFRAMES - 4) {
+ expSize += (107. - expSize) / 3.;
+ flmSize += (300. - flmSize) / 3.;
+ } else if (i < EXPLODEFRAMES) {
+ expSize -= 1.;
+ flmSize -= 1.;
+ } else if (i == EXPLODEFRAMES) {
+ expDisperse = 25.;
+ flmDisperse = 10.;
+ } else {
+ expDisperse += (107. - expDisperse) / 2.;
+ flmDisperse += (300. - flmDisperse) / 2.;
+ }
+
+ clear_to_color(explosions[i], PINK);
+ clear_to_color(flameFront[i], PINK);
+
+ for (int32_t y = std::floor(expSize); y > expDisperse; --y) {
+ double value = pow (static_cast<double>(y) / expSize, i / 4 + 1);
+ int32_t exp_col = getpixel(explosion_gradient_strip, 0,
+ static_cast<int32_t>(value * 200.));
+ circlefill (explosions[i], 107, 107, y, exp_col);
+ }
+ if (ROUND(expDisperse) > 0) {
+ circlefill (explosions[i], 107, 107, ROUND(expDisperse), PINK);
+ }
+
+ for (int32_t y = std::floor(flmSize); y > flmDisperse; --y) {
+ double value = pow (static_cast<double>(y) / flmSize, i / 4 + 1);
+ int32_t flame_col = getpixel (explosion_gradient_strip, 0,
+ static_cast<int32_t>(value * 200.));
+ ellipsefill (flameFront[i], 300, 15, y, y / 20, flame_col);
+ }
+ if (ROUND(flmDisperse) > 0) {
+ ellipsefill (flameFront[i], 300, 15, ROUND(flmDisperse),
+ ROUND(flmSize / 16.), PINK);
+ }
+ }
+
+ topbar = create_bitmap (env.screenWidth, MENUHEIGHT);
+ topbar_gradient_strip = create_gradient_strip (topbar_gradient, 100);
+
+ if (!env.ditherGradients) {
+ for (int32_t i = 0; i < MENUHEIGHT; ++i) {
+ float adjCount = (100. / MENUHEIGHT) * i;
+ int32_t col = getpixel(topbar_gradient_strip, 0, adjCount);
+ line (topbar, 0, i, env.screenWidth - 1, i, col);
+ }
+ } else {
+ for (int32_t x = 0; x < env.screenWidth; ++x) {
+ for (int32_t y = 0; y < MENUHEIGHT; ++y) {
+ float adjY = (100.0 / MENUHEIGHT) * y;
+ int offset = 0;
+
+ if ((adjY > 1) && (adjY < 99))
+ offset = rand () % 4 - 2;
+
+ int32_t col = getpixel(topbar_gradient_strip, 0, adjY + offset);
+
+ putpixel (topbar, x, y, col);
+ }
+ }
+ }
+
+ stuff_bar[0] = create_bitmap (STUFF_BAR_WIDTH, STUFF_BAR_HEIGHT);
+ stuff_bar[1] = create_bitmap (STUFF_BAR_WIDTH, STUFF_BAR_HEIGHT);
+ stuff_icon_base = create_bitmap (STUFF_BAR_WIDTH/10, STUFF_BAR_HEIGHT);
+
+ clear_to_color (stuff_bar[0], PINK);
+ clear_to_color (stuff_bar[1], PINK);
+ clear_to_color (stuff_icon_base, PINK);
+
+ stuff_bar_gradient_strip = create_gradient_strip (stuff_bar_gradient, STUFF_BAR_WIDTH);
+
+ double halfStuffBarHeight = (STUFF_BAR_HEIGHT / 2) - 2;
+
+ for (double x = 0; x < STUFF_BAR_WIDTH; x += 1.) {
+ for (double y = 0; y < STUFF_BAR_HEIGHT; y += 1.) {
+ double sides_dist = 0.1;
+ double circle_dist = FABSDISTANCE2(x, y, STUFF_BAR_WIDTH - 75, halfStuffBarHeight);
+
+ if (circle_dist < 75.)
+ circle_dist = 1. - (circle_dist / 75.0);
+ else
+ circle_dist = 0.;
+
+ if (x < (STUFF_BAR_HEIGHT/2 - 2))
+ sides_dist -= 0.1 - (x / 150.);
+ else if (x > STUFF_BAR_WIDTH - (STUFF_BAR_HEIGHT/2 - 2))
+ sides_dist -= (x - (STUFF_BAR_WIDTH - halfStuffBarHeight)) / 150.;
+
+ if (y < STUFF_BAR_HEIGHT/2 - 2)
+ sides_dist -= 0.1 - (y / 150.);
+ else
+ sides_dist -= (y - halfStuffBarHeight) / 150.;
+
+ sides_dist -= circle_dist * circle_dist;
+
+ if (sides_dist > (x / 1000.0))
+ sides_dist = x / 1000.0;
+ if (sides_dist < 0)
+ sides_dist = 0;
+ if (circle_dist > 1)
+ circle_dist = 1;
+
+ int32_t offset = (sides_dist + circle_dist) * (STUFF_BAR_WIDTH - 1);
+ if (offset >= STUFF_BAR_WIDTH)
+ offset = STUFF_BAR_WIDTH - 1;
+
+ int32_t col_a = getpixel(stuff_bar_gradient_strip,
+ 0, offset);
+
+ offset = (sides_dist + circle_dist + 0.06) * (STUFF_BAR_WIDTH - 1);
+ if (offset >= STUFF_BAR_WIDTH)
+ offset = STUFF_BAR_WIDTH - 1;
+
+ int32_t col_b = getpixel(stuff_bar_gradient_strip,
+ 0, offset);
+
+ if (x < (STUFF_BAR_WIDTH / 10)) {
+ putpixel (stuff_icon_base, x, y, col_a);
+ }
+
+ if (y < STUFF_BAR_HEIGHT - 5) {
+ putpixel (stuff_bar[0], x, y, col_a);
+ putpixel (stuff_bar[1], x, y, col_b);
+ }
+ }
+ }
+
+ initDone = true;
+}
+
+
+// === Helper Functions ===
+// ========================
+BITMAP *create_gradient_strip (const gradient *grad, int32_t len)
+{
+ BITMAP *strip = create_bitmap (1, len);
+ if (! strip)
+ return nullptr;
+
+ clear_to_color (strip, BLACK);
+
+ for (int32_t currLine = 0; currLine < len; ++currLine) {
+ int32_t color = gradientColorPoint(grad, len, currLine);
+ putpixel (strip, 0, currLine, color);
+ }
+
+ return strip;
+}
+
+int32_t gradientColorPoint(const gradient* grad, double len, double line)
+{
+ int32_t pointCount = 0;
+ double point = line / len;
+ int32_t color = BLACK;
+
+ for ( ;
+ (point >= grad[pointCount].point) && (grad[pointCount].point != -1);
+ ++pointCount) ;
+ pointCount--;
+
+ if (pointCount == -1)
+ color = makecol (grad[0].color.r, grad[0].color.g, grad[0].color.b);
+ else if (grad[pointCount + 1].point == -1)
+ color = makecol(grad[pointCount].color.r,
+ grad[pointCount].color.g,
+ grad[pointCount].color.b);
+ else {
+ double i = (point - grad[pointCount].point)
+ / (grad[pointCount + 1].point - grad[pointCount].point);
+ int32_t r = ROUND(interpolate (grad[pointCount].color.r,
+ grad[pointCount + 1].color.r, i));
+ int32_t g = ROUND(interpolate (grad[pointCount].color.g,
+ grad[pointCount + 1].color.g, i));
+ int32_t b = ROUND(interpolate (grad[pointCount].color.b,
+ grad[pointCount + 1].color.b, i));
+
+ color = makecol (r, g, b);
+ }
+
+ return color;
+}
+
diff --git a/src/gfxData.h b/src/gfxData.h
new file mode 100644
index 0000000..14d1972
--- /dev/null
+++ b/src/gfxData.h
@@ -0,0 +1,77 @@
+#pragma once
+#ifndef ATANKS_SRC_GFXDATA_H_INCLUDED
+#define ATANKS_SRC_GFXDATA_H_INCLUDED
+
+
+/*
+ * atanks - obliterate each other with oversize weapons
+ * Copyright (C) 2003 Thomas Hudson
+ *
+ * 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.
+ * */
+
+#define ALL_SKIES 16
+#define ALL_LANDS 16
+
+#define STUFF_BAR_WIDTH 400
+#define STUFF_BAR_HEIGHT 35
+
+#define EXPLODEFRAMES 18
+#define DISPERSEFRAMES 10
+#define EXPLOSIONFRAMES (EXPLODEFRAMES + DISPERSEFRAMES)
+
+#define EXPLO_CX 107.f
+#define EXPLO_CY 107.f
+#define EXPLO_H 214.f
+#define EXPLO_W 214.f
+
+#define FLAME_CX 300.f
+#define FLAME_CY 15.f
+#define FLAME_H 30.f
+#define FLAME_W 600.f
+
+/// @brief Consolidate global gfx data in a struct to have RAII in effect.
+struct sGfxData
+{
+ explicit sGfxData();
+ ~sGfxData();
+
+ void destroy();
+ void first_init();
+
+ BITMAP* sky_gradient_strips[ALL_SKIES];
+ BITMAP* land_gradient_strips[ALL_LANDS];
+ BITMAP* stuff_bar_gradient_strip = nullptr;
+ BITMAP* topbar_gradient_strip = nullptr;
+ BITMAP* explosion_gradient_strip = nullptr;
+ BITMAP* stuff_bar[2];
+ BITMAP* stuff_icon_base = nullptr;
+ BITMAP* topbar = nullptr;
+ BITMAP* explosions[EXPLOSIONFRAMES];
+ BITMAP* flameFront[EXPLOSIONFRAMES];
+
+private:
+ bool initDone = false;
+};
+
+
+// === Helper Functions ===
+// ========================
+BITMAP* create_gradient_strip (const gradient* grad, int32_t len);
+int32_t gradientColorPoint (const gradient* grad, double len, double line);
+
+
+
+#endif // ATANKS_SRC_GFXDATA_H_INCLUDED
diff --git a/src/globaldata.cpp b/src/globaldata.cpp
index 1f665fc..930b8c9 100644
--- a/src/globaldata.cpp
+++ b/src/globaldata.cpp
@@ -18,1377 +18,1258 @@
* */
#include <time.h>
+#include <cassert>
#include "player.h"
#include "globaldata.h"
#include "files.h"
-#include "network.h"
-#include "team.h"
+#include "tank.h"
+#include "sound.h"
+#include "debris_pool.h"
-GLOBALDATA::GLOBALDATA ():dataDir(NULL),configDir(NULL),updates(NULL),lastUpdates(NULL),allPlayers(NULL),
- players(NULL),currTank(NULL),saved_game_list(NULL)
+
+GLOBALDATA::GLOBALDATA()
{
- init_curland_lock();
-
- #ifdef THREADS
- command_lock = (pthread_rwlock_t*) malloc(sizeof(pthread_rwlock_t));
- if (command_lock == NULL)
- {
- printf("%s:%i: Could not allocate memory for command_lock.\n", __FILE__, __LINE__);
- }
- int result = pthread_rwlock_init(command_lock, NULL);
- switch (result)
- {
- case 0:
- //Successfully initialized
- break;
- case EAGAIN:
- //resource lack
- printf("%s:%i: Not enough resources to initialize read-write lock.\n", __FILE__, __LINE__);
- break;
- case ENOMEM:
- //out of memory
- printf("%s:%i: Not enough memory to initialize read-write lock.\n", __FILE__, __LINE__);
- break;
- case EPERM:
- //not authorized
- printf("%s:%i: Not authorized.\n", __FILE__, __LINE__);
- break;
- default:
- //If the switch ever gets to here, something very wrong happened and pthread_rwlock_init returned
- //a random value
- printf("%s:%i: Unknown error code (%i) returned by pthread_rwlock_init.\n", __FILE__, __LINE__, result);
- break;
- }
- #endif
- tank_status = (char *)calloc(128, sizeof(char));
- if (!tank_status)
- {
- perror ( "globaldata.cpp: Failed allocating memory for tank_status in GLOBALDATA::GLOBALDATA");
- // exit (1);
- }
-
- initialise ();
- language = LANGUAGE_ENGLISH;
- sound = 1.0;
- name_above_tank = TRUE;
- colourDepth = 0;
- client_player = NULL;
- screenWidth = DEFAULT_SCREEN_WIDTH;
- screenHeight = DEFAULT_SCREEN_HEIGHT;
- width_override = height_override = 0;
- temp_screenWidth = screenWidth;
- temp_screenHeight = screenHeight;
- halfWidth = screenWidth / 2;
- halfHeight = screenHeight / 2;
- menuBeginY = (screenHeight - 400) / 2;
- if (menuBeginY < 0) menuBeginY = 0;
- menuEndY = screenHeight - menuBeginY;
- frames_per_second = FRAMES_PER_SECOND;
- numPermanentPlayers = 10;
- violent_death = FALSE;
-/*
-#ifndef DOS
- cacheCirclesBG = 1;
-#endif
-
-#ifdef DOS
- cacheCirclesBG = 0;
-#endif
-*/
- ditherGradients = 1;
- detailedLandscape = 0;
- detailedSky = 0;
- colour_theme = COLOUR_THEME_CRISPY;
- startmoney = 15000;
- turntype = TURN_RANDOM;
- skipComputerPlay = SKIP_HUMANS_DEAD;
- // dataDir = DATA_DIR;
- Find_Data_Dir();
- os_mouse = 1.0;
- full_screen = FULL_SCREEN_FALSE;
- interest = 1.25;
- scoreHitUnit = 75;
- scoreSelfHit = 0;
- scoreUnitDestroyBonus = 5000;
- scoreUnitSelfDestroy = 0;
- scoreRoundWinBonus = 10000;
- sellpercent = 0.80;
- game_name[0] = '\0';
- load_game = 0.0;
- campaign_mode = 0.0;
- saved_game_index = 0;
- saved_game_list = NULL;
- max_fire_time = 0.0;
- close_button_pressed = false;
- divide_money = 0.0;
- sound_driver = SOUND_AUTODETECT;
- update_string = NULL;
- check_for_updates = 1.0;
- demo_mode = false;
- env = NULL;
- war_quotes = instructions = ingame = NULL;
- gloat = revenge = retaliation = kamikaze = suicide = NULL;
- client_message= NULL;
-
- updates = new BOX[MAXUPDATES];
- if (!updates)
- {
- perror ( "globaldata.cc: Failed allocating memory for updates in GLOBALDATA::GLOBALDATA");
- // exit (1);
- }
- lastUpdates = new BOX[MAXUPDATES];
- if (!lastUpdates)
- {
- perror ( "globaldata.cc: Failed allocating memory for lastUpdates in GLOBALDATA::GLOBALDATA");
- // exit (1);
- }
- updateCount = 0;
- lastUpdatesCount = 0;
-
- // players = new PLAYER*[MAXPLAYERS];
- players = (PLAYER **) calloc( MAXPLAYERS, sizeof(PLAYER *) );
- if (!players)
- {
- perror ( "globaldata.cc: Failed allocating memory for players in GLOBALDATA::GLOBALDATA");
- // exit (1);
- }
- numPlayers = 0;
- rounds = 5;
-
- if (allPlayers) free(allPlayers); // avoid potential leak
- allPlayers = (PLAYER**) calloc (1, sizeof (PLAYER*));
- if (!allPlayers)
- {
- fprintf (stderr, "Failed allocating memory for players in globaldata.cc\n");
- // exit (1);
- }
-
- for (int count = 0; count < 360; count++)
- {
- slope[count][0] = sin (count / (180 / PI));
- slope[count][1] = cos (count / (180 / PI));
- }
- slope[270][1] = 0;
- configDir = NULL;
- bIsGameLoaded = true;
- bIsBoxed = false;
- iHumanLessRounds = -1;
- dMaxVelocity = 0.0; // Will be set in game()
-#ifdef DEBUG_AIM_SHOW
- bASD = false; // will be set to true after the first drawing of the map
-#endif
- enable_network = 0.0;
-#ifdef NETWORK
- listen_port = DEFAULT_LISTEN_PORT;
-#endif
- strcpy(server_name, "127.0.0.1");
- strcpy(server_port, "25645");
- play_music = 1.0;
- background_music = NULL;
- music_dir = NULL;
- unicode = NULL;
- regular_font = font;
- draw_background = TRUE;
+ // memset initialization, because Visual C++ 2013 can't do lists, yet.
+ memset(order, 0, sizeof(TANK*) * MAXPLAYERS);
+ memset(tank_status, 0, sizeof(char) * 128);
+ memset(heads, 0, sizeof(vobj_t*) * CLASS_COUNT);
+ memset(tails, 0, sizeof(vobj_t*) * CLASS_COUNT);
}
-
GLOBALDATA::~GLOBALDATA()
{
- int index;
-
- if (tank_status)
- {
- tank_status[0] = 0;
- free(tank_status);
- }
-
- index = 0;
- while (sounds[index])
- {
- destroy_sample(sounds[index]);
- }
- free(sounds);
-
- if (music_dir)
- closedir(music_dir);
- if (unicode)
- destroy_font(unicode);
-
- if (war_quotes) delete war_quotes;
- if (instructions) delete instructions;
- if (ingame) delete ingame;
- if (gloat) delete gloat;
- if (revenge) delete revenge;
- if (retaliation) delete retaliation;
- if (kamikaze) delete kamikaze;
- if (suicide) delete suicide;
- if (allPlayers) free(allPlayers);
- if (update_string) free(update_string);
- #ifdef THREADS
- if (command_lock)
- {
- int result = pthread_rwlock_destroy(command_lock);
- switch (result)
- {
- case 0:
- //Successfully destroyed
- break;
- case EBUSY:
- //Some thread forgot to unlock the read-write lock
- printf("%s:%i: Can't destroy because command_lock is still locked.\n", __FILE__, __LINE__);
- break;
- case EINVAL:
- //Invalid lock
- printf("%s:%i: The lock is invalid.\n", __FILE__, __LINE__);
- break;
- default:
- //Something went wrong
- printf("%s:%i: Unknown error code (%i) returned by pthread_rwlock_destroy.\n", __FILE__, __LINE__, result);
- break;
- }
- free(command_lock);
- }
- destroy_curland_lock();
- #endif
-}
-//Destroys curland_lock and frees it. If the lock is NULL or an error occurs, a diagnostic message will be printed.
-void GLOBALDATA::destroy_curland_lock() {
- #ifdef THREADS
- if (curland_lock != NULL)
- {
- int result = pthread_mutex_destroy(curland_lock);
- switch (result)
- {
- case 0:
- //Successfully destroyed
- break;
- case EBUSY:
- //Some thread forgot to unlock result
- printf("%s:%i: Lock is still held.\n", __FILE__, __LINE__);
- break;
- case EINVAL:
- //Invalid lock
- printf("%s:%i: Lock is invalid.\n", __FILE__, __LINE__);
- break;
- default:
- printf("%s:%i: Unknown error code (%i) returned by pthread_mutex_destroy.\n", __FILE__, __LINE__, result);
- break;
- }
- free(curland_lock);
- }
- else
- {
- printf("%s:%i: Cannot destroy a null mutex.\n", __FILE__, __LINE__);
- }
- #endif
+ this->destroy();
}
-//init_curland_lock() allocates space for curland_lock and then calls pthread_mutex_init to initialize the lock. If the call fails, then this function will print diagnostic messages.
-void GLOBALDATA::init_curland_lock() {
- #ifdef THREADS
- curland_lock = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t));
- if (!curland_lock)
- {
- printf("%s:%i: Could not allocate memory for curland_lock.\n", __FILE__, __LINE__);
- }
- int result = pthread_mutex_init(curland_lock, NULL);
- switch (result)
- {
- case 0:
- //Succesfully initialized
- break;
- case EAGAIN:
- //Not enough resources
- printf("%s:%i: Not enough resources to create mutex.\n", __FILE__, __LINE__);
- break;
- case ENOMEM:
- printf("%s:%i: Not enough memory to create mutex.\n", __FILE__, __LINE__);
- break;
- case EPERM:
- printf("%s:%i: Not authorized.\n", __FILE__, __LINE__);
- break;
- case EBUSY:
- printf("%s:%i: The mutex is already initialized.\n", __FILE__, __LINE__);
- break;
- case EINVAL:
- printf("%s:%i: Invalid attribute.\n", __FILE__, __LINE__);
- break;
- default:
- printf("%s:%i: Unknown error code (%i) returned by pthread_mutex_init.\n", __FILE__, __LINE__, result);
- break;
- }
- #endif
+
+/// @brief goes through the columns from @a left to @a right and sets slide type according to @a do_lock
+void GLOBALDATA::addLandSlide(int32_t left, int32_t right, bool do_lock)
+{
+ // Opt out soon if no landslide is to be done
+ if ( (SLIDE_NONE == env.landSlideType)
+ || (SLIDE_TANK_ONLY == env.landSlideType) )
+ return;
+
+ int32_t minX = std::min(left, right);
+ int32_t maxX = std::max(left, right);
+
+ if (minX < 1)
+ minX = 1;
+ if (minX > (env.screenWidth - 1) )
+ minX = env.screenWidth - 1;
+ if (maxX < 1)
+ maxX = 1;
+ if (maxX > (env.screenWidth - 1) )
+ maxX = env.screenWidth - 1;
+
+ if (do_lock)
+ memset(&done[minX], 3, sizeof(char) * (maxX - minX + 1) );
+ else
+ memset(&done[minX], 2, sizeof(char) * (maxX - minX + 1) );
}
-//lock_curland() locks curland and prints diagnostic messages if the lock operation fails.
-//Use get_curland() to read the value instead of directly using lock_curland()/unlock_curland() pairs.
-void GLOBALDATA::lock_curland()
+
+
+void GLOBALDATA::addObject (vobj_t *object)
{
- #ifdef THREADS
- int result = pthread_mutex_lock(curland_lock);
- switch (result)
- {
- case 0:
- //Got the lock.
- break;
- case EINVAL:
- //Priority too high
- printf("%s:%i: Either this thread's priority is higher than the mutex's priority, or the mutex is uninitialized.\n", __FILE__, __LINE__);
- break;
- case EAGAIN:
- printf("%s:%i: Too many locks on the mutex.\n", __FILE__, __LINE__);
- break;
- case EDEADLK:
- //We have the lock already
- printf("%s:%i: Already have lock.\n", __FILE__, __LINE__);
- break;
- default:
- //What error is this?
- printf("%s:%i: Unknown error code (%i) returned by pthread_mutex_lock.\n", __FILE__, __LINE__, result);
- break;
- }
- #endif
+ if (nullptr == object)
+ return;
+
+ eClasses class_ = object->getClass();
+
+ objLocks[class_].lock();
+
+ /// --- case 1: first of its kind ---
+ if (nullptr == tails[class_]) {
+ heads[class_] = object;
+ tails[class_] = object;
+ }
+
+ /// --- case 2: normal addition ---
+ else {
+ tails[class_]->next = object;
+ object->prev = tails[class_];
+ tails[class_] = object;
+ }
+
+ objLocks[class_].unlock();
}
-//unlock_curland() will unlock curland and print diagnostic messages if the unlock operation fails.
-void GLOBALDATA::unlock_curland()
+
+
+// Combine both make_update and make_bgupdate with safety checks for
+// the dimensions. This reduces code duplication.
+void GLOBALDATA::addUpdate(int32_t x, int32_t y, int32_t w, int32_t h,
+ BOX* target, int32_t &target_count)
{
- #ifdef THREADS
- int result = pthread_mutex_unlock(curland_lock);
- switch (result)
- {
- case 0:
- //Released the lock
- break;
- case EPERM:
- //Forgot to get a lock on the mutex
- printf("%s:%i: Mutex isn't locked\n", __FILE__, __LINE__);
- break;
- case EINVAL:
- //Uninitialized mutex
- printf("%s:%i: waiting_sky_lock is uninitialized.\n", __FILE__, __LINE__);
- break;
- default:
- //?
- printf("%s:%i: Unknown error code (%i) returned by pthread_mutex_unlock.\n", __FILE__, __LINE__, result);
- break;
- }
- #endif
+ assert (target && "ERROR: addUpdate called with nullptr target!");
+
+ bool combined = false;
+
+ assert ( (w > 0) && (h > 0) ); // No zero/negative updates, please!
+
+ int32_t left = std::max(x - 1, 0);
+ int32_t top = std::max(y - 1, 0);
+ int32_t right = std::min(x + w + 1, env.screenWidth);
+ int32_t bottom = std::min(y + h + 1, env.screenHeight);
+
+ // If the update is outside the screen, it is not needed:
+ if ( (bottom <= 0) /* most common case */
+ || (left >= env.screenWidth)
+ || (right <= 0)
+ || (top >= env.screenHeight) )
+ return;
+
+ assert( (left < right ) );
+ assert( (top < bottom) );
+
+ if ( combineUpdates && target_count
+ && (target_count < env.max_screen_updates)) {
+ // Re-purpose BOX::w as x2 and BOX::h as y2:
+ BOX prev(target[target_count - 1].x,
+ target[target_count - 1].y,
+ target[target_count - 1].x + target[target_count - 1].w,
+ target[target_count - 1].y + target[target_count - 1].h);
+ BOX next(left, top, right, bottom);
+
+ if ( (next.w > (prev.x - 3))
+ && (prev.w > (next.x - 3))
+ && (next.h > (prev.y - 3))
+ && (prev.h > (next.y - 3)) ) {
+ next.set(next.x < prev.x ? next.x : prev.x,
+ next.y < prev.y ? next.y : prev.y,
+ next.w > prev.w ? next.w : prev.w,
+ next.h > prev.h ? next.h : prev.h);
+ // recalculate x2/y2 back into w/h
+ target[target_count - 1].set(next.x, next.y,
+ next.w - next.x,
+ next.h - next.y);
+
+ // Make sure the target update is sane:
+ assert( (target[target_count - 1].w > 0)
+ && (target[target_count - 1].h > 0) );
+
+ combined = true;
+ }
+ }
+
+ if (!combined)
+ target[target_count++].set(left, top, right - left, bottom - top);
+
+ if (!stopwindow && (target_count <= env.max_screen_updates))
+ env.window_update(left, top, right - left, bottom - top);
}
-//get_curland() locks curland, reads the value, unlocks curland, then returns the value. The value returned cannot be assigned to.
-//Use this only for reading the value of curland, not for writing.
-//For writing, use lock_curland()/unlock_curland().
-int GLOBALDATA::get_curland()
+
+
+// return true if any living tank is in the given box.
+// left/right and top/bottom are determined automatically.
+bool GLOBALDATA::areTanksInBox(int32_t x1, int32_t y1, int32_t x2, int32_t y2)
{
- lock_curland();
- int c = curland;
- unlock_curland();
- return c;
+ TANK* lt = static_cast<TANK*>(heads[CLASS_TANK]);
+
+ while (lt) {
+ // Tank found, is it in the box?
+ if ( (!lt->destroy) && lt->isInBox(x1, y1, x2, y2))
+ return true;
+ lt->getNext(<);
+ }
+
+ return false;
}
-//Locks global->command for writing. Unlock with GLOBALDATA::unlock_command().
-void GLOBALDATA::wr_lock_command()
+
+
+// This function checks to see if one full second has passed since the
+// last time the function was called.
+// The function returns true if time has passed. The function
+// returns false if time hasn't passed or it was unable to tell
+// how much time has passed.
+bool GLOBALDATA::check_time_changed()
{
- #ifdef THREADS
- int result = pthread_rwlock_wrlock(command_lock);
- switch (result)
- {
- case 0:
- //Got the lock.
- break;
- case EINVAL:
- //No lock has been created yet
- printf("%s:%i: Read-write lock is uninitialized.\n", __FILE__, __LINE__);
- break;
- case EDEADLK:
- //We have the lock already
- printf("%s:%i: Can't lock for writing because the lock is already locked for either reading or writing.\n", __FILE__, __LINE__);
- break;
- default:
- //What error is this?
- printf("%s:%i: Unknown error code (%i) returned by pthread_rwlock_wrlock.\n", __FILE__, __LINE__, result);
- break;
- }
- #endif
+ volatile
+ static time_t last_second = 0;
+ static time_t current_second = 0;
+
+ time(¤t_second);
+
+ if ( current_second == last_second )
+ return false;
+
+ // time has changed
+ last_second = current_second;
+
+ return true;
}
-//Unlocks a read or a write lock. Do not try to use this function to unlock a lock acquired by calling get_command().
-void GLOBALDATA::unlock_command()
+
+
+/// @brief remove and delete *all* objects stored.
+void GLOBALDATA::clear_objects()
{
- #ifdef THREADS
- int result = pthread_rwlock_unlock(command_lock);
- switch (result)
- {
- case 0:
- //Released the lock
- break;
- case EPERM:
- //We have released all locks
- printf("%s:%i: Can't unlock because no read or write lock is currently being held.\n", __FILE__, __LINE__);
- break;
- case EINVAL:
- //Uninitialized read-write lock
- printf("%s:%i: global->command_lock is uninitialized.\n", __FILE__, __LINE__);
- break;
- default:
- //Something went wrong
- printf("%s:%i: Unknown error code (%i) returned by pthread_rwlock_unlock.\n", __FILE__, __LINE__, result);
- break;
- }
- #endif
+ int32_t class_ = 0;
+
+ while (class_ < CLASS_COUNT) {
+ while (tails[class_])
+ delete tails[class_];
+ ++class_;
+ }
}
-//Locks global->command for reading, reads value, then unlocks the variable and returns the value.
-int GLOBALDATA::get_command()
+
+
+// Call before calling allegro_exit()!
+void GLOBALDATA::destroy()
{
- #ifdef THREADS
- int result = pthread_rwlock_rdlock(command_lock);
- switch (result)
- {
- case 0:
- //Got the lock
- break;
- case EINVAL:
- //No lock has been created.
- printf("%s:%i: *global->command_lock is uninitialized.\n", __FILE__, __LINE__);
- break;
- case EAGAIN:
- //Can't read since already trying to read
- //Maybe locks are not being unlocked?
- printf("%s:%i: Too many read locks already held.\n", __FILE__, __LINE__);
- break;
- case EDEADLK:
- //Obtaining read locks while possessing a write lock is forbidden with read-write locks.
- printf("%s:%i: Already own write lock; can't get read lock.\n", __FILE__, __LINE__);
- break;
- default:
- //What error is this?
- printf("%s:%i: Unknown error code (%i) returned by pthread_rwlock_rdlock.\n", __FILE__, __LINE__, result);
- break;
- }
- #endif
- int c = command;
- unlock_command();
- return c;
+ clear_objects();
+
+ if (debris_pool) {
+ delete debris_pool;
+ debris_pool = nullptr;
+ }
+
+ if (canvas) destroy_bitmap(canvas); canvas = nullptr;
+ if (terrain) destroy_bitmap(terrain); terrain = nullptr;
+ if (done) delete [] done; done = nullptr;
+ if (fp) delete [] fp; fp = nullptr;
+ if (surface) delete [] surface; surface = nullptr;
+ if (dropTo) delete [] dropTo; dropTo = nullptr;
+ if (velocity) delete [] velocity; velocity = nullptr;
+ if (dropIncr) delete [] dropIncr; dropIncr = nullptr;
+ if (updates) delete [] updates; updates = nullptr;
+ if (lastUpdates) delete [] lastUpdates; lastUpdates = nullptr;
}
-/*
-This function saves the global data to a text file. If all goes
-well, TRUE is returned, on error, FALSE is returned.
--- Jesse
-*/
-int GLOBALDATA::saveToFile_Text( FILE *file)
+
+void GLOBALDATA::do_updates ()
{
- if (! file) return FALSE;
-
- setlocale(LC_NUMERIC, "C");
-
- screenWidth = (int) temp_screenWidth;
- screenHeight = (int) temp_screenHeight;
-
- fprintf (file, "*GLOBAL*\n");
-
- fprintf (file, "NUMPLAYERS=%d\n", numPlayers);
- fprintf (file, "ROUNDS=%f\n", rounds);
- fprintf (file, "DITHER=%f\n", ditherGradients);
- fprintf (file, "DETAILEDSKY=%f\n", detailedSky);
- fprintf (file, "DETAILEDLAND=%f\n", detailedLandscape);
- fprintf (file, "STARTMONEY=%f\n", startmoney);
- fprintf (file, "TURNTYPE=%f\n", turntype);
- fprintf (file, "INTEREST=%f\n", interest);
- fprintf (file, "SCOREROUNDWINBONUS=%f\n", scoreRoundWinBonus);
- fprintf (file, "SCOREHITUNIT=%f\n", scoreHitUnit);
- fprintf (file, "SCOREUNITDESTROYBONUS=%f\n", scoreUnitDestroyBonus);
- fprintf (file, "SCOREUNITSELFDESTROY=%f\n", scoreUnitSelfDestroy);
- fprintf (file, "ACCELERATEDAI=%f\n", skipComputerPlay);
- fprintf (file, "SELLPERCENT=%f\n", sellpercent);
- fprintf (file, "ENABLESOUND=%f\n", sound);
- fprintf (file, "SCREENWIDTH=%d\n", screenWidth);
- fprintf (file, "SCREENHEIGHT=%d\n", screenHeight);
- fprintf (file, "OSMOUSE=%f\n", os_mouse);
- fprintf (file, "NUMPERMANENTPLAYERS=%d\n", numPermanentPlayers);
- fprintf (file, "LANGUAGE=%f\n", language);
- fprintf (file, "COLOURTHEME=%f\n", colour_theme);
- fprintf (file, "FRAMES=%f\n", frames_per_second);
- fprintf (file, "VIOLENTDEATH=%f\n", violent_death);
- fprintf (file, "MAXFIRETIME=%f\n", max_fire_time);
- fprintf (file, "DIVIDEMONEY=%f\n", divide_money);
- fprintf (file, "CHECKUPDATES=%f\n", check_for_updates);
- fprintf (file, "NETWORKING=%f\n", enable_network);
- fprintf (file, "LISTENPORT=%f\n", listen_port);
- fprintf (file, "SOUNDDRIVER=%f\n", sound_driver);
- fprintf (file, "PLAYMUSIC=%f\n", play_music);
- fprintf (file, "FULLSCREEN=%f\n", full_screen);
- fprintf (file, "***\n");
- return TRUE;
+ bool isBgUpdNeeded = lastUpdatesCount > 0;
+
+ acquire_bitmap(screen);
+ for (int32_t i = 0; i < updateCount; ++i) {
+ blit( canvas, screen,
+ updates[i].x, updates[i].y, updates[i].x, updates[i].y,
+ updates[i].w, updates[i].h);
+
+ if (isBgUpdNeeded)
+ make_bgupdate( updates[i].x, updates[i].y,
+ updates[i].w, updates[i].h);
+ }
+ release_bitmap(screen);
+ if (!isBgUpdNeeded) {
+ lastUpdatesCount = updateCount;
+ memcpy (lastUpdates, updates, sizeof (BOX) * updateCount);
+ }
+ updateCount = 0;
}
-/*
-This function loads global settings from a text
-file. The function returns TRUE on success and FALSE if
-any erors are encountered.
--- Jesse
-*/
-int GLOBALDATA::loadFromFile_Text (FILE *file)
+// Do what has to be done after the game starts
+void GLOBALDATA::first_init()
{
- char line[MAX_CONFIG_LINE];
- int equal_position, line_length;
- char field[MAX_CONFIG_LINE], value[MAX_CONFIG_LINE];
- char *result = NULL;
- bool done = false;
- double sound_bookmark = 1.0;
-
- setlocale(LC_NUMERIC, "C");
- if (! sound)
- sound_bookmark = sound;
-
- // read until we hit line "*ENV*" or "***" or EOF
- do
- {
- result = fgets(line, MAX_CONFIG_LINE, file);
- if (! result) // eof
- return FALSE;
- if (! strncmp(line, (char *)"***", 3) ) // end of record
- return FALSE;
- }
- while ( strncmp(line, (char *)"*GLOBAL*", 5) ); // read until we hit new record
-
- while ( (result) && (!done) )
- {
- // read a line
- memset(line, '\0', MAX_CONFIG_LINE);
- result = fgets(line, MAX_CONFIG_LINE, file);
- if (result)
- {
- // if we hit end of the record, stop
- if (! strncmp(line, (char *)"***", 3) )
- {
- return TRUE;
- }
- // find equal sign
- line_length = strlen(line);
- // strip newline character
- if ( line[line_length - 1] == '\n')
- {
- line[line_length - 1] = '\0';
- line_length--;
- }
- equal_position = 1;
- while ( ( equal_position < line_length) && (line[equal_position] != '=') )
- equal_position++;
- // make sure we have valid equal sign
-
- if ( equal_position <= line_length )
- {
- // seperate field from value
- memset(field, '\0', MAX_CONFIG_LINE);
- memset(value, '\0', MAX_CONFIG_LINE);
- strncpy(field, line, equal_position);
- strcpy(value, & (line[equal_position + 1]));
- if (! strcasecmp(field, "numplayers") )
- sscanf(value, "%d", &numPlayers);
- else if (! strcasecmp(field, "rounds") )
- sscanf(value, "%lf", &rounds);
- else if (! strcasecmp(field, "dither"))
- sscanf(value, "%lf", &ditherGradients);
- else if (! strcasecmp(field, "detailedsky"))
- sscanf(value, "%lf", &detailedSky);
- else if (! strcasecmp(field, "detailedland"))
- sscanf(value, "%lf", &detailedLandscape);
- else if (! strcasecmp(field, "startmoney"))
- sscanf(value, "%lf", &startmoney);
- else if (! strcasecmp(field, "turntype"))
- sscanf(value, "%lf", &turntype);
- else if (! strcasecmp(field, "interest"))
- sscanf(value, "%lf", &interest);
- else if (! strcasecmp(field, "scoreroundwinbonus"))
- sscanf(value, "%lf", &scoreRoundWinBonus);
- else if (! strcasecmp(field, "scorehitunit"))
- sscanf(value, "%lf", &scoreHitUnit);
- else if (! strcasecmp(field, "scoreunitdestroybonus"))
- sscanf(value, "%lf", &scoreUnitDestroyBonus);
- else if (! strcasecmp(field, "scoreunitselfdestroy"))
- sscanf(value, "%lf", &scoreUnitSelfDestroy);
- else if (! strcasecmp(field, "acceleratedai"))
- sscanf(value, "%lf", &skipComputerPlay);
- else if (! strcasecmp(field, "sellpercent"))
- sscanf(value, "%lf", &sellpercent);
- else if (! strcasecmp(field, "enablesound"))
- sscanf(value, "%lf", &sound);
- else if (! strcasecmp(field, "screenwidth"))
- sscanf(value, "%d", &screenWidth);
- else if (! strcasecmp(field, "screenheight"))
- sscanf(value, "%d", &screenHeight);
- else if (! strcasecmp(field, "OSMOUSE"))
- sscanf(value, "%lf", &os_mouse);
- else if (! strcasecmp(field, "numpermanentplayers"))
- sscanf(value, "%d", &numPermanentPlayers);
- else if (! strcasecmp(field, "language") )
- sscanf(value, "%lf", &language);
- else if (! strcasecmp(field, "colourtheme") )
- sscanf(value, "%lf", &colour_theme);
- else if (! strcasecmp(field, "frames") )
- sscanf(value, "%lf", &frames_per_second);
- else if (! strcasecmp(field, "violentdeath") )
- sscanf(value, "%lf", &violent_death);
- else if (! strcasecmp(field, "maxfiretime") )
- sscanf(value, "%lf", &max_fire_time);
- else if (! strcasecmp(field, "dividemoney") )
- sscanf(value, "%lf", ÷_money);
- else if (!strcasecmp(field, "checkupdates"))
- sscanf(value, "%lf", &check_for_updates);
- else if (!strcasecmp(field, "networking"))
- sscanf(value, "%lf", &enable_network);
- else if (!strcasecmp(field, "listenport"))
- sscanf(value, "%lf", &listen_port);
- else if (!strcasecmp(field, "sounddriver"))
- sscanf(value, "%lf", &sound_driver);
- else if (!strcasecmp(field, "playmusic"))
- sscanf(value, "%lf", &play_music);
- else if (!strcasecmp(field, "fullscreen"))
- sscanf(value, "%lf", &full_screen);
-
- } // end of found field=value line
-
- } // end of read a line properly
- } // end of while not done
- if (! sound_bookmark)
- sound = sound_bookmark;
-
- if (width_override)
- screenWidth = width_override;
- if (height_override)
- screenHeight = height_override;
-
- halfWidth = screenWidth / 2;
- halfHeight = screenHeight / 2;
-
- menuBeginY = (screenHeight - 400) / 2;
- if (menuBeginY < 0) menuBeginY = 0;
- menuEndY = screenHeight - menuBeginY;
-
- if (skipComputerPlay > SKIP_HUMANS_DEAD)
- skipComputerPlay = SKIP_HUMANS_DEAD;
-
- return TRUE;
+ // get memory for updates
+ try {
+ updates = new BOX[env.max_screen_updates];
+ } catch (std::bad_alloc &e) {
+ cerr << "globaldata.cpp:" << __LINE__ << ":first_init() : "
+ << "Failed to allocate memory for updates ["
+ << e.what() << "]" << endl;
+ exit(1);
+ }
+
+ // get memory for lastUpdates
+ try {
+ lastUpdates = new BOX[env.max_screen_updates];
+ } catch (std::bad_alloc &e) {
+ cerr << "globaldata.cpp:" << __LINE__ << ":first_init() : "
+ << "Failed to allocate memory for lastUpdates ["
+ << e.what() << "]" << endl;
+ exit(1);
+ }
+
+ canvas = create_bitmap (env.screenWidth, env.screenHeight);
+ if (!canvas) {
+ cout << "Failed to create canvas bitmap: " << allegro_error << endl;
+ exit(1);
+ }
+
+ terrain = create_bitmap (env.screenWidth, env.screenHeight);
+ if (!terrain) {
+ cout << "Failed to create terrain bitmap: " << allegro_error << endl;
+ exit(1);
+ }
+
+
+ // get memory for the debris pool
+ try {
+ debris_pool = new sDebrisPool(env.max_screen_updates);
+ } catch (std::bad_alloc &e) {
+ cerr << "globaldata.cpp:" << __LINE__ << ":first_init() : "
+ << "Failed to allocate memory for debris_pool ["
+ << e.what() << "]" << endl;
+ exit(1);
+ }
+
+
+ try {
+ done = new int8_t[env.screenWidth]{0};
+ fp = new int32_t[env.screenWidth]{0};
+ surface = new ai32_t[env.screenWidth]{ { 0 } };
+ dropTo = new int32_t[env.screenWidth]{0};
+ velocity = new double[env.screenWidth]{0};
+ dropIncr = new double[env.screenWidth]{0};
+ } catch (std::bad_alloc &e) {
+ cerr << "globaldata.cpp:" << __LINE__ << ":first_init() : "
+ << "Failed to allocate memory for base data arrays ["
+ << e.what() << "]" << endl;
+ exit(1);
+ }
+
+ initialise ();
}
+/** @brief delegate freeing of a debris item to the debris pool.
+ *
+ * This delegating function, instead of making the debris pool public,
+ * exists as a point where locking, if it becomes necessary, can be
+ * added without having to rewrite a lot of code.
+**/
+void GLOBALDATA::free_debris_item(item_t* item)
+{
+ debris_pool->free_item(item);
+}
-void GLOBALDATA::initialise ()
+int32_t GLOBALDATA::get_avg_bgcolor(int32_t x1, int32_t y1,
+ int32_t x2, int32_t y2,
+ double xv, double yv)
{
- numTanks = 0;
+ // Movement
+ int32_t mvx = ROUND(10. * xv); // eliminate slow movement
+ int32_t mvy = ROUND(10. * yv); // eliminate slow movement
+ bool mv_left = mvx < 0;
+ bool mv_right = mvx > 0;
+ bool mv_up = mvy < 0;
+ bool mv_down = mvy > 0;
+
+ // Boundaries
+ int32_t min_x = 1;
+ int32_t max_x = env.screenWidth - 2;
+ int32_t min_y = env.isBoxed ? MENUHEIGHT + 1 : MENUHEIGHT;
+ int32_t max_y = env.screenHeight - 2;
+
+ // Coordinates
+ int32_t left = std::max(std::min(x1, x2), min_x);
+ int32_t right = std::min(std::max(x1, x2), max_x);
+ int32_t centre = (x1 + x2) / 2;
+ int32_t top = std::max(std::min(y1, y2), min_y);
+ int32_t bottom = std::min(std::max(y1, y2), max_y);
+ int32_t middle = (y1 + y2) / 2;
+
+
+ // Colors:
+ int32_t col_tl, col_tc, col_tr; // top row
+ int32_t col_ml, col_mc, col_mr; // middle row
+ int32_t col_bl, col_bc, col_br; // bottom row
+ int32_t r = 0, g = 0, b = 0;
+
+
+ // Get Sky or Terrain colour, whatever fits:
+ /*---------------------
+ --- Left side ---
+ ---------------------*/
+ if ( PINK == (col_tl = getpixel(terrain, left, top)) )
+ col_tl = getpixel(env.sky, left, top);
+ if ( PINK == (col_ml = getpixel(terrain, left, middle)) )
+ col_ml = getpixel(env.sky, left, middle);
+ if ( PINK == (col_bl = getpixel(terrain, left, bottom)) )
+ col_bl = getpixel(env.sky, left, bottom);
+
+ /*---------------------
+ --- The Center ---
+ ---------------------*/
+ if ( PINK == (col_tc = getpixel(terrain, centre, top)) )
+ col_tc = getpixel(env.sky, centre, top);
+ if ( PINK == (col_mc = getpixel(terrain, centre, middle)) )
+ col_mc = getpixel(env.sky, centre, middle);
+ if ( PINK == (col_bc = getpixel(terrain, centre, bottom)) )
+ col_bc = getpixel(env.sky, centre, bottom);
+
+ /*----------------------
+ --- Right side ---
+ ----------------------*/
+ if ( PINK == (col_tr = getpixel(terrain, right, top)) )
+ col_tr = getpixel(env.sky, right, top);
+ if ( PINK == (col_mr = getpixel(terrain, right, middle)) )
+ col_mr = getpixel(env.sky, right, middle);
+ if ( PINK == (col_br = getpixel(terrain, right, bottom)) )
+ col_br = getpixel(env.sky, right, bottom);
+
+
+ // Fetch the rgb parts, according to movement:
+
+ /* --- X-Movement --- */
+ if (mv_left) {
+ // Movement to the left, weight left side colour twice
+ r += (GET_R(col_tl) + GET_R(col_ml) + GET_R(col_bl)) * 2;
+ g += (GET_G(col_tl) + GET_G(col_ml) + GET_G(col_bl)) * 2;
+ b += (GET_B(col_tl) + GET_B(col_ml) + GET_B(col_bl)) * 2;
+ // The others are counted once
+ r += GET_R(col_tc) + GET_R(col_mc) + GET_R(col_bc)
+ + GET_R(col_tr) + GET_R(col_mr) + GET_R(col_br);
+ g += GET_G(col_tc) + GET_G(col_mc) + GET_G(col_bc)
+ + GET_G(col_tr) + GET_G(col_mr) + GET_G(col_br);
+ b += GET_B(col_tc) + GET_B(col_mc) + GET_B(col_bc)
+ + GET_B(col_tr) + GET_B(col_mr) + GET_B(col_br);
+ } else if (mv_right) {
+ // Movement to the right, weight right side colour twice
+ r += (GET_R(col_tr) + GET_R(col_mr) + GET_R(col_br)) * 2;
+ g += (GET_G(col_tr) + GET_G(col_mr) + GET_G(col_br)) * 2;
+ b += (GET_B(col_tr) + GET_B(col_mr) + GET_B(col_br)) * 2;
+ // The others are counted once
+ r += GET_R(col_tc) + GET_R(col_mc) + GET_R(col_bc)
+ + GET_R(col_tl) + GET_R(col_ml) + GET_R(col_bl);
+ g += GET_G(col_tc) + GET_G(col_mc) + GET_G(col_bc)
+ + GET_G(col_tl) + GET_G(col_ml) + GET_G(col_bl);
+ b += GET_B(col_tc) + GET_B(col_mc) + GET_B(col_bc)
+ + GET_B(col_tl) + GET_B(col_ml) + GET_B(col_bl);
+ } else {
+ // No x-movement, weight centre colour twice
+ r += (GET_R(col_tc) + GET_R(col_mc) + GET_R(col_bc)) * 2;
+ g += (GET_G(col_tc) + GET_G(col_mc) + GET_G(col_bc)) * 2;
+ b += (GET_B(col_tc) + GET_B(col_mc) + GET_B(col_bc)) * 2;
+ // The others are counted once
+ r += GET_R(col_tl) + GET_R(col_ml) + GET_R(col_bl)
+ + GET_R(col_tr) + GET_R(col_mr) + GET_R(col_br);
+ g += GET_G(col_tl) + GET_G(col_ml) + GET_G(col_bl)
+ + GET_G(col_tr) + GET_G(col_mr) + GET_G(col_br);
+ b += GET_B(col_tl) + GET_B(col_ml) + GET_B(col_bl)
+ + GET_B(col_tr) + GET_B(col_mr) + GET_B(col_br);
+ }
+
+ /* --- Y-Movement --- */
+ if (mv_up) {
+ // Movement upwards, weight top side colour twice
+ r += (GET_R(col_tl) + GET_R(col_tc) + GET_R(col_tr)) * 2;
+ g += (GET_G(col_tl) + GET_G(col_tc) + GET_G(col_tr)) * 2;
+ b += (GET_B(col_tl) + GET_B(col_tc) + GET_B(col_tr)) * 2;
+ // The others are counted once
+ r += GET_R(col_ml) + GET_R(col_mc) + GET_R(col_mr)
+ + GET_R(col_bl) + GET_R(col_bc) + GET_R(col_br);
+ g += GET_G(col_ml) + GET_G(col_mc) + GET_G(col_mr)
+ + GET_G(col_bl) + GET_G(col_bc) + GET_G(col_br);
+ b += GET_B(col_ml) + GET_B(col_mc) + GET_B(col_mr)
+ + GET_B(col_bl) + GET_B(col_bc) + GET_B(col_br);
+ } else if (mv_down) {
+ // Movement downwards, weight bottom side colour twice
+ r += (GET_R(col_bl) + GET_R(col_bc) + GET_R(col_br)) * 2;
+ g += (GET_G(col_bl) + GET_G(col_bc) + GET_G(col_br)) * 2;
+ b += (GET_B(col_bl) + GET_B(col_bc) + GET_B(col_br)) * 2;
+ // The others are counted once
+ r += GET_R(col_ml) + GET_R(col_mc) + GET_R(col_mr)
+ + GET_R(col_tl) + GET_R(col_tc) + GET_R(col_tr);
+ g += GET_G(col_ml) + GET_G(col_mc) + GET_G(col_mr)
+ + GET_G(col_tl) + GET_G(col_tc) + GET_G(col_tr);
+ b += GET_B(col_ml) + GET_B(col_mc) + GET_B(col_mr)
+ + GET_B(col_tl) + GET_B(col_tc) + GET_B(col_tr);
+ } else {
+ // No y-movement, weight middle colour twice
+ r += (GET_R(col_ml) + GET_R(col_mc) + GET_R(col_mr)) * 2;
+ g += (GET_G(col_ml) + GET_G(col_mc) + GET_G(col_mr)) * 2;
+ b += (GET_B(col_ml) + GET_B(col_mc) + GET_B(col_mr)) * 2;
+ // The others are counted once
+ r += GET_R(col_tl) + GET_R(col_tc) + GET_R(col_tr)
+ + GET_R(col_bl) + GET_R(col_bc) + GET_R(col_br);
+ g += GET_G(col_tl) + GET_G(col_tc) + GET_G(col_tr)
+ + GET_G(col_bl) + GET_G(col_bc) + GET_G(col_br);
+ b += GET_B(col_tl) + GET_B(col_tc) + GET_B(col_tr)
+ + GET_B(col_bl) + GET_B(col_bc) + GET_B(col_br);
+ }
+
+
+ /* I know this looks weird, but what we now have is some kind of summed
+ * matrix, which is always the same:
+ * Let's assume that xv and yv are both 0.0, so no movement is happening.
+ * The result is: (In counted times)
+ * 2|3|2 ( = 7)
+ * -+-+-
+ * 3|4|3 ( = 10)
+ * -+-+-
+ * 2|3|2 ( = 7)
+ * = 24
+ * And it is always 24, no matter which movement combination you try
+ */
+
+ r /= 24;
+ g /= 24;
+ b /= 24;
+
+ return makecol(r > 0xff ? 0xff : r,
+ g > 0xff ? 0xff : g,
+ b > 0xff ? 0xff : b);
}
-void GLOBALDATA::addPlayer (PLAYER *player)
+
+// Locks global->command for reading, reads value, then unlocks the variable
+// and returns the value.
+int32_t GLOBALDATA::get_command()
{
- if (numPlayers < MAXPLAYERS)
- {
- players[numPlayers] = player;
- numPlayers++;
- if ((int)player->type == HUMAN_PLAYER)
- {
- numHumanPlayers++;
- computerPlayersOnly = FALSE;
- }
- }
+ cmdLock.lock();
+ int32_t c = command;
+ cmdLock.unlock();
+ return c;
}
-void GLOBALDATA::removePlayer (PLAYER *player)
+
+TANK* GLOBALDATA::get_curr_tank()
{
- int fromCount = 0;
- int toCount = -1;
-
- if ((int)player->type == HUMAN_PLAYER)
- {
- numHumanPlayers--;
- if (numHumanPlayers == 0)
- {
- computerPlayersOnly = TRUE;
- }
- }
-
- while (fromCount < numPlayers)
- {
- if (player != players[fromCount])
- {
- if ((toCount >= 0) && (fromCount > toCount))
- {
- players[toCount] = players[fromCount];
- players[fromCount] = NULL;
- toCount++;
- }
- }
- else
- // Position found,1G now move the remaining players down!
- toCount = fromCount;
- fromCount++;
- }
- numPlayers--;
+ return currTank;
}
-PLAYER *GLOBALDATA::getNextPlayer (int *playerCount)
+
+/** @brief delegate getting a debris item to the debris pool.
+ *
+ * This delegating function, instead of making the debris pool public,
+ * exists as a point where locking, if it becomes necessary, can be
+ * added without having to rewrite a lot of code.
+**/
+sDebrisItem* GLOBALDATA::get_debris_item(int32_t radius)
{
- (*playerCount)++;
- if (*playerCount >= numPlayers)
- *playerCount = 0;
- return (players[*playerCount]);
+ return debris_pool->get_item(radius);
}
-PLAYER *GLOBALDATA::createNewPlayer (ENVIRONMENT *env)
+
+TANK* GLOBALDATA::get_next_tank(bool *wrapped_around)
{
- PLAYER **reallocatedPlayers;
- PLAYER *player;
-
- reallocatedPlayers = (PLAYER**)realloc (allPlayers, sizeof (PLAYER*) * (numPermanentPlayers + 1));
- if (reallocatedPlayers != NULL)
- {
- allPlayers = reallocatedPlayers;
- }
- else
- {
- perror ( (char *)"atanks.cc: Failed allocating memory for reallocatedPlayers in GLOBALDATA::createNewPlayer");
- // exit (1);
- }
- player = new PLAYER (this, env);
- if (!player)
- {
- perror ( (char *)"globaldata.cc: Failed allocating memory for player in GLOBALDATA::createNewPlayer");
- // exit (1);
- }
- allPlayers[numPermanentPlayers] = player;
- numPermanentPlayers++;
-
- return (player);
+ bool found = false;
+ int32_t index = tankindex + 1;
+ int32_t oldindex = tankindex;
+ int32_t wrapped = 0;
+
+ while (!found && (wrapped < 2)) {
+ if (index >= MAXPLAYERS) {
+ index = 0;
+ *wrapped_around = true;
+ wrapped++;
+ }
+
+ if ( order[index]
+ && (index != oldindex)
+ && !order[index]->destroy)
+ found = true;
+ else
+ ++index;
+ }
+
+ tankindex = index;
+
+ // If this tank is valid, the currently selected weapon must be checked
+ // first and changed if depleted
+ TANK* next_tank = order[index];
+ if (next_tank && next_tank->player)
+ next_tank->check_weapon();
+
+ // Whatever happened, the status bar needs an update:
+ if (oldindex != index)
+ updateMenu = true;
+
+ return next_tank;
}
-void GLOBALDATA::destroyPlayer (PLAYER *player)
+
+void GLOBALDATA::initialise ()
{
- int fromCount = 0;
- int toCount = 0;
-
- for (; fromCount < numPermanentPlayers; fromCount++)
- {
- if (allPlayers[fromCount] != player)
- {
- allPlayers[toCount] = allPlayers[fromCount];
- toCount++;
- }
- }
- numPermanentPlayers--;
+ clear_objects();
+ numTanks = 0;
+ clear_to_color (canvas, WHITE);
+ clear_to_color (terrain, PINK);
+
+ for (int32_t i = 0; i < env.screenWidth; ++i) {
+ done[i] = 0;
+ dropTo[i] = env.screenHeight - 1;
+ fp[i] = 0;
+ }
}
-
-// This function returns the path to the
-// config directory used by Atanks
-char *GLOBALDATA::Get_Config_Path()
+// return true if the dirt reaches into the given box.
+// left/right and top/bottom are determined automatically.
+bool GLOBALDATA::isDirtInBox(int32_t x1, int32_t y1, int32_t x2, int32_t y2)
{
- char *my_config_dir;
- char *homedir;
+ int32_t top = std::max(std::min(y1, y2),
+ env.isBoxed ? MENUHEIGHT + 1 : MENUHEIGHT);
+ // Exit early if the box is below the playing area
+ if (top >= env.screenHeight)
+ return false;
+
+ int32_t bottom = std::min(std::max(y1, y2), env.screenHeight - 2);
+ // Exit early if the box is over the playing area
+ if (bottom <= MENUHEIGHT)
+ return false;
+
+ int32_t left = std::max(std::min(x1, x2), 1);
+ int32_t right = std::min(std::max(x1, x2), env.screenWidth - 2);
+
+ // If the box is outside the playing area, this loop won't do anything
+ for (int32_t x = left; x <= right; ++x) {
+ if (surface[x].load(ATOMIC_READ) <= bottom)
+ return true;
+ }
+
+ return false;
+}
- // figure out file name
- homedir = getenv(HOME_DIR);
- if (! homedir)
- homedir = (char *)".";
- my_config_dir = (char *) calloc( strlen(homedir) + 24,
- sizeof(char) );
- if (! my_config_dir)
- return NULL;
- sprintf (my_config_dir, "%s/.atanks", homedir);
- return my_config_dir;
+/// @return true if the close button was pressed
+bool GLOBALDATA::isCloseBtnPressed()
+{
+ cbpLock.lock();
+ bool result = close_button_pressed;
+ cbpLock.unlock();
+ return result;
}
-
-// This function checks to see if one full second has passed since the
-// last time the function was called.
-// The function returns true if time has passed. The function
-// returns false if time hasn't passed or it was unable to tell
-// how much time has passed.
-bool GLOBALDATA::Check_Time_Changed()
+/** @brief load global data from a file
+ * This method is still present to provide backwards
+ * compatibility with configurations that were saved
+ * before the values were moved to ENVIRONMENT
+**/
+void GLOBALDATA::load_from_file (FILE* file)
{
- static time_t last_second = 0;
- time_t current_second;
+ char line[ MAX_CONFIG_LINE + 1] = { 0 };
+ char field[MAX_CONFIG_LINE + 1] = { 0 };
+ char value[MAX_CONFIG_LINE + 1] = { 0 };
+ char* result = nullptr;
+
+ setlocale(LC_NUMERIC, "C");
+
+ // read until we hit line "*GLOBAL*" or "***" or EOF
+ do {
+ result = fgets(line, MAX_CONFIG_LINE, file);
+ if ( !result
+ || !strncmp(line, "***", 3) )
+ // eof OR end of record
+ return;
+ } while ( strncmp(line, "*GLOBAL*", 8) );
+
+ bool done = false;
+
+ while (result && !done) {
+ // read a line
+ memset(line, '\0', MAX_CONFIG_LINE);
+ if ( ( result = fgets(line, MAX_CONFIG_LINE, file) ) ) {
+
+ // if we hit end of the record, stop
+ if (! strncmp(line, "***", 3) )
+ return;
+
+ // strip newline character
+ int32_t line_length = strlen(line);
+ while ( line[line_length - 1] == '\n') {
+ line[line_length - 1] = '\0';
+ line_length--;
+ }
+
+ // find equal sign
+ int32_t equal_position = 1;
+ while ( ( equal_position < line_length )
+ && ( line[equal_position] != '=' ) )
+ equal_position++;
+
+ // make sure the equal sign position is valid
+ if (line[equal_position] != '=')
+ continue; // Go to next line
+
+ // seperate field from value
+ memset(field, '\0', MAX_CONFIG_LINE);
+ memset(value, '\0', MAX_CONFIG_LINE);
+ strncpy(field, line, equal_position);
+ strncpy(value, &( line[equal_position + 1] ), MAX_CONFIG_LINE);
+
+
+ // Values that were moved to ENVIRONMENT:
+ // They are loaded, for compatibility, but the next
+ // save will put them into the correct section anyway.
+ // So these can eventually be removed.
+ if (!strcasecmp(field, "acceleratedai")) {
+ sscanf(value, "%d", &env.skipComputerPlay);
+ if (env.skipComputerPlay > SKIP_HUMANS_DEAD)
+ env.skipComputerPlay = SKIP_HUMANS_DEAD;
+ } else if (!strcasecmp(field, "checkupdates")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ env.check_for_updates = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "colourtheme") ) {
+ sscanf(value, "%d", &env.colourTheme);
+ if (env.colourTheme < CT_REGULAR) env.colourTheme = CT_REGULAR;
+ if (env.colourTheme > CT_CRISPY) env.colourTheme = CT_CRISPY;
+ } else if (!strcasecmp(field, "debrislevel") )
+ sscanf(value, "%d", &env.debris_level);
+ else if (!strcasecmp(field, "detailedland")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ env.detailedLandscape = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "detailedsky")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ env.detailedSky = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "dither")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ env.ditherGradients = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "dividemoney") ) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ env.divide_money = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "enablesound")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ env.sound_enabled = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "frames") ) {
+ int32_t new_fps = 0;
+ sscanf(value, "%d", &new_fps);
+ env.set_fps(new_fps);
+ } else if (!strcasecmp(field, "fullscreen"))
+ sscanf(value, "%d", &env.full_screen);
+ else if (!strcasecmp(field, "interest"))
+ sscanf(value, "%lf", &env.interest);
+ else if (!strcasecmp(field, "language") ) {
+ uint32_t stored_lang = 0;
+ sscanf(value, "%u", &stored_lang);
+ env.language = static_cast<eLanguages>(stored_lang);
+ } else if (!strcasecmp(field, "listenport"))
+ sscanf(value, "%d", &env.network_port);
+ else if (!strcasecmp(field, "maxfiretime") )
+ sscanf(value, "%d", &env.maxFireTime);
+ else if (!strcasecmp(field, "networking")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ env.network_enabled = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "numpermanentplayers"))
+ sscanf(value, "%d", &env.numPermanentPlayers);
+ else if (!strcasecmp(field, "OSMOUSE")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ env.osMouse = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "playmusic")) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ env.play_music = val > 0 ? true : false;
+ } else if (!strcasecmp(field, "rounds") )
+ sscanf(value, "%u", &env.rounds);
+ else if (!strcasecmp(field, "screenwidth")
+ && !env.temp_screenWidth) {
+ sscanf(value, "%d", &env.screenWidth);
+ env.halfWidth = env.screenWidth / 2;
+ env.temp_screenWidth = env.screenWidth;
+ } else if (!strcasecmp(field, "screenheight")
+ && !env.temp_screenHeight) {
+ sscanf(value, "%d", &env.screenHeight);
+ env.halfHeight = env.screenHeight / 2;
+ env.temp_screenHeight = env.screenHeight;
+ }
+ else if (!strcasecmp(field, "scorehitunit"))
+ sscanf(value, "%d", &env.scoreHitUnit);
+ else if (!strcasecmp(field, "scoreselfhit"))
+ sscanf(value, "%d", &env.scoreSelfHit);
+ else if (!strcasecmp(field, "scoreroundwinbonus"))
+ sscanf(value, "%d", &env.scoreRoundWinBonus);
+ else if (!strcasecmp(field, "scoreteamhit"))
+ sscanf(value, "%d", &env.scoreTeamHit);
+ else if (!strcasecmp(field, "scoreunitdestroybonus"))
+ sscanf(value, "%d", &env.scoreUnitDestroyBonus);
+ else if (!strcasecmp(field, "scoreunitselfdestroy"))
+ sscanf(value, "%d", &env.scoreUnitSelfDestroy);
+ else if (!strcasecmp(field, "sellpercent"))
+ sscanf(value, "%lf", &env.sellpercent);
+ else if (!strcasecmp(field, "sounddriver"))
+ sscanf(value, "%d", &env.sound_driver);
+ else if (!strcasecmp(field, "startmoney"))
+ sscanf(value, "%d", &env.startmoney);
+ else if (!strcasecmp(field, "turntype"))
+ sscanf(value, "%d", &env.turntype);
+ else if (!strcasecmp(field, "violentdeath") )
+ sscanf(value, "%d", &env.violent_death);
+ } // end of read a line properly
+ } // end of while not done
+}
- current_second = time(NULL);
- if ( current_second == last_second )
- return false;
- // time has changed
- last_second = current_second;
- return true;
+void GLOBALDATA::lockClass(eClasses class_)
+{
+ objLocks[class_].lock();
}
+void GLOBALDATA::lockLand()
+{
+ landLock.lock();
+}
+
-/*
- * This function Loads a music file (if there is one available.
- * A pointer to the music file is returned. If no music can
- * be found, then NULL is returned.
-*/
-SAMPLE *GLOBALDATA::Load_Background_Music()
+void GLOBALDATA::make_bgupdate (int32_t x, int32_t y, int32_t w, int32_t h)
{
- SAMPLE *my_sample = NULL;;
- struct dirent *folder_entry;
-
- // see if we should bother
- if (! play_music)
- return NULL;
-
- // see if we have the music folder open
- if (! music_dir)
- {
- char *buffer = (char *) calloc( strlen(configDir) + 32, sizeof(char) );
- if (! buffer)
- return NULL;
-
- sprintf(buffer, "%s/music", configDir);
- music_dir = opendir(buffer);
- free(buffer);
- if (! music_dir)
- return NULL;
- }
-
- // at this point we should have an open music folder
- // the music folder is closed by global's deconstructor
- // search for files ending in .wav
- folder_entry = readdir(music_dir);
- while ( (folder_entry) && (! my_sample) )
- {
- // we have something, see if it is a wav file
- if ( strstr(folder_entry->d_name, ".wav") )
- {
- char *filename = (char *) calloc( strlen(configDir) + strlen(folder_entry->d_name) + 64, sizeof(char));
- if (filename)
- {
- sprintf(filename, "%s/music/%s", configDir, folder_entry->d_name);
- my_sample = load_sample(filename);
- free(filename);
- }
- }
- if (! my_sample)
- folder_entry = readdir(music_dir);
- }
-
- if (! folder_entry) // hit end of folder
- {
- closedir(music_dir);
- music_dir = NULL;
- }
-
- return my_sample;
+ if (lastUpdatesCount >= env.max_screen_updates) {
+ make_fullUpdate();
+ return;
+ }
+
+ assert( (w > 0) && (h > 0) );
+
+ if ( (w > 0) && (h > 0) )
+ addUpdate(x, y, w, h, lastUpdates, lastUpdatesCount);
}
+void GLOBALDATA::make_fullUpdate()
+{
+ // Replace Updates with a full screen update:
+ combineUpdates = false;
+ updateCount = 0;
+ lastUpdatesCount = 0;
+
+ // They are split into 2 x 2 updates:
+ for (int32_t x = 0; x < 2; ++x) {
+ make_update( env.halfWidth * x, 0,
+ env.halfWidth, env.halfHeight);
+ make_bgupdate(env.halfWidth * x, 0,
+ env.halfWidth, env.halfHeight);
+ make_update( env.halfWidth * x, env.halfHeight,
+ env.halfWidth, env.halfHeight);
+ make_bgupdate(env.halfWidth * x, env.halfHeight,
+ env.halfWidth, env.halfHeight);
+ }
+
+ combineUpdates = true;
+}
-/*
- * This function sets all variables, which get written to the
- * config file, back to their defaults.
- * -- Jesse
- * */
-void GLOBALDATA::Reset_Options()
+void GLOBALDATA::make_update (int32_t x, int32_t y, int32_t w, int32_t h)
{
- ditherGradients = 1;
- detailedLandscape = 0;
- detailedSky = 0;
- interest = 1.25;
- scoreRoundWinBonus = 10000;
- scoreHitUnit = 75;
- scoreUnitDestroyBonus = 5000;
- scoreUnitSelfDestroy = 0;
- sellpercent = 0.80;
- startmoney = 15000;
- turntype = TURN_RANDOM;
- skipComputerPlay = SKIP_HUMANS_DEAD;
- sound = 1.0;
- screenWidth = DEFAULT_SCREEN_WIDTH;
- screenHeight = DEFAULT_SCREEN_HEIGHT;
- os_mouse = 1.0;
- language = LANGUAGE_ENGLISH;
- colour_theme = COLOUR_THEME_CRISPY;
- frames_per_second = FRAMES_PER_SECOND;
- violent_death = FALSE;
- max_fire_time = 0.0;
- divide_money = 0.0;
- check_for_updates = 1.0;
- enable_network = 0.0;
- #ifdef NETWORK
- listen_port = DEFAULT_LISTEN_PORT;
- #endif
- play_music = 1.0;
-}
+ if (updateCount >= env.max_screen_updates) {
+ make_fullUpdate();
+ return;
+ }
+ // These asserts should catch screwed updates that make no sense
+ assert( (h <= env.screenHeight) && (w <= env.screenWidth) );
+ assert( (w > 0) && (h > 0) );
+ if ( (h > 0) && (w > 0) )
+ addUpdate(x, y, w, h, updates, updateCount);
+}
-/*
- * This function loads all sounds from the data folder and saves them
- * in an array.
- * The function returns TRUE on success or FALSE if an error happens.
-*/
-int GLOBALDATA::Load_Sounds()
+void GLOBALDATA::newRound()
{
- int file_count = 0, array_size = 10;
- char *file_name;
- FILE *my_file;
- SAMPLE *temp_sample;
-
- file_name = (char *) calloc( strlen(dataDir) + 128, sizeof(char) );
- if (! file_name)
- return FALSE;
-
- // allocate space for sound samples
- sounds = (SAMPLE **) calloc(10, sizeof(SAMPLE *) );
- if (! sounds)
- {
- free(file_name);
- printf("Unable to create sound array.\n");
- return FALSE;
- }
-
- // read from directory
- sprintf(file_name, "%s/sound/%d.wav", dataDir, file_count);
- my_file = fopen(file_name, "r");
- while ( (my_file) && (sounds) )
- {
- fclose(my_file);
- temp_sample = load_sample(file_name);
- if (! temp_sample )
- printf("An error occured loading sound file %s\n", file_name);
- sounds[file_count] = temp_sample;
- file_count++;
-
- // make sure we have enough memory for more samples
- if (file_count >= array_size)
- {
- array_size += 10;
- sounds = (SAMPLE**) realloc(sounds, sizeof(SAMPLE*) * (array_size + 1));
- if (! sounds)
- printf("We just ran out of memory loading sound files.\n");
- else
- {
- // zero out new memory pointers
- int counter;
- for (counter = file_count; counter <= array_size; counter++)
- sounds[counter] = NULL;
- }
- }
- sprintf(file_name, "%s/sound/%d.wav", dataDir, file_count);
- my_file = fopen(file_name, "r");
- }
-
- free(file_name);
- return TRUE;
+ if ( (currentround > 0) && (currentround-- < env.nextCampaignRound) )
+ env.nextCampaignRound -= env.campaign_rounds;
+
+ tankindex = 0;
+ naturals_activated = 0;
+ combineUpdates = true;
+
+ // clean all but texts and tanks
+ int32_t class_ = 0;
+ while (class_ < CLASS_COUNT) {
+ if ( (CLASS_FLOATTEXT != class_) && (CLASS_TANK != class_) ) {
+ while (tails[class_])
+ delete tails[class_];
+ }
+ ++class_;
+ }
+
+
+ // Re-init land slide
+ for (int32_t i = 0; i < env.screenWidth; ++i) {
+ done[i] = 2; // Check at once
+ dropTo[i] = env.screenHeight - 1;
+ fp[i] = 0;
+ }
+
+ // Init order array
+ for (int32_t i = 0; i < MAXPLAYERS; ++i)
+ order[i] = nullptr;
}
-
-/*
- * This function loads all the bitmaps needed by th game.
- * Bitmaps are found in a series of sub-folders under the
- * dataDir. The function returns TRUE on success and FALSE
- * if an error occures.
-*/
-int GLOBALDATA::Load_Bitmaps()
+/// @brief Tell global that the close button was pressed
+void GLOBALDATA::pressCloseButton()
{
- int file_group = 0;
- int array_size, file_count;
- char *file_name;
- char sub_folder[64];
- FILE *my_file;
- BITMAP *new_bitmap, **bitmap_array;
-
- file_name = (char *) calloc( strlen(dataDir) + 128, sizeof(char) );
- if (!file_name)
- return FALSE;
-
- while (file_group < 7)
- {
- // set the folder we're looking at
- switch (file_group)
- {
- case 0: strcpy(sub_folder, "title"); break;
- case 1: strcpy(sub_folder, "button"); break;
- case 2: strcpy(sub_folder, "misc"); break;
- case 3: strcpy(sub_folder, "missile"); break;
- case 4: strcpy(sub_folder, "stock"); break;
- case 5: strcpy(sub_folder, "tank"); break;
- case 6: strcpy(sub_folder, "tankgun"); break;
- }
-
- // set up empty array
- array_size = 10;
- bitmap_array = (BITMAP **) calloc(10, sizeof(BITMAP *) );
- if (! bitmap_array)
- {
- printf("Ran out of memory, loading bitmaps.\n");
- free(file_name);
- return FALSE;
- }
-
- // search for files
- file_count = 0;
- sprintf(file_name, "%s/%s/%d.bmp", dataDir, sub_folder, file_count);
- my_file = fopen(file_name, "r");
- while ( (my_file) && (bitmap_array) )
- {
- fclose(my_file);
- new_bitmap = load_bitmap(file_name, NULL);
- if (! new_bitmap)
- printf("An error occured loading bitmap %s\n", file_name);
- bitmap_array[file_count] = new_bitmap;
- file_count++;
-
- // make sure array is large enough
- if ( file_count >= array_size)
- {
- array_size += 10;
- bitmap_array = (BITMAP **) realloc(bitmap_array, sizeof(BITMAP *) * (array_size + 1) );
- if (! bitmap_array)
- printf("Unable to increase array size while loading bitmaps.\n");
- else
- {
- // clear memory
- int count;
- for (count = file_count; count <= array_size; count++)
- bitmap_array[count] = NULL;
- }
- }
-
- // get next file
- sprintf(file_name, "%s/%s/%d.bmp", dataDir, sub_folder, file_count);
- my_file = fopen(file_name, "r");
- }
-
- // save the new array
- switch (file_group)
- {
- case 0: title = bitmap_array; break;
- case 1: button = bitmap_array; break;
- case 2: misc = bitmap_array; break;
- case 3: missile = bitmap_array; break;
- case 4: stock = bitmap_array; break;
- case 5: tank = bitmap_array; break;
- case 6: tankgun = bitmap_array; break;
- }
-
- file_group++;
- }
-
- free(file_name);
- return TRUE;
+ cbpLock.lock();
+ close_button_pressed = true;
+ cbpLock.unlock();
+ set_command(GLOBAL_COMMAND_QUIT);
}
-
-// This file loads in extra fonts the game requires.
-// Fonts should be stored in the datafolder. On
-// success the function returns TRUE. When an
-// error occures, it returns FALSE.
-int GLOBALDATA::Load_Fonts()
+void GLOBALDATA::removeObject (vobj_t *object)
{
- char *filename;
-
- filename = (char *) calloc( strlen(dataDir) + 32, sizeof(char));
- if (!filename)
- return FALSE;
-
- sprintf(filename, "%s/unicode.dat", dataDir);
- unicode = load_font(filename, NULL, NULL);
- if (! unicode)
- printf("Unable to load font %s\n", filename);
- free(filename);
-
- if (unicode)
- {
- Change_Font();
- return TRUE;
- }
- else return FALSE;
+ if (nullptr == object)
+ return;
+
+ eClasses class_ = object->getClass();
+
+ /// --- 1: Is the list empty? ---
+ if (nullptr == heads[class_])
+ return;
+
+ objLocks[class_].lock();
+
+ /// --- 2: If the object is head, set it anew:
+ if (object == heads[class_])
+ heads[class_] = object->next;
+
+ /// --- 4: If the object is tail, set it anew:
+ if (object == tails[class_])
+ tails[class_] = object->prev;
+
+ /// --- 5: Take it out of the list:
+ if (object->prev)
+ object->prev->next = object->next;
+ if (object->next)
+ object->next->prev = object->prev;
+ object->prev = nullptr;
+ object->next = nullptr;
+
+ objLocks[class_].unlock();
}
-// This function selects the font to use. This should be called
-// right after a language change.
-void GLOBALDATA::Change_Font()
+void GLOBALDATA::removeTank(TANK* tank)
{
- // FONT *temp_font = font;
-
- if ( (language == LANGUAGE_RUSSIAN) || (language == LANGUAGE_GERMAN) )
- font = unicode;
- else
- font = regular_font;
-
- // if (temp_font != font) // font has changed
- // {
- Load_Weapons_Text(this);
- Load_Text_Files();
- // }
-}
+ if (nullptr == tank)
+ return;
+ for (int32_t i = 0 ; i < MAXPLAYERS ; ++i) {
+ if (tank == order[i])
+ order[i] = nullptr;
+ }
+}
-void GLOBALDATA::Update_Player_Menu()
+void GLOBALDATA::replace_canvas ()
{
- int index;
- for (index = 0; index < numPermanentPlayers; index++)
- {
- if (allPlayers[index])
- allPlayers[index]->initMenuDesc();
- }
+ for (int32_t i = 0; i < lastUpdatesCount; ++i) {
+ if ((lastUpdates[i].y + lastUpdates[i].h) > MENUHEIGHT) {
+ blit (env.sky, canvas, lastUpdates[i].x, lastUpdates[i].y - MENUHEIGHT,
+ lastUpdates[i].x, lastUpdates[i].y,
+ lastUpdates[i].w, lastUpdates[i].h);
+ masked_blit (terrain, canvas, lastUpdates[i].x, lastUpdates[i].y,
+ lastUpdates[i].x, lastUpdates[i].y,
+ lastUpdates[i].w, lastUpdates[i].h);
+ } // End of having an update below the top bar
+ }
+
+ int32_t l = 0;
+ int32_t r = env.screenWidth - 1;
+ int32_t t = MENUHEIGHT;
+ int32_t b = env.screenHeight - 1;
+
+ vline(canvas, l, t, b, env.wallColour); // Left edge
+ vline(canvas, l + 1, t, b, env.wallColour); // Left edge
+ vline(canvas, r, t, b, env.wallColour); // right edge
+ vline(canvas, r - 1, t, b, env.wallColour); // right edge
+ hline(canvas, l, b, r, env.wallColour); // bottom edge
+ if (env.isBoxed)
+ hline(canvas, l, t, r, env.wallColour); // top edge
+
+ lastUpdatesCount = 0;
}
+// Set a new command, lock guarded
+void GLOBALDATA::set_command(int32_t cmd)
+{
+ cmdLock.lock();
+ command = cmd;
+ cmdLock.unlock();
+}
-// This function loads all needed text files, based on
-// language, into memory. If a previous text was loaded, it is
-// removed from memory first.
-int GLOBALDATA::Load_Text_Files()
+void GLOBALDATA::set_curr_tank(TANK* tank_)
{
- char *filename;
- char suffix[32];
-
- filename = (char *) calloc( strlen(dataDir) + 64, sizeof(char) );
- if (! filename)
- return FALSE;
-
- if (war_quotes) delete war_quotes;
- if (instructions) delete instructions;
- if (gloat) delete gloat;
- if (revenge) delete revenge;
- if (retaliation) delete retaliation;
- if (suicide) delete suicide;
- if (kamikaze) delete kamikaze;
- if (ingame) delete ingame;
-
- if (language == LANGUAGE_RUSSIAN)
- sprintf(filename, "%s/text/war_quotes_ru.txt", dataDir);
- else if (language == LANGUAGE_SPANISH)
- sprintf(filename, "%s/text/war_quotes_ES.txt", dataDir);
- else
- sprintf(filename, "%s/text/war_quotes.txt", dataDir);
- war_quotes = new TEXTBLOCK(filename);
-
- if (language == LANGUAGE_PORTUGUESE)
- strcpy(suffix, ".pt_BR.txt");
- else if (language == LANGUAGE_FRENCH)
- strcpy(suffix, "_fr.txt");
- else if (language == LANGUAGE_GERMAN)
- strcpy(suffix, "_de.txt");
- else if (language == LANGUAGE_SLOVAK)
- strcpy(suffix, "_sk.txt");
- else if (language == LANGUAGE_RUSSIAN)
- strcpy(suffix, "_ru.txt");
- else if (language == LANGUAGE_SPANISH)
- strcpy(suffix, "_ES.txt");
- else if (language == LANGUAGE_ITALIAN)
- strcpy(suffix, "_it.txt");
- else
- strcpy(suffix, ".txt"); // default to english
-
- sprintf(filename, "%s/text/instr%s", dataDir, suffix);
- instructions = new TEXTBLOCK(filename);
-
- sprintf(filename, "%s/text/gloat%s", dataDir, suffix);
- gloat = new TEXTBLOCK(filename);
- sprintf(filename, "%s/text/revenge%s", dataDir, suffix);
- revenge = new TEXTBLOCK(filename);
- sprintf(filename, "%s/text/retaliation%s", dataDir, suffix);
- retaliation = new TEXTBLOCK(filename);
- sprintf(filename, "%s/text/suicide%s", dataDir, suffix);
- suicide = new TEXTBLOCK(filename);
- sprintf(filename, "%s/text/kamikaze%s", dataDir, suffix);
- kamikaze = new TEXTBLOCK(filename);
- sprintf(filename, "%s/text/ingame%s", dataDir, suffix);
- ingame = new TEXTBLOCK(filename);
-
- free(filename);
- return TRUE;
+ if (tank_ != currTank) {
+ if (currTank)
+ currTank->deactivate();
+ currTank = tank_;
+ if (currTank)
+ currTank->activate();
+ }
}
-#ifdef NETWORK
-// This function sends a message to all connected game clients.
-// Returns TRUE on success or FALSE if the message could not be sent
-int GLOBALDATA::Send_To_Clients(char *message)
+/** @brief go through the land and slide what is to be slid and is not locked
+ * Slide land basic control is done using the 'done[]' array.
+ * done[x] == 0 : Nothing to do. All values assumed to be correct.
+ * done[x] == 1 : This column is currently in sliding.
+ * done[x] == 2 : This column is about to be slid, but the base values aren't set.
+ * done[x] == 3 : This column is about to be slid but locked. (Explosion not done)
+**/
+void GLOBALDATA::slideLand()
{
- int index;
- int message_length;
-
- if (! message) return FALSE;
- message_length = strlen(message);
- for (index = 0; index < numPlayers; index++)
- {
- if ( (players[index]) && (players[index]->type == NETWORK_CLIENT) )
- write(players[index]->server_socket, message, message_length);
- } // done all players
- return TRUE;
+ // Opt out soon if no landslide is to be done
+ if ( (SLIDE_NONE == env.landSlideType)
+ || (SLIDE_TANK_ONLY == env.landSlideType)
+ || ( (SLIDE_CARTOON == env.landSlideType)
+ && (env.time_to_fall > 0) ) )
+ return;
+
+ for (int32_t col = 1; col < (env.screenWidth - 1); ++col) {
+
+ // Skip this column if it is done or locked
+ if (!done[col] || (3 == done[col]))
+ continue;
+
+ // Set base settings if this hasn't happen, yet
+ if (2 == done[col]) {
+ surface[col].store(0, ATOMIC_WRITE);
+ dropTo[col] = env.screenHeight - 1;
+ done[col] = 1;
+
+ // Calc the top and bottom of the column to slide
+
+ // Find top-most non-PINK pixel
+ int32_t row = MENUHEIGHT + (env.isBoxed ? 1 : 0);
+
+ for ( ;(row < dropTo[col])
+ && (PINK == getpixel(terrain, col, row));
+ ++row) ;
+ surface[col].store(row, ATOMIC_WRITE); // This is the top pixel with all gaps
+
+ // Find bottom-most PINK pixel
+ int32_t top_row = row;
+ for (row = dropTo[col];
+ (row > top_row)
+ && (PINK != getpixel(terrain, col, row));
+ --row) ;
+ dropTo[col] = row;
+
+ // Find bottom-most unsupported pixel
+ for ( ;(row >= top_row)
+ && (PINK == getpixel(terrain, col, row));
+ --row) ;
+
+ // Check whether there is anything to do or not
+ if ((row >= top_row) && (top_row < dropTo[col])) {
+ fp[col] = row - top_row + 1;
+ velocity[col] = 0; // Not yet
+ done[col] = 1; // Can be processed
+ }
+
+ // Otherwise this column is done
+ else {
+ if ( !skippingComputerPlay
+ && (velocity[col] > .5)
+ && (fp[col] > 1) )
+ play_natural_sound(DIRT_FRAGMENT, col, 64,
+ 1000 - (fp[col] * 800 / env.screenHeight));
+ done[col] = 0; // Nothing to do
+ fp[col] = 0;
+ }
+ } // End of preparations
+
+ // Do the slide if possible
+ if (1 == done[col]) {
+
+ // Only slide if no neighbours are locked
+ bool can_slide = true;
+ for (int32_t j = col - 1; can_slide && (j > 0) ; --j) {
+ if (3 == done[j])
+ can_slide = false;
+ else if (!done[j])
+ j = 0; // no further look needed.
+ }
+ for (int32_t j = col + 1; can_slide && (j < (env.screenWidth - 1)) ; ++j) {
+ if (3 == done[j])
+ can_slide = false;
+ else if (!done[j])
+ j = env.screenWidth; // no further look needed.
+ }
+
+ if (can_slide) {
+ // Do instant first, because only GRAVITY remains
+ // which is the case if cartoon wait time is over.
+ if ( (SLIDE_INSTANT == env.landSlideType) || skippingComputerPlay) {
+ int32_t surf = surface[col].load(ATOMIC_READ);
+ make_bgupdate (col, surf, 1, dropTo[col] - surf + 1);
+ make_update (col, surf, 1, dropTo[col] - surf + 1);
+ blit (terrain, terrain, col, surf, col,
+ dropTo[col] - fp[col] + 1, 1, fp[col]);
+ vline(terrain, col, surf, dropTo[col] - fp[col], PINK);
+ velocity[col] = fp[col]; // Or no sound would be played if done
+ done[col] = 2; // Recheck
+ } else {
+ velocity[col] += env.gravity;
+ dropIncr[col] += velocity[col];
+
+ int32_t dropAdd = ROUND(dropIncr[col]);
+ int32_t max_top = MENUHEIGHT + (env.isBoxed ? 1 : 0);
+
+ if (dropAdd > 0) {
+
+ int32_t top_row = surface[col].load(ATOMIC_READ);
+
+ assert( (top_row >= 0)
+ && (top_row < terrain->h)
+ && "ERROR: top_row out of range!");
+
+ // If the top pixel is not PINK, and the source is not
+ // too high, increase dropAdd:
+ int32_t over_top = top_row - dropAdd;
+ while ( ( over_top <= max_top)
+ && ( over_top > 0)
+ && ( PINK != getpixel(terrain, col, over_top) ) ) {
+ ++dropAdd;
+ --over_top;
+ }
+
+ if (dropAdd > (dropTo[col] - (top_row + fp[col])) ) {
+ dropAdd = static_cast<int32_t>(dropTo[col]
+ - (top_row + fp[col])
+ + 1);
+ dropIncr[col] = dropAdd;
+ done[col] = 2; // Recheck
+ over_top = top_row - dropAdd;
+ }
+
+ int32_t slide_height = fp[col] + dropAdd;
+
+ assert( (over_top >= 0)
+ && (over_top < terrain->h)
+ && "ERROR: top_row - dropAdd out of range!");
+ assert( (slide_height > 0)
+ && (slide_height <= terrain->h)
+ && "ERROR: slide_height out of range!");
+ assert( ( (over_top + slide_height) <= terrain->h)
+ && "ERROR: over_top + slide_height is out of range!");
+ assert( ( (top_row + slide_height) <= terrain->h)
+ && "ERROR: over_top + slide_height is out of range!");
+
+ blit (terrain, terrain,
+ col, over_top,
+ col, top_row, 1,
+ slide_height);
+ make_bgupdate(col, over_top, 1,
+ slide_height + dropAdd + 1);
+ make_update (col, over_top, 1,
+ slide_height + dropAdd + 1);
+ // If the top row reaches to the ceiling, there might
+ // not be a PINK pixel to blit. In that case, one has
+ // to be painted "by hand", or the slide will produce
+ // nice long columns. (Happens with dirt balls when
+ // "fixed" under the menubar.
+ if (over_top <= max_top) {
+ putpixel(terrain, col, max_top, PINK);
+ putpixel(terrain, col, max_top + 1, PINK);
+ }
+
+ surface[col].fetch_add(dropAdd);
+ dropIncr[col] -= dropAdd;
+ }
+ }
+ }
+ } // End of actual slide
+ } // End of looping columns
}
-#endif
-
-// This function tries to figure out where the dataDir is. It
-// first checks the current working directory, ".". If the
-// proper files are not found, then we try the defined value of
-// DATA_DIR.
-// On success, TRUE is returned. If no usable directory is
-// found, then FALSE is returned.
-int GLOBALDATA::Find_Data_Dir()
+void GLOBALDATA::unlockClass(eClasses class_)
{
- char *current_dir = NULL;
- FILE *my_phile;
-
- current_dir = (char *) calloc( strlen(DATA_DIR) + 32, sizeof(char));
- if (! current_dir)
- return FALSE;
-
- // try current dir
- strcpy(current_dir, "./unicode.dat"); // local dir
- my_phile = fopen(current_dir, "r");
- if (my_phile)
- {
- fclose(my_phile);
- free(current_dir);
- dataDir = ".";
- return TRUE;
- }
-
- // try system dir
- sprintf(current_dir, "%s/unicode.dat", DATA_DIR);
- my_phile = fopen(current_dir, "r");
- if (my_phile)
- {
- fclose(my_phile);
- free(current_dir);
- dataDir = DATA_DIR;
- return TRUE;
- }
-
- dataDir = DATA_DIR; // fall back
- free(current_dir);
- return FALSE;
+ objLocks[class_].unlock();
}
+void GLOBALDATA::unlockLand()
+{
+ landLock.unlock();
+}
-/*
-Find the max velocity of a missile
-*/
-double GLOBALDATA::Calc_Max_Velocity()
+
+/// @brief goes through the columns from @a left to @a right and unlocks what is locked.
+void GLOBALDATA::unlockLandSlide(int32_t left, int32_t right)
{
- dMaxVelocity = (double) MAX_POWER * (100.0 / (double) frames_per_second) / 100.0;
- return dMaxVelocity;
+ // Opt out soon if no landslide is to be done
+ if ( (SLIDE_NONE == env.landSlideType)
+ || (SLIDE_TANK_ONLY == env.landSlideType) )
+ return;
+
+ int32_t minX = std::min(left, right);
+ int32_t maxX = std::max(left, right);
+
+ if (minX < 1)
+ minX = 1;
+ if (maxX > (env.screenWidth - 1) )
+ maxX = env.screenWidth - 1;
+
+ for (int32_t col = minX; col <= maxX; ++col) {
+ if ((done[col] > 2) || !done[col])
+ done[col] = 2;
+ }
}
+#ifndef USE_MUTEX_INSTEAD_OF_SPINLOCK
+/// === Spin Lock Implementations ===
-/* See how many humans and networked players we have */
-int GLOBALDATA::Count_Humans()
+/// @brief Default ctor
+CSpinLock::CSpinLock() :
+ is_destroyed(ATOMIC_VAR_INIT(false))
{
- int count;
- int humans = 0;
-
- for (count = 0; count < numPlayers; count++)
- {
- if (players[count]->type == HUMAN_PLAYER)
- humans++;
- else if (players[count]->type == NETWORK_CLIENT)
- humans++;
- }
-
- return humans;
+ lock_flag.clear(); // Done this way, because VC++ can't do it normally.
+ owner_id = std::thread::id();
}
-/*
-This function checks to see if there is a single player left standing.
-If there is, that player's index is returned. If there is no winner, then
--1 is returned. If all tanks have been destroyed, then -2 is returned.
-*/
-int GLOBALDATA::Check_For_Winner()
+/// @brief destructor - mark as destroyed, lock and go
+CSpinLock::~CSpinLock()
{
- int index = 0, tank_count = 0;
- int last_alive = -1;
-
- while ( (index < numPlayers) && (tank_count < 2) )
- {
- if (players[index]->tank)
- {
- last_alive = index;
- tank_count++;
- }
- index++;
- }
-
- if ( (last_alive >= 0) && (tank_count == 1) )
- return last_alive;
- else if (tank_count > 1)
- return -1;
- else // all dead
- return -2;
+ std::thread::id this_id = std::this_thread::get_id();
+ bool need_lock = (owner_id != this_id);
+
+ if (need_lock)
+ lock();
+ is_destroyed.store(true);
+ if (need_lock)
+ unlock();
}
-/*
-This function gives credits, score and money to the winner(s).
-*/
-void GLOBALDATA::Credit_Winners(int winner)
+/// @brief return true if this thread has an active lock
+bool CSpinLock::hasLock()
+{
+ // This works, because unlock() sets the owner_id to -1.
+ return (std::this_thread::get_id() == owner_id);
+}
+
+
+/** @brief Get a lock
+ * Warning: No recursive locking possible! Only lock once!
+**/
+void CSpinLock::lock()
+{
+ std::thread::id this_id = std::this_thread::get_id();
+ assert( (owner_id != this_id) && "ERROR: Lock already owned!");
+
+ if (false == is_destroyed.load(ATOMIC_READ)) {
+ while (lock_flag.test_and_set()) {
+ std::this_thread::yield();
+ }
+ owner_id = this_id;
+ }
+}
+
+/// @brief unlock if this thread owns the lock. Otherwise do nothing.
+void CSpinLock::unlock()
{
- int team_members = 0;
- int index;
-
- if (winner < 0) // no winner
- return;
-
- if (winner == JEDI_WIN)
- {
- for (index = 0; index < numPlayers; index++)
- {
- if (players[index]->team == TEAM_JEDI)
- {
- players[index]->score++;
- players[index]->won++;
- team_members++;
- }
- }
-
- }
-
- else if (winner == SITH_WIN)
- {
- for (index = 0; index < numPlayers; index++)
- {
- if (players[index]->team == TEAM_SITH)
- {
- players[index]->score++;
- players[index]->won++;
- team_members++;
- }
- }
- }
- else // credit single winner
- {
- players[winner]->score++;
- players[winner]->won++;
- players[winner]->money += (int) scoreRoundWinBonus;
- }
-
- // team gets their money too
- if (team_members)
- {
- int team_bonus = (int) scoreRoundWinBonus / team_members;
- for (index = 0; index < numPlayers; index++)
- {
- if ( ((winner == JEDI_WIN) && (players[index]->team == TEAM_JEDI) ) ||
- ((winner == SITH_WIN) && (players[index]->team == TEAM_SITH) ) )
- players[index]->money += team_bonus;
- }
- }
+ std::thread::id this_id = std::this_thread::get_id();
+ assert( (owner_id == this_id) && "ERROR: Lock *NOT* owned!");
+
+ if (owner_id == this_id) {
+ owner_id = std::thread::id();
+ lock_flag.clear(std::memory_order_release);
+ }
}
+#endif // USE_MUTEX_INSTEAD_OF_SPINLOCK
+
diff --git a/src/globaldata.h b/src/globaldata.h
index 020b00b..4a65410 100644
--- a/src/globaldata.h
+++ b/src/globaldata.h
@@ -17,221 +17,229 @@
* 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.
- * */
-
-#include <sys/types.h>
-#include <dirent.h>
+ *
+ */
#include "main.h"
-#include "text.h"
-#define DEFAULT_SCREEN_WIDTH 800
-#define DEFAULT_SCREEN_HEIGHT 600
-#define LANGUAGE_ENGLISH 0.0
-#define LANGUAGE_PORTUGUESE 1.0
-#define LANGUAGE_FRENCH 2.0
-#define LANGUAGE_GERMAN 3.0
-#define LANGUAGE_SLOVAK 4.0
-#define LANGUAGE_RUSSIAN 5.0
-#define LANGUAGE_SPANISH 6.0
-#define LANGUAGE_ITALIAN 7.0
+#include <sys/types.h>
+#include <atomic>
+
-#define COLOUR_THEME_REGULAR 0.0
-#define COLOUR_THEME_CRISPY 1.0
+#ifndef HAS_DIRENT
+# if defined(ATANKS_IS_MSVC)
+# include "extern/dirent.h"
+# else
+# include <dirent.h>
+# endif // Linux
+# define HAS_DIRENT 1
+#endif //HAS_DIRENT
-#define GAME_NAME_LENGTH 64
-#define VIOLENT_DEATH_OFF 0
-#define VIOLENT_DEATH_LIGHT 1
-#define VIOLENT_DEATH_MEDIUM 2
-#define VIOLENT_DEATH_HEAVY 3
+#ifdef USE_MUTEX_INSTEAD_OF_SPINLOCK
+# include <mutex>
+# define CSpinLock std::mutex
+#endif // USE_MUTEX_INSTEAD_OF_SPINLOCK
-#define MAX_INTEREST_AMOUNT 100000
-#define MAX_TEAM_AMOUNT 500000
-#define DEMO_WAIT_TIME 60
+#include "text.h"
+#include "globaltypes.h"
+#include "environment.h"
-#define SOUND_AUTODETECT 0
-#define SOUND_OSS 1
-#define SOUND_ESD 2
-#define SOUND_ARTS 3
-#define SOUND_ALSA 4
-#define SOUND_JACK 5
-#define FULL_SCREEN_EITHER 10.0
-#define FULL_SCREEN_TRUE 1.0
-#define FULL_SCREEN_FALSE 0.0
+extern int32_t BLACK;
-#define MAX_AI_TIME 10
-#define ALL_SOCKETS -1
+/// Forwards that do not need to be known here
+struct sDebrisItem;
+struct sDebrisPool;
+class PLAYER;
+class TANK;
+class VIRTUAL_OBJECT;
-enum skipComputerPlayType
+#ifndef USE_MUTEX_INSTEAD_OF_SPINLOCK
+/** @brief minimal spinlock class
+ * It can do nothing but lock and unlock. No recursive locks.
+ * But then it is a lot faster and leaner than mutexes and critical
+ * sections ever can be. ;)
+**/
+class CSpinLock
{
- SKIP_NONE, SKIP_HUMANS_DEAD// , SKIP_AUTOPLAY
+public:
+ explicit CSpinLock();
+ ~CSpinLock();
+
+ CSpinLock(const CSpinLock&) = delete;
+ CSpinLock &operator=(const CSpinLock&) = delete;
+
+ bool hasLock();
+ void lock();
+ void unlock();
+private:
+
+ abool_t is_destroyed;
+ aflag_t lock_flag;
+ std::thread::id owner_id;
};
+#endif // USE_MUTEX_INSTEAD_OF_SPINLOCK
-class PLAYER;
-class TANK;
+
+/** @class GLOBALDATA
+ * @brief Values used globally during a game round.
+ *
+ * This class holds all values and the corresponding functions for everything
+ * that can change during a game round.
+ *
+ * Everything that is fixed during a game round is consolidated in ENVIRONMENT.
+**/
class GLOBALDATA
- {
- private:
- DIR *music_dir;
- public:
- ~GLOBALDATA();
- int WHITE, BLACK, PINK;
- double slope[360][2];
-
- char *dataDir;
- char *configDir;
- BOX *updates, *lastUpdates, window;
- int updateCount, lastUpdatesCount;
- int stopwindow;
- int command;
- double frames_per_second;
-
- PLAYER **allPlayers;
- int numPermanentPlayers;
- #ifdef THREADS
- pthread_rwlock_t* command_lock;
- #endif
- void wr_lock_command ();
- void unlock_command ();
- int get_command ();
- PLAYER **players;
- int numPlayers;
- int numHumanPlayers;
- int computerPlayersOnly;
- double skipComputerPlay; /* options requires doubles - grr */
- /* It's a lot simpler than having
- * special cases for each type */
- int numTanks;
- int maxNumTanks;
- TANK *currTank;
-
- int updateMenu;
-
- int curland, cursky;
- int get_curland();
- void unlock_curland();
- void lock_curland();
- void destroy_curland_lock();
- void init_curland_lock();
- #ifdef THREADS
- pthread_mutex_t* curland_lock;
- #endif
- int colourDepth;
- int screenWidth, screenHeight;
- int menuBeginY, menuEndY;
- int halfWidth, halfHeight;
- int width_override, height_override;
- double temp_screenWidth, temp_screenHeight;
- PLAYER *client_player; // the index we use to know which one is the player on the client side
- gfxDataStruct gfxData;
- // DATAFILE *SOUND;
- ENVIRONMENT *env;
-
- // bool full_screen;
- // int cacheCirclesBG ; // This is just a flag, so it need only be
- // an integer, not a double
- void Reset_Options();
-
- /* Logically, these three variables should be ints. However, converting
- them to ints (or even an enumerated type) would require some rewritting
- of the options function - and that's a lot of work. 2003.09.05 */
- /* Hence being double. 2004.01.05 */
- double ditherGradients;
- double detailedLandscape;
- double detailedSky;
- double os_mouse; // whether we should use the OS or custom mouse
- double colour_theme; // land and sky gradiant theme
- double sound_driver;
-
- /* All this money data; couldn't it be moved into some separate data
- structure or object */
- /* It could, but it's not a problem */
- double startmoney;
- double interest;
- double scoreHitUnit;
- double scoreSelfHit;
- double scoreUnitDestroyBonus;
- double scoreUnitSelfDestroy;
- double scoreRoundWinBonus;
- double sellpercent;
- double divide_money;
- double play_music;
- double full_screen;
- char server_name[128], server_port[128];
-
- /* double? */
- /* double for options() reasons, no messing about with casting or
- * special cases. */
- double turntype;
- double rounds;
- int currentround;
- double sound;
- double language;
- int name_above_tank;
- int tank_status_colour;
- char *tank_status;
- char game_name[GAME_NAME_LENGTH];
- double load_game;
- double campaign_mode;
- double violent_death;
- double saved_game_index;
- char **saved_game_list;
- double max_fire_time;
- bool close_button_pressed;
- char *update_string;
- double check_for_updates;
- bool demo_mode;
- double enable_network, listen_port;
- int draw_background;
- BITMAP **button, **misc, **missile, **stock, **tank, **tankgun, **title;
- SAMPLE **sounds;
- SAMPLE *background_music;
- FONT *unicode, *regular_font;
- TEXTBLOCK *war_quotes, *instructions, *ingame;
- TEXTBLOCK *gloat, *revenge, *retaliation, *suicide, *kamikaze;
- char *client_message; // message sent from client to main menu
-
-
- GLOBALDATA ();
- void initialise ();
- int saveToFile_Text (FILE *file);
- int loadFromFile_Text (FILE *file);
- int loadFromFile(ifstream &my_file);
- void addPlayer (PLAYER *player);
- void removePlayer (PLAYER *player);
- PLAYER *getNextPlayer (int *playerCount);
- PLAYER *createNewPlayer (ENVIRONMENT *env);
- void destroyPlayer (PLAYER *player);
- char *Get_Config_Path();
- bool Check_Time_Changed(); // check to see if one second has passed
- bool bIsGameLoaded;
- bool bIsBoxed;
- int iHumanLessRounds;
- double dMaxVelocity;
- int Load_Sounds();
- int Load_Bitmaps();
- SAMPLE *Load_Background_Music();
- int Load_Fonts();
- void Change_Font();
- void Update_Player_Menu();
- int Load_Text_Files();
- #ifdef NETWORK
- int Send_To_Clients(char *message); // send a short message to all network clients
- #endif
-#ifdef DEBUG_AIM_SHOW
- bool bASD;
-#endif
- int Find_Data_Dir();
- double Calc_Max_Velocity();
- int Count_Humans();
- int Check_For_Winner(); // returns winner index or -1 for no winner
- void Credit_Winners(int winner);
- };
+{
+ typedef VIRTUAL_OBJECT vobj_t;
+ typedef sDebrisItem item_t;
+
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ explicit GLOBALDATA ();
+ ~GLOBALDATA();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+ void addLandSlide (int32_t left, int32_t right, bool do_lock);
+ void addObject (vobj_t* object);
+ bool areTanksInBox (int32_t x1, int32_t y1, int32_t x2, int32_t y2);
+ bool check_time_changed(); // check to see if one second has passed
+ void clear_objects ();
+ void destroy ();
+ void do_updates ();
+ void first_init ();
+ void free_debris_item (item_t* item);
+ int32_t get_avg_bgcolor (int32_t x1, int32_t y1, int32_t x2, int32_t y2,
+ double xv, double yv);
+ int32_t get_command ();
+ TANK* get_curr_tank ();
+ item_t* get_debris_item (int32_t radius);
+ TANK* get_next_tank (bool* wrapped_around);
+ void initialise ();
+ bool isCloseBtnPressed ();
+ bool isDirtInBox (int32_t x1, int32_t y1, int32_t x2, int32_t y2);
+ void load_from_file (FILE* file);
+ void lockClass (eClasses class_);
+ void lockLand ();
+ void make_bgupdate (int32_t x, int32_t y, int32_t w, int32_t h);
+ void make_fullUpdate ();
+ void make_update (int32_t x, int32_t y, int32_t w, int32_t h);
+ void newRound ();
+ void pressCloseButton ();
+ void removeObject (vobj_t* object);
+ void removeTank (TANK* tank);
+ void replace_canvas ();
+ bool save_to_file (FILE* file);
+ void set_curr_tank (TANK* tank_);
+ void set_command (int32_t cmd);
+ void slideLand ();
+ void unlockClass (eClasses class_);
+ void unlockLand ();
+ void unlockLandSlide (int32_t left, int32_t right);
+
+ template<typename Head_T>
+ void getHeadOfClass (eClasses class_, Head_T** head_)
+ {
+ if (class_ < CLASS_COUNT) {
+ objLocks[class_].lock();
+ *head_ = static_cast<Head_T*>(heads[class_]);
+ objLocks[class_].unlock();
+ } else
+ *head_ = nullptr;
+ }
+
+
+
+ /* ----------------------
+ * --- Public members ---
+ * ----------------------
+ */
+
+ int32_t AI_clock = -1;
+ BITMAP* canvas = nullptr;
+ const char* client_message = nullptr; // message sent from client to main menu
+ PLAYER* client_player = nullptr; // the index we use to know which one is the player on the client side
+ int32_t curland = 0;
+ int32_t current_drawing_mode = DRAW_MODE_SOLID;
+ uint32_t currentround = 0;
+ int32_t cursky = 0;
+ bool demo_mode = false;
+ bool hasTooMuchDeco = false; // Set to true if the set FPS are too hard to reach.
+ BOX* lastUpdates = nullptr;
+ int32_t lastUpdatesCount = 0;
+ double lastwind = 0.;
+ int32_t naturals_activated = 0;
+ int32_t numTanks = 0;
+ TANK* order[MAXPLAYERS];
+ bool showScoreBoard = false;
+ bool skippingComputerPlay = false;
+ int32_t stage = STAGE_AIM;
+ bool stopwindow = false;
+ ai32_t* surface = nullptr;
+ char tank_status[128];
+ int32_t tank_status_colour = BLACK;
+ BITMAP* terrain = nullptr;
+ bool updateMenu = true;
+ BOX* updates = nullptr;
+ char* update_string = nullptr;
+ int32_t used_voices = 0;
+ double wind = 0.;
+
+
+private:
+
+ typedef sDebrisPool debpool_t;
+
+
+ /* -----------------------
+ * --- Private methods ---
+ * -----------------------
+ */
+
+ // Combine make_update and make_bgupdate with safety checks
+ void addUpdate(int32_t x, int32_t y, int32_t w, int32_t h, BOX* target,
+ int32_t &target_count);
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ bool close_button_pressed = false;
+ CSpinLock cbpLock; //[c]lose_[b]utton_[p]ressed
+ CSpinLock cmdLock;
+ bool combineUpdates = true;
+ int32_t command = 0;
+ TANK* currTank = nullptr;
+ debpool_t* debris_pool = nullptr;
+ int8_t* done = nullptr;
+ double* dropIncr = nullptr;
+ int32_t* dropTo = nullptr;
+ int32_t* fp = nullptr;
+ vobj_t* heads[CLASS_COUNT];
+ CSpinLock landLock;
+ CSpinLock objLocks[CLASS_COUNT];
+ vobj_t* tails[CLASS_COUNT];
+ int32_t tankindex = 0;
+ int32_t updateCount = 0;
+ double* velocity = nullptr;
+};
+
+#define HAS_GLOBALDATA 1
#endif
diff --git a/src/globals.h b/src/globals.h
index 2f128a8..754ab57 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -1,295 +1,76 @@
-#ifndef GLOBALS_DEFINE
-#define GLOBALS_DEFINE
+#ifndef ATANKS_SRC_ATANKS_CPP
+# error "globals.h must not be included from anywhere but atanks.cpp!"
+#endif // ATANKS_SRC_ATANKS_CPP
-/*
- * atanks - obliterate each other with oversize weapons
- * Copyright (C) 2003 Thomas Hudson
- *
- * 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.
- * */
-
-
-/*
-#include "virtobj.h"
-#include "floattext.h"
-#include "physobj.h"
-#include "tank.h"
-#include "missile.h"
-#include "explosion.h"
-#include "player.h"
-#include "environment.h"
-*/
#include "globaldata.h"
-/*
-#include "teleport.h"
-#include "decor.h"
-#include "beam.h"
-*/
-using namespace std;
-char *errorMessage;
-int errorX, errorY;
+// === The two most important things in the game: ;) ===
+GLOBALDATA global;
+ENVIRONMENT env;
+
+// === Defined colours used everywhere ===
+int32_t BLACK, BLUE, DARK_GREEN, DARK_GREY, DARK_RED, GREY, GREEN,
+ LIGHT_GREEN, LIME_GREEN, ORANGE, PINK, PURPLE, RED, SILVER,
+ TURQUOISE, WHITE, YELLOW;
-int WHITE, BLACK, PINK, RED, GREEN, DARK_GREEN, BLUE, PURPLE, YELLOW;
+// === General values that are globally used ===
+char buf[100]; // buffer for general use
+const char* errorMessage;
+int32_t errorX, errorY;
+int32_t k, K; // k = key pressed, K = Key Code from k
+int32_t fi, lx, ly;
-int k;
-int ctrlUsedUp;
-const gradient topbar_gradient[] =
+// === Gradients ===
+gradient topbar_gradient[4] =
{
- {{200,200,200,0}, 0.0},
- {{255,255,255,0}, 0.5},
- {{128,128,128,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{200,200,200,0}, 0.0},
+ {{255,255,255,0}, 0.5},
+ {{128,128,128,0}, 1.0},
+ {{0,0,0,0}, -1}
};
-const gradient stuff_bar_gradient[] =
+gradient stuff_bar_gradient[11] =
{
- {{ 0,120, 0,0}, 0.0},
- {{ 10,210, 50,0}, 0.1},
- {{100,150,150,0}, 0.28},
- {{100,170,170,0}, 0.31},
- {{200,200,200,0}, 0.33},
- {{120,150,120,0}, 0.35},
- {{180,190,180,0}, 0.5},
- {{210,210,210,0}, 0.55},
- {{200,220,200,0}, 0.57},
- {{255,255,255,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 0,120, 0,0}, 0.0},
+ {{ 10,210, 50,0}, 0.1},
+ {{100,150,150,0}, 0.28},
+ {{100,170,170,0}, 0.31},
+ {{200,200,200,0}, 0.33},
+ {{120,150,120,0}, 0.35},
+ {{180,190,180,0}, 0.5},
+ {{210,210,210,0}, 0.55},
+ {{200,220,200,0}, 0.57},
+ {{255,255,255,0}, 1.0},
+ {{0,0,0,0}, -1}
};
-const gradient circles_gradient[] =
+gradient circles_gradient[4] =
{
- {{100, 75, 50,0}, 0.0},
- {{ 0,100, 0,0}, 0.5},
- {{255,255,255,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{100, 75, 50,0}, 0.0},
+ {{ 0,100, 0,0}, 0.5},
+ {{255,255,255,0}, 1.0},
+ {{0,0,0,0}, -1}
};
// Explosion gradient
-const gradient explosion_gradient1[] =
+gradient explosion_gradient1[3] =
{
- {{150, 75, 30,0}, 0.0},
- {{255,255,255,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{150, 75, 30,0}, 0.0},
+ {{255,255,255,0}, 1.0},
+ {{0,0,0,0}, -1}
};
// Explosion gradient
-const gradient explosion_gradient2[] =
+gradient explosion_gradient2[3] =
{
- {{255,255,102,0}, 0.0}, // ff6
- {{255,136, 0,0}, 1.0}, // f80
- {{0,0,0,0}, -1}
+ {{255,255,102,0}, 0.0}, // ff6
+ {{255,136, 0,0}, 1.0}, // f80
+ {{0,0,0,0}, -1}
};
-const gradient * const explosion_gradients[] =
+gradient* explosion_gradients[2] =
{
explosion_gradient1,
explosion_gradient2
};
-
-/*
-// Sky gradients, first line is top of screen
-const gradient sky_gradient1[] =
-{
- {{255,255,255,0}, 0.0},
- {{100,100,100,0}, 0.1},
- {{ 80,100,100,0}, 0.5},
- {{0,0,0,0}, -1}
-};
-
-const gradient sky_gradient2[] =
-{
- {{ 0, 0, 40,0}, 0.0},
- {{ 40, 40,100,0}, 0.5},
- {{ 80,80,100,0}, 0.75},
- {{0,0,0,0}, -1}
-};
-
-const gradient sky_gradient3[] =
-{
- {{240, 0, 40,0}, 0.0},
- {{140, 40,100,0}, 0.15},
- {{ 80, 80,100,0}, 0.75},
- {{0,0,0,0}, -1}
-};
-
-const gradient sky_gradient4[] =
-{
- {{ 40, 40, 40,0}, 0.0},
- {{100, 40,100,0}, 0.2},
- {{ 80, 80,100,0}, 0.75},
- {{0,0,0,0}, -1}
-};
-
-const gradient sky_gradient5[] =
-{
- {{ 0, 90, 40,0}, 0.0},
- {{ 0,120,100,0}, 0.2},
- {{ 40,200,100,0}, 0.75},
- {{0,0,0,0}, -1}
-};
-
-// Sunset
-const gradient sky_gradient6[] =
-{
- {{ 70,240,240,0}, 0.0},
- {{ 70,200,200,0}, 0.3},
- {{ 70,200,160,0}, 0.35},
- {{255,200, 70,0}, 0.6},
- {{255,255,128,0}, 1.0},
- {{0,0,0,0}, -1}
-};
-
-// Burning sky
-const gradient sky_gradient7[] =
-{
- {{ 20, 20, 20,0}, 0.0},
- {{255,200, 0,0}, 0.08},
- {{255,255, 0,0}, 0.13},
- {{ 20, 20, 20,0}, 0.16},
- {{255,200, 0,0}, 0.5},
- {{255,255, 0,0}, 1.0},
- {{0,0,0,0}, -1}
-};
-
-// Burning landscape, black skies
-const gradient sky_gradient8[] =
-{
- {{ 0, 0, 0,0}, 0.0},
- {{100, 0, 0,0}, 0.4},
- {{255,255,255,0}, 0.8},
- {{0,0,0,0}, -1}
-};
-
-// Sky gradients, first line is top of screen
-// dark blue to darker blue
-const gradient sky_gradient9[] =
-{
- {{ 90, 90,255,0}, 0.0},
- {{ 60, 60,200,0}, 0.3},
- {{ 30, 30,150,0}, 1.0},
- {{0,0,0,0}, -1}
-};
-
-// dark blue to blue-grey
-const gradient sky_gradient10[] =
-{
- {{110,110,255,0}, 0.0},
- {{150,150,255,0}, 0.3},
- {{200,200,255,0}, 1.0},
- {{0,0,0,0}, -1}
-};
-
-// white to grey-blue to dark blue
-const gradient sky_gradient11[] =
-{
- {{255,255,255,0}, 0.0},
- {{200,200,255,0}, 0.3},
- {{ 80, 80,180,0}, 1.0},
- {{0,0,0,0}, -1}
-};
-
-// simple purple: dark to light
-const gradient sky_gradient12[] =
-{
- {{133, 33,133,0}, 0.0},
- {{220,120,220,0}, 1.0},
- {{0,0,0,0}, -1}
-};
-
-// night sky: black to dark purple
-const gradient sky_gradient13[] =
-{
- {{ 0, 0, 0,0}, 0.0},
- {{ 50, 0, 50,0}, 1.0},
- {{0,0,0,0}, -1}
-};
-
-// Sunset
-const gradient sky_gradient14[] =
-{
- {{ 0, 0, 0,0}, 0.0},
- {{ 50, 0, 50,0}, 0.1},
- {{ 90, 20, 0,0}, 0.3},
- {{180, 50, 0,0}, 0.7},
- {{255,150,150,0}, 0.9},
- {{255,255,100,0}, 1.0},
- {{0,0,0,0}, -1}
-};
-
-// Burning sky
-const gradient sky_gradient15[] =
-{
- {{185, 60, 60,0}, 0.0},
- {{240,110,110,0}, 0.5},
- {{255,110,110,0}, 1.0},
- {{0,0,0,0}, -1}
-};
-
-// Burning landscape, black skies
-const gradient sky_gradient16[] =
-{
- {{ 0, 0, 0,0}, 0.0},
- {{170, 50, 50,0}, 0.5},
- {{220,110,110,0}, 1.0},
- {{0,0,0,0}, -1}
-};
-
-
-const gradient * const sky_gradients[] =
-{
- sky_gradient1,
- sky_gradient2,
- sky_gradient3,
- sky_gradient4,
- sky_gradient5,
- sky_gradient6,
- sky_gradient7,
- sky_gradient8,
- sky_gradient9,
- sky_gradient10,
- sky_gradient11,
- sky_gradient12,
- sky_gradient13,
- sky_gradient14,
- sky_gradient15,
- sky_gradient16
-};
-*/
-
-int cclock,
-fps, frames,
-fi, lx, ly;
-#define CLOCK_MAX 10
-
-int steep=2, mheight=200, mbase=0;
-double msteep=.2, smooth=.00;
-
-int winner;
-
-char buf[100];
-
-bool quit_right_now;
-
-WEAPON weapon[WEAPONS];
-WEAPON naturals[NATURALS];
-ITEM item[ITEMS];
-
-GLOBALDATA *my_global;
-
-
-#endif
-
diff --git a/src/globaltypes.cpp b/src/globaltypes.cpp
new file mode 100644
index 0000000..3590aad
--- /dev/null
+++ b/src/globaltypes.cpp
@@ -0,0 +1,68 @@
+#include "globaltypes.h"
+
+/** @file globaltypes.cpp
+ * @brief define helper operators for enum rotation
+**/
+
+/// @brief pre-increment for eDataStages
+eDataStage &operator++ (eDataStage &ds)
+{
+ if (DS_DATA == ds)
+ ds = DS_NAME;
+ else if (DS_DESC == ds)
+ ds = DS_DATA;
+ else
+ ds = DS_DESC;
+ return ds;
+}
+
+
+eLanguages &operator+=(eLanguages &lang, int32_t val)
+{
+ int32_t cur = static_cast<int32_t>(lang) + val;
+ if (cur > 0)
+ cur %= EL_LANGUAGE_COUNT;
+ if (cur < 0)
+ cur = EL_LANGUAGE_COUNT - ((-1 * cur) % EL_LANGUAGE_COUNT);
+ lang = static_cast<eLanguages>(cur);
+ return lang;
+}
+
+
+eLanguages &operator-=(eLanguages &lang, int32_t val)
+{
+ return lang += -1 * val;
+}
+
+
+/// @brief pre-increment for the language
+eLanguages &operator++(eLanguages &lang)
+{
+ return lang += 1;
+}
+
+
+/// @brief post-increment for the language type
+eLanguages operator++(eLanguages &lang, int)
+{
+ eLanguages tmp = lang;
+ lang += 1;
+ return tmp;
+}
+
+
+/// @brief pre-decrement for the language
+eLanguages &operator--(eLanguages &lang)
+{
+ return lang += -1;
+}
+
+
+/// @brief post-decrement for the language type
+eLanguages operator--(eLanguages &lang, int)
+{
+ eLanguages tmp = lang;
+ lang += -1;
+ return tmp;
+}
+
diff --git a/src/globaltypes.h b/src/globaltypes.h
new file mode 100644
index 0000000..1650c11
--- /dev/null
+++ b/src/globaltypes.h
@@ -0,0 +1,337 @@
+#pragma once
+#ifndef ATANKS_SRC_GLOBALTYPES_H_INCLUDED
+#define ATANKS_SRC_GLOBALTYPES_H_INCLUDED
+
+/*
+ * atanks - obliterate each other with oversize weapons
+ *
+ * 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.
+ *
+ */
+
+#define DEFAULT_SCREEN_WIDTH 800
+#define DEFAULT_SCREEN_HEIGHT 600
+#define GAMENAMELEN 64
+#define MAX_INTEREST_AMOUNT 100000
+#define MAX_TEAM_AMOUNT 500000
+#define DEMO_WAIT_TIME 60
+#ifdef ATANKS_IS_WINDOWS
+# define MAX_AI_TIME 30 // DirectDraw is too slow, allow more time
+#else
+# define MAX_AI_TIME 10 // Standard with anything but windows
+#endif // ATANKS_IS_MSVC
+#define ALL_SOCKETS -1
+
+// Start enforcing unified integer typing
+#include <cstdint>
+
+// Use atomic types for thread safety where locks are a bad idea
+#include <atomic>
+typedef std::atomic_bool abool_t;
+typedef std::atomic_flag aflag_t;
+typedef std::atomic_int_fast32_t ai32_t;
+
+
+/** @file globaltypes.h
+ * @brief Definitions of types relevant to global data.
+**/
+
+
+/** @enum eBackgroundTypes
+ * @brief types for drawing menu background
+**/
+enum eBackgroundTypes
+{
+ BACKGROUND_BLANK = 0,
+ BACKGROUND_CIRCLE,
+ BACKGROUND_LINE,
+ BACKGROUND_SQUARE,
+ BACKGROUND_COUNT,
+};
+
+
+/** @enum eBoxModes
+ * @brief Whether boxed mode is on, off or random.
+**/
+enum eBoxModes
+{
+ BM_OFF = 0,
+ BM_ON,
+ BM_RANDOM
+};
+
+
+/** @enum eColourTheme
+ * @brief determine which colour theme to use
+**/
+enum eColourTheme
+{
+ CT_REGULAR = 0,
+ CT_CRISPY
+};
+
+
+/** @enum eCOntrol
+ * @brief control results for human and computer control
+**/
+enum eControl
+{
+ CONTROL_NONE = 0,
+ CONTROL_FIRE = 101, // Explicitly fire a weapon/item
+ CONTROL_OTHER = 102, // Something else but firing something
+ CONTROL_SKIP = 201, // Turn on skipping computer play through in game menu
+ CONTROL_QUIT = 202 // Any means to quit the game
+};
+
+
+
+/** @enum eDataStage
+ * @brief The data stage of the weapons text file loading
+**/
+enum eDataStage
+{
+ DS_NAME = 0,
+ DS_DESC,
+ DS_DATA
+};
+eDataStage &operator++ (eDataStage &ds); // Enable pre-increment
+
+
+/** @enum eFileStage
+ * @brief The file stage of the weapons text file loading
+**/
+enum eFileStage
+{
+ FS_WEAPONS = 0,
+ FS_NATURALS,
+ FS_ITEMS
+};
+
+
+/** @enum eFullScreen
+ * @brief Whether to use full screen or not.
+**/
+enum eFullScreen
+{
+ FULL_SCREEN_EITHER = 0,
+ FULL_SCREEN_TRUE,
+ FULL_SCREEN_FALSE
+};
+
+
+/** @enum eLandscapeTypes
+ * @brief determine the types the landscape can have
+**/
+enum eLandscapeTypes
+{
+ LAND_RANDOM = 0,
+ LAND_CANYONS,
+ LAND_MOUNTAINS,
+ LAND_VALLEYS,
+ LAND_HILLS,
+ LAND_FOOTHILLS,
+ LAND_PLAIN,
+ LAND_NONE
+};
+
+
+/** @enum eLandSlideTypes
+ * @brief determine the kind of land sliding.
+**/
+enum eLandSlideTypes
+{
+ SLIDE_NONE = 0, // gravity does not exist
+ SLIDE_TANK_ONLY, // dirt falls, tank does not
+ SLIDE_INSTANT, // dirt falls without you seeing it
+ SLIDE_GRAVITY, // normal
+ SLIDE_CARTOON // gravity is delayed
+};
+
+
+/** @enum eLanguages
+ * @brief Declare the list of supported languages.
+ *
+ * The last item EL_LANGUAGE_COUNT can be used to retrieve the
+ * number of supported languages.
+ *
+ * This enum is sorted in the order the languages should be listed.
+**/
+enum eLanguages
+{
+ EL_ENGLISH = 0,
+ EL_PORTUGUESE,
+ EL_FRENCH,
+ EL_GERMAN,
+ EL_SLOVAK,
+ EL_RUSSIAN,
+ EL_SPANISH,
+ EL_ITALIAN,
+ EL_LANGUAGE_COUNT
+};
+
+// Helper operators to rotate languages:
+eLanguages &operator++(eLanguages &lang);
+eLanguages operator++ (eLanguages &lang, int);
+eLanguages &operator--(eLanguages &lang);
+eLanguages operator-- (eLanguages &lang, int);
+eLanguages &operator+=(eLanguages &lang, int32_t val);
+eLanguages &operator-=(eLanguages &lang, int32_t val);
+
+
+/** @enum eSatelliteLaser
+ * @brief Size of satellite laser
+**/
+enum eSatelliteLaser
+{
+ SL_NONE = 0,
+ SL_WEAK,
+ SL_STRONG,
+ SL_SUPER
+};
+
+
+/** @enum eSaveGameStage
+ * @brief Stages written in a saved game file
+**/
+enum eSaveGameStage
+{
+ SGS_NONE = 0,
+ SGS_GLOBAL,
+ SGS_ENVIRONMENT,
+ SGS_PLAYERS
+};
+
+
+/** @enum eSkipPlayType
+ * @brief How skipping computer play is managed
+**/
+enum eSkipPlayType
+{
+ SKIP_NONE = 0,
+ SKIP_HUMANS_DEAD
+};
+
+
+/** @enum eSoundDriver
+ * @brief determine which sound driver to use
+**/
+enum eSoundDriver
+{
+ SD_AUTODETECT = 0,
+ SD_OSS,
+ SD_ESD, // Does anybody still use that?
+ SD_ARTS, // Long long deprecated
+ SD_ALSA,
+ SD_JACK
+ // What about PulseAudio?
+};
+
+
+/** @enum eSounds
+ * @brief enum that describes the sound array
+**/
+enum eSounds
+{
+ // === FIRE a weapon / an item ===
+ SND_FIRE_MISS_SML = 0,
+ SND_FIRE_MISS_MED = 1,
+ SND_FIRE_MISS_LRG = 2,
+ SND_FIRE_NUKE = 3,
+ SND_FIRE_DEATHEAD = 4,
+ SND_FIRE_LASER = 5,
+ SND_FIRE_TELEPORT = 6,
+ SND_FIRE_WIND_FAN = 7,
+
+ // === EXPLosion of a weapon / an item ===
+ SND_EXPL_MISS_SML = 10,
+ SND_EXPL_MISS_MED = 11,
+ SND_EXPL_MISS_LRG = 12,
+ SND_EXPL_NUKE = 13,
+ SND_EXPL_DEATHEAD = 14,
+ SND_EXPL_DIRT_BALL_BOMB = 15,
+ SND_EXPL_SHAPED_CHARGE = 16,
+ SND_EXPL_WIDE_BOY = 17,
+ SND_EXPL_CUTTER = 18,
+ SND_EXPL_NAPALM = 19,
+ SND_EXPL_NAPALM_BURN = 20,
+ SND_EXPL_PER_CENT_BOMB = 21,
+ SND_EXPL_REDUCER = 22,
+
+ // === NATUral going off ===
+ SND_NATU_THUNDER_SMLMED = 30,
+ SND_NATU_THUNDER_LRG = 31,
+ SND_NATU_DIRT_FALL = 32,
+
+ // === INTErface sounds ===
+ SND_INTE_BUTTON_CLICK = 40,
+
+ // Play BackGround MUSIC
+ SND_BG_MUSIC = 50,
+ SND_COUNT
+
+};
+
+
+/** @enum eStages
+ * @brief General stages for the game flow.
+**/
+enum eRoundStages
+{
+ STAGE_AIM = 0,
+ STAGE_FIRE,
+ STAGE_SCOREBOARD, // The scoreboard is displayed
+ STAGE_ENDGAME // All actions have ceased, the round has ended.
+};
+
+
+/** @enum eViolentDeath
+ * @brief Level of automatic violent death option.
+**/
+enum eViolentDeath
+{
+ VD_OFF = 0,
+ VD_LIGHT,
+ VD_MEDIUM,
+ VD_HEAVY
+};
+
+
+/** @enum eWallTypes
+ * @brief All the types the walls can have.
+**/
+enum eWallTypes
+{
+ WALL_RUBBER = 0,
+ WALL_STEEL,
+ WALL_SPRING,
+ WALL_WRAP,
+ WALL_RANDOM
+};
+
+
+/** @enum eWinner
+ * @brief All possible winning sets
+**/
+enum eWinner
+{
+ WINNER_NO_WIN = 101,
+ WINNER_DRAW = 102,
+ WINNER_JEDI = 104,
+ WINNER_SITH = 105
+};
+
+
+#endif // ATANKS_SRC_GLOBALTYPES_H_INCLUDED
+
diff --git a/src/land.cpp b/src/land.cpp
index 2db0c57..6eab1a6 100644
--- a/src/land.cpp
+++ b/src/land.cpp
@@ -1,209 +1,234 @@
#include "land.h"
#include "files.h"
+#include "externs.h"
+#include "gameloop.h"
-#ifdef THREADS
-#include <pthread.h>
-#endif
-// Define how the land will look.
-
-void generate_land (GLOBALDATA *global, ENVIRONMENT *env, BITMAP *land, int xoffset, int yoffset, int heightx)
-{
- const int land_height = heightx * 5 / 6;
- double smoothness = 100;
- int octaves = 8;
- double lambda = 0.25;
- double * depthStrip[2];
- //Ignore updates from other threads by using a temporary variable.
- int curland_temp=global->get_curland();
-
- depthStrip[0] = (double *)calloc(global->screenHeight, sizeof(double));
- depthStrip[1] = (double *)calloc(global->screenHeight, sizeof(double));
-
- if (!depthStrip[0] || !depthStrip[1])
- {
- cerr << "ERROR: Unable to allocate " << (global->screenHeight * 2) << " bytes in generate_land() !!!" << endl;
- }
-
- int landType = (env->landType == LANDTYPE_RANDOM)? (rand () % LANDTYPE_PLAIN) + 1 : (int)env->landType;
-
- switch (landType)
- {
- case LANDTYPE_MOUNTAINS:
- smoothness = 200;
- octaves = 8;
- lambda = 0.65;
- break;
- case LANDTYPE_CANYONS:
- smoothness = 50;
- octaves = 8;
- lambda = 0.25;
- break;
- case LANDTYPE_VALLEYS:
- smoothness = 200;
- octaves = 8;
- lambda = 0.25;
- break;
- case LANDTYPE_HILLS:
- smoothness = 600;
- octaves = 6;
- lambda = 0.40;
- break;
- case LANDTYPE_FOOTHILLS:
- smoothness = 1200;
- octaves = 3;
- lambda = 0.25;
- break;
- case LANDTYPE_PLAIN:
- smoothness = 4000;
- octaves = 2;
- lambda = 0.2;
- break;
- case LANDTYPE_NONE:
- break;
- default:
- break;
- }
-
- if (global->detailedLandscape)
- memset (depthStrip[1], 0, global->screenHeight * sizeof (double));
-
- for (int x = 0; x < global->screenWidth; x++)
- {
- int depth = 0;
- if (landType == LANDTYPE_NONE)
- env->height[x] = 1;
- else
- env->height[x] = ((perlin2DPoint (1.0, smoothness, xoffset + x, yoffset, lambda, octaves) + 1) / 2);
-
- if (global->detailedLandscape)
- {
- memcpy (depthStrip[0], depthStrip[1], global->screenHeight * sizeof(double));
- for (depth = 1; depth < global->screenHeight; depth++)
- {
- depthStrip[1][depth] = ((perlin2DPoint (1.0, smoothness, xoffset + x, yoffset + depth, lambda, octaves) + 1) / 2 * land_height - (global->screenHeight - depth));
- if (depthStrip[1][depth] > env->height[x] * land_height)
- depthStrip[1][depth] = env->height[x] * land_height;
- }
- depthStrip[1][0] = 0;
- depth = 1;
- }
+/*****************************************************************************
+Static temp land bitmap for faster land creation
+*****************************************************************************/
+static BITMAP* temp_land = nullptr;
- if (landType == LANDTYPE_NONE)
- env->height[x] = 1;
- else
- env->height[x] *= land_height;
-
- for (int y = 0; y <= env->height[x]; y++)
- {
- double offset = 0;
- int color = 0;
- double shade = 0;
-
- if (global->detailedLandscape)
- {
- double bot; // top,
- double minBot, maxTop, btdiff, i;
- double a1, a2, angle;
- while ((depthStrip[1][depth] <= y) && (depth < global->screenHeight))
- depth++;
- bot = (depthStrip[0][depth - 1] + depthStrip[1][depth - 1]) / 2;
- // top = (depthStrip[0][depth] + depthStrip[1][depth]) / 2;
- minBot = MIN (depthStrip[0][depth - 1], depthStrip[1][depth - 1]);
- maxTop = MAX (depthStrip[0][depth], depthStrip[1][depth]);
- btdiff = maxTop - minBot;
- i = (y - bot) / btdiff;
- a1 = atan2 (depthStrip[0][depth - 1] - depthStrip[1][depth - 1], 1.0) * 180 / PI + 180;
- a2 = atan2 (depthStrip[0][depth] - depthStrip[1][depth], 1.0) * 180 / PI + 180;
-
- angle = interpolate (a1, a2, i);
- shade = global->slope[(int)angle][0];
- }
-
- if (global->ditherGradients)
- offset += rand () % 10 - 5;
-
- if (global->detailedLandscape)
- offset += (global->screenHeight - depth) * 0.5;
-
- while (y + offset < 0)
- offset /= 2;
- while (y + offset > global->screenHeight)
- offset /= 2;
-
- color = gradientColorPoint (land_gradients[curland_temp], env->height[x], y + offset);
- if (global->detailedLandscape)
- {
- float h, s, v;
- int r, g, b;
-
- r = getr (color);
- g = getg (color);
- b = getb (color);
- rgb_to_hsv (r, g, b, &h, &s, &v);
- shade += (double)(rand () % 1000 - 500) * (1.0/10000);
- if (shade < 0)
- v += v * shade * 0.5;
- else
- v += (1 - v) * shade * 0.5;
- hsv_to_rgb (h, s, v, &r, &g, &b);
- color = makecol (r, g, b);
- }
-
- #ifdef THREADS
- solid_mode();
- #endif
- putpixel (land, x, global->screenHeight - y, color);
- #ifdef THREADS
- drawing_mode(global->env->current_drawing_mode, NULL, 0, 0);
- #endif
- }
- }
- free(depthStrip[0]);
- free(depthStrip[1]);
-}
-
-
-
-#ifdef THREADS
-// This function should be called as a separate thread. Its sole purpose
-// is to generate terrain images in the background. Then pass them
-// to the main program.
-void *Generate_Land_In_Background(void *new_env)
+// Define how the land will look.
+void generate_land (LevelCreator* lcr, int32_t xoffset, int32_t yoffset, int32_t heightx)
{
- ENVIRONMENT *env = (ENVIRONMENT *) new_env;
- GLOBALDATA *global = env->Get_Globaldata();
- BITMAP *land = NULL;
- int xoffset;
-
- while (global->get_command() != GLOBAL_COMMAND_QUIT)
- {
- if (! env->get_waiting_terrain())
- {
- // The sky thread runs around the same time. We will wait a few seconds
- LINUX_DREAMLAND;
- land = create_bitmap(global->screenWidth, global->screenHeight);
- if (! land)
- {
- printf("Memory error while creating land in background.\n");
- }
- else
- {
- clear_to_color(land, PINK);
- xoffset = rand();
- generate_land(global, env, land, xoffset, 0, global->screenHeight);
- }
- env->lock(env->waiting_terrain_lock);
- env->waiting_terrain = land;
- env->unlock(env->waiting_terrain_lock);
- }
- LINUX_SLEEP; // avoid taxing the CPU
- } // while still running program
-
- pthread_exit(NULL);
- return NULL; // keeps the compiler happy
+ double* depthStrip[2] = { nullptr, nullptr };
+ double smoothness = 100.;
+ int32_t octaves = 8;
+ double lambda = .25;
+ int32_t curland = global.curland;
+ int32_t land_type = LAND_RANDOM == env.landType
+ ? (rand () % LAND_PLAIN) + 1
+ : env.landType;
+ int32_t land_height = heightx / 6 * 5;
+
+ depthStrip[0] = (double *)calloc(env.screenHeight + 1, sizeof(double));
+ depthStrip[1] = (double *)calloc(env.screenHeight + 1, sizeof(double));
+
+ if (!depthStrip[0] || !depthStrip[1]) {
+ cerr << "ERROR: Unable to allocate ";
+ cerr << ( (env.screenHeight + 1) * 2 * sizeof(double));
+ cerr << " bytes in LandGenerator() !" << endl;
+ return;
+ }
+
+ switch (land_type) {
+ case LAND_MOUNTAINS:
+ smoothness = 200;
+ octaves = 8;
+ lambda = 0.65;
+ break;
+ case LAND_CANYONS:
+ smoothness = 50;
+ octaves = 8;
+ lambda = 0.25;
+ break;
+ case LAND_VALLEYS:
+ smoothness = 200;
+ octaves = 8;
+ lambda = 0.25;
+ break;
+ case LAND_HILLS:
+ smoothness = 600;
+ octaves = 6;
+ lambda = 0.40;
+ break;
+ case LAND_FOOTHILLS:
+ smoothness = 1200;
+ octaves = 3;
+ lambda = 0.25;
+ break;
+ case LAND_PLAIN:
+ smoothness = 4000;
+ octaves = 2;
+ lambda = 0.2;
+ break;
+ case LAND_NONE:
+ default:
+ break;
+ }
+
+ temp_land = create_bitmap(env.screenWidth, env.screenHeight);
+ clear_to_color(temp_land, PINK);
+ clear_to_color(global.terrain, PINK);
+
+ for (int32_t x = 0; lcr->can_work() && (x < env.screenWidth); ++x) {
+ int32_t surface = 1;
+
+ // surface[x] will end up being the y coordinate of the
+ // top land pixel. It is not the real height (distance from bottom to
+ // even level) but this coordinate.
+ // This is why the array is called surface (again) and not (no longer)
+ // height.
+
+ if (land_type != LAND_NONE)
+ surface = (1. + perlin2DPoint(1.0, smoothness, xoffset + x, yoffset,
+ lambda, octaves) )
+ / 2. * land_height;
+ global.surface[x].store(surface);
+ } // End of generating surface array
+
+ // If this is a wrapped landcape, smooth out both sides towards their
+ // opposite counterparts
+ if (WALL_WRAP == env.current_wallType) {
+ int32_t length = env.screenWidth / 20; // 5% left + right = 10% overall
+
+ for (int32_t x = 0; lcr->can_work() && (x < length) ; ++x) {
+ // The idea is to compare the strips from left to right with the
+ // points being taken by a ratio greater the nearer to its wall.
+
+ int32_t left = x;
+ int32_t right = env.screenWidth - (x + 1);
+
+ double ratio_n = ( static_cast<double>(x)
+ / static_cast<double>(length)
+ / 2.) + .5; // [n]ear: 50% at the wall, 100% at length.
+ double ratio_f = 1. - ratio_n; // [f]ar : 50% at the wall, 0% at length.
+
+ // Get the heights currently set
+ double old_left_y = global.surface[left ].load(ATOMIC_READ);
+ double old_right_y = global.surface[right].load(ATOMIC_READ);
+
+ // Assemble new heights
+ double new_left_y = (old_left_y * ratio_n) + (old_right_y * ratio_f);
+ double new_right_y = (old_right_y * ratio_n) + (old_left_y * ratio_f);
+
+ // Now the new surface values can be stored:
+ global.surface[left ].store(ROUND(new_left_y));
+ global.surface[right].store(ROUND(new_right_y));
+ }
+ }
+
+ // Generate detailed depths
+ for (int32_t x = 0; lcr->can_work() && (x < env.screenWidth); ++x) {
+ int32_t depth = 0;
+ int32_t surface = global.surface[x].load();
+ if (env.detailedLandscape && (LAND_NONE != land_type)) {
+ memcpy (depthStrip[0], depthStrip[1], env.screenHeight * sizeof(double));
+ for (depth = 1; depth < env.screenHeight; depth++) {
+ depthStrip[1][depth] = (1. + perlin2DPoint (1.0, smoothness,
+ xoffset + x,
+ yoffset + depth,
+ lambda, octaves) )
+ / 2. * land_height
+ - (env.screenHeight - depth);
+ if (depthStrip[1][depth] > surface )
+ depthStrip[1][depth] = surface;
+ }
+ depthStrip[1][0] = 0;
+ depth = 1;
+ }
+
+ // Now generate the height colourization
+ for (int32_t y = 1; lcr->can_work() && (y <= surface); ++y) {
+
+ lcr->yield();
+
+ double offset = 0;
+ int32_t color = BLACK;
+ double shade = 0;
+
+ if (env.detailedLandscape) {
+ while ( (depth < env.screenHeight)
+ && (depthStrip[1][depth] <= y) )
+ ++depth;
+
+ double bot = (depthStrip[0][depth - 1]
+ + depthStrip[1][depth - 1]) / 2;
+ double minBot = std::min(depthStrip[0][depth - 1],
+ depthStrip[1][depth - 1]);
+ double maxTop = std::max(depthStrip[0][depth],
+ depthStrip[1][depth]);
+ double btdiff = maxTop - minBot;
+ double i = (y - bot) / btdiff;
+ double a1 = RAD2DEG(atan2(depthStrip[0][depth - 1]
+ - depthStrip[1][depth - 1], 1.0))
+ + 180.;
+ double a2 = RAD2DEG(atan2(depthStrip[0][depth]
+ - depthStrip[1][depth], 1.0))
+ + 180.;
+ double angle = interpolate (a1, a2, i);
+ shade = env.slope[(int)angle][0];
+ }
+
+ if (env.ditherGradients)
+ offset += rand () % 10 - 5;
+
+ if (env.detailedLandscape)
+ offset += (env.screenHeight - depth) * 0.5;
+
+ while ( (y + offset) < 0)
+ offset /= 2;
+ while ( (y + offset) > env.screenHeight)
+ offset /= 2;
+
+ color = gradientColorPoint (land_gradients[curland], surface, y + offset);
+
+ if (env.detailedLandscape) {
+ float h, s, v;
+ int32_t r = getr (color);
+ int32_t g = getg (color);
+ int32_t b = getb (color);
+ rgb_to_hsv(r, g, b, &h, &s, &v);
+
+ shade += (double)(rand () % 1000 - 500) * (1.0/10000);
+
+ if (shade < 0)
+ v += v * shade * 0.5;
+ else
+ v += (1 - v) * shade * 0.5;
+
+ hsv_to_rgb (h, s, v, &r, &g, &b);
+ color = makecol (r, g, b);
+ }
+
+ if (lcr->can_work()) {
+ global.lockLand();
+ solid_mode();
+ putpixel (temp_land, x, env.screenHeight - y, color);
+ drawing_mode(global.current_drawing_mode, NULL, 0, 0);
+ global.unlockLand();
+ }
+ } // End of looping y coordinate
+ } // end of looping x coordinate
+
+ // Put temp land onto the real bitmap:
+ global.lockLand();
+ solid_mode();
+ blit(temp_land, global.terrain, 0, 0, 0, 0, env.screenWidth, env.screenHeight);
+ global.unlockLand();
+
+ // clean up
+ if (temp_land)
+ destroy_bitmap(temp_land);
+ temp_land = nullptr;
+
+ if (depthStrip[0])
+ free(depthStrip[0]);
+
+ if (depthStrip[1])
+ free(depthStrip[1]);
}
-#endif
diff --git a/src/land.h b/src/land.h
index 3c9158c..e889022 100644
--- a/src/land.h
+++ b/src/land.h
@@ -1,169 +1,168 @@
#ifndef LAND_HEADER_FILE__
#define LAND_HEADER_FILE__
-#include "globaldata.h"
-#include "environment.h"
+#include "main.h"
+
+#define LANDS 8
+// The first LANDS are regular, the second are crispy.
const gradient land_gradient1[] =
{
- {{ 20, 40, 20,0}, 0.0},
- {{ 80,100, 80,0}, 0.5},
- {{ 80,120,100,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 20, 40, 20,0}, 0.0},
+ {{ 80,100, 80,0}, 0.5},
+ {{ 80,120,100,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient2[] =
{
- {{100,200,100,0}, 0.0},
- {{ 80,100, 80,0}, 0.5},
- {{ 80,120,100,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{100,200,100,0}, 0.0},
+ {{ 80,100, 80,0}, 0.5},
+ {{ 80,120,100,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient3[] =
{
- {{200,100,100,0}, 0.0},
- {{100, 70, 80,0}, 0.5},
- {{120, 80,100,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{200,100,100,0}, 0.0},
+ {{100, 70, 80,0}, 0.5},
+ {{120, 80,100,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient4[] =
{
- {{ 80, 50, 60,0}, 0.0},
- {{100, 70, 80,0}, 0.25},
- {{150,120, 80,0}, 0.75},
- {{200,180,150,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 80, 50, 60,0}, 0.0},
+ {{100, 70, 80,0}, 0.25},
+ {{150,120, 80,0}, 0.75},
+ {{200,180,150,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient5[] =
{
- {{ 20, 20, 20,0}, 0.0},
- {{100,100,100,0}, 0.7},
- {{250,250,250,0}, 0.75},
- {{250,250,250,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 20, 20, 20,0}, 0.0},
+ {{100,100,100,0}, 0.7},
+ {{250,250,250,0}, 0.75},
+ {{250,250,250,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient6[] =
{
- {{200,180, 70,0}, 0.0},
- {{100, 90, 30,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{200,180, 70,0}, 0.0},
+ {{100, 90, 30,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient7[] =
{
- {{ 50,200,150,0}, 0.0},
- {{130,120,180,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 50,200,150,0}, 0.0},
+ {{130,120,180,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient8[] =
{
- {{ 0, 0, 0,0}, 0.0},
- {{ 50, 40, 50,0}, 0.4},
- {{100, 0, 0,0}, 0.8},
- {{0,0,0,0}, -1}
+ {{ 0, 0, 0,0}, 0.0},
+ {{ 50, 40, 50,0}, 0.4},
+ {{100, 0, 0,0}, 0.8},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient9[] =
{
- {{100,100,100,0}, 0.0},
- {{ 50, 50, 50,0}, 0.7},
- {{255,255,255,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{100,100,100,0}, 0.0},
+ {{ 50, 50, 50,0}, 0.7},
+ {{255,255,255,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient10[] =
{
- {{100, 50, 50,0}, 0.0},
- {{ 50, 0, 0,0}, 0.25},
- {{100, 10, 10,0}, 0.5},
- {{100, 0, 0,0}, 0.95},
- {{ 0, 0, 0,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{100, 50, 50,0}, 0.0},
+ {{ 50, 0, 0,0}, 0.25},
+ {{100, 10, 10,0}, 0.5},
+ {{100, 0, 0,0}, 0.95},
+ {{ 0, 0, 0,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient11[] =
{
- {{ 50,100, 50,0}, 0.0},
- {{ 0, 50, 0,0}, 0.7},
- {{ 0,100, 0,0}, 0.95},
- {{ 0, 0, 0,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 50,100, 50,0}, 0.0},
+ {{ 0, 50, 0,0}, 0.7},
+ {{ 0,100, 0,0}, 0.95},
+ {{ 0, 0, 0,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient12[] =
{
- {{ 90, 90, 90,0}, 0.0},
- {{ 30, 30, 30,0}, 0.4},
- {{ 60, 60, 60,0}, 0.95},
- {{ 0, 0, 0,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 90, 90, 90,0}, 0.0},
+ {{ 30, 30, 30,0}, 0.4},
+ {{ 60, 60, 60,0}, 0.95},
+ {{ 0, 0, 0,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient13[] =
{
- {{ 90, 90, 90,0}, 0.0},
- {{ 0, 60, 0,0}, 0.95},
- {{ 0, 0, 0,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 90, 90, 90,0}, 0.0},
+ {{ 0, 60, 0,0}, 0.95},
+ {{ 0, 0, 0,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient14[] =
{
- {{ 0,175, 0,0}, 0.0},
- {{ 0, 50, 0,0}, 0.95},
- {{ 0, 0, 0,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 0,175, 0,0}, 0.0},
+ {{ 0, 50, 0,0}, 0.95},
+ {{ 0, 0, 0,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient15[] =
{
- {{100, 50, 50,0}, 0.0},
- {{ 50, 0, 0,0}, 0.95},
- {{ 0, 0, 0,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{100, 50, 50,0}, 0.0},
+ {{ 50, 0, 0,0}, 0.95},
+ {{ 0, 0, 0,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient land_gradient16[] =
{
- {{200, 0, 0,0}, 0.0},
- {{200,200, 0,0}, 0.25},
- {{ 0,200, 0,0}, 0.5},
- {{ 0, 0,200,0}, 0.75},
- {{200, 0,200,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{200, 0, 0,0}, 0.0},
+ {{200,200, 0,0}, 0.25},
+ {{ 0,200, 0,0}, 0.5},
+ {{ 0, 0,200,0}, 0.75},
+ {{200, 0,200,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient * const land_gradients[] =
{
- land_gradient1,
- land_gradient2,
- land_gradient3,
- land_gradient4,
- land_gradient5,
- land_gradient6,
- land_gradient7,
- land_gradient8,
- land_gradient9,
- land_gradient10,
- land_gradient11,
- land_gradient12,
- land_gradient13,
- land_gradient14,
- land_gradient15,
- land_gradient16,
-};
-
-
-void generate_land(GLOBALDATA *global, ENVIRONMENT *env, BITMAP *land, int xoffest, int yoffset, int heightx);
-
-#ifdef THREADS
-void *Generate_Land_In_Background(void *new_env);
-#endif
+ land_gradient1,
+ land_gradient2,
+ land_gradient3,
+ land_gradient4,
+ land_gradient5,
+ land_gradient6,
+ land_gradient7,
+ land_gradient8,
+ land_gradient9,
+ land_gradient10,
+ land_gradient11,
+ land_gradient12,
+ land_gradient13,
+ land_gradient14,
+ land_gradient15,
+ land_gradient16
+};
+
+class LevelCreator;
+
+void generate_land(LevelCreator* lcr, int32_t xoffest, int32_t yoffset, int32_t heightx);
#endif
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..3f5eed1
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,156 @@
+#include "main.h"
+
+bool operator==(const BOX &lhs, const BOX &rhs)
+{
+ return (&lhs == &rhs)
+ || ( (lhs.x == rhs.x)
+ && (lhs.y == rhs.y)
+ && (lhs.w == rhs.w)
+ && (lhs.h == rhs.h) );
+}
+
+bool operator!=(const BOX &lhs, const BOX &rhs)
+{
+ return !(lhs == rhs);
+}
+
+BOX::BOX(int32_t x_, int32_t y_, int32_t w_, int32_t h_) :
+ x(x_),
+ y(y_),
+ w(w_),
+ h(h_)
+{ /* nothing to do here */ }
+
+/// @brief normal assignment operator
+BOX &BOX::operator= (const BOX &src)
+{
+ if (&src != this) {
+ x = src.x;
+ y = src.y;
+ w = src.w;
+ h = src.h;
+ }
+ return *this;
+}
+
+/// @brief rvalue reference assignment operator
+BOX &BOX::operator= (const BOX &&src)
+{
+ x = src.x;
+ y = src.y;
+ w = src.w;
+ h = src.h;
+ return *this;
+}
+
+void BOX::set(int32_t x_, int32_t y_, int32_t w_, int32_t h_)
+{
+ x = x_;
+ y = y_;
+ w = w_;
+ h = h_;
+}
+
+
+POINT_t::POINT_t(int32_t x_, int32_t y_) :
+ x(x_),
+ y(y_)
+{ /* nothing to do here */ }
+
+POINT_t &POINT_t::operator=( const POINT_t& src )
+{
+ if (&src != this) {
+ x = src.x;
+ y = src.y;
+ }
+ return *this;
+}
+
+
+// Safe ctor for WEAPON class
+WEAPON::WEAPON()
+{
+ memset(desc, 0, sizeof(char) * MAX_ITEM_DESC_LEN + 1);
+ memset(name, 0, sizeof(char) * MAX_ITEM_NAME_LEN + 1);
+}
+
+
+/// @brief Get the delay or 1 if delay is zero.
+int32_t WEAPON::getDelayDiv() const
+{
+ return delay > 0 ? delay : 1;
+}
+
+
+/// @brief Get the current weapon description
+const char* WEAPON::getDesc() const
+{
+ return desc;
+}
+
+
+/// @brief Get the current weapon name
+const char* WEAPON::getName() const
+{
+ return name;
+}
+
+
+/// @brief Safely set a new weapon description
+void WEAPON::setDesc(const char* desc_)
+{
+ if (strlen(desc_) > MAX_ITEM_DESC_LEN)
+ fprintf(stderr, "Weapon description for \"%s\" truncated! (%d/%lu characters)\n",
+ name, MAX_ITEM_DESC_LEN, strlen(desc_));
+ strncpy(desc, desc_, MAX_ITEM_DESC_LEN);
+}
+
+
+/// @brief Safely set a new weapon name
+void WEAPON::setName(const char* name_)
+{
+ if (strlen(name_) > MAX_ITEM_NAME_LEN)
+ fprintf(stderr, "Weapon name for \"%s\" truncated! (%d/%lu characters)\n",
+ name_, MAX_ITEM_NAME_LEN, strlen(name_));
+ strncpy(name, name_, MAX_ITEM_NAME_LEN);
+}
+
+
+// Safe ctor for WEAPON class
+ITEM::ITEM()
+{
+ memset(vals, 0, sizeof(double) * MAX_ITEMVALS);
+ memset(desc, 0, sizeof(char) * MAX_ITEM_DESC_LEN + 1);
+ memset(name, 0, sizeof(char) * MAX_ITEM_NAME_LEN + 1);
+}
+
+
+/// @brief Get the current item description
+const char* ITEM::getDesc() const
+{
+ return desc;
+}
+
+/// @brief Get the current item name
+const char* ITEM::getName() const
+{
+ return name;
+}
+
+/// @brief Safely set a new item description
+void ITEM::setDesc(const char* desc_)
+{
+ if (strlen(desc_) > MAX_ITEM_DESC_LEN)
+ fprintf(stderr, "Item description for \"%s\" truncated! (%d/%lu characters)\n",
+ name, MAX_ITEM_DESC_LEN, strlen(desc_));
+ strncpy(desc, desc_, MAX_ITEM_DESC_LEN);
+}
+
+/// @brief Safely set a new item name
+void ITEM::setName(const char* name_)
+{
+ if (strlen(name_) > MAX_ITEM_NAME_LEN)
+ fprintf(stderr, "Item name for \"%s\" truncated! (%d/%lu characters)\n",
+ name_, MAX_ITEM_NAME_LEN, strlen(name_));
+ strncpy(name, name_, MAX_ITEM_NAME_LEN);
+}
diff --git a/src/main.h b/src/main.h
index 591cdda..1f3fd89 100644
--- a/src/main.h
+++ b/src/main.h
@@ -21,453 +21,574 @@
* */
#ifndef VERSION
-#define VERSION "6.0"
+# error "VERSION information is missing. Fix Makefile."
#endif
#ifdef GENTOO
-#ifndef DATA_DIR
-#define DATA_DIR "/usr/share/games/atanks"
-#endif
-#endif
+# ifndef DATA_DIR
+# define DATA_DIR "/usr/share/games/atanks"
+# endif // DATA_DIR
+#endif // GENTOO
#ifndef BUFFER_SIZE
-#define BUFFER_SIZE 256
+# define BUFFER_SIZE 256
#endif
-#ifndef _UNUSED
-#define _UNUSED __attribute__ ((unused))
-#endif // _UNUSED
-#ifndef _WARNUNUSED
-#define _WARNUNUSED __attribute__ ((warn_unused_result))
-#endif // _WARNUNUSED
-#include <allegro.h>
-#ifdef WIN32
-#include <winalleg.h>
-#endif
-#include <stdlib.h>
-#include <stdio.h>
-#include <math.h>
-#include <string.h>
-#include <time.h>
-#include "imagedefs.h"
+/// Important: debug.h not only detects which OS/compiler this is,
+/// it puts an important fix with allegro on windows in place.
+/// Therefore it *must* stay before the block including winalleg.h!
+#include "debug.h"
-#include <iostream>
-#include <fstream>
-#ifdef LINUX
-#include <unistd.h>
-#endif /* LINUX */
+// The windows port does some crazy stuff with int*_t types.
+#if defined(ATANKS_IS_WINDOWS)
+# include <cstdint>
+# ifndef ALLEGRO_HAVE_STDINT_H
+# define ALLEGRO_HAVE_STDINT_H 1
+# endif // ALLEGRO_HAVE_STDINT_H
+# if !defined(ATANKS_SRC_ATANKS_CPP)
+# define ALLEGRO_NO_MAGIC_MAIN
+# endif // Not called from atanks.cpp
+#endif // Windows build system
-#include <string>
+# include <allegro.h>
-#define GAME_SPEED 14000
+#if defined(ATANKS_IS_WINDOWS)
+# include <winalleg.h>
+#endif // windows
-#ifdef LINUX
-#define LINUX_SLEEP usleep(10000)
-#define LINUX_REST usleep(40000)
-#define LINUX_DREAMLAND sleep(5)
-#endif
-#ifdef MACOSX
-#define LINUX_SLEEP usleep(10000)
-#define LINUX_REST usleep(40000)
-#define LINUX_DREAMLAND sleep(5)
+// For visual studio some "workarounds" must be put in place
+#if defined(ATANKS_IS_MSVC)
+# define PATH_MAX MAX_PATH
+ // Needed for M_PIl to show up
+# if !defined(_USE_MATH_DEFINES)
+# define _USE_MATH_DEFINES 1
+# endif
+# include <cmath>
+#else
+# include <unistd.h>
+# include <cmath>
+#endif // Windows versus Linux
+
+
+// Be sure M_PI and M_PIl are set:
+#if !defined(M_PI)
+# define M_PI 3.14159265358979323846f
+#endif
+#if !defined(M_PIl)
+# define M_PIl 3.14159265358979323846L
#endif
-#ifdef WIN32
-#include <windows.h>
-#include <winbase.h>
-#define LINUX_SLEEP Sleep(10)
-#define LINUX_REST Sleep(40)
-#define LINUX_DREAMLAND Sleep(500)
+
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <ctime>
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <chrono>
+#include <thread>
+#include <algorithm>
+
+
+#include "globaltypes.h"
+
+
+#define DONE_IMAGE 11
+#define FAST_UP_ARROW_IMAGE 12
+#define UP_ARROW_IMAGE 13
+#define DOWN_ARROW_IMAGE 14
+#define FAST_DOWN_ARROW_IMAGE 15
+
+
+#ifndef HAS_DIRENT
+# if defined(ATANKS_IS_MSVC)
+# include "extern/dirent.h"
+# else
+# include <dirent.h>
+# endif // Linux
+# define HAS_DIRENT 1
+#endif // HAS_DIRENT
+
+
+// Some more workarounds to compile using visual studio:
+#if defined(ATANKS_IS_MSVC)
+# define snprintf atanks_snprintf
+# define strncpy(d, s, c) strncpy_s(d, c+1, s, c)
+# define strncat(d, s, c) strncat_s(d, c+1, s, c)
+# define sscanf sscanf_s
+# define access _access
+# define F_OK 02
+# define R_OK 04
+# define W_OK 02
+# define strcasecmp _stricmp
+# define strdup _strdup
+# define unlink _unlink
+# define mkdir _mkdir
#endif
-using namespace std;
+// Note: See winclock.h why this is necessary
+/// REMOVE_VS12_WORKAROUND
+#if defined(ATANKS_IS_MSVC) && !defined(ATANKS_IS_AT_LEAST_MSVC13)
+#define USLEEP(microseconds_) Sleep(microseconds_ / 1000);
+#define MSLEEP(milliseconds_) Sleep(milliseconds_);
+#else
+#define USLEEP(microseconds_) std::this_thread::sleep_for(std::chrono::microseconds(microseconds_));
+#define MSLEEP(milliseconds_) std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds_));
+#endif // VS12 workaround
+#define LINUX_SLEEP MSLEEP(10)
+#define LINUX_REST MSLEEP(40)
+
+
+using std::cerr;
+using std::cout;
+using std::endl;
+using std::string;
+
// place to save config and save games
-#ifdef WIN32
-#define HOME_DIR "AppData"
-#endif
-#ifdef LINUX
-#define HOME_DIR "HOME"
-#endif
-#ifdef MACOSX
-#define HOME_DIR "HOME"
-#endif
+#if defined(ATANKS_IS_WINDOWS)
+# define HOME_DIR "AppData"
+#elif defined(ATANKS_IS_LINUX)
+# define HOME_DIR "HOME"
+#endif // Windows versus Linux
#ifndef DATA_DIR
-#define DATA_DIR "."
+# define DATA_DIR "."
#endif
-#define SCREEN_WIDTH 800
-#define SCREEN_HEIGHT 600
-#define HALF_WIDTH (SCREEN_WIDTH/2)
-#define HALF_HEIGHT (SCREEN_HEIGHT/2)
-#define STUFF_BAR_WIDTH 400
-#define STUFF_BAR_HEIGHT 35
-
-#define GFILE_KEY 0x14233241
-
-#define FRAMES_PER_SECOND 60
-#ifndef PI
-#define PI 3.1415926535897932384626433832795029L
-#endif // PI
-// #define PI 3.14
-#define MAXUPDATES 256 // This one needs to be watched! If the display goes bye bye, reduce the value!
+
#define MAX_OVERSHOOT 10000
-#define ABSDISTANCE(x1,y1,x2,y2) abs((int)sqrt((long double)pow(((long double)x2) - ((long double)x1), 2) + (long double)pow(((long double)y2) - ((long double)y1), 2)))
-#define FABSDISTANCE(x1,y1,x2,y2) fabs(sqrt((long double)pow(((long double)x2) - ((long double)x1), 2) + (long double)pow(((long double)y2) - ((long double)y1), 2)))
-#define MAXPLAYERS 10
-#define MAX_POWER 2000
-#define MAX_OBJECTS 400
-#define MAX_ROUNDS 1000
-#define TANKHEIGHT 20
-#define TANKWIDTH 15
-#define GUNLENGTH 16
-#define MENUHEIGHT 30
-#define SKIES 8
-#define LANDS 8
-#define ALL_SKIES 16
-#define ALL_LANDS 16
+
+
+// The nex few are some math helpers that shorten things dramatically.
+#define SIGN(x_arg) ((x_arg) < 0 ? -1 : 1 )
+#define SIGNd(x_arg) ((x_arg) < 0. ? -1. : 1.)
+#define ROUND(x_arg) static_cast<int32_t>( (x_arg) + (SIGNd(x_arg) * .5))
+#define ROUNDu(x_arg) static_cast<uint32_t>((x_arg) + .5)
+
+#define FABSDISTANCE2(x1,y1,x2,y2) \
+ std::sqrt(std::pow(static_cast<double>(x2) - static_cast<double>(x1), 2.) \
+ + std::pow(static_cast<double>(y2) - static_cast<double>(y1), 2.) )
+#define FABSDISTANCE3(x1, y1, z1, x2, y2, z2) \
+ std::sqrt(std::pow(static_cast<double>(x2) - static_cast<double>(x1), 2.) \
+ + std::pow(static_cast<double>(y2) - static_cast<double>(y1), 2.) \
+ + std::pow(static_cast<double>(z2) - static_cast<double>(z1), 2.) )
+
+#define ABSDISTANCE2(x1,y1,x2,y2) ROUNDu(FABSDISTANCE2(x1,y1,x2,y2))
+#define ABSDISTANCE3(x1,y1,z1,x2,y2,z2) ROUNDu(FABSDISTANCE3(x1,y1,z1,x2,y2,z2))
+
+#define DEG2RAD(degree_) (static_cast<double>(degree_) * M_PIl / 180. )
+#define RAD2DEG(radian_) (static_cast<double>(radian_) * 180. / M_PIl)
+
+
+/** @brief show or hide the custom mouse cursor
+ *
+ * This macro can either hide the custom mouse cursor when @a where is set
+ * to nullptr, or draw it on @a where, which then must be a pointer to a
+ * BITMAP.
+ *
+ * This macro should be used to hide the custom mouse cursor before doing any
+ * drawing and to place the mouse cursor on @a where once all other drawing is
+ * done.
+ *
+ * If the OS mouse cursor is used, this macro does nothing.
+ *
+ * @param[in] where BITMAP pointer to draw the custom cursor on or nullptr to
+ * hide the custom mouse cursor.
+**/
+#define SHOW_MOUSE(where) { \
+ if (!env.osMouse) { \
+ if (where != nullptr) unscare_mouse(); \
+ else scare_mouse(); \
+ show_mouse(where); \
+ /* Make the neccessary updates */ \
+ if (where != nullptr) { \
+ global.make_update (mouse_x, mouse_y, env.misc[0]->w, env.misc[0]->h); \
+ global.make_update (lx, ly, env.misc[0]->w, env.misc[0]->h); \
+ lx = mouse_x; \
+ ly = mouse_y; \
+ } \
+ } \
+}
+
+#define MAXPLAYERS 10
+#define MAX_POWER 2000
+#define MIN_POWER 100
+#define MAX_ROUNDS 10000
+
+#define MENUHEIGHT 40
+#define BOXED_TOP 41 // This is the highest non-border pixel in boxed mode
#define BALLISTICS 52
#define BEAMWEAPONS 3
#define WEAPONS (BALLISTICS + BEAMWEAPONS)
#define ITEMS 24
#define THINGS (WEAPONS + ITEMS)
#define NATURALS 6
+#define DIRT_FRAGMENT -1
#define RADII 6
#define MAXRADIUS 200
#define BUTTONFRAMES 2
-#define EXPLODEFRAMES 18
-#define DISPERSEFRAMES 10
-#define EXPLOSIONFRAMES (EXPLODEFRAMES + DISPERSEFRAMES)
-#define TANKSAG 10
+
#define MENUBUTTONS 7
-#define MISSILEFRAMES 1
-#define ACHANGE 256/360
#define INGAMEBUTTONS 4
-#define MAX_MISSILES 10
#define SPREAD 10
-#define SHIELDS 6
-#define WEAPONSOUNDS 4
-#define NAME_LENGTH 24
+#define NAME_LEN 24
#define ADDRESS_LENGTH 16
-//Score coeficients
+#define WAIT_AT_END_OF_ROUND 1 // second (enough with the new live score board)
-#define SCORE_DESTROY_UNIT_BONUS 5000
-#define SCORE_UNIT_SELF_DESTROY 0
-#define SCORE_HIT_UNIT 50
-#define SCORE_SELF_HIT 0
+#define MAX_ITEM_NAME_LEN 127
+#define MAX_ITEM_DESC_LEN 511
+#define MAX_ITEMS_IN_STOCK 999999
+#define MAX_MONEY_IN_WALLET 1000000000
-//Wind
-#define WIND_COEF 0.3
-#define MAX_WIND 3
+// Use these instead of the (most strict) defaults,
+// But only where timing by memory fences do not matter.
+#define ATOMIC_READ std::memory_order_acquire
+#define ATOMIC_WRITE std::memory_order_release
-
-// bitmaps for tanks
-#define NORMAL_TANK 0
-#define CLASSIC_TANK 1 // really 8 in the .dat file
-#define BIGGREY_TANK 2
-#define T34_TANK 3
-#define HEAVY_TANK 4
-#define FUTURE_TANK 5
-#define UFO_TANK 6
-#define SPIDER_TANK 7
-#define BIGFOOT_TANK 8
-#define MINI_TANK 9
+//turns
+enum turnTypes
+{
+ TURN_HIGH = 0,
+ TURN_LOW,
+ TURN_RANDOM,
+ TURN_SIMUL
+};
-// background images
-#define BACKGROUND_CIRCLE 0
-#define BACKGROUND_LINE 1
-#define BACKGROUND_BLANK 2
+struct POINT_t
+{
+ int32_t x = 0;
+ int32_t y = 0;
-#ifdef NEW_GAMELOOP
-#define WAIT_AT_END_OF_ROUND 300
-#else
-#define WAIT_AT_END_OF_ROUND 100
-#endif
+ explicit
+ POINT_t() { }
+ POINT_t(int32_t x_, int32_t y_);
+ POINT_t &operator=( const POINT_t &src );
+};
-// time between volly shots
-#define VOLLY_DELAY 50
+struct BOX
+{
+ int32_t x = 0;
+ int32_t y = 0;
+ int32_t w = 0;
+ int32_t h = 0;
+
+ explicit
+ BOX () { }
+ BOX (int32_t x_, int32_t y_, int32_t w_, int32_t h_);
+ BOX &operator=(const BOX &src);
+ BOX &operator=(const BOX &&src);
+
+ void set(int32_t x_, int32_t y_, int32_t w_, int32_t h_);
+};
-// defines for teams
-#define TEAM_SITH 0
-#define TEAM_NEUTRAL 1
-#define TEAM_JEDI 2
+// Make the BOX usage easier:
+bool operator==(const BOX &lhs, const BOX &rhs);
+bool operator!=(const BOX &lhs, const BOX &rhs);
-//turns
-enum turnTypes
+struct gradient
{
- TURN_HIGH, TURN_LOW, TURN_RANDOM, TURN_SIMUL
+ RGB color;
+ float point;
};
-class BOX
- {
- public:
- int x, y, w, h;
- };
-
-
-typedef struct gradient_struct
- {
- RGB color;
- float point;
- } gradient;
class WEAPON
- {
- public:
- char name[128]; //name of weapon
- char description[512];
- int cost; //$ :))
- int amt; //number of weapons in one buying package
- double mass;
- double drag;
- int radius; // of the explosion
- int sound;
- int etime;
- int damage; //damage power
- int eframes;
- int picpoint; // which picture do we show in flight?
- int spread; // number of weapons in the shot
- int delay; // volleys etc.
- int noimpact;
- int techLevel;
- int warhead; // Is it a warhead?
- int numSubmunitions;// Number of submunitions
- int submunition; // The next stage
- double impartVelocity; // Impart velocity 0.0-1.0 to subs
- int divergence; // Total angle for submunition spread
- double spreadVariation;// Uniform or random distribution
- // 0-1.0 (0=uniform, 1.0=random)
- // divergence at centre of range
- double launchSpeed; // Speed given to submunitions
- double speedVariation; // Uniform or random speed
- // 0-1.0 (0=uniform, 1.0=random)
- // launchSpeed at centre of range
- int countdown; // Set the countdown to this
- double countVariation; // Uniform or random countdown
- // 0-1.0 (0=uniform, 1.0=random)
- // countdown at centre of range
- };
+{
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ explicit WEAPON();
+
+
+ /* -----------------------------------
+ * --- Public methods ---
+ * -----------------------------------
+ */
+
+ int32_t getDelayDiv() const;
+ const char* getDesc() const;
+ const char* getName() const;
+
+ void setDesc(const char* desc_);
+ void setName(const char* name_);
+
+
+ /* -----------------------------------
+ * --- Public members ---
+ * -----------------------------------
+ */
+ int32_t cost = 0; //!< $ :))
+ int32_t amt = 0; //!< number of weapons in one buying package
+ double mass = 0.;
+ double drag = 0.;
+ int32_t radius = 0; //!< of the explosion
+ int32_t sound = 0;
+ int32_t etime = 0;
+ int32_t damage = 0; //!< damage power
+ int32_t picpoint = 0; //!< which picture do we show in flight?
+ int32_t spread = 0; //!< number of weapons in the shot
+ int32_t delay = 0; //!< volleys etc.
+ int32_t noimpact = 0;
+ int32_t techLevel = 0;
+ int32_t warhead = 0; //!< Is it a warhead?
+ int32_t numSubmunitions = 0; //!< Number of submunitions
+ int32_t submunition = 0; //!< The next stage
+ double impartVelocity = 0.; //!< Impart velocity 0.0-1.0 to subs
+ int32_t divergence = 0; //!< Total angle for submunition spread
+ double spreadVariation = 0.; //!< Uniform or random distribution
+ //!< 0-1.0 (0=uniform, 1.0=random)
+ //!< divergence at centre of range
+ double launchSpeed = 0.; //!< Speed given to submunitions
+ double speedVariation = 0.; //!< Uniform or random speed
+ //!< 0-1.0 (0=uniform, 1.0=random)
+ //!< launchSpeed at centre of range
+ int32_t countdown = 0; //!< Set the countdown to this
+ double countVariation = 0.; //!< Uniform or random countdown
+ //!< 0-1.0 (0=uniform, 1.0=random)
+ //!< countdown at centre of range
+
+
+private:
+
+
+ /* -----------------------------------
+ * --- Private members ---
+ * -----------------------------------
+ */
+
+ char desc[MAX_ITEM_DESC_LEN + 1];
+ char name[MAX_ITEM_NAME_LEN + 1];
+};
#define MAX_ITEMVALS 10
class ITEM
- {
- public:
- char name[128];
- char description[256];
- int cost;
- int amt;
- int selectable;
- int techLevel;
- int sound;
- double vals[MAX_ITEMVALS];
- };
+{
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ explicit ITEM();
+
+
+ /* -----------------------------------
+ * --- Public methods ---
+ * -----------------------------------
+ */
+
+ const char* getDesc() const;
+ const char* getName() const;
+
+ void setDesc(const char* desc_);
+ void setName(const char* name_);
+
+
+ /* -----------------------------------
+ * --- Public members ---
+ * -----------------------------------
+ */
+
+ int32_t cost = 0;
+ int32_t amt = 0;
+ int32_t selectable = 0;
+ int32_t techLevel = 0;
+ int32_t sound = 0;
+ double vals[MAX_ITEMVALS];
+
+
+private:
+
+
+ /* -----------------------------------
+ * --- Private members ---
+ * -----------------------------------
+ */
+
+ char desc[MAX_ITEM_DESC_LEN + 1];
+ char name[MAX_ITEM_NAME_LEN + 1];
+};
enum shieldVals
{
- SHIELD_ENERGY,
- SHIELD_REPULSION,
- SHIELD_RED,
- SHIELD_GREEN,
- SHIELD_BLUE,
- SHIELD_THICKNESS
+ SHIELD_ENERGY,
+ SHIELD_REPULSION,
+ SHIELD_RED,
+ SHIELD_GREEN,
+ SHIELD_BLUE,
+ SHIELD_THICKNESS
};
+
+
enum selfDestructVals
{
- SELFD_TYPE,
- SELFD_NUMBER
+ SELFD_TYPE = 0,
+ SELFD_NUMBER
};
+
enum weaponType
{
- SML_MIS, MED_MIS, LRG_MIS, SML_NUKE, NUKE, DTH_HEAD,
- SML_SPREAD, MED_SPREAD, LRG_SPREAD, SUP_SPREAD, DTH_SPREAD, ARMAGEDDON,
- CHAIN_MISSILE, CHAIN_GUN, JACK_HAMMER,
- SHAPED_CHARGE, WIDE_BOY, CUTTER,
- SML_ROLLER, LRG_ROLLER, DTH_ROLLER,
- SMALL_MIRV,
- ARMOUR_PIERCING,
- CLUSTER, SUP_CLUSTER,
- FUNKY_BOMB, FUNKY_DEATH,
- FUNKY_BOMBLET, FUNKY_DEATHLET,
- BOMBLET, SUP_BOMBLET,
- BURROWER, PENETRATOR,
- SML_NAPALM, MED_NAPALM, LRG_NAPALM,
- NAPALM_JELLY,
- DRILLER,
- TREMOR, SHOCKWAVE, TECTONIC,
- RIOT_BOMB, HVY_RIOT_BOMB,
- RIOT_CHARGE, RIOT_BLAST,
- DIRT_BALL, LRG_DIRT_BALL, SUP_DIRT_BALL,
- SMALL_DIRT_SPREAD,
- CLUSTER_MIRV,
- PERCENT_BOMB,
- REDUCER,
- SML_LAZER, MED_LAZER, LRG_LAZER,
- SML_METEOR, MED_METEOR, LRG_METEOR,
- SML_LIGHTNING, MED_LIGHTNING, LRG_LIGHTNING
+ SML_MIS = 0,
+ MED_MIS = 1,
+ LRG_MIS = 2,
+ SML_NUKE = 3,
+ NUKE = 4,
+ DTH_HEAD = 5,
+ SML_SPREAD = 6,
+ MED_SPREAD = 7,
+ LRG_SPREAD = 8,
+ SUP_SPREAD = 9,
+ DTH_SPREAD = 10,
+ ARMAGEDDON = 11,
+ CHAIN_MISSILE = 12,
+ CHAIN_GUN = 13,
+ JACK_HAMMER = 14,
+ SHAPED_CHARGE = 15,
+ WIDE_BOY = 16,
+ CUTTER = 17,
+ SML_ROLLER = 18,
+ LRG_ROLLER = 19,
+ DTH_ROLLER = 20,
+ SMALL_MIRV = 21,
+ ARMOUR_PIERCING = 22,
+ CLUSTER = 23,
+ SUP_CLUSTER = 24,
+ FUNKY_BOMB = 25,
+ FUNKY_DEATH = 26,
+ FUNKY_BOMBLET = 27,
+ FUNKY_DEATHLET = 28,
+ BOMBLET = 29,
+ SUP_BOMBLET = 30,
+ BURROWER = 31,
+ PENETRATOR = 32,
+ SML_NAPALM = 33,
+ MED_NAPALM = 34,
+ LRG_NAPALM = 35,
+ NAPALM_JELLY = 36,
+ DRILLER = 37,
+ TREMOR = 38,
+ SHOCKWAVE = 39,
+ TECTONIC = 40,
+ RIOT_BOMB = 41,
+ HVY_RIOT_BOMB = 42,
+ RIOT_CHARGE = 43,
+ RIOT_BLAST = 44,
+ DIRT_BALL = 45,
+ LRG_DIRT_BALL = 46,
+ SUP_DIRT_BALL = 47,
+ SMALL_DIRT_SPREAD = 48,
+ CLUSTER_MIRV = 49,
+ PERCENT_BOMB = 50,
+ REDUCER = 51, // Last ballistic
+ SML_LAZER = 52,
+ MED_LAZER = 53,
+ LRG_LAZER = 54, // Last weapon (WEAPONS == 55)
+ SML_METEOR = 55,
+ MED_METEOR = 56,
+ LRG_METEOR = 57,
+ SML_LIGHTNING = 58,
+ MED_LIGHTNING = 59,
+ LRG_LIGHTNING = 60 // Last natural
};
-// #define LAST_EXPLOSIVE NAPALM_JELLY
+
#define LAST_EXPLOSIVE DRILLER
-#define ITEM_NO_SHIELD -1
+#define ITEM_NO_SHIELD -1
enum itemType
{
- ITEM_TELEPORT,
- ITEM_SWAPPER,
- ITEM_MASS_TELEPORT,
- ITEM_FAN,
- ITEM_VENGEANCE,
- ITEM_DYING_WRATH,
- ITEM_FATAL_FURY,
- ITEM_LGT_SHIELD,
- ITEM_MED_SHIELD,
- ITEM_HVY_SHIELD,
- ITEM_LGT_REPULSOR_SHIELD,
- ITEM_MED_REPULSOR_SHIELD,
- ITEM_HVY_REPULSOR_SHIELD,
- ITEM_ARMOUR,
- ITEM_PLASTEEL,
- ITEM_INTENSITY_AMP,
- ITEM_VIOLENT_FORCE,
- ITEM_SLICKP,
- ITEM_DIMPLEP,
- ITEM_PARACHUTE,
- ITEM_REPAIRKIT,
- ITEM_FUEL,
- ITEM_ROCKET,
- ITEM_SDI
+ ITEM_TELEPORT = 0, // 55 (weap_idx - WEAPONS)
+ ITEM_SWAPPER = 1, // 56
+ ITEM_MASS_TELEPORT = 2, // 57
+ ITEM_FAN = 3, // 58
+ ITEM_VENGEANCE = 4, // 59
+ ITEM_DYING_WRATH = 5, // 60
+ ITEM_FATAL_FURY = 6, // 61
+ ITEM_LGT_SHIELD = 7,
+ ITEM_MED_SHIELD = 8,
+ ITEM_HVY_SHIELD = 9,
+ ITEM_LGT_REPULSOR_SHIELD = 10,
+ ITEM_MED_REPULSOR_SHIELD = 11,
+ ITEM_HVY_REPULSOR_SHIELD = 12,
+ ITEM_ARMOUR = 13,
+ ITEM_PLASTEEL = 14,
+ ITEM_INTENSITY_AMP = 15,
+ ITEM_VIOLENT_FORCE = 16,
+ ITEM_SLICKP = 17,
+ ITEM_DIMPLEP = 18,
+ ITEM_PARACHUTE = 19,
+ ITEM_REPAIRKIT = 20,
+ ITEM_FUEL = 21, // 76
+ ITEM_ROCKET = 22, // 77
+ ITEM_SDI = 23 // Last item
};
#define SHIELD_COUNT 6
//signals
-#define SIG_QUIT_GAME -1
-#define SIG_OK 0
-#define GLOBAL_COMMAND_QUIT -1
-#define GLOBAL_COMMAND_MENU 0
-#define GLOBAL_COMMAND_OPTIONS 1
-#define GLOBAL_COMMAND_PLAYERS 2
-#define GLOBAL_COMMAND_CREDITS 3
-#define GLOBAL_COMMAND_HELP 4
-#define GLOBAL_COMMAND_PLAY 5
-#define GLOBAL_COMMAND_DEMO 6
-#define GLOBAL_COMMAND_NETWORK 7
-
-
-// Classes
-#define ANY_CLASS -1
-#define VIRTUAL_OBJECT_CLASS 0
-#define FLOATTEXT_CLASS 1
-#define PHYSICAL_OBJECT_CLASS 2
-#define MISSILE_CLASS 3
-#define TANK_CLASS 4
-#define EXPLOSION_CLASS 5
-#define TELEPORT_CLASS 6
-#define BEAM_CLASS 7
-#define DECOR_CLASS 8
-
-
-typedef struct
- {
- // DATAFILE *M, *T, *TITLE, *S, *E, *B, *L,
- // *TG, *MI, *STOCK_IMAGE;
-
- BITMAP *sky_gradient_strips[ALL_SKIES];
- BITMAP *land_gradient_strips[ALL_LANDS];
- // BITMAP *circle_gradient_strip;
- BITMAP *stuff_bar_gradient_strip;
- BITMAP *topbar_gradient_strip;
- BITMAP *explosion_gradient_strip;
- BITMAP *stuff_bar[2];
- BITMAP *stuff_icon_base;
- // BITMAP *circlesBG;
- BITMAP *topbar;
- BITMAP *explosions[EXPLOSIONFRAMES];
- BITMAP *flameFront[EXPLOSIONFRAMES];
- } gfxDataStruct;
-
-
-
-class GLOBALDATA;
-class ENVIRONMENT;
-int drawFracture (GLOBALDATA *global, ENVIRONMENT *env, BITMAP *dest, BOX *updateArea, int x, int y, int angle, int width, int segmentLength, int maxRecurse, int recurseDepth);
-int setSlideColumnDimensions (GLOBALDATA *global, ENVIRONMENT *env, int x, bool reset);
-double Noise (int x);
-double Noise2D (int x, int y);
-double interpolate (double x1, double x2, double i);
-double perlin1DPoint (double amplitude, double scale, double xo, double lambda, int octaves);
-double perlin2DPoint (double amplitude, double scale, double xo, double yo, double lambda, int octaves);
-
-void quickChange (GLOBALDATA *global, BITMAP *target) ;
-void change(GLOBALDATA *global, BITMAP *target);
-
-int checkPixelsBetweenTwoPoints (GLOBALDATA *global, ENVIRONMENT *env, double *startX, double *startY, double endX, double endY);
-long int calcTotalPotentialDamage (int weapNum);
-long int calcTotalEffectiveDamage (int weapNum);
-
-void close_button_handler(void);
-
-void doLaunch(GLOBALDATA *gd, ENVIRONMENT *env);
-
-#define GENSKY_DETAILED 1
-#define GENSKY_DITHERGRAD 2
-void generate_sky (GLOBALDATA *global, BITMAP* bmp, const gradient* grad, int flags ) ;
-
-// load all game settings
-bool Load_Game_Settings(GLOBALDATA *global, ENVIRONMENT *env, char *text_file) _WARNUNUSED;
-bool loadPlayers(GLOBALDATA *global, ENVIRONMENT *env, ifstream &ifsFile) _WARNUNUSED;
-// save all game settings
-bool Save_Game_Settings(GLOBALDATA *global, ENVIRONMENT *env, char *text_file, bool bIsSaveGame = false) _WARNUNUSED;
-bool savePlayers (GLOBALDATA *global, ofstream &ofsFile) _WARNUNUSED;
-int Save_Game_Settings_Text(GLOBALDATA *global, ENVIRONMENT *env, char *path_to_file);
-
-// These defines are needed to identify parts of the binary save files.
-#define FILEPART_ENVIRON "[Environment]"
-#define FILEPART_GLOBALS "[Global]"
-#define FILEPART_PLAYERS "[Players]"
-#define FILEPART_ENDSECT "[EndSection]"
-#define FILEPART_ENDPLYR "[EndPlayer]"
-#define FILEPART_ENDFILE "[EndFile]"
-
-// methods to handle loading and saving of data
-
-/* --- take data out of filestream -- */
-bool popColo(int &aData, ifstream &ifsFile) _WARNUNUSED;
-bool popData(int &aData, ifstream &ifsFile) _WARNUNUSED;
-bool popData(double &aData, ifstream &ifsFile) _WARNUNUSED;
-bool popData(char * aArea, bool &aIsData, ifstream &ifsFile) _WARNUNUSED;
-//bool popData( const char * aArea, int &aData, ifstream &ifsFile) _WARNUNUSED;
-//bool popData( const char * aArea, double &aData, ifstream &ifsFile) _WARNUNUSED;
-//bool popData(char * aArea, double &aData, ifstream &ifsFile) _WARNUNUSED;
-
-/* --- put data into filestream -- */
-//bool pushData(const int aData, ofstream &ofsFile) _WARNUNUSED;
-//bool pushData(const double aData, ofstream &ofsFile) _WARNUNUSED;
-bool pushColo(const char * aArea, const int aData, ofstream &ofsFile) _WARNUNUSED;
-bool pushData(const char * aData, ofstream &ofsFile) _WARNUNUSED;
-bool pushData(const char * aArea, const int aData, ofstream &ofsFile) _WARNUNUSED;
-//bool pushName(const char * aData, ofstream &ofsFile) _WARNUNUSED; // Special function for name saving
-bool pushData(const char * aArea, const double aData, ofstream &ofsFile) _WARNUNUSED;
-
-/* --- seek specific data within filestream -- */
-//bool seekData(const int aData, ifstream &ifsFile) _WARNUNUSED;
-//bool seekData(const char * aData, ifstream &ifsFile) _WARNUNUSED;
-
-// handle changes to global settings
-int Change_Settings(double old_mouse, double old_sound, double new_mouse, double new_sound, void *mouse_image);
-
-int *Sort_Scores(GLOBALDATA *global);
-int Game_Client(GLOBALDATA *global, ENVIRONMENT *env, int socket_number);
+#define SIG_QUIT_GAME -1
+#define SIG_OK 0
+#define GLOBAL_COMMAND_QUIT -1
+#define GLOBAL_COMMAND_MENU 0
+#define GLOBAL_COMMAND_OPTIONS 1
+#define GLOBAL_COMMAND_PLAYERS 2
+#define GLOBAL_COMMAND_CREDITS 3
+#define GLOBAL_COMMAND_HELP 4
+#define GLOBAL_COMMAND_PLAY 5
+#define GLOBAL_COMMAND_DEMO 6
+#define GLOBAL_COMMAND_NETWORK 7
+
+
+/** @enum eClasses
+ * @brief class definitions of everything from virtual objects up
+ *
+ * The ordering here determines the order of the drawing.
+**/
+enum eClasses
+{
+ CLASS_MISSILE = 0,
+ CLASS_BEAM,
+ CLASS_TANK,
+ CLASS_TELEPORT,
+ CLASS_DECOR_DIRT,
+ CLASS_DECOR_SMOKE,
+ CLASS_EXPLOSION,
+ CLASS_FLOATTEXT,
+ CLASS_COUNT
+};
+
+
+#ifndef HAS_TANK
+class TANK; // forwarding if not known
+#endif // HAS_TANK
+
+/// === Global functions used in several compilation units ====
+void drawMenuBackground(eBackgroundTypes backType, int32_t tOffset,
+ int32_t numItems);
+double interpolate (double x1, double x2, double i);
+double Noise (int x);
+double Noise2D (int x, int y);
+double perlin1DPoint (double amplitude, double scale, double xo,
+ double lambda, int octaves);
+double perlin2DPoint (double amplitude, double scale, double xo, double yo,
+ double lambda, int octaves);
+void quickChange (bool clearerror);
#include "externs.h"
diff --git a/src/menu.cpp b/src/menu.cpp
new file mode 100644
index 0000000..793f537
--- /dev/null
+++ b/src/menu.cpp
@@ -0,0 +1,1214 @@
+#include "optioncontent.h"
+#include "optionitemcolour.h"
+#include "main.h"
+#include "button.h"
+#include "menu.h"
+#include "player.h"
+#include "externs.h"
+#include "clock.h"
+
+#include <cassert>
+#include <exception>
+
+
+void flush_inputs(); // From files.h
+void init_mouse_cursor(); // from atanks.cpp to change the mouse cursor
+
+static int32_t MOUSE_RELEASE_DELAY = 20; // Slows down constant mouse presses
+static int32_t MOUSE_DELAY_REDUCT = 5; // Every so many rounds the delay is reduced
+
+
+/* -------------------------------------------
+ * --- Public constructors and destructors ---
+ * -------------------------------------------
+ */
+
+Menu::Menu(eMenuClass class_, int32_t menuX, int32_t menuY) :
+ menu_class(class_),
+ menu_x(menuX),
+ menu_y(menuY)
+{
+ // Save here to detect language changes.
+ menu_lang = env.language;
+
+ assert ( (menu_class < MC_MENUCLASS_COUNT)
+ && "ERROR: class_ must be smaller than MC_MENUCLASS_COUNT");
+
+ // Set title according to class and language:
+ title = MenuTitleText[menu_class][menu_lang][0];
+ title_len = text_length(font, title);
+ title_x = menu_x + text_length(font, "W") + 2;
+
+ // Set background style
+ bgType = env.dynamicMenuBg
+ ? static_cast<eBackgroundTypes>(rand() % BACKGROUND_COUNT)
+ : BACKGROUND_BLANK;
+ bgOffset = (RAND_MAX / 4) + (rand() % (RAND_MAX / 4));
+ bgItems = (rand() % 100) + 20;
+}
+
+
+Menu::~Menu()
+{
+ // Remove all options
+ while (entry_cnt > 0) {
+ OptionItemBase* curr = tail;
+ tail = curr->getPrev();
+ delete curr; // Removes it automatically
+ --entry_cnt;
+ }
+
+ // Delete title if it was set manually
+ if (title_set && title)
+ free (const_cast<char*>(title));
+}
+
+
+
+/* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+
+/** @brief add a button to the menu
+ *
+ * This button shows a bitmap with text on it and returns a key code when
+ * clicked on.
+ *
+ * If no bitmaps are defined, a light button is drawn "by hand".
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * @param[in] title_idx index of the title text if it is listed in MenuTitleText.
+ * @param[in] title_ Pointer to a fixed title to be used instead of an indexed one.
+ * @param[in] key_code The key code to return if the button is clicked.
+ * @param[in] bmp Bitmap to use for regular display.
+ * @param[in] hover Bitmap to use when the mouse pointer hovers over the button.
+ * @param[in] released Bitmap to use when the button was clicked on.
+ * @param[in] text_only If set to true the button is only its title text.
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] padding Distance between title, display and wheel buttons.
+ * @return Number of options in the menu after adding the button.
+**/
+int32_t Menu::addButton(int32_t title_idx, const char* title_, int key_code,
+ BITMAP* bmp, BITMAP* hover, BITMAP* released,
+ bool text_only, int left, int top, int width, int height,
+ int padding)
+{
+ OptionItemBase* curr = nullptr;
+ BUTTON* btn = nullptr;
+ bool title_valid = is_title_idx_valid(title_idx);
+
+ // 1) Create the button
+ try {
+ if (bmp || hover || released)
+ btn = new BUTTON(title_ ? title_
+ : title_valid
+ ? MenuTitleText[menu_class][menu_lang][title_idx]
+ : nullptr,
+ text_only, menu_x + left, menu_y + top,
+ bmp, hover, released);
+ else
+ btn = new BUTTON(title_ ? title_
+ : title_valid
+ ? MenuTitleText[menu_class][menu_lang][title_idx]
+ : nullptr,
+ text_only, menu_x + left, menu_y + top, width, height);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new BUTTON\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+
+ // 2) Create the option
+ try {
+ curr = new OptionItem<int, int>(key_code, nullptr,
+ nullptr,
+ title_ ? title_
+ : title_valid
+ ? MenuTitleText[menu_class][menu_lang][title_idx]
+ : "",
+ title_idx, btn,
+ left, top, width, height, padding);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new ET_BUTTON option\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+
+ // 3) Insert option:
+ return this->insert_option(curr);
+}
+
+
+/** @brief This adds a color option to pick a color value
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * @param[in] target Pointer to the target to handle.
+ * @param[in] title_idx Index of the title if it is listed in MenuTitleText.
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] show_size Border length of the square displaying the currently picked color.
+ * @param[in] padding Distance between title and display.
+**/
+int32_t Menu::addColor(int32_t* target, int32_t title_idx,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t show_size, int32_t padding)
+{
+ OptionItemBase* curr = nullptr;
+ bool title_valid = is_title_idx_valid(title_idx);
+
+ assert (title_valid && "ERROR: The given title index is invalid");
+
+ if (target && title_valid) {
+ try {
+ curr = new OptionItemColour(
+ target,
+ MenuTitleText[menu_class][menu_lang][title_idx],
+ title_idx,
+ menu_y + top, menu_x + left, width, height,
+ padding, show_size);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new ET_COLOR OptionItem\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+ }
+
+ return this->insert_option(curr);
+}
+
+
+/** @brief This adds a sub menu option with direct menu access
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * @param[in] menu Pointer to the menu to handle.
+ * @param[in] title_idx Index of the title if it is listed in MenuTitleText.
+ * @param[in] color Regular display color of the title.
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] padding Distance between title and display.
+ * @return Number of options in the menu after adding the button.
+**/
+int32_t Menu::addMenu(Menu* menu, int32_t title_idx, int32_t color,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding)
+{
+ OptionItemBase* curr = nullptr;
+ bool title_valid = is_title_idx_valid(title_idx);
+
+ assert (title_valid && "ERROR: The given title index is invalid");
+
+ if (menu && title_valid) {
+ try {
+ curr = new OptionItemMenu(menu,
+ MenuTitleText[menu_class][menu_lang][title_idx],
+ title_idx, color,
+ menu_y + top, menu_x + left, width, height,
+ padding);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new ET_MENU OptionItem\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+ }
+
+ return this->insert_option(curr);
+}
+
+
+/** @brief This adds a sub menu option that handles a player (edit/create)
+ *
+ * Important: This _MUST_ have an action function that does the real work. This
+ * method ads an OptionItemPlayer instance, which is only a bridge to the
+ * action function.
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * @param[in,out] player_ Pointer to the PLAYER instance to handle.
+ * @param[in,out] action_ Pointer to the action function handling the button click.
+ * @param[in] title_idx Index of the title if it is listed in MenuTitleText.
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] padding Distance between title and display.
+ * @return Number of options in the menu after adding the button.
+**/
+int32_t Menu::addMenu(PLAYER** player,
+ int32_t (*action_)(PLAYER** player_, int32_t),
+ int32_t title_idx,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding)
+{
+ OptionItemBase* curr = nullptr;
+ bool title_valid = is_title_idx_valid(title_idx);
+
+ assert (action_ && "ERROR: No action function, no player menu.");
+
+ if (player && action_) {
+ try {
+ curr = new OptionItemPlayer(
+ player, action_,
+ title_valid
+ ? MenuTitleText[menu_class][menu_lang][title_idx]
+ : nullptr, // The ctor uses the player name if nullptr.
+ title_idx,
+ menu_y + top, menu_x + left, width, height,
+ padding);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new ET_MENU OptionItem\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+ }
+
+ return this->insert_option(curr);
+}
+
+
+/** @brief This adds a text option with editable text
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * Please also note that a title is displayed to the left of the display
+ * area.
+ *
+ * @param[in] target Pointer to the target to handle.
+ * @param[in] title_idx Index of the title if it is listed in MenuTitleText.
+ * @param[in] max_len Maximum number of characters to allow.
+ * @param[in] color Regular display color of the title/target.
+ * @param[in] format The format to represent the text, used by snprintf().
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] padding Distance between title and display.
+**/
+int32_t Menu::addText(char* target, int32_t title_idx, uint32_t max_len,
+ int32_t color, const char* format,
+ int left, int top, int width, int height, int padding)
+{
+ OptionItemBase* curr = nullptr;
+ bool title_valid = is_title_idx_valid(title_idx);
+
+ assert (title_valid && "ERROR: The given title index is invalid");
+
+ if (target && title_valid) {
+ try {
+ curr = new OptionItem<char, uint32_t>(
+ target, max_len, color, ET_TEXT,
+ MenuTitleText[menu_class][menu_lang][title_idx],
+ title_idx, format,
+ menu_y + top, menu_x + left, width, height,
+ padding);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new TEXT OptionItem\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+ }
+
+ return this->insert_option(curr);
+}
+
+
+/** @brief This adds an TC_TOGGLE feeding a boolean using a variable title
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * @param[in] target Pointer to the target to handle.
+ * @param[in] title_idx Index of the title if it is listed in MenuTitleText.
+ * @param[in] color Regular display color of the title/target.
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] padding Distance between title and display.
+**/
+int32_t Menu::addToggle(bool* target, int32_t title_idx, int32_t color,
+ int left, int top, int width, int height, int padding)
+{
+ OptionItemBase* curr = nullptr;
+ bool title_valid = is_title_idx_valid(title_idx);
+
+ assert (title_valid && "ERROR: The given title index is invalid");
+
+ if (target && title_valid) {
+ try {
+ curr = new OptionItem<bool, uint32_t>(
+ target, nullptr, ET_TOGGLE,
+ MenuTitleText[menu_class][menu_lang][title_idx],
+ title_idx, nullptr, color, TC_NONE,
+ 0, 0, 0, nullptr,
+ menu_y + top, menu_x + left, width, height,
+ padding, nullptr);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new TOGGLE OptionItem\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+ }
+
+ return this->insert_option(curr);
+}
+
+
+/** @brief This adds an TC_TOGGLE feeding a boolean using a fixed title
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * @param[in] target Pointer to the target to handle.
+ * @param[in] title Fixed title to display.
+ * @param[in] color Regular display color of the title/target.
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] padding Distance between title and display.
+**/
+int32_t Menu::addToggle(bool* target, const char* title_, int32_t color,
+ int left, int top, int width, int height, int padding)
+{
+ OptionItemBase* curr = nullptr;
+
+ assert (title_ && "ERROR: title_ must be set but is nullptr");
+
+ if (target && title_) {
+ try {
+ curr = new OptionItem<bool, uint32_t>(
+ target, nullptr, ET_TOGGLE,
+ title_, -1, nullptr, color, TC_NONE,
+ 0, 0, 0, nullptr,
+ menu_y + top, menu_x + left, width, height,
+ padding, nullptr);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new TOGGLE OptionItem\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+ }
+
+ return this->insert_option(curr);
+}
+
+
+/** @brief This adds an TC_TOGGLE handling PLAYER::selected
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * @param[in] player Pointer pointer to the player to handle.
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] padding Distance between title and display.
+**/
+int32_t Menu::addToggle(PLAYER** player,
+ int left, int top, int width, int height, int padding)
+{
+ OptionItemBase* curr = nullptr;
+
+ assert (player && *player
+ && "ERROR: For a player toggle *player must be valid.");
+
+ if (player && *player) {
+ try {
+ curr = new OptionItemPlayer(player, nullptr,
+ nullptr, -1,
+ menu_y + top, menu_x + left,
+ width, height, padding);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new ET_TOGGLE OptionItem\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+ }
+
+ return this->insert_option(curr);
+}
+
+
+/// @brief call clear_display(full_display) on all entries
+void Menu::clearAll(bool full_clear)
+{
+ OptionItemBase* curr = root;
+ while (curr) {
+ curr->clear_display(full_clear);
+ curr = curr->getNext();
+ }
+}
+
+
+/// @brief return number of menu elements
+int32_t Menu::count()
+{
+ return entry_cnt;
+}
+
+
+/// @brief deletes entry with index @a index
+int32_t Menu::delete_entry(int32_t index)
+{
+ if ( (index >= 0) && (index < entry_cnt) ) {
+ OptionItemBase* curr = this->operator[](index);
+ if (curr) {
+ if (root == curr)
+ root = curr->getNext();
+ if (tail == curr)
+ tail = curr->getPrev();
+ delete curr; // This removes it from the list.
+ --entry_cnt;
+ }
+ }
+
+ return entry_cnt;
+}
+
+
+/// @brief call display(full_display) on all entries
+void Menu::displayAll(bool full_display)
+{
+ OptionItemBase* curr = root;
+ while (curr) {
+ // If a text field (ET_TEXT) is selected, it must be forced
+ // to redraw, so the cursor flipping can be in effect:
+ if (curr->is_selected() && (ET_TEXT == curr->getType()))
+ curr->cursor_flip();
+
+ curr->display(full_display);
+ curr = curr->getNext();
+ }
+}
+
+
+/** @brief distribute a range of items over specified space
+ *
+ * This method distributes the items with the index @a first_idx to
+ * @a last_idx over columns and rows according to the largest item
+ * and available space.
+ *
+ * @param[in] first_idx The first index to distribute
+ * @param[in] last_idx The last index to distribute
+ * @param[in] list_width The total width to distribute over if needed
+ * @param[in] list_height The total height to distribute over
+ * @param[in] y_off Y-Offset where the list starts
+ * @param[in] do_update whether to clear the old display or not.
+ *
+**/
+void Menu::distribute(int32_t first_idx, int32_t last_idx,
+ int32_t list_width, int32_t list_height,
+ int32_t y_off, bool do_update)
+{
+ int32_t item_count = last_idx - first_idx + 1;
+ int32_t item_height = 0;
+ int32_t item_width = 0;
+
+ // valid?
+ assert( ( last_idx >= first_idx)
+ && "ERROR: last_idx must not be smaller than first_idx!");
+ assert( ( last_idx < entry_cnt )
+ && "ERROR: last_idx is out of range");
+ if ( (last_idx < first_idx) || (last_idx >= entry_cnt) )
+ return;
+
+ // 1: Determine minimum width and height:
+ int32_t curr_w = 0, curr_h = 0;
+
+ for (int32_t num = first_idx; num <= last_idx; ++num) {
+ OptionItemBase* curr = this->operator[](num);
+ if (curr) {
+ curr->getDimension(curr_w, curr_h);
+ if (curr_w > item_width)
+ item_width = curr_w;
+ if (curr_h > item_height)
+ item_height = curr_h;
+ }
+ }
+
+ // The width must be increased, as items might get selected:
+ item_width += select_text_len;
+
+ // Set base values
+ int32_t rows = list_height / item_height;
+ int32_t cols = (item_count / rows)
+ + (item_count % rows ? 1 : 0);
+ int32_t colOff = (list_width / 2) - (cols * (item_width / 2));
+
+ for (int32_t idx = first_idx; idx <= last_idx; ++idx) {
+ OptionItemBase* curr = this->operator[](idx);
+ if (curr) {
+ int32_t num = idx - first_idx;
+ int32_t cur_col = num / rows;
+ int32_t x = colOff + (cur_col * item_width);
+ int32_t y = item_height * (num % rows);
+ curr->move(menu_x + x, menu_y + y_off + y, do_update);
+ }
+ }
+}
+
+
+/// @brief return pointer to the currently selected entry or nullptr if none is selected
+OptionItemBase* Menu::getSelected()
+{
+ if ( (entry_sel > -1) && (entry_sel < entry_cnt))
+ return this->operator[](entry_sel);
+ return nullptr;
+}
+
+
+/// @brief return a const pointer to the menu title
+const char* Menu::getTitle() const
+{
+ return title;
+}
+
+
+/// @brief move an entry somewhere else
+void Menu::move_entry(int32_t from_idx, int32_t to_idx)
+{
+ assert( (from_idx > -1) && (from_idx < entry_cnt)
+ && "ERROR: from_idx is out of range!");
+ assert( (to_idx > -1) && (to_idx < entry_cnt)
+ && "ERROR: to_idx is out of range!");
+
+ if (from_idx != to_idx) {
+ OptionItemBase* toMove = operator[](from_idx);
+ assert (toMove && "ERROR: Something is completely FUBAR here!");
+
+ if (to_idx > from_idx)
+ // in this case the spot will move one down
+ --to_idx;
+
+ // Take it out:
+ if (0 == from_idx)
+ // It is root
+ root = toMove->getNext();
+ if ((entry_cnt - 1) == from_idx)
+ // Or tail
+ tail = toMove->getPrev();
+ toMove->remove();
+ --entry_cnt;
+
+ // And re-insert
+ if (0 == to_idx) {
+ toMove->insert_before(root);
+ root = toMove;
+ } else if (entry_cnt == to_idx) {
+ toMove->insert_after(tail);
+ tail = toMove;
+ } else
+ toMove->insert_before(this->operator[](to_idx));
+ ++entry_cnt;
+ } // end of to_idx != from_idx
+}
+
+
+/// @brief do a redraw of one element
+void Menu::redraw(int32_t index, bool update_full)
+{
+ if ( (index >= 0) && (index < entry_cnt) ) {
+ OptionItemBase* curr = this->operator[](index);
+ if (curr) {
+ curr->clear_display(update_full);
+ curr->display(update_full);
+ }
+ }
+}
+
+
+/// @brief do a full redraw of everything
+void Menu::redrawAll(bool full_redraw)
+{
+ SHOW_MOUSE(nullptr)
+ this->clearAll(full_redraw);
+
+ // If this is a full redraw, the background and
+ // menu title must be drawn as well.
+ if (full_redraw) {
+ if (++bgOffset == INT_MAX)
+ bgOffset = 0;
+ drawMenuBackground (bgType, bgOffset, bgItems);
+ textout_ex (global.canvas, font, title, title_x + 2, menu_y + 12, BLACK, -1);
+ textout_ex (global.canvas, font, title, title_x + 5, menu_y + 14, WHITE, -1);
+ }
+
+ this->displayAll(full_redraw);
+ SHOW_MOUSE(global.canvas)
+
+ if (full_redraw)
+ quickChange(false);
+
+}
+
+
+void Menu::setLanguage(bool autorefresh)
+{
+ if (env.language != menu_lang) {
+ menu_lang = env.language;
+
+ if (!title_set)
+ title = MenuTitleText[menu_class][menu_lang][0];
+
+ OptionItemBase* curr = root;
+ const char* const* titles = MenuTitleText[menu_class][menu_lang];
+
+ while (curr) {
+ int32_t title_idx = curr->getTitleIdx();
+
+ // 1: Set new title (if not manually set)
+ if (title_idx > -1)
+ curr->setTitle(titles[title_idx]);
+
+ // 2: Set new text array if based on a pre-set
+ if (curr->needs_text()) {
+ const char* const* texts = OptionClassText[curr->getTextClass()][menu_lang];
+ curr->setTexts(const_cast<const char**>(texts));
+ }
+
+ // 3: If this is a sub-menu, call an update dispatcher
+ if (ET_MENU == curr->getType())
+ static_cast<OptionItemMenu*>(curr)->setLanguage();
+
+ curr = curr->getNext();
+ }
+
+ if (autorefresh)
+ this->redrawAll(true);
+ }
+}
+
+
+void Menu::setTitle(const char* new_title, bool autorefresh)
+{
+ if (new_title) {
+ // Delete old title if it was set already
+ if (title_set && title)
+ free(const_cast<char*>(title));
+
+ title = strdup(new_title);
+ title_set = true;
+
+ if (autorefresh)
+ this->redrawAll(true);
+ }
+}
+
+
+/* ------------------------
+ * --- Public operators ---
+ * ------------------------
+ */
+
+
+/** @brief Parentheses operator to use an instance like a function
+ *
+ * The operator hands over input control to the menu. It will return only
+ * if an option is a button that returns a key code. So make sure to have
+ * at least one button per menu that lets you get out ... or stay forever. ;)
+ *
+ * <I>Note</I>: As a safety measure the operator checks for the existence of
+ * a returning button and asserts that existence. New menus should be checked
+ * in debug mode at least once.
+ *
+ * @return The key code of a clicked exiting button.
+**/
+int32_t Menu::operator()()
+{
+ bool has_exit_button = false;
+ OptionItemBase* curr = root;
+
+ while (!has_exit_button && curr) {
+ has_exit_button = curr->isExitButton();
+ curr = curr->getNext();
+ }
+
+ assert(has_exit_button && "ERROR: A Menu without an exit button is unleavable!");
+
+ // Needed Loop values :
+ int32_t key_code = -1;
+ int32_t end_event = 0;
+ int32_t allegro_key = 0;
+ bool mlb_is_pressed = false; // Left mouse button is pressed,
+ bool mlb_is_released = true; // and was released.
+ bool mrb_is_pressed = false; // Right mouse button is pressed,
+ bool mrb_is_released = true; // and was released.
+ int32_t ms_per_frame = 1000 / env.frames_per_second;
+ int32_t mlb_x = 0;
+ int32_t mlb_y = 0;
+ int32_t mouse_clock = MOUSE_RELEASE_DELAY;
+ int32_t mouse_round = 0;
+ int32_t mouse_reduct = 0;
+ bool has_ctrl_down = false;
+ eEntryType last_clicked = ET_NONE;
+
+ flush_inputs();
+ WIN_CLOCK_INIT
+ menu_ms_reset();
+
+ // Set background style
+ bgType = env.dynamicMenuBg
+ ? static_cast<eBackgroundTypes>(rand() % BACKGROUND_COUNT)
+ : BACKGROUND_BLANK;
+ bgOffset = (RAND_MAX / 4) + (rand() % (RAND_MAX / 4));
+ bgItems = (rand() % 100) + 20;
+
+ // Initial display:
+ redrawAll(true);
+
+ /* ---------------------------------------
+ * --- Input handling and drawing loop ---
+ * ---------------------------------------
+ */
+ while (-1 == key_code) {
+ int32_t ms_unused = ms_per_frame - menu_ms_get();
+ if (ms_unused > 0)
+ MSLEEP(ms_unused)
+ redrawAll(true);
+
+ if (global.isCloseBtnPressed()) {
+ key_code = KEY_ESC; // Exit loop
+ end_event = key_code; // Exit menu
+ continue;
+ }
+
+ /// --------------------------------------
+ /// --- A) Pre-handle key press events ---
+ /// --------------------------------------
+ has_ctrl_down = (key[KEY_LCONTROL] || key[KEY_RCONTROL]);
+
+ if ( keypressed() ) {
+ allegro_key = readkey();
+ key_code = allegro_key >> 8;
+ if (KEY_DOWN == key_code) {
+ this->selectNext();
+ key_code = -1;
+ } else if (KEY_UP == key_code) {
+ this->selectPrev();
+ key_code = -1;
+ } else if (KEY_ENTER_PAD == key_code)
+ key_code = KEY_ENTER;
+
+ } // End of having a pressed key
+
+
+ /// --------------------------------------
+ /// --- B) Handle mouse button events ---
+ /// --------------------------------------
+
+ mlb_x = mouse_x;
+ mlb_y = mouse_y;
+
+ // Set mouse button status anew
+ mlb_is_pressed = mouse_b & 1 ? true : false;
+ mrb_is_pressed = mouse_b & 2 ? true : false;
+
+ // Fix release status on mouse button states:
+ if (!mlb_is_pressed) mlb_is_released = true;
+ if (!mrb_is_pressed) mrb_is_released = true;
+
+ // reset mouse clock if both are released
+ if (mlb_is_released && mrb_is_released) {
+ mouse_clock = MOUSE_RELEASE_DELAY;
+ mouse_round = 0;
+ mouse_reduct = 0;
+ }
+
+ // Be sure only new left mouse button presses are recorded
+ if (mlb_is_released && mlb_is_pressed)
+ mlb_is_released = false;
+ else if (mlb_is_pressed) {
+ if ( (--mouse_clock > 0)
+ || ( (ET_VALUE != last_clicked)
+ && (ET_COLOR != last_clicked) ) )
+ mlb_is_pressed = false;
+ }
+
+ // The same applies to the right button
+ if (mrb_is_released && mrb_is_pressed && !mlb_is_pressed)
+ mrb_is_released = false;
+ // Note: But the release is set to false anyway, so pressing
+ // both buttons will not result in a right mouse button event
+ // if held and the left button is released.
+ else if (mrb_is_pressed) {
+ if ( (--mouse_clock > 0)
+ || ( (ET_VALUE != last_clicked)
+ && (ET_COLOR != last_clicked) ) )
+ mrb_is_pressed = false;
+ }
+
+ // Handle the mouse delay:
+ if (!mouse_clock) {
+ if ( (ET_VALUE == last_clicked)
+ || (ET_COLOR == last_clicked) ) {
+ if (MOUSE_DELAY_REDUCT == ++mouse_round) {
+ mouse_round = 0;
+ if ( (ET_COLOR == last_clicked)
+ || (++mouse_reduct >= MOUSE_RELEASE_DELAY) )
+ mouse_reduct = MOUSE_RELEASE_DELAY - 1;
+ }
+ } else
+ mouse_reduct = 0;
+ mouse_clock = MOUSE_RELEASE_DELAY - mouse_reduct;
+ }
+
+ // Determine whether a click hit something
+ int32_t event = mlb_is_pressed || mrb_is_pressed
+ ? selectClicked(mlb_x, mlb_y)
+ : 0;
+
+
+ /// --------------------------------
+ /// --- C) Handle current events ---
+ /// --------------------------------
+
+ if ( event || (key_code > 0) ) {
+ curr = getSelected();
+ if (curr) {
+ eEntryType type = curr->getType();
+ bool old_mouse = env.osMouse; // To catch mouse changes
+
+ // Note whether clicked on elements for the clock delay reduction
+ if (event)
+ last_clicked = type;
+ else
+ last_clicked = ET_NONE;
+
+ // ET_VALUE needs handling for left/right keys:
+ if (ET_VALUE == type) {
+ if (KEY_RIGHT == key_code)
+ event = 1;
+ else if (KEY_LEFT == key_code)
+ event = -1;
+ // If the right mouse button or ctrl key was pressed,
+ // multiply the event by 10
+ if (mrb_is_pressed || has_ctrl_down)
+ event *= 10;
+ }
+
+ // Set key_code to activation result
+ key_code = curr->activate(event, mlb_x, mlb_y, allegro_key);
+
+ // If this was a sub menu, redraw the current menu:
+ if (ET_MENU == type)
+ redrawAll(true);
+
+ // Some elements trigger end-events
+ if ( (key_code > 0)
+ // If this was a sub menu with KEY_ESC result,
+ // it just means that the sub menu was closes:
+ && ( (ET_MENU != type)
+ || (KEY_ESC != key_code) ) ) {
+ end_event = key_code;
+ } else
+ key_code = -1;
+ allegro_key = 0;
+
+ // If the mouse was changed, the change must be performed
+ // at once. If we didn't do this here, switching back to
+ // standard mouse makes it invisible until the menu exits.
+ if (old_mouse != env.osMouse)
+ init_mouse_cursor();
+
+ } // End of having a menu entry to handle
+ } // End of having mouse button or key event
+
+ // Update non-OS mouse movements
+ SHOW_MOUSE(global.canvas)
+
+ } // End of input/drawing loop
+
+ WIN_CLOCK_REMOVE
+
+ // As the menu does not clear error messages, a possible
+ // message must be cleared now:
+ if (errorMessage)
+ errorMessage = nullptr;
+
+ return end_event;
+
+} // End of Menu::operator()()
+
+
+/** @brief Get a stored option by index
+ *
+ * If @a index is out of range, nullptr is returned.
+ *
+ * @param[in] index The index of the wanted option, starting with 0.
+**/
+OptionItemBase* Menu::operator[](int32_t index)
+{
+ OptionItemBase* result = nullptr;
+
+ if ((-1 < index) && (entry_cnt > index)) {
+ int32_t cur_idx = 0;
+ bool go_up = true;
+ result = root;
+
+ // Start front or end?
+ if (index > (entry_cnt / 2)) {
+ result = tail;
+ cur_idx = entry_cnt - 1;
+ go_up = false;
+ }
+
+ // Just wander, this should be safe.
+ while (result && (cur_idx != index)) {
+ if (go_up) {
+ result = result->getNext();
+ ++cur_idx;
+ } else {
+ result = result->getPrev();
+ --cur_idx;
+ }
+
+ assert(result && "ERROR: Something is wrong with the list!");
+ }
+ } // End of having a sane index
+
+ return result;
+}
+
+
+/* -----------------------
+ * --- Private methods ---
+ * -----------------------
+ */
+
+/// @brief simple singly list insert
+int32_t Menu::insert_option(OptionItemBase* new_opt)
+{
+ if (new_opt) {
+ // Insert into list:
+ if (tail) {
+ new_opt->insert_after(tail);
+ tail = new_opt;
+ } else {
+ root = new_opt;
+ tail = new_opt;
+ }
+
+ ++entry_cnt;
+ }
+ return entry_cnt;
+}
+
+
+/// @brief simple singly list insert with title setting
+int32_t Menu::insert_option(OptionItemBase* new_opt, int32_t title_idx,
+ const char* title_)
+{
+ if (new_opt) {
+ if (title_)
+ new_opt->setTitle(title_);
+ else if (is_title_idx_valid(title_idx))
+ new_opt->setTitle(MenuTitleText[menu_class][menu_lang][title_idx]);
+ return insert_option(new_opt);
+ }
+ return entry_cnt;
+}
+
+
+/// @brief return true if @a title_idx is lower than the first 0x0 entry
+bool Menu::is_title_idx_valid(int32_t title_idx)
+{
+ int32_t curr_idx = 0;
+ const char* const* titles = MenuTitleText[menu_class][menu_lang];
+
+ while ( (curr_idx < title_idx) && titles[curr_idx] )
+ ++curr_idx;
+
+ return ( (title_idx > -1) && (curr_idx == title_idx) && titles[curr_idx]);
+}
+
+
+/** @brief This method selects the entry under the mouse.
+ *
+ * If the mouse position, described by the @a x and @a y parameters,
+ * is not over any entry, nothing happens and 0 is returned.
+ *
+ * If the activated entry is an ET_VALUE and one of the change buttons is hit,
+ * the method returns -1 for down and +1 for up.
+ *
+ * If the activated entry is an ET_BUTTON with associated key code, the key
+ * code is returned.
+ *
+ * In all other cases 0 is returned, as clicking must be activated manually.
+ *
+ * @param[in] x Mouse x coordinate.
+ * @param[in] y Mouse y coordinate.
+ * @return -1/+1 for ET_VALUE change buttons, associated key code for ET_BUTTON
+ * and 0 in all other cases.
+**/
+int32_t Menu::selectClicked(int32_t x, int32_t y)
+{
+ OptionItemBase* curr = root;
+ OptionItemBase* result = nullptr;
+ int32_t retval = 0;
+ int32_t curr_idx = -1;
+
+ while (curr && !result) {
+ ++curr_idx;
+ if (curr->is_click_in(x, y, retval))
+ result = curr;
+ else
+ curr = curr->getNext();
+ }
+
+ if (result && !result->is_selected()) {
+ unselect();
+ entry_sel = curr_idx;
+ result->select();
+ }
+
+ return retval;
+}
+
+
+/** @brief unselect current selected entry (if any) and select the next.
+ * If no entry is selected, the first one will be chosen.
+ * If the last entry is selected, no entry will be chosen.
+**/
+void Menu::selectNext()
+{
+ OptionItemBase* curr = nullptr;
+
+ if (entry_sel > -1) {
+ curr = operator[](entry_sel);
+ if (curr)
+ curr->unselect();
+ }
+
+ // If this was the last entry, none is to be selected
+ if (++entry_sel >= entry_cnt)
+ entry_sel = -1;
+ else {
+ if (curr)
+ // The previous was unselected
+ curr = curr->getNext();
+ else
+ curr = operator[](entry_sel);
+
+ // Be sure the container works properly!
+ assert(curr && "ERROR: Something is wrong with the OptionEntry list!");
+
+ if (curr)
+ curr->select();
+ }
+}
+
+
+/** @brief unselect current selected entry (if any) and select the previous.
+ * If no entry is selected, the first one will be chosen.
+ * If the last entry is selected, no entry will be chosen.
+**/
+void Menu::selectPrev()
+{
+ OptionItemBase* curr = nullptr;
+
+ if (entry_sel > -1) {
+ curr = operator[](entry_sel);
+ if (curr)
+ curr->unselect();
+ }
+
+ // If this was the first entry, none is to be selected
+ if (--entry_sel != -1) {
+ // Rotate to the end if none was selected
+ if (entry_sel < -1)
+ entry_sel = entry_cnt - 1;
+
+ if (curr)
+ // The next was unselected
+ curr = curr->getPrev();
+ else
+ curr = operator[](entry_sel);
+
+ // Be sure the container works properly!
+ assert(curr && "ERROR: Something is wrong with the OptionEntry list!");
+
+ if (curr)
+ curr->select();
+ }
+}
+
+
+/// @brief little helper to be able to add options from inside the header
+void Menu::setTexts(OptionItemBase* item,
+ const char** texts,
+ eTextClass text_class)
+{
+ assert( item && (texts || (TC_FREETEXT != text_class))
+ && (TC_NONE != text_class) && "ERROR: This does not fit at all!");
+ if (item) {
+ if ( (TC_FREETEXT == text_class) && texts) {
+ item->setTextClass(text_class);
+ item->setTexts(texts);
+ } else if (TC_NONE != TC_FREETEXT) {
+ item->setTextClass(text_class);
+ item->setTexts(const_cast<const char**>(OptionClassText[text_class][menu_lang]));
+ }
+ }
+}
+
+
+/** @brief unselect current selected entry (if any).
+ * If no entry is selected, nothing happens.
+**/
+void Menu::unselect()
+{
+ OptionItemBase* curr = nullptr;
+
+ if (entry_sel > -1) {
+ curr = operator[](entry_sel);
+ if (curr)
+ curr->unselect();
+ entry_sel = -1;
+ }
+}
+
+
+/// @brief display function for tank bitmaps plus tank type text
+bool display_tank_desc(int32_t* tanknum, int32_t x, int32_t y)
+{
+ assert(tanknum && "ERROR: tanknum must be set");
+
+ assert(*tanknum > -1 && "ERROR: tanknum too low");
+ assert(*tanknum < TT_TANK_COUNT && "ERROR: tanknum too high");
+
+ if (!tanknum || (*tanknum < 0) || (*tanknum >= TT_TANK_COUNT))
+ return false;
+
+ BITMAP* tank_bmp = env.tank[ *tanknum ? *tanknum + TO_TANK : *tanknum];
+ BITMAP* turr_bmp = env.tankgun[*tanknum ? *tanknum + TO_TURRET : *tanknum];
+ int32_t tank_off_x = ROUNDu(tank_bmp->w / 2);
+ int32_t tank_off_y = tank_bmp->h;
+ int32_t turr_off_x = ROUNDu(turr_bmp->w / 2);
+ int32_t turr_off_y = ROUNDu(turr_bmp->h / 2) - 2;
+ int32_t tank_x = x + tank_off_x + 1;
+ int32_t tank_y = y + turr_off_y + 1;
+ int32_t text_y = tank_y + (tank_off_y / 2) - (env.fontHeight / 2);
+ int32_t text_x = tank_x + tank_off_x + 5;
+ const char* tank_text = OptionClassText[TC_TANKTYPE][env.language][*tanknum];
+
+ draw_sprite (global.canvas, tank_bmp, tank_x - tank_off_x, tank_y);
+ rotate_sprite (global.canvas, turr_bmp, tank_x - turr_off_x, tank_y - turr_off_y,
+ itofix(224) );
+
+ textout_ex (global.canvas, font, tank_text, text_x, text_y, BLACK, -1);
+
+ global.make_update(x, y, text_x + text_length(font, tank_text) - x,
+ tank_y + turr_off_y + tank_off_y - y);
+
+ return true;
+}
+
diff --git a/src/menu.h b/src/menu.h
index 2f5bf24..7a933e3 100644
--- a/src/menu.h
+++ b/src/menu.h
@@ -1,9 +1,9 @@
-#ifndef MENU_HEADER
-#define MENU_HEADER
+#pragma once
+#ifndef ATANKS_SRC_MENU_H_INCLUDED
+#define ATANKS_SRC_MENU_H_INCLUDED
/*
* atanks - obliterate each other with oversize weapons
- * Copyright (C) 2003 Thomas Hudson
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -18,52 +18,425 @@
* 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.
- * */
-#include "globaldata.h"
+ *
+ */
+
+#include "optionitem.h"
+#include "optionitemmenu.h"
+#include "optionitemplayer.h"
+#include "button.h"
+#include <new> // for bad_alloc exception
-#define PLAY_GAME 1
-#define LOAD_GAME 2
-#define ESC_MENU 3
+/** @file menu.h
+ * @brief Declare Menu class for self managing menus
+**/
-#define TEXT_BOX_LENGTH 14
+
+/** @enum eMenuReturnCodes
+ * @brief Standard return codes for the main loop
+**/
+enum eMenuReturnCodes {
+ MRC_None = 0,
+ MRC_Play_Game,
+ MRC_Load_Game,
+ MRC_Esc_Menu
+};
-enum menuEntryType
+/** @class Menu
+ * @brief A class to build menus out of option items.
+ *
+ * @todo : Write more
+**/
+class Menu
{
- OPTION_MENUTYPE, OPTION_DOUBLETYPE, OPTION_TOGGLETYPE, OPTION_SPECIALTYPE, OPTION_ACTIONTYPE, OPTION_TEXTTYPE, OPTION_COLORTYPE
+public:
+
+ /* -------------------------------------------
+ * --- Public constructors and destructors ---
+ * -------------------------------------------
+ */
+
+ explicit Menu(eMenuClass class_, int32_t menuX, int32_t menuY);
+ ~Menu();
+
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ // Add a button without action function.
+ int32_t addButton(int32_t title_idx, const char* title_, int32_t key_code,
+ BITMAP* bmp, BITMAP* hover,
+ BITMAP* released, bool text_only,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding);
+
+
+ // Add a color option
+ int32_t addColor(int32_t* target, int32_t title_idx,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t show_size, int32_t padding);
+
+
+ // Add a sub menu option with Menu target
+ int32_t addMenu(Menu* menu, int32_t title_idx, int32_t color,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding);
+
+
+ // Add a sub menu option with PLAYER target (set title_idx to -1 to use player name)
+ int32_t addMenu(PLAYER** player,
+ int32_t (*action_)(PLAYER** player_, int32_t),
+ int32_t title_idx,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding);
+
+
+ // Special minimum variant for editable text options
+ int32_t addText(char* target, int32_t title_idx, uint32_t max_len,
+ int32_t color, const char* format,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding);
+
+
+ /** @brief This adds a text option with readonly text
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * Please also note that a title is displayed to the left of the display
+ * area.
+ *
+ * @param[in] target Pointer to the target to display.
+ * @param[in] title_idx Index of the title if it is listed in MenuTitleText.
+ * @param[in] color Regular display color of the title/target.
+ * @param[in] format The format to represent the target, used by snprintf().
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] padding Distance between title and display.
+ **/
+ template<typename tgt_T>
+ int32_t addText(tgt_T* target, int32_t title_idx,
+ int32_t color, const char* format,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding)
+ {
+ OptionItemBase* curr = nullptr;
+ bool title_valid = is_title_idx_valid(title_idx);
+
+ assert (title_valid && "ERROR: The given title index is invalid");
+
+ if (target && title_valid) {
+ try {
+ curr = new OptionItem<tgt_T, int32_t>(
+ target, 0, color, ET_TEXT,
+ "", title_idx, format,
+ menu_y + top, menu_x + left, width, height,
+ padding);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new TEXT OptionItem\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+ }
+
+ return this->insert_option(curr, title_idx, nullptr);
+ }
+
+
+ // Special minimum variant for toggle types feeding a bool with variable title
+ int32_t addToggle(bool* target, int32_t title_idx, int32_t color,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding);
+
+
+ // Special minimum variant for toggle types feeding a bool with fixed title
+ int32_t addToggle(bool* target, const char* title_, int32_t color,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding);
+
+
+ // Special minimum variant for toggle types handling PLAYER::selected
+ int32_t addToggle(PLAYER** player,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding);
+
+
+ /** @brief Simple ET_VALUE option with direct value representation
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * Please also note that a title is displayed to the left of the display
+ * area and wheel buttons to the right.
+ *
+ * @param[in] target Pointer to the target to handle.
+ * @param[in] title_idx Index of the title if it is listed in MenuTitleText.
+ * @param[in] color Regular display color of the title/target.
+ * @param[in] minimum Minimum value for ET_VALUE targets.
+ * @param[in] maximum Maximum value for ET_VALUE targets.
+ * @param[in] increment Value to increment/decrement the target on activation.
+ * @param[in] format printf format that can pretty print @a target.
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] padding Distance between title, display and wheel buttons.
+ **/
+ template<typename tgt_T, typename opt_T = int32_t>
+ int32_t addValue(tgt_T* target, int32_t title_idx, int32_t color,
+ opt_T minimum, opt_T maximum, opt_T increment,
+ const char* format,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding)
+ {
+ OptionItemBase* curr = nullptr;
+
+ if (target) {
+ try {
+ curr = new OptionItem<tgt_T, opt_T>(
+ target, "", title_idx,
+ nullptr, color, TC_NONE,
+ minimum, maximum, increment, format,
+ menu_y + top, menu_x + left, width, height,
+ padding, nullptr);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new OptionItem\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+ }
+
+ return this->insert_option(curr, title_idx, nullptr);
+ }
+
+
+ /** @brief Simple option with text array representation
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * Please also note that a title is displayed to the left of the display
+ * area.
+ *
+ * @param[in] target Pointer to the target to handle.
+ * @param[in] title_idx Index of the title if it is listed in MenuTitleText.
+ * @param[in] texts Free text array.
+ * @param[in] color Regular display color of the title/target.
+ * @param[in] text_class The text class, set to TC_FREETEXT to use @a texts.
+ * @param[in] maximum Maximum value for ET_VALUE targets.
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] padding Distance between title and display.
+ **/
+ template<typename tgt_T, typename opt_T = int32_t>
+ int32_t addValue(tgt_T* target, int32_t title_idx,
+ const char** texts, int32_t color,
+ eTextClass text_class, opt_T maximum,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding)
+ {
+ OptionItemBase* curr = nullptr;
+
+ if (target) {
+ try {
+ curr = new OptionItem<tgt_T, opt_T>(
+ target, "", title_idx,
+ nullptr, color, TC_NONE, 0, maximum, 1, nullptr,
+ menu_y + top, menu_x + left, width, height,
+ padding, nullptr);
+ this->setTexts(curr, texts, text_class);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new OptionItem\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+ }
+
+ return this->insert_option(curr, title_idx, nullptr);
+ }
+
+
+ /** @brief Value option with text array representation and display function
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * Please also note that a title is displayed to the left of the display
+ * area.
+ *
+ * @param[in] target Pointer to the target to handle.
+ * @param[in] title_idx Index of the title if it is listed in MenuTitleText.
+ * @param[in] texts Free text array.
+ * @param[in] color Regular display color of the title/target.
+ * @param[in] text_class The text class, set to TC_FREETEXT to use @a texts.
+ * @param[in] maximum Maximum value for ET_VALUE targets.
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] padding Distance between title and display.
+ * @param[in] display_ optional display function to use.
+ **/
+ template<typename tgt_T, typename opt_T = int32_t>
+ int32_t addValue(tgt_T* target, int32_t title_idx,
+ const char** texts, int32_t color,
+ eTextClass text_class, opt_T maximum,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding,
+ bool (*display_)(tgt_T* target, int32_t x, int32_t y) )
+ {
+ OptionItemBase* curr = nullptr;
+
+ if (target) {
+ try {
+ curr = new OptionItem<tgt_T, opt_T>(
+ target, "", title_idx,
+ nullptr, color, TC_NONE, 0, maximum, 1, nullptr,
+ menu_y + top, menu_x + left, width, height,
+ padding, display_);
+ this->setTexts(curr, texts, text_class);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new OptionItem\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+ }
+
+ return this->insert_option(curr, title_idx, nullptr);
+ }
+
+
+ /** @brief Value option with text array representation and action function
+ *
+ * Please note: The position @a left / @a top are relative to
+ * the menu position.
+ *
+ * Please also note that a title is displayed to the left of the display
+ * area.
+ *
+ * @param[in] target Pointer to the target to handle.
+ * @param[in,out] action_ Pointer to the action function handling the wheel button click.
+ * @param[in] title_idx Index of the title if it is listed in MenuTitleText.
+ * @param[in] texts Free text array.
+ * @param[in] color Regular display color of the title/target.
+ * @param[in] text_class The text class, set to TC_FREETEXT to use @a texts.
+ * @param[in] maximum Maximum value for ET_VALUE targets.
+ * @param[in] left Relative left position of the display area to the menu.
+ * @param[in] top Relative top position of the display area to the menu.
+ * @param[in] width Width of the display area. The real width might be larger.
+ * @param[in] height Height of the display area.
+ * @param[in] padding Distance between title and display.
+ **/
+ template<typename tgt_T, typename opt_T = int32_t>
+ int32_t addValue(tgt_T* target,
+ int32_t (*action_)(tgt_T* target, int32_t val),
+ int32_t title_idx,
+ const char** texts, int32_t color,
+ eTextClass text_class, opt_T maximum,
+ int32_t left, int32_t top, int32_t width, int32_t height,
+ int32_t padding)
+ {
+ OptionItemBase* curr = nullptr;
+
+ if (target) {
+ try {
+ curr = new OptionItem<tgt_T, opt_T>(
+ target, action_, ET_VALUE,
+ "", title_idx, nullptr, color, TC_NONE,
+ 0, maximum, 1, nullptr,
+ menu_y + top, menu_x + left, width, height,
+ padding, nullptr);
+ this->setTexts(curr, texts, text_class);
+ } catch (std::bad_alloc &e) {
+ cerr << __FUNCTION__ << " : failed to allocate new OptionItem\n";
+ cerr << " [" << e.what() << "]" << endl;
+ }
+ }
+
+ return this->insert_option(curr, title_idx, nullptr);
+ }
+
+
+ void clearAll (bool full_clear);
+ int32_t count ();
+ int32_t delete_entry(int32_t index);
+ void displayAll (bool full_display);
+ void distribute (int32_t first_idx, int32_t last_idx,
+ int32_t list_width, int32_t list_height,
+ int32_t y_off, bool do_update);
+ OptionItemBase* getSelected ();
+ const char* getTitle () const;
+ void move_entry (int32_t from_idx, int32_t to_idx);
+ void redraw (int32_t index, bool update_full);
+ void redrawAll (bool full_redraw);
+ void setLanguage (bool autorefresh);
+ void setTitle (const char* new_title, bool autorefresh);
+
+
+ /* ------------------------
+ * --- Public operators ---
+ * ------------------------
+ */
+
+ // operator() to use a menu instance like a function
+ int32_t operator()();
+
+ // Get a stored option by index
+ OptionItemBase* operator[](int32_t index);
+
+private:
+
+ /* -----------------------
+ * --- Private methods ---
+ * -----------------------
+ */
+
+ int32_t insert_option (OptionItemBase* new_opt);
+ int32_t insert_option (OptionItemBase* new_opt, int32_t title_idx,
+ const char* title_);
+ bool is_title_idx_valid(int32_t title_idx);
+ int32_t selectClicked (int32_t x, int32_t y);
+ void selectNext ();
+ void selectPrev ();
+ void setTexts (OptionItemBase* item, const char** texts,
+ eTextClass text_class);
+ void unselect ();
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ int32_t bgItems = 0;
+ int32_t bgOffset = 0;
+ eBackgroundTypes bgType = BACKGROUND_BLANK;
+ int32_t entry_cnt = 0; //!< Number of entries currently in the list.
+ int32_t entry_sel = -1; //!< Currently selected entry or -1 if none is selected.
+ eMenuClass menu_class = MC_MAIN; //!< The class of the menu, decides upon what to display.
+ eLanguages menu_lang = EL_ENGLISH; //!< The language to display
+ int32_t menu_x = 0; //!< X-Pos where the menu background starts
+ int32_t menu_y = 0; //!< Y-Pos where the menu background starts
+ OptionItemBase* root = nullptr; //!< The first menu item
+ OptionItemBase* tail = nullptr; //!< The last menu item
+ const char* title = nullptr; //!< Name/Title of the menu
+ uint32_t title_len = 0; //!< Length of the menu title with the current font.
+ bool title_set = false; //!< Set to true if this has been changed to be an individual title
+ int32_t title_x = 0;
};
-typedef struct
- {
- const char *name;
- int (*displayFunc) (ENVIRONMENT*, int, int, void*);
- int color;
- double *value;
- void *data;
- const char *format;
- double min, max;
- double increment;
- double defaultv;
- char **specialOpts;
- char type;
- int viewonly;
- int x;
- int y;
- } MENUENTRY;
-
-typedef struct
- {
- const char *title;
- int numEntries;
- MENUENTRY *entries;
- int quitButton;
- int okayButton;
- } MENUDESC;
-
-
-// Set the menus to appear in English or Portugese
-void Select_Menu_Language(GLOBALDATA *global);
-
-int options (GLOBALDATA *global, ENVIRONMENT *env, MENUDESC *menu);
-
-#endif
+#define MENU_CLASS_DECLARES 1
+
+
+// --- Helper functions for action/display usage that need optioncontent.h ---
+
+/// @brief display function to display the chosen tank at a specific location
+bool display_tank_desc(int32_t* tanknum, int32_t x, int32_t y);
+
+
+
+#endif // ATANKS_SRC_MENU_H_INCLUDED
+
diff --git a/src/menucontent.h b/src/menucontent.h
deleted file mode 100644
index 04c6a17..0000000
--- a/src/menucontent.h
+++ /dev/null
@@ -1,852 +0,0 @@
-#ifndef MENUCONTENT_HEADER_
-#define MENUCONTENT_HEADER_
-
-/*
- * atanks - obliterate each other with oversize weapons
- * Copyright (C) 2003 Thomas Hudson
- *
- * 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.
- * */
-
-char *onOffText[2] = { "Off", "On"};
-char *onOffRandomText[3] = { "Off", "On", "Random"};
-char *landSlideText[5] = { "None", "Tank Only", "Instant", "Gravity", "Cartoon"};
-char *wallTypeText[5] = { "Rubber", "Steel", "Spring", "Wrap", "Random"};
-char *mouseText[2] = { "Custom", "Default"};
-char *meteorText[4] = { "Off", "Light", "Heavy", "Lethal"};
-char *lightningText[4] = { "Off", "Weak", "Energetic", "Violent"};
-char *laserSatelliteText[4] = { "Off", "Weak", "Strong", "Super"};
-char *languageText[8] = { "English", "Português", "Français", "Deutsch", "Slovak", "Russian", "Spanish", "Italian"};
-char *colourText[2] = { "Regular", "Crispy"};
-char *landTypeText[8] = { "Random", "Canyons", "Mountains", "Valleys", "Hills", "Foothills", "Plains", "None" };
-char *turnTypeText[4] = { "High+", "Low+", "Random", "Simul" };
-char *skipTypeText[2] = { "Off", "Humans Dead"};
-char *soundDriver[6] = { "Auto Detect", "OSS", "ESD", "ARTS", "ALSA", "JACK" };
-
-// portuege version
-char *onOffText_ptbr[2] = { "Desligado", "Ligado"};
-/*translate*/
-char *onOffRandomText_ptbr[3] = { "Desligado", "Ligado", "Aleatório"};
-char *landSlideText_ptbr[5] = { "Nenhum", "Tanque Somente", "Instantâneo", "Gravidade", "Cartoon"};
-char *wallTypeText_ptbr[5] = { "Elástico", "Aço", "Mola", "Envoltório", "Aleatório"};
-char *mouseText_ptbr[2] = { "Personalizado", "Padrão"};
-char *meteorText_ptbr[4] = { "Desligado", "Fraco", "Forte", "Letal"};
-char *lightningText_ptbr[4] = { "Desligado", "Fraco", "Energético", "Violento"};
-char *laserSatelliteText_ptbr[4] = { "Desligado", "Fraco", "Forte", "Super"};
-char *languageText_ptbr[8] = { "English", "Português", "Français", "Deutsch", "Slovak", "Russian", "Spanish", "Italian"};
-char *colourText_ptbr[2] = { "Regular", "Crispy"};
-char *landTypeText_ptbr[8] = { "Aleatório", "Canyons", "Montanhas", "Vales", "Colinas", "Morros", "Planos", "Nenhum" };
-char *turnTypeText_ptbr[4] = { "Melhores+", "Piores+", "Aleatório", "Simular" };
-char *skipTypeText_ptbr[2] = { "Sim", "Não" };
-
-
-// french version
-char *onOffText_fr[2] = { "Non", "Oui"};
-/*translate*/
-char *onOffRandomText_fr[3] = { "Non", "Oui", "Hasard"};
-char *landSlideText_fr[5] = { "Aucun", "Réservoir Seulement", "Instantané", "Gravité", "Dessin animé"};
-char *wallTypeText_fr[5] = { "Elastique", "Acier", "Mou", "Enveloppe", "Aléatoire"};
-char *mouseText_fr[2] = { "Pesonnel", "Défaut"};
-char *meteorText_fr[4] = { "Off", "Light", "Heavy", "Lethal"};
-char *lightningText_fr[4] = { "Aucun", "Faible", "Energique", "Violent"};
-char *laserSatelliteText_fr[4] = { "Aucun", "Faible", "Fort", "Super"};
-char *languageText_fr[8] = { "English", "Português", "Français", "Deutsch", "Slovak", "Russian", "Spanish", "Italian"};
-char *colourText_fr[2] = { "Régulier", "Croustillant"};
-char *landTypeText_fr[8] = { "Aléatoire", "Canyons", "Montagnes", "Vallées", "Collines", "Contreforts", "Plaines", "Aucun" };
-char *turnTypeText_fr[4] = { "Haut", "Bas", "Aléatoire", "Similaire" };
-char *skipTypeText_fr[2] = { "Oui", "Non"};
-
-
-// german version
-char *onOffText_de[2] = { "Aus", "An"};
-char *onOffRandomText_de[3] = { "Aus", "An", "Zufällig"};
-char *landSlideText_de[5] = { "Keine", "Nur Panzer", "Sofort", "Schwerkraft", "Cartoon"};
-char *wallTypeText_de[5] = { "Gummi", "Stahl", "Federnd", "Verbunden", "Zufällig"};
-char *mouseText_de[2] = { "Angepasst", "Standard"};
-char *meteorText_de[4] = { "Aus", "Leicht", "Schwer", "Tödlich"};
-char *lightningText_de[4] = { "Aus", "Schwach", "Stark", "Brutal"};
-char *laserSatelliteText_de[4] = { "Aus", "Schwach", "Stark", "Super"};
-char *languageText_de[8] = { "English", "Português", "Français", "Deutsch", "Slovak", "Russian", "Spanish", "Italian"};
-char *colourText_de[2] = { "Normal", "Kontrastreich"};
-char *landTypeText_de[8] = { "Zufällig", "Canyons", "Berge", "Täler", "Hügel", "Flache Hügel", "Ebene", "Nichts" };
-char *turnTypeText_de[4] = { "Hoch+", "Niedrig+", "Zufällig", "Simul" };
-char *skipTypeText_de[2] = { "Aus", "An"};
-
-// slovak version
-char *onOffText_sk[2] = { "Vypnuté", "Zapnuté"};
-char *onOffRandomText_sk[3] = { "Vypnutý", "Zapnutý", "Náhodný"};
-char *landSlideText_sk[5] = { "Žiaden", "Iba tank", "Okamžitý", "Gravitácia", "Kresl.film"};
-char *wallTypeText_sk[5] = { "Guma", "Oceľ", "Pružina", "Prikrývka", "Náhodný"};
-char *mouseText_sk[2] = { "Vlastné", "Východzie"};
-char *meteorText_sk[4] = { "Vypnuté", "Ľahké", "Ťažké", "Smrteľné"};
-char *lightningText_sk[4] = { "Vypnuté", "Slabé", "Energetické", "Kruté"};
-char *laserSatelliteText_sk[4] = { "Vypnutý", "Slabý", "Silný", "Super"};
-char *languageText_sk[8] = { "Anglicky", "Portugalsky", "Francúzsky", "Nemecky", "Slovensky", "Rusky", "Spanish", "Italian"};
-char *colourText_sk[2] = { "Normálna", "Svieža"};
-char *landTypeText_sk[8] = { "Náhodná", "Kaňony", "Hory", "Údolia", "Kopce", "Úpätia", "Nížiny", "Žiadna" };
-char *turnTypeText_sk[4] = { "Vysoký+", "Nízky+", "Náhodný", "Simul" };
-char *skipTypeText_sk[2] = { "Vypnuté", "Smrť ľudí"};
-
-// Russian version
-char *onOffText_ru[2] = { "Выкл.", "Вкл."};
-char *onOffRandomText_ru[3] = { "Выкл.", "Вкл.", "Случайно"};
-char *landSlideText_ru[5] = { "Выкл.", "Только танки", "Сразу же", "По умолчанию", "Как в мультиках"};
-char *wallTypeText_ru[5] = { "Резиновые", "Непробиваемые", "Пружинящие", "Бесконечность", "Случайные"};
-char *mouseText_ru[2] = { "Собственный", "По умолчанию"};
-char *meteorText_ru[4] = { "Нет", "Слабый", "Сильный", "Смертельный"};
-char *lightningText_ru[4] = { "Нет", "Слабые", "Сильные", "Мощные"};
-char *laserSatelliteText_ru[4] = { "Нет", "Слабые", "Сильные", "Супер!!"};
-char *languageText_ru[8] = { "English", "Português", "Français", "Deutsch", "Slovak", "Русский", "Spanish", "Italian"};
-char *colourText_ru[2] = { "Обычная", "Четкая"};
-char *landTypeText_ru[8] = { "Случайный", "Каньоны", "Горы", "Возвышенность", "Холмы", "Предгорья", "Равнины", "Выкл." };
-char *turnTypeText_ru[4] = { "Сильные +", "Слабые +", "Случайно", "Все сразу" };
-char *skipTypeText_ru[2] = { "Выкл.", "Вкл."};
-
-
-
-
-// declare variables
-MENUDESC mainMenu;
-
-if ( (global->language == LANGUAGE_ENGLISH) || (global->language == LANGUAGE_SPANISH) || (global->language == LANGUAGE_ITALIAN) )
- {
- static MENUENTRY physicsOpts[8] =
- {
- { "Gravity", NULL, WHITE, &env->gravity, NULL, "%2.3f", .025, .325, 0.025, .075, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 80},
- { "Viscosity", NULL, WHITE, &env->viscosity, NULL, "%2.2f", .25, 2.0, 0.25, 1.0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 60},
- { "Land Slide", NULL, WHITE, &env->landSlideType, NULL, "%s", 0, 4, 1, 3, landSlideText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 40},
- { "Land Slide Delay", NULL, WHITE, &env->landSlideDelay, NULL, "%4.0f", 1, 5, 1, 3, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 20},
- { "Wall Type", NULL, WHITE, &env->wallType, NULL, "%s", 0, 4, 1, 1, wallTypeText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight},
- { "Boxed Mode", NULL, WHITE, &env->dBoxedMode, NULL, "%s", 0, 2, 1, 0, onOffRandomText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 20},
- { "Violent Death", NULL, WHITE, &global->violent_death, NULL, "%s", 0, 3, 1, 0, lightningText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 40},
- { "Timed Shots", NULL, WHITE, &global->max_fire_time, NULL, "%3.0f", 0, 180, 5, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 60}
- };
- MENUDESC physicsMenu = { "Physics", 8, physicsOpts, TRUE, FALSE};
-
- static MENUENTRY weatherOpts[7] =
- {
- { "Meteor Showers", NULL, WHITE, &env->meteors, NULL, "%s", 0, 3, 1, 0, meteorText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Lightning", NULL, WHITE, &env->lightning, NULL, "%s", 0, 3, 1, 0, lightningText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Falling Dirt", NULL, WHITE, &env->falling_dirt_balls, NULL, "%s", 0, 3, 1, 0, meteorText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Laser Satellite", NULL, WHITE, &env->satellite, NULL, "%s", 0, 3, 1, 0, laserSatelliteText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Fog", NULL, WHITE, &env->fog, NULL, "%s", 0, 1, 1, 0, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Max Wind Strength", NULL, WHITE, (double*)&env->windstrength, NULL, "%2.0f", 0, 100, 5, 40, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Wind Variation", NULL, WHITE, (double*)&env->windvariation, NULL, "%2.1f", 0, 100, 3, 10, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- };
- MENUDESC weatherMenu = { "Weather", 7, weatherOpts, TRUE, FALSE};
-
-
- static MENUENTRY soundOpts[3] =
- {
- { "All Sound", NULL, WHITE, &global->sound, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Sound Driver", NULL, WHITE, &global->sound_driver, NULL, "%s", 0, 5, 1, 0, soundDriver, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Music", NULL, WHITE, &global->play_music, NULL, "%s", 0, 1, 1, 0, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28}
- };
- MENUDESC soundMenu = { "Sound", 3, soundOpts, TRUE, FALSE};
-
-
- static MENUENTRY graphicsOpts[12] =
- {
- { "Full Screen", NULL, WHITE, &global->full_screen, NULL, "%s", 0, 1, 1, 0, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 108},
- { "Dithering", NULL, WHITE, &global->ditherGradients, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 88},
- { "Detailed Land", NULL, WHITE, &global->detailedLandscape, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Detailed Sky", NULL, WHITE, &global->detailedSky, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Fading Text", NULL, WHITE, &env->dFadingText, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Shadowed Text", NULL, WHITE, &env->dShadowedText, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Colour Theme", NULL, WHITE, &global->colour_theme, NULL, "%s", 0, 1, 1, 1, colourText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Screen Width", NULL, WHITE, &global->temp_screenWidth, NULL, "%4.0f", 800, 1600, 200, 800, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Screen Height", NULL, WHITE, &global->temp_screenHeight, NULL, "%4.0f", 600, 1200, 200, 600, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Mouse Pointer", NULL, WHITE, &global->os_mouse, NULL, "%s", 0, 1, 1, 1, mouseText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 1, global->halfHeight + 72},
- { "Game Speed", NULL, WHITE, &global->frames_per_second, NULL, "%3.0f", 30, 120, 5, 60, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 92},
- { "Custom Background", NULL, WHITE, &env->custom_background, NULL, "%s", 0, 1, 1, 0, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112}
- };
- MENUDESC graphicsMenu = { "Graphics", 12, graphicsOpts, TRUE, FALSE};
-
- static MENUENTRY financeOpts[9] =
- {
- { "Starting Money", NULL, WHITE, (double*)&global->startmoney, NULL, "%2.0f", 0, 200000, 5000, 20000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Interest Rate", NULL, WHITE, (double*)&global->interest, NULL, "%2.2f", 1.0, 1.5, 0.05, 1.25, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Round Win Bonus", NULL, WHITE, (double*)&global->scoreRoundWinBonus, NULL, "%2.0f", 0, 50000, 5000, 10000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Damage Bounty", NULL, WHITE, (double*)&global->scoreHitUnit, NULL, "%2.0f", 0, 500, 25, 75, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Self-Damage Penalty", NULL, WHITE, (double*)&global->scoreSelfHit, NULL, "%2.0f", 0, 10000, 1000, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Tank Destruction Bonus", NULL, WHITE, (double*)&global->scoreUnitDestroyBonus, NULL, "%2.0f", 0, 20000, 2500, 5000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Tank Self-Destruction Penalty", NULL, WHITE, (double*)&global->scoreUnitSelfDestroy, NULL, "%2.0f", 0, 20000, 2500, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 72},
- { "Item Sell Multiplier", NULL, WHITE, (double*)&global->sellpercent, NULL, "%1.2f", 0.0, 1.0, 0.10, 0.80, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth -3, global->halfHeight + 92},
- { "Teams Share", NULL, WHITE, (double *) &global->divide_money, NULL, "%s", 0.0, 1.0, 1.0, 0.0, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112}
- };
- MENUDESC financeMenu = { "Money", 9, financeOpts, TRUE, FALSE};
-
- static MENUENTRY networkOpts[5] =
- {
- { "Check Updates", NULL, WHITE, (double*) &global->check_for_updates, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Networking", NULL, WHITE, (double*) &global->enable_network, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Listen Port", NULL, WHITE, (double*) &global->listen_port, NULL, "%5.0f", 10645, 64645, 1000, 25645, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Server address", NULL, WHITE, (double *) &(global->server_name), NULL, "%s", 0, 0, 0, 0, NULL, OPTION_TEXTTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12 },
- { "Server port", NULL, WHITE, (double *) &(global->server_port), NULL, "%s", 0, 0, 0, 0, NULL, OPTION_TEXTTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32}
- };
- MENUDESC networkMenu = { "Network", 5, networkOpts, TRUE, FALSE};
-
- void *pPhysicsMenu = &physicsMenu;
- void *pWeatherMenu = &weatherMenu;
- void *pGraphicsMenu = &graphicsMenu;
- void *pFinanceMenu = &financeMenu;
- void *pnetworkMenu = &networkMenu;
- void *pSoundMenu = &soundMenu;
-
- static MENUENTRY mainOpts[12] =
- {
- { "Physics", NULL, WHITE, (double*)pPhysicsMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 88},
- { "Weather", NULL, WHITE, (double*)pWeatherMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Graphics", NULL, WHITE, (double*)pGraphicsMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Money", NULL, WHITE, (double*)pFinanceMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Network", NULL, WHITE, (double*)pnetworkMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Sound", NULL, WHITE, (double*) pSoundMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Weapon Tech Level", NULL, WHITE, (double*)&env->weapontechLevel, NULL, "%2.0f", 0, 5, 1, 5, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Item Tech Level", NULL, WHITE, (double *) &env->itemtechLevel, NULL, "%2.0f", 0, 5, 1, 5, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Landscape", NULL, WHITE, (double*)&env->landType, NULL, "%s", 0, 7, 1, LANDTYPE_HILLS, landTypeText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 72},
- { "Turn Order", NULL, WHITE, (double*)&global->turntype, NULL, "%s", 0, 3, 1, TURN_RANDOM, turnTypeText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 92},
- { "Skip AI-only play", NULL, WHITE, &global->skipComputerPlay, NULL, "%s", 0, 1, 1, 1, skipTypeText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112},
- { "Language", NULL, WHITE, &global->language, NULL, "%s", 0, 7, 1, 0, languageText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 132}
- };
-// mainMenu = { "Main Menu", 10, mainOpts, TRUE, FALSE};
- mainMenu.title = "Main Menu";
- mainMenu.numEntries = 12;
- mainMenu.entries = mainOpts;
- mainMenu.quitButton = TRUE;
- mainMenu.okayButton = FALSE;
-
- } // end of English
-
-if (global->language == LANGUAGE_PORTUGUESE) // Portuguese
- {
- static MENUENTRY physicsOpts[8] =
- {
- { "Gravidade", NULL, WHITE, &env->gravity, NULL, "%2.3f", .025, .325, 0.025, .075, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 80},
- { "Viscosidade", NULL, WHITE, &env->viscosity, NULL, "%2.2f", .25, 2.0, 0.25, 1.0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 60},
- { "Deslizamento de Terra", NULL, WHITE, &env->landSlideType, NULL, "%s", 0, 4, 1, 3, landSlideText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 40},
- { "Corrediça da terra atrasa", NULL, WHITE, &env->landSlideDelay, NULL, "%4.0f", 1, 5, 1, 3, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 20},
- { "Tipo de Parede", NULL, WHITE, &env->wallType, NULL, "%s", 0, 4, 1, 1, wallTypeText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight},
- { "Modalidade encaixotada", NULL, WHITE, &env->dBoxedMode, NULL, "%s", 0, 2, 1, 0, onOffRandomText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 20},
- { "Morte violenta", NULL, WHITE, &global->violent_death, NULL, "%s", 0, 3, 1, 0, lightningText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 40},
- { "Tiro programado", NULL, WHITE, &global->max_fire_time, NULL, "%3.0f", 0, 180, 5, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 60}
-
- };
- MENUDESC physicsMenu = { "Física", 7, physicsOpts, TRUE, FALSE};
-
- static MENUENTRY weatherOpts[7] =
- {
- { "Chuvas de Meteoro", NULL, WHITE, &env->meteors, NULL, "%s", 0, 3, 1,
- 0, meteorText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3,
- global->halfHeight - 68},
- { "Relâmpagos", NULL, WHITE, &env->lightning, NULL, "%s", 0, 3, 1, 0,
- lightningText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3,
- global->halfHeight - 48},
- { "Sujeira de queda", NULL, WHITE, &env->falling_dirt_balls, NULL, "%s", 0, 3, 1, 0, meteorText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Satélite do Laser", NULL, WHITE, &env->satellite, NULL, "%s", 0, 3, 1, 0,
- laserSatelliteText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3,
- global->halfHeight - 8},
- { "Neblina", NULL, WHITE, &env->fog, NULL, "%s", 0, 1, 1,
- 0, onOffText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3,
- global->halfHeight + 12},
- { "Velocidade Max do Vento", NULL, WHITE, (double*)&env->windstrength,
- NULL, "%2.0f", 0, 100, 5, 40, NULL, OPTION_DOUBLETYPE, FALSE,
- global->halfWidth - 3, global->halfHeight + 32 },
- { "Variação do Vento", NULL, WHITE, (double*)&env->windvariation,
- NULL, "%2.1f", 0, 100, 3, 10, NULL, OPTION_DOUBLETYPE, FALSE,
- global->halfWidth - 3, global->halfHeight + 52},
- };
- MENUDESC weatherMenu = { "Condições Meteorológicas", 7, weatherOpts,
- TRUE, FALSE
- };
-
- static MENUENTRY soundOpts[3] =
- {
- { "Efeitos de Som", NULL, WHITE, &global->sound, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Sistema de Som", NULL, WHITE, &global->sound_driver, NULL, "%s", 0, 5, 1, 0, soundDriver, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Música", NULL, WHITE, &global->play_music, NULL, "%s", 0, 1, 1, 0, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28}
- };
- MENUDESC soundMenu = { "Som", 3, soundOpts, TRUE, FALSE};
-
-
- static MENUENTRY graphicsOpts[12] =
- {
- { "Tela Cheia", NULL, WHITE, &global->full_screen, NULL, "%s", 0, 1, 1, 0, onOffText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 108},
- { "Pontilhamento", NULL, WHITE, &global->ditherGradients, NULL, "%s",
- 0, 1, 1, 1, onOffText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3,
- global->halfHeight - 88},
- { "Detalhes do Terreno", NULL, WHITE, &global->detailedLandscape, NULL,
- "%s", 0, 1, 1, 1, onOffText_ptbr, OPTION_SPECIALTYPE, FALSE,
- global->halfWidth - 3, global->halfHeight - 68},
- { "Detalhes do Céu", NULL, WHITE, &global->detailedSky, NULL, "%s", 0,
- 1, 1, 1, onOffText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3,
- global->halfHeight - 48},
- { "texto sombreado", NULL, WHITE, &env->dFadingText, NULL, "%s", 0, 1, 1, 1, onOffText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "texto de desvanecimento", NULL, WHITE, &env->dShadowedText, NULL, "%s", 0, 1, 1, 1, onOffText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "tema da cor", NULL, WHITE, &global->colour_theme, NULL, "%s", 0, 1, 1, 1, colourText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
-
- { "Largura da Tela", NULL, WHITE, &global->temp_screenWidth,
- NULL, "%4.0f", 800, 1600, 200, 800, NULL, OPTION_DOUBLETYPE, FALSE,
- global->halfWidth - 3, global->halfHeight + 32 },
- { "Altura da Tela", NULL, WHITE, &global->temp_screenHeight,
- NULL, "%4.0f", 600, 1200, 200, 600, NULL, OPTION_DOUBLETYPE, FALSE,
- global->halfWidth - 3, global->halfHeight + 52},
- { "Ponteiro do Rato", NULL, WHITE, &global->os_mouse, NULL,
- "%s", 0, 1, 1, 1, mouseText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth
- - 1, global->halfHeight + 72},
- { "Velocidade do jogo", NULL, WHITE, &global->frames_per_second, NULL, "%3.0f", 30, 120, 5, 60, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 92},
- { "Fundo personalizado", NULL, WHITE, &env->custom_background, NULL, "%s", 0, 1, 1, 0, onOffText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112}
-
- };
- MENUDESC graphicsMenu = { "Gráficos", 12, graphicsOpts, TRUE, FALSE};
-
- static MENUENTRY financeOpts[9] =
- {
- { "Dinheiro inicial", NULL, WHITE, (double*)&global->startmoney, NULL,
- "%2.0f", 0, 200000, 5000, 20000, NULL, OPTION_DOUBLETYPE, FALSE,
- global->halfWidth - 3, global->halfHeight - 68},
- { "Taxa de Juros", NULL, WHITE, (double*)&global->interest, NULL,
- "%2.2f", 1.0, 1.5, 0.05, 1.25, NULL, OPTION_DOUBLETYPE, FALSE,
- global->halfWidth - 3, global->halfHeight - 48},
- { "Bônus por Vitória", NULL, WHITE,
- (double*)&global->scoreRoundWinBonus, NULL, "%2.0f", 0, 50000, 5000, 10000, NULL, OPTION_DOUBLETYPE,
- FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Bônus por Estrago", NULL, WHITE, (double*)&global->scoreHitUnit,
- NULL, "%2.0f", 0, 500, 25, 75, NULL, OPTION_DOUBLETYPE, FALSE,
- global->halfWidth - 3, global->halfHeight + 12},
- { "Penalidade por Auto-Estrago", NULL, WHITE,
- (double*)&global->scoreSelfHit, NULL, "%2.0f", 0, 10000, 1000, 0, NULL, OPTION_DOUBLETYPE,
- FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Bônus por Tanque Destruído", NULL, WHITE,
- (double*)&global->scoreUnitDestroyBonus, NULL, "%2.0f", 0, 20000, 2500, 5000, NULL,
- OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Penalidade por Auto-Destruição", NULL, WHITE,
- (double*)&global->scoreUnitSelfDestroy, NULL, "%2.0f", 0, 20000, 2500, 0, NULL,
- OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 72},
- { "Multiplicador de Item Vendido", NULL, WHITE,
- (double*)&global->sellpercent, NULL, "%1.2f", 0.0, 1.0, 0.10, 0.80, NULL, OPTION_DOUBLETYPE,
- FALSE, global->halfWidth -3, global->halfHeight + 92},
- { "Parte das equipes", NULL, WHITE, (double *) &global->divide_money, NULL, "%s", 0.0, 1.0, 1.0, 0.0, onOffText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112}
-
- };
- MENUDESC financeMenu = { "Dinheiro", 9, financeOpts, TRUE, FALSE};
-
- static MENUENTRY networkOpts[3] =
- {
- { "Procurar actualizações", NULL, WHITE, (double*) &global->check_for_updates, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Activar Rede", NULL, WHITE, (double*) &global->enable_network, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Número de Porta", NULL, WHITE, (double*) &global->listen_port, NULL, "%5.0f", 10645, 64645, 1000, 25645, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8}
- };
- MENUDESC networkMenu = { "Network", 3, networkOpts, TRUE, FALSE};
-
-
- void *pPhysicsMenu = &physicsMenu;
- void *pWeatherMenu = &weatherMenu;
- void *pGraphicsMenu = &graphicsMenu;
- void *pFinanceMenu = &financeMenu;
- void *pnetworkMenu = &networkMenu;
- void *pSoundMenu = &soundMenu;
-
- static MENUENTRY mainOpts[12] =
- {
- { "Física", NULL, WHITE, (double*)pPhysicsMenu, NULL, NULL, 0, 0, 0,
- 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3,
- global->halfHeight - 88},
- { "Condições Meteorológicas", NULL, WHITE, (double*)pWeatherMenu,
- NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth -
- 3, global->halfHeight - 68},
- { "Gráficos", NULL, WHITE, (double*)pGraphicsMenu, NULL, NULL, 0, 0,
- 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3,
- global->halfHeight - 48},
- { "Finanças", NULL, WHITE, (double*)pFinanceMenu, NULL, NULL, 0, 0, 0,
- 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3,
- global->halfHeight - 28},
- { "Rede", NULL, WHITE, (double*)pnetworkMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Som", NULL, WHITE, (double*) pSoundMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Arma Nível Tecnológico", NULL, WHITE, (double*)&env->weapontechLevel, NULL,
- "%2.0f", 0, 5, 1, 5, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth -
- 3, global->halfHeight + 32},
- { "Artigo Nível Tecnológico", NULL, WHITE, (double*) &env->itemtechLevel, NULL,
- "%2.0f", 0, 5, 1, 5, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Cenário", NULL, WHITE, (double*)&env->landType, NULL, "%s", 0, 7,
- 1, LANDTYPE_HILLS, landTypeText_ptbr, OPTION_SPECIALTYPE, FALSE,
- global->halfWidth - 3, global->halfHeight + 72},
- { "Ordem de Jogadas", NULL, WHITE, (double*)&global->turntype, NULL,
- "%s", 0, 3, 1, TURN_RANDOM, turnTypeText_ptbr, OPTION_SPECIALTYPE, FALSE,
- global->halfWidth - 3, global->halfHeight + 92},
- { "Continuar o Jogo Só com Robôs", NULL, WHITE, &global->skipComputerPlay,
- NULL, "%s", 0, 2, 1, 1, skipTypeText_ptbr, OPTION_SPECIALTYPE, FALSE,
- global->halfWidth - 3, global->halfHeight + 112},
- { "Língua", NULL, WHITE, &global->language, NULL, "%s", 0, 7, 1, 0, languageText_ptbr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 132}
-
- };
-// mainMenu = { "Menu Principal", 10, mainOpts, TRUE, FALSE};
- mainMenu.title = "Menu Principal";
- mainMenu.numEntries = 12;
- mainMenu.entries = mainOpts;
- mainMenu.quitButton = TRUE;
- mainMenu.okayButton = FALSE;
-
- } // end of Portuguese
-
-
-// french
-if (global->language == LANGUAGE_FRENCH)
- {
- static MENUENTRY physicsOpts[8] =
- {
- { "Gravité", NULL, WHITE, &env->gravity, NULL, "%2.3f", .025, .325, 0.025, .075, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 80},
- { "Viscosité", NULL, WHITE, &env->viscosity, NULL, "%2.2f", .25, 2.0, 0.25, 1.0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 40},
- { "Glissements de terrain", NULL, WHITE, &env->landSlideType, NULL, "%s", 0, 4, 1, 3, landSlideText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 40},
- { "Délai glissements de terrain", NULL, WHITE, &env->landSlideDelay, NULL, "%4.0f", 1, 5, 1, 3, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 20},
- { "Murs", NULL, WHITE, &env->wallType, NULL, "%s", 0, 4, 1, 1, wallTypeText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight},
- { "Enfermé dans boîte", NULL, WHITE, &env->dBoxedMode, NULL, "%s", 0, 2, 1, 0, onOffRandomText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 20},
- { "Mort violente", NULL, WHITE, &global->violent_death, NULL, "%s", 0, 3, 1, 0, lightningText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 40},
- { "Projectile synchronisé", NULL, WHITE, &global->max_fire_time, NULL, "%3.0f", 0, 180, 5, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 60}
-
- };
- MENUDESC physicsMenu = { "Physique", 7, physicsOpts, TRUE, FALSE};
-
- static MENUENTRY weatherOpts[7] =
- {
- { "Orages de météorites", NULL, WHITE, &env->meteors, NULL, "%s", 0, 3, 1, 0, meteorText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Éclairs", NULL, WHITE, &env->lightning, NULL, "%s", 0, 3, 1, 0, lightningText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Saleté en chute", NULL, WHITE, &env->falling_dirt_balls, NULL, "%s", 0, 3, 1, 0, meteorText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Satellites Laser", NULL, WHITE, &env->satellite, NULL, "%s", 0, 3, 1, 0, laserSatelliteText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Brouillard", NULL, WHITE, &env->fog, NULL, "%s", 0, 1, 1, 0, onOffText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Force maxi du vent", NULL, WHITE, (double*)&env->windstrength, NULL, "%2.0f", 0, 100, 5, 40, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Variation du vent", NULL, WHITE, (double*)&env->windvariation, NULL, "%2.1f", 0, 100, 3, 10, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- };
- MENUDESC weatherMenu = { "Météo", 7, weatherOpts, TRUE, FALSE};
-
- static MENUENTRY soundOpts[3] =
- {
- { "Effets Sonores", NULL, WHITE, &global->sound, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Système de Son", NULL, WHITE, &global->sound_driver, NULL, "%s", 0, 5, 1, 0, soundDriver, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Musique", NULL, WHITE, &global->play_music, NULL, "%s", 0, 1, 1, 0, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28}
- };
- MENUDESC soundMenu = { "Sound", 3, soundOpts, TRUE, FALSE};
-
-
- static MENUENTRY graphicsOpts[12] =
- {
- { "Full Screen", NULL, WHITE, &global->full_screen, NULL, "%s", 0, 1, 1, 0, onOffText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 108},
- { "Tramage", NULL, WHITE, &global->ditherGradients, NULL, "%s", 0, 1, 1, 1, onOffText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 88},
- { "Détails du terrain", NULL, WHITE, &global->detailedLandscape, NULL, "%s", 0, 1, 1, 1, onOffText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Ciel détaillé", NULL, WHITE, &global->detailedSky, NULL, "%s", 0, 1, 1, 1, onOffText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "texte ombragé", NULL, WHITE, &env->dFadingText, NULL, "%s", 0, 1, 1, 1, onOffText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "texte de effacement", NULL, WHITE, &env->dShadowedText, NULL, "%s", 0, 1, 1, 1, onOffText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Thème de couleurs", NULL, WHITE, &global->colour_theme, NULL, "%s", 0, 1, 1, 1, colourText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Largeur d'écran", NULL, WHITE, &global->temp_screenWidth, NULL, "%4.0f", 800, 1600, 200, 800, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Hauteur d'écran", NULL, WHITE, &global->temp_screenHeight, NULL, "%4.0f", 600, 1200, 200, 600, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Curseur de souris", NULL, WHITE, &global->os_mouse, NULL, "%s", 0, 1, 1, 1, mouseText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 1, global->halfHeight + 72},
- { "Vitesse du jeu", NULL, WHITE, &global->frames_per_second, NULL, "%3.0f", 30, 120, 5, 60, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 92},
- { "Fond fait sur commande", NULL, WHITE, &env->custom_background, NULL, "%s", 0, 1, 1, 0, onOffText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112}
- };
- MENUDESC graphicsMenu = { "Graphismes", 12, graphicsOpts, TRUE, FALSE};
-
- static MENUENTRY financeOpts[9] =
- {
- { "Somme de départ", NULL, WHITE, (double*)&global->startmoney, NULL, "%2.0f", 0, 200000, 5000, 20000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Taux d'intérêt", NULL, WHITE, (double*)&global->interest, NULL, "%2.2f", 1.0, 1.5, 0.05, 1.25, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Gains par victoire", NULL, WHITE, (double*)&global->scoreRoundWinBonus, NULL, "%2.0f", 0, 50000, 5000, 10000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Bonus dommages", NULL, WHITE, (double*)&global->scoreHitUnit, NULL, "%2.0f", 0, 500, 25, 75, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Pénalité auto-dommages", NULL, WHITE, (double*)&global->scoreSelfHit, NULL, "%2.0f", 0, 10000, 1000, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Bonus destruction tank", NULL, WHITE, (double*)&global->scoreUnitDestroyBonus, NULL, "%2.0f", 0, 20000, 2500, 5000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Pénalité autodestruction tank", NULL, WHITE, (double*)&global->scoreUnitSelfDestroy, NULL, "%2.0f", 0, 20000, 2500, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 72},
- { "Coeff. vente item", NULL, WHITE, (double*)&global->sellpercent, NULL, "%1.2f", 0.0, 1.0, 0.10, 0.80, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth -3, global->halfHeight + 92},
- { "Part d'equipes", NULL, WHITE, (double *) &global->divide_money, NULL, "%s", 0.0, 1.0, 1.0, 0.0, onOffText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112}
-
- };
- MENUDESC financeMenu = { "Finances", 9, financeOpts, TRUE, FALSE};
-
- static MENUENTRY networkOpts[3] =
- {
- { "Check Updates", NULL, WHITE, (double*) &global->check_for_updates, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Networking", NULL, WHITE, (double*) &global->enable_network, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Listen Port", NULL, WHITE, (double*) &global->listen_port, NULL, "%5.0f", 10645, 64645, 1000, 25645, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8}
- };
- MENUDESC networkMenu = { "Network", 3, networkOpts, TRUE, FALSE};
-
-
-
- void *pPhysicsMenu = &physicsMenu;
- void *pWeatherMenu = &weatherMenu;
- void *pGraphicsMenu = &graphicsMenu;
- void *pFinanceMenu = &financeMenu;
- void *pnetworkMenu = &networkMenu;
- void *pSoundMenu = &soundMenu;
-
- static MENUENTRY mainOpts[12] =
- {
- { "Physique", NULL, WHITE, (double*)pPhysicsMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 88},
- { "Météo", NULL, WHITE, (double*)pWeatherMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Graphismes", NULL, WHITE, (double*)pGraphicsMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Finances", NULL, WHITE, (double*)pFinanceMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Réseau", NULL, WHITE, (double*)pnetworkMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Sound", NULL, WHITE, (double*) pSoundMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Niveau technique armes", NULL, WHITE, (double*)&env->weapontechLevel, NULL, "%2.0f", 0, 5, 1, 5, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Niveau technique équipement", NULL, WHITE, (double *) &env->itemtechLevel, NULL, "%2.0f", 0, 5, 1, 5, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Paysage", NULL, WHITE, (double*)&env->landType, NULL, "%s", 0, 7, 1, LANDTYPE_HILLS, landTypeText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 72},
- { "Ordre de passage", NULL, WHITE, (double*)&global->turntype, NULL, "%s", 0, 3, 1, TURN_RANDOM, turnTypeText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 92},
- { "Continuer le Jeu Robots seuls", NULL, WHITE, &global->skipComputerPlay, NULL, "%s", 0, 1, 1, 1, skipTypeText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112},
- { "Langue", NULL, WHITE, &global->language, NULL, "%s", 0, 7, 1, 0, languageText_fr, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 132}
- };
-// mainMenu = { "Menu principal", 10, mainOpts, TRUE, FALSE};
- mainMenu.title = "Menu principal";
- mainMenu.numEntries = 12;
- mainMenu.entries = mainOpts;
- mainMenu.quitButton = TRUE;
- mainMenu.okayButton = FALSE;
-
-
- } // end of french
-
-if (global->language == LANGUAGE_GERMAN)
- {
- static MENUENTRY physicsOpts[8] =
- {
- { "Gravitation", NULL, WHITE, &env->gravity, NULL, "%2.3f", .025, .325, 0.025, .075, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 80},
- { "Reibung", NULL, WHITE, &env->viscosity, NULL, "%2.2f", .25, 2.0, 0.25, 1.0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 60},
- { "Erdrutsch", NULL, WHITE, &env->landSlideType, NULL, "%s", 0, 4, 1, 3, landSlideText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 40},
- { "Erdrutsch Verzögerung", NULL, WHITE, &env->landSlideDelay, NULL, "%4.0f", 1, 5, 1,3, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 20},
- { "Wand Art", NULL, WHITE, &env->wallType, NULL, "%s", 0, 4, 1, 1, wallTypeText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight},
- { "Höhlenmodus", NULL, WHITE, &env->dBoxedMode, NULL, "%s", 0, 2, 1, 0, onOffRandomText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 20},
- { "Gewalttätiger Tod", NULL, WHITE, &global->violent_death, NULL, "%s", 0, 3, 1, 0, lightningText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 40},
- { "Zeitlimit", NULL, WHITE, &global->max_fire_time, NULL, "%3.0f", 0, 180, 5, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 60}
- };
- MENUDESC physicsMenu = { "Physik", 8, physicsOpts, TRUE, FALSE};
-
- static MENUENTRY weatherOpts[7] =
- {
- { "Meteoritenregen", NULL, WHITE, &env->meteors, NULL, "%s", 0, 3, 1, 0, meteorText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Gewitter", NULL, WHITE, &env->lightning, NULL, "%s", 0, 3, 1, 0, lightningText_de,OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Schmutzregen", NULL, WHITE, &env->falling_dirt_balls, NULL, "%s", 0, 3, 1, 0, meteorText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Lasersatellit", NULL, WHITE, &env->satellite, NULL, "%s", 0, 3, 1, 0, laserSatelliteText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Nebel", NULL, WHITE, &env->fog, NULL, "%s", 0, 1, 1, 0, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Max Windstärke", NULL, WHITE, (double*)&env->windstrength, NULL, "%2.0f", 0, 100, 5, 40, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Windveränderung", NULL, WHITE, (double*)&env->windvariation, NULL, "%2.1f", 0, 100, 3, 10, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- };
- MENUDESC weatherMenu = { "Wetter", 7, weatherOpts, TRUE, FALSE};
-
- static MENUENTRY soundOpts[3] =
- {
- { "Alle Sounds", NULL, WHITE, &global->sound, NULL, "%s", 0, 1, 1, 1, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Sound Treiber", NULL, WHITE, &global->sound_driver, NULL, "%s", 0, 5, 1, 0, soundDriver, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Musik", NULL, WHITE, &global->play_music, NULL, "%s", 0, 1, 1, 0, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28}
- };
- MENUDESC soundMenu = { "Sounds", 3, soundOpts, TRUE, FALSE};
-
-
-
- static MENUENTRY graphicsOpts[12] =
- {
- { "Full Screen", NULL, WHITE, &global->full_screen, NULL, "%s", 0, 1, 1, 0, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 108},
- { "Dithering", NULL, WHITE, &global->ditherGradients, NULL, "%s", 0, 1, 1, 1, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 88},
- { "Landdetails", NULL, WHITE, &global->detailedLandscape, NULL, "%s", 0, 1, 1, 1, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Himmeldetails", NULL, WHITE, &global->detailedSky, NULL, "%s", 0, 1, 1, 1, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Ausblendender Text", NULL, WHITE, &env->dFadingText, NULL, "%s", 0, 1, 1, 1, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Schattierter Text", NULL, WHITE, &env->dShadowedText, NULL, "%s", 0, 1, 1, 1, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Farbschema", NULL, WHITE, &global->colour_theme, NULL, "%s", 0, 1, 1, 1, colourText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Bildschirmbreite", NULL, WHITE, &global->temp_screenWidth, NULL, "%4.0f", 800, 1600, 200, 800, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Bildschirmhöhe", NULL, WHITE, &global->temp_screenHeight, NULL, "%4.0f", 600, 1200, 200, 600, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Mauszeiger", NULL, WHITE, &global->os_mouse, NULL, "%s", 0, 1, 1, 1, mouseText_de,OPTION_SPECIALTYPE, FALSE, global->halfWidth - 1, global->halfHeight + 72},
- { "Spielgeschwindigket", NULL, WHITE, &global->frames_per_second, NULL, "%3.0f", 30, 120, 5, 60, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 92},
- { "kundenspezifischer Hintergrund", NULL, WHITE, &env->custom_background, NULL, "%s", 0, 1, 1, 0, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112}
- };
- MENUDESC graphicsMenu = { "Grafik", 12, graphicsOpts, TRUE, FALSE};
-
-
- static MENUENTRY financeOpts[9] =
- {
- { "Startgeld", NULL, WHITE, (double*)&global->startmoney, NULL, "%2.0f", 0, 200000, 5000, 20000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Zinssatz", NULL, WHITE, (double*)&global->interest, NULL, "%2.2f", 1.0, 1.5, 0.05,1.25, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Rundenbonus", NULL, WHITE, (double*)&global->scoreRoundWinBonus, NULL, "%2.0f", 0,50000, 5000, 10000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Schadensbonus", NULL, WHITE, (double*)&global->scoreHitUnit, NULL, "%2.0f", 0, 500, 25, 75, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Strafe für Selbstschaden", NULL, WHITE, (double*)&global->scoreSelfHit, NULL, "%2.0f", 0, 10000, 1000, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Zerstörungsbonus", NULL, WHITE, (double*)&global->scoreUnitDestroyBonus, NULL, "%2.0f", 0, 20000, 2500, 5000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Selbstzerstörungsstrafe", NULL, WHITE, (double*)&global->scoreUnitSelfDestroy, NULL, "%2.0f", 0, 20000, 2500, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 72},
- { "Verkaufsmultiplikator", NULL, WHITE, (double*)&global->sellpercent, NULL, "%1.2f",0.0, 1.0, 0.10, 0.80, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth -3, global->halfHeight + 92},
- { "Mannschaftanteil", NULL, WHITE, (double *) &global->divide_money, NULL, "%s", 0.0, 1.0, 1.0, 0.0, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112}
-
- };
- MENUDESC financeMenu = { "Geld", 9, financeOpts, TRUE, FALSE};
-
- static MENUENTRY networkOpts[3] =
- {
- { "Auf Aktualisierungen prüfen", NULL, WHITE, (double*) &global->check_for_updates, NULL, "%s", 0, 1, 1, 1, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Netzwerk", NULL, WHITE, (double*) &global->enable_network, NULL, "%s", 0, 1, 1, 1, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "offener Port", NULL, WHITE, (double*) &global->listen_port, NULL, "%5.0f", 10645, 64645, 1000, 25645, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8}
- };
- MENUDESC networkMenu = { "Netzwerk", 3, networkOpts, TRUE, FALSE};
-
-
- void *pPhysicsMenu = &physicsMenu;
- void *pWeatherMenu = &weatherMenu;
- void *pGraphicsMenu = &graphicsMenu;
- void *pFinanceMenu = &financeMenu;
- void *pnetworkMenu = &networkMenu;
- void *pSoundMenu = &soundMenu;
-
- static MENUENTRY mainOpts[12] =
- {
- { "Physik", NULL, WHITE, (double*)pPhysicsMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 88},
- { "Wetter", NULL, WHITE, (double*)pWeatherMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Grafik", NULL, WHITE, (double*)pGraphicsMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Geld", NULL, WHITE, (double*)pFinanceMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Netzwerk", NULL, WHITE, (double*)pnetworkMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Sounds", NULL, WHITE, (double*) pSoundMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Technologiestufe Waffen", NULL, WHITE, (double*)&env->weapontechLevel, NULL, "%2.0f", 0, 5, 1, 5, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Technologiestufe Gegenstände", NULL, WHITE, (double *) &env->itemtechLevel, NULL, "%2.0f", 0, 5, 1, 5, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Landschaft", NULL, WHITE, (double*)&env->landType, NULL, "%s", 0, 7, 1, LANDTYPE_HILLS, landTypeText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight+ 72},
- { "Reihenfolge", NULL, WHITE, (double*)&global->turntype, NULL, "%s", 0, 3, 1, TURN_RANDOM, turnTypeText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 92},
- { "Überspringe Nur-KI", NULL, WHITE, &global->skipComputerPlay, NULL, "%s", 0, 1, 1, 1, skipTypeText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112},
- { "Sprache", NULL, WHITE, &global->language, NULL, "%s", 0, 7, 1, 0, languageText_de,OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 132}
- };
-// mainMenu = { "Hauptmenü", 10, mainOpts, TRUE, FALSE};
- mainMenu.title = "Hauptmenü";
- mainMenu.numEntries = 12;
- mainMenu.entries = mainOpts;
- mainMenu.quitButton = TRUE;
- mainMenu.okayButton = FALSE;
-
- } // end of German
-
-
-if (global->language == LANGUAGE_SLOVAK)
- {
- static MENUENTRY physicsOpts[8] =
- {
- { "Gravitácia", NULL, WHITE, &env->gravity, NULL, "%2.3f", .025, .325, 0.025, .075, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 80},
- { "Viskozita", NULL, WHITE, &env->viscosity, NULL, "%2.2f", .25, 2.0, 0.25, 1.0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 60},
- { "Zosun zeme", NULL, WHITE, &env->landSlideType, NULL, "%s", 0, 4, 1, 3, landSlideText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 40},
- { "Zdržanie zosunu zeme", NULL, WHITE, &env->landSlideDelay, NULL, "%4.0f", 1, 5, 1, 3, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 20},
- { "Typ steny", NULL, WHITE, &env->wallType, NULL, "%s", 0, 4, 1, 1, wallTypeText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight},
- { "Režim krabíc", NULL, WHITE, &env->dBoxedMode, NULL, "%s", 0, 2, 1, 0, onOffRandomText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 20},
- { "Krutá smrť", NULL, WHITE, &global->violent_death, NULL, "%s", 0, 3, 1, 0, lightningText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 40},
- { "Časované strely", NULL, WHITE, &global->max_fire_time, NULL, "%3.0f", 0, 180, 5, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 60}
- };
- MENUDESC physicsMenu = { "Fyzika", 7, physicsOpts, TRUE, FALSE};
-
- static MENUENTRY weatherOpts[7] =
- {
- { "Dážď meteorov", NULL, WHITE, &env->meteors, NULL, "%s", 0, 3, 1, 0, meteorText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Blesky", NULL, WHITE, &env->lightning, NULL, "%s", 0, 3, 1, 0, lightningText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Padajúca zem", NULL, WHITE, &env->falling_dirt_balls, NULL, "%s", 0, 3, 1, 0, meteorText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Laserový satelit", NULL, WHITE, &env->satellite, NULL, "%s", 0, 3, 1, 0, laserSatelliteText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Hmla", NULL, WHITE, &env->fog, NULL, "%s", 0, 1, 1, 0, onOffText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Maximálna sila vetra", NULL, WHITE, (double*)&env->windstrength, NULL, "%2.0f", 0, 100, 5, 40, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Zmena vetra", NULL, WHITE, (double*)&env->windvariation, NULL, "%2.1f", 0, 100, 3, 10, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- };
- MENUDESC weatherMenu = { "Počasie", 7, weatherOpts, TRUE, FALSE};
-
- static MENUENTRY soundOpts[3] =
- {
- { "Všetky zvuky", NULL, WHITE, &global->sound, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Ovládač zvuku", NULL, WHITE, &global->sound_driver, NULL, "%s", 0, 5, 1, 0, soundDriver, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Hudba", NULL, WHITE, &global->play_music, NULL, "%s", 0, 1, 1, 0, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28}
- };
- MENUDESC soundMenu = { "Zvuk", 3, soundOpts, TRUE, FALSE};
-
-
-
- static MENUENTRY graphicsOpts[12] =
- {
- { "Na celú obrazovku", NULL, WHITE, &global->full_screen, NULL, "%s", 0, 1, 1, 0, onOffText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 108},
- { "Rozptyl", NULL, WHITE, &global->ditherGradients, NULL, "%s", 0, 1, 1, 1, onOffText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 88},
- { "Detaily krajiny", NULL, WHITE, &global->detailedLandscape, NULL, "%s", 0, 1, 1, 1, onOffText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Detaily oblohy", NULL, WHITE, &global->detailedSky, NULL, "%s", 0, 1, 1, 1, onOffText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Slabnúci text", NULL, WHITE, &env->dFadingText, NULL, "%s", 0, 1, 1, 1, onOffText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Text s tieňom", NULL, WHITE, &env->dShadowedText, NULL, "%s", 0, 1, 1, 1, onOffText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Farebná téma", NULL, WHITE, &global->colour_theme, NULL, "%s", 0, 1, 1, 1, colourText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Šírka obrazovky", NULL, WHITE, &global->temp_screenWidth, NULL, "%4.0f", 800, 1600, 200, 800, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Výška obrazovky", NULL, WHITE, &global->temp_screenHeight, NULL, "%4.0f", 600, 1200, 200, 600, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Ukazovateľ myši", NULL, WHITE, &global->os_mouse, NULL, "%s", 0, 1, 1, 1, mouseText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 1, global->halfHeight + 72},
- { "Rýchlosť hry", NULL, WHITE, &global->frames_per_second, NULL, "%3.0f", 30, 120, 5, 60, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 92},
- { "Vlastné pozadie", NULL, WHITE, &env->custom_background, NULL, "%s", 0, 1, 1, 0, onOffText_de, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112}
- };
- MENUDESC graphicsMenu = { "Grafika", 12, graphicsOpts, TRUE, FALSE};
-
- static MENUENTRY financeOpts[9] =
- {
- { "Peniaze na začiatku", NULL, WHITE, (double*)&global->startmoney, NULL, "%2.0f", 0, 200000, 5000, 20000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Úroková miera", NULL, WHITE, (double*)&global->interest, NULL, "%2.2f", 1.0, 1.5, 0.05, 1.25, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Bonus pri skončení kola", NULL, WHITE, (double*)&global->scoreRoundWinBonus, NULL, "%2.0f", 0, 50000, 5000, 10000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Odmena za poškodenie", NULL, WHITE, (double*)&global->scoreHitUnit, NULL, "%2.0f", 0, 500, 25, 75, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Pokuta za vlastné poškodenie", NULL, WHITE, (double*)&global->scoreSelfHit, NULL, "%2.0f", 0, 10000, 1000, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Bonus za zničenie tanku", NULL, WHITE, (double*)&global->scoreUnitDestroyBonus, NULL, "%2.0f", 0, 20000, 2500, 5000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Pokuta za vlastné zničenie tanku", NULL, WHITE, (double*)&global->scoreUnitSelfDestroy, NULL, "%2.0f", 0, 20000, 2500, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 72},
- { "Násobiteľ pri predaji položiek", NULL, WHITE, (double*)&global->sellpercent, NULL, "%1.2f", 0.0, 1.0, 0.10, 0.80, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth -3, global->halfHeight + 92},
- { "Teamy zdieľajú peniaze", NULL, WHITE, (double *) &global->divide_money, NULL, "%s", 0.0, 1.0, 1.0, 0.0, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112}
-
- };
- MENUDESC financeMenu = { "Peniaze", 9, financeOpts, TRUE, FALSE};
-
-
- static MENUENTRY networkOpts[3] =
- {
- { "Kontrola aktualizácii", NULL, WHITE, (double*) &global->check_for_updates, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Sieťová hra", NULL, WHITE, (double*) &global->enable_network, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Port pre načúvanie", NULL, WHITE, (double*) &global->listen_port, NULL, "%5.0f", 10645, 64645, 1000, 25645, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8}
- };
- MENUDESC networkMenu = { "Sieť", 3, networkOpts, TRUE, FALSE};
-
-
- void *pPhysicsMenu = &physicsMenu;
- void *pWeatherMenu = &weatherMenu;
- void *pGraphicsMenu = &graphicsMenu;
- void *pFinanceMenu = &financeMenu;
- void *pnetworkMenu = &networkMenu;
- void *pSoundMenu = &soundMenu;
-
-
- static MENUENTRY mainOpts[12] =
- {
- { "Fyzika", NULL, WHITE, (double*)pPhysicsMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 88},
- { "Počasie", NULL, WHITE, (double*)pWeatherMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Grafika", NULL, WHITE, (double*)pGraphicsMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Peniaze", NULL, WHITE, (double*)pFinanceMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Sieť", NULL, WHITE, (double*)pnetworkMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Zvuk", NULL, WHITE, (double*) pSoundMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Tech úroveň zbraní", NULL, WHITE, (double*)&env->weapontechLevel, NULL, "%2.0f", 0, 5, 1, 5, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Tech úroveň vecí", NULL, WHITE, (double *) &env->itemtechLevel, NULL, "%2.0f", 0, 5, 1, 5, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Krajina", NULL, WHITE, (double*)&env->landType, NULL, "%s", 0, 7, 1, LANDTYPE_HILLS, landTypeText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 72},
- { "Poradie", NULL, WHITE, (double*)&global->turntype, NULL, "%s", 0, 3, 1, TURN_RANDOM, turnTypeText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 92},
- { "Preskočiť hru samotného PC", NULL, WHITE, &global->skipComputerPlay, NULL, "%s", 0, 1, 1, 1, skipTypeText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112},
- { "Jazyk", NULL, WHITE, &global->language, NULL, "%s", 0, 7, 1, 0, languageText_sk, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 132}
- };
- mainMenu.title = "Hlavné menu";
- mainMenu.numEntries = 12;
- mainMenu.entries = mainOpts;
- mainMenu.quitButton = TRUE;
- mainMenu.okayButton = FALSE;
-
- } // end of Slovak
-
-
-if (global->language == LANGUAGE_RUSSIAN)
-{
- static MENUENTRY physicsOpts[8] =
- {
- { "Гравитация", NULL, WHITE, &env->gravity, NULL, "%2.3f", .025, .325, 0.025, .075, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 80},
- { "Сила трения", NULL, WHITE, &env->viscosity, NULL, "%2.2f", .25, 2.0, 0.25, 1.0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 60},
- { "Падение земли", NULL, WHITE, &env->landSlideType, NULL, "%s", 0, 4, 1, 3, landSlideText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 40},
- { "Задержка падения земли", NULL, WHITE, &env->landSlideDelay, NULL, "%4.0f", 1, 5, 1, 3, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 20},
- { "Тип стен", NULL, WHITE, &env->wallType, NULL, "%s", 0, 4, 1, 1, wallTypeText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight},
- { "Потолок", NULL, WHITE, &env->dBoxedMode, NULL, "%s", 0, 2, 1, 0, onOffRandomText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 20},
- { "Мощные взрывы танков", NULL, WHITE, &global->violent_death, NULL, "%s", 0, 3, 1, 0, lightningText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 40},
- { "Задержка выстрела", NULL, WHITE, &global->max_fire_time, NULL, "%3.0f", 0, 180, 5, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 60}
- };
- MENUDESC physicsMenu = { "Физика", 7, physicsOpts, TRUE, FALSE};
-
-
- static MENUENTRY weatherOpts[7] =
- {
- { "Метеоритный дождь", NULL, WHITE, &env->meteors, NULL, "%s", 0, 3, 1, 0, meteorText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Молнии", NULL, WHITE, &env->lightning, NULL, "%s", 0, 3, 1, 0, lightningText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Падающая грязь", NULL, WHITE, &env->falling_dirt_balls, NULL, "%s", 0, 3, 1, 0, meteorText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Удары со спутника", NULL, WHITE, &env->satellite, NULL, "%s", 0, 3, 1, 0, laserSatelliteText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Туман", NULL, WHITE, &env->fog, NULL, "%s", 0, 1, 1, 0, onOffText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Макс. сила ветра", NULL, WHITE, (double*)&env->windstrength, NULL, "%2.0f", 0, 100, 5, 40, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Изменения силы ветра", NULL, WHITE, (double*)&env->windvariation, NULL, "%2.1f", 0, 100, 3, 10, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- };
- MENUDESC weatherMenu = { "Погода", 7, weatherOpts, TRUE, FALSE};
-
-
- static MENUENTRY soundOpts[3] =
- {
- { "All Sound", NULL, WHITE, &global->sound, NULL, "%s", 0, 1, 1, 1, onOffText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Sound Driver", NULL, WHITE, &global->sound_driver, NULL, "%s", 0, 5, 1, 0, soundDriver, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Music", NULL, WHITE, &global->play_music, NULL, "%s", 0, 1, 1, 0, onOffText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28}
- };
- MENUDESC soundMenu = { "Sound", 3, soundOpts, TRUE, FALSE};
-
-
- static MENUENTRY graphicsOpts[12] =
- {
- { "Full Screen", NULL, WHITE, &global->full_screen, NULL, "%s", 0, 1, 1, 0, onOffText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 108},
- { "Сглаживание", NULL, WHITE, &global->ditherGradients, NULL, "%s", 0, 1, 1, 1, onOffText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 88},
- { "Детализированный ландшафт", NULL, WHITE, &global->detailedLandscape, NULL, "%s", 0, 1, 1, 1, onOffText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Детализированное небо", NULL, WHITE, &global->detailedSky, NULL, "%s", 0, 1, 1, 1, onOffText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Исчезающий текст", NULL, WHITE, &env->dFadingText, NULL, "%s", 0, 1, 1, 1, onOffText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Оттененный текст", NULL, WHITE, &env->dShadowedText, NULL, "%s", 0, 1, 1, 1, onOffText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Цветовая тема", NULL, WHITE, &global->colour_theme, NULL, "%s", 0, 1, 1, 1, colourText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Ширина окна игры", NULL, WHITE, &global->temp_screenWidth, NULL, "%4.0f", 800, 1600, 200, 800, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Высота окна игры", NULL, WHITE, &global->temp_screenHeight, NULL, "%4.0f", 600, 1200, 200, 600, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Курсор в игре", NULL, WHITE, &global->os_mouse, NULL, "%s", 0, 1, 1, 1, mouseText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 1, global->halfHeight + 72},
- { "Скорость игры", NULL, WHITE, &global->frames_per_second, NULL, "%3.0f", 30, 120, 5, 60, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 92},
- { "Собственный фон", NULL, WHITE, &env->custom_background, NULL, "%s", 0, 1, 1, 0, onOffText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112}
- };
- MENUDESC graphicsMenu = { "Графика", 12, graphicsOpts, TRUE, FALSE};
-
-
- static MENUENTRY financeOpts[9] =
- {
- { "Начальные деньги", NULL, WHITE, (double*)&global->startmoney, NULL, "%2.0f", 0, 200000, 5000, 20000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Банковский процент", NULL, WHITE, (double*)&global->interest, NULL, "%2.2f", 1.0, 1.5, 0.05, 1.25, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Бонус за победу", NULL, WHITE, (double*)&global->scoreRoundWinBonus, NULL, "%2.0f", 0, 50000, 5000, 10000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Бонус за попадание", NULL, WHITE, (double*)&global->scoreHitUnit, NULL, "%2.0f", 0, 500, 25, 75, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Штраф за попадание в себя", NULL, WHITE, (double*)&global->scoreSelfHit, NULL, "%2.0f", 0, 10000, 1000, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Бонус за уничтожение", NULL, WHITE, (double*)&global->scoreUnitDestroyBonus, NULL, "%2.0f", 0, 20000, 2500, 5000, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Штраф за самоуничтожение", NULL, WHITE, (double*)&global->scoreUnitSelfDestroy, NULL, "%2.0f", 0, 20000, 2500, 0, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 72},
- { "Коэфф. продажи снаряжения", NULL, WHITE, (double*)&global->sellpercent, NULL, "%1.2f", 0.0, 1.0, 0.10, 0.80, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth -3, global->halfHeight + 92},
- { "Командные боеприпасы", NULL, WHITE, (double *) &global->divide_money, NULL, "%s", 0.0, 1.0, 1.0, 0.0, onOffText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112}
- };
- MENUDESC financeMenu = { "Экономика", 9, financeOpts, TRUE, FALSE};
-
-
- static MENUENTRY networkOpts[3] =
- {
- { "Проверять обновления", NULL, WHITE, (double*) &global->check_for_updates, NULL, "%s", 0, 1, 1, 1, onOffText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Networking", NULL, WHITE, (double*) &global->enable_network, NULL, "%s", 0, 1, 1, 1, onOffText, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Listen Port", NULL, WHITE, (double*) &global->listen_port, NULL, "%5.0f", 10645, 64645, 1000, 25645, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8}
- };
- MENUDESC networkMenu = { "Настройки сети", 3, networkOpts, TRUE, FALSE};
-
- void *pPhysicsMenu = &physicsMenu;
- void *pWeatherMenu = &weatherMenu;
- void *pGraphicsMenu = &graphicsMenu;
- void *pFinanceMenu = &financeMenu;
- void *pnetworkMenu = &networkMenu;
- void *pSoundMenu = &soundMenu;
-
-
- static MENUENTRY mainOpts[12] =
- {
- { "Физика", NULL, WHITE, (double*)pPhysicsMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 88},
- { "Погода", NULL, WHITE, (double*)pWeatherMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 68},
- { "Графика", NULL, WHITE, (double*)pGraphicsMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 48},
- { "Экономика", NULL, WHITE, (double*)pFinanceMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 28},
- { "Настройки сети", NULL, WHITE, (double*)pnetworkMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight - 8},
- { "Звук", NULL, WHITE, (double*)pSoundMenu, NULL, NULL, 0, 0, 0, 0, NULL, OPTION_MENUTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 12},
- { "Уровень оружия", NULL, WHITE, (double*)&env->weapontechLevel, NULL, "%2.0f", 0, 5, 1, 5, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 32},
- { "Уровень снаряжения", NULL, WHITE, (double *) &env->itemtechLevel, NULL, "%2.0f", 0, 5, 1, 5, NULL, OPTION_DOUBLETYPE, FALSE, global->halfWidth - 3, global->halfHeight + 52},
- { "Тип ландшафта", NULL, WHITE, (double*)&env->landType, NULL, "%s", 0, 7, 1, LANDTYPE_HILLS, landTypeText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 72},
- { "Порядок хода", NULL, WHITE, (double*)&global->turntype, NULL, "%s", 0, 3, 1, TURN_RANDOM, turnTypeText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 92},
- { "Пропускать игру компьютеров", NULL, WHITE, &global->skipComputerPlay, NULL, "%s", 0, 1, 1, 1, skipTypeText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 112},
- { "Язык (Language)", NULL, WHITE, &global->language, NULL, "%s", 0, 7, 1, 0, languageText_ru, OPTION_SPECIALTYPE, FALSE, global->halfWidth - 3, global->halfHeight + 132}
- };
-// mainMenu = { "Main Menu", 10, mainOpts, TRUE, FALSE};
- mainMenu.title = "Главное меню";
- mainMenu.numEntries = 12;
- mainMenu.entries = mainOpts;
- mainMenu.quitButton = TRUE;
- mainMenu.okayButton = FALSE;
-
-} // end of Russian
-
-
-
-#endif
-
diff --git a/src/missile.cpp b/src/missile.cpp
index 330dc7e..670619d 100644
--- a/src/missile.cpp
+++ b/src/missile.cpp
@@ -18,782 +18,1141 @@
* */
-#include "environment.h"
-#include "globaldata.h"
#include "explosion.h"
#include "missile.h"
#include "decor.h"
#include "tank.h"
+#include "player.h"
+#include "beam.h"
+#include "sound.h"
+#include "aicore.h"
+
+
+/* Note: If you wonder why the MISSILE ctor needs the AI_LEVEL, it is used
+ * for two things:
+ * 1. Whether repulsion is considered for mind shots depends on the
+ * ai_level of the bot tracking the missile, and
+ * 2. the SDI check must make sure to not re-test its own mind shots.
+ */
+MISSILE::MISSILE (PLAYER* player_, double xpos, double ypos,
+ double xvel, double yvel, int32_t weapon_type,
+ eMissileType missile_type, int32_t ai_level_) :
+ PHYSICAL_OBJECT(MT_WEAPON == missile_type),
+ ai_level(ai_level_),
+ missileType(missile_type)
+{
+ this->player = player_;
+
+#ifdef NETWORK
+ char buffer[256];
+ sprintf(buffer, "MISSILE %d %d %lf %lf %d",
+ ROUND(xpos), ROUND(ypos),
+ xvel, yvel, weapon_type);
+ env.sendToClients(buffer);
+#endif
+
+ // Set position and movement
+ x = xpos;
+ y = ypos;
+ xv = xvel;
+ yv = yvel;
+
+ // Get and set weapon/item data
+ weapType = weapon_type;
+ if (weapType < WEAPONS)
+ weap = &weapon[weapType];
+ else
+ weap = &naturals[weapType - WEAPONS];
+ setBitmap(env.missile[weap->picpoint]);
+ drag = weap->drag;
+ mass = weap->mass;
+ noimpact = weap->noimpact;
+
+ // The maxVel value results in a small missile being able to be accelerated
+ // by 25% over MAX_POWER, while a large Napalm Bomb can go up to 220%.
+ maxVel = env.maxVelocity * (1.20 + (mass / (.01 * MAX_POWER)));
+ DEBUG_LOG_PHY("PHYSICAL_OBJECT",
+ "env.maxVel: %5.2lf, mass: %5.2lf, obj.maxVel: %5.2lf",
+ env.maxVelocity, mass, maxVel)
+
+ // Meteors and dirt balls are "volatile" and can not be accelerated
+ // over MAX_POWER. (Pre-caution against "forever" going naturals)
+ if ( ( (SML_METEOR <= weapType) && (LRG_METEOR >= weapType) )
+ || ( (DIRT_BALL <= weapType) && (SUP_DIRT_BALL >= weapType) ) )
+ maxVel = std::min(maxVel, static_cast<double>(MAX_POWER));
+
+ if ( (SML_METEOR <= weapType) && (LRG_METEOR >= weapType) ) {
+ angle = rand () % 360;
+ spin = (rand () % 20) - 10;
+ maxAge = MAX_METEOR_AGE;
+ } else if (weapType == NAPALM_JELLY) {
+ // Napalm grows, others do not:
+ isGrowing = true;
+ growRadius = 1;
+ maxAge = MAX_JELLY_AGE;
+ allowDirtyWrap = false;
+ } else {
+ growRadius = weap->radius;
+ if (FUNKY_BOMBLET == weapType)
+ maxAge = (MAX_MISSILE_AGE / 7) + (rand() % (MAX_MISSILE_AGE / 4));
+ // With MMA 15 seconds, this is 2 + [0;3] = [2;5] seconds
+ else if (FUNKY_DEATHLET == weapType)
+ maxAge = (MAX_MISSILE_AGE / 5) + (rand() % (MAX_MISSILE_AGE / 3));
+ // With MMA 15 seconds, this is 3 + [0;4] = [3;7] seconds
+ else
+ maxAge = MAX_MISSILE_AGE;
+ }
+
+ // Finalize maxAge to be for frames, not seconds:
+ maxAge *= env.frames_per_second;
+
+ // Set funky colour of the funky bomblets/deathlets and add some maxAge
+ // variation so they do not detonate in groups.
+ if ( (weapType == FUNKY_BOMBLET) || (weapType == FUNKY_DEATHLET) ) {
+ int32_t temp_number = rand() % 5;
+ switch (temp_number)
+ {
+ case 0: funky_colour = makecol(200, 0, 0); break;
+ case 1: funky_colour = makecol( 0, 200, 0); break;
+ case 2: funky_colour = makecol( 0, 0, 200); break;
+ case 3: funky_colour = makecol(200, 200, 0); break;
+ case 4: funky_colour = makecol(200, 0, 200); break;
+ }
+
+ // Variation +/- 1 Second in frames:
+ maxAge += (rand() % (2 * env.frames_per_second)) - env.frames_per_second;
+ }
+
+ // Some weapons must not wrap through dirt ceilings if the bottom
+ // pixel they would warp into is occupied:
+ if ( ( (SML_ROLLER <= weapType) && (SMALL_MIRV >= weapType) )
+ || ( (CLUSTER <= weapType) && (SUP_CLUSTER >= weapType) )
+ || ( (SML_NAPALM <= weapType) && (LRG_NAPALM >= weapType) )
+ || (CLUSTER_MIRV == weapType) )
+ allowDirtyWrap = false;
+
+ // Add to the chain:
+ // Do not put mind shots in there or the object updaters
+ // will not only try to apply physics, but use delete on
+ // them when they get destroyed.
+ if (MT_MIND_SHOT != missileType)
+ global.addObject(this);
+}
MISSILE::~MISSILE ()
{
- _env->removeObject (this);
- _global = NULL;
- _env = NULL;
- weap = NULL;
+ // Take out of the chain:
+ if (MT_MIND_SHOT != missileType)
+ global.removeObject(this);
}
-MISSILE::MISSILE (GLOBALDATA *global, ENVIRONMENT *env, double xpos, double ypos,
- double xvel, double yvel,
- int weaponType):PHYSICAL_OBJECT(),weap(NULL)
-{
- setEnvironment (env);
- player = NULL;
- _align = LEFT;
- _global = global;
- #ifdef NETWORK
- char buffer[256];
- sprintf(buffer, "MISSILE %d %d %lf %lf %d", (int)xpos, (int)ypos,
- xvel, yvel, weaponType);
- global->Send_To_Clients(buffer);
- #endif
- x = xpos;
- y = ypos;
- xv = xvel;
- yv = yvel;
- type = weaponType;
- if (type < WEAPONS)
- weap = &weapon[type];
- else
- weap = &naturals[type - WEAPONS];
- mass = weap->mass;
- drag = weap->drag;
- sound = weap->sound;
- expSize = weap->radius;
- etime = weap->etime;
- damage = weap->damage;
- eframes = weap->eframes;
- picpoint = weap->picpoint;
- noimpact = weap->noimpact;
- countdown = -1;
- _bitmap = (BITMAP *)_global->missile[picpoint];
- physics = 0;
- age = 0;
- destroy = FALSE;
- if (type >= SML_METEOR && type <= LRG_METEOR)
- {
- angle = rand () % 360;
- spin = rand () % 20 - 10;
- }
- else
- {
- angle = 0;
- spin = 0;
- }
-
- // Napalm grows, others do not:
- if (type == NAPALM_JELLY)
- {
- bIsGrowing = true;
- iGrowRadius= 1;
- }
- else
- {
- bIsGrowing = false;
- iGrowRadius= expSize;
- }
- if ( (type == FUNKY_BOMBLET) || (type == FUNKY_DEATHLET) )
- {
- int temp_number = rand() % 5;
- switch (temp_number)
- {
- case 0: funky_colour = makecol(200, 0, 0); break;
- case 1: funky_colour = makecol(0, 200, 0); break;
- case 2: funky_colour = makecol(0, 0, 200); break;
- case 3: funky_colour = makecol(200, 200, 0); break;
- case 4: funky_colour = makecol(200, 0, 200); break;
- }
- }
-}
-void MISSILE::initialise ()
+void MISSILE::applyPhysics ()
{
- PHYSICAL_OBJECT::initialise ();
- physics = 0;
- age = 0;
- if (type >= SML_METEOR && type <= LRG_METEOR)
- {
- angle = rand () % 360;
- spin = rand () % 20 - 10;
- }
- else
- {
- angle = 0;
- spin = 0;
- }
+ int32_t above_ground = -1;
+
+ // Increase age and get rid of the missile if it
+ // is caught in some endless loop.
+ if ( (++age > maxAge)
+ || (y < -65535) ) {
+ hitSomething = true;
+ if (MT_MIND_SHOT != missileType)
+ trigger();
+ else
+ destroy = true;
+ return;
+ } else
+ hitSomething = false;
+
+ // Napalm grows first:
+ if (isGrowing) {
+ if (age < maxAge) {
+ growRadius = ROUND(static_cast<double>(weap->radius)
+ * (static_cast<double>(age)
+ / static_cast<double>(maxAge)));
+ if (growRadius < 2)
+ growRadius = 2;
+ } else
+ isGrowing = false; // Finished growing!
+ }
+
+ // Riot charges / blasts go off immediately:
+ if ( (RIOT_CHARGE <= weapType) && (RIOT_BLAST >= weapType) ) {
+ trigger();
+ return;
+ }
+
+
+ // Normal and digging physic types need an angle
+ // and can be repulsed
+ if ( (PT_NORMAL == physType) || (PT_DIGGING == physType)) {
+
+ if ( (SML_METEOR <= weapType) && (LRG_METEOR >= weapType) )
+ angle = (angle + spin) % 360;
+ else
+ angle = ROUND(RAD2DEG(atan(yv / xv)) * 256./360.)
+ - 64 + ( xv < 0 ? 128 : 0);
+
+ if ( (MT_MIND_SHOT != missileType)
+ || (RAND_AI_0P && RAND_AI_0P) )
+ Repulse_Missile();
+ }
+
+ // === 1) Handle standard physics projectiles ===
+ if (PT_NORMAL == physType) {
+ // Standard physics can be applied
+ PHYSICAL_OBJECT::applyPhysics ();
+
+
+ // mirvs trigger above ground
+ if ( !hitSomething
+ && ( (weapType == SMALL_MIRV) || (weapType == CLUSTER_MIRV) )
+ && (yv > 0) ) {
+
+ above_ground = Height_Above_Ground();
+
+ if ( (above_ground > 0)
+ && (above_ground < TRIGGER_HEIGHT) )
+ hitSomething = true;
+ }
+
+
+ // Missiles that get too slow on a rubber floor, trigger when stopped.
+ if ( !hitSomething && (WALL_RUBBER == env.current_wallType)
+ && (ROUND(y) >= (env.screenHeight - 2))
+ && ( (std::abs(xv) + std::abs(yv)) < 0.8) )
+ hitSomething = true;
+
+
+ // Unless something is hit, smoke might be produced:
+ if ( !hitSomething && !global.skippingComputerPlay
+ && (MT_MIND_SHOT != missileType)
+ && !(rand() % (env.frames_per_second / 10)) ) {
+ try {
+ new DECOR (x, y,
+ xv / env.frames_per_second,
+ xv / env.frames_per_second,
+ weap->radius / 20, DECOR_SMOKE, 0);
+ } catch (std::exception) {
+ perror ( "missile.cpp: Failed allocating memory for decor in applyPhysics");
+ }
+ }
+ } // --- End of normal physic types ---
+
+ // === 2) Handle rolling projectiles ===
+ else if (PT_ROLLING == physType) {
+ // check whether anything is hit
+ if ( (x < 2)
+ || (x > (env.screenWidth - 3) )
+ || (y > (env.screenHeight - 3) ) ) {
+ int32_t round_x = ROUND(x);
+ int32_t round_y = ROUND(y);
+ if(PINK != getpixel(global.terrain, round_x, round_y))
+ hitSomething = true;
+ }
+
+ if (!hitSomething) {
+ // roll roller
+ float surfY = global.surface[ROUND(x)].load(ATOMIC_READ) - 1;
+
+ // Check whether terrain is going down
+ if ( (surfY > y) && (y < (env.screenHeight - 5)) ) {
+
+ // Do not fall more than five pixels if terrain gives way.
+ if ( surfY < (y + 5) )
+ y = surfY;
+ else
+ y += 5;
+ } else {
+ // Normal movement:
+ x += xv > 0 ? 1 : xv < 0 ? -1 : 0;
+
+ if (y >= MENUHEIGHT) {
+ // The small roller can climb two, the large three and
+ // the death roller four pixels. Everything else
+ // rolling is limited to one pixel.
+ int32_t maxHeight = y -
+ (SML_ROLLER == weapType ? 2 :
+ LRG_ROLLER == weapType ? 3 :
+ DTH_ROLLER == weapType ? 4 : 1);
+ if (surfY >= maxHeight)
+ y = surfY - 1;
+ }
+ } // End of normal movement
+
+ // Adapt angle according to movement
+ if (xv > 0.0) angle = (angle + 3) % 256;
+ if (xv < 0.0) angle = (angle + 253) % 256;
+
+ // Fix y if the projectile threats to go through the floor
+ if (!hitSomething && (y > (env.screenHeight - 5)) )
+ y = env.screenHeight - 5;
+ } // End of rolling projectile movement
+ } // --- End of rolling physic types ---
+
+ // === 3) Handle funky projectiles ===
+ else if (PT_FUNKY_FLOAT == physType) {
+
+ // Funky Floats have a 0.5% chance to randomly reverse either direction:
+ if (0 == (rand() % 200)) {
+ if (rand() % 2)
+ xv *= -1.;
+ else
+ yv *= -1.;
+ }
+
+ // Funky floats simply bounce on borders.
+ if ( ((x + xv) < 1) || ((x + xv) > (env.screenWidth - 1) ) )
+ xv = -xv;
+ else
+ x += xv;
+
+ // The same applies to the screen bottom,
+ // but here according to floor type
+ if ((y + yv) >= env.screenHeight) {
+ if (WALL_RUBBER == env.current_wallType) {
+ yv *= -BOUNCE_CHANGE;
+ xv *= 0.95;
+ } else if (WALL_SPRING == env.current_wallType) {
+ yv *= -SPRING_CHANGE;
+ xv *= 1.05;
+ } else if ( (WALL_WRAP == env.current_wallType)
+ && env.isBoxed && env.do_box_wrap ) {
+ y = MENUHEIGHT + 1;
+ } else {
+ y = env.screenHeight;
+ yv = 0;
+ hitSomething = true;
+ age = maxAge;
+ }
+ }
+
+ // On the screen top the direction is just reversed, even if
+ // there is a ceiling in boxed mode. However, if it is a wrap
+ // ceiling, and the ceiling wrap is enabled, got to the bottom.
+ else if ( (y + yv) <= MENUHEIGHT) {
+ if ( (WALL_WRAP == env.current_wallType)
+ && env.isBoxed && env.do_box_wrap ) {
+ y = env.screenHeight - 2;
+ } else {
+ yv *= -0.95;
+ xv *= 0.95;
+ }
+ }
+
+ // eventually apply yv
+ y += yv;
+ } // --- End of funky float physic types ---
+
+ // === 4) Handle other projectiles ===
+ else {
+
+ // Check X:
+ if ( ((x + xv) < 1) || ((x + xv) > (env.screenWidth - 1)) ) {
+ if (WALL_RUBBER == env.current_wallType)
+ xv *= -0.5;
+ else if (WALL_SPRING == env.current_wallType)
+ xv *= -SPRING_CHANGE;
+ else if (WALL_WRAP == env.current_wallType)
+ x = xv > 0. ? 1 : env.screenWidth - 1;
+ else {
+ x = xv < 0. ? 1 : env.screenWidth - 1;
+ xv = 0;
+ hitSomething = true;
+ }
+ }
+
+ // Check Y :
+ if ( ((y + yv) >= env.screenHeight) || ((y + yv) < MENUHEIGHT) ) {
+ yv *= -0.5;
+ xv *= 0.95;
+ }
+
+ // Apply gravitation, digging types are reversed and scorch the ground:
+ if (PT_DIGGING == physType)
+ yv -= env.gravity * 0.05 * env.FPS_mod;
+ else
+ yv += env.gravity * 0.05 * env.FPS_mod;
+
+ // Apply velocity
+ y += yv;
+ x += xv;
+
+ } // End of checking 'others'
+
+ // Final check against the terrain
+ if (!hitSomething && (y > MENUHEIGHT)
+ && (y < (env.screenHeight - 1)) ) {
+ int32_t round_x = ROUND(x);
+ int32_t round_y = ROUND(y);
+ int32_t hitpix = getpixel(global.terrain, round_x, round_y);
+ if ( ( (PT_DIGGING == physType) && (PINK == hitpix) )
+ || ( (PT_DIGGING != physType) && (PINK != hitpix) ) )
+ hitSomething = true;
+ }
+
+ // No "ceiling drops" are triggered in boxed mode
+ if (!hitSomething && (y <= MENUHEIGHT) && env.isBoxed) {
+ yv = 0;
+ hitSomething = true;
+ }
+
+ // Be sure any trigger/detonation is on screen ...
+ if (hitSomething && (y <= MENUHEIGHT))
+ y = MENUHEIGHT + 1;
+
+ triggerTest();
}
-
-int MISSILE::triggerTest ()
+/// @return The number of bounces done since the missile was fired
+int32_t MISSILE::bounced() const
{
- int quell = 1;
- TANK *tank;
- double old_delta_x = xv;
-
- // Has it hit a tank?
- for (int objCount = 0; (tank = (TANK*)_env->getNextOfClass (TANK_CLASS, &objCount)) && tank; objCount++)
- {
- if (x > tank->x - TANKWIDTH && x < tank->x + TANKWIDTH && y > tank->y && y < tank->y + TANKHEIGHT && tank->l > 0)
- {
- hitSomething = 1;
- tank->requireUpdate ();
- }
- }
- if (noimpact)
- {
- quell = 1;
- }
- else if ((y > 0) && (hitSomething || (y >= _global->screenHeight) || (getpixel (_env->terrain, (int)x, (int)y) != PINK)))
- // else if ( (y > 0) && (hitSomething || (y >= _global->screenHeight) || (_env->surface[(int)x] <= y) ) )
- {
- quell = 0;
- if (type >= SML_ROLLER && type <= DTH_ROLLER && !physics)
- {
- /* rollerupdate */
- if (age > 1)
- {
-
- quell = 1;
- physics = 1;
- y -= 5;
- if (x < 1)
- x = 1;
- if (x > (_global->screenWidth - 2))
- x = (_global->screenWidth - 2);
-
- if ( (y >= _env->surface[(int)x]) // y is surface or below
- && (y <= _env->surface[(int)x] + 2) ) // but not burried more than 2 px
- y = _env->surface[(int)x] - 1;
-
- yv = 0.0;
- // if (xv > 0.0) yv = 1.0;
- // if (xv < 0.0) yv =-1.0;
-
- xv = 0.0;
- if (x == 1)
- xv++;
- else if (x == (_global->screenWidth - 2) )
- xv--;
- else
- {
- int can_go_left = FALSE, can_go_right = FALSE;
- if (getpixel (_env->terrain, (int)x + 1, (int)y + 1) == PINK)
- can_go_right = TRUE;
- if (getpixel (_env->terrain, (int)x - 1, (int)y + 1) == PINK)
- can_go_left = TRUE;
-
- if ( (can_go_left) && (!can_go_right) )
- {
- xv--;
- }
- else if ( (! can_go_left) && (can_go_right) )
- {
- xv++;
- }
- else if ( (can_go_left) && (can_go_right) )
- {
- xv = old_delta_x; // if both are open, continue on old course
- }
- }
- if (xv == 0.0) // nothing worked, try something!
- {
- xv = yv;
- }
- yv = 0.0;
- hitSomething = 0;
- }
- else
- {
- physics = 4;
- }
- }
- else if (type == BURROWER || type == PENETRATOR)
- {
- if (physics == 1)
- {
- if (!hitSomething)
- quell = 1;
- }
- if (!physics)
- {
- physics = 1;
- xv *= 0.1;
- yv *= 0.1;
- quell = 1;
- }
-
- }
- // our weapon has sub weapons
- else if (weap->submunition >= 0)
- {
- WEAPON *submunition = &weapon[weap->submunition];
-
- quell = 1;
- if (weap->numSubmunitions > 0)
- {
- double divergenceStep = weap->divergence / (weap->numSubmunitions - 1);
- int startPoint;
- int randStart = rand () % 1000000;
- int submunitionPhys = 0;
- double inheritedXV = weap->impartVelocity * xv;
- double inheritedYV = weap->impartVelocity * yv;
-
- if (type == FUNKY_BOMB || type == FUNKY_DEATH)
- submunitionPhys = 1;
-
- if (divergenceStep < 0)
- {
- startPoint = 0;
- }
- else
- {
- startPoint = 180;
- }
- for (int spreadCount = 0; spreadCount < weap->numSubmunitions; spreadCount++)
- {
- MISSILE *newmis;
- double launchSpeed;
- int newmisCount;
- int dAngle;
-
- dAngle = (int) (startPoint - (weap->divergence / 2) + (divergenceStep * spreadCount));
- if (weap->spreadVariation > 0)
- {
- double variation = Noise (randStart + 1054 + spreadCount) * weap->spreadVariation;
- dAngle += (int)(weap->divergence * variation);
- }
- while (dAngle < 0)
- dAngle += 360;
- while (dAngle >= 360)
- dAngle -= 360;
-
- newmisCount = submunition->countdown;
- if (submunition->countVariation > 0)
- {
- double variation = Noise (randStart + 78689 + spreadCount) * submunition->countVariation;
- newmisCount += (int)(submunition->countdown * variation);
- if (newmisCount <= 0)
- newmisCount = 1;
- }
-
- launchSpeed = weap->launchSpeed;
- if (weap->speedVariation > 0)
- {
- double variation = Noise (randStart + 124786 + spreadCount) * weap->speedVariation;
- launchSpeed += weap->launchSpeed * variation;
- }
-
- newmis = new MISSILE (_global, _env, x, y - 20, _global->slope[dAngle][0] * launchSpeed * (100.0 / _global->frames_per_second) + inheritedXV, _global->slope[dAngle][1] * launchSpeed * (100.0 / _global->frames_per_second) + inheritedYV, weap->submunition);
-
- if (newmis)
- {
-
- newmis->physics = submunitionPhys;
- newmis->player = player;
- newmis->countdown = newmisCount;
- newmis->noimpact = weapon[weap->submunition].noimpact;
- newmis->setUpdateArea
- ((int) newmis->x - 20,
- (int) newmis->y - 20, 40, 40);
- }
- else
- perror ( "missile.cc: Failed allocating memory for newmis in MISSILE::triggerTest (CLUSTER)");
- }
- }
- destroy = TRUE;
- }
- }
-
- if (countdown >= 0 && age >= countdown)
- {
- quell = 0;
- }
- if (type >= RIOT_CHARGE && type <= RIOT_BLAST)
- {
- quell = 0;
- destroy = TRUE;
- }
- if (!quell)
- {
- _env->realm--;
- trigger ();
- }
-
-
- return (!quell);
+ return bounces;
}
-int MISSILE::applyPhysics ()
+
+/// @return -1 if the missile flies to the left, 1 if it flies to the right
+/// or does not have any vertical movement
+int32_t MISSILE::direction() const
{
- TANK *ltank;
- int detonate = FALSE;
- int max_age;
- int above_ground;
-
- age++;
-
- // meteors live less time than regular missles
- if ( (type >= SML_METEOR) && (type <= LRG_METEOR) )
- max_age = MAX_METEOR_AGE;
- else
- max_age = MAX_MISSLE_AGE;
-
- // Napalm grows first:
- if (bIsGrowing)
- {
- if (age < max_age)
- {
- iGrowRadius = (int)round((double)expSize * ((double)age / (double)max_age));
- if (iGrowRadius < 2)
- iGrowRadius = 2;
- }
- else
- bIsGrowing = false; // Finished growing!
- }
-
- if ( (age > max_age * _global->frames_per_second) || (y < -65535) )
- detonate = 1;
- if (!physics)
- {
- if (type >= SML_METEOR && type <= LRG_METEOR)
- {
- angle += spin;
- angle = angle % 360;
- }
- else
- {
- angle = (int) (atan (yv / xv) * (180 / PI) * ACHANGE) - 64 + ((xv < 0) ? 128 : 0);
- }
- for (int objCount = 0; (ltank = (TANK*)_env->getNextOfClass (TANK_CLASS, &objCount)) && ltank; objCount++)
- {
- if (ltank->player != player)
- {
- double xaccel = 0, yaccel = 0;
- ltank->repulse (x + xv, y + yv, &xaccel, &yaccel, type);
- xv += xaccel;
- yv += yaccel;
- }
- }
- if ( PHYSICAL_OBJECT::applyPhysics () )
- {
- detonate = 1;
- }
- if (! detonate)
- detonate = checkPixelsBetweenPrevAndNow ();
-
- // mirvs trigger above ground
- if ( (! detonate) && ( (type == SMALL_MIRV) || (type == CLUSTER_MIRV) ) )
- {
- above_ground = Height_Above_Ground();
- if ( (above_ground > 0) && (above_ground < TIGGER_HEIGHT) &&
- (yv > 0) )
- detonate = 1;
- }
-
- // if (! detonate &&
- if ( (type == BURROWER || type == PENETRATOR) )
- {
- hitSomething = 0;
- if ( ( y >= _global->screenHeight) &&
- ( (_env->current_wallType == WALL_STEEL) || (_env->current_wallType == WALL_WRAP) ) )
- {
- detonate = TRUE;
- hitSomething = 1;
- y = _global->screenHeight;
- yv = 0;
- }
- }
- if (rand () % 5 == 0)
- {
- DECOR *decor;
- decor = new DECOR (_global, _env, x, y, xv, yv, expSize / 20, DECOR_SMOKE);
- if (!decor)
- {
- perror ( "missile.cc: Failed allocating memory for decor in MISSILE::applyPhysics (METEOR)");
- // exit (1);
- }
- }
- }
- else
- {
- if (type >= SML_ROLLER && type <= DTH_ROLLER)
- {
- /* rollerupdate */
- // check whether we have hit anything
- if ( (x < 1)
- ||(x > (_global->screenWidth - 2))
- ||(y > (_global->screenHeight - 2))
- ||(getpixel(_env->terrain, (int)x, (int)y) != PINK))
- detonate = 1;
- else
- {
- // roll roller
- float fSurfY = (float)_env->surface[(int)x] - 1;
- if ((fSurfY > y) && (y < (_global->screenHeight - 5)))
- {
- if (fSurfY < (y + 5))
- y = fSurfY;
- else
- y+=5;
- if (xv > 0.0) angle += 3;
- if (xv < 0.0) angle -= 3;
- if (angle < 0) angle += 256;
- if (angle > 255) angle -= 256;
- }
- else
- {
- if (xv > 0.0)
- {
- angle += 3;
- x++;
- }
- else
- {
- angle -= 3;
- x--;
- }
- if (angle < 0)
- angle += 256;
- if (angle > 255)
- angle -= 256;
- if (y >= MENUHEIGHT)
- {
- if (fSurfY > y)
- y++;
- else if (fSurfY >= (y - 2))
- y = fSurfY - 1;
- }
- }
- if (y > (_global->screenHeight - 5) && !detonate)
- y = (_global->screenHeight - 5);
- }
- }
- else if (type == FUNKY_BOMBLET || type == FUNKY_DEATHLET)
- {
- //double accel = (_env->wind - xv) / mass * drag;
- if (x + xv < 1 || x + xv > (_global->screenWidth-1))
- {
- xv = -xv; //bounce on the border
- }
- else
- {
- x += xv;
- }
-
- // hit screen bottom then change the y velocity direction
- if (y + yv >= _global->screenHeight)
- {
- // only bounce if the wall is rubber
- if ( _env->current_wallType == WALL_RUBBER )
- {
- yv = -yv * 0.5;
- xv *= 0.95;
- }
- // bounce back with more force
- else if ( _env->current_wallType == WALL_SPRING )
- {
- yv = -yv * SPRING_CHANGE;
- xv *= 1.05;
- }
- // steel or wrap floor, detonate
- else
- {
- y = _global->screenHeight;
- yv = 0;
- detonate = TRUE;
- }
- }
- else if (y+yv < 0) //hit screen top
- {
- yv = -yv * 0.5;
- xv *= 0.95;
- }
- y += yv;
- }
- else if (type == BURROWER || type == PENETRATOR)
- {
- angle = (int) (atan (yv / xv) * (180 / PI) * ACHANGE) - 64 + ((xv < 0) ? 128 : 0);
- for (int objCount = 0; (ltank = (TANK*)_env->getNextOfClass (TANK_CLASS, &objCount)) && ltank; objCount++)
- {
- if (ltank->player != player)
- {
- double xaccel = 0, yaccel = 0;
- ltank->repulse (x + xv, y + yv, &xaccel, &yaccel, type);
- xv += xaccel * 0.1;
- yv += yaccel * 0.1;
- }
- }
- if (x + xv < 1 || x + xv > (_global->screenWidth-1))
- {
- // if the wall is rubber, then bouce
- if ( _env->current_wallType == WALL_RUBBER )
- xv = -xv; //bounce on the border
- // bounce with more force
- else if ( _env->current_wallType == WALL_SPRING )
- xv = -xv * SPRING_CHANGE;
- // wall is steel, detonate
- else if ( _env->current_wallType == WALL_STEEL )
- detonate = TRUE;
- // wrap around to other side of the screen
- else if ( _env->current_wallType == WALL_WRAP )
- {
- if (xv < 0)
- x = _global->screenWidth - 1;
- else
- x = 1;
- }
- }
- if (y + yv >= _global->screenHeight)
- {
- yv = -yv * 0.5;
- xv *= 0.95;
- }
- else if (y+yv < 0) //hit screen top
- {
- yv = -yv *0.5;
- xv *= 0.95;
- }
- y += yv;
- x += xv;
- yv -= _env->gravity * 0.05 * (100.0 / _global->frames_per_second);
- if (getpixel (_env->terrain, (int)x, (int)y) == PINK)
- {
- detonate = TRUE;
- }
- }
- }
-
- if (detonate)
- {
- hitSomething = 1;
- if (y <= MENUHEIGHT)
- y = MENUHEIGHT + 1;
- }
- else if ((y <= MENUHEIGHT) && _global->bIsBoxed)
- {
- detonate = 1;
- hitSomething = 1; // Sorry, no more ceiling-drops for rollers!
- }
-
- return (detonate);
+ return SIGN(xv);
}
-void MISSILE::trigger ()
+void MISSILE::draw ()
{
- EXPLOSION *explosion;
-
- if (type >= TREMOR && type <= TECTONIC)
- explosion = new EXPLOSION (_global, _env, x, y, xv, yv, type);
- else if (type >= RIOT_CHARGE && type <= RIOT_BLAST)
- explosion = new EXPLOSION (_global, _env, x, y, xv, yv, type);
- else
- explosion = new EXPLOSION (_global, _env, x, y, type);
-
- if (!explosion)
- {
- perror ( "missile.cc: Failed allocating memory for explosion in MISSILE::trigger");
- // exit (1);
- }
- else
- explosion->player = player;
-
-
-
- if ((_env->current_wallType == WALL_WRAP) && (type < WEAPONS))
- {
- EXPLOSION *cSecondExplo = NULL;
- if (x < weapon[type].radius)
- {
- if ( (type >= TREMOR && type <= TECTONIC)
- ||(type >= RIOT_CHARGE && type <= RIOT_BLAST) )
- cSecondExplo = new EXPLOSION (_global, _env, _global->screenWidth + x, y, xv, yv, type);
- else
- cSecondExplo = new EXPLOSION (_global, _env, _global->screenWidth + x, y, type);
- if (!cSecondExplo)
- {
- perror ( "missile.cc: Failed allocating memory for cSecondExplo in MISSILE::trigger");
- // exit (1);
- }
- }
- if (x > (_global->screenWidth - weapon[type].radius))
- {
- if ( (type >= TREMOR && type <= TECTONIC)
- ||(type >= RIOT_CHARGE && type <= RIOT_BLAST) )
- cSecondExplo = new EXPLOSION (_global, _env, x - _global->screenWidth, y, xv, yv, type);
- else
- cSecondExplo = new EXPLOSION (_global, _env, x - _global->screenWidth, y, type);
- if (!cSecondExplo)
- {
- perror ( "missile.cc: Failed allocating memory for cSecondExplo in MISSILE::trigger");
- // exit (1);
- }
- }
- if (cSecondExplo)
- cSecondExplo->player = player;
- }
-
-
-
- destroy = TRUE;
-
- if ((type >= DIRT_BALL && type <= SMALL_DIRT_SPREAD) || (type >= RIOT_CHARGE && type <= RIOT_BLAST))
- {
- play_sample ((SAMPLE *) _global->sounds[9], _env->scaleVolume(128 + (expSize / 2)), 128, 1000 - (expSize * 2), 0);
- }
- else if (type == NAPALM_JELLY)
- {
- // TODO?
- //play_sample ((SAMPLE *) _global->SOUND[9].dat, 128 + (expSize / 2), 128, 1000 - (expSize * 2), 0);
- }
- else
- {
- play_sample ((SAMPLE *) _global->sounds[WEAPONSOUNDS + sound], _env->scaleVolume(255), 128, 1000, 0);
- }
+ if (destroy)
+ return;
+
+ // Do not draw mind shots
+ if (MT_MIND_SHOT == missileType)
+ return;
+
+ // draw arrow impostor if it is above the screen
+ if (y < MENUHEIGHT) {
+ // Back up original values
+ BITMAP* bbitmap = getBitmap();
+ double by = y;
+ int32_t bangle = angle;
+
+ // Set arrow values
+ setBitmap(env.misc[3]);
+ y = MENUHEIGHT + (height / 2);
+ angle = 0;
+ VIRTUAL_OBJECT::draw();
+
+ // restore original values:
+ setBitmap(bbitmap);
+ y = by;
+ angle = bangle;
+
+ return;
+ }
+
+ // draw missile on the screen
+
+ // Napalm jellies need a special drawing due to their growing nature.
+ if (weapType == NAPALM_JELLY) {
+ if (isGrowing)
+ draw_Napalm_Blob(this, x, y, growRadius, age / weap->etime);
+ else
+ draw_Napalm_Blob(this, x, y, weap->radius, age / weap->etime);
+
+ } // end of napalm
+
+ // try drawing a funky bomblet
+ else if ( (FUNKY_BOMBLET == weapType)
+ || (FUNKY_DEATHLET == weapType) ) {
+
+ circlefill(global.canvas, x, y, 4, funky_colour );
+ circle (global.canvas, x, y, 5, BLACK );
+ setUpdateArea( x - 10, y - 10, 20, 20);
+ requireUpdate();
+ }
+
+ // draw anything else
+ else {
+
+ // Digging weapons scorch the earth they travel through
+ if (PT_DIGGING == physType) {
+ int32_t scorches = 3 + (3 * ABSDISTANCE2(x, y, x + xv, y + yv));
+
+ for (int32_t i = 0; i < scorches; ++i) {
+ int32_t sx = x + ((rand() % 5) - 2); // [-2;2]
+ int32_t sy = y + ((rand() % 5) - 2); // [-2;2]
+
+ if ( (sx > 1) && (sx < env.screenWidth)
+ && (sy > MENUHEIGHT) && (sy < env.screenHeight) ) {
+ int32_t pc = getpixel(global.terrain, sx, sy);
+ if (PINK != pc) {
+ putpixel(global.terrain, sx, sy, makecol(
+ ROUNDu(static_cast<double>(getr(pc)) * .900),
+ ROUNDu(static_cast<double>(getg(pc)) * .825),
+ ROUNDu(static_cast<double>(getb(pc)) * .866)));
+ }
+ } // end of having valid coordinates
+ } // end of looping scorches
+ } // end of scorching
+
+ VIRTUAL_OBJECT::draw();
+ }
}
-void MISSILE::draw (BITMAP *dest)
-{
- if (!destroy)
- {
- // draw missile if it is above the screen
- if (y < MENUHEIGHT)
- {
- BITMAP *bbitmap = _bitmap;
- double by = y;
- int bangle = angle;
-
- _bitmap = (BITMAP*)_global->misc[3];
- y = (double)MENUHEIGHT + (_bitmap->h / 2);
- angle = 0;
-
- VIRTUAL_OBJECT::draw (dest);
- _bitmap = bbitmap;
- y = by;
- angle = bangle;
- }
-
- // draw missile on the screen
- else
- {
-
- if (type == NAPALM_JELLY)
- {
- if (bIsGrowing)
- {
- circlefill (_env->db, (int)x, (int)y, iGrowRadius - 2, makecol (255, 0, 0));
- circle(_env->db, (int)x, (int)y, iGrowRadius - 1, makecol(255, 150, 0));
- circle(_env->db, (int)x, (int)y, iGrowRadius, makecol(255, 150, 0));
- setUpdateArea ( (int)x - (iGrowRadius + 1),
- (int)y - (iGrowRadius + 1),
- (iGrowRadius + 1) * 2,
- (iGrowRadius + 1) * 2);
- }
- else
- {
- circlefill (_env->db, (int)x, (int)y, expSize - 2, makecol (255, 0, 0));
- circle(_env->db, (int)x, (int)y, expSize - 1, makecol(255, 150, 0));
- circle(_env->db, (int)x, (int)y, expSize, makecol(255, 150, 0));
- setUpdateArea ((int)x - (expSize + 1), (int)y - (expSize + 1), (expSize + 1) * 2, (expSize + 1) * 2);
- }
- requireUpdate ();
- } // end of napalm
-
- // try drawing a funky bomblet
- else if ( ( type == FUNKY_BOMBLET) || (type == FUNKY_DEATHLET) )
- {
- circlefill(_env->db, (int) x, (int) y, 4, funky_colour );
- circle(_env->db, (int) x, (int) y, 5, makecol(0, 0, 0) );
- setUpdateArea( (int) x - 10, (int) y - 10, 20, 20);
- requireUpdate();
- }
- // draw anything else, besides napalm
- else
- {
- VIRTUAL_OBJECT::draw (dest);
- }
- }
- }
-}
+/// === Private methods ===
+///=========================
-void MISSILE::setEnvironment(ENVIRONMENT *env)
- {
- if (!_env || (_env != env))
- {
- _env = env;
- _index = _env->addObject (this);
- }
- }
+/// @brief little helper struct to fire SDI lasers in a fairer way
+struct sSDI
+{
+ int32_t am = 0;
+ double dist = 0.; // Distance used for sorting
+ double lvl = 0.; // AI level, human players are counted as deadly.
+ sSDI* next = nullptr;
+ double range = 100.; // The more SDI, the further the shot
+ TANK* tank = nullptr;
+ double x = 0.;
+ double y = 0.;
+
+};
-int MISSILE::isSubClass (int classNum)
+// Check to see if any tanks have SDI defense. If they
+// do, then see if this missile should be shot down.
+// Returns the shooting tank if a shot is to be fired
+// or NULL if no tank will shoot.
+/* Update:
+ * -------
+ * Check_SDI() can do it all on its own and is now called from
+ * triggerTest() if (and *only* if) the missile isn't going off
+ * anyway.
+ * This allows SDI to be spared on missiles that are exploding anyway.
+ * Further it makes the game loop much simpler.
+ * - Sven
+ *
+ * Update:
+ * -------
+ * If an SDI is really considered to fire, a MIND SHOT is now used to
+ * determine where the missile will explode, or whether it'll miss.
+ * Tests showed, that it will result in a lower average number of
+ * iterations than the old per-pixel check. And as a nice side effect,
+ * the result is many times more accurate. ;-)
+ * - Sven
+*/
+void MISSILE::Check_SDI()
{
- if (classNum == MISSILE_CLASS)
- return (TRUE);
- else
- return (FALSE);
- //return (PHYSICAL_OBJECT::isSubClass (classNum));
+ static sSDI sdi[MAXPLAYERS];
+
+ // The Predictor don't checks itself:
+ if (SDI_PREDICTOR == ai_level)
+ return;
+
+ // can't shoot jelly, dirt balls, digging projectiles
+ // and anything with submunition.
+ if ( (PT_DIGGING == physType)
+ || (weapType == NAPALM_JELLY)
+ || ( (weapType >= DIRT_BALL) && (weapType <= SMALL_DIRT_SPREAD) )
+ || (weap->submunition > 0) )
+ return;
+
+ // Funky Floats are "invisible" with a chance of 50%
+ if ( (PT_FUNKY_FLOAT == physType)
+ && (rand() % 2) )
+ return;
+
+ // Reset SDI list:
+ for (int32_t i = 0; i < MAXPLAYERS; ++i)
+ sdi[i].next = nullptr;
+
+ // Create SDI list
+ TANK* lt = nullptr;
+ bool shotDown = false;
+ sSDI* pSDI = nullptr;
+ int32_t idx = 0;
+
+ global.getHeadOfClass(CLASS_TANK, <);
+ while (lt) {
+ /* A tank is not considered for SDI shots if:
+ * 1 The tank is destroyed (obviously)
+ * 2 The tank is this missiles owners tank
+ * 3 The tank is flying
+ * 4 The tank already has an SDI beam firing
+ * 5 The SDI has no shots left for this sequence
+ * 6 The SDI beam would shoot downwards.
+ */
+ if ( !lt->destroy // 1
+ && (lt->player != player) // 2
+ && !lt->isFlying() // 3
+ && !lt->player->sdi_has_fired.load(ATOMIC_READ) // 4
+ && (lt->player->ni[ITEM_SDI] > lt->player->sdiShots) // 5
+ && ( (lt->y - 10.) >= y) ) { // 6
+ double startX = lt->x;
+ double startY = lt->y - 10.;
+ sdi[idx].am = lt->player->ni[ITEM_SDI] - lt->player->sdiShots;
+ sdi[idx].lvl = static_cast<double>(
+ ( ( lt->player->type == HUMAN_PLAYER )
+ || ( lt->player->type > DEADLY_PLAYER) )
+ ? DEADLY_PLAYER : lt->player->type);
+ sdi[idx].tank = lt;
+ sdi[idx].range = static_cast<double>(SDI_DISTANCE)
+ + ( static_cast<double>(sdi[idx].am - 1)
+ * 2.5);
+ sdi[idx].dist = FABSDISTANCE2(x, y, startX, startY);
+ sdi[idx].x = startX;
+ sdi[idx].y = startY;
+
+ /* Add the SDI to the list if:
+ * 1: The missile is within maximum range
+ * 2: but further away than the minimum distance and
+ * 3: no dirt is between the gun top and the missile.
+ */
+ if ( (sdi[idx].dist <= sdi[idx].range) // 1
+ && (sdi[idx].dist > lt->player->ni[ITEM_SDI]) // 2
+ && !checkPixelsBetweenTwoPoints(&startX, &startY, x, y) /* 3 */ ) {
+ // This can be added!
+ if (pSDI) {
+ // Must be sorted in
+ sSDI* curr = pSDI;
+ while (curr->next && (curr->next->dist < sdi[idx].dist) )
+ curr = curr->next;
+ // Now curr->next is either nullptr or is farther away.
+ sdi[idx].next = curr->next;
+ curr->next = &sdi[idx];
+ } else
+ // It is the new head
+ pSDI = &sdi[idx];
+ ++idx;
+ } // end of in range
+ } // End of having SDI
+ lt->getNext(<);
+ } // End of looping tanks
+
+ // Move through the sorted list of SDI stations and see whether anybody
+ // can shoot this one down.
+ while (!shotDown && pSDI) {
+ // 20% base chance with +1% per SDI over one.
+ if ( (rand() % 100) < (19 + pSDI->am) ) {
+ // Try to predict the coordinates where the missile will go down:
+ MISSILE mind_shot(player, x, y, xv, yv, weapType, MT_MIND_SHOT,
+ SDI_PREDICTOR);
+
+ // Adapt missile drag if the player has dimpled/slick projectiles
+ if (player->ni[ITEM_DIMPLEP])
+ mind_shot.drag *= item[ITEM_DIMPLEP].vals[0];
+ else if (player->ni[ITEM_SLICKP])
+ mind_shot.drag *= item[ITEM_SLICKP].vals[0];
+
+ // Keep flying/rolling/digging/whatever until the missile hits something
+ // or the tank is out of explosion range
+ double x_dist = pSDI->x - mind_shot.x;
+ double y_dist = pSDI->y - mind_shot.y;
+ double x_vel = 0.;
+ double y_vel = 0.;
+ uint32_t max_range = pSDI->lvl
+ * std::max(ROUND(pSDI->range), weap->radius);
+ mind_shot.getVelocity(x_vel, y_vel);
+
+ // Apply physics until the missile is either destroyed, or
+ // it is moving away from the tank and the tank is outside
+ // the blast radius.
+ while (!mind_shot.destroy
+ && ( (SIGN(x_dist) == SIGN(x_vel))
+ || (SIGN(y_dist) == SIGN(y_vel))
+ || ( ABSDISTANCE2(pSDI->x, pSDI->y, mind_shot.x, mind_shot.y)
+ < max_range) ) ) {
+ mind_shot.applyPhysics();
+ x_dist = pSDI->x - mind_shot.x;
+ y_dist = pSDI->y - mind_shot.y;
+ mind_shot.getVelocity(x_vel, y_vel);
+ }
+
+ // If the missile is destroyed, check whether the explosion would
+ // a) hit this tank and be) be nearer than the missile is now.
+ // If so, shoot it down!
+ bool will_hit = false;
+ if (mind_shot.destroy) {
+ int32_t x_rad = DRILLER == weapType
+ ? weap->radius / 20
+ : weap->radius;
+ int32_t y_rad = ( (SHAPED_CHARGE <= weapType)
+ && (CUTTER >= weapType) )
+ ? weap->radius / 20
+ : weap->radius;
+
+ if ( (std::abs(x_dist) <= x_rad) // tank in x range
+ && (std::abs(y_dist) <= y_rad) // tank in y range
+ && ( ( std::abs(x - pSDI->x) > x_rad) // misses x radius now
+ || ( std::abs(y - pSDI->y) > y_rad) // misses y radius now
+ // Is now farther away than when it goes off:
+ || ( ABSDISTANCE2(x, y, pSDI->tank->x, pSDI->tank->y)
+ >= ABSDISTANCE2(x, y, mind_shot.x, mind_shot.y) ) ) )
+ will_hit = true;
+ }
+
+ if (will_hit) {
+ shotDown = true;
+
+ // The actual shooting is only done if this is no mind shot
+ if (MT_MIND_SHOT != missileType) {
+ lt = pSDI->tank;
+
+ // The player has a 1% chance per SDI (with 50% max)
+ // that one of the lasers burns out.
+ // The chance can become this high to prevent players with
+ // few missiles to shoot down to buy hundreds of SDI units.
+ int32_t chance = pSDI->am > 50 ? 50 : pSDI->am;
+ bool burnt = (rand() % 100) < chance ? true : false;
+
+ try {
+ new BEAM(lt->player, pSDI->x, pSDI->y, x, y,
+ pSDI->am > 50 ? LRG_LAZER :
+ pSDI->am > 25 ? MED_LAZER :
+ SML_LAZER, burnt);
+
+ trigger();
+
+ if ( burnt )
+ pSDI->tank->player->ni[ITEM_SDI]--;
+
+ pSDI->tank->player->sdi_has_fired.store(true, ATOMIC_WRITE);
+
+ } catch (...) {
+ // Just not shot down. ;)
+ shotDown = false;
+ }
+ } else
+ // But the bot needs to know that its shot ends here
+ destroy = true;
+ } // end of not missing
+ }
+ pSDI = pSDI->next;
+ } // End of going through SDI list
}
// This function returns the distance above ground of
// the missile.
-int MISSILE::Height_Above_Ground()
+int32_t MISSILE::Height_Above_Ground()
{
- int height = (int)y + 1;
+ int32_t rx = ROUND(x);
+
+ if ( (rx < 1) || (rx >= env.screenWidth) )
+ return -1;
+
+ double sx = xv / yv;
+ double px = x + sx;
+ double py = y + 1.;
+ int32_t height = 1;
+
+ while ( (py < env.screenHeight)
+ && (px > .9)
+ && (px < (env.screenWidth - .9) )
+ && ( (py < BOXED_TOP )
+ || (PINK == getpixel(global.terrain, px, py) ) ) ) {
+ px += sx;
+ py += 1.;
+ ++height;
+
+ // If this is a wrapping wall, px must be wrapped of course
+ if (WALL_WRAP == env.current_wallType) {
+ if (px < 1.)
+ px = env.screenWidth - 1. - (1. - std::abs(px));
+ if (px > (env.screenWidth - 1.) )
+ px = 1 + (env.screenWidth - 1. - px);
+ }
+ }
+
+ return height;
+}
- if (y < 0)
- return height;
- height = _env->surface[(int) x] - y;
- return height;
+// Modify xv/yv according to repulse shields in the vicinity of the missile
+void MISSILE::Repulse_Missile()
+{
+ TANK* lt = nullptr;
+ double xaccel = 0;
+ double yaccel = 0;
+
+ global.getHeadOfClass(CLASS_TANK, <);
+
+ while (lt) {
+ if ( !lt->destroy && (lt->player != player) ) {
+
+ if (lt->repulse (x + xv, y + yv, &xaccel, &yaccel, physType)) {
+ xv += xaccel;
+ yv += yaccel;
+ }
+ }
+ lt->getNext(<);
+ }
}
+void MISSILE::trigger ()
+{
+ // Create explosion
+ try {
+ new EXPLOSION (player, x, y, xv, yv, weapType, isWeaponFire);
+ } catch (...) {
+ perror ( "missile.cpp: Failed allocating memory for explosion in MISSILE::trigger");
+ }
+
+ // If the explosion is near a wrapping wall, a second "fake"
+ // explosion must be generated to display the wrapping effect
+ if ( (WALL_WRAP == env.current_wallType) && (weapType < WEAPONS) ) {
+ int32_t left = 0;
+ int32_t top = 0;
+ int32_t x_rad = weapon[weapType].radius;
+ int32_t y_rad = weapon[weapType].radius;
+
+ // Driller is smaller
+ if (DRILLER == weapType)
+ x_rad /= 20;
+
+ // shaped charges are flatter
+ if ( (SHAPED_CHARGE <= weapType) && (CUTTER >= weapType) )
+ y_rad /= 20;
+
+ // Set wrapped x position
+ if (x < x_rad)
+ left = env.screenWidth + x;
+ else if (x > (env.screenWidth - x_rad))
+ left = x - env.screenWidth;
+
+ // (possibly) set wrapped y position
+ if (env.isBoxed && env.do_box_wrap) {
+ if (y < (y_rad + MENUHEIGHT) )
+ top = env.screenHeight + y;
+ else if (y > (env.screenHeight - y_rad))
+ top = y - env.screenHeight + MENUHEIGHT;
+ }
+
+ // If an x/y-position is found, generate second explosion:
+ if ( left || top ) {
+ int32_t new_x = left ? left : x;
+ int32_t new_y = top ? top : y;
+ try {
+ new EXPLOSION (player, new_x, new_y, xv, yv, weapType, isWeaponFire);
+ } catch (...) {
+ perror ( "missile.cpp: Failed allocating memory for secondary explosion in MISSILE::trigger");
+ }
+ }
+ }
+
+ destroy = true;
+
+ if (weapType < SML_METEOR)
+ play_explosion_sound(weapType, x, 255, 1000);
+ else
+ play_natural_sound(weapType, x, 255, 1000);
+}
-// Check to see if any tanks have SDI defense. If they
-// do, then see if this missile should be shot down.
-// Returns the shooting tank if a shot is to be fired
-// or NULL if no tank will shoot.
-TANK *MISSILE::Check_SDI(GLOBALDATA *global)
+void MISSILE::triggerTest ()
{
- TANK *my_tank;
- int index;
- double distance;
- int chance;
-
- if (type == NAPALM_JELLY) // can't shoot jelly
- return NULL;
-
- for (index = 0; index < global->numPlayers; index++)
- {
- if ( ( global->players[index] ) && ( global->players[index]->tank) )
- {
- my_tank = global->players[index]->tank;
- if ( (my_tank->player->ni[ITEM_SDI]) && (my_tank->player != this->player) )
- {
- distance = pow(x - my_tank->x, 2);
- distance += pow(y - my_tank->y, 2);
- distance = sqrt(distance);
- // only fire if within range and above the tank
- if ( ( distance < SDI_DISTANCE ) && (my_tank->y > y) )
- {
- chance = rand() % 5;
- if (! chance)
- {
- return my_tank;
- }
- } // within range
- } // we have SDI
- } // end of we have a valid player/tank
-
- } // finished going through all players
-
- return NULL;
+ bool quell = noimpact ? true : false;
+
+ // No tests are needed, if a too high velocity has
+ // just lacerated the projectile.
+ if ( lacerated ) {
+ if ( (MT_MIND_SHOT == missileType) || quell)
+ // Only end of performance is needed to know.
+ destroy = true;
+ else
+ trigger();
+ return;
+ }
+
+ TANK* lt = nullptr;
+ double old_delta_x = xv;
+
+ // Has it hit a tank?
+ global.getHeadOfClass(CLASS_TANK, <);
+ while (lt) {
+ if ( !lt->destroy
+ && lt->isInBox(x, y, x, y) ) {
+ hitSomething = true;
+ if (MT_MIND_SHOT != missileType)
+ lt->requireUpdate ();
+
+ // Be sure the detonation does not take place
+ // on the gun top.
+ if (y < lt->y)
+ y = lt->y; // I think we can live with this 'shift'.
+ }
+ lt->getNext(<);
+ }
+
+ // Unless quelled, check what is to be done
+ bool do_check = (!quell && (y > MENUHEIGHT));
+
+ // Only really check if something is hit, the y coordinate is through the
+ // floor or a non-PINK pixel is in the way:
+ if (do_check && !hitSomething && (y < env.screenHeight)) {
+ // neither hit nor floor crunch, check the pixel:
+ int32_t round_x = ROUND(x);
+ int32_t round_y = ROUND(y);
+ if ( (round_x >= 0) && (round_x < env.screenWidth) ) {
+ do_check = (PINK != getpixel(global.terrain, round_x, round_y));
+ }
+ } // End of pixel check
+
+ // Only continue if all checks passed:
+ if (do_check) {
+
+ // A roller changes from flying to rolling
+ if ( (weapType >= SML_ROLLER)
+ && (weapType <= DTH_ROLLER)
+ && (PT_NORMAL == physType) ) {
+
+ if (age > 1) {
+ quell = true; // No detonation, just switch to rolling
+ physType = PT_ROLLING;
+ age = 0;
+
+ // Set rolling start position and initial movement
+ y -= 5;
+ xv = 0;
+ yv = 0;
+
+ // Possibly fix x
+ if (x <= 1) {
+ x = 1;
+ xv = 1;
+ } else if (x >= (env.screenWidth - 2)) {
+ x = env.screenWidth - 2;
+ xv = -1;
+ }
+
+ // Possibly fix y
+ int32_t round_x = ROUND(x);
+ int32_t surf_y = global.surface[round_x].load(ATOMIC_READ);
+ if ( (y >= surf_y) // y is surface or below
+ && (y <= (surf_y + 2) ) ) // but not buried more than 2 px
+ y = surf_y - 1;
+
+ // Set movement if not done already:
+ if ( (xv > -0.9) && (xv < 0.9) ) {
+ bool can_go_left = (round_x > 3);
+ bool can_go_right = (round_x < (env.screenWidth - 4));
+
+ if (can_go_left || can_go_right) {
+ if (can_go_left)
+ can_go_left = (PINK == getpixel(global.terrain,
+ round_x - 1, y));
+ if (can_go_right)
+ can_go_right = (PINK == getpixel(global.terrain,
+ round_x + 1, y));
+ } // End of checking direction pixels
+
+ if (can_go_left && can_go_right)
+ // Prefer old movement direction
+ xv = SIGN(old_delta_x);
+ else if (can_go_left)
+ xv = -1;
+ else if (can_go_right)
+ xv = 1;
+ else
+ // nothing worked, both paths are blocked.
+ xv = rand() % 2 ? -1 : 1;
+ }
+
+ // If the roller is hammered into a wall, detonate it
+ if ( ( (WALL_STEEL == env.current_wallType)
+ && ( (x <= 2) || (x >= (env.screenWidth - 3) ) ) )
+ || (env.isBoxed
+ && (y <= MENUHEIGHT)
+ && ( ( (WALL_WRAP == env.current_wallType)
+ && ( !env.do_box_wrap
+ || ( global.surface[ROUND(x)].load(ATOMIC_READ)
+ < env.screenHeight) ) )
+ || (WALL_STEEL == env.current_wallType) ) ) ) {
+ quell = false;
+ hitSomething = true;
+ }
+ } // End of switching roller physics
+ } // End of roller handling
+
+ // A burrower or penetrator changes from flying to digging
+ else if ( (weapType == BURROWER) || (weapType == PENETRATOR) ) {
+ if (PT_DIGGING == physType)
+ // If it hit, it must not be quelled.
+ quell = !hitSomething;
+ else if (PT_NORMAL == physType) {
+ // This is the switch
+ physType = PT_DIGGING;
+ quell = true;
+ xv *= 0.1;
+ yv *= 0.1;
+ age = 0;
+ }
+ } // End of burrower handling
+
+ // If a weapon has submunition, it goes off now
+ else if (weap->submunition >= 0) {
+ quell = true; // This one is done
+
+ if ( (weap->numSubmunitions > 0) && (MT_MIND_SHOT != missileType) ) {
+ WEAPON* submunition = &weapon[weap->submunition];
+ double divergenceStep = static_cast<double>(weap->divergence)
+ / static_cast<double>(weap->numSubmunitions - 1);
+ int32_t startPoint = divergenceStep < 0. ? 0 : 180;
+ int32_t randStart = rand () % 1000000;
+ ePhysType submunitionPhys = PT_NORMAL;
+ double inheritedXV = weap->impartVelocity * xv;
+ double inheritedYV = weap->impartVelocity * yv;
+ int32_t startY = y - 20;
+ bool ceiling_crash = false;
+
+ // See whether the weapon was fired into a ceiling:
+ // This applies for both steel ceilings and wrap ceilings,
+ // but the latter only if no ceiling wrap is activated or
+ // if the next pixel at the bottom is dirt.
+ if (env.isBoxed && (startY <= MENUHEIGHT) // Base condition
+ && ( ( (WALL_WRAP == env.current_wallType)
+ && (!env.do_box_wrap // <- No wrap makes it steel
+ // \/ dirt makes the ceiling unwrapable
+ || ( global.surface[ROUND(x)].load(ATOMIC_READ)
+ < env.screenHeight) ) )
+ || ( WALL_STEEL == env.current_wallType) ) ) { // This always blasts
+ ceiling_crash = true;
+ // If the weapon is fired into a ceiling, adapt starting y
+ startY = MENUHEIGHT + 20;
+ }
+
+ // if napalm is going off, play its burn out sound
+ if ( (weapType >= SML_NAPALM) && (weapType <= LRG_NAPALM))
+ play_explosion_sound(weapType, x, 128 + (weap->radius / 2), 1000);
+
+ // Change physics of the submunitions for the funky bombs
+ if ( (weapType == FUNKY_BOMB) || (weapType == FUNKY_DEATH) )
+ submunitionPhys = PT_FUNKY_FLOAT;
+
+ // If this is a steel wall hit, the start point angle needs
+ // to be adapted.
+ if ( (WALL_STEEL == env.current_wallType) && !ceiling_crash) {
+ if ( (CLUSTER <= weapType) && (SUP_CLUSTER >= weapType) ) {
+ if ( x < 2 )
+ startPoint -= weap->divergence + 1 + (rand() % 10);
+ else if ( x > (env.screenWidth - 3) )
+ startPoint += weap->divergence + 1 + (rand() % 10);
+ } else if ( (SML_NAPALM <= weapType)
+ && (LRG_NAPALM >= weapType) ) {
+ if ( x < 2 )
+ startPoint -= 10 + rand() % 21;
+ else if ( x > (env.screenWidth - 3) )
+ startPoint += 10 + rand() % 21;
+ }
+ }
+
+ // If this is a ceiling crash, the start point angle needs
+ // to be erased.
+ if (ceiling_crash) {
+ startPoint = 0;
+ if ( (SMALL_MIRV == weapType)
+ || (CLUSTER_MIRV == weapType) )
+ inheritedYV = std::abs(inheritedYV);
+ }
+
+ // The spread can be created!
+ for (int32_t sc = 0; sc < weap->numSubmunitions; ++sc) {
+ MISSILE *newmis = nullptr;
+ double launchSpeed = weap->launchSpeed;
+ int32_t newMissCount = submunition->countdown;
+ int32_t newMissAngle = ROUND(
+ (divergenceStep * static_cast<double>(sc))
+ + static_cast<double>(startPoint)
+ - (static_cast<double>(weap->divergence) / 2.) );
+
+ // Manipulate angle if applicable
+ if (weap->spreadVariation > 0.)
+ newMissAngle += ROUND(
+ static_cast<double>(weap->divergence)
+ * weap->spreadVariation
+ * Noise(randStart + 1054 + sc) );
+
+ // Be sure the angle is valid
+ while (newMissAngle < 0)
+ newMissAngle += 360;
+ newMissAngle %= 360;
+
+ // Manipulate number of submunition projectiles if applicable
+ if (submunition->countVariation > 0) {
+ newMissCount += ROUND(
+ static_cast<double>(submunition->countdown)
+ * submunition->countVariation
+ * Noise(randStart + 78689 + sc) );
+ // This might go wrong, so be sure it doesn't
+ if (newMissCount <= 0)
+ newMissCount = 0;
+ }
+
+ // Manipulate launching speed if applicable
+ if (weap->speedVariation > 0)
+ launchSpeed += ROUND(
+ weap->speedVariation
+ * weap->launchSpeed
+ * Noise(randStart + 124786 + sc) );
+
+ // Launch new submunition missile
+ try {
+ newmis = new MISSILE (player, x, startY,
+ env.slope[newMissAngle][0]
+ * launchSpeed
+ * env.FPS_mod
+ + inheritedXV,
+ env.slope[newMissAngle][1]
+ * launchSpeed
+ * env.FPS_mod
+ + inheritedYV, weap->submunition,
+ missileType, ai_level);
+ newmis->physType = submunitionPhys;
+ newmis->countdown = newMissCount;
+ newmis->setUpdateArea(newmis->x - 20, newmis->y - 20, 40, 40);
+ } catch (...) {
+ perror ( "missile.cpp: Failed to allocate memory for"
+ "newmis in MISSILE::triggerTest (CLUSTER)");
+ }
+ } // End of looping submunitions
+ } // End of having submunition count
+
+ destroy = true;
+ } // End of having a submunition type defined
+
+ }
+
+ // If a countdown was set and this is old enough, this missile is no
+ // longer quelled, regardless of what this means for this weapon type
+ if ( (countdown >= 0) && (age >= countdown) )
+ quell = false;
+
+ // Riot charges always go of in an instant.
+ if ( (weapType >= RIOT_CHARGE) && (weapType <= RIOT_BLAST) ) {
+ quell = false;
+ destroy = true;
+ }
+
+
+ // Eventually trigger missiles that hit something or are lacerated
+ if (hitSomething && !quell) {
+ if (MT_MIND_SHOT == missileType)
+ destroy = true;
+ else
+ trigger();
+ } else if ( (yv > 0.)
+ && ( (weapType < RIOT_BOMB) || (weapType > SMALL_DIRT_SPREAD) ) )
+ // Otherwise it is time to check whether any tank SDI shoots it down
+ Check_SDI();
}
+/// @brief special method to update private members iof sub munition missiles.
+/// This method is only interesting for AICore tracing clusters.
+void MISSILE::update_submun(ePhysType p_type, int32_t cnt_down)
+{
+ physType = p_type;
+ countdown = cnt_down;
+}
+
diff --git a/src/missile.h b/src/missile.h
index db78512..0c5630d 100644
--- a/src/missile.h
+++ b/src/missile.h
@@ -24,51 +24,85 @@
#include "main.h"
#include "physobj.h"
-#define MAX_MISSLE_AGE 20
-#define MAX_METEOR_AGE 5
+// The ages are *seconds* and transformed to frames in the ctor.
+#define MAX_JELLY_AGE 1
+#define MAX_MISSILE_AGE 15
+#define MAX_METEOR_AGE 5
-#define TIGGER_HEIGHT 300
+#define SDI_DISTANCE 100
+#define TRIGGER_HEIGHT 300
+
+
+/** @enum eMissileType
+ * @brief Determines what kind of weapon is shot
+**/
+enum eMissileType
+{
+ MT_WEAPON = 0, //!< Normal weapon, nothing special
+ MT_ITEM, //!< Not a weapon but an item
+ MT_NATURAL, //!< Fired by natural disaster, like meteors and dirt balls.
+ MT_MIND_SHOT //!< AI thinking.
+};
-#define SDI_DISTANCE 100
class MISSILE: public PHYSICAL_OBJECT
- {
- private:
- // New values for growing napalm jelly:
- int iGrowRadius;
- int bIsGrowing;
-
- public:
- int countdown;
- int expSize;
- int etime;
- int damage;
- int eframes;
- int picpoint;
- int type;
- int sound;
- int funky_colour;
- WEAPON *weap;
-
- virtual ~MISSILE ();
- MISSILE (GLOBALDATA *global, ENVIRONMENT *env, double xpos, double ypos, double xvel, double yvel, int weaponType);
- void draw (BITMAP *dest);
- int triggerTest ();
- void trigger ();
- // void explode ();
- int applyPhysics ();
- void initialise ();
- int isSubClass (int classNum);
- void setEnvironment(ENVIRONMENT *env);
-
- inline virtual int getClass ()
- {
- return (MISSILE_CLASS);
- }
-
- int Height_Above_Ground ();
- TANK *Check_SDI(GLOBALDATA *global); // see if missile should be shot down
- };
+{
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ explicit MISSILE (PLAYER* player_, double xpos, double ypos,
+ double xvel, double yvel,
+ int32_t weapon_type, eMissileType missile_type,
+ int32_t ai_level_);
+ ~MISSILE ();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ void applyPhysics();
+ int32_t bounced () const;
+ int32_t direction () const;
+ void draw ();
+
+ eClasses getClass() { return CLASS_MISSILE; }
+
+ void update_submun(ePhysType p_type, int32_t cnt_down);
+
+
+private:
+
+ /* -----------------------
+ * --- Private methods ---
+ * -----------------------
+ */
+
+ void Check_SDI(); // see if missile should be shot down
+ int32_t Height_Above_Ground();
+ void Repulse_Missile();
+ void trigger ();
+ void triggerTest ();
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ int32_t ai_level = 0; // Level of the AI shooting a mind shot
+ int32_t countdown = -1;
+ int32_t funky_colour = BLACK;
+ int32_t growRadius = 0;
+ bool isGrowing = false;
+ eMissileType missileType = MT_WEAPON;
+ WEAPON* weap = nullptr;
+};
#endif
diff --git a/src/network.cpp b/src/network.cpp
index e3bff5a..a8cdb48 100644
--- a/src/network.cpp
+++ b/src/network.cpp
@@ -1,3 +1,7 @@
+#include "network.h"
+#include "player.h"
+#include "globaldata.h"
+
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -6,16 +10,15 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
+#if defined(ATANKS_IS_BSD)
+# include <arpa/inet.h>
+# include <netinet/in.h>
+#endif // BSD
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
-#include <pthread.h>
#endif
-#include "network.h"
-#include "player.h"
-#include "globaldata.h"
-
// init the object
MESSAGE_QUEUE::MESSAGE_QUEUE()
@@ -45,7 +48,7 @@ bool MESSAGE_QUEUE::Add(char *some_text, int to)
new_message = (MESSAGE *) calloc(1, sizeof(MESSAGE));
if (! new_message)
return false;
- new_message->text = (char *) calloc( strlen(some_text), sizeof(char) );
+ new_message->text = (char *) calloc( strlen(some_text) + 1, sizeof(char) );
if (! new_message->text)
{
free(new_message);
@@ -53,7 +56,7 @@ bool MESSAGE_QUEUE::Add(char *some_text, int to)
}
// fill the message structure
- strcpy(new_message->text, some_text);
+ strncpy(new_message->text, some_text, strlen(some_text));
new_message->to = to;
// next is already cleared by calloc
@@ -103,7 +106,7 @@ MESSAGE *MESSAGE_QUEUE::Peek()
my_message = (MESSAGE *) calloc( 1, sizeof(MESSAGE) );
if (! my_message)
return NULL;
- my_message->text = (char *) calloc( strlen(first_message->text), sizeof(char) );
+ my_message->text = (char *) calloc( strlen(first_message->text) + 1, sizeof(char) );
if (! my_message->text)
{
free(my_message);
@@ -111,9 +114,9 @@ MESSAGE *MESSAGE_QUEUE::Peek()
}
// we have an empty message. Now fill it
- strcpy(my_message->text, first_message->text);
+ strncpy(my_message->text, first_message->text, strlen(first_message->text));
my_message->to = first_message->to;
-
+
return my_message;
}
@@ -149,7 +152,7 @@ MESSAGE *MESSAGE_QUEUE::Read_To(int my_to)
if (! my_message)
return NULL;
- my_message->text = (char *) calloc( strlen(current->text), sizeof(char));
+ my_message->text = (char *) calloc( strlen(current->text) + 1, sizeof(char));
if (! my_message->text)
{
free(my_message);
@@ -157,7 +160,7 @@ MESSAGE *MESSAGE_QUEUE::Read_To(int my_to)
}
my_message->to = current->to;
- strcpy(my_message->text, current->text);
+ strncpy(my_message->text, current->text, strlen(current->text));
if (previous)
previous->next = current->next;
else
@@ -234,16 +237,16 @@ int Setup_Server_Socket(int port)
{
int listensocket;
struct sockaddr_in myaddr;
-
+
listensocket = socket(AF_INET, SOCK_STREAM, 0);
myaddr.sin_port = htons(port);
myaddr.sin_addr.s_addr = INADDR_ANY;
- if (bind(listensocket, (struct sockaddr *) &myaddr, sizeof(myaddr)) < 0)
+ if (bind(listensocket, (struct sockaddr *) &myaddr, sizeof(myaddr)) < 0)
{
printf("Bind failed: %s\n", strerror(errno));
return -1;
}
- if (listen(listensocket, 5))
+ if (listen(listensocket, 5))
{
printf("Listen failed: %s\n", strerror(errno));
return -1;
@@ -431,21 +434,23 @@ int Check_For_Errors(int socket_number)
// This function will probably be called as a separate thread at the
// start of the game. It will set up a listening port, accept
// incoming connections and manage them. That is, they will be
-// passed on to AI players.
+// passed on to AI players.
void *Send_And_Receive(void *all_the_data)
{
SEND_RECEIVE_TYPE *send_receive_data = (SEND_RECEIVE_TYPE *) all_the_data;
int server_socket, new_socket;
int status, counter;
- GLOBALDATA *global = (GLOBALDATA *) send_receive_data->global;
+
bool found;
+ char buffer[7] = { 0 };
+ int32_t towrite, written;
// set up listening socket
server_socket = Setup_Server_Socket(send_receive_data->listening_port);
if (server_socket == -1)
{
printf("Error creating listening socket.\n");
- pthread_exit(NULL);
+ return nullptr;
}
while (! send_receive_data->shut_down)
@@ -459,16 +464,16 @@ void *Send_And_Receive(void *all_the_data)
// give connection to AI player
found = false;
counter = 0;
- while ( (! found) && (counter < global->numPlayers) )
+ while ( (! found) && (counter < env.numGamePlayers) )
{
- if ( ( global->players[counter]->type >= USELESS_PLAYER) &&
- ( global->players[counter]->type <= DEADLY_PLAYER) )
+ if ( ( env.players[counter]->type >= USELESS_PLAYER) &&
+ ( env.players[counter]->type <= DEADLY_PLAYER) )
{
found = true;
- global->players[counter]->server_socket = new_socket;
- global->players[counter]->previous_type = global->players[counter]->type;
- global->players[counter]->type = NETWORK_CLIENT;
- printf("Assigned connection to %s\n", global->players[counter]->getName() );
+ env.players[counter]->server_socket = new_socket;
+ env.players[counter]->previous_type = env.players[counter]->type;
+ env.players[counter]->type = NETWORK_CLIENT;
+ printf("Assigned connection to %s\n", env.players[counter]->getName() );
}
else
counter++;
@@ -477,30 +482,30 @@ void *Send_And_Receive(void *all_the_data)
if (! found)
{
printf("Unable to assign new connection to player.\n");
- write(new_socket, "NOROOM", strlen("NOROOM") );
+ SAFE_WRITE(new_socket, "%s", "NOROOM")
close(new_socket);
}
}
-
+
// consider resting for a moment?
- usleep(10000);
+ LINUX_SLEEP
}
// clean up everything
printf("Cleaning up networking\n");
Clean_Up_Server_Socket(server_socket);
counter = 0;
- while (counter < global->numPlayers)
+ while (counter < env.numGamePlayers)
{
- if (global->players[counter]->type == NETWORK_CLIENT)
+ if (env.players[counter]->type == NETWORK_CLIENT)
{
- write(global->players[counter]->server_socket, "CLOSE", strlen("CLOSE") );
- close(global->players[counter]->server_socket);
+ SAFE_WRITE(env.players[counter]->server_socket, "%s", "CLOSE")
+ close(env.players[counter]->server_socket);
}
counter++;
}
- pthread_exit(NULL);
+ return nullptr;
}
diff --git a/src/network.h b/src/network.h
index 3fbdb38..7bc5c78 100644
--- a/src/network.h
+++ b/src/network.h
@@ -19,14 +19,14 @@ updated to run on other operating systems.
#define MAX_MESSAGE_LENGTH 256
-typedef struct message_struct
+struct MESSAGE
{
char *text;
int to; // which client does the message go to? May not be used as most will go to everyone
void *next;
-} MESSAGE;
+};
+
-
class MESSAGE_QUEUE
{
@@ -52,23 +52,36 @@ public:
// erase all messages in the queue
void Erase_All();
-
+
};
-typedef struct send_receive_struct
+struct SEND_RECEIVE_TYPE
{
int listening_port;
int shut_down;
- void *global;
-} SEND_RECEIVE_TYPE;
+};
+#define MAX_CLIENTS 10
+#define DEFAULT_NETWORK_PORT 25645
+
#ifdef NETWORK
-#define MAX_CLIENTS 10
-#define DEFAULT_LISTEN_PORT 25645
+
+/// Wrapper for safe socket writes with return value check.
+/// A char buffer named "buffer" must be available to put the message in.
+/// The two size_t values "towrite" and "written" must be at least declared.
+/// All three variables will be overwritten.
+#define SAFE_WRITE(sock_, fmt_, ...) { \
+ sprintf(buffer, fmt_, __VA_ARGS__); \
+ towrite = strlen(buffer); \
+ written = write(sock_, buffer, towrite); \
+ if (written < towrite) \
+ fprintf(stderr, "%s:%d: Warning: Only %d/%d bytes sent to server\n", \
+ __FILE__, __LINE__, written, towrite); \
+}
int Setup_Server_Socket(int port);
@@ -90,6 +103,8 @@ int Check_For_Errors(int socket_number);
void *Send_And_Receive(void *data_we_need);
+#else
+ #define SAFE_WRITE(sock_, fmt_, ...) { }
#endif
diff --git a/src/optioncontent.h b/src/optioncontent.h
new file mode 100644
index 0000000..3068130
--- /dev/null
+++ b/src/optioncontent.h
@@ -0,0 +1,1326 @@
+#pragma once
+#ifndef ATANKS_SRC_OPTIONCONTENT_H_INCLUDED
+#define ATANKS_SRC_OPTIONCONTENT_H_INCLUDED
+
+/*
+ * atanks - obliterate each other with oversize weapons
+ *
+ * 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.
+ *
+ */
+
+#include "optiontypes.h"
+#include "globaltypes.h"
+
+/** @file optioncontent.h
+ *
+ * This file defines static string arrays with the text content of the
+ * player, play and option menu and all sub menus.
+ *
+ * It is not necessary to include this file anywhere else but in menu.cpp.
+ *
+**/
+
+
+// Maximum number of entries including Title and 0x0 termination per menu
+const uint32_t maxEntriesPerMenu = 18;
+
+
+// Maximum text entries per text class including 0x0 termination
+const uint32_t maxEntriesPerClass = 11;
+
+
+/** @brief string array for the menu content
+ *
+ * The ordering, although it looks a bit overwhelming here, is quite simple.
+ * The first index is the menu class, the second is the language.
+ *
+ * With this both translation and adding new content is fairly easy. Just
+ * copy a block (after adding new enum entries at the proper places in
+ * optiontypes.h) and edit to the new content.
+ *
+ * All text arrays end with a zero 0x0 entry. It is therefore not needed to
+ * hard code any menu list sizes.
+ *
+ * As a rule of thumb, the title is the first line, every other texts are
+ * listed with two entries per line. Unless a possible third entry is the
+ * finalizing 0x0 entry, it does not need its own line.
+**/
+const char* const
+MenuTitleText[MC_MENUCLASS_COUNT][EL_LANGUAGE_COUNT][maxEntriesPerMenu] = {
+ {
+ /* -------------------- *
+ * --- AREYOUSURE --- *
+ * -------------------- */
+ {
+ /* === EL_ENGLISH === */
+ "Are you sure?",
+ "Yes", "No", 0x0
+ }, {
+ /* === EL_PORTUGUESE === */
+ /* ===== Needs to be translated ===== */
+ "Are you sure?",
+ "Yes", "No", 0x0
+ }, {
+ /* === EL_FRENCH === */
+ /* ===== Needs to be translated ===== */
+ "Are you sure?",
+ "Yes", "No", 0x0
+ }, {
+ /* === EL_GERMAN === */
+ "Sind Sie sicher?",
+ "Ja", "Nein", 0x0
+ }, {
+ /* === EL_SLOVAK === */
+ /* ===== Needs to be translated ===== */
+ "Are you sure?",
+ "Yes", "No", 0x0
+ }, {
+ /* === EL_RUSSIAN === */
+ /* ===== Needs to be translated ===== */
+ "Are you sure?",
+ "Yes", "No", 0x0
+ }, {
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ "Are you sure?",
+ "Yes", "No", 0x0
+ }, {
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ "Are you sure?",
+ "Yes", "No", 0x0
+ }
+ }, {
+ /* -------------------- *
+ * --- FINANCE --- *
+ * -------------------- */
+ {
+ /* === EL_ENGLISH === */
+ "Money",
+ "Starting Money", "Interest Rate",
+ "Round Win Bonus", "Damage Bounty",
+ "Self-Damage Penalty", "Team-Damage Penalty", "Tank Destruction Bonus",
+ "Tank Self-Destruction Penalty", "Item Sell Multiplier",
+ "Teams Share" , "Back", 0x0
+ }, {
+ /* === EL_PORTUGUESE === */
+ "Dinheiro",
+ "Dinheiro inicial", "Taxa de Juros",
+ "Bônus por Vitória", "Bônus por Estrago",
+ "Penalidade por Auto-Estrago", "Team-Damage Penalty", "Bônus por Tanque Destruído",
+ "Penalidade por Auto-Destruição", "Multiplicador de Item Vendido",
+ "Parte das equipes", "Back", 0x0
+ }, {
+ /* === EL_FRENCH === */
+ "Finances",
+ "Somme de départ", "Taux d'intérêt",
+ "Gains par victoire", "Bonus dommages",
+ "Pénalité auto-dommages", "Team-Damage Penalty", "Bonus destruction tank",
+ "Pénalité autodestruction tank", "Coeff. vente item",
+ "Part d'equipes", "Back", 0x0
+ }, {
+ /* === EL_GERMAN === */
+ "Geld",
+ "Startgeld", "Zinssatz",
+ "Rundenbonus", "Schadensbonus",
+ "Strafe für Selbstschaden", "Strafe für Teamschaden", "Zerstörungsbonus",
+ "Selbstzerstörungsstrafe", "Verkaufsmultiplikator",
+ "Mannschaftanteil", "Zurück", 0x0
+ }, {
+ /* === EL_SLOVAK === */
+ "Peniaze",
+ "Peniaze na začiatku", "Úroková miera",
+ "Bonus pri skončení kola", "Odmena za poškodenie",
+ "Pokuta za vlastné poškodenie", "Team-Damage Penalty", "Bonus za zničenie tanku",
+ "Pokuta za vlastné zničenie tanku", "Násobiteľ pri predaji položiek",
+ "Teamy zdieľajú peniaze", "Späť", 0x0
+ }, {
+ /* === EL_RUSSIAN === */
+ "Экономика",
+ "Начальные деньги", "Банковский процент",
+ "Бонус за победу", "Бонус за попадание",
+ "Штраф за попадание в себя", "Team-Damage Penalty", "Бонус за уничтожение",
+ "Штраф за самоуничтожение", "Коэфф. продажи снаряжения",
+ "Командные боеприпасы", "Назад", 0x0
+ }, {
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ "Money",
+ "Starting Money", "Interest Rate",
+ "Round Win Bonus", "Damage Bounty",
+ "Self-Damage Penalty", "Team-Damage Penalty", "Tank Destruction Bonus",
+ "Tank Self-Destruction Penalty", "Item Sell Multiplier",
+ "Teams Share" , "Back", 0x0
+ }, {
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ "Money",
+ "Starting Money", "Interest Rate",
+ "Round Win Bonus", "Damage Bounty",
+ "Self-Damage Penalty", "Team-Damage Penalty", "Tank Destruction Bonus",
+ "Tank Self-Destruction Penalty", "Item Sell Multiplier",
+ "Teams Share", "Back", 0x0
+ }
+ }, {
+ /* -------------------- *
+ * --- GRAPHICS --- *
+ * -------------------- */
+ {
+ /* === EL_ENGLISH === */
+ "Graphics",
+ "Full Screen", "Dithering",
+ "Detailed Land", "Detailed Sky",
+ "Fading Text", "Shadowed Text",
+ "Swaying Text",
+ "Colour Theme", "Screen Width",
+ "Screen Height", "Mouse Pointer",
+ "Game Speed", "Custom Background",
+ "Show AI Feedback", "Dynamic Menu Background",
+ "Back", 0x0
+ }, {
+ /* === EL_PORTUGUESE === */
+ "Gráficos",
+ "Tela Cheia", "Pontilhamento",
+ "Detalhes do Terreno", "Detalhes do Céu",
+ "texto sombreado", "texto de desvanecimento",
+ "Swaying Text",
+ "tema da cor", "Largura da Tela",
+ "Altura da Tela", "Ponteiro do Rato",
+ "Velocidade do jogo", "Fundo personalizado",
+ "Show AI Feedback", "Dynamic Menu Background",
+ "Back", 0x0
+ }, {
+ /* === EL_FRENCH === */
+ "Graphismes",
+ "Full Screen", "Tramage",
+ "Détails du terrain", "Ciel détaillé",
+ "texte ombragé", "texte de effacement",
+ "Swaying Text",
+ "Thème de couleurs", "Largeur d'écran",
+ "Hauteur d'écran", "Curseur de souris",
+ "Vitesse du jeu", "Fond fait sur commande",
+ "Show AI Feedback", "Dynamic Menu Background",
+ "Back", 0x0
+ }, {
+ /* === EL_GERMAN === */
+ "Grafik",
+ "Vollbild", "Dithering",
+ "Landdetails", "Himmeldetails",
+ "Ausblendender Text", "Schattierter Text",
+ "Schwingender Text",
+ "Farbschema", "Bildschirmbreite",
+ "Bildschirmhöhe", "Mauszeiger",
+ "Spielgeschwindigket", "Eigener Hintergrund",
+ "Zeige AI Feedback", "Dynamischer Menühintergrund",
+ "Zurück", 0x0
+ }, {
+ /* === EL_SLOVAK === */
+ "Grafika",
+ "Na celú obrazovku", "Rozptyl",
+ "Detaily krajiny", "Detaily oblohy",
+ "Slabnúci text", "Text s tieňom",
+ "Swaying Text",
+ "Farebná téma", "Šírka obrazovky",
+ "Výška obrazovky", "Ukazovateľ myši",
+ "Rýchlosť hry", "Vlastné pozadie",
+ "Show AI Feedback", "Dynamic Menu Background",
+ "Späť", 0x0
+ }, {
+ /* === EL_RUSSIAN === */
+ "Графика",
+ "Full Screen", "Сглаживание",
+ "Детализированный ландшафт", "Детализированное небо",
+ "Исчезающий текст", "Оттененный текст",
+ "Swaying Text",
+ "Цветовая тема", "Ширина окна игры",
+ "Высота окна игры", "Курсор в игре",
+ "Скорость игры", "Собственный фон",
+ "Show AI Feedback", "Dynamic Menu Background",
+ "Назад", 0x0
+ }, {
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ "Graphics",
+ "Full Screen", "Dithering",
+ "Detailed Land", "Detailed Sky",
+ "Fading Text", "Shadowed Text",
+ "Swaying Text",
+ "Colour Theme", "Screen Width",
+ "Screen Height", "Mouse Pointer",
+ "Game Speed", "Custom Background",
+ "Show AI Feedback", "Dynamic Menu Background",
+ "Back", 0x0
+ }, {
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ "Graphics",
+ "Full Screen", "Dithering",
+ "Detailed Land", "Detailed Sky",
+ "Fading Text", "Shadowed Text",
+ "Swaying Text",
+ "Colour Theme", "Screen Width",
+ "Screen Height", "Mouse Pointer",
+ "Game Speed", "Custom Background",
+ "Show AI Feedback", "Dynamic Menu Background",
+ "Back", 0x0
+ }
+ }, {
+ /* -------------------- *
+ * --- MAIN --- *
+ * -------------------- */
+ {
+ /* === EL_ENGLISH === */
+ "Main Menu",
+ "Reset All", "Physics",
+ "Weather", "Graphics",
+ "Money", "Network",
+ "Sound", "Weapon Tech Level",
+ "Item Tech Level", "Landscape",
+ "Turn Order", "Skip AI-only play",
+ "Show FPS", "Language",
+ "Back", 0x0
+ }, {
+ /* === EL_PORTUGUESE === */
+ "Menu Principal",
+ "Reset All", "Física",
+ "Condições Meteorológicas", "Gráficos",
+ "Finanças", "Rede",
+ "Som", "Arma Nível Tecnológico",
+ "Artigo Nível Tecnológico", "Cenário",
+ "Ordem de Jogadas", "Continuar o Jogo Só com Robôs",
+ "Show FPS", "Língua",
+ "Back", 0x0
+ }, {
+ /* === EL_FRENCH === */
+ "Menu principal",
+ "Reset All", "Physique",
+ "Météo", "Graphismes",
+ "Finances", "Réseau",
+ "Sound", "Niveau technique armes",
+ "Niveau technique équipement", "Paysage",
+ "Ordre de passage", "Continuer le Jeu Robots seuls",
+ "Show FPS", "Langue",
+ "Back", 0x0
+ }, {
+ /* === EL_GERMAN === */
+ "Hauptmenü",
+ "Alles zurücksetzen", "Physik",
+ "Wetter", "Grafik",
+ "Geld", "Netzwerk",
+ "Sounds", "Technologiestufe Waffen",
+ "Technologiestufe Gegenstände", "Landschaft",
+ "Reihenfolge", "Überspringe Nur-KI",
+ "FPS anzeigen", "Sprache",
+ "Zurück", 0x0
+ }, {
+ /* === EL_SLOVAK === */
+ "Hlavné menu",
+ "Reset All", "Fyzika",
+ "Počasie", "Grafika",
+ "Peniaze", "Sieť",
+ "Zvuk", "Tech úroveň zbraní",
+ "Tech úroveň vecí", "Krajina",
+ "Poradie","Preskočiť hru samotného PC",
+ "Show FPS", "Jazyk",
+ "Späť", 0x0
+ }, {
+ /* === EL_RUSSIAN === */
+ "Главное меню",
+ "Reset All", "Физика",
+ "Погода", "Графика",
+ "Экономика", "Настройки сети",
+ "Звук", "Уровень оружия",
+ "Уровень снаряжения", "Тип ландшафта",
+ "Порядок хода", "Пропускать игру компьютеров",
+ "Show FPS", "Язык (Language)",
+ "Назад", 0x0
+ }, {
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ "Main Menu",
+ "Reset All", "Physics",
+ "Weather", "Graphics",
+ "Money", "Network",
+ "Sound", "Weapon Tech Level",
+ "Item Tech Level", "Landscape",
+ "Turn Order", "Skip AI-only play",
+ "Show FPS", "Language",
+ "Back", 0x0
+ }, {
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ "Main Menu",
+ "Reset All", "Physics",
+ "Weather", "Graphics",
+ "Money", "Network",
+ "Sound", "Weapon Tech Level",
+ "Item Tech Level", "Landscape",
+ "Turn Order", "Skip AI-only play",
+ "Show FPS", "Language",
+ "Back", 0x0
+ }
+ }, {
+ /* -------------------- *
+ * --- NETWORK --- *
+ * -------------------- */
+ {
+ /* === EL_ENGLISH === */
+ "Network",
+ "Check Updates", "Networking",
+ "Listen Port", "Server Address",
+ "Server Port", "Back", 0x0
+ }, {
+ /* === EL_PORTUGUESE === */
+ "Network",
+ "Procurar actualizações", "Activar Rede",
+ "Número de Porta", "Server Address",
+ "Server Port", "Back", 0x0
+ }, {
+ /* === EL_FRENCH === */
+ /* ===== Needs to be translated ===== */
+ "Network",
+ "Check Updates", "Networking",
+ "Listen Port", "Server Address",
+ "Server Port", "Back", 0x0
+ }, {
+ /* === EL_GERMAN === */
+ "Netzwerk",
+ "Auf Aktualisierungen prüfen", "Netzwerk",
+ "offener Port", "Serveraddresse",
+ "Server Port", "Zurück", 0x0
+ }, {
+ /* === EL_SLOVAK === */
+ "Sieť",
+ "Kontrola aktualizácii", "Sieťová hra",
+ "Port pre načúvanie", "Server Address",
+ "Server Port", "Späť", 0x0
+ }, {
+ /* === EL_RUSSIAN === */
+ "Настройки сети",
+ "Проверять обновления", "Networking",
+ "Listen Port", "Server Address",
+ "Server Port", "Назад", 0x0
+ }, {
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ "Network",
+ "Check Updates", "Networking",
+ "Listen Port", "Server Address",
+ "Server Port", "Back", 0x0
+ }, {
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ "Network",
+ "Check Updates", "Networking",
+ "Listen Port", "Server Address",
+ "Server Port", "Back", 0x0
+ }
+ }, {
+ /* -------------------- *
+ * --- PHYSICS --- *
+ * -------------------- */
+ {
+ /* === EL_ENGLISH === */
+ "Physics",
+ "Gravity", "Viscosity",
+ "Land Slide", "Land Slide Delay",
+ "Wall Type", "Boxed Mode",
+ "Boxed Ceiling Wrapping",
+ "Violent Death", "Timed Shots",
+ "Volley Delay", "Explosion Debris",
+ "Back", 0x0
+ }, {
+ /* === EL_PORTUGUESE === */
+ "Física",
+ "Gravidade", "Viscosidade",
+ "Deslizamento de Terra", "Corrediça da terra atrasa",
+ "Tipo de Parede", "Modalidade encaixotada",
+ "Boxed Ceiling Wrapping",
+ "Morte violenta", "Tiro programado",
+ "Volley Delay", "Explosion Debris",
+ "Back", 0x0
+ }, {
+ /* === EL_FRENCH === */
+ "Physique",
+ "Gravité", "Viscosité",
+ "Glissements de terrain", "Délai glissements de terrain",
+ "Murs", "Enfermé dans boîte",
+ "Boxed Ceiling Wrapping",
+ "Mort violente", "Projectile synchronisé",
+ "Volley Delay", "Explosion Debris",
+ "Back", 0x0
+ }, {
+ /* === EL_GERMAN === */
+ "Physik",
+ "Gravitation", "Reibung",
+ "Erdrutsch", "Erdrutsch Verzögerung",
+ "Wand Art", "Höhlenmodus",
+ "Höhlendeckenwarp",
+ "Gewalttätiger Tod", "Zeitlimit",
+ "Mehrfachschussverzögerung", "Explosionsschrott",
+ "Zurück", 0x0
+ }, {
+ /* === EL_SLOVAK === */
+ "Fyzika",
+ "Gravitácia", "Viskozita",
+ "Zosun zeme", "Zdržanie zosunu zeme",
+ "Typ steny", "Režim krabíc",
+ "Boxed Ceiling Wrapping",
+ "Krutá smrť", "Časované strely",
+ "Volley Delay", "Explosion Debris",
+ "Späť", 0x0
+ }, {
+ /* === EL_RUSSIAN === */
+ "Физика",
+ "Гравитация", "Сила трения",
+ "Падение земли", "Задержка падения земли",
+ "Тип стен", "Потолок",
+ "Boxed Ceiling Wrapping",
+ "Мощные взрывы танков", "Задержка выстрела",
+ "Volley Delay", "Explosion Debris",
+ "Назад", 0x0
+ }, {
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ "Physics",
+ "Gravity", "Viscosity",
+ "Land Slide", "Land Slide Delay",
+ "Wall Type", "Boxed Mode",
+ "Boxed Ceiling Wrapping",
+ "Violent Death", "Timed Shots",
+ "Volley Delay", "Explosion Debris",
+ "Back", 0x0
+ }, {
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ "Physics",
+ "Gravity", "Viscosity",
+ "Land Slide", "Land Slide Delay",
+ "Wall Type", "Boxed Mode",
+ "Boxed Ceiling Wrapping",
+ "Violent Death", "Timed Shots",
+ "Volley Delay", "Explosion Debris",
+ "Back", 0x0
+ }
+ }, {
+ /* -------------------- *
+ * --- PLAY --- *
+ * -------------------- */
+ {
+ /* === EL_ENGLISH === */
+ "Select Players",
+ "Rounds", "New Game Name",
+ "or Load Game", "Load Game",
+ "Campaign", "Okay",
+ "Back", 0x0
+ }, {
+ /* === EL_PORTUGUESE === */
+ /* ===== Needs to be translated ===== */
+ "Select Players",
+ "Rounds", "New Game Name",
+ "or Load Game", "Load Game",
+ "Campaign", "Okay",
+ "Back", 0x0
+ }, {
+ /* === EL_FRENCH === */
+ /* ===== Needs to be translated ===== */
+ "Select Players",
+ "Rounds", "New Game Name",
+ "or Load Game", "Load Game",
+ "Campaign", "Okay",
+ "Back", 0x0
+ }, {
+ /* === EL_GERMAN === */
+ "Spieler auswählen",
+ "Rundenanzahl", "Neues Spiel",
+ "oder Spiel laden", "Spiel laden",
+ "Kampagne", "Starten",
+ "Zurück", 0x0
+ }, {
+ /* === EL_SLOVAK === */
+ "Výber hráčov",
+ "Kolá", "Názov novej hry",
+ "alebo načítať hru", "Načítať hru",
+ "Kampaň", "OK",
+ "Späť", 0x0
+ }, {
+ /* === EL_RUSSIAN === */
+ "Выберите игроков",
+ "Кол-во раундов", "Имя для игры",
+ "или имя прошлой игры", "Загрузить игру",
+ "Кампания", "OK",
+ "Назад", 0x0
+ }, {
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ "Select Players",
+ "Rounds", "New Game Name",
+ "or Load Game", "Load Game",
+ "Campaign", "Okay",
+ "Back", 0x0
+ }, {
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ "Select Players",
+ "Rounds", "New Game Name",
+ "or Load Game", "Load Game",
+ "Campaign", "Okay",
+ "Back", 0x0
+ }
+ }, {
+ /* -------------------- *
+ * --- PLAYER --- *
+ * -------------------- *
+ * Note: The title says "New Player", but this class is used for the
+ * player editing, too. There the title is substituted by the player
+ * name.
+ * Further the "New Player" screen itself does not display the
+ * "Delete This Player" option.
+ */
+ {
+ /* === EL_ENGLISH === */
+ "New Player",
+ "Name", "Colour",
+ "Type", "Team",
+ "Generate Pref", "Played",
+ "Won", "Tank Type",
+ "Delete This Player", "Okay",
+ "Back", 0x0
+ }, {
+ /* === EL_PORTUGUESE === */
+ /* ===== Needs to be translated ===== */
+ "New Player",
+ "Name", "Colour",
+ "Type", "Team",
+ "Generate Pref", "Played",
+ "Won", "Tank Type",
+ "Delete This Player", "Okay",
+ "Back", 0x0
+ }, {
+ /* === EL_FRENCH === */
+ /* ===== Needs to be translated ===== */
+ "New Player",
+ "Name", "Colour",
+ "Type", "Team",
+ "Generate Pref", "Played",
+ "Won", "Tank Type",
+ "Delete This Player", "Okay",
+ "Back", 0x0
+ }, {
+ /* === EL_GERMAN === */
+ "Neuer Spieler",
+ "Name", "Farbe",
+ "Typ", "Team",
+ "Erzeuge Konfig", "Gespielt",
+ "Gewonnen", "Panzertyp",
+ "Diesen Spieler Löschen", "Anlegen",
+ "Zurück", 0x0
+ }, {
+ /* === EL_SLOVAK === */
+ /* ===== Needs to be translated ===== */
+ "New Player",
+ "Name", "Colour",
+ "Type", "Team",
+ "Generate Pref", "Played",
+ "Won", "Tank Type",
+ "Delete This Player", "OK",
+ "Späť", 0x0
+ }, {
+ /* === EL_RUSSIAN === */
+ /* ===== Needs to be translated ===== */
+ "New Player",
+ "Name", "Colour",
+ "Type", "Team",
+ "Generate Pref", "Played",
+ "Won", "Tank Type",
+ "Delete This Player", "OK",
+ "Назад", 0x0
+ }, {
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ "New Player",
+ "Name", "Colour",
+ "Type", "Team",
+ "Generate Pref", "Played",
+ "Won", "Tank Type",
+ "Delete This Player", "Okay",
+ "Back", 0x0
+ }, {
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ "New Player",
+ "Name", "Colour",
+ "Type", "Team",
+ "Generate Pref", "Played",
+ "Won", "Tank Type",
+ "Delete This Player", "Okay",
+ "Back", 0x0
+ }
+ }, {
+ /* -------------------- *
+ * --- PLAYERS --- *
+ * -------------------- */
+ {
+ /* === EL_ENGLISH === */
+ "Players",
+ "Create New", "Back", 0x0
+ }, {
+ /* === EL_PORTUGUESE === */
+ /* ===== Needs to be translated ===== */
+ "Players",
+ "Create New", "Back", 0x0
+ }, {
+ /* === EL_FRENCH === */
+ /* ===== Needs to be translated ===== */
+ "Players",
+ "Create New", "Back", 0x0
+ }, {
+ /* === EL_GERMAN === */
+ "Spieler",
+ "Neuer Spieler", "Zurück", 0x0
+ }, {
+ /* === EL_SLOVAK === */
+ /* ===== Needs to be translated ===== */
+ "Players",
+ "Create New", "Späť", 0x0
+ }, {
+ /* === EL_RUSSIAN === */
+ /* ===== Needs to be translated ===== */
+ "Players",
+ "Create New", "Назад", 0x0
+ }, {
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ "Players",
+ "Create New", "Back", 0x0
+ }, {
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ "Players",
+ "Create New", "Back", 0x0
+ }
+ }, {
+ /* -------------------- *
+ * --- RESET --- *
+ * -------------------- */
+ {
+ /* === EL_ENGLISH === */
+ "Reset Options?",
+ "Reset", "Back", 0x0
+ }, {
+ /* === EL_PORTUGUESE === */
+ /* ===== Needs to be translated ===== */
+ "Reset Options?",
+ "Reset", "Back", 0x0
+ }, {
+ /* === EL_FRENCH === */
+ /* ===== Needs to be translated ===== */
+ "Optionen zurücksetzen?",
+ "Zurücksetzen", "Abbruch", 0x0
+ }, {
+ /* === EL_GERMAN === */
+ "Reset Options?",
+ "Reset", "Back", 0x0
+ }, {
+ /* === EL_SLOVAK === */
+ /* ===== Needs to be translated ===== */
+ "Reset Options?",
+ "Reset", "Back", 0x0
+ }, {
+ /* === EL_RUSSIAN === */
+ /* ===== Needs to be translated ===== */
+ "Reset Options?",
+ "Reset", "Back", 0x0
+ }, {
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ "Reset Options?",
+ "Reset", "Back", 0x0
+ }, {
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ "Reset Options?",
+ "Reset", "Back", 0x0
+ }
+ }, {
+ /* -------------------- *
+ * --- SOUND --- *
+ * -------------------- */
+ {
+ /* === EL_ENGLISH === */
+ "Sound",
+ "All Sound", "Sound Driver",
+ "Music", "Volume Factor",
+ "Back", 0x0
+ }, {
+ /* === EL_PORTUGUESE === */
+ "Som",
+ "Efeitos de Som", "Sistema de Som",
+ "Música", "Volume Factor",
+ "Back", 0x0
+ }, {
+ /* === EL_FRENCH === */
+ "Sound",
+ "Effets Sonores", "Système de Son",
+ "Musique", "Volume Factor",
+ "Back", 0x0
+ }, {
+ /* === EL_GERMAN === */
+ "Sounds",
+ "Alle Sounds", "Sound Treiber",
+ "Musik", "Lautstärkefaktor",
+ "Zurück", 0x0
+ }, {
+ /* === EL_SLOVAK === */
+ "Zvuk",
+ "Všetky zvuky", "Ovládač zvuku",
+ "Hudba", "Volume Factor",
+ "Späť", 0x0
+ }, {
+ /* === EL_RUSSIAN === */
+ /* ===== Needs to be translated ===== */
+ "Sound",
+ "All Sound", "Sound Driver",
+ "Music", "Volume Factor",
+ "Назад", 0x0
+ }, {
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ "Sound",
+ "All Sound", "Sound Driver",
+ "Music", "Volume Factor",
+ "Back", 0x0
+ }, {
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ "Sound",
+ "All Sound", "Sound Driver",
+ "Music", "Volume Factor",
+ "Back", 0x0
+ }
+ }, {
+ /* -------------------- *
+ * --- WEATHER --- *
+ * -------------------- */
+ {
+ /* === EL_ENGLISH === */
+ "Weather",
+ "Meteor Showers", "Lightning",
+ "Falling Dirt", "Laser Satellite",
+ "Fog", "Max Wind Strength",
+ "Wind Variation", "Back", 0x0
+ }, {
+ /* === EL_PORTUGUESE === */
+ "Condições Meteorológicas",
+ "Chuvas de Meteoro", "Relâmpagos",
+ "Sujeira de queda", "Satélite do Laser",
+ "Neblina", "Velocidade Max do Vento",
+ "Variação do Vento", "Back", 0x0
+ }, {
+ /* === EL_FRENCH === */
+ "Météo",
+ "Orages de météorites", "Éclairs",
+ "Saleté en chute", "Satellites Laser",
+ "Brouillard", "Force maxi du vent",
+ "Variation du vent", "Back", 0x0
+ }, {
+ /* === EL_GERMAN === */
+ "Wetter",
+ "Meteoritenregen", "Gewitter",
+ "Schmutzregen", "Lasersatellit",
+ "Nebel", "Max Windstärke",
+ "Windveränderung", "Zurück", 0x0
+ }, {
+ /* === EL_SLOVAK === */
+ "Počasie",
+ "Dážď meteorov", "Blesky",
+ "Padajúca zem", "Laserový satelit",
+ "Hmla", "Maximálna sila vetra",
+ "Zmena vetra", "Späť", 0x0
+ }, {
+ /* === EL_RUSSIAN === */
+ "Погода",
+ "Метеоритный дождь", "Молнии",
+ "Падающая грязь", "Удары со спутника",
+ "Туман", "Макс. сила ветра",
+ "Изменения силы ветра", "Назад", 0x0
+ }, {
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ "Weather",
+ "Meteor Showers", "Lightning",
+ "Falling Dirt", "Laser Satellite",
+ "Fog", "Max Wind Strength",
+ "Wind Variation", "Back", 0x0
+ }, {
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ "Weather",
+ "Meteor Showers", "Lightning",
+ "Falling Dirt", "Laser Satellite",
+ "Fog", "Max Wind Strength",
+ "Wind Variation", "Back", 0x0
+ }
+ }
+};
+
+
+/** @brief string array for the option text class content
+ *
+ * The ordering, although it looks a bit overwhelming here, is quite simple.
+ * The first index is the text class, the second is the language.
+ *
+ * With this both translation and adding new content is fairly easy. Just
+ * copy a block (after adding new enum entries at the proper places in
+ * optiontypes.h) and edit to the new content.
+ *
+ * All text arrays end with a zero 0x0 entry. It is therefore not needed to
+ * hard code any option value sizes.
+**/
+const char* const
+OptionClassText[TC_TEXTCLASS_COUNT][EL_LANGUAGE_COUNT][maxEntriesPerClass] = {
+ {
+ /* -------------------- *
+ * --- TC_COLOUR --- *
+ * -------------------- */
+ /* === EL_ENGLISH === */
+ { "Regular", "Crispy", 0x0 },
+ /* === EL_PORTUGUESE === */
+ /* ===== Needs to be translated ===== */
+ { "Regular", "Crispy", 0x0 },
+ /* === EL_FRENCH === */
+ { "Régulier", "Croustillant", 0x0 },
+ /* === EL_GERMAN === */
+ { "Normal", "Kontrastreich", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Normálna", "Svieža", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Обычная", "Четкая", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Regular", "Crispy", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "Regular", "Crispy", 0x0 }
+
+ }, {
+ /* --------------------- *
+ * --- TC_LANDSLIDE --- *
+ * --------------------- */
+ /* === EL_ENGLISH === */
+ { "None", "Tank Only", "Instant", "Gravity", "Cartoon", 0x0 },
+ /* === EL_PORTUGUESE === */
+ { "Nenhum", "Tanque Somente", "Instantâneo", "Gravidade", "Cartoon", 0x0 },
+ /* === EL_FRENCH === */
+ { "Aucun", "Réservoir Seulement", "Instantané", "Gravité", "Dessin animé", 0x0 },
+ /* === EL_GERMAN === */
+ { "Keine", "Nur Panzer", "Sofort", "Schwerkraft", "Cartoon", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Žiaden", "Iba tank", "Okamžitý", "Gravitácia", "Kresl.film", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Выкл.", "Только танки", "Сразу же", "По умолчанию", "Как в мультиках", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "None", "Tank Only", "Instant", "Gravity", "Cartoon", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "None", "Tank Only", "Instant", "Gravity", "Cartoon", 0x0 }
+
+ }, {
+ /* -------------------- *
+ * --- TC_LANDTYPE --- *
+ * -------------------- */
+ /* === EL_ENGLISH === */
+ { "Random", "Canyons", "Mountains", "Valleys", "Hills", "Foothills", "Plains", "None", 0x0 },
+ /* === EL_PORTUGUESE === */
+ { "Aleatório", "Canyons", "Montanhas", "Vales", "Colinas", "Morros", "Planos", "Nenhum", 0x0 },
+ /* === EL_FRENCH === */
+ { "Aléatoire", "Canyons", "Montagnes", "Vallées", "Collines", "Contreforts", "Plaines", "Aucun", 0x0 },
+ /* === EL_GERMAN === */
+ { "Zufällig", "Canyons", "Berge", "Täler", "Hügel", "Flache Hügel", "Ebene", "Nichts", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Náhodná", "Kaňony", "Hory", "Údolia", "Kopce", "Úpätia", "Nížiny", "Žiadna", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Случайный", "Каньоны", "Горы", "Возвышенность", "Холмы", "Предгорья", "Равнины", "Выкл.", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Random", "Canyons", "Mountains", "Valleys", "Hills", "Foothills", "Plains", "None", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "Random", "Canyons", "Mountains", "Valleys", "Hills", "Foothills", "Plains", "None", 0x0 }
+
+ }, {
+ /* -------------------- *
+ * --- TC_LANGUAGE --- *
+ * -------------------- */
+
+ /* === EL_ENGLISH === */
+ { "English", "Português", "Français", "Deutsch", "Slovak", "Russian", "Spanish", "Italian", 0x0 },
+ /* === EL_PORTUGUESE === */
+ { "English", "Português", "Français", "Deutsch", "Slovak", "Russian", "Spanish", "Italian", 0x0 },
+ /* === EL_FRENCH === */
+ { "English", "Português", "Français", "Deutsch", "Slovak", "Russian", "Spanish", "Italian", 0x0 },
+ /* === EL_GERMAN === */
+ { "English", "Português", "Français", "Deutsch", "Slovak", "Russian", "Spanish", "Italian", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Anglicky", "Portugalsky", "Francúzsky", "Nemecky", "Slovensky", "Rusky", "Spanish", "Italian", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "English", "Português", "Français", "Deutsch", "Slovak", "Русский", "Spanish", "Italian", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "English", "Português", "Français", "Deutsch", "Slovak", "Russian", "Spanish", "Italian", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "English", "Português", "Français", "Deutsch", "Slovak", "Russian", "Spanish", "Italian", 0x0 }
+
+ }, {
+ /* --------------------- *
+ * --- TC_LIGHTNING --- *
+ * --------------------- */
+ /* === EL_ENGLISH === */
+ { "Off", "Weak", "Energetic", "Violent", 0x0 },
+ /* === EL_PORTUGUESE === */
+ { "Desligado", "Fraco", "Energético", "Violento", 0x0 },
+ /* === EL_FRENCH === */
+ { "Aucun", "Faible", "Energique", "Violent", 0x0 },
+ /* === EL_GERMAN === */
+ { "Aus", "Schwach", "Energetisch", "Brutal", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Vypnuté", "Slabé", "Energetické", "Kruté", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Нет", "Слабые", "Сильные", "Мощные", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "Weak", "Energetic", "Violent", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "Weak", "Energetic", "Violent", 0x0 }
+
+ }, {
+ /* -------------------- *
+ * --- TC_METEOR --- *
+ * -------------------- */
+ /* === EL_ENGLISH === */
+ { "Off", "Light", "Heavy", "Lethal", 0x0 },
+ /* === EL_PORTUGUESE === */
+ { "Desligado", "Fraco", "Forte", "Letal", 0x0 },
+ /* === EL_FRENCH === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "Light", "Heavy", "Lethal", 0x0 },
+ /* === EL_GERMAN === */
+ { "Aus", "Leicht", "Schwer", "Tödlich", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Vypnuté", "Ľahké", "Ťažké", "Smrteľné", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Нет", "Слабый", "Сильный", "Смертельный", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "Light", "Heavy", "Lethal", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "Light", "Heavy", "Lethal", 0x0 }
+
+ }, {
+ /* -------------------- *
+ * --- TC_MOUSE --- *
+ * -------------------- */
+ /* === EL_ENGLISH === */
+ { "Custom", "Default", 0x0 },
+ /* === EL_PORTUGUESE === */
+ { "Personalizado", "Padrão", 0x0 },
+ /* === EL_FRENCH === */
+ { "Pesonnel", "Défaut", 0x0 },
+ /* === EL_GERMAN === */
+ { "Angepasst", "Standard", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Vlastné", "Východzie", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Собственный", "По умолчанию", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Custom", "Default", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "Custom", "Default", 0x0 }
+
+ }, {
+ /* -------------------- *
+ * --- TC_OFFON --- *
+ * -------------------- */
+ /* === EL_ENGLISH === */
+ { "Off", "On", 0x0 },
+ /* === EL_PORTUGUESE === */
+ { "Desligado", "Ligado", 0x0 },
+ /* === EL_FRENCH === */
+ { "Non", "Oui", 0x0 },
+ /* === EL_GERMAN === */
+ { "Aus", "An", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Vypnuté", "Zapnuté", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Выкл.", "Вкл.", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "On", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "On", 0x0 }
+
+ }, {
+ /* ----------------------- *
+ * --- TC_OFFONRANDOM --- *
+ * ----------------------- */
+ /* === EL_ENGLISH === */
+ { "Off", "On", "Random", 0x0 },
+ /* === EL_PORTUGUESE === */
+ { "Desligado", "Ligado", "Aleatório", 0x0 },
+ /* === EL_FRENCH === */
+ { "Non", "Oui", "Hasard", 0x0 },
+ /* === EL_GERMAN === */
+ { "Aus", "An", "Zufällig", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Vypnuté", "Zapnuté", "Náhodný", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Выкл.", "Вкл.", "Случайно", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "On", "Random", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "On", "Random", 0x0 }
+
+ }, {
+ /* ----------------------- *
+ * --- TC_PLAYERPREF --- *
+ * ----------------------- */
+ /* === EL_ENGLISH === */
+ { "Per Game", "Only Once", 0x0 },
+ /* === EL_PORTUGUESE === */
+ /* ===== Needs to be translated ===== */
+ { "Per Game", "Only Once", 0x0 },
+ /* === EL_FRENCH === */
+ /* ===== Needs to be translated ===== */
+ { "Per Game", "Only Once", 0x0 },
+ /* === EL_GERMAN === */
+ { "Pro Spiel", "Nur einmal", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Na hru", "Iba raz", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Каждую игру заново", "Только один раз", 0x0 },
+ /* === EL_SPANISH === */
+ { "Por Juego", "Solo una vez", 0x0 },
+ /* === EL_ITALIAN === */
+ { "Per Gioco", "Only Once", 0x0 },
+
+ } , {
+ /* ----------------------- *
+ * --- TC_PLAYERTEAM --- *
+ * ----------------------- */
+ /* === EL_ENGLISH === */
+ { "Sith", "Neutral", "Jedi", 0x0 },
+ /* === EL_PORTUGUESE === */
+ /* ===== Needs to be translated ===== */
+ { "Sith", "Neutral", "Jedi", 0x0 },
+ /* === EL_FRENCH === */
+ /* ===== Needs to be translated ===== */
+ { "Sith", "Neutral", "Jedi", 0x0 },
+ /* === EL_GERMAN === */
+ { "Sith", "Neutral", "Jedi", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Sith", "Neutrálny", "Jedi", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Ситх", "Нейтральный", "Джедай", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Sith", "Neutral", "Jedi", 0x0 },
+ /* === EL_ITALIAN === */
+ { "Sith", "Neutrale", "Jedi", 0x0 },
+
+ } , {
+ /* ----------------------- *
+ * --- TC_PLAYERTYPE --- *
+ * ----------------------- */
+ /* === EL_ENGLISH === */
+ { "Human", "Useless", "Guesser", "Range", "Targetter", "Deadly", 0x0 },
+ /* === EL_PORTUGUESE === */
+ /* ===== Needs to be translated ===== */
+ { "Human", "Useless", "Guesser", "Range", "Targetter", "Deadly", 0x0 },
+ /* === EL_FRENCH === */
+ /* ===== Needs to be translated ===== */
+ { "Human", "Useless", "Guesser", "Range", "Targetter", "Deadly", 0x0 },
+ /* === EL_GERMAN === */
+ { "Mensch", "Nutzlos", "Ratlos", "Schütze", "Scharfschütze", "Tödlich", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Človek", "Nepoužiteľný", "Ten, čo háda", "Ten, čo hľadá správnu silu", "Ten, čo mieri", "Ten, čo prináša smrť", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Человек", "Ноль", "Слабый ИИ", "Средний ИИ", "Сильный ИИ", "Терминатор", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Humano", "Inservible", "Guesser", "Rango", "Targetter", "Mortal", 0x0 },
+ /* === EL_ITALIAN === */
+ { "Umano", "Sottodotato", "Mediocre", "Medio", "Elevato", "Mortale", 0x0 },
+
+ }, {
+ /* --------------------- *
+ * --- TC_SATELLITE --- *
+ * --------------------- */
+ /* === EL_ENGLISH === */
+ { "Off", "Weak", "Strong", "Super", 0x0 },
+ /* === EL_PORTUGUESE === */
+ { "Desligado", "Fraco", "Forte", "Super", 0x0 },
+ /* === EL_FRENCH === */
+ { "Aucun", "Faible", "Fort", "Super", 0x0 },
+ /* === EL_GERMAN === */
+ { "Aus", "Schwach", "Stark", "Super", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Vypnutý", "Slabý", "Silný", "Super", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Нет", "Слабые", "Сильные", "Супер!!", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "Weak", "Strong", "Super", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "Weak", "Strong", "Super", 0x0 }
+
+ }, {
+ /* -------------------- *
+ * --- TC_SKIPTYPE --- *
+ * -------------------- */
+ /* === EL_ENGLISH === */
+ { "Off", "Humans Dead", 0x0 },
+ /* === EL_PORTUGUESE === */
+ /* ===== Wrong translation ? ===== */
+ { "Desligado", "Ligado", 0x0 },
+ /* === EL_FRENCH === */
+ /* ===== Wrong translation ? ===== */
+ { "Non", "Oui", 0x0 },
+ /* === EL_GERMAN === */
+ { "Aus", "Menschen Tot", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Vypnuté", "Smrť ľudí", 0x0 },
+ /* === EL_RUSSIAN === */
+ /* ===== Wrong translation ? ===== */
+ { "Выкл.", "Вкл.", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "Humans Dead", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "Off", "Humans Dead", 0x0 }
+
+ }, {
+ /* ----------------------- *
+ * --- TC_SOUNDDRIVER --- *
+ * ----------------------- */
+ /* === EL_ENGLISH === */
+ { "Auto Detect", "OSS", "ESD", "ARTS", "ALSA", "JACK", 0x0 },
+ /* === EL_PORTUGUESE === */
+ /* ===== Needs to be translated ===== */
+ { "Auto Detect", "OSS", "ESD", "ARTS", "ALSA", "JACK", 0x0 },
+ /* === EL_FRENCH === */
+ /* ===== Needs to be translated ===== */
+ { "Auto Detect", "OSS", "ESD", "ARTS", "ALSA", "JACK", 0x0 },
+ /* === EL_GERMAN === */
+ { "Automatisch", "OSS", "ESD", "ARTS", "ALSA", "JACK", 0x0 },
+ /* === EL_SLOVAK === */
+ /* ===== Needs to be translated ===== */
+ { "Auto Detect", "OSS", "ESD", "ARTS", "ALSA", "JACK", 0x0 },
+ /* === EL_RUSSIAN === */
+ /* ===== Needs to be translated ===== */
+ { "Auto Detect", "OSS", "ESD", "ARTS", "ALSA", "JACK", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Auto Detect", "OSS", "ESD", "ARTS", "ALSA", "JACK", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "Auto Detect", "OSS", "ESD", "ARTS", "ALSA", "JACK", 0x0 }
+
+ }, {
+ /* -------------------- *
+ * --- TC_TANKTYPE --- *
+ * -------------------- */
+ /* === EL_ENGLISH === */
+ { "Normal", "Classic", "Big Grey", "T34", "Heavy", "Future", "UFO", "Spider", "Big Foot", "Mini", 0x0 },
+ /* === EL_PORTUGUESE === */
+ /* ===== Needs to be translated ===== */
+ { "Normal", "Classic", "Big Grey", "T34", "Heavy", "Future", "UFO", "Spider", "Big Foot", "Mini", 0x0 },
+ /* === EL_FRENCH === */
+ /* ===== Needs to be translated ===== */
+ { "Normal", "Classic", "Big Grey", "T34", "Heavy", "Future", "UFO", "Spider", "Big Foot", "Mini", 0x0 },
+ /* === EL_GERMAN === */
+ { "Normal", "Klassisch", "Der Große Graue", "T34", "Schwergewicht", "Futuristisch", "UFO", "Spinne", "Big Foot", "Mini", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Bežný", "Klasický", "Veľký šedý", "T34", "Ťažký", "Futuristický", "UFO", "Spider", "Big Foot", "Mini", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Обычный", "В старом стиле", "Большой Серый Танк", "Т-34", "Heavy", "Future", "UFO", "Spider", "Big Foot", "Mini", 0x0 },
+ /* === EL_SPANISH === */
+ { "Normal", "Clasico", "Big Grey", "T34", "Pesado", "Futuro", "UFO", "Araña", "Big Foot", "Mini", 0x0 },
+ /* === EL_ITALIAN === */
+ { "Normale", "Classico", "Big Grey", "T34", "Pesante", "Futuro", "UFO", "Spider", "Big Foot", "Mini", 0x0 },
+
+ }, {
+ /* -------------------- *
+ * --- TC_TURNTYPE --- *
+ * -------------------- */
+ /* === EL_ENGLISH === */
+ { "High+", "Low+", "Random", "Simul", 0x0 },
+ /* === EL_PORTUGUESE === */
+ { "Melhores+", "Piores+", "Aleatório", "Simular", 0x0 },
+ /* === EL_FRENCH === */
+ { "Haut", "Bas", "Aléatoire", "Similaire", 0x0 },
+ /* === EL_GERMAN === */
+ { "Hoch+", "Niedrig+", "Zufällig", "Simul", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Vysoký+", "Nízky+", "Náhodný", "Simul", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Сильные +", "Слабые +", "Случайно", "Все сразу", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "High+", "Low+", "Random", "Simul", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "High+", "Low+", "Random", "Simul", 0x0 }
+
+ }, {
+ /* -------------------- *
+ * --- TC_WALLTYPE --- *
+ * -------------------- */
+ /* === EL_ENGLISH === */
+ { "Rubber", "Steel", "Spring", "Wrap", "Random", 0x0 },
+ /* === EL_PORTUGUESE === */
+ { "Elástico", "Aço", "Mola", "Envoltório", "Aleatório", 0x0 },
+ /* === EL_FRENCH === */
+ { "Elastique", "Acier", "Mou", "Enveloppe", "Aléatoire", 0x0 },
+ /* === EL_GERMAN === */
+ { "Gummi", "Stahl", "Federnd", "Verbunden", "Zufällig", 0x0 },
+ /* === EL_SLOVAK === */
+ { "Guma", "Oceľ", "Pružina", "Prikrývka", "Náhodný", 0x0 },
+ /* === EL_RUSSIAN === */
+ { "Резиновые", "Непробиваемые", "Пружинящие", "Бесконечность", "Случайные", 0x0 },
+ /* === EL_SPANISH === */
+ /* ===== Needs to be translated ===== */
+ { "Rubber", "Steel", "Spring", "Wrap", "Random", 0x0 },
+ /* === EL_ITALIAN === */
+ /* ===== Needs to be translated ===== */
+ { "Rubber", "Steel", "Spring", "Wrap", "Random", 0x0 }
+
+ }
+}; // End of MenuClassText
+
+
+#endif // ATANKS_SRC_OPTIONCONTENT_H_INCLUDED
+
diff --git a/src/optionitem.h b/src/optionitem.h
new file mode 100644
index 0000000..83e4c0d
--- /dev/null
+++ b/src/optionitem.h
@@ -0,0 +1,567 @@
+#pragma once
+#ifndef ATANKS_SRC_OPTIONITEM_H_INCLUDED
+#define ATANKS_SRC_OPTIONITEM_H_INCLUDED
+
+/*
+ * atanks - obliterate each other with oversize weapons
+ *
+ * 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.
+ *
+ */
+
+#include "optionitembase.h"
+
+/** @file optionitem.h
+ * @brief declaration of the option entry template
+**/
+
+
+/** @class OptionItem
+ * @brief abstract one option menu entry
+ *
+ * One option menu entry is what the user interacts with. Each option menu
+ * entry can handle exactly one target value, or none if none is needed.
+ * Target values can be manipulated directly or with the help of an action
+ * function set via function pointer upon creation.
+ * Either a target value or an action function must be set unless the entry
+ * is a sub menu.
+ * In the latter case activating the option displays the sub menu and hands
+ * over control to its menu handler.
+ *
+ * The setup is fairly easy, just use the appropriate setData() method. The
+ * template itself has only two other methods: activate() and display(). See
+ * OptionItemBase for more public methods.
+ *
+ * When using the empty ctor, remember to call it with an explicit type.
+ * To be usable with the new-operator this class has an empty ctor which
+ * leaves all members with neutral default values. To become a usable instance
+ * the method setData() must be called with the desired values.
+ *
+ *
+ * One <B>BIG</B> fat warning, though: Do not use ET_TEXT with anything other
+ * than a char* target, and only allow the usage of OptionItem::activate()
+ * if it is not full, yet!\n
+ * Background: Allowing other types would make the internals insanely
+ * complicated, and all string options that can be manipulated with the option
+ * menu are char* strings (or arrays) already. Do not change this.
+ *
+ * Why a template?\n
+ * With a template the option menu becomes type agnostic. This allowed to
+ * correct all global, environment and player data values with inappropriate
+ * types to proper types without the need to adapt the option menu.
+ *
+ * Template parameters:\n
+ * While tgt_T defines the target type, opt_T is used for the manipulating
+ * values like minimum, maximum, increment/decrement value and action function
+ * typing. This is done to allow a more intuitive usage.\n
+ * As an example, if the target is a double, minimum, maximum and increment
+ * values can be set to -1.0, 1.0 and 0.1 without the need for a postfix 'L'.
+**/
+template<typename tgt_T, typename opt_T = int32_t>
+class OptionItem : public OptionItemBase
+{
+public:
+
+ /* -------------------------------------------
+ * --- Public constructors and destructors ---
+ * -------------------------------------------
+ */
+
+ /** @brief default ctor, no special functions or variants.
+ *
+ * This simplest ctor defines an option that can only be ET_TEXT.
+ *
+ * @param[in,out] target_ Pointer to the value to handle.
+ * @param[in] max_ Maximum length for ET_TEXT options.
+ * @param[in] color_ Color of the text to display.
+ * @param[in] type_ Type of the option.
+ * @param[in] title_ The title of the option to display.
+ * @param[in] titleIdx_ Index value of the submitted title. -1 means @a title_ is fixed.
+ * @param[in] format_ Format string to use when displaying the ET_TEXT target.
+ * @param[in] top_ Top position of the display area.
+ * @param[in] left_ Left position of the display area.
+ * @param[in] width_ Width of the display area.
+ * @param[in] height_ Height of the display area.
+ * @param[in] padding_ Padding of the title and buttons to the display area.
+ **/
+ OptionItem(tgt_T* target_,
+ opt_T max_,
+ int32_t color_,
+ eEntryType type_,
+ const char* title_,
+ int32_t titleIdx_,
+ const char* format_,
+ int32_t top_, int32_t left_, int32_t width_, int32_t height_,
+ int32_t padding_) :
+ OptionItemBase(type_, title_, titleIdx_,
+ nullptr, color_, TC_NONE, format_,
+ top_, left_, width_, height_, padding_, 0),
+ maxVal(max_),
+ target(target_)
+ {
+ // The value of max_ determines whether this
+ // is read only or not. If it is set, it is writable.
+ // The default is true, so only if it maxVal is 0, something has to be done.
+ if (maxVal)
+ read_only = false;
+ }
+
+
+ /** @brief ctor for ET_VALUE with optional display function.
+ *
+ * This ctor creates an ET_VALUE, a different type can not be set.
+ *
+ * @param[in,out] target_ Pointer to the value to handle.
+ * @param[in] title_ The title of the option to display.
+ * @param[in] titleIdx_ Index value of the submitted title. -1 means @a title_ is fixed.
+ * @param[in] text_ Array of texts to display.
+ * @param[in] color_ Color of the text to display.
+ * @param[in] class_ The text class of @a text_.
+ * @param[in] min_ Minimum value target can become.
+ * @param[in] max_ Maximum value target can become.
+ * @param[in] decinc_ Value target is changed on each action.
+ * @param[in] format_ Format string to use when displaying the target.
+ * @param[in] top_ Top position of the display area.
+ * @param[in] left_ Left position of the display area.
+ * @param[in] width_ Width of the display area.
+ * @param[in] height_ Height of the display area.
+ * @param[in] padding_ Padding of the title and buttons to the display area.
+ * @param[in] display_ optional display function to use.
+ **/
+ explicit
+ OptionItem(tgt_T* target_,
+ const char* title_,
+ int32_t titleIdx_,
+ const char** text_,
+ int32_t color_,
+ eTextClass class_,
+ opt_T min_, opt_T max_, opt_T decinc_,
+ const char* format_,
+ int32_t top_, int32_t left_, int32_t width_, int32_t height_, int32_t padding_,
+ bool (*display_)(tgt_T* target, int32_t x, int32_t y)) :
+ OptionItemBase(ET_VALUE, title_, titleIdx_,
+ text_, color_, class_, format_,
+ top_, left_, width_, height_, padding_, 0),
+ displayFunc(display_),
+ decinc(decinc_),
+ maxVal(max_),
+ minVal(min_),
+ target(target_)
+ {
+ // The target must be set, this variant does not allow
+ // an action function:
+ assert(target && "A target must be set with ET_VALUE");
+
+ // maxVal must be larger than minVal, otherwise they are swapped
+ if (maxVal < minVal) {
+ opt_T tmp = minVal;
+ minVal = maxVal;
+ maxVal = tmp;
+ }
+
+ // If this is a text rotator, entryNum must be set to *target:
+ entryNum = static_cast<int32_t>(*target);
+
+ // Either format, texts or display must be set
+ assert( (format || texts || displayFunc || (TC_NONE == class_))
+ && "Either format, texts or display must be set with ET_VALUE");
+ }
+
+
+ /** @brief free ctor with action function.
+ *
+ * Here the handling is done by an action function. A display
+ * function can be optionally set, too.
+ *
+ * If this is an ET_ACTION type, a display function must be set.
+ * For ET_BUTTON, an action function is mandatory.
+ *
+ * @param[in,out] target_ Pointer to the value to handle.
+ * @param[in,out] action_ Pointer to the action function handling the target.
+ * @param[in] type_ Type of the option.
+ * @param[in] title_ The title of the option to display.
+ * @param[in] titleIdx_ Index value of the submitted title. -1 means @a title_ is fixed.
+ * @param[in] text_ Array of texts to display. Only needed with ET_VALUE.
+ * @param[in] color_ Color of the text to display.
+ * @param[in] class_ The text class of @a text.
+ * @param[in] format_ Format string to use when displaying the target.
+ * @param[in] top_ Top position of the display area.
+ * @param[in] left_ Left position of the display area.
+ * @param[in] width_ Width of the display area.
+ * @param[in] height_ Height of the display area.
+ * @param[in] padding_ Padding of the title and buttons to the display area.
+ * @param[in] display_ optional display function to use.
+ **/
+ OptionItem(tgt_T* target_,
+ int32_t (*action_)(tgt_T* target, int32_t val),
+ eEntryType type_,
+ const char* title_,
+ int32_t titleIdx_,
+ const char** text_,
+ int32_t color_,
+ eTextClass class_,
+ opt_T min_, opt_T max_, opt_T decinc_,
+ const char* format_,
+ int32_t top_, int32_t left_, int32_t width_, int32_t height_,
+ int32_t padding_,
+ bool (*display_)(tgt_T* target, int32_t x, int32_t y)) :
+ OptionItemBase(type_, title_, titleIdx_,
+ text_, color_, class_, format_,
+ top_, left_, width_, height_, padding_, 0),
+ actionFunc(action_),
+ displayFunc(display_),
+ decinc(decinc_),
+ maxVal(max_),
+ minVal(min_),
+ target(target_)
+ {
+ // Either action or target must be set
+ assert ( (actionFunc || target)
+ && "Either action or target must be set");
+
+ // If this is an ET_ACTION, both action and display
+ // functions must be set:
+ assert ( ( (ET_ACTION != type) || (actionFunc && displayFunc) )
+ && "ET_ACTION needs both display and action function!" );
+
+ // An ET_MENU must have a display function set. To be more concrete,
+ // it must be OptionMenu->displaySub(). (Although this isn't checked.)
+ assert ( ( (ET_MENU != type) || displayFunc )
+ && "ET_MENU must have a display function (OptionMenu->displaySub()) set!");
+
+
+ // If this is ET_VALUE and no action function is set, the same
+ // limitations as with the ET_VALUE ctor are present.
+ if (ET_VALUE == type) {
+ if (maxVal < minVal) {
+ opt_T tmp = minVal;
+ minVal = maxVal;
+ maxVal = tmp;
+ }
+
+ // If this is a text rotator, entryNum must be set to *target:
+ entryNum = static_cast<int32_t>(*target);
+
+ // Either format, texts or display must be set
+ assert( (format || texts || displayFunc || (TC_NONE == class_))
+ && "Either format, texts or display must be set with ET_VALUE");
+ }
+ }
+
+
+ /** @brief special ctor to define a button.
+ *
+ * Here the handling can be done by an action function, otherwise a key
+ * code associated with the button is returned on activation.
+ *
+ * Buttons have no target, so set a dummy type when calling the ctor.
+ *
+ * @param[in] keyCode_ The key code to return when no action function is set.
+ * @param[in,out] target_ Pointer to the value to handle.
+ * @param[in,out] action_ Pointer to the action function handling the button click.
+ * @param[in] title_ The title of the option to display.
+ * @param[in] titleIdx_ Index value of the submitted title. -1 means @a title_ is fixed.
+ * @param[in] button_ Pointer to the button to use, it must be pre-created.
+ * @param[in] top_ Top position of the display area.
+ * @param[in] left_ Left position of the display area.
+ * @param[in] width_ Width of the display area.
+ * @param[in] height_ Height of the display area.
+ * @param[in] padding_ Padding of the title and buttons to the display area.
+ **/
+ OptionItem(int32_t keyCode_,
+ tgt_T* target_,
+ int32_t (*action_)(tgt_T* target, int32_t val),
+ const char* title_,
+ int32_t titleIdx_,
+ BUTTON* button_,
+ int32_t top_, int32_t left_, int32_t width_, int32_t height_,
+ int32_t padding_) :
+ OptionItemBase(ET_BUTTON, title_, titleIdx_,
+ nullptr, BLACK, TC_NONE, nullptr,
+ top_, left_, width_, height_, padding_, 0),
+ actionFunc(action_),
+ target(target_)
+ {
+ // Either action or keyCode must be set
+ assert ( (actionFunc || keyCode_)
+ && "Either action_ or keyCode_ must be set");
+
+ if (keyCode_) this->keyCode = keyCode_;
+ if (button_) {
+ this->button = button_;
+ this->button->getLocation(this->left, this->top, this->width, this->height);
+ }
+ }
+
+
+ /// @brief default dtor only setting nullptr values. No further action needed.
+ ~OptionItem()
+ {
+ actionFunc = nullptr;
+ displayFunc = nullptr;
+ target = nullptr;
+ }
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ /** @brief activate the option handling
+ *
+ * This activates whatever the option is configured for.
+ *
+ * If an action function is set, it is simply called. The parameter is
+ * then ignored. If this is an ET_VALUE and no action function is set,
+ * the configured increment/decrement value is applied according to @a val.
+ * If this is an ET_TEXT option, the appropriate ways to get the target
+ * value are used.
+ *
+ * @param[in] val Used for ET_VALUE: <0 = decrement, >0 = increment.
+ * @param[in] ignored (see OptionItemColour)
+ * @param[in] ignored (see OptionItemColour)
+ * @param[in] k The latest key press to use on an ET_TEXT.
+ * @return normally 0, but ET_BUTTON and ET_MENU can return key_codes
+ * assigned with exit buttons.
+ **/
+ int32_t activate(int32_t val, int32_t, int32_t, int32_t k)
+ {
+ int32_t result = 0;
+
+ if (actionFunc) {
+ result = actionFunc(target, val);
+
+ // Here it is important that the action function does the right
+ // thing with the target if this is an ET_VALUE and val<>0
+ if ((ET_VALUE == type) && val && texts)
+ entryNum = static_cast<int32_t>(*target);
+
+ } else {
+ // Here a target must be set as there is no action function.
+ if ((ET_BUTTON != type) && !target && !actionFunc) {
+ cerr << "\n" << __FUNCTION__ << ": Illegal setup!" << endl;
+ cerr << "A target value MUST be set with ET_";
+ cerr << ( ET_COLOR == type ? "COLOR"
+ : ET_TEXT == type ? "TEXT"
+ : ET_TOGGLE == type ? "TOGGLE"
+ : ET_VALUE == type ? "VALUE" : "UNKNOWN") << endl;
+ return false;
+ }
+
+ // Every type has its own base class activation function,
+ // so a simple switch will do.
+ switch(type) {
+ case ET_BUTTON:
+ // Can trigger end events
+ result = this->keyCode;
+ break;
+ case ET_MENU:
+ result = this->activateMenu(target);
+ break;
+ case ET_TEXT:
+ if (!read_only)
+ this->activateText(target, k);
+ break;
+ case ET_TOGGLE:
+ this->activateToggle(target);
+ break;
+ case ET_VALUE:
+ this->activateValue(val);
+ break;
+ case ET_COLOR:
+ case ET_NONE:
+ case ET_ACTION:
+ default:
+ cerr << "\n" << __FUNCTION__ << ": Illegal setup!" << endl;
+ cerr << " No action function set when it is needed!" << endl;
+ return false;
+ } // End of switch(type)
+ } // End of having no action function
+
+ // Changes are displayed at once:
+ // (unless this is ET_COLOR, it has been drawn already.)
+ if (ET_COLOR != this->type) {
+ this->clear_display(true);
+ this->display(true);
+ }
+
+ return result;
+ }
+
+
+ /// @brief return true if the target has not reached its minimum, yet
+ virtual bool canGoDown()
+ {
+ if ( (ET_VALUE == this->type) && this->format)
+ // Check format, because texts[] based options are rotated.
+ return (*target > static_cast<tgt_T>(minVal));
+ return true;
+ }
+
+
+ /// @brief return true if the target has not reached its maximum, yet
+ virtual bool canGoUp()
+ {
+ if ( (ET_VALUE == this->type) && this->format)
+ return (*target < static_cast<tgt_T>(maxVal));
+ return true;
+ }
+
+
+ /** @brief display the option content
+ *
+ * This method either calls the set display function or its own one.\n
+ * ET_ACTION and ET_MENU <B>must</B> have set a display function.
+ *
+ * If @a show_full is set to true, the option title and possible
+ * button(s) are displayed, too. This is useful to initially display a
+ * menu or when switching languages.
+ *
+ * However, if the option is either ET_MENU or ET_ACTION, @a show_full
+ * is ignored.
+ *
+ * @param[in] show_full If set to true, title and buttons are redrawn.
+ **/
+ void display(bool show_full)
+ {
+ if (displayFunc) {
+ clear_display(false);
+ displayFunc(target, left, top);
+ drawn = true;
+ } else {
+ // Every type has its own base class display function,
+ // so a simple switch will do.
+ switch(type) {
+ case ET_BUTTON:
+ this->displayButton();
+ break;
+ case ET_MENU:
+ this->displayMenu(target);
+ break;
+ case ET_TEXT:
+ this->displayText(target);
+ break;
+ case ET_TOGGLE:
+ this->displayToggle(target);
+ break;
+ case ET_VALUE:
+ this->displayValue(target);
+ break;
+ case ET_NONE:
+ case ET_ACTION:
+ case ET_COLOR:
+ default:
+ cerr << "\n" << __FUNCTION__ << ": Illegal setup!" << endl;
+ cerr << " No display function set when it is needed!" << endl;
+ return;
+ } // End of switch(type)
+ } // end of having no display function
+
+ // Show decorations if wanted: (ET_COLOR does that in displayColor())
+ if (show_full && (ET_COLOR != type))
+ this->displayDeco();
+ }
+
+
+ /// @brief return true if this is an ET_BUTTON with a key code and no action function.
+ bool isExitButton()
+ {
+ return ((ET_BUTTON == type) && (nullptr == actionFunc) && (-1 < keyCode));
+ }
+
+
+ /// @brief Quickly change (or set) the action function
+ void setAction(int32_t (*action_)(tgt_T* target, int32_t val))
+ {
+ actionFunc = action_;
+ }
+
+
+private:
+
+ /* ----------------------------------------------
+ * --- Private methods and external functions ---
+ * ----------------------------------------------
+ */
+
+ int32_t (*actionFunc )(tgt_T* target, int32_t val) = nullptr;
+ bool (*displayFunc)(tgt_T* target, int32_t x, int32_t y) = nullptr;
+
+ /// @brief templated ET_VALUE activation handling
+ void activateValue(int32_t val)
+ {
+ // A few short-cuts that make reading the following a lot easier:
+ tgt_T t_val = static_cast<tgt_T>(val * decinc);
+ tgt_T t_max = static_cast<tgt_T>(maxVal);
+ tgt_T t_min = static_cast<tgt_T>(minVal);
+
+ if (format) {
+ // If a format is set, this is just a simple adding/substracting
+ // of decinc with a check against min/max value afterwards
+ // val == 0 is simply ignored.
+ tgt_T oldTgt = *target;
+ if (val > 0) {
+ if (*target <= (t_max - t_val) )
+ *target += t_val;
+ else
+ *target = t_max;
+ } else if (val < 0) {
+ if (*target >= (t_min - t_val) )
+ *target += t_val;
+ else
+ *target = t_min;
+ }
+ // If a maximum or minimum is reached, clear the decoration
+ if ( (oldTgt != *target)
+ && ( (*target == t_min)
+ || (*target == t_max) ) ) {
+ clear_display(true);
+ display(true);
+ }
+ } else if (texts) {
+ // Otherwise entryNum is used and checked against texts[]
+ if (val > 0) {
+ if( texts[entryNum + 1]
+ && (*target < t_max ) )
+ ++entryNum;
+ else
+ entryNum = 0;
+ } else if (val < 0) {
+ if( entryNum > 0
+ && (*target > static_cast<tgt_T>(0) ) )
+ --entryNum;
+ else
+ entryNum = t_max;
+ }
+ *target = static_cast<tgt_T>(entryNum);
+ }
+ }
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ opt_T decinc = (opt_T)1; //!< Increment / decrement for ET_VALUE
+ opt_T maxVal = (opt_T)0; //!< Maximum value for ET_VALUE
+ opt_T minVal = (opt_T)0; //!< Minimum value for ET_VALUE
+ tgt_T* target = nullptr; //!< Target to handle
+};
+
+
+#endif // ATANKS_SRC_OPTIONITEM_H_INCLUDED
+
diff --git a/src/optionitembase.cpp b/src/optionitembase.cpp
new file mode 100644
index 0000000..f0ccada
--- /dev/null
+++ b/src/optionitembase.cpp
@@ -0,0 +1,768 @@
+#include "button.h"
+#include "menu.h"
+#include "optionitembase.h"
+#include "floattext.h"
+
+static const char menu_hint_text[] = "-> ";
+static uint32_t menu_hint_text_len = 0; // Set by ctor when "font" is set
+static const char select_text[] = "* ";
+uint32_t select_text_len = 0; // Set by ctor when "font" is set
+
+// Note: Direct setting is not a good idea, font might be anything when
+// the static initialization is done.
+static int32_t CURSOR_FLIP_TIME = 25; // Delays cursor flipping
+
+
+/** @brief default and only constructor
+ * @param[in] type_ Type of the option.
+ * @param[in] title_ The title of the option to display.
+ * @param[in] titleIdx_ Index value of the submitted title. -1 means @a title_ is fixed.
+ * @param[in] text_ Array of texts to display.
+ * @param[in] color_ The color to use for the text, mainly useful for OT_TOGGLE.
+ * @param[in] class_ Index value of the submitted texts or TC_NONE if no texts are needed.
+ * @param[in] format_ Format string to use when displaying the ET_TEXT target.
+ * @param[in] top_ Top position of the display area.
+ * @param[in] left_ Left position of the display area.
+ * @param[in] width_ Width of the display area.
+ * @param[in] height_ Height of the display area.
+ * @param[in] padding_ Padding of the title and buttons to the display area.
+ * @param[in] show_size_ Sets the size of the show color box. (ET_COLOR only)
+**/
+OptionItemBase::OptionItemBase( eEntryType type_,
+ const char* title_,
+ int32_t titleIdx_,
+ const char** text_,
+ int32_t color_,
+ eTextClass class_,
+ const char* format_,
+ int32_t top_, int32_t left_,
+ int32_t width_, int32_t height_,
+ int32_t padding_, int32_t show_size_) :
+ color(color_),
+ format(format_),
+ height(height_),
+ left(left_),
+ padding(padding_),
+ show_size(show_size_),
+ textClass(class_),
+ texts(text_),
+ title(title_),
+ titleIdx(titleIdx_),
+ top(top_),
+ type(type_),
+ width(width_)
+{
+ // Assert the title as the most basic value
+ assert (title && "Title not set");
+ assert ((texts || (TC_NONE == textClass)) && "text_ and class_ do not fit!");
+ titleLen = text_length(font, title);
+
+ // Set static globals
+ if (0 == menu_hint_text_len)
+ menu_hint_text_len = text_length(font, menu_hint_text);
+ if (0 == select_text_len)
+ select_text_len = text_length(font, select_text);
+}
+
+
+/// @brief simple default destructor
+OptionItemBase::~OptionItemBase()
+{
+ this->remove();
+ if (this->button)
+ delete this->button;
+}
+
+
+/* =====================================
+ * === Public method implementations ===
+ * =====================================
+ */
+
+/** @brief clear the current display
+ *
+ * This method erases the current display. It should be called
+ * before changing the displayed value or the display parameters
+ * like coordinates or dimension.
+ *
+ * The methods that change those parameters or draw the display call
+ * clear_display() automatically. Please remember to call it in external
+ * display functions.
+ *
+ * Note: As ET_TOGGLE have their title being the text to display, setting
+ * @a update_full will only change the state of the decorated state and not
+ * clear any additional space.
+ */
+void OptionItemBase::clear_display(bool update_full)
+{
+ if (drawn || (update_full && decorated)) {
+ int32_t xLeft = left;
+ int32_t xWidth = drawn ? width : 0;
+ int32_t xTop = top;
+ int32_t xHeight = height;
+
+ // clear decoration?
+ if (update_full) {
+
+ // First: Title if displayed left of the display area
+ if ((ET_TOGGLE != type) && (ET_BUTTON != type)) {
+ xLeft -= titleLen + padding;
+ xWidth += titleLen + padding;
+ }
+
+ // Second: Selection text if needed
+ if (selected) {
+ if ( (ET_BUTTON == type) && this->button ) {
+ // The button box must be erased
+ xLeft -= 3;
+ xTop -= 3;
+ xWidth += 6;
+ xHeight += 6;
+ } else {
+ xLeft -= select_text_len + padding;
+ xWidth += select_text_len + padding;
+ }
+ }
+
+ // Third: The ET_VALUE wheel buttons
+ if (ET_VALUE == type)
+ xWidth += 2 * padding + 20;
+ }
+
+ rectfill (global.canvas, xLeft, xTop, xLeft + xWidth, xTop + xHeight,
+ makecol (0,79,0));
+ global.make_update (xLeft, xTop, xWidth, xHeight);
+ drawn = false;
+ if (update_full)
+ decorated = false;
+ }
+}
+
+
+/// @brief Toggle cursor_on if this is a selected ET_TEXT
+void OptionItemBase::cursor_flip()
+{
+ if ((ET_TEXT == type) && selected && !read_only) {
+ if (--curs_clk < 1) {
+ curs_clk = CURSOR_FLIP_TIME;
+ cursor_on = !cursor_on;
+ clear_display(false);
+ }
+ }
+}
+
+
+/// @brief get width and height at once
+/// Note: Special circumstances like padding, menu indication and
+/// ET_TOGGLE extra sizes are added.
+void OptionItemBase::getDimension (int32_t &tgt_width, int32_t &tgt_height)
+{
+ tgt_width = width + padding;
+ tgt_height = height + padding + 2;
+
+ if (ET_COLOR == type)
+ tgt_width += padding + show_size;
+ if (ET_MENU == type)
+ tgt_width += menu_hint_text_len;
+ if (ET_TOGGLE == type) {
+ tgt_width += 4;
+ tgt_height += 2;
+ }
+}
+
+/// @brief return currently set key code
+int32_t OptionItemBase::getKeyCode()
+{
+ return keyCode;
+}
+
+
+/// @brief return the value of the next pointer
+OptionItemBase* OptionItemBase::getNext()
+{
+ return next;
+}
+
+
+/// @brief return the value of the prev pointer
+OptionItemBase* OptionItemBase::getPrev()
+{
+ return prev;
+}
+
+
+/// @brief return text class as index value
+uint32_t OptionItemBase::getTextClass()
+{
+ return static_cast<uint32_t>(textClass);
+}
+
+
+/// @brief return the index value of the displayed title
+uint32_t OptionItemBase::getTitleIdx()
+{
+ return titleIdx;
+}
+
+
+/// @brief return the eEntryType of the entry
+eEntryType OptionItemBase::getType()
+{
+ return type;
+}
+
+
+/** @brief Insert option after @a new_prev
+ *
+ * This is a standard insert into a doubly linked list.
+ *
+ * @param[in,out] new_prev (Optional) pointer to the option that becomes the new prev.
+ */
+void OptionItemBase::insert_after(OptionItemBase* new_prev)
+{
+ if (prev || next)
+ this->remove();
+
+ prev = new_prev;
+ if (prev) {
+ next = prev->next;
+ prev->next = this;
+ }
+
+ if (next)
+ next->prev = this;
+}
+
+
+/** @brief Insert option before @a new_next
+ *
+ * This is a standard insert into a doubly linked list.
+ *
+ * @param[in,out] new_next (Optional) pointer to the option that becomes the new next.
+ */
+void OptionItemBase::insert_before(OptionItemBase* new_next)
+{
+ if (prev || next)
+ this->remove();
+
+ next = new_next;
+ if (next) {
+ prev = next->prev;
+ next->prev = this;
+ }
+
+ if (prev)
+ prev->next = this;
+}
+
+
+/** @brief return true if @a x and @a y are in this options clickable area
+ *
+ * This method returns true if @a x and @a y are with the clickable area
+ * of this option. This means the display area and optional wheel buttons
+ * if this is an ET_VALUE type.
+ *
+ * If this option is an ET_BUTTON and has a key code assigned, or if this is
+ * an ET_VALUE and the wheel buttons are hit, @a ret is set to the appropriate
+ * value.
+ *
+ * @param[in] x X coordinate to test.
+ * @param[in] y Y coordinate to test.
+ * @param[out] ret Value to set to an assigned key code or -1/+1 for ET_VALUE inc/dec click.
+ * @return true if this option is hit, false otherwise.
+**/
+bool OptionItemBase::is_click_in(int32_t x, int32_t y, int32_t &ret)
+{
+ int32_t xLeft = left + 1;
+ int32_t xTop = top + 1;
+ int32_t xRight = xLeft + width - 2;
+ int32_t xBottom = xTop + height - 2;
+ bool result = false;
+
+ // reset ret
+ ret = 0;
+
+ // Note: No need to check anything if y is somewhere else
+ if ( (y >= xTop ) && (y <= xBottom) ) {
+ bool hasWheelresult = false;
+
+ // Check direct display area:
+ if ( (x >= xLeft) && (x <= xRight ) )
+ result = true;
+
+ // If this is an ET_VALUE, check wheel buttons
+ if (!result && (ET_VALUE == type)) {
+ int32_t up_left = left + width + padding;
+ int32_t up_right = up_left + 10;
+ int32_t dn_left = up_right + padding;
+ int32_t dn_right = dn_left + 10;
+
+ // Left "DOWN" button
+ if ( (x >= up_left) && (x <= up_right) ) {
+ result = true;
+ ret = -1;
+ hasWheelresult = true;
+ }
+
+ // Right "UP" button
+ else if ( ( x >= dn_left) && (x <= dn_right) ) {
+ result = true;
+ ret = 1;
+ hasWheelresult = true;
+ }
+ } // End of checking ET_VALUE wheel buttons
+
+ // Is there a return code to send?
+ if (result && !hasWheelresult)
+ // simply activate it
+ ret = KEY_ENTER;
+
+ // If a result is found, this must be updated:
+ if (result && (ET_COLOR != type))
+ this->clear_display(false);
+ } // End of y in range
+
+ return result;
+}
+
+
+/// @brief returns true if this entry is selected
+bool OptionItemBase::is_selected()
+{
+ return selected;
+}
+
+
+/** @brief move the display area
+ *
+ * This method clears the current display and then changes the position of the
+ * display area of this option.
+ *
+ * @param[in] new_left The new left position of the display area.
+ * @param[in] new_top The new top position of the display area.
+ * @param[in] do_update if set to true, the current display is cleared.
+ */
+void OptionItemBase::move(int32_t new_left, int32_t new_top, bool do_update)
+{
+ if ((new_left != left) || (new_top != top)) {
+ if (do_update)
+ clear_display(true);
+ left = new_left;
+ top = new_top;
+ }
+}
+
+
+/// @brief return true if this option needs set texts
+bool OptionItemBase::needs_text()
+{
+ return (textClass < TC_TEXTCLASS_COUNT);
+}
+
+
+/** @brief remove this option from the list.
+ *
+ * This is a standard remove from a doubly linked list.
+ */
+void OptionItemBase::remove()
+{
+ if (next)
+ next->prev = prev;
+ if (prev)
+ prev->next = next;
+ prev = nullptr;
+ next = nullptr;
+}
+
+
+/** @brief resize the display area
+ *
+ * This method clears the current display and then changes the dimensions of
+ * the display area of this option.
+ *
+ * @param[in] new_width The new width of the display area.
+ * @param[in] new_height The new height of the display area.
+ */
+void OptionItemBase::resize(int32_t new_width, int32_t new_height)
+{
+ if ((new_width != width) || (new_height != height)) {
+ clear_display(true);
+ width = new_width;
+ height = new_height;
+ }
+}
+
+
+/// @brief selects this entry and triggers a redraw
+void OptionItemBase::select()
+{
+ if (!selected) {
+ selected = true;
+ clear_display(true);
+ }
+}
+
+
+/** @brief Change the padding around the display area
+ *
+ * This method clears the current display and then changes the padding around
+ * the display area of this option.
+ *
+ * @param[in] new_padding The new padding value.
+ */
+void OptionItemBase::setPadding(int32_t new_padding)
+{
+ if (new_padding != padding) {
+ clear_display(true);
+ padding = new_padding;
+ }
+}
+
+
+/** @brief set a new title
+ *
+ * This method clears the current display and then changes the title. Use this
+ * to switch languages.
+ *
+ * @param[in] new_title Pointer to the new title to display
+ */
+void OptionItemBase::setTitle(const char* new_title)
+{
+ if (new_title != title) {
+ clear_display(true);
+ title = new_title;
+ titleLen = text_length(font, title);
+ if ((ET_BUTTON == type) && button)
+ button->setText(title);
+ }
+}
+
+
+/** @brief set a new text class
+ *
+ * The only reaal purpose is to be able to add text-less
+ * options in templated add methods in the Menu class,
+ * where the OptionClassText is not available.
+ *
+ * Call this then from the compilation unit.
+**/
+void OptionItemBase::setTextClass (eTextClass new_class)
+{
+ textClass = new_class;
+}
+
+
+/** @brief set new texts array
+ *
+ * This method clears the current display without decorations and changes the
+ * used texts array. Use this to switch languages.
+ */
+void OptionItemBase::setTexts(const char** new_texts)
+{
+ if (new_texts != texts) {
+ clear_display(false);
+ texts = new_texts;
+ }
+}
+
+
+/// @brief unselects this entry and triggers a redraw
+void OptionItemBase::unselect()
+{
+ if (selected) {
+ clear_display(true);
+ selected = false;
+ cursor_on = false;
+ curs_clk = CURSOR_FLIP_TIME;
+ }
+}
+
+
+/* ========================================
+ * === Protected method implementations ===
+ * ========================================
+ */
+
+
+/** @brief return the outcome of a menu activation
+ * @param[in] target pointer to the menu
+ * @return Result of Menu::operator()
+**/
+int32_t OptionItemBase::activateMenu(Menu* target)
+{
+ if (target)
+ return target->operator()();
+ return 0;
+}
+
+
+/** @brief Add the result of key press @a k to @a target.
+ *
+ * Please make sure that @a has at least one byte free space excluding
+ * null character termination!
+ *
+ * @param[out] target The char array (or string) to receive the result.
+ * @param[in] k Allegro 4 raw key code, or the allegro 5 unichar field
+ */
+void OptionItemBase::activateText(char* target, int32_t k)
+{
+ if (target && (k > 0) ) {
+ int32_t oldTextLen = strlen(target);
+ char chr = static_cast<char>(k & 0xff);
+ textLen = oldTextLen;
+
+ if ( ((0x08 == chr) || (0x7f == chr)) && textLen )
+ target[--textLen] = 0x0;
+ else if (isprint(chr))
+ target[textLen++] = chr;
+
+ if (oldTextLen != textLen)
+ clear_display(false);
+ }
+}
+
+
+/** @brief Switch the state of @a target
+ *
+ * @param[in,out] target The target value to switch
+ */
+void OptionItemBase::activateToggle(bool* target)
+{
+ if (target) {
+ *target = !(*target);
+ clear_display(false);
+ }
+}
+
+
+/** @brief display the set button
+**/
+void OptionItemBase::displayButton()
+{
+ if (!drawn) {
+ if (this->button)
+ button->draw();
+ drawn = true;
+ }
+}
+
+
+/** @brief Display the options decoration (title plus button(s))
+ *
+ * Note: If this is ET_TOGGLE, the method will only change the decorated
+ * state to true, as the title for this type is the displayed text.
+ *
+ * @param[in] show_color Used only for the ET_COLOR show box.
+ */
+void OptionItemBase::displayDeco(int32_t show_color)
+{
+ if (!decorated) {
+ int32_t xLeft = left;
+ int32_t xWidth = 0;
+ int32_t xTop = top;
+ int32_t xHeight = height;
+ int32_t text_top = top + (height / 2) - (env.fontHeight / 2);
+ int32_t deco_top = text_top + 4;
+
+ // First: Title text
+ if (title
+ && (ET_BUTTON != type)
+ && (ET_MENU != type)
+ && (ET_TOGGLE != type)) {
+ int32_t tColor = (ET_COLOR == type) || textOnly ? BLACK : color;
+
+ xLeft -= titleLen + padding;
+ xWidth = titleLen + padding;
+
+ // Add a nice shadow if wanted
+ if (env.shadowedText)
+ textout_ex (global.canvas, font, title, xLeft + 1, text_top + 1,
+ GetShadeColor(tColor, true, PINK), -1);
+
+ textout_ex (global.canvas, font, title, xLeft, text_top, tColor, -1);
+ }
+
+ // ET_MENU has an additional "-> " displayed next to it
+ if ( (ET_MENU == type) && show_menu) {
+ xLeft -= menu_hint_text_len + padding;
+ xWidth += menu_hint_text_len + padding;
+ textout_ex (global.canvas, font, menu_hint_text, xLeft, text_top, BLACK, -1);
+ }
+
+ // Second: Selection text if needed
+ if (selected) {
+ if ( (ET_BUTTON == type) && this->button ) {
+ // Buttons get a box drawn around them
+ xTop -= 3;
+ xLeft -= 3;
+ xWidth += 6 + width; // spans over
+ xHeight += 6;
+ rect (global.canvas, xLeft, xTop, xLeft + xWidth, xTop + xHeight, WHITE);
+ } else {
+ xLeft -= select_text_len + padding;
+ xWidth += select_text_len + padding;
+ textout_ex (global.canvas, font, select_text, xLeft, text_top, BLACK, -1);
+ }
+ }
+
+ // If an actual width could be determined, add an update
+ // for the text region:
+ if (xWidth > 0)
+ global.make_update (xLeft, xTop, xWidth, xHeight);
+
+ // Third: The ET_VALUE wheel buttons / ET_COLOR display box
+ if (ET_VALUE == type) {
+ BITMAP* arrow = env.misc[6];
+ int32_t dn_left = left + width + padding;
+ int32_t up_left = dn_left + padding + 10;
+
+ if (this->canGoDown())
+ draw_sprite_v_flip (global.canvas, arrow, dn_left, deco_top);
+ if (this->canGoUp())
+ draw_sprite (global.canvas, arrow, up_left, deco_top);
+
+ global.make_update (up_left, top, 10, height);
+ } else if (ET_COLOR == type) {
+ int32_t x = left + width + padding;
+
+ rect (global.canvas, x, deco_top, x + show_size, deco_top + show_size, BLACK);
+ rectfill (global.canvas,
+ x + 1, deco_top + 1,
+ x + show_size - 1, deco_top + show_size - 1,
+ show_color);
+ global.make_update (x, deco_top, show_size, show_size);
+ }
+
+ decorated = true;
+ }
+}
+
+
+/** @brief Display the @a target menu title as text.
+ *
+ * @param[in] target pointer to the menu to display
+ */
+void OptionItemBase::displayMenu(Menu* target)
+{
+ if (title && (titleIdx > -1))
+ this->displayText(title);
+ else if (target)
+ this->displayText(target->getTitle());
+ else
+ this->displayText("Oh no! No Menu Title!");
+}
+
+
+/** @brief Display the @a target as text.
+ *
+ * @param[in] target (optional) pointer to the text to display
+ */
+void OptionItemBase::displayText(char* target)
+{
+ this->displayText(static_cast<const char*>(target));
+}
+
+
+/** @brief Display the @a target as text.
+ *
+ * @param[in] target (optional) pointer to the text to display
+ */
+void OptionItemBase::displayText(const char* target)
+{
+ if (!drawn) {
+ if (!textOnly) {
+ rect (global.canvas, left, top, left + width, top + height, BLACK);
+ rectfill (global.canvas,
+ left + 1, top + 1,
+ left + width - 1, top + height - 1,
+ WHITE);
+ }
+
+ // First display the text
+ const char* txt_p = target;
+ int32_t txt_len = txt_p ? text_length(font, txt_p) : 0;
+
+ if (txt_p) {
+ int32_t len_available = width - 6;
+ int32_t text_top = top + (height / 2) - (env.fontHeight / 2);
+
+
+ // If this is a read/write selected ET_TEXT, it has a cursor being
+ // drawn, and thus less space to display the text:
+ if ((ET_TEXT == type) && selected && !read_only)
+ len_available -= text_length(font, "W");
+
+ // Scroll text until it fits.
+ while (txt_p && txt_p[0] && (txt_len > len_available))
+ txt_len = text_length(font, ++txt_p);
+
+ // Now print the result
+ if (txt_p && txt_p[0]) {
+
+ // With a shadow? But not in text field mode
+ if (env.shadowedText && textOnly)
+ textout_ex (global.canvas, font, txt_p, left + 4, text_top + 1,
+ GetShadeColor(color, true, PINK), -1);
+
+ textout_ex (global.canvas, font, txt_p, left + 3, text_top,
+ textOnly ? color : BLACK, -1);
+ }
+ }
+
+ // Then a cursor if turned on
+ if (cursor_on) {
+ int32_t offset = txt_len + left + 3;
+ rectfill (global.canvas, offset, top + 3, offset + 6, top + height - 3, BLACK);
+ }
+
+ global.make_update (left, top, width, height);
+ drawn = true;
+ }
+}
+
+
+/** @brief Display the @a target as text.
+ *
+ * @param[in] target (optional) pointer to the text to display
+ */
+void OptionItemBase::displayText(uint32_t* target)
+{
+ this->displayValue(target);
+}
+
+
+/** @brief Displays a toggle option in either black on white or vice versa
+ *
+ * Note: The title is actually the displayed text.
+ *
+ * @param[in] target (optional) pointer to the toggle option.
+ * to be set by OptionItemPlayer only.
+ */
+void OptionItemBase::displayToggle(bool* target)
+{
+ if (!drawn) {
+ int32_t fg_color = color;
+ int32_t bg_color = BLACK;
+ int32_t sh_color = DARK_GREY;
+ int32_t x_radius = (titleLen / 1.8) + padding + 5;
+ int32_t y_radius = height / 2;
+ int32_t xLeft = left + (width / 2);
+ int32_t xTop = top + (height / 2);
+ int32_t txtLeft = xLeft - (titleLen / 2) + 1;
+
+ // Swap if activated
+ if (target && *target) {
+ fg_color = BLACK;
+ bg_color = color;
+ sh_color = GREY;
+ }
+
+ // To make this nicer looking, a shade color is generated for an outline:
+ ellipsefill (global.canvas, xLeft, xTop, x_radius, y_radius, bg_color);
+ ellipse (global.canvas, xLeft, xTop, x_radius, y_radius, sh_color);
+ textout_ex(global.canvas, font, title ? title : "", txtLeft , top + 4, fg_color, -1);
+
+ global.make_update (left, top, width, height);
+ drawn = true;
+ }
+}
diff --git a/src/optionitembase.h b/src/optionitembase.h
new file mode 100644
index 0000000..f7a76eb
--- /dev/null
+++ b/src/optionitembase.h
@@ -0,0 +1,207 @@
+#pragma once
+#ifndef ATANKS_SRC_OPTIONITEMBASE_H_INCLUDED
+#define ATANKS_SRC_OPTIONITEMBASE_H_INCLUDED
+
+/*
+ * atanks - obliterate each other with oversize weapons
+ *
+ * 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.
+ *
+ */
+
+#include "environment.h"
+#include "globaldata.h"
+#include "optiontypes.h"
+
+#include <cassert>
+#include <string>
+
+
+/** @file optionitembase.h
+ * @brief declaration of the option item base class
+**/
+
+extern uint32_t select_text_len; // needed for the item distribution
+
+// Forward BUTTON if it isn't known, yet:
+#ifndef BUTTON_HEADER_
+class BUTTON;
+#endif // BUTTON_HEADER_
+
+// Forward Menu if it isn't known, yet:
+#ifndef MENU_CLASS_DECLARES
+class Menu;
+#endif // MENU_CLASS_DECLARES
+
+
+/** @class OptionItemBase
+ * @brief Common base class for all option items.
+ *
+ * This base class holds all common values of the option item represented and
+ * is used to order option items as a doubly linked list.
+ *
+ * The class has several public methods to change the inner state, like moving
+ * or resizing the display area, changing the title, switching the text content
+ * array and so on.
+**/
+class OptionItemBase
+{
+public:
+
+ /* ------------------------------
+ * --- Public ctors and dtors ---
+ * ------------------------------
+ */
+
+ explicit OptionItemBase(
+ eEntryType type_,
+ const char* title_,
+ int32_t titleIdx_,
+ const char** text_,
+ int32_t color_,
+ eTextClass class_,
+ const char* format_,
+ int32_t top_, int32_t left_, int32_t width_, int32_t height_,
+ int32_t padding_, int32_t show_size_);
+
+ virtual ~OptionItemBase();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ void clear_display(bool update_full);
+ void cursor_flip ();
+ void getDimension (int32_t &tgt_width, int32_t &tgt_height);
+ int32_t getKeyCode ();
+ OptionItemBase* getNext ();
+ OptionItemBase* getPrev ();
+ uint32_t getTextClass ();
+ uint32_t getTitleIdx ();
+ eEntryType getType ();
+ void insert_after (OptionItemBase* new_prev);
+ void insert_before(OptionItemBase* new_next);
+ bool is_click_in (int32_t x, int32_t y, int32_t &ret);
+ bool is_selected ();
+ void move (int32_t new_left, int32_t new_top, bool do_update);
+ bool needs_text ();
+ void remove ();
+ void resize (int32_t new_width, int32_t new_height);
+ void select ();
+ void setPadding (int32_t new_padding);
+ void setTitle (const char* new_title);
+ void setTextClass (eTextClass new_class);
+ void setTexts (const char** new_texts);
+ void unselect ();
+
+ // virtuals to be implemented by the deriving template
+ virtual int32_t activate (int32_t val, int32_t x, int32_t y, int32_t k) =0;
+ virtual bool canGoDown () =0;
+ virtual bool canGoUp () =0;
+ virtual void display (bool show_full) =0;
+ virtual bool isExitButton() =0;
+
+
+protected:
+
+ /* -------------------------
+ * --- Protected methods ---
+ * -------------------------
+ */
+ int32_t activateMenu(Menu* target);
+ void activateText(char* target, int32_t k);
+ void activateToggle(bool* target);
+ void displayButton();
+ void displayDeco(int32_t show_color = BLACK);
+ void displayMenu(Menu* target);
+ void displayText(char* target);
+ void displayText(const char* target);
+ void displayText(uint32_t* target);
+ void displayToggle(bool* target);
+
+ /// @brief As OT_VALUE might be anything, it is templated on method scale.
+ template<typename tgt_T>
+ void displayValue(tgt_T* target)
+ {
+ if (format) {
+ char txt_buf[256] = { 0x0 };
+ snprintf(txt_buf, 255, format, *target);
+ textLen = strlen(txt_buf);
+ this->displayText(txt_buf);
+ } else if (texts && texts[entryNum]) {
+ textLen = strlen(texts[entryNum]);
+ this->displayText(texts[entryNum]);
+ }
+ }
+
+ // Note: The following templates only define a path for the dispatcher
+ // when checking for which method to call. If a calling path, and thus any
+ // option configuration, is invalid, they will print an error message and
+ // terminate. These templates are only instantiated by the compiler for
+ // syntax and call path checking, and thrown away being unused later.
+ // This looks like a waste, but makes the dispatching a lot less complex
+ // and more secure.
+#define EMERGENCY_OUT fprintf(stderr, \
+ "%s:%d [%s] : Illegal target type, template called!\n", \
+ __FILE__, __LINE__, __FUNCTION__); \
+ std::terminate();
+ template<typename T> int32_t activateMenu(T*) { EMERGENCY_OUT }
+ template<typename T> void activateText(T*, int) { EMERGENCY_OUT }
+ template<typename T> void activateToggle(T*) { EMERGENCY_OUT }
+ template<typename T> void displayMenu(T*) { EMERGENCY_OUT }
+ template<typename T> void displayText(T*) { EMERGENCY_OUT }
+ template<typename T> void displayToggle(T*) { EMERGENCY_OUT }
+#undef EMERGENCY_OUT
+
+
+ /* -------------------------
+ * --- Protected members ---
+ * -------------------------
+ */
+
+ BUTTON* button = nullptr; //!< The button used by ET_BUTTON.
+ int32_t color = BLACK; //!< The color to use for the text, mainly useful for OT_TOGGLE.
+ bool cursor_on = false; //!< selected ET_TEXT elements feature a cursor.
+ int32_t curs_clk = 0; //!< Only react on every CURSOR_FLIP_TIME call.
+ bool decorated = false; //!< Set to true by displayDeco() and false by clear_display(true)
+ bool drawn = false; //!< Set to true by display methods, and false by clear_display().
+ int32_t entryNum = 0; //!< Store the currently displayed text index with OT_VALUE options.
+ const char* format = nullptr; //!< Format string to use with OT_VALUE
+ int32_t height = 0; //!< Height of the display area.
+ int32_t keyCode = 0; //!< Key Code returned when clicking an ET_BUTTON.
+ int32_t left = 0; //!< Left x position of the display area.
+ OptionItemBase* next = nullptr; //!< Next option entry in a doubly linked list.
+ int32_t padding = 2; //!< Padding of the title and possible buttons to the display area.
+ OptionItemBase* prev = nullptr; //!< Previous option entry in a doubly linked list.
+ bool read_only = true; //!< Whether ET_TEXT reacts on clicks and keys or not.
+ bool selected = false; //!< Whether this entry is selected or not.
+ bool show_menu = true; //!< If set to true, the sub menu indicator is shown.
+ int32_t show_size = 0; //!< Size of the color box ET_COLOR displays the current color in
+ int32_t textLen = 0; //!< Store the current size of OT_TEXT content.
+ eTextClass textClass = TC_NONE; //!< Noted for language switch.
+ bool textOnly = false; //!< If set to true, displayText() draws no box.
+ const char** texts = nullptr; //!< Text array to use for OT_VALUE
+ const char* title = nullptr; //!< Title to display, mandatory
+ int32_t titleIdx = 0; //!< Noted for language switch. -1 means the title is fixed.
+ int32_t titleLen = 0; //!< Length of the title in pixels
+ int32_t top = 0; //!< Top y position of the display area.
+ eEntryType type = ET_NONE; //!< Type of the option, mandatory
+ int32_t width = 0; //!< Width of the display area.
+};
+
+#endif // ATANKS_SRC_OPTIONITEMBASE_H_INCLUDED
+
diff --git a/src/optionitemcolour.cpp b/src/optionitemcolour.cpp
new file mode 100644
index 0000000..00d1255
--- /dev/null
+++ b/src/optionitemcolour.cpp
@@ -0,0 +1,203 @@
+#include "optionitemcolour.h"
+
+
+/** @brief Default constructor.
+ *
+ * The target is the color instance to handle.
+ *
+ * @param[in,out] player_ Pointer to the PLAYER instance to handle.
+ * @param[in,out] action_ Pointer to the action function handling the button click.
+ * @param[in] title_ The title of the option to display.
+ * @param[in] titleIdx_ Index value of the submitted title. -1 means @a title_ is fixed.
+ * @param[in] top_ Top position of the display area.
+ * @param[in] left_ Left position of the display area.
+ * @param[in] width_ Width of the display area.
+ * @param[in] height_ Height of the display area.
+ * @param[in] padding_ Padding of the title and buttons to the display area.
+**/
+OptionItemColour::OptionItemColour(
+ int32_t* color_,
+ const char* title_,
+ int32_t titleIdx_,
+ int32_t top_, int32_t left_, int32_t width_, int32_t height_,
+ int32_t padding_, int32_t show_size_) :
+ OptionItemBase(ET_COLOR,
+ title_,
+ titleIdx_,
+ nullptr,
+ color_ ? *color_: WHITE, // WHITE to indicate an error.
+ TC_NONE, nullptr,
+ top_, left_, width_, height_, padding_, show_size_)
+{
+ // For ET_COLOR, only the color_ is needed
+ assert (color_ && "ERROR: color_ must be set");
+ tgt_color = color_;
+
+ // Create the rainbow box:
+ tgt_bitmap = create_bitmap(width, height);
+
+ assert((width <= tgt_bitmap->w) && "Width too small");
+ assert((height <= tgt_bitmap->h) && "Height too small");
+
+ solid_mode();
+ clear_to_color (tgt_bitmap, BLACK);
+ int32_t r, g, b;
+
+ for (int32_t x = 0.; x <= width; ++x) {
+ for (int32_t y = 0.; y <= height; ++y) {
+ double h = static_cast<double>(x) / static_cast<double>(width) * 360.;
+ double p = static_cast<double>(y) / static_cast<double>(height);
+ double v = 1.;
+ double s = p * 2.;
+
+ if (p > 0.5) {
+ v = 1. - ((p - 0.5) * 2.);
+ s = 1.;
+ }
+
+ assert ( (h >= 0.) && (h <= 360.) && "h out of range");
+ assert ( (s >= 0.) && (s <= 1.) && "s out of range");
+ assert ( (v >= 0.) && (v <= 1.) && "v out of range");
+
+ hsv_to_rgb(h, s, v, &r, &g, &b);
+
+ putpixel(tgt_bitmap, x + 1, y + 1, makecol(r, g, b));
+ }
+ }
+}
+
+
+/// @brief default dtor only setting nullptr values. No further action needed.
+OptionItemColour::~OptionItemColour()
+{
+ destroy_bitmap(tgt_bitmap);
+ tgt_bitmap = nullptr;
+ tgt_color = nullptr;
+
+}
+
+
+/* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+/** @brief pick a new color from @a x / @a y and store it in @a target
+ *
+ * @param[in] x The x coordinate of the pixel to pick the colour from.
+ * @param[in] y The y coordinate of the pixel to pick the colour from.
+ */
+int32_t OptionItemColour::activate(int32_t, int32_t x, int32_t y, int32_t)
+{
+ int32_t pick_x = x - left;
+ int32_t pick_y = y - top;
+ if ( (pick_x >= 1)
+ && (pick_y >= 1)
+ && (pick_x <= (width - 1))
+ && (pick_y <= (height - 1)) ) {
+
+ // Note down where to draw the cross
+ act_x = pick_x;
+ act_y = pick_y;
+
+ // To show both, do a redraw
+ clear_display(false);
+ display(false);
+
+ // Get fresh pixel
+ *tgt_color = getpixel(tgt_bitmap, pick_x, pick_y);
+
+ // Draw helper cross:
+ displayCross();
+
+ // Redraw decoration:
+ this->displayDeco(*tgt_color);
+ } else {
+ // No cross
+ act_x = 0;
+ act_y = 0;
+ }
+
+ return 0;
+}
+
+
+/// @brief returns always true
+bool OptionItemColour::canGoDown()
+{
+ return true;
+}
+
+
+/// @brief returns always true
+bool OptionItemColour::canGoUp()
+{
+ return true;
+}
+
+
+/** @brief Display a color chooser for @a target and the display box.
+ *
+ * The display area is where to draw the "rainbow box". The box in which
+ * to draw the currently selected color is a "deco", but not drawn by
+ * display_deco(), because it would not have access to the target.
+ *
+ * @param[in] show_full decorations are drawn if this is set to true
+ */
+void OptionItemColour::display(bool show_full)
+{
+ if (!drawn) {
+
+ // Here is the chooser, the display box is "deco"
+ blit(tgt_bitmap, global.canvas, 0, 0, left, top, width, height);
+ rect (global.canvas, left, top, left + width, top + height, BLACK);
+
+ // Draw helper cross
+ displayCross();
+
+ drawn = true;
+ }
+
+ if (show_full)
+ this->displayDeco(*tgt_color);
+}
+
+
+/// @brief Draw the selector cross
+void OptionItemColour::displayCross()
+{
+ if ( (act_x > 0) && (act_y > 0) ) {
+ // To make the color picking easier, draw a cross
+ // to show where the user clicked in
+ int32_t right = left + width;
+ int32_t bottom = top + height;
+ int32_t x = left + act_x;
+ int32_t y = top + act_y;
+ int32_t xl = x - 5;
+ int32_t xr = x + 5;
+ int32_t yt = y - 5;
+ int32_t yb = y + 5;
+
+ // Do not overdraw:
+ if (xl <= left ) xl = x > left ? left + 1 : 0;
+ if (yt <= top ) yt = y > top ? top + 1 : 0;
+ if (xr >= right ) xr = x < right ? right - 1 : 0;
+ if (yb >= bottom) yb = y < bottom ? bottom - 1 : 0;
+
+ // If a coordinate is zero, the line is not drawn.
+ int32_t cross_col = makecol(255 - getr(*tgt_color),
+ 255 - getg(*tgt_color),
+ 255 - getb(*tgt_color) );
+ if (xl) hline (global.canvas, xl, y, x - 1, cross_col);
+ if (xr) hline (global.canvas, x + 1, y, xr, cross_col);
+ if (yt) vline (global.canvas, x, yt, y - 1, cross_col);
+ if (yb) vline (global.canvas, x, y + 1, yb, cross_col);
+ }
+}
+
+
+/// @brief return true, the action function must be able to return an exit code.
+bool OptionItemColour::isExitButton()
+{
+ return true;
+}
diff --git a/src/optionitemcolour.h b/src/optionitemcolour.h
new file mode 100644
index 0000000..890a956
--- /dev/null
+++ b/src/optionitemcolour.h
@@ -0,0 +1,94 @@
+#pragma once
+#ifndef ATANKS_SRC_OPTIONITEMCOLOUR_H_INCLUDED
+#define ATANKS_SRC_OPTIONITEMCOLOUR_H_INCLUDED
+
+/*
+ * atanks - obliterate each other with oversize weapons
+ *
+ * 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.
+ *
+ */
+
+#include "optionitembase.h"
+
+/** @file optionitemcolour.h
+ * @brief declaration of the option entry class specialized on handling
+ * int32_t instances representing colours.
+**/
+
+
+/** @class OptionItemColour
+ * @brief abstract one option menu entry to handle an int32_t instance
+ *
+ * This class is a special version of the OptionItem template that can only
+ * handle int32_t instances representing colours.
+ *
+ * The the only entry type supported is the ET_COLOR.
+**/
+class OptionItemColour : public OptionItemBase
+{
+public:
+
+ /* -------------------------------------------
+ * --- Public constructors and destructors ---
+ * -------------------------------------------
+ */
+
+ explicit
+ OptionItemColour(
+ int32_t* color_,
+ const char* title_,
+ int32_t titleIdx_,
+ int32_t top_, int32_t left_, int32_t width_, int32_t height_,
+ int32_t padding_, int32_t show_size_);
+ virtual ~OptionItemColour();
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ virtual int32_t activate (int32_t, int32_t, int32_t, int32_t);
+ virtual bool canGoDown ();
+ virtual bool canGoUp ();
+ virtual void display (bool show_full);
+ virtual bool isExitButton();
+ void setLanguage ();
+
+private:
+
+ /* ----------------------------------------------
+ * --- Private methods and external functions ---
+ * ----------------------------------------------
+ */
+
+
+ void displayCross();
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ int32_t act_x = 0;
+ int32_t act_y = 0;
+ BITMAP* tgt_bitmap = nullptr; //!< Pre-drawn Rainbow box
+ int32_t* tgt_color = nullptr; //!< Colour instance to handle
+};
+
+
+#endif // ATANKS_SRC_OPTIONITEMCOLOUR_H_INCLUDED
+
diff --git a/src/optionitemmenu.cpp b/src/optionitemmenu.cpp
new file mode 100644
index 0000000..efb5137
--- /dev/null
+++ b/src/optionitemmenu.cpp
@@ -0,0 +1,123 @@
+#include "optionitemmenu.h"
+#include "menu.h"
+#include "clock.h"
+
+/** @brief Default constructor.
+ *
+ * The target is the Menu instance to handle.
+ *
+ * Activation is a simple call to the Menus operator(), its return value
+ * is then returned without further ado.
+ *
+ * @param[in,out] menu_ Pointer to the Menu instance to handle.
+ * @param[in] title_ The title of the option to display.
+ * @param[in] titleIdx_ Index value of the submitted title. -1 means @a title_ is fixed.
+ * @param[in] color Regular display color of the title.
+ * @param[in] top_ Top position of the display area.
+ * @param[in] left_ Left position of the display area.
+ * @param[in] width_ Width of the display area.
+ * @param[in] height_ Height of the display area.
+ * @param[in] padding_ Padding of the title and buttons to the display area.
+**/
+OptionItemMenu::OptionItemMenu(
+ Menu* menu_,
+ const char* title_,
+ int32_t titleIdx_,
+ int32_t color_,
+ int32_t top_, int32_t left_, int32_t width_, int32_t height_,
+ int32_t padding_) :
+ OptionItemBase(ET_MENU,
+ title_ ? title_ : menu_ ? menu_->getTitle() : nullptr,
+ titleIdx_,
+ nullptr, color_,
+ TC_NONE, nullptr,
+ top_, left_, width_, height_, padding_, 0),
+ menu(menu_)
+{
+ // Both action or player must be set
+ assert ( menu_ && "A nullptr menu_ makes no sense...");
+ // As the title is displayed as text, textOnly must be set:
+ this->textOnly = true;
+}
+
+
+/// @brief default dtor only setting nullptr values. No further action needed.
+OptionItemMenu::~OptionItemMenu()
+{
+ menu = nullptr;
+}
+
+
+/* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+/** @brief activate the sub menu
+ *
+ * This calls operator() on the menu.
+ *
+ * Note: The parameters are defined by OptionItemBase but unused
+ * here.
+ *
+ * @return The return code of the sub menu.
+**/
+int32_t OptionItemMenu::activate(int32_t, int32_t, int32_t, int32_t)
+{
+ // Remove parent menu timer
+ WIN_CLOCK_REMOVE
+
+ int32_t result = (*menu)();
+
+ // Changes are displayed at once:
+ this->drawn = false;
+ this->display(false);
+
+ // Re-add parent menu timer
+ WIN_CLOCK_INIT
+
+ return result;
+}
+
+
+/// @brief returns always true
+bool OptionItemMenu::canGoDown()
+{
+ return true;
+}
+
+
+/// @brief returns always true
+bool OptionItemMenu::canGoUp()
+{
+ return true;
+}
+
+
+/** @brief display the sub menu title
+ *
+ * @param[in] show_full If set to true, title and buttons are redrawn.
+**/
+void OptionItemMenu::display(bool show_full)
+{
+ this->displayMenu(menu);
+
+ // Show decorations if wanted:
+ if (show_full)
+ this->displayDeco();
+}
+
+
+/// @brief return true, the menu must be able to return an exit code.
+bool OptionItemMenu::isExitButton()
+{
+ return true;
+}
+
+
+/// @brief simply calls setLanguage(false) on the target menu
+void OptionItemMenu::setLanguage()
+{
+ if (menu)
+ menu->setLanguage(false);
+}
diff --git a/src/optionitemmenu.h b/src/optionitemmenu.h
new file mode 100644
index 0000000..3d6e2d5
--- /dev/null
+++ b/src/optionitemmenu.h
@@ -0,0 +1,86 @@
+#pragma once
+#ifndef ATANKS_SRC_OPTIONITEMMENU_H_INCLUDED
+#define ATANKS_SRC_OPTIONITEMMENU_H_INCLUDED
+
+/*
+ * atanks - obliterate each other with oversize weapons
+ *
+ * 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.
+ *
+ */
+
+#include "optionitembase.h"
+
+/** @file optionitemmenu.h
+ * @brief declaration of the option entry class specialized on handling
+ * Menu instances
+**/
+
+
+/** @class OptionItemMenu
+ * @brief abstract one option menu entry to handle a Menu instance
+ *
+ * This class is a special version of the OptionItem template that can only
+ * handle Menu instances.
+ *
+ * The the only entry type supported is the ET_MENU.
+**/
+class OptionItemMenu : public OptionItemBase
+{
+public:
+
+ /* -------------------------------------------
+ * --- Public constructors and destructors ---
+ * -------------------------------------------
+ */
+
+ explicit
+ OptionItemMenu(Menu* menu_,
+ const char* title_,
+ int32_t titleIdx_,
+ int32_t color_,
+ int32_t top_, int32_t left_, int32_t width_, int32_t height_,
+ int32_t padding_);
+ virtual ~OptionItemMenu();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ virtual int32_t activate (int32_t, int32_t, int32_t, int32_t);
+ virtual bool canGoDown ();
+ virtual bool canGoUp ();
+ virtual void display (bool show_full);
+ virtual bool isExitButton();
+ void setLanguage ();
+
+private:
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ Menu* menu = nullptr; //!< Menu instance to handle
+};
+
+
+#endif // ATANKS_SRC_OPTIONITEMMENU_H_INCLUDED
+
+
+
diff --git a/src/optionitemplayer.cpp b/src/optionitemplayer.cpp
new file mode 100644
index 0000000..7bca9a2
--- /dev/null
+++ b/src/optionitemplayer.cpp
@@ -0,0 +1,231 @@
+#include "optionitemplayer.h"
+#include "player.h"
+#include "floattext.h"
+
+/** @brief Default constructor.
+ *
+ * The target is the player instance to handle. And the @a action_ function
+ * must do this if set.
+ *
+ * The target is a pointer pointer, so the class can be used for both
+ * editing and creating a player.
+ *
+ * If @a title_ is nullptr, the player name and color are used. The set
+ * title and BLACK otherwise.
+ *
+ * This class can be both, an ET_MENU (If an @a action_ function is set)
+ * or an ET_TOGGLE otherwise.
+ *
+ * @param[in,out] player_ Pointer to the PLAYER instance to handle.
+ * @param[in,out] action_ Pointer to the action function handling the button click.
+ * @param[in] title_ The title of the option to display.
+ * @param[in] titleIdx_ Index value of the submitted title. -1 means @a title_ is fixed.
+ * @param[in] top_ Top position of the display area.
+ * @param[in] left_ Left position of the display area.
+ * @param[in] width_ Width of the display area.
+ * @param[in] height_ Height of the display area.
+ * @param[in] padding_ Padding of the title and buttons to the display area.
+**/
+OptionItemPlayer::OptionItemPlayer(
+ PLAYER** player_,
+ int32_t (*action_)(PLAYER** player_, int32_t),
+ const char* title_,
+ int32_t titleIdx_,
+ int32_t top_, int32_t left_, int32_t width_, int32_t height_,
+ int32_t padding_) :
+ OptionItemBase(ET_NONE,
+ title_ ? title_
+ : (player_ && *player_)
+ ? (*player_)->getName()
+ : nullptr,
+ titleIdx_,
+ nullptr,
+ title_ ? SILVER
+ : (player_ && *player_)
+ ? (*player_)->color
+ : WHITE, // WHITE to indicate an error.
+ TC_NONE, nullptr,
+ top_, left_, width_, height_, padding_, 0)
+{
+ // For ET_TOGGLE, only the player_ is needed
+ assert (player_ && "ERROR: player_ must be set");
+
+ // For ET_MENU, action_ must be set, too, and for ET_TOGGLE *player_ must be set.
+ assert ( (action_ || (player_ && *player_ ) )
+ && "ERROR: If no action_ function is set, *player_ must be valid");
+ actionFunc = action_;
+ player = player_;
+
+ if (actionFunc) {
+ this->type = ET_MENU;
+
+ // If this is a regular player, no menu indicator is needed.
+ if (nullptr == title_)
+ this->show_menu = false;
+ } else if (player && *player)
+ this->type = ET_TOGGLE;
+}
+
+
+/// @brief default dtor only setting nullptr values. No further action needed.
+OptionItemPlayer::~OptionItemPlayer()
+{
+ actionFunc = nullptr;
+ player = nullptr;
+}
+
+
+/* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+/** @brief activate the player sub menu
+ *
+ * This calls the provided action function.
+ *
+ * Note: The parameters are defined by OptionItemBase but unused
+ * here.
+ *
+ * @return The return code of the action function.
+**/
+int32_t OptionItemPlayer::activate(int32_t, int32_t, int32_t, int32_t)
+{
+ int32_t result = -1;
+
+ if (ET_MENU == this->type)
+ result = actionFunc(player, 0);
+ else if (ET_TOGGLE == this->type)
+ this->activateToggle(&(*player)->selected);
+
+ // Changes are displayed at once:
+ if (ET_NONE != this->type)
+ this->clear_display(true);
+
+ return result;
+}
+
+
+/// @brief returns always true
+bool OptionItemPlayer::canGoDown()
+{
+ return true;
+}
+
+
+/// @brief returns always true
+bool OptionItemPlayer::canGoUp()
+{
+ return true;
+}
+
+
+/** @brief display the sub menu title
+ *
+ * @param[in] show_full If set to true, title and buttons are redrawn.
+**/
+void OptionItemPlayer::display(bool show_full)
+{
+ static const int32_t team_col_hi = 0xc0;
+ static const int32_t team_col_mi = 0x40;
+ static const int32_t team_col_lo = 0x18;
+ static const char* team_Indicator[4] = { "S", "N", "J", "?" };
+ static const int32_t team_color_bg[4] = {
+ makecol(team_col_mi, team_col_lo, team_col_lo),
+ makecol(team_col_lo, team_col_mi, team_col_lo),
+ makecol(team_col_lo, team_col_lo, team_col_mi),
+ makecol(team_col_mi, team_col_mi, team_col_mi)
+ };
+ static const int32_t team_color_fg[4] = {
+ makecol(team_col_hi, team_col_mi, team_col_mi),
+ makecol(team_col_mi, team_col_hi, team_col_mi),
+ makecol(team_col_mi, team_col_mi, team_col_hi),
+ makecol(team_col_hi, team_col_hi, team_col_hi)
+ };
+
+ if (!drawn) {
+ // Be sure to have the current name and color:
+ color = player && *player ? (*player)->color : color;
+ if ( player && *player && (!title || (strcmp((*player)->getName(), title))) )
+ setTitle((*player)->getName());
+
+ // Now display the player
+ int32_t tWidth = -1 == titleIdx ? text_length(font, "W") + padding + 4 : 0;
+ int32_t xOff = -1 == titleIdx ? 15 + padding + tWidth : 0;
+ int32_t txtLeft = left + xOff;
+ int32_t txtColor = color;
+ int32_t xTop = top + 1;
+ int32_t xHeight = height - 2;
+
+ // If this is a toggle, it must be displayed first:
+ if (ET_TOGGLE == this->type) {
+ int32_t bgColor = BLACK;
+ int32_t shColor = makecol(getr(color) / 3, getg(color) / 3, getb(color) / 3);
+
+ // Swap colors if the player is selected
+ if ((*player)->selected) {
+ bgColor = color;
+ txtColor = BLACK;
+ }
+
+ // Add a button like area for the name
+ rect( global.canvas, txtLeft, top, left + width, top + height, txtColor);
+ rect( global.canvas, txtLeft + 1, top + 1, left + width - 1, top + height - 1, txtColor);
+ hline( global.canvas, txtLeft + 1, top + height - 1, left + width - 1, shColor);
+ hline( global.canvas, txtLeft, top + height, left + width, shColor);
+ vline( global.canvas, left + width - 1, top + 1, top + height - 1, shColor);
+ vline( global.canvas, left + width, top, top + height, shColor);
+ rectfill(global.canvas, txtLeft + 2, top + 2, left + width - 2, top + height - 2, bgColor);
+
+ // Additional text offset for the border
+ txtLeft += 3;
+ xHeight -= 2;
+ }
+
+ // Then display the player name
+ eTeamTypes pTeam = player && *player ? (*player)->team : TEAM_COUNT;
+ if (title && title[0]) {
+
+ // Is the text shadowed, then create one:
+ if (env.shadowedText)
+ textout_ex (global.canvas, font, title, txtLeft + 2, xTop + 2,
+ GetShadeColor(txtColor, true, PINK), -1);
+
+ textout_ex (global.canvas, font, title, txtLeft + 1, xTop + 1, txtColor, -1);
+ }
+
+ // Second the player type indicator:
+ if (-1 == titleIdx ) {
+ (*player)->drawIndicator(left, xTop, xHeight);
+
+ // and third the team indicator:
+ int32_t xLeft = left + 19;
+
+ rectfill(global.canvas, xLeft + 1, xTop + 3,
+ xLeft + 12, xTop + xHeight - 2,
+ team_color_bg[pTeam]);
+ rect(global.canvas, xLeft, xTop + 2, xLeft + 13, xTop + xHeight - 1,
+ team_color_fg[pTeam]);
+
+ xLeft += 7;
+
+ textout_centre_ex(global.canvas, font, team_Indicator[pTeam],
+ xLeft - (TEAM_SITH == pTeam ? 1 : 0), xTop + 1,
+ team_color_fg[pTeam], -1);
+ } // end of player indicator
+
+ global.make_update (left, top, width, height);
+ drawn = true;
+ }
+
+ // Show decorations if wanted:
+ if (show_full)
+ this->displayDeco();
+}
+
+
+/// @brief return true, the action function must be able to return an exit code.
+bool OptionItemPlayer::isExitButton()
+{
+ return true;
+}
diff --git a/src/optionitemplayer.h b/src/optionitemplayer.h
new file mode 100644
index 0000000..e9c597b
--- /dev/null
+++ b/src/optionitemplayer.h
@@ -0,0 +1,92 @@
+#pragma once
+#ifndef ATANKS_SRC_OPTIONITEMPLAYER_H_INCLUDED
+#define ATANKS_SRC_OPTIONITEMPLAYER_H_INCLUDED
+
+/*
+ * atanks - obliterate each other with oversize weapons
+ *
+ * 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.
+ *
+ */
+
+#include "optionitembase.h"
+
+/** @file optionitemplayer.h
+ * @brief declaration of the option entry class specialized on handling
+ * PLAYER instances
+**/
+
+
+/** @class OptionItemPlayer
+ * @brief abstract one option menu entry to handle a PLAYER instance
+ *
+ * This class is a special version of the OptionItem template that can only
+ * handle PLAYER instances.
+ *
+ * The the only entry type supported is the ET_MENU.
+**/
+class OptionItemPlayer : public OptionItemBase
+{
+public:
+
+ /* -------------------------------------------
+ * --- Public constructors and destructors ---
+ * -------------------------------------------
+ */
+
+ explicit
+ OptionItemPlayer(
+ PLAYER** player_,
+ int32_t (*action_)(PLAYER** player_, int32_t),
+ const char* title_,
+ int32_t titleIdx_,
+ int32_t top_, int32_t left_, int32_t width_, int32_t height_,
+ int32_t padding_);
+ virtual ~OptionItemPlayer();
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ virtual int32_t activate (int32_t, int32_t, int32_t, int32_t);
+ virtual bool canGoDown ();
+ virtual bool canGoUp ();
+ virtual void display (bool show_full);
+ virtual bool isExitButton();
+ void setLanguage ();
+
+private:
+
+ /* ----------------------------------------------
+ * --- Private methods and external functions ---
+ * ----------------------------------------------
+ */
+
+ int32_t (*actionFunc )(PLAYER** target, int32_t) = nullptr;
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ PLAYER** player = nullptr; //!< PLAYER instance to handle
+};
+
+
+#endif // ATANKS_SRC_OPTIONITEMPLAYER_H_INCLUDED
+
+
diff --git a/src/optionscreens.cpp b/src/optionscreens.cpp
new file mode 100644
index 0000000..5f5b899
--- /dev/null
+++ b/src/optionscreens.cpp
@@ -0,0 +1,1038 @@
+#include "optionscreens.h"
+#include "player.h"
+#include "files.h"
+#include "sound.h"
+
+// Helper functions to build the sub menus for the options screen
+static void build_Physics (Menu &mPhysics,
+ int32_t menuMid, int32_t itemWidth, int32_t itemHeight,
+ int32_t itemPadding, int32_t itemY);
+static void build_Weather (Menu &mWeather,
+ int32_t menuMid, int32_t itemWidth, int32_t itemHeight,
+ int32_t itemPadding, int32_t itemY);
+static void build_Graphics(Menu &mGraphics,
+ int32_t menuMid, int32_t itemWidth, int32_t itemHeight,
+ int32_t itemPadding, int32_t itemY);
+static void build_Money (Menu &mMoney,
+ int32_t menuMid, int32_t itemWidth, int32_t itemHeight,
+ int32_t itemPadding, int32_t itemY);
+static void build_Network (Menu &mNetwork,
+ int32_t menuMid, int32_t itemWidth, int32_t itemHeight,
+ int32_t itemPadding, int32_t itemY);
+static void build_Sound (Menu &mSound,
+ int32_t menuMid, int32_t itemWidth, int32_t itemHeight,
+ int32_t itemPadding, int32_t itemY);
+
+// Helper action function to do direct language changes
+#define LANG_SWITCH_TRIGGER 0x0BadCafe
+int32_t switch_language(eLanguages* lang, int32_t val);
+
+
+/** @brief draw the Menu Background
+ *
+ * Draws a 600x400 centred box, fills it with some random lines or circles.
+ * Someday, we should make this more generic; have it take the box dimensions
+ * as an input parameter.
+**/
+void drawMenuBackground (eBackgroundTypes backType, int32_t tOffset, int32_t numItems)
+{
+ rectfill (global.canvas, env.halfWidth - 300, env.menuBeginY, // 100,
+ env.halfWidth + 300, env.menuEndY, // env.screenHeight - 100,
+ makecol (0, 79, 0));
+ rect (global.canvas, env.halfWidth - 300, env.menuBeginY, // 100,
+ env.halfWidth + 300, env.menuEndY, // env.screenHeight - 100,
+ makecol (128, 255, 128));
+
+ if (BACKGROUND_BLANK == backType)
+ return;
+
+ drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
+ global.current_drawing_mode = DRAW_MODE_TRANS;
+ set_trans_blender (0, 0, 0, 15);
+
+ for (int32_t tCount = 0; tCount < numItems; tCount++) {
+ int32_t radius = static_cast<int32_t>( (perlin1DPoint (1.0, 5,
+ (tOffset * 0.0333) + tCount + 423346,
+ 0.5, 8) + 1.1) * 20); // [0.1;2.1]
+ int32_t xpos = env.halfWidth
+ + static_cast<int32_t>(perlin1DPoint (1.0, 3,
+ (tOffset * 0.0166) + tCount + 232662,
+ 0.3, 3) * (299 - radius));
+ int32_t ypos = env.halfHeight
+ + static_cast<int32_t>(perlin1DPoint (1.0, 2,
+ (tOffset * 0.0175) + tCount + 42397,
+ 0.3, 3)
+ * (env.halfHeight - env.menuBeginY
+ - radius - 1));
+ switch (backType) {
+ case BACKGROUND_CIRCLE:
+ circlefill (global.canvas, xpos, ypos, radius, LIME_GREEN);
+ break;
+ case BACKGROUND_LINE:
+ rectfill (global.canvas, xpos - radius / 2, env.menuBeginY + 1,
+ xpos + radius / 2, env.menuEndY - 1, LIME_GREEN);
+ break;
+ case BACKGROUND_SQUARE:
+ rectfill (global.canvas, xpos - radius, ypos - radius,
+ xpos + radius, ypos + radius, LIME_GREEN);
+ break;
+ case BACKGROUND_BLANK:
+ default:
+ break;
+ }
+
+ }
+
+ solid_mode();
+ global.current_drawing_mode = DRAW_MODE_SOLID;
+}
+
+
+/// @brief Show a screen listing all players allowing to create new and edit existing ones.
+void editPlayers ()
+{
+ /// @todo : Currently the width is fixed on 600. This should be made
+ /// more dynamic like the height. Although the height is fixed, too...
+ /// However, there is much to do to get an adaptable and good looking menu...
+
+ int32_t optionsRetVal = 0;
+ int32_t menuMid = 300;
+ int32_t itemHeight = env.fontHeight + 2;
+ int32_t itemPadding = 2;
+ int32_t itemY = (itemHeight + itemPadding) * 2;
+ int32_t btnHeight = env.misc[7]->h + itemPadding;
+ int32_t menuHeight = env.menuEndY - env.menuBeginY; // Raw height
+ int32_t plListHeight = menuHeight - itemY // Top area reserved for the title
+ - btnHeight - itemPadding - 2; // Bottom area reserved for buttons
+ // "Select Players"
+ Menu menu(MC_PLAYERS, env.halfWidth - menuMid, env.menuBeginY);
+
+ // "Create New"
+ PLAYER* player_new = nullptr;
+ int32_t first_idx = menu.addMenu(&player_new, new_player, 1,
+ menuMid - 53, itemY,
+ 100, itemHeight, itemPadding);
+ itemY += itemHeight + itemPadding;
+
+ // Add one entry per player
+ // One Menu per player:
+ // Add one edit option per player
+ int32_t last_idx = first_idx;
+ int32_t max_width = 100;
+ plListHeight -= itemY; // this is left.
+
+ // First loop to determine maximum name width
+ for (int32_t num = 0; num < env.numPermanentPlayers; num++) {
+ int32_t xLen = text_length(font, env.allPlayers[num]->getName());
+ if (xLen > max_width)
+ max_width = xLen;
+ }
+
+ // Now really add them
+ for (int32_t num = 0; num < env.numPermanentPlayers; num++)
+ last_idx = menu.addMenu(&env.allPlayers[num], edit_player, -1,
+ 0, 0, max_width + 15, itemHeight, itemPadding);
+ // last_idx is one too high now
+ last_idx--;
+
+ // Distribute the player list:
+ menu.distribute(first_idx, last_idx, menuMid * 2, plListHeight, itemY, false);
+
+ // Add "back" button
+ menu.addButton( 2, nullptr, KEY_ESC,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid - (env.misc[7]->w / 2),
+ menuHeight - btnHeight - itemPadding - 2,
+ 0, 0, 2);
+
+ while ( (KEY_ESC != optionsRetVal)
+ && !global.isCloseBtnPressed() ) {
+ optionsRetVal = menu();
+
+ // Was an edit confirmed?
+ if (PE_CONFIRM_EDIT & optionsRetVal)
+ menu.redraw(optionsRetVal ^ PE_CONFIRM_EDIT, true);
+ else if (PE_CONFIRM_DEL & optionsRetVal) {
+ // delete the menu entry
+ int32_t num = optionsRetVal ^ PE_CONFIRM_DEL;
+ menu.delete_entry(num + first_idx);
+ --last_idx;
+
+ // The player has to be deleted, too:
+ PLAYER* to_delete = env.allPlayers[num];
+ env.deletePermPlayer(to_delete);
+
+ // redistribute the remaining:
+ menu.distribute(first_idx, last_idx, menuMid * 2, plListHeight, itemY, true);
+
+ optionsRetVal = 0;
+ } else if (PE_CONFIRM_NEW & optionsRetVal) {
+ if (player_new) {
+ // Add a menu entry for the new player:
+ menu.addMenu(&env.allPlayers[env.numPermanentPlayers - 1],
+ edit_player, -1, 0, 0, max_width + 15, itemHeight,
+ itemPadding);
+ // Move the new menu entry up, the "back" button is in the way
+ menu.move_entry(last_idx + 2, last_idx + 1);
+
+ // And re-distribute
+ menu.distribute(first_idx, ++last_idx, menuMid * 2,
+ plListHeight, itemY, true);
+ }
+ optionsRetVal = 0;
+ }
+ }
+}
+
+
+/// @brief The main options menu
+void optionsMenu ()
+{
+ /// @todo : Currently the width is fixed on 600. This should be made
+ /// more dynamic like the height. Although the height is fixed, too...
+ /// However, there is much to do to get an adaptable and good looking menu...
+
+ int32_t optionsRetCode = 0;
+ int32_t menuMid = 300;
+ int32_t itemWidth = 210;
+ int32_t itemHeight = env.fontHeight + 2;
+ int32_t itemPadding = 2;
+ int32_t itemFullHeight = itemHeight + itemPadding;
+ int32_t itemY = itemFullHeight * 3;
+ int32_t btnHeight = env.misc[7]->h + itemPadding;
+ int32_t menuHeight = env.menuEndY - env.menuBeginY; // Raw height
+ int32_t menuLeft = env.halfWidth - menuMid;
+ int32_t menuTop = env.menuBeginY;
+ int32_t idx = 1;
+
+ Menu mMain (MC_MAIN, menuLeft, menuTop);
+ Menu mPhysics (MC_PHYSICS, menuLeft, menuTop);
+ Menu mWeather (MC_WEATHER, menuLeft, menuTop);
+ Menu mGraphics(MC_GRAPHICS, menuLeft, menuTop);
+ Menu mMoney (MC_FINANCE, menuLeft, menuTop);
+ Menu mNetwork (MC_NETWORK, menuLeft, menuTop);
+ Menu mSound (MC_SOUND, menuLeft, menuTop);
+
+ // As the sub menus must be attached to the main options menu,
+ // they have to be build first, before the main menu can be built.
+ build_Sound (mSound, menuMid, itemWidth, itemHeight, itemPadding, itemY);
+ build_Network (mNetwork, menuMid, itemWidth, itemHeight, itemPadding, itemY);
+ build_Money (mMoney, menuMid, itemWidth, itemHeight, itemPadding, itemY);
+ build_Graphics(mGraphics, menuMid, itemWidth, itemHeight, itemPadding, itemY);
+ build_Weather (mWeather, menuMid, itemWidth, itemHeight, itemPadding, itemY);
+ build_Physics (mPhysics, menuMid, itemWidth, itemHeight, itemPadding, itemY);
+
+ // Now the main options screen can be built:
+
+ // "Reset All"
+ Menu mReset(MC_RESET, menuLeft, menuTop);
+ mReset.addButton( 1, nullptr, RO_RESET,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid + 50,
+ menuHeight- btnHeight - 6, 0, 0, itemPadding);
+ mReset.addButton( 2, nullptr, RO_BACK,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid - env.misc[7]->w - 50,
+ menuHeight- btnHeight - 6, 0, 0, itemPadding);
+
+ // "Reset options"
+ mMain.addMenu ( &mReset, idx++, RED, menuMid - (env.misc[7]->w / 2), itemY,
+ 150, itemFullHeight, itemPadding);
+ itemY += btnHeight + itemPadding;
+
+ // "Physics"
+ mMain.addMenu(&mPhysics, idx++, WHITE, menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Weather"
+ mMain.addMenu(&mWeather, idx++, WHITE, menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Graphics"
+ mMain.addMenu(&mGraphics, idx++, WHITE, menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Money"
+ mMain.addMenu(&mMoney, idx++, WHITE, menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Network"
+ mMain.addMenu(&mNetwork, idx++, WHITE, menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Sound"
+ mMain.addMenu(&mSound, idx++, WHITE, menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Weapon Tech Level"
+ mMain.addValue(&env.weapontechLevel, idx++, WHITE, 0, 5, 1, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Item Tech Level"
+ mMain.addValue(&env.itemtechLevel, idx++, WHITE, 0, 5, 1, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Landscape"
+ mMain.addValue(&env.landType, idx++, nullptr, WHITE,
+ TC_LANDTYPE, static_cast<int32_t>(LAND_NONE),
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Turn Order"
+ mMain.addValue(&env.turntype, idx++, nullptr, WHITE,
+ TC_TURNTYPE, static_cast<int32_t>(TURN_SIMUL),
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Skip AI-only play"
+ mMain.addValue(&env.skipComputerPlay, idx++, nullptr, WHITE,
+ TC_SKIPTYPE, static_cast<int32_t>(SKIP_HUMANS_DEAD),
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Show FPS"
+ mMain.addValue(&env.showFPS, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Language"
+ mMain.addValue(&env.language, switch_language, idx++, nullptr, WHITE,
+ TC_LANGUAGE, static_cast<int32_t>(EL_ITALIAN),
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+
+ // "Back"
+ mMain.addButton(idx, nullptr, KEY_ESC,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid - (env.misc[7]->w / 2),
+ menuHeight - btnHeight - itemPadding - 2,
+ 0, 0, 2);
+
+ // Safe the current language, if a new one is selected,
+ // the text files must be reloaded.
+ eLanguages cur_lang = env.language;
+
+ while (0 == optionsRetCode) {
+ int32_t old_fps = env.frames_per_second;
+
+ optionsRetCode = mMain();
+
+ if (RO_RESET == optionsRetCode) {
+ env.Reset_Options();
+ optionsRetCode = 0;
+ } else if (RO_BACK == optionsRetCode)
+ optionsRetCode = 0;
+ else if (LANG_SWITCH_TRIGGER == optionsRetCode) {
+ mMain.setLanguage(true);
+ optionsRetCode = 0;
+ }
+
+ // If no exit code is set, redraw the menu:
+ if (0 == optionsRetCode)
+ mMain.redrawAll(true);
+
+ // Update pre-calculated values if FPS has been changed:
+ if (old_fps != env.frames_per_second)
+ env.set_fps(0); // 0 triggers re-calculation only
+ } // End of menu loop
+
+ // Did the language change?
+ if (env.language != cur_lang) {
+ env.load_text_files();
+ Load_Weapons_Text();
+ }
+}
+
+
+/// @brief Show a screen that shows the preparations to create a new game.
+int32_t selectPlayers ()
+{
+ /// @todo : Currently the width is fixed on 600. This should be made
+ /// more dynamic like the height. Although the height is fixed, too...
+ /// However, there is much to do to get an adaptable and good looking menu...
+
+ int32_t optionsRetVal = 0;
+ int32_t menuMid = 300;
+ int32_t itemWidth = 120;
+ int32_t itemHeight = env.fontHeight + 4;
+ int32_t itemPadding = 2;
+ int32_t itemFullHeight = itemHeight + itemPadding;
+ int32_t itemY = itemFullHeight * 2;
+ int32_t btnHeight = env.misc[7]->h + itemPadding;
+ int32_t menuHeight = env.menuEndY - env.menuBeginY; // Raw height
+ int32_t plListHeight = menuHeight - itemY // Top area reserved for the title
+ - btnHeight - itemPadding - 2; // Bottom area reserved for buttons
+ int32_t idx = 1;
+
+ uint32_t number_saved_games = 0;
+ dirent** saved_game_names;
+ char** game_list = NULL;
+
+ // Use new menu system:
+ // "Select Players"
+ Menu menu(MC_PLAY, env.halfWidth - menuMid, env.menuBeginY);
+
+ // "Rounds"
+ menu.addValue(&env.rounds, idx++, BLACK, 1, MAX_ROUNDS, 1, "%u",
+ menuMid - (itemWidth / 2), itemY, itemWidth, itemHeight,
+ itemPadding);
+ itemY += itemFullHeight + 2;
+
+ // "New Game Name"
+ strncpy(env.game_name, "New Game", GAMENAMELEN);
+ menu.addText(env.game_name, idx++, GAMENAMELEN, BLACK, "%s",
+ menuMid - (itemWidth / 2), itemY, itemWidth, itemHeight,
+ itemPadding);
+ itemY += itemFullHeight + 2;
+
+ // find saved games
+ saved_game_names = Find_Saved_Games(number_saved_games);
+
+ if ( ( saved_game_names ) && ( number_saved_games ) ) {
+
+ // Extend the global list if it is too small
+ if (env.saved_game_list_size <= number_saved_games) {
+ // Note: The number of saved games in ENVRIONMENT must be
+ // one entry larger, as the text array for the option menu
+ // has to be terminated by a null pointer.
+ game_list = (char**)realloc(env.saved_game_list,
+ sizeof(char*) * (number_saved_games + 1));
+ if (game_list) {
+ // The old names must be freed and the new initialized
+ for (uint32_t i = 0; i <= number_saved_games ; ++i) {
+ if ( ( i < env.saved_game_list_size ) && game_list[i] )
+ free(game_list[i]);
+ game_list[i] = nullptr;
+ }
+
+ env.saved_game_list = const_cast<const char**>(game_list);
+ env.saved_game_list_size = number_saved_games + 1;
+ } else {
+ cerr << "ERROR extending saved game list from ";
+ cerr << env.saved_game_list_size << " entries to ";
+ cerr << number_saved_games << " entries." << endl;
+ free (saved_game_names);
+ return KEY_ESC;
+ }
+ } else
+ game_list = const_cast<char**>(env.saved_game_list);
+ // End of preparing space for the saved game list
+
+ // Copy the found names (Without the extra entry of course)
+ for (uint32_t i = 0; i < number_saved_games; ++i) {
+
+ // If the name is already set, free it, it was strdup'd
+ if ( game_list[i] )
+ free(game_list[i]);
+ game_list[i] = strdup(saved_game_names[i]->d_name);
+
+ // clear trailing extension
+ if ( strchr(game_list[i], '.') )
+ strchr(game_list[i], '.')[0] = '\0';
+ }
+
+ // set up menu for selecting saved games
+ // "or Load Game"
+ env.saved_gameindex = 0;
+ menu.addValue(&env.saved_gameindex, idx++, env.saved_game_list,
+ BLACK, TC_FREETEXT, number_saved_games - 1,
+ menuMid - (itemWidth / 2), itemY, itemWidth, itemHeight,
+ itemPadding);
+ itemY += itemFullHeight + 4; // Next two options need more height
+
+ // "Load Game"
+ menu.addToggle(&env.loadGame, idx++, WHITE,
+ menuMid - 125, itemY, 100, itemHeight + 5, itemPadding);
+ // itemY stays, "Campaign" is on the same row.
+ } // End of having saved games
+
+ // If no saved games are there, idx must be advanced nevertheless or
+ // the texts go mixed up:
+ else
+ idx += 2;
+
+
+ // "Campaign"
+ // Note: And save the result, it is the first player index
+ int32_t first_idx = menu.addToggle(&env.campaign_mode, idx++, WHITE,
+ menuMid + 25, itemY, 100, itemHeight + 5,
+ itemPadding);
+ itemY += itemFullHeight + 7; // ET_TOGGLE needs more height
+
+ // Add one entry per player
+ int32_t last_idx = first_idx;
+ int32_t max_width = 100;
+ plListHeight -= itemY; // this is left.
+
+ // First loop to determine maximum name width
+ for (int32_t num = 0; num < env.numPermanentPlayers; num++) {
+ int32_t xLen = text_length(font, env.allPlayers[num]->getName())
+ + (2 * itemPadding);
+ if (xLen > max_width)
+ max_width = xLen;
+ }
+
+ // Now really add them
+ for (int32_t num = 0; num < env.numPermanentPlayers; num++)
+ last_idx = menu.addToggle(&env.allPlayers[num],
+ 0, 0, max_width + 21, itemHeight, itemPadding);
+
+ // last_idx is one too high now.
+ last_idx--;
+
+
+ // Distribute the player list:
+ menu.distribute(first_idx, last_idx, menuMid * 2, plListHeight, itemY, false);
+
+ // The "Okay" and "Back" buttons have their own texts to be translated
+ menu.addButton( idx + 1, nullptr, KEY_ESC,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid - env.misc[7]->w - 25,
+ menuHeight - btnHeight - itemPadding - 2,
+ 0, 0, 2);
+ menu.addButton( idx, nullptr, KEY_ENTER,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid + 25,
+ menuHeight - btnHeight - itemPadding - 2,
+ 0, 0, 2);
+
+ // Let the menu take over user input until the game is either started,
+ // or the user opts out to the main menu.
+ // Idea for the future:
+ // Start the menu as a thread and do some fancy stuff with the background
+ // while we are waiting.
+ do {
+ optionsRetVal = menu();
+
+ if ( env.loadGame ) {
+ if ( env.saved_game_list
+ && ( env.saved_gameindex < env.saved_game_list_size )
+ && env.saved_game_list[env.saved_gameindex][0]) {
+
+ strncpy(env.game_name,
+ env.saved_game_list[env.saved_gameindex],
+ GAMENAMELEN);
+ }
+ }
+
+ if (optionsRetVal == KEY_ENTER) {
+ if (env.loadGame) {
+ // A set game shall be loaded
+ if (Check_For_Saved_Game())
+ optionsRetVal = MRC_Load_Game;
+ else {
+ optionsRetVal = 0;
+ errorMessage = env.ingame->Get_Line(39);
+ errorX = env.halfWidth - text_length(font, errorMessage) / 2;
+ errorY = env.menuBeginY + itemFullHeight;
+ }
+ } else {
+ // Start a new game
+ int32_t playerCount = 0;
+ env.numGamePlayers = 0;
+
+ // Add selected players to the game:
+ for (int z = 0; z < env.numPermanentPlayers; z++) {
+ if (env.allPlayers[z]->selected) {
+ env.addGamePlayer(env.allPlayers[z]);
+ playerCount++;
+ }
+ }
+
+ // Check selected players
+ if ((playerCount < 2) || (playerCount > MAXPLAYERS)) {
+ if (playerCount < 2)
+ errorMessage = env.ingame->Get_Line(8);
+ else if (playerCount > MAXPLAYERS)
+ errorMessage = env.ingame->Get_Line(9);
+
+ errorX = env.halfWidth - text_length(font, errorMessage) / 2;
+ errorY = env.menuBeginY + itemFullHeight;
+ optionsRetVal = 0;
+ } else
+ optionsRetVal = MRC_Play_Game;
+ } // End of loading versus starting anew
+ } // End of KEY_ENTER result
+
+ // zero means an error occured.
+ // keep running the loop until ESC is pressed or a non-zero value appears
+ } while ((KEY_ESC != optionsRetVal)
+ && (MRC_Play_Game != optionsRetVal)
+ && (MRC_Load_Game != optionsRetVal)
+ && !global.isCloseBtnPressed() );
+
+ if ( (KEY_ESC == optionsRetVal)
+ || global.isCloseBtnPressed() )
+ optionsRetVal = MRC_Esc_Menu;
+
+ if (saved_game_names) {
+ for (uint32_t i = 0; i < number_saved_games; ++i) {
+ if ( saved_game_names[i] ) {
+
+#if defined(ATANKS_IS_WINDOWS)
+ // Under windows d_name is strdup'd
+ if ( saved_game_names[i]->d_name )
+ free(saved_game_names[i]->d_name);
+#endif // Windows
+
+ free(saved_game_names[i]);
+ }
+ }
+ free(saved_game_names);
+ }
+
+ return optionsRetVal;
+}
+
+
+// Helper functions to build the sub menus for the options screen
+static void build_Physics(Menu &mPhysics,
+ int32_t menuMid, int32_t itemWidth, int32_t itemHeight,
+ int32_t itemPadding, int32_t itemY)
+{
+ int32_t itemFullHeight = itemHeight + itemPadding;
+ int32_t btnHeight = env.misc[7]->h + itemPadding;
+ int32_t menuHeight = env.menuEndY - env.menuBeginY; // Raw height
+ int32_t idx = 1;
+
+ assert( (0 == mPhysics.count()) && "ERROR: mPhysics already built?");
+
+ if (mPhysics.count())
+ return; // Don't build twice!
+
+ // "Gravity"
+ mPhysics.addValue(&env.gravity, idx++, WHITE, .025, .325, .025, "%5.3f",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Viscosity",
+ mPhysics.addValue(&env.viscosity, idx++, WHITE, .25, 2., .25, "%3.2f",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Land Slide"
+ mPhysics.addValue(&env.landSlideType, idx++, nullptr, WHITE,
+ TC_LANDSLIDE, static_cast<int32_t>(SLIDE_CARTOON),
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Land Slide Delay",
+ mPhysics.addValue(&env.landSlideDelay, idx++, WHITE, 1, 5, 1, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+
+ // "Wall Type"
+ mPhysics.addValue(&env.wallType, idx++, nullptr, WHITE,
+ TC_WALLTYPE, static_cast<int32_t>(WALL_RANDOM),
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+
+ // "Boxed Mode",
+ mPhysics.addValue(&env.boxedMode, idx++, nullptr, WHITE,
+ TC_OFFONRANDOM, static_cast<int32_t>(BM_RANDOM),
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+
+ // "Boxed Ceiling Wrapping",
+ mPhysics.addValue(&env.do_box_wrap, idx++, nullptr, WHITE,
+ TC_OFFON, static_cast<int32_t>(BM_ON),
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+
+ // "Violent Death"
+ mPhysics.addValue(&env.violent_death, idx++, nullptr, WHITE,
+ TC_LIGHTNING, static_cast<int32_t>(VD_HEAVY),
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+
+ // "Timed Shots"
+ mPhysics.addValue(&env.maxFireTime, idx++, WHITE, 0, 180, 5, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Volley Delay"
+ mPhysics.addValue(&env.volley_delay, idx++, WHITE, 5, 50, 1, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Explosion Debris"
+ mPhysics.addValue(&env.debris_level, idx++, nullptr, WHITE,
+ TC_LIGHTNING, 3,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+
+ // "Back" Button
+ mPhysics.addButton(idx, nullptr, KEY_ESC,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid - (env.misc[7]->w / 2),
+ menuHeight - btnHeight - itemPadding - 2,
+ 0, 0, 2);
+}
+
+
+static void build_Weather(Menu &mWeather,
+ int32_t menuMid, int32_t itemWidth, int32_t itemHeight,
+ int32_t itemPadding, int32_t itemY)
+{
+ int32_t itemFullHeight = itemHeight + itemPadding;
+ int32_t btnHeight = env.misc[7]->h + itemPadding;
+ int32_t menuHeight = env.menuEndY - env.menuBeginY; // Raw height
+ int32_t idx = 1;
+
+ assert( (0 == mWeather.count()) && "ERROR: mWeather already built?");
+
+ if (mWeather.count())
+ return; // Don't build twice!
+
+
+ // "Meteor Showers"
+ mWeather.addValue(&env.meteors, idx++, nullptr, WHITE,
+ TC_METEOR, 3,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Lightning"
+ mWeather.addValue(&env.lightning, idx++, nullptr, WHITE,
+ TC_LIGHTNING, 3,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Falling Dirt"
+ mWeather.addValue(&env.falling_dirt_balls, idx++, nullptr, WHITE,
+ TC_METEOR, 3,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Laser Satellite"
+ mWeather.addValue(&env.satellite, idx++, nullptr, WHITE,
+ TC_SATELLITE, static_cast<int32_t>(SL_SUPER),
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Fog"
+ mWeather.addValue(&env.fog, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+
+ // "Max Wind Strength"
+ mWeather.addValue(&env.windstrength, idx++, WHITE, 0, 100, 5, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+
+ // "Wind Variation"
+ mWeather.addValue(&env.windvariation, idx++, WHITE, 0, 100, 3, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+
+ // "Back"
+ mWeather.addButton(idx, nullptr, KEY_ESC,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid - (env.misc[7]->w / 2),
+ menuHeight - btnHeight - itemPadding - 2,
+ 0, 0, 2);
+}
+
+
+static void build_Graphics(Menu &mGraphics,
+ int32_t menuMid, int32_t itemWidth, int32_t itemHeight,
+ int32_t itemPadding, int32_t itemY)
+{
+ int32_t itemFullHeight = itemHeight + itemPadding;
+ int32_t btnHeight = env.misc[7]->h + itemPadding;
+ int32_t menuHeight = env.menuEndY - env.menuBeginY; // Raw height
+ int32_t idx = 1;
+
+ assert( (0 == mGraphics.count()) && "ERROR: mGraphics already built?");
+
+ if (mGraphics.count())
+ return; // Don't build twice!
+
+ // "Full Screen"
+ mGraphics.addValue(&env.full_screen, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Dithering"
+ mGraphics.addValue(&env.ditherGradients, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Detailed Land"
+ mGraphics.addValue(&env.detailedLandscape, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Detailed Sky"
+ mGraphics.addValue(&env.detailedSky, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Fading Text"
+ mGraphics.addValue(&env.fadingText, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Shadowed Text"
+ mGraphics.addValue(&env.shadowedText, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Swaying Text"
+ mGraphics.addValue(&env.swayingText, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Colour Theme"
+ mGraphics.addValue(&env.colourTheme, idx++, nullptr, WHITE,
+ TC_COLOUR, static_cast<int32_t>(CT_CRISPY),
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Screen Width"
+ mGraphics.addValue(&env.temp_screenWidth, idx++, WHITE, 800, 2000, 100, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Screen Height"
+ mGraphics.addValue(&env.temp_screenHeight, idx++, WHITE, 600, 1400, 100, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Mouse Pointer"
+ mGraphics.addValue(&env.osMouse, idx++, nullptr, WHITE,
+ TC_MOUSE, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Game Speed"
+ mGraphics.addValue(&env.frames_per_second, idx++, WHITE, 30, 1000, 5, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Custom Background"
+ mGraphics.addValue(&env.custom_background, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Show AI Feedback"
+ mGraphics.addValue(&env.showAIFeedback, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Dynamic Menu Background"
+ mGraphics.addValue(&env.dynamicMenuBg, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+
+
+ // "Back"
+ mGraphics.addButton(idx, nullptr, KEY_ESC,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid - (env.misc[7]->w / 2),
+ menuHeight - btnHeight - itemPadding - 2,
+ 0, 0, 2);
+}
+
+
+static void build_Money(Menu &mMoney,
+ int32_t menuMid, int32_t itemWidth, int32_t itemHeight,
+ int32_t itemPadding, int32_t itemY)
+{
+ int32_t itemFullHeight = itemHeight + itemPadding;
+ int32_t btnHeight = env.misc[7]->h + itemPadding;
+ int32_t menuHeight = env.menuEndY - env.menuBeginY; // Raw height
+ int32_t idx = 1;
+
+ assert( (0 == mMoney.count()) && "ERROR: mMoney already built?");
+
+ if (mMoney.count())
+ return; // Don't build twice!
+
+ // "Starting Money"
+ mMoney.addValue(&env.startmoney, idx++, WHITE, 0, 200000, 5000, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Interest Rate"
+ mMoney.addValue(&env.interest, idx++, WHITE, 1., 1.5, .05, "%3.2f",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Round Win Bonus"
+ mMoney.addValue(&env.scoreRoundWinBonus, idx++, WHITE, 0, 50000, 5000, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Damage Bounty"
+ mMoney.addValue(&env.scoreHitUnit, idx++, WHITE, 0, 500, 25, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Self-Damage Penalty"
+ mMoney.addValue(&env.scoreSelfHit, idx++, WHITE, 0, 10000, 1000, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Team-Damage Penalty"
+ mMoney.addValue(&env.scoreTeamHit, idx++, WHITE, 0, 10000, 1000, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Tank Destruction Bonus"
+ mMoney.addValue(&env.scoreUnitDestroyBonus, idx++, WHITE, 0, 20000, 2500, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Tank Self-Destruction Penalty"
+ mMoney.addValue(&env.scoreUnitSelfDestroy, idx++, WHITE, 0, 20000, 2500, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Item Sell Multiplier"
+ mMoney.addValue(&env.sellpercent, idx++, WHITE, 0., 1., .1, "%2.2f",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Teams Share"
+ mMoney.addValue(&env.divide_money, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+
+ // "Back"
+ mMoney.addButton(idx, nullptr, KEY_ESC,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid - (env.misc[7]->w / 2),
+ menuHeight - btnHeight - itemPadding - 2,
+ 0, 0, 2);
+}
+
+
+static void build_Network(Menu &mNetwork,
+ int32_t menuMid, int32_t itemWidth, int32_t itemHeight,
+ int32_t itemPadding, int32_t itemY)
+{
+ int32_t itemFullHeight = itemHeight + itemPadding;
+ int32_t btnHeight = env.misc[7]->h + itemPadding;
+ int32_t menuHeight = env.menuEndY - env.menuBeginY; // Raw height
+ int32_t idx = 1;
+
+ assert( (0 == mNetwork.count()) && "ERROR: mNetwork already built?");
+
+ if (mNetwork.count())
+ return; // Don't build twice!
+
+
+ // "Check Updates"
+ mNetwork.addValue(&env.check_for_updates, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Networking"
+ mNetwork.addValue(&env.network_enabled, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Listen Port"
+ mNetwork.addValue(&env.network_port, idx++, WHITE, 10645, 64645, 1000, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Server Address"
+ mNetwork.addText(env.server_name, idx++, 127, WHITE, "%s",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Server Port"
+ mNetwork.addText(env.server_port, idx++, 127, WHITE, "%s",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+
+ // "Back"
+ mNetwork.addButton(idx, nullptr, KEY_ESC,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid - (env.misc[7]->w / 2),
+ menuHeight - btnHeight - itemPadding - 2,
+ 0, 0, 2);
+}
+
+
+static void build_Sound(Menu &mSound,
+ int32_t menuMid, int32_t itemWidth, int32_t itemHeight,
+ int32_t itemPadding, int32_t itemY)
+{
+ int32_t itemFullHeight = itemHeight + itemPadding;
+ int32_t btnHeight = env.misc[7]->h + itemPadding;
+ int32_t menuHeight = env.menuEndY - env.menuBeginY; // Raw height
+ int32_t idx = 1;
+
+ assert( (0 == mSound.count()) && "ERROR: mSound already built?");
+
+ if (mSound.count())
+ return; // Don't build twice!
+
+ // "All Sound"
+ mSound.addValue(&env.sound_enabled, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Sound Driver"
+ mSound.addValue(&env.sound_driver, idx++, nullptr, WHITE,
+ TC_SOUNDDRIVER, static_cast<int32_t>(SD_JACK),
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Music"
+ mSound.addValue(&env.play_music, idx++, nullptr, WHITE,
+ TC_OFFON, 1,
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Volume Faktor"
+ mSound.addValue(&env.volume_factor, idx++, WHITE, 0, MAX_VOLUME_FACTOR, 1, "%d",
+ menuMid - 50, itemY, itemWidth, itemHeight, itemPadding);
+
+ // "Back"
+ mSound.addButton(idx, nullptr, KEY_ESC,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid - (env.misc[7]->w / 2),
+ menuHeight - btnHeight - itemPadding - 2,
+ 0, 0, 2);
+}
+
+
+/// @brief Switch language helper function
+/// Note: The real use of this function is, that it generates a return
+/// code, so optionsMenu() can react on the language change. ;-)
+int32_t switch_language(eLanguages* lang, int32_t val)
+{
+ eLanguages old_lang = *lang;
+
+ if (val > 0)
+ ++(*lang);
+ else if (val < 0)
+ --(*lang);
+
+ if (*lang != old_lang)
+ return LANG_SWITCH_TRIGGER;
+ return 0;
+}
+
diff --git a/src/imagedefs.h b/src/optionscreens.h
similarity index 64%
copy from src/imagedefs.h
copy to src/optionscreens.h
index f09fa5c..83cd06e 100644
--- a/src/imagedefs.h
+++ b/src/optionscreens.h
@@ -1,5 +1,6 @@
-#ifndef IMAGEDEFS_DEFINE
-#define IMAGEDEFS_DEFINE
+#pragma once
+#ifndef ATANKS_SRC_OPTIONSCREENS_H_INCLUDED
+#define ATANKS_SRC_OPTIONSCREENS_H_INCLUDED
/*
* atanks - obliterate each other with oversize weapons
@@ -12,18 +13,25 @@
*
* 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
+ * 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
+ * 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.
* */
-#define DONE_IMAGE 11
-#define FAST_UP_ARROW_IMAGE 12
-#define UP_ARROW_IMAGE 13
-#define DOWN_ARROW_IMAGE 14
-#define FAST_DOWN_ARROW_IMAGE 15
+/** @file optionscreens.h
+ * @brief Here the functions providing option screens and menus are declared
+**/
+
+#include "menu.h"
+
+void drawMenuBackground (int32_t itemType, int32_t tOffset, int32_t numItems);
+void editPlayers ();
+void optionsMenu ();
+int32_t selectPlayers ();
+
+
+#endif // ATANKS_SRC_OPTIONSCREENS_H_INCLUDED
-#endif // IMAGEDEFS_DEFINE
diff --git a/src/optiontypes.cpp b/src/optiontypes.cpp
new file mode 100644
index 0000000..1541d6e
--- /dev/null
+++ b/src/optiontypes.cpp
@@ -0,0 +1,187 @@
+#include "optiontypes.h"
+
+
+/** @brief get the name of an entry type name
+ *
+ * This function returns a string, as it is most secure and
+ * only really needed for error and/or debugging messages
+ * where speed and efficiency are as unimportant as they
+ * can get.
+ *
+ * @param[in] etype The enum entry to get the name of.
+ * @return A string with the name or "UNIMPLEMENTED" if a new entry hasn't been
+ * added here, yet.
+ */
+std::string getEntryTypeName(eEntryType etype)
+{
+ switch (etype)
+ {
+ case ET_NONE:
+ return std::string("ET_NONE");
+ break;
+ case ET_ACTION:
+ return std::string("ET_ACTION");
+ break;
+ case ET_BUTTON:
+ return std::string("ET_BUTTON");
+ break;
+ case ET_COLOR:
+ return std::string("ET_COLOR");
+ break;
+ case ET_MENU:
+ return std::string("ET_MENU");
+ break;
+ case ET_OPTION:
+ return std::string("ET_OPTION");
+ break;
+ case ET_TEXT:
+ return std::string("ET_NONE");
+ break;
+ case ET_TOGGLE:
+ return std::string("ET_TOGGLE");
+ break;
+ case ET_VALUE:
+ return std::string("ET_VALUE");
+ break;
+ default:
+ break;
+ }
+ return std::string("UNIMPLEMENTED");
+}
+
+
+/** @brief get the name of menu class name
+ *
+ * This function returns a string, as it is most secure and
+ * only really needed for error and/or debugging messages
+ * where speed and efficiency are as unimportant as they
+ * can get.
+ *
+ * @param[in] mclass The enum entry to get the name of.
+ * @return A string with the name or "UNIMPLEMENTED" if a new entry hasn't been
+ * added here, yet.
+ */
+std::string getMenuClassName(eMenuClass mclass)
+{
+ switch(mclass)
+ {
+ case MC_FINANCE:
+ return std::string("MC_FINANCE");
+ break;
+ case MC_GRAPHICS:
+ return std::string("MC_GRAPHICS");
+ break;
+ case MC_MAIN:
+ return std::string("MC_MAIN");
+ break;
+ case MC_NETWORK:
+ return std::string("MC_NETWORK");
+ break;
+ case MC_PHYSICS:
+ return std::string("MC_PHYSICS");
+ break;
+ case MC_PLAY:
+ return std::string("MC_PLAY");
+ break;
+ case MC_PLAYERS:
+ return std::string("MC_PLAYERS");
+ break;
+ case MC_SOUND:
+ return std::string("MC_SOUND");
+ break;
+ case MC_WEATHER:
+ return std::string("MC_WEATHER");
+ break;
+ case MC_MENUCLASS_COUNT:
+ return std::string("MC_MENUCLASS_COUNT");
+ break;
+ default:
+ break;
+ }
+ return std::string("UNIMPLEMENTED");
+}
+
+
+/** @brief get the name of a text class name
+ *
+ * This function returns a string, as it is most secure and
+ * only really needed for error and/or debugging messages
+ * where speed and efficiency are as unimportant as they
+ * can get.
+ *
+ * @param[in] tclass The enum entry to get the name of.
+ * @return A string with the name or "UNIMPLEMENTED" if a new entry hasn't been
+ * added here, yet.
+ */
+std::string getTextClassName(eTextClass tclass)
+{
+ switch (tclass)
+ {
+ case TC_COLOUR:
+ return std::string("TC_COLOUR");
+ break;
+ case TC_LANDSLIDE:
+ return std::string("TC_LANDSLIDE");
+ break;
+ case TC_LANDTYPE:
+ return std::string("TC_LANDTYPE");
+ break;
+ case TC_LANGUAGE:
+ return std::string("TC_LANGUAGE");
+ break;
+ case TC_LIGHTNING:
+ return std::string("TC_LIGHTNING");
+ break;
+ case TC_METEOR:
+ return std::string("TC_METEOR");
+ break;
+ case TC_MOUSE:
+ return std::string("TC_MOUSE");
+ break;
+ case TC_OFFON:
+ return std::string("TC_OFFON");
+ break;
+ case TC_OFFONRANDOM:
+ return std::string("TC_OFFONRANDOM");
+ break;
+ case TC_PLAYERPREF:
+ return std::string("TC_PLAYERPREF");
+ break;
+ case TC_PLAYERTEAM:
+ return std::string("TC_PLAYERTEAM");
+ break;
+ case TC_PLAYERTYPE:
+ return std::string("TC_PLAYERTYPE");
+ break;
+ case TC_SATELLITE:
+ return std::string("TC_SATELLITE");
+ break;
+ case TC_SKIPTYPE:
+ return std::string("TC_SKIPTYPE");
+ break;
+ case TC_SOUNDDRIVER:
+ return std::string("TC_SOUNDDRIVER");
+ break;
+ case TC_TANKTYPE:
+ return std::string("TC_TANKTYPE");
+ break;
+ case TC_TURNTYPE:
+ return std::string("TC_TURNTYPE");
+ break;
+ case TC_WALLTYPE:
+ return std::string("TC_WALLTYPE");
+ break;
+ case TC_TEXTCLASS_COUNT:
+ return std::string("TC_TEXTCLASS_COUNT");
+ break;
+ case TC_FREETEXT:
+ return std::string("TC_FREETEXT");
+ break;
+ case TC_NONE:
+ return std::string("TC_NONE");
+ break;
+ default:
+ break;
+ }
+ return std::string("UNIMPLEMENTED");
+}
diff --git a/src/optiontypes.h b/src/optiontypes.h
new file mode 100644
index 0000000..41e6a33
--- /dev/null
+++ b/src/optiontypes.h
@@ -0,0 +1,128 @@
+#pragma once
+#ifndef ATANKS_SRC_OPTIONTYPES_H_INCLUDED
+#define ATANKS_SRC_OPTIONTYPES_H_INCLUDED
+
+/*
+ * atanks - obliterate each other with oversize weapons
+ *
+ * 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 menu) 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.
+ *
+ */
+
+/** @file optiontypes.h
+ *
+ * This file defines enums used for the menus.
+**/
+
+
+#include <string>
+
+
+/** @enum eMenuClass
+ * @brief List of menu classes. Every menu class is a menu in itself.
+ *
+ * MC_MENUCLASS_COUNT can be used to retrieve the number of menu classes.
+ *
+ * This enum is sorted in alphabetical order to make maintenance easier.
+**/
+enum eMenuClass
+{
+ MC_AREYOUSURE = 0, //!< The "Are you sure?" <yes> <no> screen
+ MC_FINANCE, //!< The finance ("Money") options sub menu
+ MC_GRAPHICS, //!< The graphics options sub menu
+ MC_MAIN, //!< Menu shown when using "Options" button
+ MC_NETWORK, //!< The network options sub menu
+ MC_PHYSICS, //!< The physics options sub menu
+ MC_PLAY, //!< Menu shown when using "Play" button
+ MC_PLAYER, //!< The player edit menu
+ MC_PLAYERS, //!< Menu shown when hitting "Players" button
+ MC_RESET, //!< The "Reset options?" <Reset> <Back> screen
+ MC_SOUND, //!< The sound options sub menu
+ MC_WEATHER, //!< The weather options sub menu
+ MC_MENUCLASS_COUNT
+};
+
+
+/** @enum eTextClass
+ * @brief Declare the menu option text classes.
+ *
+ * These are used so repeating texts do not need to be translated over and
+ * over again.
+ * TC_TEXTCLASS_COUNT can be used to retrieve the number of
+ * fixed menu entry text classes in OptionClassText[][][].
+ *
+ * This enum is sorted alphabetically to make maintenance easier.
+**/
+enum eTextClass
+{
+ TC_COLOUR = 0,
+ TC_LANDSLIDE,
+ TC_LANDTYPE,
+ TC_LANGUAGE,
+ TC_LIGHTNING,
+ TC_METEOR,
+ TC_MOUSE,
+ TC_OFFON,
+ TC_OFFONRANDOM,
+ TC_PLAYERPREF,
+ TC_PLAYERTEAM,
+ TC_PLAYERTYPE,
+ TC_SATELLITE,
+ TC_SKIPTYPE,
+ TC_SOUNDDRIVER,
+ TC_TANKTYPE,
+ TC_TURNTYPE,
+ TC_WALLTYPE,
+ TC_TEXTCLASS_COUNT, //!< First value after fixed text class list
+ TC_FREETEXT, //!< Special value for dynamically composed text arrays
+ TC_NONE //!< Special value for no text class at all
+};
+
+
+/** @enum eEntryType
+ * @brief Declare the different entry types option items can have
+**/
+enum eEntryType
+{
+ ET_NONE = 0,
+ ET_ACTION,
+ ET_BUTTON,
+ ET_COLOR,
+ ET_MENU,
+ ET_OPTION,
+ ET_TEXT,
+ ET_TOGGLE,
+ ET_VALUE
+};
+
+
+/** @enum eResetOptions
+ * @brief return codes for the "Are you sure" reset button question
+**/
+enum eResetOptions
+{
+ RO_BACK = 667,
+ RO_RESET = 1337
+};
+
+
+// Some helper functions to get names for enum entries
+std::string getEntryTypeName(eEntryType etype);
+std::string getMenuClassName(eMenuClass mclass);
+std::string getTextClassName(eTextClass tclass);
+
+
+#endif // ATANKS_SRC_OPTIONTYPES_H_INCLUDED
+
diff --git a/src/perlin.cpp b/src/perlin.cpp
index ed08e9b..ca62feb 100644
--- a/src/perlin.cpp
+++ b/src/perlin.cpp
@@ -79,10 +79,10 @@ double interpolate (double x1, double x2, double i)
double f = (1 - cos(ft)) * 0.5;
double result = (x1 * (1 - f) + (x2 * f));
- if (isnan(x1)||isnan(x2))
+ if (std::isnan(x1) || std::isnan(x2))
return 0.0;
- if (isnan(result))
- return (x1+x2)/2.0; /* fall back to linear interpolation */
+ if (std::isnan(result))
+ return (x1 + x2) / 2.0; /* fall back to linear interpolation */
return result;
}
diff --git a/src/physobj.cpp b/src/physobj.cpp
index 695b95e..b5b7fae 100644
--- a/src/physobj.cpp
+++ b/src/physobj.cpp
@@ -20,231 +20,614 @@
#include "physobj.h"
#include "environment.h"
+#include "globaldata.h"
-void PHYSICAL_OBJECT::initialise ()
- {
- VIRTUAL_OBJECT::initialise ();
- hitSomething = 0;
- }
+PHYSICAL_OBJECT::PHYSICAL_OBJECT(bool is_weapon) :
+ VIRTUAL_OBJECT(),
+ isWeaponFire(is_weapon)
+{ /* nothing to do here */ }
-void PHYSICAL_OBJECT::draw (BITMAP *dest)
- {
- VIRTUAL_OBJECT::draw (dest);
- }
+void PHYSICAL_OBJECT::initialise()
+{
+ VIRTUAL_OBJECT::initialise();
+ hitSomething = false;
+}
+
+
+/// @brief return true if this object was fired from a player weapon
+bool PHYSICAL_OBJECT::isWeapon()
+{
+ return isWeaponFire;
+}
+/// @brief get the current velocity. Only important for AICore to track clusters.
+void PHYSICAL_OBJECT::getVelocity(double &xv_, double &yv_)
+{
+ xv_ = xv;
+ yv_ = yv;
+}
-int PHYSICAL_OBJECT::checkPixelsBetweenPrevAndNow ()
- {
- double startX = x - xv;
- double startY = y - yv;
- if (checkPixelsBetweenTwoPoints (_global, _env, &startX, &startY, x, y))
- {
- x = startX;
- y = startY;
- return (1);
- }
+/// @brief check whether the object hit something and return true if it has
+/// Note: The objects x and y position is updated to the impact coordinates
+/// if it hit anything.
+bool PHYSICAL_OBJECT::checkPixelsBetweenPrevAndNow()
+{
+ double startX = x - xv;
+ double startY = y - yv;
- return (0);
- }
+ if (checkPixelsBetweenTwoPoints(&startX, &startY, x, y)) {
+ x = startX;
+ y = startY;
+ return true;
+ }
+
+ return false;
+}
/** @brief applyPhysics
*
- * Moves the object according to momentum and wind, boundec off of walls/ceiling/floor, and checks
- * whether something is hit.
+ * Moves the object according to momentum and wind, bounces off of
+ * walls/ceiling/floor, and checks whether something is hit.
+ *
+ * @return true if something was hit, false otherwise.
*/
-int PHYSICAL_OBJECT::applyPhysics()
+void PHYSICAL_OBJECT::applyPhysics()
{
- // Normal physics
-
- double dPrevX = x;
- double dPrevY = y;
- if (((x + xv) <= 1) || ((x + xv) >= (_global->screenWidth - 1)))
- {
- if ( (((x + xv) < 1) && checkPixelsBetweenTwoPoints (_global, _env, &dPrevX, &dPrevY, 1, y))
- ||( ((x + xv) > (_global->screenWidth - 2))
- && checkPixelsBetweenTwoPoints (_global, _env, &dPrevX, &dPrevY, (_global->screenWidth - 2), y)) )
- {
- x = dPrevX;
- y = dPrevY;
- hitSomething = true;
- }
- else
- {
- //motion - wind affected
- switch (_env->current_wallType)
- {
- case WALL_RUBBER:
- xv = -xv; //bounce on the border
- break;
- case WALL_SPRING:
- xv = -xv * SPRING_CHANGE;
- break;
- case WALL_WRAP:
- if (xv < 0)
- x = _global->screenWidth - 2; // -1 is the wall itself
- else
- x = 1;
- break;
- case WALL_STEEL:
- x += xv;
- if (x < 1)
- x = 1;
- if (x > (_global->screenWidth - 2))
- x = _global->screenWidth - 2;
- xv = 0; // already applied!
- hitSomething = 1; // blow up on steel
- break;
- }
- }
- }
- if (!hitSomething)
- xv += (double)(_env->wind - xv) / mass * drag * _env->viscosity;
-
- // hit floor or boxed top
- if ((y + yv >= (_global->screenHeight - 1))
- ||((y + yv <= MENUHEIGHT) && (_global->bIsBoxed)))
- {
- if ( ( _global->bIsBoxed && ((y + yv) <= MENUHEIGHT)
- && checkPixelsBetweenTwoPoints (_global, _env, &dPrevX, &dPrevY, x, MENUHEIGHT + 1))
- ||( ((y + yv) > (_global->screenHeight - 2))
- && checkPixelsBetweenTwoPoints (_global, _env, &dPrevX, &dPrevY, x, (_global->screenHeight - 2))) )
- {
- x = dPrevX;
- y = dPrevY;
- hitSomething = true;
- }
- else
- {
- switch (_env->current_wallType)
- {
- case WALL_RUBBER:
- yv = -yv * 0.5;
- xv *= 0.95;
- break;
- case WALL_SPRING:
- yv = -yv * SPRING_CHANGE;
- xv *= 1.05;
- break;
- default:
- // steel or wrap floor (ceiling)
- y += yv;
- if (y >= (_global->screenHeight - 1))
- y = _global->screenHeight - 2; // -1 would be the floor itself!
- else
- y = MENUHEIGHT + 1; // +1 or it would be the wall itself
- yv = 0; // already applied!
- hitSomething = 1;
- }
- if (fabs(xv) + fabs(yv) < 0.8)
- hitSomething = 1;
- }
- }
-
- // velocity check:
- /** the old calculation was wrong, but the new one, taking FPS into account, eventually works! **/
- double dActVelocity = ABSDISTANCE((long double)xv, (long double)yv, 0, 0); // a²+b²=c² ... says Pythagoras :)
- double dMaxVelocity = _global->dMaxVelocity * (1.20 + ((double)mass / ((double)MAX_POWER / 10.0)));
- // apply mass, as more mass allows more max velocity:
-
- // The default means, that a small missile can be accelerated by 25% over MAX_POWER,
- // while a large Napalm Bomb can go up to 220%.
-
- if (dActVelocity > dMaxVelocity)
- {
-#ifdef DEBUG
- cout << "** Missile too fast!" << endl;
- cout << "mass: " << mass << " - Max Velocity: " << dMaxVelocity << " (FPS: " << _global->frames_per_second << ")" << endl;
- cout << "xv: " << xv << " - yv: " << yv << " - Act Velocity: " << dActVelocity << endl;
-#endif // DEBUG
- // apply *some* velocity, as the thing is killed on its way
- y += yv / (1.0 + ((double)(rand() % 40) / 10.0)); // This produces somthing
- x += xv / (1.0 + ((double)(rand() % 40) / 10.0)); // between 1.0 and 5.0
- xv = 0.0;
- yv = 0.0;
- if (y <= MENUHEIGHT)
- y = MENUHEIGHT + 1;
- if (y > (_global->screenHeight - 2))
- y = _global->screenHeight - 2;
- if ((x < 1) && (_env->current_wallType != WALL_WRAP))
- x = 1; // Wrapped Explosion takes care for explosions off the screen
- if ((x > (_global->screenWidth - 2)) && (_env->current_wallType != WALL_WRAP))
- x = _global->screenWidth - 2; // Wrapped Explosion takes care for explosions off the screen
- hitSomething = 1;
- }
- if (!hitSomething)
- {
- x += xv;
- y += yv;
- yv += _env->gravity * (100.0 / _global->frames_per_second);
- // Barrier test:
- if ( (yv <= -1.0) && (y <= (_global->screenHeight * -25.0)))
- yv *= -1.0;
- }
- else
- {
- // if something *is* hit, ensure that that damn thing is on screen! (only y matters here!)
- if ((y < (MENUHEIGHT + 1)) && _global->bIsBoxed) y = MENUHEIGHT + 1;
- if ((y < MENUHEIGHT) && !_global->bIsBoxed) y = MENUHEIGHT;
- }
- return (hitSomething);
+ // Apply wind to x-movement
+ xv += (global.wind - xv) / mass * drag * env.viscosity;
+
+ // Apply gravity to y movement
+ yv += env.gravity * env.FPS_mod;
+
+ // Barrier test:
+ if ( (yv <= -1.0) && (y <= (env.screenHeight * -25.0)))
+ yv *= -1.0;
+
+ bool isMoving = (std::abs(xv) + std::abs(yv)) < 0.01 ? false : true;
+
+ if (!isMoving)
+ return; // early out
+
+ /* There are 6 steps:
+ * 1. Does the object hit a wall?
+ * 2. Does it hit something else before reaching the wall?
+ * 3. If nothing is hit, do the wall handling.
+ * 4. If nothing is hit and if movement is left, determine movement delta
+ * and continue with 1.
+ * 5. Does the object hit something on its way to its final destination?
+ * 6. If nothing is hit, check the object velocity and detonate if too fast.
+ */
+
+ // Shortcuts:
+ bool hasTop = env.isBoxed;
+ int32_t left = 1;
+ int32_t right = env.screenWidth - 2;
+ int32_t top = MENUHEIGHT + (hasTop ? 1 : 0);
+ int32_t bottom = env.screenHeight - 2;
+ double xv_cur = xv;
+ double yv_cur = yv;
+
+ // Special handling for Napalm Jellies if this is wrap or steel
+ // ceiling. They sort of 'glide off' of the ceiling instead of
+ // getting glued to it.
+ bool jelly = (NAPALM_JELLY == weapType)
+ && ( (WALL_STEEL == env.current_wallType)
+ || ( (WALL_WRAP == env.current_wallType)
+ && (!env.isBoxed || !env.do_box_wrap) ) )
+ ? true : false;
+
+ // Easiest way is a loop that traces the path step-wise
+ while (isMoving && !hitSomething) {
+ double currX = x;
+ double currY = y;
+ double nextX = x + xv_cur;
+ double nextY = y + yv_cur;
+ double deltaX = 0.;
+ double deltaY = 0.;
+ double hitX = 0.; // <0 = left, >0 = right
+ double hitY = 0.; // <0 = top, >0 = bottom
+ bool hitWall = false;
+ bool hitFloor = false;
+ bool hitLeft = false; // Helper to not needing to check deltaX against 0.
+ bool hitTop = false; // Helper to not needing to check deltaY against 0.
+
+ // === 1.: Does the object hit a wall ? ===
+ // ========================================
+ if (nextX < left ) {
+ deltaX = left - x;
+ hitX = deltaX / xv_cur;
+ hitWall = true;
+ hitLeft = true;
+ } else if (nextX > right ) {
+ deltaX = right - x;
+ hitX = deltaX / xv_cur;
+ hitWall = true;
+ }
+ if (nextY > bottom) {
+ deltaY = bottom - y;
+ hitY = deltaY / yv_cur;
+ hitFloor = true;
+ } else if (hasTop && (nextY < top) ) {
+ deltaY = top - y;
+ hitY = deltaY / yv_cur;
+ hitFloor = true;
+ hitTop = true;
+ }
+
+ // Note: hit[XY] is now the percentage of the full movement to the hit.
+
+ if (hitWall || hitFloor) {
+ // === 2. Does it hit something else before reaching the wall? ===
+ // Note: This is just preparation, check 5 can do this once prepared.
+ // ===============================================================
+ if (!hitFloor || (hitWall && (std::abs(hitX) <= std::abs(hitY))) ) {
+ // The X-movement hits a wall earlier or at the same time (corner).
+ nextX = hitLeft ? left : right;
+ deltaY = yv_cur * hitX;
+ nextY = y + deltaY;
+ hitFloor = false; // not reached
+ } else {
+ // The Y-movement hits bottom/top earlier
+ nextY = hitTop ? top : bottom;
+ deltaX = xv_cur * hitY;
+ nextX = x + deltaX;
+ hitWall = false; // not reached
+ if (jelly && hitTop) {
+ nextY += 1.0;
+ yv = static_cast<double>( (rand() % 10) + 1 ) / 25.00; // 0.04 - 0.40
+ xv /= static_cast<double>( (rand() % 4) + 2 ) / 1.66; // 1.20 - 3.01
+ }
+ }
+ xv_cur -= deltaX;
+ yv_cur -= deltaY;
+ }
+
+ // === 5. Does the object hit something on its way to its final ===
+ // === destination? ===
+ // Note: This is done before the wall handling (step 3/4) as it also
+ // terminates the movement towards a wall. Only if the path
+ // really is clear wall handling makes sense.
+ // =================================================================
+ if (checkPixelsBetweenTwoPoints(&currX, &currY, nextX, nextY)) {
+ xv_cur = currX - nextX;
+ yv_cur = currY - nextY;
+ nextX = currX;
+ nextY = currY;
+ if (PT_DIRTBOUNCE == physType) {
+ double rxv, ryv;
+ getDirtBounceReact(nextX, nextY, xv, yv, rxv, ryv);
+
+ // Modify rxv/ryv, this is no full bounce:
+ if (std::abs(rxv) > std::abs(ryv)) {
+ rxv *= 0.66;
+ if (ryv < 0.)
+ ryv *= 0.5;
+ } else {
+ rxv *= 0.5;
+ if (ryv < 0.)
+ ryv *= 0.66;
+ }
+
+ // See how much of the current movement is left
+ double vel_rest = FABSDISTANCE2(xv_cur, yv_cur, 0., 0.)
+ / FABSDISTANCE2(xv, yv, 0., 0.);
+
+ // Now apply what is left:
+ xv_cur = rxv * vel_rest;
+ yv_cur = ryv * vel_rest;
+ xv = rxv;
+ yv = ryv;
+ } else {
+ hitSomething = true;
+ isMoving = false;
+ }
+
+ hitWall = false;
+ hitFloor = false;
+ } else if (hitWall || hitFloor) {
+ // === 3. If nothing is hit, do the wall handling. ===
+ // ===================================================
+
+ // Note: Dirt bounce must be done first, it is handled
+ // differently but for x-wrapping
+ if ( (PT_DIRTBOUNCE == physType)
+ && ( (WALL_WRAP != env.current_wallType)
+ || hitFloor) ) {
+ if (hitWall) {
+ xv_cur *= -0.5;
+ xv *= -0.5;
+ if (yv < 0.) {
+ yv_cur *= 0.66;
+ yv *= 0.66;
+ }
+ } else {
+ // Yes, dirt does not wrap though ceilings.
+ xv_cur *= 0.66;
+ xv *= 0.66;
+ yv_cur *= nextY >= bottom ? -0.5 : -1.0;
+ yv *= nextY >= bottom ? -0.5 : -1.0;
+ }
+ } else {
+ // count the bounce:
+ ++bounces;
+
+ switch(env.current_wallType) {
+ case WALL_RUBBER:
+ if (hitWall) {
+ xv_cur = -xv_cur * BOUNCE_CHANGE;
+ xv = -xv * BOUNCE_CHANGE;
+ yv_cur *= BOUNCE_CHANGE;
+ yv *= BOUNCE_CHANGE;
+ } else {
+ yv_cur = -yv_cur * BOUNCE_CHANGE;
+ yv = -yv * BOUNCE_CHANGE;
+ xv_cur *= BOUNCE_CHANGE;
+ xv *= BOUNCE_CHANGE;
+ }
+ break;
+ case WALL_SPRING:
+ if (hitWall) {
+ xv_cur = -xv_cur * SPRING_CHANGE;
+ xv = -xv * SPRING_CHANGE;
+ } else {
+ yv_cur = -yv_cur * SPRING_CHANGE;
+ yv = -yv * SPRING_CHANGE;
+ }
+ break;
+ case WALL_WRAP:
+ if (hitWall) {
+ if (hitLeft) nextX = right;
+ else nextX = left;
+ } else if (env.isBoxed && env.do_box_wrap) {
+ if (hitTop) {
+ // Some weapons do not warp through the
+ // ceiling if the bottom pixel is occupied
+ // and trigger instead:
+ int32_t bX = ROUNDu(nextX);
+ bool floor_free = global.surface[bX].load(ATOMIC_READ)
+ >= bottom;
+ if (allowDirtyWrap || floor_free)
+ nextY = bottom;
+ else {
+ yv *= -1.;
+ hitSomething = true;
+ }
+ } else
+ nextY = top;
+ } else
+ hitSomething = true;
+ break;
+ case WALL_STEEL:
+ default:
+ hitSomething = true;
+ break;
+ } // End of wall type switch
+ }
+
+ } // End of nothing hit, wall handling needed
+
+
+ // === 6. If nothing is hit, check the object velocity and ===
+ // === detonate if too fast. (depending on the mass) ===
+ // ===========================================================
+ if (!hitSomething) {
+ double actVel = FABSDISTANCE2(xv_cur, yv_cur, 0, 0); // a²+b²=c² ... says Pythagoras :)
+ if ( (actVel > maxVel)
+ || std::isinf(xv_cur) || std::isinf(yv_cur)
+ || std::isinf(xv) || std::isinf(yv) ) {
+ // apply *some* velocity, as the thing is killed on its way
+ // (unless the current veocity is infinite of course
+ double velMod = 1.0 + ((double)(rand() % 40) / 10.0);
+ // This produces something between 1.0 and 5.0
+ if (!std::isinf(xv_cur))
+ nextX = x + (xv_cur / velMod);
+ if (!std::isinf(yv_cur))
+ nextY = y + (yv_cur / velMod);
+ xv = 0.0;
+ yv = 0.0;
+ if (nextY < top)
+ nextY = top;
+ else if (nextY > bottom)
+ nextY = bottom;
+ if (nextX < left) {
+ if (WALL_WRAP == env.current_wallType)
+ nextX = right - (static_cast<int32_t>(std::abs(nextX)) % right);
+ else
+ nextX = left;
+ } else if (nextX > right) {
+ if (WALL_WRAP == env.current_wallType)
+ nextX = static_cast<int32_t>(nextX) % right;
+ else
+ nextX = right;
+ }
+ hitSomething = true;
+ lacerated = true; // oh dear...
+ }
+
+ // If the velocities were not only partly applied due to
+ // some wall/floor hit, all movement has been used up now.
+ if (!(hitWall || hitFloor))
+ isMoving = false;
+ } // End of velocity check
+
+ // === 4. If nothing is hit and if movement is left, check ===
+ // === remaining movement and prepare for 1. or exit ===
+ // ===========================================================
+ if ( !hitSomething && isMoving && (hitWall || hitFloor)
+ && ( (std::abs(xv) + std::abs(yv)) < 0.8) )
+ // If the movement has slowed down too much, take it as a hit
+ hitSomething = true;
+ else if (!hitSomething && isMoving && (hitWall || hitFloor)
+ && ( (std::abs(xv_cur) + std::abs(yv_cur)) < 0.01) )
+ // Just stop, wall bouncing/wrapping didn't leave enough rest
+ isMoving = false;
+
+ // Finally set x/y
+ x = nextX;
+ y = nextY;
+ } // End of tracing the movement path
}
-/* --- global method --- */
-int checkPixelsBetweenTwoPoints (GLOBALDATA *global, ENVIRONMENT *env, double *startX, double *startY, double endX, double endY)
+/* --- global function --- */
+bool checkPixelsBetweenTwoPoints (double *startX, double *startY, double endX, double endY)
{
- int landFound = 0;
-
- // only check if on screen
- double xDist = endX - *startX;
- double yDist = endY - *startY;
- double length;
- double xInc;
- double yInc;
-
- if ((fabs(xDist) < 2) && (fabs(yDist) < 2))
- {
- if ( (endX > 0) // 0 is the wall!
- &&(endX < (global->screenWidth - 1)) // -1 is the wall!
- &&(endY < (global->screenHeight - 1)) // -1 is the floor!
- &&(endY > (MENUHEIGHT + 1)) // or it would always fail for high shots!
- &&(getpixel (env->terrain, (int)endX, (int)endY) != PINK) )
- return (1);
- else
- return (0);
- }
-
- length = FABSDISTANCE(xDist, yDist, 0, 0);
- yInc = yDist / length;
- xInc = xDist / length;
-
- // sanity check
- if (length > (global->screenWidth + global->screenHeight))
- length = global->screenWidth + global->screenHeight;
-
- //check all pixels along line for land
- for (int lengthPos = 0; lengthPos < length; lengthPos++)
- {
- //found land
- if ( (*startX > 0) // 0 is the wall!
- &&(*startX < (global->screenWidth - 1)) // -1 is the wall!
- &&(*startY < (global->screenHeight - 1)) // -1 is the floor!
- &&(*startY > (MENUHEIGHT + 1)) // or it would always fail for high shots!
- &&(getpixel(env->terrain, (int)*startX, (int)*startY) != PINK) )
- {
- // Leaves startX and Y at current point if within range!
- landFound = 1;
- break;
- }
- *startX += xInc;
- *startY += yInc;
- }
-
- return (landFound);
+ // return at once if there can't be any dirt in the box.
+ if (!global.isDirtInBox(*startX, *startY, endX, endY)) {
+ *startX = endX;
+ *startY = endY;
+ return false;
+ }
+
+ bool result = false;
+ double xDist = endX - *startX;
+ double yDist = endY - *startY;
+ double length = FABSDISTANCE2(xDist, yDist, 0, 0);
+
+ // Shortcuts:
+ bool hasTop = env.isBoxed;
+ double left = 1;
+ double right = env.screenWidth - 2;
+ double top = MENUHEIGHT + (hasTop ? 1 : 0);
+ double bottom = env.screenHeight - 2;
+
+
+ // Drop out early if a neighbouring pixel is checked and it is a hit
+ if (length < 2.) {
+ if ( (endX > left)
+ && (endX < right)
+ && (endY > top)
+ && (endY < bottom) ) {
+
+ if (PINK != getpixel(global.terrain, endX, endY) ) {
+ *startX = endX;
+ *startY = endY;
+ result = true;
+ }
+
+ } // End of having a valid position
+ return result;
+ } // End of early drop out
+
+ // Otherwise the path must be checked
+ double xInc = xDist / length;
+ double yInc = yDist / length;
+
+ // sanity check
+ if (length > (env.screenWidth + env.screenHeight))
+ length = env.screenWidth + env.screenHeight;
+
+ // check all pixels along the line for land
+
+ // As xInc/yInc are known now, left, right, top and bottom can
+ // be corrected if the line would not leave the screen.
+ left = std::min(std::min(*startX, left), *startX + (length * xInc));
+ top = std::min(std::min(*startY, top), *startY + (length * yInc));
+ right = std::max(std::max(*startX, right), *startX + (length * xInc));
+ bottom = std::max(std::max(*startY, bottom), *startY + (length * yInc));
+
+ // Note: Start with 1 and increase startX/Y first, as
+ // the starting pixel can be assumed to be clean.
+ for (int32_t pos = 1; !result && (pos < length); ++pos) {
+ *startX += xInc;
+ *startY += yInc;
+
+ if ( (*startX > left)
+ && (*startX < right)
+ && (*startY > top)
+ && (*startY < bottom) ) {
+
+ if (PINK != getpixel (global.terrain, *startX, *startY) )
+ result = true;
+ // Note: startX/startY now point to the hit pixel
+
+ } // End of having a valid position
+ } // End of walking positions
+
+ // If nothing was hit, make sure startX/Y point to endX/Y
+ if (!result) {
+ *startX = endX;
+ *startY = endY;
+ }
+
+ return result;
}
+/** @brief Return the reaction velocity values of an object that hits dirt
+ *
+ * The function analyses the pixels around @a x and @a y to find
+ * a plane the vector @a xv / @a yv has an angle to and returns
+ * appropriate reaction velocity values in @a rxv and @a ryv.
+**/
+void getDirtBounceReact(int32_t x, int32_t y, double xv, double yv,
+ double &rxv, double &ryv)
+{
+ int32_t from_x = xv < 0. ? 1 : -1;
+ double vel = FABSDISTANCE2(xv, yv, 0., 0.);
+
+ // First find the heights around x/y:
+ int32_t y_map[5];
+ for (int i = 0 ; i < 5 ; ++i) {
+ int32_t xpos = x + (i - 2);
+ int32_t min_y = y - 2;
+ int32_t max_y = y + 2;
+ int32_t start_y = std::max(min_y, MENUHEIGHT);
+ int32_t stop_y = std::min(max_y + 1, env.screenHeight);
+ y_map[i] = -1;
+
+ if ( (xpos > 0) && (xpos < env.screenWidth)
+ && (min_y < env.screenHeight) && (max_y > MENUHEIGHT)
+ && ( (stop_y - start_y) > 0) ) {
+ y_map[i] = 5;
+
+ for (int32_t j = 4; (j >= 0) && (5 == y_map[i]); --j) {
+ int32_t ypos = y + (j - 2);
+
+ if ( (ypos < start_y)
+ || (ypos >= stop_y)
+ || (PINK != getpixel(global.terrain, xpos, ypos)) )
+ y_map[i] = j;
+ }
+ }
+ } // End of generating height map.
+
+ /* atan2(x, y) will be used which results in the following angles:
+ * Only right 1/ 0 : 90 (270) (The number in brackets is the normalized
+ * Only left -1/ 0 : - 89 ( 91) angle used in atanks)
+ * Only down 0/ 1 : 0 (180)
+ * Only up 0/-1 : 180 (360)
+ * Down right 1/ 1 : 45 (225)
+ * Up right 1/-1 : 135 (315)
+ * Down left -1/ 1 : - 44 (136)
+ * Up left -1/-1 : -134 ( 46)
+ *
+ * The plane is calculated using the same x direction if present.
+ * If the plane is a vertical wall, only the x-movement is reversed using
+ * a plane angle of 0.
+ *
+ * HA = Hit Angle, PA = Plane Angle, RA = Reaction Angle
+ *
+ * 1: HA = PA - Movement angle
+ * 2: RA = PA + (PA - HA)
+ * 3: Values > 180 gets 360 substracted
+ * 4: Value < -180 gets 360 added
+ *
+ * Examples:
+ * 1: Shot from left to right and down (down right = 1/1 = 45°)
+ * Plane is horizontal (only right = 1/0 = 90°)
+ * HA = 90 - 45 = 45
+ * RA = 90 + (90 - 45) = 135 (Up right)
+ * 2: Shot is the same, but the plane goes up right (1/-1 = 135°)
+ * HA = 135 - 45 = 90
+ * RA = 135 + (135 - 45) = 225 => 225 - 360 = -135 (Up left)
+ * 3: Shot from right to left and down (down left = -1/1 = -45)
+ * Plane is horizontal (only left = -1/0 = -90°)
+ * HA = -90 - -45 = -45
+ * RA = -90 + (-90 - -45) = -135 (Up left)
+ * 4: Shot from the right bottom (up-left (-1/-1) = -135)
+ * Plane is a vertical wall (only down = 0)
+ * HA = 0 - -135 = 135 = RA
+ * 5: Shot from right to left down (-1/1) = -40
+ * Plane up left (-1/-1) = -135
+ * HA = -135 - -40 = -95
+ * RA = -135 + (-135 - -95) = -175
+ */
+
+ // Find a plane with angle using the height map
+ int32_t MA = GET_ANGLE(xv, yv);
+ int32_t PA = 0, HA = 0, RA = 0;
+
+ // Look for the special case of a vertical wall first:
+ if ( (y_map[2] < 2) && (y_map[2 + from_x] > 3) )
+ if (MA)
+ RA = 0 - MA;
+ else
+ // Just let it drip off
+ RA = 5 * (y_map[3] ? 1 : -1);
+ else {
+ // Here a plane must be determined.
+ double x1 = 2., y1 = y_map[2];
+ double x2 = 2., y2 = y_map[2];
+ double p1 = 1., p2 = 1.;
+
+ // If y2 is 0, no further look is needed into movement direction
+ if (y2 > 0.) {
+ // Ah, it is.
+ int32_t ly1 = y_map[2 + (-1 * from_x)];
+ int32_t ly2 = y_map[2 + (-2 * from_x)];
+
+ if (ly1 > -1) {
+ x2 += 2. + (-1. * from_x);
+ y2 += ly1;
+ p2 += 1.;
+
+ // Must ly2 be considered?
+ if (ly2 > -1) {
+ if ( ( (ly1 <= y_map[2]) && (ly2 <= ly1) )
+ || ( (ly1 >= y_map[2]) && (ly2 >= ly1) ) ) {
+ x2 += 2. + (-2. * from_x);
+ y2 += ly2;
+ p2 += 1.;
+ }
+ } // End of having a non-wall ly2
+
+ // Set final x2/y2:
+ x2 /= p2;
+ y2 /= p2;
+ } // End of having a non-wall ly1
+ } // end of having a non-vertical hit plane.
+
+ // For the 'from'-direction the already set y2 can be used
+ int32_t ly1 = y_map[2 + from_x];
+ int32_t ly2 = y_map[2 + (2 * from_x)];
+
+ if ( (ly1 > -1)
+ && ( ( (ly1 >= y_map[2])
+ && (ly1 >= y2) )
+ || ( (ly1 <= y_map[2])
+ && (ly1 <= y2) ) ) ) {
+ x1 += 2. + (1. * from_x);
+ y1 += ly1;
+ p1 += 1.;
+
+ // Only continue if useful.
+ if (ly2 > -1) {
+ if ( ( (ly1 <= y_map[2]) && (ly2 <= ly1) )
+ || ( (ly1 >= y_map[2]) && (ly2 >= ly1) ) ) {
+ x1 += 2. + (2. * from_x);
+ y1 += ly2;
+ p1 += 1.;
+ }
+ } // end of having a non-wall ly2
+
+ // Set final x1/y1:
+ x1 /= p1;
+ y1 /= p1;
+ } // End of having a non-wall ly1 without contra-slope.
+
+ // Set resulting angles:
+ PA = GET_ANGLE(x2 - x1, y2 - y1);
+ HA = PA - MA;
+ RA = PA + (PA - HA);
+
+ // Secure against vertical drop traps:
+ if (!MA && !PA && !HA)
+ // RA is now 0 but must be 180
+ RA = 180;
+
+ if (RA > 180) RA -= 360;
+ if (RA < -180) RA += 360;
+ } // End of plane determination
+
+ // The [R]eaction [A]angle now has to be translated into
+ // atanks compatible velocity values:
+ if (RA < 0) RA += 360; // atanks range
+
+ rxv = env.slope[RA][0] * vel;
+ ryv = env.slope[RA][1] * vel;
+}
diff --git a/src/physobj.h b/src/physobj.h
index 32cfd72..06c8551 100644
--- a/src/physobj.h
+++ b/src/physobj.h
@@ -20,40 +20,103 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* */
-
-#include "main.h"
+#include "globaltypes.h"
#include "virtobj.h"
-#include "globaldata.h"
-//#include "environment.h"
+
+// Switch sides (real angle!)
+#define FLIP_ANGLE(angle_) (180 + (180 - (angle_)))
+
+// Get the direct angle without any checks
+#define GET_ANGLE(x,y) ([](double a, double b)->int32_t { \
+ double result = RAD2DEG(std::atan2(a, b)); \
+ /* atan2 returns an angle with 180° up, 90° right */ \
+ /* and -90° left. But we need it from 90° right to */ \
+ /* 270° left counter-clockwise. */ \
+ if (result < 0) result += 360.; \
+ return ROUND(result); \
+}(static_cast<double>(x), static_cast<double>(y)))
+
+
+// Get the angle brought into the 90-270 degree range
+// To be usable more widely, this macro allows an additional
+// argument "m", which is the angle modifier (errors made
+// by the AI and such things)
+#define GET_SAFE_ANGLE(x,y,m) ([](double a, double b, double c)->int32_t { \
+ double result = RAD2DEG(std::atan2(a, b)) + c; \
+ if (result < 0.) result += 360.; \
+ if (result < 90.) result = 90.; \
+ else if (result > 270.) result = 270.; \
+ return ROUND(result); \
+}(static_cast<double>(x), static_cast<double>(y), static_cast<double>(m)))
+
+// Re-calculate angle_ into a value displayable on the top bar:
+#define GET_DISP_ANGLE(angle_) (180 - ((angle_) - 90))
+
class PHYSICAL_OBJECT: public VIRTUAL_OBJECT
- {
- public:
- int noimpact;
- int hitSomething;
- double mass;
- double drag;
- int spin;
+{
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+ explicit PHYSICAL_OBJECT(bool is_weapon);
+ // No explicit dtor needed
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ virtual void draw () _PURE;
+ void getVelocity(double &xv_, double &yv_);
+ bool isWeapon();
+
+
+ /* ----------------------
+ * --- Public members ---
+ * ----------------------
+ */
+
+ bool allowDirtyWrap = true; //!< Whether ceiling wrap is allowed into dirt bottom
+ double drag = 0.;
+ bool hitSomething = false;
+ int32_t weapType = 0;
+
+protected:
- /* --- constructor --- */
- PHYSICAL_OBJECT ():VIRTUAL_OBJECT(),hitSomething(0) { }
+ /* -------------------------
+ * --- Protected methods ---
+ * -------------------------
+ */
- /* --- pure virtual methods --- */
- virtual int isSubClass (int classNum)_PURE;
- virtual int getClass ()_PURE;
+ void applyPhysics ();
+ bool checkPixelsBetweenPrevAndNow ();
+ void initialise ();
- /* --- non-virtual methods --- */
- /* --- inline methods --- */
- int applyPhysics ();
+ /* -------------------------
+ * --- Protected members ---
+ * -------------------------
+ */
+ int32_t bounces = 0; //!< Bounces off walls, floor and ceiling
+ bool isWeaponFire = true;
+ bool lacerated = false; //!< Set to true if the velocity check fails.
+ double mass = 0.;
+ double maxVel = 0.; //!< maximum Velocity
+ bool noimpact = false;
+ int32_t spin = 0;
- int checkPixelsBetweenPrevAndNow ();
+};
- void initialise ();
- void draw (BITMAP *dest);
- };
+/// global helper methods:
+bool checkPixelsBetweenTwoPoints(double *startX, double *startY,
+ double endX, double endY);
+void getDirtBounceReact(int32_t x, int32_t y, double xv, double yv,
+ double &rxv, double &ryv);
#endif
diff --git a/src/player.cpp b/src/player.cpp
index bbcf37f..573a609 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -26,4521 +26,940 @@
#include "files.h"
#include "floattext.h"
#include "network.h"
+#include "missile.h"
+#include "aicore.h"
-// When defined draws AI 'planning'
-//#define AI_PLANNING_DEBUG 1
+#include <cassert>
-PLAYER::PLAYER (GLOBALDATA *global, ENVIRONMENT *env):_global(global),_env(env),_turnStage(0),_target(NULL),
- _oldTarget(NULL),iTargettingRound(0),revenge(NULL),tank(NULL),pColor(NULL),menuopts(NULL),menudesc(NULL)
-{
- int my_colour;
-
- money = 15000;
- score = 0;
- played = 0;
- won = 0;
- tank = NULL;
- team = TEAM_NEUTRAL;
-
- iBoostItemsBought = -1;
-
- selected = FALSE;
- changed_weapon = false;
-
-
- tank_bitmap = 0.0;
-
- nm[0] = 99;
- for (int count = 1; count < WEAPONS; count++)
- nm[count] = 0;
-
- for (int count = 0; count < ITEMS; count++)
- ni[count] = 0;
-
- strncpy (_name, "New", NAME_LENGTH);
- type = HUMAN_PLAYER;
-
- // 25% of time set to perplay weapon preferences
- preftype = (rand () % 4)?ALWAYS_PREF:PERPLAY_PREF;
-// generatePreferences (); <-- not here!
-
- defensive = (double)((rand () % 10000) - 5000) / 5000.0;
- vengeful = rand () % 100;
- vengeanceThreshold = (double)(rand () % 1000) / 1000.0;
- revenge = NULL;
- /** @TODO: These two values are nowhere used.
- * We should think about a usage, because bot characteristics could become
- * alot more wide spread with their help. **/
- selfPreservation = (double)(rand () % 3000) / 1000;
- painSensitivity = (double)(rand () % 3000) / 1000;
-
- // color = rand () % WHITE;
- my_colour = rand() % 4;
- switch (my_colour)
- {
- case 0:
- color = makecol(200 + (rand() % 56), rand() % 25, rand() % 25);
- break;
- case 1:
- color = makecol( rand() % 25, 200 + (rand() % 56), rand() % 25);
- break;
- case 2:
- color = makecol( rand() % 25, rand() % 25, 200 + (rand() % 56) );
- break;
- case 3:
- color = makecol( 200 + (rand() % 56), rand() % 25, 200 + (rand() % 56));
- break;
- }
- typeText[0] = global->ingame->complete_text[54];
- typeText[1] = global->ingame->complete_text[55];
- typeText[2] = global->ingame->complete_text[56];
- typeText[3] = global->ingame->complete_text[57];
- typeText[4] = global->ingame->complete_text[58];
- typeText[5] = global->ingame->complete_text[59];
- preftypeText[0] = global->ingame->complete_text[60];
- preftypeText[1] = global->ingame->complete_text[61];
- tank_type[0] = global->ingame->complete_text[62];
- tank_type[1] = global->ingame->complete_text[63];
- tank_type[2] = global->ingame->complete_text[64];
- tank_type[3] = global->ingame->complete_text[65];
- tank_type[4] = global->ingame->complete_text[73];
- tank_type[5] = global->ingame->complete_text[74];
- tank_type[6] = global->ingame->complete_text[75];
- tank_type[7] = global->ingame->complete_text[76];
- teamText[0] = global->ingame->complete_text[66];
- teamText[1] = global->ingame->complete_text[67];
- teamText[2] = global->ingame->complete_text[68];
-
- // Weapon Preferences need to be initalized!
- for (int weapCount = 0; weapCount < THINGS; weapCount++)
- _weaponPreference[weapCount] = 0;
-
- menudesc = NULL;
- menuopts = NULL;
- initMenuDesc ();
- skip_me = false;
- #ifdef NETWORK
- server_socket = 0;
- #endif
-}
-
-/*
-PLAYER::PLAYER (GLOBALDATA *global, ENVIRONMENT *env, ifstream &ifsFile, bool file):_global(global),_env(env),
- _target(NULL),revenge(NULL),tank(NULL),pColor(NULL),menuopts(NULL),menudesc(NULL)
-{
- money = 15000;
- score = 0;
- _turnStage = 0;
- selected = FALSE;
-
- tank_bitmap = 0.0;
- team = TEAM_NEUTRAL;
-
- iBoostItemsBought = -1;
-
- nm[0] = 99;
- for (int count = 1; count < WEAPONS; count++)
- nm[count] = 0;
-
- for (int count = 0; count < ITEMS; count++)
- ni[count] = 0;
-
- if (file)
- loadFromFile(ifsFile);
-
- type = (int)type;
-
- typeText[0] = "Human";
- typeText[1] = "Useless";
- typeText[2] = "Guesser";
- typeText[3] = "Ranger";
- typeText[4] = "Targetter";
- typeText[5] = "Deadly";
- preftypeText[0] = "Per Game";
- preftypeText[1] = "Only Once";
- tank_type[0] = "Normal";
- tank_type[1] = "Classic";
- tank_type[2] = "Big Grey";
- tank_type[3] = "T34";
- teamText[0] = "Sith";
- teamText[1] = "Neutral";
- teamText[2] = "Jedi";
- initMenuDesc ();
- skip_me = false;
- #ifdef NETWORK
- net_command[0] = '\0';
- #endif
-}
-*/
-
-
-int PLAYER::getBoostValue()
-{
- return ((int)(ni[ITEM_ARMOUR] * ((double) item[ITEM_ARMOUR].vals[0] / (double) item[ITEM_PLASTEEL].vals[0])) + ni[ITEM_PLASTEEL] +
- (int)(ni[ITEM_INTENSITY_AMP] * ((double) item[ITEM_INTENSITY_AMP].vals[0] / (double) item[ITEM_VIOLENT_FORCE].vals[0])) +
- ni[ITEM_VIOLENT_FORCE]);
-}
-
-
-
-void PLAYER::setComputerValues (int aOffset)
-{
- int iType = (int) type + aOffset;
- if (iType > (int) DEADLY_PLAYER)
- iType = (int) DEADLY_PLAYER;
- rangeFindAttempts = (int)(pow(iType + 1, 2) + 1); // Useless: 5 , Deadly: 37
- retargetAttempts = (int)(pow(iType, 2) + 1); // Useless: 2 , Deadly: 26
- focusRate = ( (double) iType * 2.0) / 10.0; // Useless: 0.2, Deadly: 1.0
- errorMultiplier = (double)(DEADLY_PLAYER + 1 - iType) / (double)rangeFindAttempts;
- if (errorMultiplier > 2.0) errorMultiplier = 2.0;
-}
-
-int displayPlayerName (GLOBALDATA *global, ENVIRONMENT *env, int x, int y, void *data);
-void PLAYER::initMenuDesc ()
-{
- GLOBALDATA *global = _global;
- int destroyPlayer (GLOBALDATA *global, ENVIRONMENT *env, void *data);
- int i = 0;
- int height = -68;
-
- // before we get started, eraw any old version of the menu
- if (menuopts)
- free(menuopts);
- if (menudesc)
- free(menudesc);
-
- // menudesc = new MENUDESC;
- menudesc = (MENUDESC *) calloc(1, sizeof(MENUDESC));
- if (!menudesc)
- {
- perror ( "player.cc: Failed allocating memory for menudesc in PLAYER::initMenuDesc");
- return;
- // exit (1);
- }
- menudesc->title = _name;
-
- // Name,Color
- menudesc->numEntries = 10;
- menudesc->okayButton = TRUE;
- menudesc->quitButton = FALSE;
-
- // menuopts = new MENUENTRY[menudesc->numEntries];
- menuopts = (MENUENTRY *) calloc(menudesc->numEntries, sizeof(MENUENTRY));
- if (!menuopts)
- {
- perror ( "player.cc: Failed allocating memory for menuopts in PLAYER::initMenuDesc");
- return;
- // exit (1);
- }
-
- //init memory
- // memset(menuopts, 0, menudesc->numEntries * sizeof(MENUENTRY));
-
- // Player name
- menuopts[i].name = global->ingame->complete_text[29];
- menuopts[i].displayFunc = NULL;
- menuopts[i].color = color;
- menuopts[i].value = (double*)_name;
- menuopts[i].specialOpts = NULL;
- menuopts[i].type = OPTION_TEXTTYPE;
- menuopts[i].viewonly = FALSE;
- menuopts[i].x = _global->halfWidth - 3;
- menuopts[i].y = _global->halfHeight + height;
- i++;
- height += 20;
-
- // Player colour
- menuopts[i].name = global->ingame->complete_text[30];
- menuopts[i].displayFunc = NULL;
- menuopts[i].color = WHITE;
- pColor = &color;
- menuopts[i].value = (double*) pColor;
- menuopts[i].specialOpts = NULL;
- menuopts[i].type = OPTION_COLORTYPE;
- menuopts[i].viewonly = FALSE;
- menuopts[i].x = _global->halfWidth - 3;
- menuopts[i].y = _global->halfHeight + height;
- i++;
- height += 20;
-
- // Player type (human, computer)
- menuopts[i].name = global->ingame->complete_text[31];
- menuopts[i].displayFunc = NULL;
- menuopts[i].color = WHITE;
- menuopts[i].value = (double*)&type;
- menuopts[i].min = 0;
- menuopts[i].max = LAST_PLAYER_TYPE - 1;
- menuopts[i].increment = 1;
- menuopts[i].defaultv = 0;
- menuopts[i].format = "%s";
- menuopts[i].specialOpts = typeText;
- menuopts[i].type = OPTION_SPECIALTYPE;
- menuopts[i].viewonly = FALSE;
- menuopts[i].x = _global->halfWidth - 3;
- menuopts[i].y = _global->halfHeight + height;
- i++;
- height += 20;
-
- menuopts[i].name = global->ingame->complete_text[20];
- menuopts[i].displayFunc = NULL;
- menuopts[i].color = WHITE;
- menuopts[i].value = (double *)&team;
- menuopts[i].min = 0;
- menuopts[i].max = TEAM_JEDI;
- menuopts[i].increment = 1;
- menuopts[i].defaultv = TEAM_NEUTRAL;
- menuopts[i].format = "%s";
- menuopts[i].specialOpts = teamText;
- menuopts[i].type = OPTION_SPECIALTYPE;
- menuopts[i].viewonly = FALSE;
- menuopts[i].x = _global->halfWidth - 3;
- menuopts[i].y = _global->halfHeight + height;
- i++;
- height += 20;
-
- // Player preftype (human, computer)
- menuopts[i].name = global->ingame->complete_text[32];
- menuopts[i].displayFunc = NULL;
- menuopts[i].color = WHITE;
- menuopts[i].value = (double*)&preftype;
- menuopts[i].min = 0;
- menuopts[i].max = ALWAYS_PREF;
- menuopts[i].increment = 1;
- menuopts[i].defaultv = 0;
- menuopts[i].format = "%s";
- menuopts[i].specialOpts = preftypeText;
- menuopts[i].type = OPTION_SPECIALTYPE;
- menuopts[i].viewonly = FALSE;
- menuopts[i].x = _global->halfWidth - 3;
- menuopts[i].y = _global->halfHeight + height;
- i++;
- height += 20;
-
- menuopts[i].name = global->ingame->complete_text[33];
- menuopts[i].displayFunc = NULL;
- menuopts[i].color = WHITE;
- menuopts[i].value = (double*)&played;
- menuopts[i].format = "%.0f";
- menuopts[i].specialOpts = NULL;
- menuopts[i].type = OPTION_DOUBLETYPE;
- menuopts[i].viewonly = TRUE;
- menuopts[i].x = _global->halfWidth - 3;
- menuopts[i].y = _global->halfHeight + height;
- i++;
- height += 20;
-
- menuopts[i].name = global->ingame->complete_text[34];
- menuopts[i].displayFunc = NULL;
- menuopts[i].color = WHITE;
- menuopts[i].value = (double*)&won;
- menuopts[i].format = "%.0f";
- menuopts[i].specialOpts = NULL;
- menuopts[i].type = OPTION_DOUBLETYPE;
- menuopts[i].viewonly = TRUE;
- menuopts[i].x = _global->halfWidth - 3;
- menuopts[i].y = _global->halfHeight + height;
- i++;
- height += 20;
-
- menuopts[i].name = global->ingame->complete_text[35];
- menuopts[i].displayFunc = Display_Tank_Bitmap;
- menuopts[i].color = WHITE;
- menuopts[i].value = &tank_bitmap;
- menuopts[i].min = 0;
- menuopts[i].max = TANK_TYPES;
- menuopts[i].increment = 1;
- menuopts[i].defaultv = 0;
- menuopts[i].format = "%1.0f";
- menuopts[i].specialOpts = tank_type;
- menuopts[i].type = OPTION_SPECIALTYPE;
- menuopts[i].viewonly = FALSE;
- menuopts[i].x = _global->halfWidth - 3;
- menuopts[i].y = _global->halfHeight + height;
- i++;
- height += 20;
-
- menuopts[i].name = global->ingame->complete_text[36];
- menuopts[i].displayFunc = NULL;
- menuopts[i].color = WHITE;
- menuopts[i].value = (double*)destroyPlayer;
- menuopts[i].data = (void*)this;
- menuopts[i].type = OPTION_ACTIONTYPE;
- menuopts[i].viewonly = FALSE;
- menuopts[i].x = _global->halfWidth - 3;
- menuopts[i].y = _global->halfHeight + height;
- i++;
- height += 20;
-
- menudesc->entries = menuopts;
-}
-
-PLAYER::~PLAYER ()
-{
- if (tank)
- delete(tank);
-
- if (menuopts)
- free(menuopts);
- // delete(menuopts);
- if (menudesc)
- free(menudesc);
- // delete(menudesc);
-
- _global = NULL;
- _env = NULL;
- _target = NULL;
- revenge = NULL;
-
- pColor = NULL;
-}
-
-
-
-
-/*
-Save the player settings in text form. Fields are
-formateed as
-[name]=[value]\n
-
-Function returns true on success and false on failure.
-*/
-int PLAYER::saveToFile_Text (FILE *file)
-{
- int count;
-
- if (! file) return FALSE;
- // start section with (char *)"*PLAYER*"
- fprintf (file, "*PLAYER*\n");
- fprintf (file, "NAME=%s\n", _name);
- fprintf (file, "VENGEFUL=%d\n", vengeful);
- fprintf (file, "VENGEANCETHRESHOLD=%lf\n", vengeanceThreshold);
- fprintf (file, "TYPE=%lf\n", type);
- fprintf (file, "TYPESAVED=%lf\n", type_saved);
- fprintf (file, "COLOR=%d\n", color);
- fprintf (file, "COLOR2=%d\n", color2);
- for (count = 0; count < THINGS; count++)
- fprintf (file, "WEAPONPREFERENCES=%d %d\n", count, _weaponPreference[count]);
-
- fprintf (file, "PLAYED=%lf\n", played);
- fprintf (file, "WON=%lf\n", won);
- fprintf (file, "PREFTYPE=%lf\n", preftype);
- fprintf (file, "SELFPRESERVATION=%lf\n", selfPreservation);
- fprintf (file, "PAINSENSITIVITY=%lf\n", painSensitivity);
- fprintf (file, "DEFENSIVE=%lf\n", defensive);
- fprintf (file, "TANK_BITMAP=%lf\n", tank_bitmap);
- fprintf (file, "TEAM=%lf\n", team);
- fprintf (file, "***\n");
- return TRUE;
-}
-
-
-/*
-This function tries to load player data from a text file.
-Each line is parsed for (char *)"field=value", except WEAPONPREFERENCES
-which is parsed (char *)"field=index value".
-If all goes well TRUE is returned, on error the function returns FALSE.
--- Jesse
-*/
-
-int PLAYER::loadFromFile_Text (FILE *file)
-{
- char line[MAX_CONFIG_LINE];
- int equal_position, line_length;
- int index, wp_value;
- char field[MAX_CONFIG_LINE], value[MAX_CONFIG_LINE];
- char *result = NULL;
- bool done = false;
-
- if (! file) return FALSE;
-
- // read until we hit line (char *)"*PLAYER*" or "***" or EOF
- do
- {
- result = fgets(line, MAX_CONFIG_LINE, file);
- if (! result) // eof
- return FALSE;
- if (! strncmp(line, "***", 3) ) // end of record
- return FALSE;
- }
- while ( strncmp(line, "*PLAYER*", 8) ); // read until we hit new record
-
- while ( (result) && (!done) )
- {
- // read a line
- memset(line, '\0', MAX_CONFIG_LINE);
- result = fgets(line, MAX_CONFIG_LINE, file);
- if (result)
- {
- // if we hit end of the record, stop
- if (! strncmp(line, "***", 3) ) return TRUE;
- // find equal sign
- line_length = strlen(line);
- // strip newline character
- if ( line[line_length - 1] == '\n')
- {
- line[line_length - 1] = '\0';
- line_length--;
- }
- equal_position = 1;
- while ( ( equal_position < line_length) && (line[equal_position] != '=') )
- equal_position++;
- // make sure we have valid equal sign
- if (equal_position <= line_length)
- {
- // seperate field from value
- memset(field, '\0', MAX_CONFIG_LINE);
- memset(value, '\0', MAX_CONFIG_LINE);
- strncpy(field, line, equal_position);
- strcpy(value, & (line[equal_position + 1]));
- // check which field we have and process value
- if (! strcasecmp(field, "name") )
- strncpy(_name, value, NAME_LENGTH);
- else if (! strcasecmp(field, "vengeful") )
- sscanf(value, "%d", &vengeful);
- else if (! strcasecmp(field, "vengeancethreshold") )
- sscanf(value, "%lf", &vengeanceThreshold);
- else if (! strcasecmp(field, "type") )
- sscanf(value, "%lf", &type);
- else if (! strcasecmp(field, "typesaved") )
- {
- sscanf(value, "%lf", &type_saved);
- if (type_saved > HUMAN_PLAYER)
- type = type_saved;
- }
- else if (! strcasecmp(field, "color") )
- sscanf(value, "%d", &color);
- else if (! strcasecmp(field, "color2") )
- sscanf(value, "%d", &color2);
- else if (! strcasecmp(field, "played") )
- sscanf(value, "%lf", &played);
- else if (! strcasecmp(field, "won") )
- sscanf(value, "%lf", &won);
- else if (! strcasecmp(field, "preftype") )
- sscanf(value, "%lf", &preftype);
- else if (! strcasecmp(field, "selfpreservation") )
- sscanf(value, "%lf", &selfPreservation);
- else if (! strcasecmp(field, "painsensititveity") )
- sscanf(value, "%lf", &painSensitivity);
- else if (! strcasecmp(field, "defensive") )
- sscanf(value, "%lf", &defensive);
- else if (! strcasecmp(field, "tank_bitmap") )
- sscanf(value, "%lf", &tank_bitmap);
- else if (! strcasecmp(field, "team") )
- sscanf(value, "%lf", &team);
- else if (! strcasecmp(field, "weaponpreferences") )
- {
- sscanf(value, "%d %d", &index, &wp_value);
- if ( (index < THINGS) && (index >= 0) )
- _weaponPreference[index] = wp_value;
- }
-
- } // end of valid data line
- } // end of if we read a line properly
- } // end of while not done
-
- // make sure previous human players are restored as humans
- if (type == PART_TIME_BOT)
- type = HUMAN_PLAYER;
-
- return TRUE;
-}
-
-
-
-
-void PLAYER::exitShop ()
-{
- double tmpDM = 0;
-
- damageMultiplier = 1.0;
- tmpDM += ni[ITEM_INTENSITY_AMP] * item[ITEM_INTENSITY_AMP].vals[0];
- tmpDM += ni[ITEM_VIOLENT_FORCE] * item[ITEM_VIOLENT_FORCE].vals[0];
- if (tmpDM > 0)
- damageMultiplier += pow (tmpDM, 0.6);
-}
-
-
-// run this at the begining of each turn
-void PLAYER::newRound ()
-{
- // if the player is under computer control, give it back to the player
- if ( type == PART_TIME_BOT )
- type = HUMAN_PLAYER;
-
- setComputerValues ();
-
- if (!tank)
- {
- tank = new TANK(_global, _env);
- if (tank)
- {
- tank->player = this;
- tank->initialise();
- }
- else
- perror ( "player.cc: Failed allocating memory for tank in PLAYER::newRound");
- }
- // newRound() doesn't need to be called, because ENVIRONMENT::newRound() has already done that!
-
- changed_weapon = false;
- // if we are playing in a campaign, raise the AI level
- if (_global->campaign_mode)
- {
- if ( (type > HUMAN_PLAYER) && (type < DEADLY_PLAYER) )
- type += 1.0;
- }
-
- // forget revenge under certain circumstances
- if (revenge)
- {
- if ((team != TEAM_NEUTRAL) && (team == revenge->team))
- revenge = NULL; // No more round breaking revenge on team mates!
- else if ( (team == TEAM_NEUTRAL)
- ||((team != TEAM_NEUTRAL) && (revenge->team == TEAM_NEUTRAL)))
- {
- // neutral to !neutral and vice versa might forget...
- if ( (!(rand() % (int)labs((type + 3) / 2)))
- ||((rand() % 100) > vengeful) )
- revenge = NULL;
- /* This gives:
- * USELESS: (1 + 3) / 2) = 2 => 50%
- * GUESSER: (2 + 3) / 2) = 2 => 50%
- * RANGEFI: (3 + 3) / 2) = 3 => 34%
- * TARGETT: (4 + 3) / 2) = 3 => 34%
- * DEADLY : (5 + 3) / 2) = 4 => 25%
- * chance to "forgive". Should be okay...
- * The check against "vengeful" makes "peacefull" bots to forget more easily
- */
- }
- }
-
- if (!revenge && (rand() % ((int)type + 1)))
- {
- // If there is no revengee there is a small chance the player will seek the leader!
- int iMaxScore = score;
- int iCurScore = 0;
- for (int i = 0; i < _global->numPlayers; i++)
- {
- if (_global->players[i])
- {
- iCurScore = _global->players[i]->score;
- if ( (iCurScore > iMaxScore)
- &&( (team == TEAM_NEUTRAL)
- ||(team != _global->players[i]->team)) )
- {
- // Higher score found, record as possible revengee
- iMaxScore = iCurScore;
- if (abs(iMaxScore - score) > (int)type)
- revenge = _global->players[i];
- }
- }
- }
- }
-
- time_left_to_fire = (int) _global->max_fire_time;
- skip_me = false;
- iTargettingRound = 0;
- _target = NULL;
- _oldTarget = NULL;
- last_shield_used = 0;
-}
-
-
-void PLAYER::initialise ()
-{
- long int totalPrefs;
- int rouletteCount;
-
- nm[0] = 99;
- for (int count = 1; count < WEAPONS; count++)
- nm[count] = 0;
-
- for (int count = 0; count < ITEMS; count++)
- ni[count] = 0;
-
- totalPrefs = 0;
- for (int weapCount = 0; weapCount < THINGS; weapCount++)
- totalPrefs += _weaponPreference[weapCount];
-
- rouletteCount = 0;
- for (int weapCount = 0; weapCount < THINGS; weapCount++)
- {
- int weapRSpace = (int)((double)_weaponPreference[weapCount] / totalPrefs * NUM_ROULETTE_SLOTS);
- int weapRCount = 0;
-
- if (weapRSpace < 1)
- weapRSpace = 1;
- while (weapRCount < weapRSpace && rouletteCount + weapRCount < NUM_ROULETTE_SLOTS)
- {
- _rouletteWheel[rouletteCount + weapRCount] = weapCount;
- weapRCount++;
- }
- rouletteCount += weapRSpace;
- }
- while (rouletteCount < NUM_ROULETTE_SLOTS)
- _rouletteWheel[rouletteCount++] = rand () % THINGS;
-
- kills = 0;
- killed = 0;
- tank = NULL;
- _target = NULL;
- _oldTarget = NULL;
-}
-
-void PLAYER::generatePreferences ()
-{
- double dBaseProb = (double) MAX_WEAP_PROBABILITY / 2.0;
- int currItem = 0;
- double dWorth;
- int iValue;
- bool bIsWarhead = false;
- int iMaxWeapPref = 0;
- int iMaxItemPref = 0;
-
- defensive = (2.0 * ( (double) rand () / (double) RAND_MAX)) - 1.0;
- double dDefenseMod = (defensive * -1.0) + 2.0;
- // DefenseMod will be between 1.0 (defensive) and 3.0 (offensive)
- // and is used to modifiy vengeful, vengeanceThreshold, selfPreservation and painSensitivity
- vengeful *= 1.0 + (dDefenseMod / 5.0); // +0% - +60% (defensive - offensive)
- if (vengeful > 100) vengeful = 100;
- vengeanceThreshold *= 1.0 - (dDefenseMod / 5.0); // -0% - -60%
- selfPreservation /= dDefenseMod;
- painSensitivity /= dDefenseMod;
-
- // Now defensive can be modified by team:
- if (team == TEAM_JEDI)
- {
- defensive += (double)((double)(rand() % 1000)) / 1000.0;
- if (defensive > 1.25)
- defensive = 1.25; // + 1.25 is SuperDefensive
- }
- if (team == TEAM_SITH)
- {
- defensive -= (double)((double)(rand() % 1000)) / 1000.0;
- if (defensive < -1.25)
- defensive = -1.25; // - 1.25 is SuperAggressive
- }
-#ifdef DEBUG
- cout << "Generating Preferences for \"" << getName() << "\" (" << defensive << ")" << endl;
- cout << "----------------------------------------------------" << endl;
-#endif // DEBUG
- _weaponPreference[0] = 0; // small missiles are always zero!
- for (int weapCount = 1; weapCount < THINGS; weapCount++)
- {
- dWorth = -1.0 * ( (double) MAX_WEAP_PROBABILITY / 4.0);
- bIsWarhead = false;
- if (weapCount < WEAPONS)
- {
- // Talking about weapons
- currItem = weapCount;
- if (weapon[currItem].warhead || ( (currItem >= SML_METEOR) && (currItem <= LRG_LIGHTNING)))
- bIsWarhead = true; // Bots don't think about warheads or environment!
- else
- {
- // 1. Damage:
- int iWarheads = weapon[currItem].spread; // For non-spread this is always 1
- if (weapon[currItem].numSubmunitions > 0)
- {
- iWarheads = weapon[currItem].numSubmunitions; // It's a cluster
-
- dWorth = weapon[weapon[currItem].submunition].damage * iWarheads; // total damage for clusters
-
- if ( ( (currItem >= SML_NAPALM) && (currItem <= LRG_NAPALM))
- || ( (currItem >= FUNKY_BOMB) && (currItem <= FUNKY_DEATH)))
- dWorth /= defensive + 1.0 + ( (double) type / 2.0);
- // These weapons are too unpredictable to be counted full
- // But a true offensive useless bot devides only by 1.0 (not all all, doesn't mind)
- // And a true defensive deadly bot devides by 5.0
- if (dWorth > dBaseProb) dWorth = dBaseProb; // Or Large Napalm will always be everybodys favorite
- }
- else
- dWorth = weapon[currItem].damage * (iWarheads * 2.0); // total damage for (non-)spreads
- // Note: iWarheads is counted twice, because otherwise spread weapons get far too low score!
- dWorth = dWorth * (dBaseProb * 0.0005); // 1 Damage is worth 0.5%o of dBaseProb
-
- // 2. Defensiveness multiplier
- // As said above, defensive players avoid spread/cluster weapons. Thus they rate non-spreads higher:
- if (iWarheads == 1)
- dWorth *= (defensive + 1.5) * ( (double) type / 2.0);
- else
- dWorth -= (defensive * ( (double) type / 2.0)) * dWorth;
-
- // 3. Dirtballs do no damage and have to be rated by defensiveness
- if ( (currItem >= DIRT_BALL) && (currItem <= SUP_DIRT_BALL))
- dWorth = (double) (currItem - (double) DIRT_BALL + 1.0) * 150.0 * ( (double) type / 2.0) * (defensive + 2.0);
-
- // 4.: Shaped charges, wide boys and cutters are deadly but limited:
- if ( (currItem >= SHAPED_CHARGE) && (currItem <= CUTTER))
- dWorth *= 1.0 - ( ( (double) type + (defensive * 5.0)) / 20.0);
- // useless, full offensive: * 1.20
- // deadly, full defensive : * 0.50
-
- // 5.: rollers and penetrators are modified by type, as they *are* usefull
- if ( ((currItem >= SML_ROLLER) && (currItem <= DTH_ROLLER))
- ||((currItem >= BURROWER) && (currItem <= PENETRATOR)))
- dWorth *= 1.0 + ((double)type / 10.0) + (defensive / 2);
-
- // 6.: Tectonis need to be raised!
- if ((currItem >= TREMOR) && (currItem <= TECTONIC))
- dWorth *= 2.0 + ((double)type / 10.0) + (defensive / 2);
-
- // finally dWorth must not be greater than the 3/4 of MAX_WEAPON_PROBABILITY
- if (dWorth > (MAX_WEAP_PROBABILITY * 0.75))
- dWorth = MAX_WEAP_PROBABILITY * 0.75;
- }
- }
- else
- {
- // Talking about items
- currItem = weapCount - WEAPONS;
- // unfortunately we can only switch here...
- /* Theory:
- As for armour/amps/shields, offensive bots go for amps and reflector
- shields, defensive bots go for armour and hard shields. */
- switch (currItem)
- {
- case ITEM_TELEPORT:
- dWorth = -1.0 * ((defensive - 1.5) * ((double)MAX_WEAP_PROBABILITY / 10.0));
- break;
- case ITEM_SWAPPER:
- dWorth = -1.0 * ((defensive - 1.5) * ((double)MAX_WEAP_PROBABILITY / 7.5));
- break;
- case ITEM_FAN:
- dWorth = 0.0; // useless things!
- break;
- case ITEM_VENGEANCE:
- case ITEM_DYING_WRATH:
- case ITEM_FATAL_FURY:
- dWorth = (defensive + 1.5) * ((double)weapon[(int)item[currItem].vals[0]].damage * (double)item[currItem].vals[1]);
- break;
- case ITEM_ARMOUR:
- case ITEM_PLASTEEL:
- dWorth = dBaseProb * ( (double) item[currItem].vals[0] / (double) item[ITEM_PLASTEEL].vals[0]);
- dWorth *= defensive;
- break;
- case ITEM_LGT_SHIELD:
- case ITEM_MED_SHIELD:
- case ITEM_HVY_SHIELD:
- dWorth = dBaseProb * ( (double) item[currItem].vals[0] / (double) item[ITEM_HVY_SHIELD].vals[0]);
- dWorth *= defensive;
- break;
- case ITEM_INTENSITY_AMP:
- case ITEM_VIOLENT_FORCE:
- dWorth = dBaseProb * ( (double) item[currItem].vals[0] / (double) item[ITEM_VIOLENT_FORCE].vals[0]);
- dWorth *= (-1.0 * defensive);
- break;
- case ITEM_LGT_REPULSOR_SHIELD:
- case ITEM_MED_REPULSOR_SHIELD:
- case ITEM_HVY_REPULSOR_SHIELD:
- dWorth = dBaseProb * ( (double) item[currItem].vals[0] / (double) item[ITEM_HVY_REPULSOR_SHIELD].vals[0]);
- dWorth *= (-1.0 * defensive);
- break;
- case ITEM_REPAIRKIT:
- dWorth = dBaseProb * ( (defensive + 1.0) / 2.0);
- break;
- case ITEM_PARACHUTE:
- dWorth = dBaseProb * ( (defensive + 1.0) / 1.5); // Parachutes *are* popular! :)
- break;
- case ITEM_SLICKP:
- dWorth = (int) type * 250;
- break;
- case ITEM_DIMPLEP:
- dWorth = (int) type * 500;
- break;
- case ITEM_FUEL:
- dWorth = -5000; // Bots don't need fuel
- bIsWarhead = true; // Yes, it's a lie. ;-)
- }
- // dWorth must not be greater than the half of MAX_WEAPON_PROBABILITY
- if (dWorth > (MAX_WEAP_PROBABILITY / 2))
- dWorth = MAX_WEAP_PROBABILITY / 2;
- }
- iValue = fabs (dWorth);
- if (iValue < (MAX_WEAP_PROBABILITY / 10)) iValue = MAX_WEAP_PROBABILITY / 10;
- dWorth += (double) (rand() % iValue); // allow to double (more or less)
-
- if (dWorth > MAX_WEAP_PROBABILITY)
- dWorth = MAX_WEAP_PROBABILITY;
- if (dWorth < (MAX_WEAP_PROBABILITY / 100.0))
- dWorth = MAX_WEAP_PROBABILITY / 100.0; // Which is very very little...
-
- if (bIsWarhead)
- _weaponPreference[weapCount] = 0; // It will not get any slot!
- else
- _weaponPreference[weapCount] = (int) dWorth;
-
- if ((weapCount < WEAPONS) && (_weaponPreference[weapCount] > iMaxWeapPref))
- iMaxWeapPref = _weaponPreference[weapCount];
- if ((weapCount >= WEAPONS) && (_weaponPreference[weapCount] > iMaxItemPref))
- iMaxItemPref = _weaponPreference[weapCount];
-
-#ifdef DEBUG
- if (weapCount < WEAPONS)
- printf( "%23s (weapon): % 5d", weapon[weapCount].name, _weaponPreference[weapCount]);
- else
- printf( "%23s ( item ): % 5d", item[weapCount-WEAPONS].name, _weaponPreference[weapCount]);
- cout << endl;
-#endif // DEBUG
- }
-
- // Before we are finished, we need to amplify the preferences (well, maybe...)
- if (iMaxWeapPref < MAX_WEAP_PROBABILITY)
- {
- // Yes, amplification for the weapons needed!
- dWorth = (double)MAX_WEAP_PROBABILITY / (double)iMaxWeapPref;
- for (int weapCount = 1; weapCount < WEAPONS; weapCount++)
- {
- if (_weaponPreference[weapCount] > (MAX_WEAP_PROBABILITY / 100.0))
- {
- _weaponPreference[weapCount] = (int)((double)_weaponPreference[weapCount] * dWorth);
-#ifdef DEBUG
- printf( "%23s (weapon) amplified to: % 5d", weapon[weapCount].name, _weaponPreference[weapCount]);
- cout << endl;
-#endif // DEBUG
- }
- }
- }
- if (iMaxItemPref < MAX_WEAP_PROBABILITY)
- {
- // Yes, amplification for the items needed!
- dWorth = (double)MAX_WEAP_PROBABILITY / (double)iMaxItemPref;
- for (int weapCount = WEAPONS; weapCount < THINGS; weapCount++)
- {
- if (_weaponPreference[weapCount] > (MAX_WEAP_PROBABILITY / 100.0))
- {
- _weaponPreference[weapCount] = (int)((double)_weaponPreference[weapCount] * dWorth);
-#ifdef DEBUG
- printf( "%23s ( item ) amplified to: % 5d", item[weapCount-WEAPONS].name, _weaponPreference[weapCount]);
- cout << endl;
-#endif // DEBUG
- }
- }
- }
-
-#ifdef DEBUG
- cout << "===================================================" << endl << endl;
-#endif // DEBUG
-}
-
-int PLAYER::selectRandomItem ()
-{
- // return (_rouletteWheel[rand () % NUM_ROULETTE_SLOTS]);
- return rand() % THINGS;
-}
-
-void PLAYER::setName (char *name)
-{
- // initalize name
- memset(_name, '\0', NAME_LENGTH);
- strncpy (_name, name, NAME_LENGTH - 1);
-}
-
-int PLAYER::controlTank ()
-{
- if (key[KEY_F1])
- save_bmp ( "scrnshot.bmp", _env->db, NULL);
-
- if (key_shifts & KB_CTRL_FLAG && ctrlUsedUp)
- {
- if (key[KEY_LEFT] || key[KEY_RIGHT] ||
- key[KEY_UP] || key[KEY_DOWN] ||
- key[KEY_PGUP] || key[KEY_PGDN] ||
- key[KEY_A] || key[KEY_D] || //additional control
- key[KEY_W] || key[KEY_S] ||
- key[KEY_R] || key[KEY_F])
- ctrlUsedUp = TRUE;
- else
- ctrlUsedUp = FALSE;
- }
- else
- {
- ctrlUsedUp = FALSE;
- }
-
- if (_global->computerPlayersOnly &&
- ((int)_global->skipComputerPlay >= SKIP_HUMANS_DEAD))
- {
- if (_env->stage == STAGE_ENDGAME)
- return (-1);
- }
-
- k = 0;
- #ifdef NEW_GAMELOOP
- if ( keypressed() )
- #else
- if (keypressed () && !fi)
- #endif
- {
- k = readkey ();
-
- if ((_env->stage == STAGE_ENDGAME) &&
- (k >> 8 == KEY_ENTER ||
- k >> 8 == KEY_ESC ||
- k >> 8 == KEY_SPACE))
- return (-1);
- if ( (k >> 8 == KEY_ESC) || (k >> 8 == KEY_P) )
- {
- void clockadd ();
- install_int_ex (clockadd, SECS_TO_TIMER(6000));
- int mm = _env->ingamemenu ();
- install_int_ex(clockadd, BPS_TO_TIMER(_global->frames_per_second));
- _env->make_update (0, 0, _global->screenWidth, _global->screenHeight);
- _env->make_bgupdate (0, 0, _global->screenWidth, _global->screenHeight);
-
- //Main Menu
- if (mm == 1)
- {
- _global->wr_lock_command();
- _global->command = GLOBAL_COMMAND_MENU;
- _global->unlock_command();
- return (-1);
- }
- else if (mm == 2) //Quit game
- {
- _global->wr_lock_command();
- _global->command = GLOBAL_COMMAND_QUIT;
- _global->unlock_command();
- return (-1);
- }
- else if (mm == 3) // skip AI
- {
- return (-2);
- }
- }
- // check for number key being pressed
- if ( (k >> 8 >= KEY_0) && (k >> 8 <= KEY_9) )
- {
- int value = (k >> 8) - KEY_0;
-
- // make sure the value is within range
- if (value < _global->numPlayers)
- {
- if ( _global->players[value] )
- {
- TANK *my_tank = _global->players[value]->tank;
- if (my_tank)
- {
- sprintf(_global->tank_status, "%s: %d + %d -- Team: %s", _global->players[value]->_name, my_tank->l, my_tank->sh, _global->players[value]->Get_Team_Name() );
- /* We do this in atanks.cpp, to kill this wretched "No Format Error"
- strcat(_global->tank_status, _global->players[value]->Get_Team_Name()); */
- _global->tank_status_colour = _global->players[value]->color;
- _global->updateMenu = 1;
- }
- else
- _global->tank_status[0] = 0;
- }
- }
-
- } // end of check status keys
- if ( (k & 0xff) == 'v' )
- _env->decreaseVolume();
- else if ( (k & 0xff) == 'V' )
- _env->increaseVolume();
- }
-
- if ((int)type == HUMAN_PLAYER || !tank)
- {
- return (humanControls ());
- }
- #ifdef NETWORK
- else if ((int) type == NETWORK_CLIENT)
- return (Execute_Network_Command(TRUE));
- #endif
- else if (_env->stage == STAGE_AIM)
- {
- return (computerControls ());
- }
- return (0);
-}
-
-int PLAYER::computerSelectPreBuyItem (int aMaxBoostValue)
-{
- double dMood = 1.0 + defensive + (double) ( (double) (rand() / ( (double) RAND_MAX / 2.0)));
- // dMood is 0.0 <= x <= 4.0
- int currItem = 0;
- /* Prior buying anything else, a 5 step system takes place:
- 1.: Parachutes (if gravity is on)
- 2.: Minimum weapon probability (aka 5 medium and 3 large missiles
- 3.: Armor/Amps
- 4.: "Tools" to free themselves like Riot Blasts
- 5.: Shields, if enough money is there
- 6.: if all is set, look for dimpled/slick projectiles! */
-
- // Step 1:
-
- if ( (type >= RANGEFINDER_PLAYER)
- && (_env->landSlideType > LANDSLIDE_NONE)
- && (ni[ITEM_PARACHUTE] < 10))
- currItem = WEAPONS + ITEM_PARACHUTE;
-
- // Step 3:
- // Even here bots might forget
- if (!currItem && (rand() % ( (int) type + 1)))
- {
- int iLimit = aMaxBoostValue - getBoostValue(); // > 0 means: Someone has more than we have!
-#ifdef DEBUG
- printf( "%10s: Boost: %4d, Max: %4d, Limit: %4d\n", getName(), getBoostValue(), aMaxBoostValue, iLimit);
-#endif // DEBUG
- if ( ((dMood >= 2.75) && (iLimit > 0)) || ((dMood >= 2.0) && (iLimit > getBoostValue())) )
- {
- // The player is in a defensive mood
- // If we have 25% more money than the plasteel cost, buy it, else the armour will do
- if (money >= (item[ITEM_PLASTEEL].cost * 1.25))
- currItem = WEAPONS + ITEM_PLASTEEL;
- else
- if ( (money >= (item[ITEM_ARMOUR].cost * 2.0))
- && ( (ni[ITEM_ARMOUR] < ni[ITEM_PLASTEEL])
- || (dMood >= 3.5)))
- currItem = WEAPONS + ITEM_ARMOUR;
- }
-
- // Now iBoostItemsBought must be checked:
- if (currItem && (iBoostItemsBought >= (int)type))
- currItem = 0; // Sorry, bought enough this round!
- if (currItem && (iBoostItemsBought < (int)type))
- iBoostItemsBought++; // Okay, take it!
- }
-
- // 5.: Shields
- if (!currItem && (rand() % ( (int) type + 1)) && ( (int) type >= RANGEFINDER_PLAYER))
- {
- if (dMood <= 1.5)
- {
- // offensive type, go through reflectors
- if ( (ni[ITEM_LGT_REPULSOR_SHIELD] <= (item[ITEM_LGT_REPULSOR_SHIELD].amt * (int) type))
- && (money >= (item[ITEM_LGT_REPULSOR_SHIELD].cost * 2.0)))
- currItem = WEAPONS + ITEM_LGT_REPULSOR_SHIELD;
-
- if ( (ni[ITEM_MED_REPULSOR_SHIELD] <= (item[ITEM_MED_REPULSOR_SHIELD].amt * (int) type))
- && (money >= (item[ITEM_MED_REPULSOR_SHIELD].cost * 1.75)))
- currItem = WEAPONS + ITEM_MED_REPULSOR_SHIELD;
-
- if ( (ni[ITEM_HVY_REPULSOR_SHIELD] <= (item[ITEM_HVY_REPULSOR_SHIELD].amt * (int) type))
- && (money >= (item[ITEM_HVY_REPULSOR_SHIELD].cost * 1.5)))
- currItem = WEAPONS + ITEM_HVY_REPULSOR_SHIELD;
- }
-
- if (dMood >= 2.5)
- {
- // defensive type, go through hard shields
- if ( (ni[ITEM_LGT_SHIELD] <= (item[ITEM_LGT_SHIELD].amt * (int) type))
- && (money >= (item[ITEM_LGT_SHIELD].cost * 2.0)))
- currItem = WEAPONS + ITEM_LGT_SHIELD;
-
- if ( (ni[ITEM_MED_SHIELD] <= (item[ITEM_MED_SHIELD].amt * (int) type))
- && (money >= (item[ITEM_MED_SHIELD].cost * 1.75)))
- currItem = WEAPONS + ITEM_MED_SHIELD;
-
- if ( (ni[ITEM_HVY_SHIELD] <= (item[ITEM_HVY_SHIELD].amt * (int) type))
- && (money >= (item[ITEM_HVY_SHIELD].cost * 1.5)))
- currItem = WEAPONS + ITEM_HVY_SHIELD;
- }
- }
-
- return (currItem);
-}
-
-int PLAYER::chooseItemToBuy (int aMaxBoostValue)
-{
- int currItem = computerSelectPreBuyItem (aMaxBoostValue);
- int itemNum = 0;
- int cumulative;
- int nextMod, curramt, newamt;
- int iTRIES = THINGS; // pow((int)type + 1, 2);
- int iDesiredItems[THINGS]; // Deadly bots have large shopping carts. ;)
- bool bIsSorted = false; // Whether the cart is sorted or not
- bool bIsPreSelected = currItem?true:false; // Whether or not the PreBuy steps found something
- int i = 0;
-
- if (currItem)
- {
- if ( Buy_Something(currItem) )
- return currItem;
- }
-
- // init desired items
- for (i = 0; i < iTRIES; i++)
- iDesiredItems[i] = 0;
-
- // 1.: Fill cart
- i = 0;
- while (i < iTRIES)
- {
- currItem = (int) fabs (selectRandomItem());
- if (currItem >= THINGS)
- currItem %= THINGS; // Put in range
- // now currItem is 0<= currItem < THINGS
- if ( (_env->isItemAvailable (currItem)) && (currItem != 0))
- {
- iDesiredItems[i] = currItem;
- }
- i++;
- }
-
- // 2.: sort these items by preferences
- while (!bIsSorted)
- {
- if (bIsPreSelected)
- i = 2; // The first item shall not be sorted somewhere else!
- else
- i = 1;
- bIsSorted = true;
- while (i < iTRIES)
- {
- if (_weaponPreference[iDesiredItems[i-1]] < _weaponPreference[iDesiredItems[i]])
- {
- bIsSorted = false;
- currItem = iDesiredItems[i];
- iDesiredItems[i] = iDesiredItems[i-1];
- iDesiredItems[i-1] = currItem;
- }
- i++;
- }
- }
-
- // 3.: loop through all weapon preferences
- for (int i = 0; i < iTRIES; i++)
- {
- currItem = iDesiredItems[i];
- itemNum = currItem - WEAPONS;
- //determine the likelyhood of purchasing this selection
- //less likely the more of the item is owned
- //if have zero of item, it is a fifty/fifty chance of purchase
- if (currItem < WEAPONS)
- {
- curramt = nm[currItem];
- newamt = weapon[currItem].amt;
- cumulative = FALSE;
- }
- else
- {
- curramt = ni[itemNum];
- newamt = item[itemNum].amt;
- if ( (itemNum >= ITEM_INTENSITY_AMP &&
- itemNum <= ITEM_VIOLENT_FORCE) ||
- (itemNum == ITEM_REPAIRKIT) ||
- (itemNum >= ITEM_ARMOUR &&
- itemNum <= ITEM_PLASTEEL))
- cumulative = TRUE;
- else
- cumulative = FALSE;
- }
- nextMod = 1;
- if (!cumulative)
- nextMod = curramt / newamt;
- if (nextMod <= 0)
- nextMod = 1;
- if (rand () % nextMod)
- continue;
-
- //weapon
- if (currItem < WEAPONS)
- {
- //don't buy if already maxed out
- if (nm[currItem] >= 99)
- continue;
-
- //purchase the item
- if (money >= weapon[currItem].cost)
- {
- money -= weapon[currItem].cost;
- nm[currItem] += weapon[currItem].amt;
- //don't allow more than 99
- if (nm[currItem] > 99)
- nm[currItem] = 99;
- return currItem;
- }
- }
- else //item
- {
- //don't buy if already maxed out
- if (ni[itemNum] >= 99)
- continue;
- //purchase the item
- if (money >= item[itemNum].cost)
- {
- // Check against iBoostItemsBought
- if ( (itemNum >= ITEM_INTENSITY_AMP &&
- itemNum <= ITEM_VIOLENT_FORCE) ||
- (itemNum >= ITEM_ARMOUR &&
- itemNum <= ITEM_PLASTEEL))
- {
- if ((iBoostItemsBought >= (int)type)
- ||(getBoostValue() > aMaxBoostValue))
- continue; // no chance pal!
- else
- iBoostItemsBought++; // Okay, take it!
- }
- money -= item[itemNum].cost;
- ni[itemNum] += item[itemNum].amt;
- //don't allow more than 999
- if (ni[itemNum] > 99)
- ni[itemNum] = 99;
- return (currItem);
- }
- }
- }
- return (-1);
-}
-
-
-
-// An item has been selected, this function merely buys it. It
-// first does checks to make sure the item can be bought.
-// The function returns TRUE if we successfuly bought the item or
-// FALSE if we could not get it for some reason.
-int PLAYER::Buy_Something(int currItem)
-{
- int itemNum = currItem - WEAPONS;
- int bought = FALSE;
-
- if (currItem < WEAPONS)
- {
- if ( (money >= weapon[currItem].cost) && (nm[currItem] < 99) )
- {
- money -= weapon[currItem].cost;
- nm[currItem] += weapon[currItem].amt;
- //don't allow more than 99
- if (nm[currItem] > 99)
- nm[currItem] = 99;
- bought = TRUE;
- }
- // check tech level
- if (weapon[currItem].techLevel <= _env->weapontechLevel)
- bought = TRUE;
- else
- bought = FALSE;
-
- } // end of weapons
-
- else // item
- {
- if ( (money > item[itemNum].cost) && (ni[itemNum] < 99) )
- {
- money -= item[itemNum].cost;
- ni[itemNum] += item[itemNum].amt;
- // don't allow more than 99
- if (ni[itemNum] > 99)
- ni[itemNum] = 99;
- bought = TRUE;
- }
- // check technology level
- if (item[itemNum].techLevel <= _env->itemtechLevel)
- bought = TRUE;
- else
- bought = FALSE;
- }
- return bought;
-}
-
-
-
-char *PLAYER::selectRevengePhrase ()
-{
- char *line;
-
- line = _global->revenge->Get_Random_Line();
- return line;
-}
-
-char *PLAYER::selectGloatPhrase ()
-{
- char *line;
- line = _global->gloat->Get_Random_Line();
- return line;
-}
-
-char *PLAYER::selectSuicidePhrase ()
-{
- char *line;
- line = _global->suicide->Get_Random_Line();
- return line;
-}
-
-
-char *PLAYER::selectKamikazePhrase ()
-{
- char *line;
- line = _global->kamikaze->Get_Random_Line();
- return line;
-}
-
-char *PLAYER::selectRetaliationPhrase ()
-{
- char *line;
- char *pText;
-
- if (! revenge)
- return NULL;
-
- line = _global->retaliation->Get_Random_Line();
- pText = (char *) calloc (strlen (revenge->getName()) + 32 + strlen(line), sizeof (char));
- if (! pText)
- return NULL;
-
- strcpy(pText, line);
- strcat(pText, revenge->getName());
- strcat(pText, " !!!");
-
- return(pText);
-}
-
-int PLAYER::traceShellTrajectory (double aStartX, double aStartY, double aVelocityX, double aVelocityY, double &aReachedX, double &aReachedY)
-{
- TANK *tankPool[10]; // For repulsion
- bool bHasHit = false; // will be set to true if something is hit
- bool bIsWallHit = false; // For steel walls and wrap floor/ceiling
- bool bIsWrapped = false; // For special handling in distance calcualtion when a shot goes through the wall
- int iWallBounce = 0; // For wrap, rubber and spring walls
- int iMaxBounce = pow((int)type, 2); // Useless can calculate 1, deadly bots 25 bounces
- int iTargetDistance = ABSDISTANCE(_targetX, _targetY, aStartX, aStartY);
- int iDistance = MAX_OVERSHOOT;
- int iDirection = 0; // records the current direction of the shot
- int iPriStageTicks = 0; // There is no unlimited tracking!
- int iMaxPriStTicks = MAX_POWER * focusRate * 2.5;
- int iSecStageTicks = 0; // rollers and burrowers can't be followed forever!
- int iMaxSecStTicks = MAX_POWER * focusRate * 1.5;
- double dDrag = weapon[tank->cw].drag;
- double dMass = weapon[tank->cw].mass;
- double dPosX = aStartX;
- double dPosY = aStartY;
- double dVelX = aVelocityX;
- double dVelY = aVelocityY;
- double dMaxVelocity = 0;
- double dPrevX, dPrevY; // to check pixels between two points
- bool bIsSecondStage = false; // Applies to burrowers and rollers
-
- dMaxVelocity = _global->dMaxVelocity * (1.20 + (dMass / ((double)MAX_POWER / 10.0)));
-
- // Set drag do the correct value:
- if (ni[ITEM_DIMPLEP])
- {
- dDrag *= item[ITEM_DIMPLEP].vals[0];
- }
- else if (ni[ITEM_SLICKP])
- {
- dDrag *= item[ITEM_SLICKP].vals[0];
- }
-
- // Fill tankPool
- for (int i = 0; i < _global->numPlayers; i++)
- {
- if ( (_global->players[i]) && (_global->players[i]->tank) )
- tankPool[i] = _global->players[i]->tank;
- else
- tankPool[i] = NULL;
- }
-
- // Initial direction:
- if (dVelX > 0.0) iDirection = 1;
- if (dVelX < 0.0) iDirection = -1;
-
- // On y va!
- while (!bHasHit && !bIsWallHit && (iWallBounce < iMaxBounce)
- && (iPriStageTicks < iMaxPriStTicks) && (iSecStageTicks < iMaxSecStTicks))
- {
- /* --- First Stage - Applies to all weapons --- */
- if (!bIsSecondStage)
- {
- // Apply Repulsor effects
- for (int i = 0; i < _global->numPlayers; i++)
- {
- if (tankPool[i] && (tankPool[i] != tank))
- {
- double xAccel = 0.0, yAccel = 0.0;
- tankPool[i]->repulse (dPosX + dVelX, dPosY + dVelY, &xAccel, &yAccel, tank->cw);
- if (tankPool[i] == _target)
- {
- // Without this, the shield would be nearly useless!
- xAccel *= focusRate;
- yAccel *= focusRate;
- // But the lesser bots wouldn't hit anything anymore if more than _target would be handled like that!
- }
- dVelX += xAccel;
- dVelY += yAccel;
- }
- }
-
- dPrevX = dPosX;
- dPrevY = dPosY;
- //motion - wind affected
- if (((dPosX + dVelX) < 1) || ((dPosX + dVelX) > (_global->screenWidth - 2)))
- {
- if ( (((dPosX + dVelX) < 1) && checkPixelsBetweenTwoPoints (_global, _env, &dPrevX, &dPrevY, 1, dPosY))
- ||( ((dPosX + dVelX) > (_global->screenWidth - 2))
- && checkPixelsBetweenTwoPoints (_global, _env, &dPrevX, &dPrevY, (_global->screenWidth - 2), dPosY)) )
- {
- dPosX = dPrevX;
- dPosY = dPrevY;
- bHasHit = true;
- }
- else
- {
- switch (_env->current_wallType)
- {
- case WALL_RUBBER:
- dVelX = -dVelX; //bounce on the border
- iWallBounce++;
- break;
- case WALL_SPRING:
- dVelX = -dVelX * SPRING_CHANGE;
- iWallBounce++;
- break;
- case WALL_WRAP:
- if (dVelX < 0)
- dPosX = _global->screenWidth - 2; // -1 is the wall itself
- else
- dPosX = 1;
- iWallBounce++;
- bIsWrapped = true;
- break;
- default:
- dPosX += dVelX;
- if (dPosX < 1)
- dPosX = 1;
- if (dPosX > (_global->screenWidth - 2))
- dPosX= _global->screenWidth - 2;
- dVelX = 0; // already applied!
- bIsWallHit = true;
- }
- }
- }
-
- // hit floor or boxed top
- if ( ( (dPosY + dVelY) >= (_global->screenHeight - 1))
- ||(((dPosY + dVelY) <= MENUHEIGHT) && _global->bIsBoxed))
- {
- if ( ( _global->bIsBoxed && ((dPosY + dVelY) <= MENUHEIGHT)
- && checkPixelsBetweenTwoPoints (_global, _env, &dPrevX, &dPrevY, dPosX, MENUHEIGHT + 1))
- ||( ((dPosY + dVelY) > (_global->screenHeight - 2))
- && checkPixelsBetweenTwoPoints (_global, _env, &dPrevX, &dPrevY, dPosX, (_global->screenHeight - 2))) )
- {
- dPosX = dPrevX;
- dPosY = dPrevY;
- bHasHit = true;
- }
- else
- {
- switch (_env->current_wallType)
- {
- case WALL_RUBBER:
- dVelY = -dVelY * 0.5;
- dVelX *= 0.95;
- iWallBounce++;
- break;
- case WALL_SPRING:
- dVelY = -dVelY * SPRING_CHANGE;
- dVelX *= 1.05;
- iWallBounce++;
- break;
- default:
- // steel or wrap floor (ceiling)
- dPosY += dVelY;
- if (dPosY >= (_global->screenHeight - 1))
- dPosY = _global->screenHeight - 2; // -1 would be the floor itself!
- else
- dPosY= MENUHEIGHT + 1; // +1 or it would be the wall itself
- dVelY = 0; // already applied!
- bIsWallHit = true;
- }
- if (!bIsWallHit &&((fabs(dVelX) + fabs(dVelY)) < 0.8))
- bHasHit = true;
- }
- }
-
- // velocity check:
- double dActVelocity = ABSDISTANCE(dVelX, dVelY, 0, 0); // a²+b²=c² ... says Pythagoras :)
- if ((dActVelocity > dMaxVelocity) && !bHasHit && !bIsWallHit)
- {
- // Velocity is applied first and modified by errorMultiplier
- dPosY += dVelY * errorMultiplier;
- dPosX += dVelX * errorMultiplier;
- dVelX = 0.0;
- dVelY = 0.0;
- if ((dPosY <= MENUHEIGHT) && _global->bIsBoxed)
- dPosY = MENUHEIGHT + 1;
- if (dPosY > (_global->screenHeight - 2))
- dPosY = _global->screenHeight - 2;
- if (dPosX < 1)
- {
- if (_env->current_wallType == WALL_WRAP)
- dPosX += _global->screenWidth - 2;
- else
- dPosX = 1;
- }
- if (dPosX > (_global->screenWidth - 2))
- {
- if (_env->current_wallType == WALL_WRAP)
- dPosX -= _global->screenWidth - 2;
- else
- dPosX = _global->screenWidth - 2;
- }
- bHasHit = true;
- }
-
- // Now check for hits
- if (!bHasHit && !bIsWallHit)
- {
- // Save Positions:
- dPrevX = dPosX;
- dPrevY = dPosY;
- // Apply movement:
- dPosX += dVelX;
- dPosY += dVelY;
- dVelX += (double)(_env->wind - dVelX) / dMass * dDrag * _env->viscosity;
- dVelY += _env->gravity * (100.0 / _global->frames_per_second);
- // Barrier test:
- if ( (dVelY <= -1.0) && (dPosY <= (_global->screenHeight * -25.0)))
- dVelY *= -1.0;
-
- // See, if we have hit something
- // --- Environment-Test -- flight?
- if (checkPixelsBetweenTwoPoints (_global, _env, &dPrevX, &dPrevY, dPosX, dPosY))
- {
- dPosX = dPrevX;
- dPosY = dPrevY;
- bHasHit = true;
- }
- }
- // Now that all modifications are applied, record direction:
- if (dVelX > 0.0) iDirection = 1;
- if (dVelX < 0.0) iDirection = -1;
- }
-
- /* --- Second Stage - Applies to burrowers and rollers --- */
- if (bIsSecondStage)
- {
- iSecStageTicks++;
- // The weapon is on the ground and rolling or penetrating the ground:
- if ((tank->cw >= SML_ROLLER) && (tank->cw <= DTH_ROLLER))
- {
- // check whether we have hit anything
- if ( (dPosX < 1)
- ||(dPosX > (_global->screenWidth - 2))
- ||(dPosY > (_global->screenHeight - 2))
- ||(getpixel(_env->terrain, (int)dPosX, (int)dPosY) != PINK))
- bHasHit = true;
- else
- {
- // roll roller
- float fSurfY = (float)_env->surface[(int)dPosX] - 1;
- if ((fSurfY > dPosY) && (dPosY < (_global->screenHeight - 5)))
- {
- if (fSurfY < (dPosY + 5))
- dPosY = fSurfY;
- else
- dPosY+=5;
- }
- else
- {
- if (dVelX > 0.0)
- dPosX++;
- else
- dPosX--;
- if (dPosY >= MENUHEIGHT)
- {
- if (fSurfY > dPosY)
- dPosY++;
- else if (fSurfY >= (dPosY - 2))
- dPosY = fSurfY - 1;
- }
- }
- if (dPosY > (_global->screenHeight - 5) && !bHasHit)
- dPosY = (_global->screenHeight - 5);
- }
- }
-
- if ((tank->cw >= BURROWER) && (tank->cw <= PENETRATOR))
- {
- // Apply Repulsor effects, but not fully, as it is a burrower!
- for (int i = 0; i < _global->numPlayers; i++)
- {
- if (tankPool[i] && (tankPool[i] != tank))
- {
- double xAccel = 0.0, yAccel = 0.0;
- tankPool[i]->repulse (dPosX + dVelX, dPosY + dVelY, &xAccel, &yAccel, tank->cw);
- if (tankPool[i] == _target)
- {
- // Without this, the shield would be nearly useless!
- xAccel *= focusRate;
- yAccel *= focusRate;
- // But the lesser bots wouldn't hit anything anymore if more than _target would be handled like that!
- }
- dVelX += xAccel * 0.1;
- dVelY += yAccel * 0.1;
- }
- }
- if (((dPosX + dVelX) < 1) || ((dPosX + dVelX) > (_global->screenWidth-1)))
- {
- // if the wall is rubber, then bouce
- if ( _env->current_wallType == WALL_RUBBER )
- dVelX = -dVelX; //bounce on the border
- // bounce with more force
- else if ( _env->current_wallType == WALL_SPRING )
- dVelX = -dVelX * SPRING_CHANGE;
- // wall is steel, detonate
- else if ( _env->current_wallType == WALL_STEEL )
- bHasHit = true;
- // wrap around to other side of the screen
- else if ( _env->current_wallType == WALL_WRAP )
- {
- if (dVelX < 1)
- dPosX = _global->screenWidth - 2;
- else
- dPosX = 1;
- }
- }
- if ((dPosY + dVelY) >= (_global->screenHeight - 1))
- {
- dVelY = -dVelY * 0.5;
- dVelX *= 0.95;
- }
- else if ((dPosY + dVelY) < MENUHEIGHT) //hit screen top
- {
- dVelY = -dVelY *0.5;
- dVelX *= 0.95;
- }
- dPosY += dVelY;
- dPosX += dVelX;
- dVelY -= _env->gravity * 0.05 * (100.0 / _global->frames_per_second);
- if (getpixel (_env->terrain, (int)dPosX, (int)dPosY) == PINK)
- bHasHit = true;
- }
- }
- else
- iPriStageTicks++;
-
-#ifdef DEBUG_AIM_SHOW
- if (_global->bASD)
- {
- // Plot trajectories for debugging purposes
- circlefill (screen, (int)dPosX, (int)dPosY, 2, color);
- }
-#endif
-
- /* --- Third Stage - Applies to burrowers and rollers --- */
- if ( (!bIsSecondStage && (bHasHit || bIsWallHit))
- &&( ((tank->cw >= SML_ROLLER) && (tank->cw <= DTH_ROLLER))
- ||((tank->cw >= BURROWER) && (tank->cw <= PENETRATOR))))
- {
- // Only Rollers and Penetrators can enter the second stage!
- bIsSecondStage = true;
- bHasHit = false;
- bIsWallHit = false;
- if ((tank->cw >= SML_ROLLER) && (tank->cw <= DTH_ROLLER))
- {
- dPosY -= 5;
- if (dPosX < 1)
- dPosX = 1;
- if (dPosX > (_global->screenWidth - 2))
- dPosX = (_global->screenWidth - 2);
-
- if ( (dPosY >= _env->surface[(int)dPosX]) // y is surface or below
- &&(dPosY <= _env->surface[(int)dPosX] + 2) ) // but not burried more than 2 px
- dPosY = _env->surface[(int)dPosX] - 1;
-
-
- dVelX = 0.0;
- if (getpixel (_env->terrain, (int)dPosX + 1, (int)dPosY + 1) == PINK)
- dVelX = 1.0;
- if (getpixel (_env->terrain, (int)dPosX - 1, (int)dPosY + 1) == PINK)
- dVelX = -1.0;
- if (dVelX == 0.0)
- // if both sides are free (should be impossible, but might be a 1-pixel-peak) the shooting direction decides
- dVelX = (double)iDirection;
- dVelY = 0.0;
- }
- if ((tank->cw == BURROWER) || (tank->cw == PENETRATOR))
- {
- dVelX *= 0.1;
- dVelY *= 0.1;
- }
- if ((dPosY <= MENUHEIGHT) && !_global->bIsBoxed)
- dPosY = MENUHEIGHT + 1;
- if ((dPosY <= MENUHEIGHT) && _global->bIsBoxed)
- bIsWallHit = true; // Sorry, no more ceiling-drop!
- }
-
- /* --- Fourth Stage - Tank hit test --- */
- if (!bIsWallHit)
- {
- for (int i = 0; i < _global->numPlayers; i++)
- {
- if (tankPool[i])
- {
- if ( (dPosX > (tankPool[i]->x - TANKWIDTH))
- &&(dPosX < (tankPool[i]->x + TANKWIDTH))
- &&(dPosY > (tankPool[i]->y))
- &&(dPosY < (tankPool[i]->y + TANKHEIGHT))
- &&(tankPool[i]->l > 0))
- bHasHit = true;
- }
- }
- }
-
- // if something is hit, be sure the values are in range and movement stopped!
- if (bHasHit || bIsWallHit)
- {
- dVelX = 0.0;
- dVelY = 0.0;
- if (dPosY <= MENUHEIGHT)
- dPosY = MENUHEIGHT + 1;
- if (dPosY > (_global->screenHeight - 2))
- dPosY = _global->screenHeight - 2;
- if (dPosX < 1)
- dPosX = 1;
- if (dPosX > (_global->screenWidth - 2))
- dPosX = _global->screenWidth - 2;
- }
- }
-
-#ifdef DEBUG_AIM_SHOW
- if (_global->bASD)
- {
- // Targetting circle for debugging purposes
- circlefill (screen, (int)dPosX, (int)dPosY, 10, BLACK);
- circlefill (screen, (int)_targetX, (int)_targetY, 20, color);
- LINUX_REST;
- }
-#endif
-#ifdef DEBUG_AIM
- if (bIsWallHit)
- cout << "WALL ";
- if (bHasHit)
- cout << "HIT ";
- if (iWallBounce >= iMaxBounce)
- cout << "BOUNCE (" << iMaxBounce << ") ";
- if (iPriStageTicks >= iMaxPriStTicks)
- cout << "TICKS1 (" << iPriStageTicks << ") ";
- if (iSecStageTicks >= iMaxSecStTicks)
- cout << "TICKS2 (" << iSecStageTicks << ") ";
-#endif // DEBUG_AIM
-
- // Now see where we are and calculate the distance difference between (char *)"hit" and "has to be hit"
- if (!bIsWallHit && (iWallBounce < iMaxBounce))
- {
- iDistance = ABSDISTANCE(_targetX, _targetY, dPosX, dPosY);
-
-#ifdef DEBUG_AIM
- cout << "(" << iDistance << " <- " << (int)dPosX << " x " << (int)dPosY << ") ";
-#endif // DEBUG_AIM
-
- // Special handling for wrapped shots:
- if (bIsWrapped)
- {
- int iHalfX = _global->halfWidth;
-
- // Only shots where dPosX and _targetX are on opposite sides are relevant
- if ( (((int)dPosX < iHalfX) && (_targetX >=iHalfX))
- ||(((int)dPosX >=iHalfX) && (_targetX < iHalfX)) )
- {
- if (((int)dPosX < iHalfX) && (_targetX >=iHalfX))
- iDistance = ABSDISTANCE(dPosX + iHalfX, dPosY, _targetX, _targetY);
- else
- iDistance = ABSDISTANCE(_targetX + iHalfX, _targetY, dPosX, dPosY);
- }
- else
- bIsWrapped = false; // not relevant
- }
-
- bool bIsWrongDir = false;
- if (!bIsWrapped
- &&( ((dPosX < aStartX) && (_targetX > aStartX))
- ||((dPosX > aStartX) && (_targetX < aStartX)) ) )
- bIsWrongDir = true; // wrong side!
-
- if (!iDirection)
- {
- // If we have no direction (very unlikely!) we can only guess by comparing x-coordinates:
- if ( (iTargetDistance > ABSDISTANCE(aStartX, aStartY, dPosX, dPosY))
- ||(bIsWrongDir && (_env->current_wallType != WALL_STEEL)) )
- // Too short or wrong direction, negate iDistance!
- iDistance *= -1;
- }
- else
- {
- // with the help of the direction, we can exactly see, whether the shot is too short or too far
- if (!bIsWrapped // This doesn't apply for wrapped shots!
- &&( ( (iDirection < 0.0) // shoot to the left...
- &&(dPosX > _targetX)) // ...and the shot hits right of target
- ||( (iDirection > 0.0) // shoot to the right...
- &&(dPosX < _targetX)) )// ...and the shot hits left of target
- ) iDistance *= -1; // too short!
- }
-
- if (bIsWrongDir && (_env->current_wallType == WALL_STEEL))
- iDistance = MAX_OVERSHOOT; // wrong side and target unreachable!
- }
- else
- iDistance = MAX_OVERSHOOT;
-
- // Give the X- and Y-position back:
- aReachedX = dPosX;
- aReachedY = dPosY;
-
- return(iDistance);
-}
-
-int PLAYER::rangeFind (int &aAngle, int &aPower)
-{
-#ifdef DEBUG_AIM
-printf("This is range find function\n");
-#endif
- int iOvershoot = MAX_OVERSHOOT;
- int iActOvershoot = MAX_OVERSHOOT;
- int iSpreadOvershoot= MAX_OVERSHOOT;
- int iBestOvershoot = MAX_OVERSHOOT + 1; // So there will be at least one recorded!
- int iLastOvershoot = MAX_OVERSHOOT;
- int iAngle = aAngle;
- int iAngleMod = 0;
- double dAngleMul = 0.0;
- double dPowerMul = 0.0;
- int iCalcAngle = aAngle;
- int iLastAngle = aAngle;
- int iBestAngle = aAngle;
- int iAngleBarrier = aAngle; // This will be the flattest angle possible, thus normalized aAngle + savety
- int iIdealAngle = 135; // 135 translates to 45°, but will be raised if the barrier is higher
- bool bIsRaisingMode = false;
- int iPower = aPower;
- int iPowerMod = 0;
- int iPowerModFixed = 0;
- int iLastPower = aPower;
- int iBestPower = aPower;
- int iAttempts = 0;
- int iSelfHitMult = 0;
- int iSpread = weapon[tank->cw].spread;
- int iSpreadCount = 0;
- int iSpreadOdd[] = {0,-1 * SPREAD, SPREAD, -2 * SPREAD, 2 * SPREAD};
- int iSpreadEven[] = { int(-0.5 * SPREAD), int(0.5 * SPREAD), int(-1.5 * SPREAD), 1, int(5 * SPREAD)};
- int iMaxSpread = (int)type; // more intelligent bots can calculate more shots bearing spreads!
- double dStartX, dStartY, dVelocityX, dVelocityY; // No initialization needed, they are calculated anyway.
- double dHitX, dHitY;
-
-#ifdef DEBUG_AIM
-printf("Starting RangeFind\n");
-#endif
-
- // We need to be sure that iSpread is at least 1:
- if (iSpread < 1) iSpread = 1;
-
- // iMaxSpread needs to be adapted:
- switch ((int)type)
- {
- case USELESS_PLAYER:
- case GUESSER_PLAYER:
- if (iSpread % 2) iMaxSpread = 1;
- else iMaxSpread = 2;
- break;
- case RANGEFINDER_PLAYER:
- if (iSpread % 2) iMaxSpread = 3;
- else iMaxSpread = 4;
- break;
- case TARGETTER_PLAYER:
- if (iSpread % 2) iMaxSpread = 5;
- else iMaxSpread = 4;
- break;
- default:
- if (iSpread % 2) iMaxSpread = 9; // I know there is nothing like that...
- else iMaxSpread = 8; // ... but maybe in the future?
- break;
- }
- if (iSpread > iMaxSpread) iSpread = iMaxSpread; // Now iSpread is limited to iMaxSpread...
- // ...adapted to a test like (iSpreadCount < iSpread) but not larger then weapon[x].spread
-
- /* Outline:
- * RangeFind tries to adapt the given angle and power to minimize overshoot.
- * to do so we have some facts to keep in mind:
- * - ideal angle is 45° in both directions, giving most distance.
- * - This angle translates into 135° for a shoot to the left, and 225° to the right
- * - aAngle is considered to be the flattest shot possible, as it has been manipulated
- * by calculateAttackValues(), meaning that lowering it will lead to an obstacle
- * hit.
- * - as a safety measure, when lowering an angle, a savety range of (int)type above
- * aAngle is applied
- *
- * The calculation is done in four steps:
- * 1.: Calculate the real angle, as it is, at least for spreads, different with every shot shell
- * 2.: Calculate the overshoot for each shot (or the one if no spread is given)
- * 3.: Record angle, power and overshoot if the overshoot is smaller than the best recorderd so far
- * 4.: Alter angle, or power if the angle can't be manipulated anymore, and start over, if the best
- * overshoot is != 0;
- *
- * This fourth step is divided into the following parts:
- * a) short shots (overshoot < 0) - raise power
- * i.: angle lowering mode - adjust the angle towards 45° until it (or aAngle) is reached
- * ii.:power raising mode - after the first positive overshoot is reached, the angle won't be changed any more
- * b) long shots (overshoot > 0) - lower power
- * i.: angle raising mode - adjust the angle towards 89° until it is reached
- * ii.:power lowering mode - once the angle can't be changed any more, only power is changed
- * angle raising/lowering modes are entered, and not left until iLastAngle == iAngle
- *
- * Note: Before you think all those step 4 calculations are wrong, they aren't, I am using a math trick to reduce
- * the amount of calculations:
- * 180 + (180 - Angle) flips an angle between left and right. So the angle will be normalized to the right,
- * (aka 90° - 180°) and all calculations done there. If it was flipped, it will be flipped back for
- * traceShellTrajectory. This saves alot of (char *)"if(angle > 180)" lines!
- */
-
- // Preperations:
- if (aAngle > 180) iAngleBarrier = 180 + (180 - iAngleBarrier); // flip
-
- // if we have some (char *)"space", add a savety distance:
- if (iAngleBarrier < 170) iAngleBarrier += (int)type - 1;
-
- // We need some savety distance for spreads:
- if (weapon[tank->cw].spread > 1)
- {
- // normal spread size:
- iAngleBarrier += SPREAD * ((int)weapon[tank->cw].spread / 2);
- if (!(weapon[tank->cw].spread % 2))
- // Even spreads have half of SPREAD more than neccessary, so adapt:
- iAngleBarrier -= SPREAD / 2;
- // but be sure the barrier isn't too large:
- if (iAngleBarrier > 179) iAngleBarrier = 179;
- }
-
- // Adapt the ideal angle if we are facing an obstacel:
- if (iAngleBarrier > 135) iIdealAngle = iAngleBarrier;
-
-#ifdef DEBUG_AIM
- printf("New Target Try using....\n");
- if (tank->cw < WEAPONS)
- printf("%s\n", weapon[tank->cw].name);
- else
- printf("%s\n", item[tank->cw - WEAPONS].name);
-#endif // DEBUG_AIM
-
- // Okay, here we go:
- #ifdef DEBUG_AIM
- printf("Best: %d .. Attempts %d .. RFattempts %d\n", iBestOvershoot, iAttempts, rangeFindAttempts);
- #endif
- while (iBestOvershoot && (iAttempts < rangeFindAttempts))
- {
- iAttempts++;
-
-#ifdef DEBUG_AIM
- printf("--> %d. rangeFind:\n", iAttempts);
- printf( (char *)" Angle: %3d Power: %4d\n", (iAngle - 90), iPower);
-#endif // DEBUG_AIM
-
- /* --- Step 1: Calculate angle for spreads: --- */
- iSpreadCount = 0;
- iOvershoot = MAX_OVERSHOOT;
- iSpreadOvershoot = 0;
- iSelfHitMult = 0;
- iLastOvershoot= iOvershoot;
-
- while (iSpreadCount < iSpread)
- {
- #ifdef DEBUG_AIM
- printf("Inside the wee while loop.\n");
- #endif
- iCalcAngle = iAngle;
-
- // Two cases: Even and odd spreads.
- if (iSpread > 1)
- {
- // odd spreads:
- if (weapon[tank->cw].spread % 2)
- iCalcAngle += iSpreadOdd[iSpreadCount];
- // even spreads:
- else
- iCalcAngle += iSpreadEven[iSpreadCount];
- }
- dVelocityX = _global->slope[iCalcAngle][0] * (double)iPower * (100.0 / _global->frames_per_second) / 100.0;
- dVelocityY = _global->slope[iCalcAngle][1] * (double)iPower * (100.0 / _global->frames_per_second) / 100.0;
-
- dStartX = tank->x + (_global->slope[iCalcAngle][0] * GUNLENGTH);
- dStartY = tank->y + (_global->slope[iCalcAngle][1] * GUNLENGTH);
-
- /* --- Step 2: Calculate overshoots: --- */
- #ifdef DEBUG_AIM
- printf("Trace the shell\n");
- #endif
- iActOvershoot = traceShellTrajectory(dStartX, dStartY, dVelocityX, dVelocityY, dHitX, dHitY);
- #ifdef DEBUG_AIM
- printf("Back from shell tracing.\n");
- #endif
- if (abs(iActOvershoot) < _global->screenWidth)
- iSelfHitMult += adjustOvershoot(iActOvershoot, dHitX, dHitY);
- // Otherwise it's a wall hit and not relevant!
-
- #ifdef DEBUG_AIM
- printf("Passed hit multi\n");
- #endif
- // With this method only the best hit of spreads is counted.
- if (abs(iActOvershoot) < abs(iOvershoot))
- iOvershoot = iActOvershoot;
-
- #ifdef DEBUG_AIM
- printf("Passed overshoot. Spread: %d\n", iSpread);
- #endif
- // iSpreadOvershoot calculates the average absolute Overshoot
- if (iActOvershoot)
- iSpreadOvershoot += (int)(fabs((double)iActOvershoot) / (double)iSpread);
-
- iSpreadCount++;
-
-#ifdef DEBUG_AIM
- if (iSpreadCount > 1)
- {
- if (iSpreadCount == 2)
- printf( (char *)" (%3d ", iCalcAngle - 90);
- else
- printf( (char *)",%3d ", iCalcAngle - 90);
- if (iSpread == iSpreadCount)
- printf( (char *)"\b)");
- }
-#endif // DEBUG_AIM
- }
-
- #ifdef DEBUG_AIM
- printf("Moving right along\n");
- #endif
-
- if (iOvershoot < 0)
- iSpreadOvershoot *= -1;
-
- if (iSelfHitMult > 0)
- {
- // We hit ourselves, so the larger Overshoot will be multiplied and taken!
- if (abs(iSpreadOvershoot) > abs(iOvershoot))
- iOvershoot = iSpreadOvershoot;
- if (abs(iOvershoot) < _global->screenWidth)
- iOvershoot = _global->screenWidth;
- if (abs(iOvershoot) < MAX_OVERSHOOT)
- iOvershoot*= iSelfHitMult;
- }
- else
- {
- // everything okay, just take the smaller one!
- if (abs(iSpreadOvershoot) < abs(iOvershoot))
- iOvershoot = (int)(((double)iSpreadOvershoot + ((double)iOvershoot * focusRate)) / 2);
- else
- iOvershoot = (int)(((double)iOvershoot + ((double)iSpreadOvershoot * focusRate)) / 2);
- }
-
- /* --- Step 3.: Record angle, power and overshoot if the overshoot is smaller than the best recorderd so far: --- */
- if (abs(iOvershoot) < abs(iBestOvershoot))
- {
- iBestOvershoot = iOvershoot;
- iBestAngle = iAngle;
- iBestPower = iPower;
- }
-
-#ifdef DEBUG_AIM
- printf( (char *)" Overshoot: %6d (%4d x %4d) SH: %1d\n", iOvershoot, (int)dHitX, (int)dHitY, iSelfHitMult);
-#endif // DEBUG_AIM
-
- /* --- Step 4.: Alter angle, or power if the angle can't be manipulated anymore, and start over: --- */
- if (iOvershoot)
- {
- // Preperation: flip iAngle if neccessary
- if (iAngle > 180) iAngle = 180 + (180 - iAngle); // flip
-
- // Preperation: Decide over angle and power modification depending on overshoot
- if (abs(iOvershoot) < _global->screenWidth)
- {
- dAngleMul = 1.0 + fabs((double)iOvershoot / (double)_global->screenWidth); // between 1.0 and 2.0
- iAngleMod = (int)(fabs((double)iOvershoot) / 10.0);
- if (iAngleMod > 15)
- iAngleMod = 15; // need a barrier here, too
-
- // Power modification is calculated depending on overshoot
- // To raise or lower by 100 pixels, we need aproximately 100 power (at 45°)
- dPowerMul = pow(1.0 + (fabs((double)iAngle - 135.0) / 50.0), 2.0); // between 1.0 and 3,61
- iPowerModFixed = 5 + (int)( (double)type * (fabs(iOvershoot) / 10.0)); // useless 10%, deadly 50% fix
- iPowerMod = 5 + (int)((10.0 - (double)type) * (fabs(iOvershoot) / 10.0)); // useless 90%, deadly 50% variable
- iPowerMod = (rand() % iPowerMod) + iPowerModFixed;
- }
- else
- {
- // As the overshoot is too high, probably a wall hit, Modification is done in a more limited way:
- dAngleMul = 1.0 + ((double)type / 10.0);
- dPowerMul = 1.0 + ((double)type / 10.0);
- iAngleMod = (rand() % 11) + 5;
- iPowerMod = (MAX_POWER / 8) + (rand() % (MAX_POWER / 8));
- if ((iOvershoot < 0) || (iAngle >= 170))
- // we need to raise distance urgently, so cancel bIsRaisingMod
- bIsRaisingMode = false;
- else
- // we need to lower distance urgently, so enter bIsRaisingMod
- bIsRaisingMode = true;
- }
-
- // before entering the step 4 modification parts, we could try a trick:
- if ( (abs(iLastOvershoot) < abs(iOvershoot))
- &&( ((iLastOvershoot < 0) && (iOvershoot > 0)) // be sure that the overshoots switches
- ||((iLastOvershoot > 0) && (iOvershoot < 0)) ) // between signed and unsigned
- &&( (abs(iLastAngle - iAngle) >= 2) // There has to be something to do or
- ||(abs(iLastPower - iPower) >=10) ) // we might waste all remaining attempts
- &&(abs(iLastOvershoot) < _global->screenWidth) // don't try it on wallhits, selfhits
- &&(abs(iOvershoot) < _global->screenWidth) )
- {
- // the current modification made the shot worse,
- // but switched between too short and too long,
- // so revert to half the modification:
- iAngleMod = (iLastAngle + iAngle) / 2; // We use the mod to save declaring
- iPowerMod = (iLastPower + iPower) / 2; // two new vars!
- iLastAngle = iAngle;
- iLastPower = iPower;
- iAngle = iAngleMod;
- iPower = iPowerMod;
- // Note: This trick won't work when both are too short or too long,
- // because then bots would never get over too high obstacles!
- }
- else
- {
- // No trick needed, we are getting nearer!
- iLastAngle = iAngle;
- iLastPower = iPower;
- iAngleMod = (int)((double)iAngleMod * focusRate * dAngleMul);
- iPowerMod = (int)((double)iPowerMod * focusRate * dPowerMul);
- // * a) short shots (overshoot < 0) - raise power
- if (iOvershoot < 0)
- {
- // If we are too short and have (char *)"overbend" in raising mode, it has to be cancelled!
- if (bIsRaisingMode && (iAngle > 180))
- bIsRaisingMode = false;
-
- if (!bIsRaisingMode)
- {
- // * i.: angle lowering mode - adjust the angle towards 45° until it (or aAngle) is reached
- if (iAngle > iIdealAngle)
- {
- iAngle -= iAngleMod;
- if (iAngle < iIdealAngle) iAngle = iIdealAngle;
- }
- if (iAngle < iIdealAngle)
- {
- iAngle += iAngleMod;
- if (iAngle > iIdealAngle) iAngle = iIdealAngle;
- }
-
- // Apply as much power as is neccessary:
- iPower += iPowerMod - (abs(iLastAngle - iAngle) * 10);
-
- // check to see whether Raising mode should be entered:
- if ((iAngle == iIdealAngle) && (iAngle == iLastAngle))
- bIsRaisingMode = true;
- }
- else
- // * ii.:power raising mode - after the first positive overshoot is reached, the angle won't be changed any more
- iPower += (int)((double)iPowerMod * dAngleMul);
- }
- // * b) long shots (overshoot > 0) - lower power
- if (iOvershoot > 0)
- {
- if (bIsRaisingMode)
- {
- // * i.: angle raising mode - adjust the angle towards 89° until it is reached
- if (iAngle > 178)
- iAngleMod = 1; // for small (char *)"overbends"
- iAngle += iAngleMod;
-
- // Apply as much power as is neccessary:
- iPower -= iPowerMod - (abs(iLastAngle - iAngle) * 10);
- }
- else
- // * ii.:power lowering mode - once the angle can't be changed any more, only power is changed
- iPower -= (int)((double)iPowerMod * dAngleMul);
- }
- }
-
- // last step: check iPower and iAngle:
- while ( (iPower >= MAX_POWER)
- ||(iPower <= (MAX_POWER / 20)))
- iPower = ((MAX_POWER / 2) + iPower) / 2;
- iPower -= iPower % 5;
-
- // check the angle, it must not be flatter than iAngleBarrirer!
- if (iAngle < iAngleBarrier)
- iAngle = iAngleBarrier;
-
- // Now flip the angle back if neccessary:
- if (aAngle > 180)
- iAngle = 180 + (180 - iAngle); // flip back!
-
-#ifdef DEBUG_AIM
- printf( (char *)" --> AngleMod: %3d PowerMod: %4d\n", (iAngle - iLastAngle), (iPower - iLastPower));
-#endif // DEBUG_AIM
- } // end of if(iOvershoot)
- }
-
-#ifdef DEBUG_AIM
- printf("looks like the end of the while loop in aiming\n");
-#endif
- // Record the best found values in tank if possible
- if (abs(iBestOvershoot) < tank->smallestOvershoot)
- {
-#ifdef DEBUG_AIM
- printf( (char *)" New best Overshoot: %5d\n", iBestOvershoot);
-#endif // DEBUG_AIM
- tank->smallestOvershoot = abs(iBestOvershoot);
- tank->bestAngle = iBestAngle;
- tank->bestPower = iBestPower;
- // Give the best ones back if possible
- if (iBestAngle != aAngle) aAngle = iBestAngle;
- if (iBestPower != aPower) aPower = iBestPower;
- }
-#ifdef DEBUG_AIM
- else
- cout << " No new best Overshoot..." << endl;
-#endif // DEBUG_AIM
- return(iBestOvershoot);
-}
-
-int PLAYER::adjustOvershoot(int &aOvershoot, double aReachedX, double aReachedY)
-{
- TANK *pTankHit = NULL; // For hitting quality analysis
- long int iOvershoot = aOvershoot; // To calculate with locally
- bool bIsDirectHit = true; // false for shaped charges and napalm is chosen
- bool bIsShaped = false; // true for shaped charges (special radius calculation needed!)
- int iDamage = weapon[tank->cw].damage;
- int iRadius = weapon[tank->cw].radius;
- int iSelfHits = 0; // Will be raised for every self- and team-hit and then returned
-
- if (iRadius < 10) iRadius = 10; // several things wouldn't function otherwise
- if (iDamage < 10) iDamage = 10; // if we didn't set minimum values
-
- if ((tank->cw >= SHAPED_CHARGE) && (tank->cw <= CUTTER))
- bIsShaped = true;
- if (bIsShaped || ((tank->cw >= SML_NAPALM) && (tank->cw <= LRG_NAPALM)))
- bIsDirectHit = false;
-
- /* --- Step 1: See whether a tank is hit: */
- for (int i = 0; i < 10; i++)
- {
- if (_global->players[i] && _global->players[i]->tank)
- {
- if ( (aReachedX > (_global->players[i]->tank->x - TANKWIDTH))
- &&(aReachedX < (_global->players[i]->tank->x + TANKWIDTH))
- &&(aReachedY > (_global->players[i]->tank->y))
- &&(aReachedY < (_global->players[i]->tank->y + TANKHEIGHT))
- &&(_global->players[i]->tank->l > 0))
- pTankHit = _global->players[i]->tank;
- }
- }
-
- /* --- Step 2: See whether the target tank is hit or in weapon radius: --- */
- // check these values in case of segfault
- if ( (_target) && (pTankHit) &&
- (pTankHit == _target) && bIsDirectHit && (pTankHit->player != this))
- // A Direct hit with a direct weapon is what we want, so give 0 back
- {
- iOvershoot = 0;
-#ifdef DEBUG_AIM
- cout << "ON TARGET! ";
-#endif // DEBUG_AIM
- }
- else
- {
- if (!iOvershoot)
- {
- if ( ( ((tank->x < _targetX) && (_targetX > aReachedX))
- ||((tank->x > _targetX) && (_targetX < aReachedX)) )
- &&(iOvershoot > 0)
- &&(iOvershoot < MAX_OVERSHOOT) )
- iOvershoot *= -1; // the shot is too short
- else
- iOvershoot = 1;
- }
- if (pTankHit)
- {
- // We *have* hit a tank, let's see to that it isn't us or a friend:
- if ((pTankHit == tank) || (pTankHit->player == this))
- {
- // Ourselves, not good!
- iOvershoot *= iRadius * iDamage;
- iSelfHits++;
- }
- else if (pTankHit->player->team == team)
- {
- // We hit someone of the same team, but only Jedi and SitH care, of course:
- if (team == TEAM_JEDI)
- iOvershoot *= iRadius * (defensive + 2) * focusRate;
- if (team == TEAM_SITH)
- iOvershoot *= iRadius * (-1 * (defensive - 2)) * focusRate;
- if (team != TEAM_NEUTRAL)
- iSelfHits++;
- }
- }
-
- /* --- Step 3: See, whether we, or a team member, is in blast radius and manipulate Overshoot accordingly --- */
- for (int i = 0; i < 10; i++)
- {
- if (_global->players[i] && _global->players[i]->tank && (_global->players[i]->tank != _target))
- {
- // _target is skipped, so we don't get wrong values when revenging on a team mate!
- int iX = _global->players[i]->tank->x;
- int iY = _global->players[i]->tank->y;
- int iRadiusDist;
- int iBlastDist = ABSDISTANCE(aReachedX, aReachedY, iX, iY);
-
- iRadiusDist = iRadius - iBlastDist;
- if (!iRadiusDist) iRadiusDist = 1;
-
- if (iBlastDist < iRadius)
- {
- // Is in Blast range. (maybe)
- if ( !bIsShaped
- ||(bIsShaped && (abs(iY - (int)aReachedY) <= (iRadius / 20))))
- {
- // Either no shaped charge or in radius
- if (_global->players[i]->tank == tank)
- {
- // Ourselves, not good!
- iOvershoot *= iRadiusDist * iDamage;
- iSelfHits++;
- }
- else if (_global->players[i]->team == team)
- {
- // We hit someone of the same team, but only Jedi and SitH care, of course:
- if (team == TEAM_JEDI)
- iOvershoot *= iRadiusDist * (defensive + 2) * focusRate;
- if (team == TEAM_SITH)
- iOvershoot *= iRadiusDist * (-1 * (defensive - 2)) * focusRate;
- if (team != TEAM_NEUTRAL)
- iSelfHits++;
- }
- }
- }
- }
- }
-
- // Be sure to not give a ridiculously high overshoot back:
- while (abs(iOvershoot) >= MAX_OVERSHOOT)
- iOvershoot /= 2;
- }
- aOvershoot = (int)iOvershoot;
-
- return(iSelfHits);
-}
-
-// If Napalm or a shaped weapon is chosen, the target has to be modified!
-int PLAYER::getAdjustedTargetX(TANK * aTarget)
-{
- int iTargetX, iTargetY;
- int iMinOffset = 0;
- int iMaxOffset = 1;
-
- if (aTarget)
- {
- iTargetX = aTarget->x;
- iTargetY = aTarget->y;
- }
- else if (_target)
- {
- iTargetX = _target->x;
- iTargetY = _target->y;
- }
- else // avoid segfault
- return ( rand() % _global->screenWidth ) ;
-
-
- // tank is dead, bail out to avoid segfault
- if (! tank)
- return iTargetX;
-
- if ( (tank->cw >= SHAPED_CHARGE) && (tank->cw <= CUTTER))
- {
- int iBestLeftOffset = 0;
- int iBestRightOffset = 0;
- int iLeftY = 0;
- int iBestLeftY = 0;
- int iRightY = 0;
- int iBestRightY = 0;
- iMinOffset = (int)((TANKWIDTH / 2) + ((double)TANKWIDTH * focusRate));
- iMaxOffset = iMinOffset + (TANKWIDTH * (((int)type + 1) / 2));
-
- for (int i = iMinOffset; i < iMaxOffset; i++)
- {
- // Get Y-Data:
- if ((iTargetX - i) > 1)
- iLeftY = _env->surface[iTargetX - i];
- if ((iTargetX + i) < (_global->screenWidth - 1))
- iRightY= _env->surface[iTargetX + i];
- // Check whether new Y-Data is better than what we have:
- if (abs(iLeftY - iTargetY) < abs(iBestLeftY - iTargetY))
- {
- iBestLeftOffset = i;
- iBestLeftY = iLeftY;
- }
- if (abs(iRightY - iTargetY) < abs(iBestRightY - iTargetY))
- {
- iBestRightOffset = i;
- iBestRightY = iRightY;
- }
- }
- // Now see whether we go left or right:
- if (abs(iBestLeftY - iTargetY) < abs(iBestRightY - iTargetY))
- // use left:
- iTargetX -= iBestLeftOffset;
- else
- // use right:
- iTargetX += iBestRightOffset;
- }
-
- if ( (tank->cw >= SML_NAPALM) && (tank->cw <= LRG_NAPALM))
- {
- // here we only check one side, the one to the wind:
- iMaxOffset = abs((int)((double)iMaxOffset * _env->wind * focusRate));
- iMinOffset = abs((int)(_env->wind * (double)TANKWIDTH * focusRate));
- int iSurfY = 0;
- int iBestY = 0;
- int iOffset = 0;
- int iBestOffset = 0;
- int iDirection = 0;
-
- if (_env->wind < 0) iDirection = 1; // for some reason I do not know, wind is
- if (_env->wind > 0) iDirection = -1; // used (char *)"the wrong way". (???)
-
- // Don't stretch the offset onto ourselves:
- if ( ((tank->x < iTargetX) && ((iTargetX - tank->x - (iDirection * iMaxOffset)) < (TANKWIDTH * 2)))
- ||((tank->x > iTargetX) && ((tank->x - iTargetX - (iDirection * iMaxOffset)) < (TANKWIDTH * 2))) )
- iMaxOffset = abs(iTargetX - (int)tank->x) - (TANKWIDTH * 2);
-
- // And don't allow a negative offset either (would be useless due to wind!)
- if (iMaxOffset < TANKWIDTH)
- iMaxOffset = TANKWIDTH;
-
- // But be sure iMinOffset is smaller than iMaxOffset:
- if (iMinOffset >= iMaxOffset)
- iMinOffset = iMaxOffset - (int)type;
-
- if (iDirection)
- {
- // Without wind there is nothing to do!
- for (int i = iMinOffset; i < iMaxOffset; i++)
- {
- iOffset = i * iDirection;
- // Get Y-Data:
- if (((iTargetX + iOffset) > 1) && (((iTargetX + iOffset) < (_global->screenWidth - 1))))
- iSurfY = _env->surface[iTargetX + iOffset];
-
- // Check whether new Y-Data is better than what we have:
- if ( ((iTargetY - iSurfY) < (iTargetY - iBestY))
- ||(!iBestY) )
- {
- iBestOffset = iOffset;
- iBestY = iSurfY;
- }
- }
- iTargetX += iBestOffset;
- }
- }
-
- return (iTargetX);
-}
-
-int PLAYER::calculateAttack(TANK *aTarget)
-{
- /* There are two general possibilities:
- * - aTarget provided:
- * This function was called from computerSelectTarget() and will only check if it is possible
- * to reach a target, aka try only once and give the overshoot back.
- * - default (aTarget is automatically NULL)
- * This function was called from computerControl() and will go forth and try to hit _target
- *
- * Outline:
- * --------
- * There are the following possibilities:
- * a) An Item or a laser is chosen
- * -> a direct angle will do!
- * b) We are burried
- * -> fire unburying tool at +/-60° to the middle of the screen
- * c) Kamikaze
- * -> indicated by targetX/Y being tank.x/y
- * -> if shaped weapon is chosen, fire 45° and power 100 to the side where y is nearest to tank.y
- * -> if napalm is chosen, fire 90° and power 0
- * -> otherwise fire 90° and power 250
- * d) Fire in non-boxed mode
- * -> normal calculation
- * e) Fire in non-boxed mode
- * -> extended power-control after normal calculation
- * -> if the target can't be reached while staying below the ceiling,
- * check for an obstacle than can be removed and do so if found.
- * --> boxed mode is included in non-boxed mode now. I hope it works as I intent!
- *
- * Update for dynamization: The previous target is recorded, aiming starts at the old values,
- * if the target didn't change. */
-
- int iXdistance, iYdistance;
- if (aTarget)
- {
- iXdistance = aTarget->x - tank->x;
- iYdistance = aTarget->y - tank->y;
- }
- else
- {
- iXdistance = _targetX - tank->x;
- iYdistance = _targetY - tank->y;
- }
-
-#ifdef DEBUG_AIM
- if (!aTarget && !iTargettingRound)
- {
- printf("\n-----------------------------------------------\n");
- if (_target)
- printf("%s is starting to aim at %s\n", getName(), _target->player->getName() );
- else
- printf("%s is aiming without a target!\n", getName());
- }
-#endif // DEBUG_AIM
-
- /* --- case a) An Item is chosen --- */
- if ( (!aTarget) && ( (tank->cw >= WEAPONS) ||
- ((tank->cw >= SML_LAZER) && (tank->cw <= LRG_LAZER))))
- {
-#ifdef DEBUG_AIM
- printf("About to calc direct angle.\n");
-#endif
- _targetAngle = calculateDirectAngle (iXdistance, iYdistance);
- _targetPower = MAX_POWER / 2;
-
-#ifdef DEBUG_AIM
- cout << "Direct " << _targetAngle - 90 << " for ";
- printf("Direct %d for %s\n", _targetAngle -90, (tank->cw < WEAPONS) ? weapon[tank->cw].name : item[tank->cw - WEAPONS].name);
-#endif // DEBUG_AIM
-
- iTargettingRound = retargetAttempts; // So it is done!
-
- return(0);
- }
-
-
- /* --- case b) We are burried --- */
- if (!aTarget &&(tank->howBuried () > BURIED_LEVEL))
- {
- // Angle is 60° to the middle of the screen:
- int iAngleVariation = rand() % (20 - ((int)type * 3));
- iAngleVariation -= (int)((double)type / 1.5);
- if (tank->x <= _global->halfWidth)
- _targetAngle = 150 + iAngleVariation;
- else
- _targetAngle = 210 - iAngleVariation;
-#ifdef DEBUG_AIM
- printf("Freeing self with Angle %d and Power %d\n", _targetAngle, _targetPower);
-#endif // DEBUG_AIM
-
- iTargettingRound = retargetAttempts;
-
- return(0);
- }
-
- /* --- case c) Kamikaze --- */
- if (!aTarget && (_targetX == tank->x) && (_targetY == tank->y))
- {
-#ifdef DEBUG_AIM
- cout << "Going bye bye with ";
- if (tank->cw < WEAPONS)
- cout << weapon[tank->cw].name;
- else
- cout << item[tank->cw - WEAPONS].name;
- cout << " !!!" << endl;
- cout << "GERONNNNNIIIIIMOOOOOOOOOO !!!" << endl;
-#endif // DEBUG_AIM
-
- iTargettingRound = retargetAttempts;
-
- // For a nice bye bye we set angle/power directly
- if ((tank->cw >= SHAPED_CHARGE) && (tank->cw <= CUTTER))
- {
- // shaped weapons are a bit special:
- int iHLeft, iHRight;
- int iCount = 0;
- for (int i = TANKWIDTH; i <= (TANKWIDTH * 2); i++)
- {
- iCount++;
- iHLeft = _env->surface[(int)(tank->x - i)];
- iHRight= _env->surface[(int)(tank->x + i)];
- }
- iHRight /= iCount;
- iHLeft /= iCount;
- if (fabs(iHRight - tank->y) < fabs(iHLeft - tank->y))
- _targetAngle = 135;
- else
- _targetAngle = 225;
- _targetPower = MAX_POWER / 20;
- return(0);
- }
- // The other possibilities are easier:
- if ((tank->cw >= SML_NAPALM) && (tank->cw <= LRG_NAPALM))
- _targetPower = 0;
- else
- _targetPower = MAX_POWER / 8;
- _targetAngle = 180;
-
- return(0);
- }
-
- /* --- case d) Fire in non-boxed mode --- */
- int iRawAngle, iAdjAngle, iPower, iSavetyPower;
- int iOvershoot = MAX_OVERSHOOT;
- int iLastOvershoot = MAX_OVERSHOOT;
- int iBestOvershoot = MAX_OVERSHOOT;
-
- _targetX = getAdjustedTargetX(aTarget);
- calculateAttackValues(iXdistance, iYdistance, iRawAngle, iAdjAngle, iPower, false);
-
- if (!iTargettingRound)
- {
- // Initializiation, if this is the first try:
-
- if (!aTarget && (_oldTarget == _target) )
- {
- // Target didn't change, use last rounds values:
- tank->bestAngle = tank->a;
- tank->bestPower = tank->p;
- iRawAngle = tank->a;
- iAdjAngle = tank->a;
- iPower = tank->p;
- }
- else
- {
- // new target, new values:
- tank->bestAngle = 180;
- tank->bestPower = MAX_POWER;
- if (!aTarget)
- _oldTarget = _target;
- }
-
- tank->smallestOvershoot = MAX_OVERSHOOT + 1; // So there will be at least one recorded
-
- }
- else
- {
- // This is a follow-up round, get the last values back:
- iAdjAngle = _targetAngle;
- iPower = _targetPower;
- iBestOvershoot = tank->smallestOvershoot;
- }
-
- if (aTarget)
- {
- #ifdef DEBUG_AIM
- printf("Returning a range find.\n");
- #endif
- return(rangeFind(iAdjAngle, iPower));
- }
-
- // Now that we are here, normal handling is needed:
-#ifdef DEBUG_AIM
- if (!iTargettingRound)
- {
- printf("Intitial try:\n");
- printf("About to range find.\n");
- }
-#endif // DEBUG_AIM
-
- iOvershoot = rangeFind(iAdjAngle, iPower);
- iLastOvershoot = iBestOvershoot;
- if (abs(iOvershoot) < abs(iBestOvershoot))
- iBestOvershoot = iOvershoot;
-
-#ifdef DEBUG_AIM
- cout << "Overshoot: " << iOvershoot << " (best so far: " << iBestOvershoot << ")" << endl << endl;
- cout << iTargettingRound + 1 << ". re-try:";
-#endif // DEBUG_AIM
-
- // There is still an Overshoot, so see what we can do:
- if (abs(iLastOvershoot - iOvershoot) <= 1)
- {
- // rangeFind can't get a better version, so start completely different!
- int iAngleRange = 180;
- if (_env->current_wallType == WALL_STEEL)
- iAngleRange /= 2;
- if (_targetX > tank->x)
- // shoot basically to the right:
- iRawAngle = 90 + (rand() % iAngleRange);
- else
- // shoot basically to the left:
- iRawAngle = 270 - (rand() % iAngleRange);
- iAdjAngle = (iAdjAngle + iRawAngle) / 2;
- iPower = (iPower + (rand() % (MAX_POWER / 2))) / 2;
-#ifdef DEBUG_AIM
- cout << " Starting over! " << endl;
-#endif
- }
- else
- {
- // Just calculate anew:
- calculateAttackValues(iXdistance, iYdistance, iRawAngle, iAdjAngle, iPower, true);
-#ifdef DEBUG_AIM
- cout << " recalculating! " << endl;
-#endif
- }
-
- /* SavetyPower is as follows:
- * abs(iAdjAngle -180) changes the value to be between:
- * -> 0 : Straight upwards (originally 180)
- * -> 90 : Straight Left or right (originally 270/90)
- * 91 - abs(...) gives a value between 1 and 91 which is multiplied with the radius */
- iSavetyPower = ((91 - abs(iAdjAngle - 180)) * (weapon[tank->cw].radius * focusRate)) / focusRate;
- if (iSavetyPower > (MAX_POWER / 4)) iSavetyPower = MAX_POWER / 4;
- if (iPower < iSavetyPower) iPower = iSavetyPower;
-
- iLastOvershoot = iOvershoot;
- iOvershoot = rangeFind(iAdjAngle, iPower);
-
- if (abs(iOvershoot) < abs(iBestOvershoot))
- iBestOvershoot = iOvershoot;
-
- iTargettingRound++;
-
- // Do not count this attempt if the currently best overshoot is too high:
- if ((iBestOvershoot > _global->screenWidth) && (rand() % ((int)type * 2)))
- iTargettingRound--; // if ibestOvershoot is negative it will alwys be counted!
-
- if (tank->smallestOvershoot < (weapon[tank->cw].radius / (int)type))
- iTargettingRound = retargetAttempts;
-
- // Now the best Version has to be tested:
- iOvershoot = tank->smallestOvershoot; // Always a positive number!
- _targetAngle = tank->bestAngle; // Record for foolow-up rounds
- _targetPower = tank->bestPower; // Record for follow-up rounds
-
- if (iTargettingRound == retargetAttempts)
- {
-#ifdef DEBUG_AIM
- cout << " --- ---" << endl;
- cout << "Final Decision:" << endl;
- cout << "Angle : " << _targetAngle - 90 << endl;
- cout << "Power : " << _targetPower << endl;
- cout << "Overshoot: " << iBestOvershoot << endl;
-#endif // DEBUG_AIM
-
- if (iOvershoot > weapon[tank->cw].radius)
- {
- if (_targetAngle > 180)
- iRawAngle = _targetAngle + 10 + (rand() % ((int)type * 4));
- else
- iRawAngle = _targetAngle - (10 + (rand() % ((int)type * 4)));
-
- // There are three possibilities:
- if ( (iBestOvershoot < 0)
- &&(abs(_targetAngle - 180) <= (10 + ((iXdistance / _global->screenWidth) * 10)))
- &&(!tank->shootClearance(iRawAngle, weapon[tank->cw].radius * 1.5)))
- {
- // a) there is an obstacle in our way!
- tank->cw = getUnburyingTool();
- if (tank->cw < WEAPONS)
- {
- _targetAngle = iRawAngle;
-#ifdef DEBUG_AIM
- cout << "Revised Angle for unburying: " << _targetAngle - 90 << endl;
-#endif // DEBUG_AIM
- }
- }
- else
- {
- // b) the way is clear, so either try to switch weapon or fire away!
- if (iBestOvershoot < 0)
- {
- // As we are too short, see whether another weapon might be good
- if (nm[TREMOR]) tank->cw = TREMOR;
- if (nm[SHOCKWAVE]) tank->cw = SHOCKWAVE;
- if ((_targetY < (_global->screenHeight - iOvershoot)) && nm[BURROWER])
- tank->cw = BURROWER;
- if (nm[TECTONIC]) tank->cw = TECTONIC;
- if ((_targetY < (_global->screenHeight - iOvershoot)) && nm[PENETRATOR])
- tank->cw = PENETRATOR;
-
- // if it is boxed, it is better to teleport out!
- if ((_global->bIsBoxed) && (iOvershoot > (weapon[tank->cw].radius * 2)))
- {
- // no way, teleport out!
- if (ni[ITEM_TELEPORT]) tank->cw = ITEM_TELEPORT + WEAPONS;
- if (ni[ITEM_SWAPPER]) tank->cw = ITEM_SWAPPER + WEAPONS;
- }
- }
- else
- {
- // look for tectonis and their range
- if (nm[TREMOR] && (weapon[TREMOR].radius >= iOvershoot)) tank->cw = TREMOR;
- if (nm[SHOCKWAVE] && (weapon[SHOCKWAVE].radius >= iOvershoot)) tank->cw = SHOCKWAVE;
- if (nm[TECTONIC] && (weapon[TECTONIC].radius >= iOvershoot)) tank->cw = TECTONIC;
- }
-
- // c) we have no chance to reach anything!
- if (iOvershoot > _global->screenWidth)
- {
- // no way, teleport out!
- if (ni[ITEM_TELEPORT]) tank->cw = ITEM_TELEPORT + WEAPONS;
- if (ni[ITEM_SWAPPER]) tank->cw = ITEM_SWAPPER + WEAPONS;
- }
-
- }
- }
- else
- {
- // If we *are* hitting ok, Angle and Power need to be manipulated by errorMultiplier
- int iAngleMod, iPowerMod;
- int iAngleModLimit = (42 - (10 * ((int)type - 1))); // = 42, 32, 22, 12, 2 for useless -> deadly
- int iPowerModLimit = (210 - (50 * ((int)type - 1))); // = 210, 160, 110, 60, 10 for useless -> deadly
-
- iAngleMod = (rand() % 51) * errorMultiplier; // useless: 0 - 100, deadly: 0 - 2 (with 2 being very unlikely)
- if (iAngleMod > iAngleModLimit)
- iAngleMod = iAngleModLimit;
-
- iPowerMod = (rand() % 251) * errorMultiplier; // useless: 0 - 500, deadly: 0 - 10 (with 10 being *very* unlikely)
- if (iPowerMod > iPowerModLimit)
- iPowerMod = iPowerModLimit;
-
- // In boxed mode, errors have quite more impact and are therefore cut down to be only two thirds
- if (_global->bIsBoxed)
- {
- iAngleMod = (int)((double)iAngleMod / 3.0 * 2.0);
- iPowerMod = (int)((double)iPowerMod / 3.0 * 2.0);
- }
-
- // 25 % to get a flattening anglemod (aka nearing to the ground)
- if ((!(rand() % 4)) && (_targetAngle < 180))
- iAngleMod *= -1; // right side angle
- if ((rand() % 4) && (_targetAngle > 180))
- iAngleMod *= -1; // right side angle (other way round, because here a positive mod is flattening!
-
- // 25 % to get a lessening powermod. (it is more likely to overshoot than be too short!)
- if (!(rand() % 4)) // (and it leads to too many stupid self
- iPowerMod *= -1; // hits to allow negative PoerMod too often
-
- // The modification must not lead to a too stupid angle
- while ( iAngleMod
- &&( ( (_targetAngle < 180)
- &&( ((iAngleMod + _targetAngle) < 95)
- ||((iAngleMod + _targetAngle) > 175) ) )
- ||( (_targetAngle >=180)
- &&( ((iAngleMod + _targetAngle) > 265)
- ||((iAngleMod + _targetAngle) < 185) ) )
- ) ) iAngleMod /= 2;
- _targetAngle += iAngleMod;
-
- // Same applies for Power:
- while ( iPowerMod
- &&( ((iPowerMod + _targetPower) > MAX_POWER)
- ||((iPowerMod + _targetPower) < (MAX_POWER * 0.1)) ) )
- iPowerMod /= 2;
-
- // iPowerMod needs to be a multiplier of 5:
- iPowerMod -= iPowerMod % 5;
-
- _targetPower += iPowerMod;
-
-#ifdef DEBUG_AIM
- printf( "Error-Adjusting: Angle %3d Power %4d\n", iAngleMod, iPowerMod);
-#endif // DEBUG_AIM
- }
-
-#ifdef DEBUG_AIM
- cout << "HITTING " << _target->player->getName() << " ???" << endl << endl;
-#endif // DEBUG_AIM
-
- // Last one: if we are revenging, tell em!
- // Added _target check to avoid segfault
- if ( (tank->cw < WEAPONS) && (_target) // Wepaon selected?
- && (weapon[tank->cw].damage > 1) // One that delivers damage?
- && (revenge == _target->player) // And the target is our revengee?
- && ((rand() % ((int)DEADLY_PLAYER + 2 - (int)type))) ) // And we do really want to taunt?
- {
- FLOATTEXT *DIEText;
- char *my_text;
- my_text = selectRetaliationPhrase();
- DIEText = new FLOATTEXT (_global, _env, my_text, (int) tank->x, (int) tank->y - 30, color, CENTRE);
- if (my_text) free(my_text);
- if ( DIEText)
- {
- //DIEText->xv = 0;
- //DIEText->yv = -0.4;
- DIEText->set_speed(0.0, -0.4);
- DIEText->maxAge = 150;
- }
- else
- perror ( "player.cc: Failed allocating memory for DieText in calculateAttack().");
- }
- // As we might finish here because of a good overshoot, iTargettingRounds need to be maxed!
- iTargettingRound = retargetAttempts;
- }
- return(0);
-}
-
-void PLAYER::calculateAttackValues(int &aDistanceX, int aDistanceY, int &aRawAngle, int &aAdjAngle, int &aPower, bool aAllowFlip)
-{
- double dAirTime, dxTime;
- double dSlopeX, dSlopeY;
- double dAngleVariation, dPowerVariation;
- bool bIsWrapped = false; // Special handling for wrapped walls
-
- aDistanceX = _targetX - tank->x;
-
- /* --- Step 1: find the raw angle, aka the obstacle-free optimal angle --- */
-
- aRawAngle = (int)(atan2((double)aDistanceX, (double)(aDistanceY - abs (aDistanceX))) / PI * 180.0);
-
- // Bring in Range:
- if (aRawAngle < 0) aRawAngle = aRawAngle + 360;
- if (aRawAngle < 90) aRawAngle = 135;
- if (aRawAngle > 270) aRawAngle = 225;
-
- if ((_env->current_wallType == WALL_WRAP) && ((rand() % ((int)type + 1)) || !aAllowFlip))
- {
- // Note: We always wrap when possible and flipping not allowed, to have the shorter distance for sure!
- int iWrapDistance = 0;
-
- // if the distance through the wall is shorter, take it!
- if (tank->x < _targetX)
- iWrapDistance = -1 * (tank->x -1 + _global->screenWidth - 2 - _targetX);
- else
- iWrapDistance = tank->x -1 + _global->screenWidth - 2 - _targetX;
-
- if (abs(iWrapDistance) < abs(aDistanceX))
- {
- bIsWrapped = true;
- aDistanceX = iWrapDistance;
- aRawAngle = 180 + (180 - aRawAngle); // flip!
- }
- }
-
- // Angle variation for more flavour:
- dAngleVariation = (rand() % 21) * focusRate; // useless: 0-4, deadly: 0-20
- if (rand() % 2) dAngleVariation *= -1.0;
- while ( (dAngleVariation > 0.0)
- &&( ((aRawAngle > 180) && ( ((aRawAngle + dAngleVariation) < 190)
- ||((aRawAngle + dAngleVariation) > 260)))
- ||((aRawAngle < 180) && ( ((aRawAngle + dAngleVariation) > 170)
- ||((aRawAngle + dAngleVariation) < 100))) ) )
- dAngleVariation /= 2.0;
- aRawAngle += (int)dAngleVariation;
-
- // Maybe we could switch sides?
- if ((_env->current_wallType != WALL_STEEL) && (rand() % 2) && (rand() % (int)type) && aAllowFlip)
- {
- // Yes, we can! ( (char *)"Change is coming!" ;-) (b.h.o) )
- aRawAngle = 180 + (180 - aRawAngle); // This switches sides, yes. :-D
-
- if (tank->x < _targetX)
- {
- // We switch angle from right to left: (original distance is positive)
- switch (_env->current_wallType)
- {
- case WALL_RUBBER:
- // Add Distances to the wall to bounce from:
- aDistanceX += 2 * (tank->x - 1);
- break;
- case WALL_SPRING:
- // Spring walls add velocity, so adapt a bit
- aDistanceX += tank->x - 1 + ((tank->x - 1) * 0.75);
- break;
- case WALL_WRAP:
- // Distance is new: only distances from the wall:
- if (bIsWrapped)
- aDistanceX = tank->x -1 + _global->screenWidth - 2 - _targetX;
- else
- aDistanceX = -1 * (tank->x -1 + _global->screenWidth - 2 - _targetX);
- break;
- }
- }
- else
- {
- // We switch angle from left to right: (original distance is negative)
- switch (_env->current_wallType)
- {
- case WALL_RUBBER:
- // Add Distances to the wall to bounce from:
- aDistanceX += 2 * (_global->screenWidth - tank->x - 2);
- break;
- case WALL_SPRING:
- // Spring walls add velocity, so adapt a bit
- aDistanceX += _global->screenWidth - tank->x - 2 + ((_global->screenWidth - tank->x - 2) * 0.75);
- break;
- case WALL_WRAP:
- // Distance is new: only distances from the wall:
- if (bIsWrapped)
- aDistanceX = -1 * (tank->x -1 + _global->screenWidth - 2 - _targetX);
- else
- aDistanceX = tank->x -1 + _global->screenWidth - 2 - _targetX;
- break;
- }
- }
- }
-
- /* --- Step 2: Adjust the Angle given clearance --- */
-
- aAdjAngle = aRawAngle;
- while ((aAdjAngle < 180) && !(tank->shootClearance(aAdjAngle)))
- aAdjAngle++;
- while ((aAdjAngle > 180) && !(tank->shootClearance(aAdjAngle)))
- aAdjAngle--;
-
- /* --- Step 3: Find neccessary Power --- */
- dSlopeX = _global->slope[aAdjAngle][0];
- dSlopeY = _global->slope[aAdjAngle][1];
-
- if (dSlopeX != 0.0)
- dxTime = ((double)aDistanceX / fabs(dSlopeX));
- else
- // entirely down to the elements now
- dxTime = ((double)aDistanceX / 0.000001);
-
- // Low target, less power
- // xdistance proportional to sqrt(dy)
- dAirTime = fabs(dxTime) + (((double)aDistanceY * dSlopeY) * _env->gravity * (100.0 / _global->frames_per_second)) * 2.0;
-
- // Less airTime doesn't necessarily mean less power
- // Horizontal firing means more power needed even though
- // airTime is minimised
-
- aPower = (int)(sqrt (dAirTime * _env->gravity * (100.0 / _global->frames_per_second))) * 100;
-
- // Power variation for more flavour:
- dPowerVariation = (rand() % 51) * focusRate; // useless: 0-10, deadly: 0-50
- dPowerVariation -= (int)dPowerVariation % 5;
- if (rand() % 2) dPowerVariation *= -1.0;
- aPower += dPowerVariation;
-
- if (aPower > MAX_POWER) aPower = MAX_POWER;
- if (aPower < (MAX_POWER / 20)) aPower = MAX_POWER / 20;
-
-}
-
-int PLAYER::calculateDirectAngle (int dx, int dy)
-{
- double angle;
-
- angle = atan2 ((double)dx, (double)dy) / PI * 180;
- angle += (rand () % 40 - 20) * errorMultiplier;
-
- if (angle < 0)
- angle = angle + 360;
- if (angle < 90)
- angle = 90;
- else if (angle > 270)
- angle = 270;
-
- return ((int)angle);
-}
-
-TANK * PLAYER::computerSelectTarget (int aPreferredWeapon, bool aRotationMode)
-{
- int random_target;
- int attempts = 0;
- int max_attempts = (int)type * 3;
- TANK *best_target = NULL;
- int current_score = 0;
- int best_score = -1 * MAX_OVERSHOOT; // start with a loooooow score, so that even score<0 tanks can become best target
- TANK *current_tank = NULL;
- TANK *tankPool[10];
- int iMoneyNeed = getMoneyToSave() - money; // Are we in need for money?
- int target_count = 0;
-
-#ifdef DEBUG
- cout << " -> I need " << iMoneyNeed << " Credits *urgently*!" << endl;
- if (aPreferredWeapon < WEAPONS)
- cout << " - Searching target for " << weapon[aPreferredWeapon].name << endl;
- else
- cout << " - Searching target for " << item[aPreferredWeapon - WEAPONS].name << endl;
-#endif // DEBUG
- // find out how many tries we have to find a good target
-
- // Fill tankPool
- for (int i = 0; i < _global->numPlayers; i++)
- {
- if ( (_global->players[i]) && (_global->players[i]->tank) )
- {
- tankPool[i] = _global->players[i]->tank;
- target_count++;
- }
- else
- tankPool[i] = NULL;
- }
-
- if (target_count < 2) // just us left or nobody
- return NULL;
-
- // who do we want to shoot at?
- while (attempts < max_attempts)
- {
- // select random tank for target
- if (_global->numPlayers > 0)
- random_target = rand() % _global->numPlayers;
- else
- random_target = 0;
- current_tank = tankPool[random_target];
-
- if (current_tank)
- {
- current_score = 0;
- // only consider living tanks that are not us
-
- if ( (current_tank->l > 0) && (current_tank->player != this))
- {
- int iDamage = 0;
-
- if (weapon[aPreferredWeapon].numSubmunitions > 1)
- iDamage = (damageMultiplier
- * weapon[weapon[aPreferredWeapon].submunition].damage
- * (weapon[aPreferredWeapon].numSubmunitions / 3.0));
- else
- iDamage = (damageMultiplier
- * weapon[aPreferredWeapon].damage
- * weapon[aPreferredWeapon].spread);
-
- // compare the targets strength to ours
- int iDiffStrength = ( (tank->l + tank->sh) - (current_tank->l + current_tank->sh));
-
- current_score = iDiffStrength;
-
- if (iDiffStrength < 0)
- {
- // The target is stronger. Are we impressed?
- if ( (defensive < 0.0) && ( (rand() % ( (int) type + 1))))
- // No we aren't, add defensive-modified Strength
- // (The more offensive, the less impressed we are)
- current_score += (int) ( ( (defensive - 3.0) / 2.0) * (double) iDiffStrength);
- }
- else
- // the target is weaker, add points modified by how defensive we are
- current_score += (int) ( (double) iDiffStrength * ( (defensive + 3.0) / 2.0));
-#ifdef DEBUG
- cout << " (str)" << current_score;
-#endif
- // check to see if we are on the same team
- switch ( (int) team)
- {
- case TEAM_JEDI:
- if ((current_tank->player->team == TEAM_JEDI) && !aRotationMode)
- current_score -= 500 * (int) type;
- if ((current_tank->player->team == TEAM_JEDI) && aRotationMode)
- current_score -= MAX_OVERSHOOT; // no team consideration in rotation mode!
-
- if (current_tank->player->team == TEAM_SITH)
- current_score += -200.0 * (defensive - 2.0) * ( (double) type / 2.0);
- break;
-
- case TEAM_SITH:
- if ((current_tank->player->team == TEAM_SITH) && !aRotationMode)
- current_score -= 500 * (int) type;
- if ((current_tank->player->team == TEAM_SITH) && aRotationMode)
- current_score -= MAX_OVERSHOOT; // no team consideration in rotation mode!
-
- if (current_tank->player->team == TEAM_JEDI)
- current_score += -200.0 * (defensive - 2.0) * ( (double) type / 2.0);
- break;
-/*
- default:
- // Neutrals go rather for sith than jedi. (but not much)
- if (current_tank->player->team == TEAM_JEDI)
- current_score += -25.0 * (defensive - 2.0) * ( (double) type / 2.0);
-
- if (current_tank->player->team == TEAM_SITH)
- current_score += -50.0 * (defensive - 2.0) * ( (double) type / 2.0);
-*/
- }
-#ifdef DEBUG
- cout << " (team)" << current_score;
-#endif // DEBUG
- // do we have a grudge against the target
- if (current_tank->player == revenge)
- {
- /*
- switch ( (int) team)
- {
- case TEAM_JEDI:
- // Revenge is a dark force!
- current_score += (50 * ( (defensive - 1.5) * -1.0));
- break;
- case TEAM_SITH:
- // Revenge means power!
- current_score += (200 * ( (defensive - 1.5) * -1.0));
- break;
- default:
- */
- current_score += (100 * ( (defensive - 1.5) * -1.0));
- // }
- }
-#ifdef DEBUG
- cout << " (rev)" << current_score;
-#endif // DEBUG
- // prefer targets further away when violent death is on
- if (_global->violent_death)
- {
- int distance;
- distance = (int) fabs (tank->x - current_tank->x);
-
- if (distance > _global->halfWidth)
- current_score += 100.0 * ( (defensive + 3.0) / 2.0);
- }
-#ifdef DEBUG
- cout << " (dis)" << current_score;
-#endif // DEBUG
- // Add some points if the target is more intelligent than we are (get'em DOWN!)
- // or substract if we are the better one. (Deal with the nutter later...)
- if ( (current_tank->player->type != HUMAN_PLAYER)
- && (current_tank->player->type < DEADLY_PLAYER))
- current_score += 50 * ( (int) current_tank->player->type - (int) type);
- else
- current_score += 50 * ( (int) DEADLY_PLAYER - (int) type);
- // Players, last player type and part time bots are counted as deadly bots.
-#ifdef DEBUG
- cout << " (typ)" << current_score;
-#endif // DEBUG
- // Add points for score difference if they have more than us
- // useless bot: 1 * diff * 60 --> 4 points difference would mean +240 score
- // deadly bot : 3 * diff * 60 --> 4 points difference would mean +720 score
- if (current_tank->player->score > score)
- current_score += ( (int) type + 1) / 2 * (current_tank->player->score - score) * 60;
-
-#ifdef DEBUG
- cout << " (scr)" << current_score;
-#endif // DEBUG
- if (aPreferredWeapon < WEAPONS)
- {
- // As we are wanting to fire a weapon, add points, if the damage is greater than the targets health
- // (if we are in need for money, but not on the same team)
- int iDamageDiff = iDamage - (current_tank->l + current_tank->sh);
-
- if ( (iDamageDiff > 0) && (iMoneyNeed > 0)
- &&( ( team == TEAM_NEUTRAL )
- ||((team != TEAM_NEUTRAL) && (current_tank->player->team != team)) ) )
- current_score += (double) iDamageDiff * (1.0 + ( (defensive + (double) type) / 10.0));
-#ifdef DEBUG
- cout << " (dmg)" << current_score;
-#endif // DEBUG
- // Check whether the target is buried, and substract points for non-burrower/-penetrator
- int iBurylevel = current_tank->howBuried();
-
- if (iBurylevel > BURIED_LEVEL)
- {
- // The target is burried!
- if ( ( (aPreferredWeapon < BURROWER) || (aPreferredWeapon > PENETRATOR))
- && ( (aPreferredWeapon < TREMOR) || (aPreferredWeapon > TECTONIC)))
- {
- // Napalm and shaped charges are absolutely useless
- if ( ( (aPreferredWeapon >= SML_NAPALM) && (aPreferredWeapon <= LRG_NAPALM))
- || ( (aPreferredWeapon >= SHAPED_CHARGE) && (aPreferredWeapon <= CUTTER)))
- current_score -= (int) type * 500; // Even the useless bot isn't *that* stupid
- else
- {
- // For all other weapons we go for the radius of the blast
- if (iBurylevel < weapon[aPreferredWeapon].radius)
- current_score *= 1.0 - ( ( (double) iBurylevel / (double) weapon[aPreferredWeapon].radius) / 2.0);
- else
- current_score -= (double) iDamage * (double) type * ( (double) defensive + 3.0);
- }
- }
- else
- // As we *want* to fire an appropriate weapon, the target looks rather nice to us!
- current_score += ( (double) (iBurylevel - BURIED_LEVEL) / ( ( (double) type + 1.0) / 2.0)) * (double) iDamage;
-#ifdef DEBUG
- cout << " (bur)" << current_score;
-#endif // DEBUG
- }
-
- // Finally, for weapons, see, if we can do good blast damage!
- if (type >= RANGEFINDER_PLAYER)
- {
- int iBlastBonus = 0;
- iBlastBonus = (int) ( (1.0 - ( (double) type / 10.0))
- * getBlastValue (current_tank, weapon[aPreferredWeapon].damage, aPreferredWeapon)
- * ( (defensive - 3.0) / -2.0));
-
- if (iBlastBonus > 0)
- {
- current_score += iBlastBonus;
- // if we need money, blast bonus is valued higher:
-
- if (iMoneyNeed > 0)
- current_score += iBlastBonus * ( (double) type / 2.0);
- }
- }
- }
-
- if (aRotationMode && ((team == TEAM_NEUTRAL) || (team != current_tank->player->team)))
- {
- // In rotationmode we try to actually reach the target with the preferred weapon
- tank->cw = aPreferredWeapon;
- _target = current_tank;
- _targetX = current_tank->x;
- _targetY = current_tank->y;
- iTargettingRound = 0;
- int iOvershoot = abs(calculateAttack(current_tank));
- if (iOvershoot > _global->screenWidth)
- current_score = -1 * MAX_OVERSHOOT; // Wall-Hit! Target is unreachable!
- else
- current_score -= iOvershoot; // substract overshoot!
-#ifdef DEBUG
- cout << " (rot)" << current_score;
-#endif // DEBUG
- }
-
-#ifdef DEBUG
- cout << " => " << current_score<< " : " << current_tank->player->getName() << endl;
-#endif // DEBUG
-
- // decide if this target is better than others
- if ((current_score > best_score) || (!aRotationMode && !best_target))
- {
- best_score = current_score;
- best_target = current_tank;
- }
- attempts++;
- }
- } // end of if we have a valid tank
- }
-
- if (best_target)
- {
- _target = best_target;
- if (_target)
- {
- _targetX= _target->x;
- _targetY= _target->y;
-#ifdef DEBUG
- cout << " -> " << best_target->player->getName() << " wins! (";
- cout << best_target->l << " life, " << best_target->sh << " shield)" << endl;
-#endif // DEBUG
- }
- }
-#ifdef DEBUG
- else
- cout << " -> Unable to find target!!!" << endl;
-#endif // DEBUG
-
- return (best_target);
-}
-
-int PLAYER::getUnburyingTool()
-{
- int iTool = 0; // small missile, if nothing else fits
- if (nm[LRG_LAZER]) iTool = LRG_LAZER;
- if (nm[MED_LAZER]) iTool = MED_LAZER;
- if (nm[SML_LAZER]) iTool = SML_LAZER;
- if (ni[ITEM_TELEPORT]) iTool = WEAPONS + ITEM_TELEPORT;
- if (ni[ITEM_SWAPPER]) iTool = WEAPONS + ITEM_SWAPPER;
- if (nm[HVY_RIOT_BOMB]) iTool = HVY_RIOT_BOMB;
- if (nm[RIOT_BOMB]) iTool = RIOT_BOMB;
- if (nm[RIOT_CHARGE]) iTool = RIOT_CHARGE;
- if (nm[RIOT_BLAST]) iTool = RIOT_BLAST;
- return(iTool);
-}
-
-int PLAYER::computerSelectItem ()
-{
- int current_weapon = 0; // Current Weapon (defaults to small missile)
- int iWeaponPool[15];
- int iPoolSize = (int)type * 3;
- int count = 0;
- // Initialize targetting:
- _target = NULL;
- _targetX= 0;
- _targetY= 0;
-
-#ifdef DEBUG
- cout << getName() << " : Starting target and weapon evaluation..." << endl;
- if (defensive < -0.9) cout << "(True Offensive)" << endl;
- if ((defensive >=-0.9) && (defensive < -0.75)) cout << "(Very Offensive)" << endl;
- if ((defensive >=-0.75) && (defensive < -0.25)) cout << "(Offensive)" << endl;
- if ((defensive >=-0.25) && (defensive < 0.00)) cout << "(Slightly Offensive)" << endl;
- if (defensive == 0.0) cout << "(Neutral)" << endl;
- if ((defensive >0.0) && (defensive <= 0.25)) cout << "(Slightly Defensive)" << endl;
- if ((defensive >0.25) && (defensive <= 0.75)) cout << "(Defensive)" << endl;
- if ((defensive >0.75) && (defensive <= 0.9)) cout << "(Very Defensive)" << endl;
- if (defensive > 0.9) cout << "(True Defensive)" << endl;
- cout << "----------------------------------" << endl;
-#endif // DEBUG
- // 1.: Preselection if buried
-
- if (tank->howBuried () > BURIED_LEVEL)
- {
- current_weapon = getUnburyingTool();
-#ifdef DEBUGctank
- if (current_weapon < WEAPONS)
- cout << "I have chosen a \"" << weapon[current_weapon].name << "\" to free myself first!" << endl;
- else
- cout << "I have chosen a \"" << item[current_weapon - WEAPONS].name << "\" to free myself first!" << endl;
-#endif // DEBUG
- }
- else
- {
- // 2.: Determine iPoolSize
- if (iPoolSize > 15)
- iPoolSize = 15; // Or part-time-bots would bust array size!
-
- // 3.: Fill iWeaponPool
- iWeaponPool[0] = 0; // The Small missile is always there!
- count = 1; // ...so start from second slot!
-
- while (count < iPoolSize)
- {
- int i = 0;
- // bots get a number of tries depending on their intelligence
- current_weapon = 0;
- while (!current_weapon && (i < ( (int) type * 2)))
- {
- current_weapon = Select_Random_Weapon();
- if (! current_weapon)
- current_weapon = Select_Random_Item();
- if ( (current_weapon >= THINGS) //should never occur, but make it sure!
- || ( (current_weapon < WEAPONS) && (!nm[current_weapon]))
- || ( (current_weapon >= WEAPONS) && (!ni[current_weapon - WEAPONS])))
- current_weapon = 0;
- i++;
- }
-
- if (!current_weapon && (count > 1))
- {
- // Slot 0 is allways the small missile, slot 1 is always the first thing the bot "things" of
- // "Last Resort" switching takes place from slot 2 on
- if (nm[MED_MIS]) current_weapon = MED_MIS;
-
- // Only bots with 4+ slots (rangefinder and up) revert to the large missile
- if ( (count > 2) && nm[LRG_MIS]) current_weapon = LRG_MIS;
- }
- iWeaponPool[count] = current_weapon;
- count++;
- }
-
- // 4a.: check if a dirtball is chosen
- if ( (iWeaponPool[1] >= DIRT_BALL) && (iWeaponPool[1] <= SUP_DIRT_BALL))
- current_weapon = iWeaponPool[1];
- else
- {
- // 4b.: Sort iWeaponPool, so that the most liked weapon is first
- // (...if the bot doesn't "forget" to sort ...)
- if (rand() % ( (int) type + 1))
- {
- bool bIsSorted = false;
- while (!bIsSorted)
- {
- bIsSorted = true;
- // The bot does only sort the first few weapons (type+1)
- // Stupid: first two, deadly: first 6
- for (int i = 1; i < ( (int) type + 1); i++)
- {
- if (_weaponPreference[iWeaponPool[i-1]] < _weaponPreference[iWeaponPool[i]])
- {
- bIsSorted = false;
- current_weapon = iWeaponPool[i-1];
- iWeaponPool[i-1] = iWeaponPool[i];
- iWeaponPool[i] = current_weapon;
- }
- }
- }
- }
- current_weapon = iWeaponPool[0]; // Most liked weapon, or, if not sorted, the small missile
- // Having the small missile here means, that the bot is selecting the most easy target.
- // Obviously that means, that the more stupid a bot is, the more often it will go for the easy strike.
- }
-#ifdef DEBUG
- for (int i = 0; i < iPoolSize; i++)
- {
- cout << i << ".: ";
- printf( "% 5d Pref - ", _weaponPreference[iWeaponPool[i]]);
- if (iWeaponPool[i] < WEAPONS)
- cout << "\"" << weapon[iWeaponPool[i]].name << "\"" << endl;
- else
- cout << "\"" << item[iWeaponPool[i] - WEAPONS].name << "\"" << endl;
- }
-#endif // DEBUG
- // if boxed mode is on, we have to try to find a target in rotation mode first!
- if ( _global->bIsBoxed && (current_weapon < WEAPONS)
- && ( (current_weapon < DIRT_BALL)
- ||(current_weapon > SUP_DIRT_BALL)))
- {
- int i = 0;
- while ((i < iPoolSize) && !_target)
- {
- _target = computerSelectTarget(iWeaponPool[i], true);
- i++;
- }
- if (_target)
- current_weapon = iWeaponPool[i - 1];
- }
- if (!_target) _target = computerSelectTarget(current_weapon);
- #ifdef OLD_GAMELOOP
- if (!_target && _oldTarget) _target = _oldTarget;
- #endif
- if (!_target) return (0); // If there is no target available, we have nothing more to do.
- _targetX = _target->x;
- _targetY = _target->y;
-
- // 5.: if a weapon is choosen, cycle through the pool
- // to find the best fitting one
- if ( (current_weapon < WEAPONS)
- && ( (current_weapon < DIRT_BALL)
- || (current_weapon > SUP_DIRT_BALL)))
- {
- int iBestWeapon = current_weapon;
- int iWeaponScore = 0; // Score of the currently used weapon
- int iBestWeapScr = -5000;// Best score calculated so far
- double dDamageMod = 0.0; // modifier for how close to targets health
- int iWeaponDamage = 0; // Calculate the (real) weapon damage
- int iTargetLife = _target->l + _target->sh;
- int iBurylevel = _target->howBuried();
- for (int i = 0; i < iPoolSize; i++)
- {
- current_weapon = iWeaponPool[i];
-
- // 1.: avoid trying to shoot below the tank's level with lasers
- if ( (_targetY >= tank->y) &&
- ( (current_weapon >= SML_LAZER) && (current_weapon <= LRG_LAZER)))
- iWeaponPool[i] = current_weapon = 0; // revert to small missile
-#ifdef DEBUG
- if (current_weapon < WEAPONS)
- cout << " -> \"" << weapon[current_weapon].name << "\" : ";
- else
- cout << " -> (ERROR!) \"" << item[current_weapon - WEAPONS].name << "\" : ";
-#endif // DEBUG
- // 2.: The closer the weapon damage is to the target health, the:
- // - more points added if it kills
- // - less points substracted if it doesn't kill
-
- // avoid trying to use weapons we do not have
- if ( ( current_weapon < 0) || (current_weapon >= WEAPONS) )
- current_weapon = 0;
-
- if (weapon[current_weapon].numSubmunitions > 1)
- iWeaponDamage = damageMultiplier
- * weapon[weapon[current_weapon].submunition].damage
- * (weapon[current_weapon].numSubmunitions / (defensive + 3.0)); // Clusters don't hit well (napalm?)
- else
- {
- if (weapon[current_weapon].spread > 1)
- iWeaponDamage = damageMultiplier
- * weapon[current_weapon].damage
- * (weapon[current_weapon].spread / (defensive + 2.0)); // Spreads *might* hit well, but do seldom
- else
- iWeaponDamage = damageMultiplier
- * weapon[current_weapon].damage;
- }
-
- // The more intelligent and defensive a bot is, the more (char *)"savety bonus" is granted:
- iWeaponDamage = (int) ( (double) iWeaponDamage / (1.0 + ( ( (double) type * (defensive + 2.0)) / 50.0)));
-
- // Examples:
- // Full offensive, useless: 1.0 + ((1.0 * 1.0) / 50.0) = 1.02 <== The damage is like 102% of the real damage
- // Full defensive, deadly : 1.0 + ((5.0 * 3.0) / 50.0) = 1.30 <== The damage is like 130% of the real damage
-#ifdef DEBUG
- cout << iWeaponDamage << " damage, ";
-#endif // DEBUG
- if ( iTargetLife > iWeaponDamage)
- {
- // The weapon is too weak, substract points. (Less if we are offensive)
- dDamageMod = (double) iWeaponDamage / (double) iTargetLife;
- if (defensive < 0)
- iWeaponScore = (10 - (int) type) * (int) ( (-10.0 * (1.1 + defensive)) / dDamageMod);
- else
- iWeaponScore = (10 - (int) type) * (int) (-10.0 / dDamageMod);
-#ifdef DEBUG
- cout << "weak: ";
-#endif // DEBUG
- /* Example calculations:
- Bot is deadly: (int)type = 5
- Weapon does 25% damage of the targets health: dDamegeMod = 0.25
- iWeaponScore = (10 - 5) * (-10 / 0.25) = 5 * -40 = -200
- Weapon does 50% damage of the targets health: dDamegeMod = 0.5
- iWeaponScore = (10 - 5) * (-10 / 0.5) = 5 * -20 = -100
- Weapon does 75% damage of the targets health: dDamegeMod = 0.75
- iWeaponScore = (10 - 5) * (-10 / 0.75) = 5 * -13,3 = -66,5
- Weapon does 95% damage of the targets health: dDamegeMod = 0.95
- iWeaponScore = (10 - 5) * (-10 / 0.95) = 5 * -10,5 = -52,5 */
- }
- else
- {
- // The weapon is strong enough, add points (More, if we are defensive)
- dDamageMod = (double) iTargetLife / (double) iWeaponDamage;
- if (defensive > 0)
- iWeaponScore = (int) type * (int) ( (100.0 * (1.0 + defensive)) * dDamageMod);
- else
- iWeaponScore = (int) type * (int) (100.0 * dDamageMod);
-#ifdef DEBUG
- cout << "strong: ";
-#endif // DEBUG
- /* Example calculations:
- Bot is deadly: (int)type = 5
- Weapon does 105% damage of the targets health: dDamegeMod = 0.95
- iWeaponScore = 5 * (100 * 0.95) = 5 * 95 = 475
- Weapon does 125% damage of the targets health: dDamegeMod = 0.8
- iWeaponScore = 5 * (100 * 0.8) = 5 * 80 = 400
- Weapon does 150% damage of the targets health: dDamegeMod = 0.67
- iWeaponScore = 5 * (100 * 0.67) = 5 * 67 = 335
- Weapon does 200% damage of the targets health: dDamegeMod = 0.5
- iWeaponScore = 5 * (100 * 0.5) = 5 * 50 = 250 */
- }
-#ifdef DEBUG
- cout << dDamageMod << " dMod -> ";
-#endif // DEBUG
- // 3.: Check if the way for a laser is clear if choosen
- if ( (current_weapon >= SML_LAZER) && (current_weapon <= LRG_LAZER))
- {
- int iXlow, iXhigh, iX, iY; // temp vars to calculate with
- int iRockAmount = 0; // How much mountain there is in between
-
- if (tank->x < _targetX)
- {
- iXlow = tank->x;
- iXhigh = _targetX;
- }
- else
- {
- iXlow = _targetX;
- iXhigh = tank->x;
- }
-
- for (iX = iXlow; iX < iXhigh; iX++)
- {
- iY = tank->y - ( (tank->y - _targetY) / (iXhigh - iX)); // y the laser will be on it's way
- if (_env->surface[iX] < iY)
- iRockAmount++; // Rock in the way!
- }
- iWeaponScore -= (int) type * iRockAmount * 10.0;
- }
-
- // 4.: If the target is burried, add points if we are using an adequate weapon
- if (iBurylevel > BURIED_LEVEL)
- {
- // The target is burried!
- if ( ( (current_weapon < BURROWER) || (current_weapon > PENETRATOR))
- && ( (current_weapon < TREMOR) || (current_weapon > TECTONIC)))
- {
- // Napalm and shaped charges are absolutely useless
- if ( ( (current_weapon >= SML_NAPALM) && (current_weapon <= LRG_NAPALM))
- || ( (current_weapon >= SHAPED_CHARGE) && (current_weapon <= CUTTER)))
- iWeaponScore -= (int) type * 500; // Even the useless bot isn't *that* stupid
- else
- {
- // For all other weapons we go for the radius of the blast
- if (iBurylevel < weapon[current_weapon].radius)
- iWeaponScore *= 1.0 - ( ( (double) iBurylevel / (double) weapon[current_weapon].radius) / 2.0);
- else
- iWeaponScore -= (double) iWeaponDamage * (double) type * ( (double) defensive + 3.0);
- }
- }
- else
- // As we *want* to fire an appropriate weapon, the target looks rather nice to us!
- iWeaponScore += ( (double) (iBurylevel - BURIED_LEVEL) / ( ( (double) type + 1.0) / 2.0)) * (double) iWeaponDamage;
- }
-
- // 5.: Substract points, if we are within the blast radius
- // 6.: Check, whether other tanks are in the blast radius, and add points according
- // to the additional damage delivered
- // Note: It seems to be paradox, that defensive bots add points for spread/cluster
- // weapons, but they a) buy onyl few of them and b) add only noticably points
- // if the collateral damage is enough to kill.
- if (type >= RANGEFINDER_PLAYER)
- iWeaponScore += getBlastValue (_target, iWeaponDamage, current_weapon, dDamageMod);
-
- // 7.: Try to hit this target with the weapon, and substract the overshoot
- if ((type >= RANGEFINDER_PLAYER) && (current_weapon < WEAPONS))
- {
- tank->cw = current_weapon;
- iTargettingRound = 0;
- iWeaponScore -= abs(calculateAttack(_target));
- }
-
- // 8.: Modify the Score by weapon preferences
- if (iWeaponScore > 0)
- iWeaponScore = (int) ( (double) iWeaponScore
- * (1.0
- + ( (double) _weaponPreference[current_weapon]
- / (double) MAX_WEAP_PROBABILITY)));
- // (it will only have a slight effect unless the prefs are really wide apart and the
- // original points very close to each other)
-#ifdef DEBUG
- cout << iWeaponScore << " points!" << endl;
-#endif // DEBUG
- // See if the choice fits better
- if (iWeaponScore > iBestWeapScr)
- {
- iBestWeapScr = iWeaponScore;
- iBestWeapon = current_weapon;
- }
- }
- current_weapon = iBestWeapon;
- }
- }
-
- // 6.: Kamikaze probability:
- // The more stupid and offensive a bot is, the more chance he has
- // to blow himself up when life is falling below a certain limit.
- if ( ( (tank->l + tank->sh) < 30)
- || ( ( (tank->l + tank->sh) < 50) && (tank->howBuried () > BURIED_LEVEL)))
- {
- // A buried bot needs to free himself first, leaving all others a free shot!
- double dBlowMod = defensive + 2.0; // gives 1.0 to 3.0
- dBlowMod *= (double) type / 2.0; // gives 1.0 to 9.0
- dBlowMod += 1.0; // gives 2.0 to 10.0
- // dBlowMod is now between 2.0 <== useless full offensive bot (50% chance)
- // and 10.0 <== deadly full defensive bots (10% chance)
-
- if (! (rand() % (int) dBlowMod))
- {
- // okay, I'm a goner, so BANZAAAAIIII! (maybe, check for a way first)
- int iWeaponDamage = 0;
- current_weapon = 0;
- if (nm[NUKE]) current_weapon = NUKE;
- if (nm[DTH_SPREAD]) current_weapon = DTH_SPREAD;
- if (nm[DTH_HEAD]) current_weapon = DTH_HEAD;
- if (nm[ARMAGEDDON]) current_weapon = ARMAGEDDON;
- if (nm[CUTTER]) current_weapon = CUTTER;
- if (ni[ITEM_VENGEANCE]) current_weapon = ITEM_VENGEANCE + WEAPONS;
- if (ni[ITEM_DYING_WRATH]) current_weapon = ITEM_DYING_WRATH + WEAPONS;
- if (ni[ITEM_FATAL_FURY]) current_weapon = ITEM_FATAL_FURY + WEAPONS;
- if (current_weapon < WEAPONS)
- iWeaponDamage = weapon[current_weapon].damage * damageMultiplier;
- else
- iWeaponDamage = weapon[ (int) item[current_weapon - WEAPONS].vals[0]].damage
- * item[current_weapon - WEAPONS].vals[1]
- * damageMultiplier;
-
- // Is something available, and do we take others with us?
- if ( (current_weapon > 0) && (getBlastValue (tank, iWeaponDamage, current_weapon) > 0.0))
- {
- // Yieha! Here we go!
-#ifdef DEBUG
- printf("I have chosen to go bye bye with a...\n");
- if (current_weapon < WEAPONS)
- printf("%s\n", weapon[current_weapon].name);
- else
- printf("%s\n", item[current_weapon - WEAPONS].name);
-#endif // DEBUG
- _targetX = tank->x;
- _targetY = tank->y;
- FLOATTEXT *kamikazeText;
- kamikazeText = new FLOATTEXT (_global, _env, selectKamikazePhrase(),
- (int) tank->x, (int) tank->y - 30, color, CENTRE);
- if ( kamikazeText)
- {
- // kamikazeText->xv = 0;
- // kamikazeText->yv = -0.4;
- kamikazeText->set_speed(0.0, -0.4);
- kamikazeText->maxAge = 300;
- }
- else
- perror ( "player.cc: Failed allocating memory for kamikazeText in computerSelectItem.");
- }
- }
- }
-
-#ifdef DEBUG
- if (_target)
- printf("Finished!\nShooting at %s\n", _target->player->getName() );
- else
- printf("Finished!\nI'll free myself.\n");
- if (current_weapon < WEAPONS)
- printf("Using weapon %s\n", weapon[current_weapon].name);
- else
- printf("Using item %s\n", item[current_weapon - WEAPONS].name);
- printf("=============================================\n");
-#endif // DEBUG
- tank->cw = current_weapon;
- _global->updateMenu = 1;
- return (current_weapon);
-}
-
-int PLAYER::computerControls ()
-{
- int status = 0;
- // At the most basic: select target, select weapon, aim and fire
- tank->requireUpdate ();
- if (_turnStage == SELECT_WEAPON)
- {
- computerSelectItem();
- iTargettingRound = 0;
- _turnStage = CALCULATE_ATTACK;
- _global->updateMenu = 1;
- }
- else if (_turnStage == SELECT_TARGET)
- {
- cout << "ERROR: _turnstage became SELECT_TARGET!" << endl;
- // Target is already chosen by computerSelectItem()
- _turnStage = SELECT_WEAPON;
- }
- else if (_turnStage == CALCULATE_ATTACK)
- {
-
-#ifdef DEBUG_AIM_SHOW
- _global->bASD = true; // Now it is allowed to be true
-#endif
-
- calculateAttack(NULL);
- _turnStage = AIM_WEAPON; // If the targetting wasn't finished, it will be done later!
-
-#ifdef DEBUG_AIM_SHOW
- _global->bASD = false; // And now it isn't!
-#endif
- }
- else if (_turnStage == AIM_WEAPON)
- {
- // First: Determine whether there are any updates to be done yet:
- bool bDoAngleUpdate = false;
- bool bDoPowerUpdate = false;
- if (iTargettingRound == retargetAttempts)
- {
- // Do both when finished aiming
- bDoAngleUpdate = true;
- bDoPowerUpdate = true;
- }
- else if (iTargettingRound && (abs(_targetAngle - tank->a) <= 90))
- // Only do Angle update if still aiming and difference isn't too high
- bDoAngleUpdate = true;
-
- if (bDoAngleUpdate && (_targetAngle > tank->a && tank->a < 270) )
- {
- // Left (If already aimed)
- tank->a++;
- _global->updateMenu = 1;
- }
- else if (bDoAngleUpdate && (_targetAngle < tank->a && tank->a > 90) )
- {
- // Right (if already aimed)
- tank->a--;
- _global->updateMenu = 1;
- }
- else if (bDoPowerUpdate && (_targetPower < (tank->p - 3) && tank->p > 0))
- {
- // Reduce power (if targetting is finished)
- tank->p -= 5;
- _global->updateMenu = 1;
- }
- else if (bDoPowerUpdate && (_targetPower > (tank->p + 3) && tank->p < MAX_POWER) )
- {
- // Increase Power (if targetting is finished)
- tank->p += 5;
- _global->updateMenu = 1;
- }
- else
- {
- // Targetting finished?
- if (iTargettingRound == retargetAttempts)
- _turnStage = FIRE_WEAPON; // Finished aiming, go ahead!
- else
- _turnStage = CALCULATE_ATTACK; // Not finished, do some more aiming
- }
- }
- #ifdef OLD_GAMELOOP
- else if (fi)
- {
- // if (fi) don't do any of the following
- }
- #endif
- else if (_turnStage == FIRE_WEAPON)
- {
- // tank->activateCurrentSelection ();
- _global->updateMenu = 1;
- #ifdef DEBUG
- printf("About to activate weapon.\n");
- #endif
- tank->simActivateCurrentSelection();
- if (type == VERY_PART_TIME_BOT)
- type = NETWORK_CLIENT;
- #ifdef DEBUG
- printf("Weapon was activated.\n");
- #endif
- gloating = false;
- _turnStage = 0;
- status = CONTROL_FIRE;
- }
- return (status);
-}
-
-int PLAYER::humanControls ()
-{
- int moved = 0;
- int status = 0;
-
- if (tank)
- {
- if (!_env->mouseclock && mouse_b & 1 && mouse_x >= 250
- && mouse_x < 378 && mouse_y >= 11 && mouse_y < 19)
- {
- _global->updateMenu = 1;
- if (tank->fs)
- {
- tank->sht++;
- }
- tank->fs = 1;
- if (tank->sht > SHIELDS - 1)
- {
- tank->sht = 0;
- }
- }
- if (!_env->mouseclock && mouse_b & 1 && mouse_x >= 250
- && mouse_x < 378 && mouse_y >= 21
- && mouse_y < 29 && tank->player->ni[tank->sht] > 0 && (tank->fs || tank->sh > 0))
- {
- _global->updateMenu = 1;
- tank->ds = tank->sht;
- tank->player->ni[tank->sht]--;
- tank->sh = (int)item[tank->sht].vals[SHIELD_ENERGY];
- }
-
- tank->requireUpdate ();
- }
-
- //Keyboard control
- if ( _env->stage == STAGE_AIM)
- {
- if (tank)
- {
- if ( (key[KEY_LEFT] || key[KEY_A]) && !ctrlUsedUp && tank->a < 270)
- {
- tank->a++;
- _global->updateMenu = 1;
- if (key_shifts & KB_CTRL_FLAG)
- ctrlUsedUp = TRUE;
- }
- if ( (key[KEY_RIGHT] || key[KEY_D]) && !ctrlUsedUp && tank->a > 90)
- {
- tank->a--;
- _global->updateMenu = 1;
- if (key_shifts & KB_CTRL_FLAG)
- ctrlUsedUp = TRUE;
- }
- if ( (key[KEY_DOWN] || key[KEY_S]) && !ctrlUsedUp && tank->p > 0)
- {
- tank->p -= 5;
- _global->updateMenu = 1;
- if (key_shifts & KB_CTRL_FLAG)
- ctrlUsedUp = TRUE;
- }
- if ( (key[KEY_UP] || key[KEY_W]) && !ctrlUsedUp && tank->p < MAX_POWER)
- {
- tank->p += 5;
- _global->updateMenu = 1;
- if (key_shifts & KB_CTRL_FLAG)
- ctrlUsedUp = TRUE;
- }
- if ( (key[KEY_PGUP] || key[KEY_R]) && !ctrlUsedUp && tank->p < MAX_POWER )
- {
- tank->p += 100;
- if (tank->p > MAX_POWER)
- tank->p = MAX_POWER;
- _global->updateMenu = 1;
- if (key_shifts & KB_CTRL_FLAG)
- ctrlUsedUp = TRUE;
- }
- if ( (key[KEY_PGDN] || key[KEY_F]) && !ctrlUsedUp && tank->p > 0)
- {
- tank->p -= 100;
- if (tank->p < 0)
- tank->p = 0;
- _global->updateMenu = 1;
- if (key_shifts & KB_CTRL_FLAG)
- ctrlUsedUp = TRUE;
- }
- }
- }
+/// Static helper values to help with keyboard controls:
+static bool ctrlUsedUp = false;
+static bool has_ctrl_pressed = false;
+static bool has_shift_pressed = false;
- #ifdef OLD_GAMELOOP
- if (k && !fi)
- #endif
- #ifdef NEW_GAMELOOP
- // if ( keypressed() )
- // k = readkey();
- // else
- // k = 0;
- if (! k)
- {
- if ( keypressed() )
- k = readkey();
- }
- if (k)
- #endif
- {
- status = CONTROL_PRESSED;
- if ( _env->stage == STAGE_AIM)
- {
- if (tank)
- {
- if (k >> 8 == KEY_N)
- {
- tank->a = 180;
- _global->updateMenu = 1;
- }
- if ( (k >> 8 == KEY_TAB) || (k >> 8 == KEY_C) )
- {
- _global->updateMenu = 1;
- while (1)
- {
- tank->cw++;
- if (tank->cw >= THINGS)
- tank->cw = 0;
- if (tank->cw < WEAPONS)
- {
- if (tank->player->nm[tank->cw])
- break;
- }
- else
- {
- if (item[tank->cw - WEAPONS].selectable && tank->player->ni[tank->cw - WEAPONS])
- break;
-
- }
- }
- //calcDamageMatrix (tank, tank->cw);
- //selectTarget ();
- changed_weapon = false;
- }
- if ( ( k >> 8 == KEY_BACKSPACE) || ( k >> 8 == KEY_Z) )
- {
- _global->updateMenu = 1;
- while (1)
- {
- tank->cw--;
- if (tank->cw < 0)
- tank->cw = THINGS - 1;
-
- if (tank->cw < WEAPONS)
- {
- if (tank->player->nm[tank->cw])
- break;
- }
- else
- {
- if (item[tank->cw - WEAPONS].selectable && tank->player->ni[tank->cw - WEAPONS])
- break;
-
- }
- }
- changed_weapon = false;
- }
-
- // put the tank under computer control
- if (k >> 8 == KEY_F10)
- {
- type = PART_TIME_BOT;
- setComputerValues();
- return (computerControls());
- }
-
- // move the tank
- if (k >> 8 == KEY_COMMA) // || (key >> 8 == KEY_H) )
- moved = tank->Move_Tank(DIR_LEFT);
- else if (k >> 8 == KEY_H)
- moved = tank->Move_Tank(DIR_LEFT);
-
- if (k >> 8 == KEY_STOP) // || (key >> 8 == KEY_J) )
- moved = tank->Move_Tank(DIR_RIGHT);
- else if (k >> 8 == KEY_J)
- moved = tank->Move_Tank(DIR_RIGHT);
- if (moved)
- _global->updateMenu = 1;
-
- if ((k >> 8 == KEY_SPACE) &&
- (((tank->cw < WEAPONS) && (tank->player->nm[tank->cw] > 0)) ||
- ((tank->cw < THINGS) && (tank->player->ni[tank->cw - WEAPONS] > 0))))
- {
- // tank->activateCurrentSelection ();
- tank->simActivateCurrentSelection();
- gloating = false;
- status = CONTROL_FIRE;
- }
- }
- }
- if ((_env->stage == STAGE_ENDGAME) && (k >> 8 == KEY_ENTER || k >> 8 == KEY_ESC || k >> 8 == KEY_SPACE))
- return (-1);
- }
- return (status);
-}
+/// @brief default ctor
+PLAYER::PLAYER() :
+ sdi_has_fired(ATOMIC_VAR_INIT(false))
+{
+ // Do a memset initialization thanks to VC++
+ memset(ni, 0, sizeof(int32_t) * ITEMS);
+ memset(nm, 0, sizeof(int32_t) * WEAPONS);
+ memset(currPref, 0, sizeof(int32_t) * THINGS);
+ memset(desired, 0, sizeof(int32_t) * THINGS);
+ memset(saveMoneyFor, 0, sizeof(int32_t) * THINGS);
+ memset(name, 0, sizeof(char) * NAME_LEN);
+ memset(weapPref, 0, sizeof(int32_t) * THINGS);
+
+ nm[0] = 99; // need some small missiles. ;)
+ strncpy(name, "New Player", NAME_LEN);
+
+ // 25% of time set to perplay weapon preferences
+ preftype = (rand () % 4) ? ALWAYS_PREF : PERPLAY_PREF;
+
+ /* Generate a set of preferences now. The reason is:
+ * If the player is a PERPLAY_PREF type player, no preferences
+ * are loaded from the atanks configuration file. But if the user
+ * changes the type to ALWAYS_PREF and starts a new game, no new
+ * preferences are generated. So then these are used, which is
+ * safe enough.
+ */
+ generatePreferences();
+
+ switch (rand() % 4) {
+ case 0: // === red type ===
+ color = makecol(200 + (rand() % 56), rand() % 25, rand() % 25);
+ break;
+ case 1: // === green type ===
+ color = makecol( rand() % 25, 200 + (rand() % 56), rand() % 25);
+ break;
+ case 2: // === blue type ===
+ color = makecol( rand() % 25, rand() % 25, 200 + (rand() % 56) );
+ break;
+ case 3:
+ default: // === violet type ===
+ color = makecol( 200 + (rand() % 56), rand() % 25, 200 + (rand() % 56));
+ break;
+ }
+}
-// returns a static string to the player's team name
-char *PLAYER::Get_Team_Name()
+/// @brief default dtor
+PLAYER::~PLAYER ()
{
- char *team_name = "";
-
- switch ( (int) team)
- {
- case TEAM_JEDI:
- team_name = "Jedi";
- break;
- case TEAM_NEUTRAL:
- team_name = "Neutral";
- break;
- case TEAM_SITH:
- team_name = "Sith";
- break;
- }
-
- return team_name;
+ if (tank) {
+ delete(tank);
+ tank = nullptr;
+ }
+
+ if (opponents) {
+ delete [] opponents;
+ opponents = nullptr;
+ }
}
-
-// Pick a weapon to fire at random.
-// The weapon number is returned. If no other
-// weapon is in inventory, then 0 - small missile is
-// used.
-int PLAYER::Select_Random_Weapon()
+/// @brief update currPrefs array with considering needs and stock amounts
+void PLAYER::boostPrefences(bool boostArmour, bool boostAmps, bool boostWeapons)
{
- int index;
- int num_weapons = 0;
- int random_weapon;
-
- // count number of different weapons we have
- for (index = 1; index < WEAPONS; index++)
- {
- if ( nm[index] )
- num_weapons++;
- }
+ int32_t ai_level = static_cast<int32_t>(type);
+
+ for (int32_t i = 1; i < THINGS; ++i) {
+ double pref = currPref[i];
+
+ // boost preferences if wanted:
+ if (boostArmour
+ && ( (WEAPONS + ITEM_ARMOUR ) <= i)
+ && ( (WEAPONS + ITEM_PLASTEEL) >= i) )
+ pref *= 1. + ((1. + static_cast<double>(RAND_AI_0P)) / 10.);
+
+ if (boostAmps
+ && ( (WEAPONS + ITEM_INTENSITY_AMP) <= i)
+ && ( (WEAPONS + ITEM_VIOLENT_FORCE) >= i) )
+ pref *= 1. + ((1. + static_cast<double>(RAND_AI_0P)) / 10.);
+
+ if (boostWeapons && i && (i < WEAPONS))
+ pref *= 1. + ((1. + static_cast<double>(RAND_AI_1P)) / 10.);
+
+ // Lower weapon preferences if there are enough in stock already
+ if (i && (i < WEAPONS)) {
+ double cur_amount = nm[i] / weapon[i].getDelayDiv();
+ double one_amount = weapon[i].amt / weapon[i].getDelayDiv();
+ double max_amount = one_amount * ai_level;
+ double div_amount = cur_amount - max_amount;
+
+ // - cur_amount is the total amount of single shots. getDelayDiv()
+ // is used, because it simply returns the number of shots fired by
+ // delayed weapons, while it returns always 1 for the other weapons.
+ // - one_amount - The number of nm[i] that is gotten by buying one
+ // unit.
+ // - max_amount - below this no reduction or sale is considered.
+ // - div_amount - if positive, the bot has enough in stock.
+ // - if larger than one_amount, selling the excess
+ // amount is considered.
+
+ if (div_amount >= 1.) {
+ pref /= div_amount;
+ DEBUG_LOG_FIN(name,
+ "Lower %s pref (%d in stock) %d -> %d",
+ weapon[i].getName(),
+ ROUND(cur_amount), currPref[i], ROUND(pref))
+
+ if (env.sellpercent > 0.01) {
+
+ // saleable are the units considered to be sold:
+ int32_t saleable = (div_amount - RAND_AI_1P) / one_amount;
+
+ if (saleable > 0) {
+ money += ROUNDu(weapon[i].cost * env.sellpercent)
+ * saleable;
+ nm[i] -= weapon[i].amt * saleable;
+ DEBUG_LOG_FIN(name,
+ "Sold %d %s for $%s",
+ saleable,
+ weapon[i].getName(),
+ Add_Comma(ROUNDu( weapon[i].cost
+ * env.sellpercent)
+ * saleable))
+ }
+ } // end of selling allowed
+ } // end of having enough in stock
+ } // end of watching weapons
+
+ // Lower item preferences if there are enough in stock already
+ if (i >= WEAPONS) {
+ int32_t j = i - WEAPONS;
+ if ( (j < ITEM_ARMOUR) || (j > ITEM_VIOLENT_FORCE) ) {
+ double cur_amount = ni[j];
+ double one_amount = item[j].amt;
+ double max_amount = one_amount * ai_level;
+
+ // Note: The values are the same as above.
+
+ // Repair kit and SDI are limited differently
+ if (ITEM_REPAIRKIT == j)
+ max_amount *= painSensitivity + defensive + 2.;
+ else if (ITEM_SDI == j)
+ max_amount *= defensive + 2. + (ai_level / 2.);
+
+ double div_amount = cur_amount - max_amount;
+
+ if (div_amount >= 1.) {
+ pref /= div_amount;
+ DEBUG_LOG_FIN(name,
+ "Lower %s pref (%d in stock) %d -> %d",
+ item[j].getName(),
+ ROUND(cur_amount), currPref[i],
+ ROUND(pref))
+
+ if ( (env.sellpercent > 0.01) && (j < ITEM_VENGEANCE) ) {
+
+ // Note: Armour and Amps are not considered here!
+ int32_t saleable = (div_amount - RAND_AI_1P) / one_amount;
+
+ if (saleable > 0) {
+ money += ROUNDu(item[j].cost * env.sellpercent)
+ * saleable;
+ ni[j] -= item[j].amt * saleable;
+ DEBUG_LOG_FIN(name,
+ "Sold %d %s for $%s",
+ saleable,
+ item[j].getName(),
+ Add_Comma(ROUNDu( item[j].cost
+ * env.sellpercent)
+ * saleable))
+ }
+ } // end of selling allowed
+ } // End of having enough in stock
+ } // End of item type limitation
+ } // End of being in items range
+
+ // Write back preferences:
+ currPref[i] = ROUND(pref);
+ }
+}
- // we have no weapons, use default
- if ( num_weapons == 0 )
- return 0;
- // pick a random offset from the bottom of the list
- random_weapon = (rand() % num_weapons) + 1;
+/** @brief Buy item with index @a itemindex
+ * An item has been selected, this function merely buys it. It
+ * first does checks to make sure the item can be bought.
+ * The function returns true if we successfully bought the item or
+ * false if we could not get it for some reason.
+**/
+bool PLAYER::buy_item(int32_t itemindex, int32_t max_boost)
+{
+ bool bought = true;
+
+ if (itemindex < WEAPONS) {
+ // The three things to test:
+ // 1: Enough money?
+ // 2: Space free in stock?
+ // 3: Tech level not too high?
+ if ( (money >= weapon[itemindex].cost)
+ && (nm[itemindex] < MAX_ITEMS_IN_STOCK)
+ && (weapon[itemindex].techLevel <= env.weapontechLevel) ) {
+ money -= weapon[itemindex].cost;
+ nm[itemindex] += weapon[itemindex].amt;
+
+ // don't allow more than MAX_ITEMS_IN_STOCK
+ if (nm[itemindex] > MAX_ITEMS_IN_STOCK)
+ nm[itemindex] = MAX_ITEMS_IN_STOCK;
+ } else
+ bought = false;
+ } // end of buying a weapon
+
+ else {
+
+ // Items need an additional check:
+ // The purchase of boost items is limited by
+ // both AI type and overall boost level.
+ // The same applies to shields
+
+ int32_t ai_level = static_cast<int32_t>(type);
+ int32_t itemNum = itemindex - WEAPONS;
+ bool isBoost = ( (itemNum >= ITEM_ARMOUR)
+ && (itemNum <= ITEM_VIOLENT_FORCE) );
+ bool isShield = ( (itemNum >= ITEM_LGT_SHIELD)
+ && (itemNum <= ITEM_HVY_REPULSOR_SHIELD) );
+
+ if ( (money > item[itemNum].cost)
+ && (ni[itemNum] < MAX_ITEMS_IN_STOCK)
+ && env.isItemAvailable(itemNum)
+ && ( (HUMAN_PLAYER == type )
+ || !(isBoost || isShield)
+ || ( isBoost
+ && (ai_level > boostBought)
+ && (getBoostValue() < max_boost) )
+ || ( isShield
+ && (ai_level > shieldBought) ) ) ) {
+ money -= item[itemNum].cost;
+ ni[itemNum] += item[itemNum].amt;
+
+ // Count it if it was a boost item
+ if (isBoost)
+ boostBought++; // Okay, take it!
+
+ // Count it if it was a shield
+ if (isShield)
+ shieldBought++; // Okay, same procedure
+
+ // don't allow more than MAX_ITEMS_IN_STOCK
+ if (ni[itemNum] > MAX_ITEMS_IN_STOCK)
+ ni[itemNum] = MAX_ITEMS_IN_STOCK;
+ } else
+ bought = false;
+ }
+
+ return bought;
+}
- index = WEAPONS - 1;
- while ( (index) && (random_weapon) )
- {
- if ( nm[index] )
- random_weapon--;
- if (random_weapon)
- index--;
- }
- // If the (char *)"weapon" is a dirtball, skip it 75% of the time:
- if ( ( (index == DIRT_BALL) || (index == LRG_DIRT_BALL)) && (rand() % 4))
- return 0;
-
- // Skip if it is an unburying tool
- if ( (index == RIOT_CHARGE)
- || (index == RIOT_BOMB)
- || (index == RIOT_BLAST)
- || (index == HVY_RIOT_BOMB))
- return 0;
- return index;
+/// @brief call this after loading a game to ensure backwards compatibility
+void PLAYER::checkOppMem()
+{
+ if (!oppCount)
+ newGame();
}
+/// @brief Have the AI choosing something to buy.
+int32_t PLAYER::chooseItemToBuy (int32_t max_boost, int32_t &last_idx)
+{
+
+ // Do not do this if there is no money:
+ if (money < 1000)
+ return -1;
+
+ // Possibly pre-select an item by checking the current situation:
+ int32_t currItem = computerSelectPreBuyItem (max_boost);
+
+ // Be done already if the pre-selection provided a "must have"
+ if ( (currItem > 0) && buy_item(currItem, max_boost) )
+ return currItem;
+
+ // Loop through the wish list and try to buy something.
+ // The more of the item is in the inventory, the less likely
+ // it is that the AI tries to buy the item. If the inventory
+ // is empty, the chance is 50% for a useless bot and 88% for
+ // a deadly bot.
+ // There do not need to be any further modifications, the
+ // preferences are already tweaked by defensiveness and
+ // AI level.
+ int32_t ai_level = static_cast<int32_t>(type);
+ for ( ; last_idx < THINGS ; ++last_idx) {
+
+ currItem = desired[last_idx];
+
+ // Skip small missile, restart instead
+ if (!currItem) {
+ last_idx = 0;
+ // Note: Small missiles are sorted to the end of the list.
+ continue;
+ }
+
+ // Skip unaffordable items
+ if ( ( (currItem < WEAPONS)
+ && (weapon[currItem].cost > money) )
+ || ( (currItem >= WEAPONS)
+ && (item[currItem - WEAPONS].cost > money) ) )
+ continue;
+
+ // Now take the chance
+ int32_t amount = currItem < WEAPONS
+ ? weapon[currItem].amt
+ : item[currItem - WEAPONS].amt;
+ int32_t maxAmt = amount * ai_level;
+ double currAmt = currItem < WEAPONS
+ ? nm[currItem]
+ : ni[currItem - WEAPONS];
+ double newAmt = currAmt + amount;
+ double amtMod = currAmt > 0. ? newAmt / currAmt : 2.;
+ // Note: The more items there are already, the more amtMod
+ // will go down near 1.0, from a maximum of 2.0.
+
+ int32_t chance = ROUNDu(amtMod * (static_cast<double>(ai_level) - .5));
+ /* Results:
+ * Useless : 1 * (1 - 0.5) = 1 * (0.5) = 0.5 => 50% (rounded to 1)
+ * Useless : 2 * (1 - 0.5) = 2 * (0.5) = 1 => 50%
+ * Deadly : 1 * (5 - 0.5) = 1 * (4.5) = 4.5 => 80% (rounded to 5)
+ * Deadly : 2 * (5 - 0.5) = 2 * (4.5) = 9 => 87.5%
+ */
+
+ // Bots have far higher limits for repair units and SDIs:
+ if ( (ITEM_SDI == (currItem - WEAPONS))
+ || (ITEM_REPAIRKIT == (currItem - WEAPONS)) )
+ maxAmt *= 10;
+
+ /* The bot tries to buy the item if:
+ * a) It does not have any and the chance is taken, or
+ * b) It has equal or more than the weapons amount times its level and
+ * a negative (low) chance against its ai_level is taken.
+ */
+ if ( ( ( (currAmt < maxAmt) && (rand() % (chance + 1)) ) /* Scenario a) */
+ || ( (currAmt >= maxAmt) && RAND_AI_0N ) ) /* Scenario b) */
+ && buy_item(currItem, max_boost) ) {
+
+ // Advance index to not buy the same item over and over again
+ if (RAND_AI_1P)
+ ++last_idx;
+
+ return currItem;
+ }
+ }
+
+ return -1;
+}
+
-// This function selects a random item for
-// the AI to use.
-// This item to use is returned. If no
-// item can be found, then the function
-// returns 0
-int PLAYER::Select_Random_Item()
+eControl PLAYER::computerControls (AICore* aicore, bool allow_fire)
{
- int index, item_num;
- int num_items = 0;
- int random_item;
+ // Don't act at all when in scoreboard or endgame stage
+ if (STAGE_SCOREBOARD <= global.stage)
+ return CONTROL_NONE;
+
+ int32_t ai_weap = tank ? tank->cw : SML_MIS;
+ int32_t ai_angle = 0;
+ int32_t ai_power = 0;
+ ePlayerStages ai_stage = PS_STAGE_COUNT;
+ bool is_working = aicore->status(ai_weap, ai_angle, ai_power, ai_stage);
+
+ // If the AI is working with a different player or the AI is dead, return
+ if ( !aicore->can_work()
+ || (is_working && (this != aicore->active_player())) )
+ return CONTROL_NONE;
+
+ // Do not try to start the AI if this players tank is
+ // about to be destroyed or while it is moving.
+ if (!tank || tank->destroy || tank->isFlying() || (tank->l < 1) )
+ return CONTROL_NONE;
+
+ tank->requireUpdate ();
+
+ /* Start the AI for this player if:
+ * 1) The AI is idle and
+ * 2) the game is in aiming stage and
+ * 3) this player still has a tank that (checked above)
+ * 4) is not about to get destroyed and (checked above)
+ * 5) stands still on the ground. (checked above)
+ */
+ if ( (PS_AI_IS_IDLE == ai_stage)
+ && (STAGE_AIM == global.stage) ) {
+
+ if (aicore->start(this))
+ return CONTROL_NONE;
+ else {
+ cerr << "FATAL: Can not start idle AI with this player!" << endl;
+ global.set_command(GLOBAL_COMMAND_MENU);
+ return CONTROL_QUIT;
+ }
+ }
+
+ // Return at once if the AI is still initializing or cleaning up
+ else if ( (PS_AI_INITIALIZE == ai_stage)
+ || (PS_CLEANUP == ai_stage) )
+ return CONTROL_NONE;
+
+ // If the AI is not working (yet), return
+ if (!is_working)
+ return CONTROL_NONE;
+
+ // Now, being here, the ai is working on this very player.
+
+ // Copy stage now, as it is this players stage as well
+ plStage = ai_stage;
+
+ // Sanitize AI values:
+ // Note: None of these should ever kick in!
+ assert( (ai_angle >= 90) && "ERROR: AI set too low angle!");
+ assert( (ai_angle <= 270) && "ERROR: AI set too high angle!");
+ assert( (ai_power >= 0) && "ERROR: AI set too low power!");
+ assert( (ai_power <= MAX_POWER) && "ERROR: AI set too high power!");
+ assert( (0 == (ai_power % 5) ) && "ERROR: AI set non mod 5 power!");
+ assert( ( (ai_weap < 0) /* unset ! */
+ || ( (ai_weap < WEAPONS) && nm[ai_weap ] > 0)
+ || ( (ai_weap >= WEAPONS) && ni[ai_weap - WEAPONS] > 0) )
+ && "ERROR: AI set weapon that has a zero stock!" );
+ if (ai_angle < 90) ai_angle = 90;
+ if (ai_angle > 270) ai_angle = 270;
+ if (ai_power < 0) ai_power = 0;
+ if (ai_power > MAX_POWER) ai_power = MAX_POWER;
+ ai_power -= ai_power % 5;
+ if ( ( (ai_weap < WEAPONS) && nm[ai_weap ] <= 0)
+ || ( (ai_weap >= WEAPONS) && ni[ai_weap - WEAPONS] <= 0) )
+ ai_weap = tank->cw;
+
+ // Only put anything on the screen if this is the firing stage
+ if (PS_FIRE == plStage) {
+
+ // If there is a difference between the AI selection and the
+ // display, transport the values on the screen.
+ if ( (ai_angle != tank->a)
+ || (ai_power != tank->p)
+ || (ai_weap != tank->cw) ) {
+ global.updateMenu = true;
+
+ if (global.skippingComputerPlay) {
+ // When skipping, the values are simply copied:
+ tank->a = ai_angle;
+ tank->p = ai_power;
+ tank->cw = ai_weap;
+ } else {
+ // Transfer angle:
+ if (ai_angle > tank->a)
+ ++tank->a;
+ else if (ai_angle < tank->a)
+ --tank->a;
+
+ // Transfer power:
+ if (ai_power > tank->p)
+ tank->p += 5;
+ else if (ai_power < tank->p)
+ tank->p -= 5;
+
+ // Transfer weapon information:
+ if (ai_weap != tank->cw) {
+ changed_weapon = false;
+
+ int32_t cw_mod = tank->cw < ai_weap ? 1 : -1;
+ tank->cw += cw_mod;
+
+ // Skip unusable items and those that are
+ // out of stock
+ while ( ( !env.isItemAvailable(tank->cw)
+ || ( (tank->cw < WEAPONS) && !nm[tank->cw] )
+ || ( (tank->cw >= WEAPONS) && !ni[tank->cw - WEAPONS] ) )
+ && (tank->cw != ai_weap) )
+ tank->cw += cw_mod;
+ }
+ } // End of regular transfer
+ } // End of transferring difference
+
+ // otherwise fire the current weapon if this is allowed
+ else if (allow_fire) {
+ aicore->weapon_fired();
+ global.updateMenu = 1;
+
+ if (type == VERY_PART_TIME_BOT)
+ type = NETWORK_CLIENT;
+
+ gloating = false;
+ plStage = PS_CLEANUP;
+ return CONTROL_FIRE;
+ }
+ } // End of handling firing stage
+
+ return CONTROL_NONE;
+}
- // count the items we have
- for (index = WEAPONS; index < THINGS; index++)
- {
- item_num = index - WEAPONS;
+int32_t PLAYER::computerSelectPreBuyItem (int32_t max_boost)
+{
+ int32_t max_level = static_cast<int32_t>(DEADLY_PLAYER);
+ int32_t ai_level = static_cast<int32_t>(type);
+ double mood = 1. + defensive + (
+ (static_cast<double>(rand())
+ / (static_cast<double>(RAND_MAX) / 2.)) );
+ // mood is 0.0 <= x <= 4.0
+
+ /* Prior buying anything else, a 5 step system takes place:
+ * 1.: Parachutes (if gravity is on)
+ * 2.: Minimum weapon probability (aka 5 medium and 3 large missiles)
+ * 3.: The most expensive item from the saveMoneyFor list
+ * 4.: Armour/Amps
+ * 5.: "Tools" to free themselves like Riot Blasts
+ * 6.: Shields, if enough money is there
+ * 7.: if all is set, look for dimpled/slick projectiles!
+ */
+
+
+ // Step 1: Check for parachutes (if the bot remembers to check)
+ if ( ( (type >= RANGEFINDER_PLAYER)
+ || RAND_AI_1P )
+ && (env.landSlideType > SLIDE_NONE)
+ && (ni[ITEM_PARACHUTE] < 10)
+ && (money > item[ITEM_PARACHUTE].cost) ) {
+
+ DEBUG_LOG_FIN(name, "Pre-selecting Parachute", 0)
+ return (WEAPONS + ITEM_PARACHUTE);
+ }
+
+
+ // Step 2: Minimum range of damaging weapons:
+ // To be fair, this is always done and never forgotten.
+ if ( (nm[LRG_MIS] < 3) && (money >= weapon[LRG_MIS].cost) ) {
+
+ DEBUG_LOG_FIN(name, "Pre-selecting Large Missile", 0)
+ return LRG_MIS;
+ }
+
+ if ( (nm[MED_MIS] < 5) && (money >= weapon[MED_MIS].cost) ) {
+
+ DEBUG_LOG_FIN(name, "Pre-selecting Medium Missile", 0)
+ return MED_MIS;
+ }
+
+
+ // Step 3: Check weapons/items currently saving money for:
+ int32_t saved_cost = 0;
+ int32_t saved_item = 0;
+ for (int32_t i = 0; i < THINGS; ++i) {
+ if ( (saveMoneyFor[i] > 0)
+ && (saveMoneyFor[i] < money)
+ && (saveMoneyFor[i] > saved_cost) ) {
+ saved_cost = saveMoneyFor[i];
+ saved_item = i;
+ }
+ }
+ // Got one?
+ if (saved_item > 0) {
+ DEBUG_LOG_FIN(name, "Finally got enough money for %s!",
+ saved_item < WEAPONS
+ ? weapon[saved_item].getName()
+ : item[saved_item - WEAPONS].getName())
+ // Take it out from the wish list:
+ saveMoneyFor[saved_item] = 0;
+ return saved_item;
+ }
+
+
+ // Step 4: Check for Armour / Amps (if the bot remembers to check)
+ if ( (boostBought < ai_level) && RAND_AI_1P ) {
+ int32_t boost_value = getBoostValue();
+ int32_t boost_limit = max_boost - boost_value;
+ int32_t armour_val = getArmourValue();
+ int32_t amp_val = getAmpValue();
+
+
+ if ( (boost_limit > (max_level - ai_level + 1))
+ && (!needDamage || RAND_AI_0P) ) {
+
+ DEBUG_LOG_FIN(name,
+ "Pre-Check: Max Boost %d, Armour %d, Amp %d, Limit %d",
+ max_boost, armour_val, amp_val, boost_limit);
+
+ // See which is preferred:
+ boost_limit = max_boost - (2 * (DEADLY_PLAYER + 1)) + RAND_AI_0P;
+
+ if ( boost_value < boost_limit) {
+ if ( (amp_val - defensive)
+ < (armour_val + defensive) ) {
+ needAmp = true;
+ needArmour = false;
+ } else {
+ needAmp = false;
+ needArmour = true;
+ }
+ }
+
+
+ // Prefer armour if the mood is defensive and armour isn't too
+ // far ahead from the amps:
+ if ( needArmour || ( (mood >= 2.0) && (armour_val <= amp_val) ) ) {
+ // The player is in a defensive mood or armour has fallen behind
+ // If we have 25% more money than the plasteel cost, buy it,
+ // else the armour will do. If the armour is far behind, no
+ // money is spared.
+ if ( (money >= (item[ITEM_PLASTEEL].cost * 1.25) )
+ || ( ( (armour_val < (amp_val * 0.5)) || !armour_val)
+ && (money > item[ITEM_PLASTEEL].cost) ) ) {
+
+ DEBUG_LOG_FIN(name, "Pre-selecting Plasteel Plating", 0)
+ return (WEAPONS + ITEM_PLASTEEL);
+
+ }
+
+ if ( (money >= (item[ITEM_ARMOUR].cost * 2.0))
+ && (ni[ITEM_ARMOUR] < ni[ITEM_PLASTEEL])
+ && (mood >= 3.5) ) {
+
+ DEBUG_LOG_FIN(name, "Pre-selecting Armour", 0)
+ return (WEAPONS + ITEM_ARMOUR);
+ }
+ } // End of armour check
+
+ // Otherwise go for a shining new amp:
+ if ( needAmp || ( (mood <= 2.0) && (amp_val <= armour_val) ) ) {
+ // The player is in a offensive mood or amps have fallen behind
+ // If we have 25% more money than the violent force cost, buy
+ // it, else the normal amp will do.
+ // If the amps have fallen behind too much, no money is spared.
+ if ( (money >= (item[ITEM_VIOLENT_FORCE].cost * 1.5) )
+ || ( ( (amp_val < (armour_val * 0.5) ) || !amp_val)
+ && (money > item[ITEM_VIOLENT_FORCE].cost) ) ) {
+
+ DEBUG_LOG_FIN(name, "Pre-selecting Violent Force", 0)
+ return (WEAPONS + ITEM_VIOLENT_FORCE);
+
+ }
+
+ if ( (money >= (item[ITEM_INTENSITY_AMP].cost * 1.75))
+ && (ni[ITEM_INTENSITY_AMP] < ni[ITEM_VIOLENT_FORCE])
+ && (mood < 1.0) ) {
+
+ DEBUG_LOG_FIN(name, "Pre-selecting Intensity Amp", 0)
+ return (WEAPONS + ITEM_INTENSITY_AMP);
+ }
+ } // End of amp check
+ } // End of being allowed to by boost items
+ } // end of step 3
+
+
+ // 5.: Freeing tools
+ if (RAND_AI_0P) {
+ if ( !nm[HVY_RIOT_BOMB] || !nm[RIOT_BOMB] || (mood < 2.) ) {
+
+ // More offensive in this round, check for riot bombs
+ if ( (nm[HVY_RIOT_BOMB] < 2)
+ && (money >= weapon[HVY_RIOT_BOMB].cost) ) {
+
+ DEBUG_LOG_FIN(name, "Pre-selecting Heavy Riot Bomb", 0)
+ return HVY_RIOT_BOMB;
+ }
+
+ if ( (nm[RIOT_BOMB] < 5)
+ && (money >= weapon[RIOT_BOMB].cost) ) {
- if ( (ni[item_num]) && (item[item_num].selectable))
- num_items++;
- } // end of counting items
+ DEBUG_LOG_FIN(name, "Pre-selecting Riot Bomb", 0)
+ return RIOT_BOMB;
+ }
+ } else {
+ // In a defensive mood the charges are checked
+ if ( (nm[RIOT_BLAST] < 2)
+ && (money >= weapon[RIOT_BLAST].cost) ) {
- if (! num_items)
- return 0;
+ DEBUG_LOG_FIN(name, "Pre-selecting Riot Blast", 0)
+ return RIOT_BLAST;
+ }
+
+ if ( (nm[RIOT_CHARGE] < 5)
+ && (money >= weapon[RIOT_CHARGE].cost) ) {
+
+ DEBUG_LOG_FIN(name, "Pre-selecting Riot Charge", 0)
+ return RIOT_CHARGE;
+ }
+ }
+ } // End of step 4
+
+
+ // 6.: Shields
+ if ((shieldBought < ai_level) && RAND_AI_1P) {
+ if (mood <= 1.5) {
+
+ // offensive type, go through reflectors
+ if ( ( ni[ITEM_LGT_REPULSOR_SHIELD]
+ <= (item[ITEM_LGT_REPULSOR_SHIELD].amt * ai_level) )
+ && (money >= (item[ITEM_LGT_REPULSOR_SHIELD].cost * 2.0)) ) {
- // if we have an item, there is still a 75% chance
- // that we may not use it
- if (rand() % 4)
- return 0;
+ DEBUG_LOG_FIN(name, "Pre-selecting Light Repulsor Shield", 0)
+ return (WEAPONS + ITEM_LGT_REPULSOR_SHIELD);
+ }
- // pick a random offset from the bottom of the list
- random_item = (rand() % num_items) + 1;
+ if ( ( ni[ITEM_MED_REPULSOR_SHIELD]
+ <= (item[ITEM_MED_REPULSOR_SHIELD].amt * ai_level) )
+ && (money >= (item[ITEM_MED_REPULSOR_SHIELD].cost * 1.75)) ) {
- index = THINGS - 1;
+ DEBUG_LOG_FIN(name, "Pre-selecting Medium Repulsor Shield", 0)
+ return (WEAPONS + ITEM_MED_REPULSOR_SHIELD);
+ }
- item_num = index - WEAPONS;
+ if ( ( ni[ITEM_HVY_REPULSOR_SHIELD]
+ <= (item[ITEM_HVY_REPULSOR_SHIELD].amt * ai_level) )
+ && (money >= (item[ITEM_HVY_REPULSOR_SHIELD].cost * 1.5)) ) {
- while (item_num && random_item)
- {
- if ( (ni[item_num]) && (item[item_num].selectable))
- random_item--;
+ DEBUG_LOG_FIN(name, "Pre-selecting Heavy Repulsor Shield", 0)
+ return (WEAPONS + ITEM_HVY_REPULSOR_SHIELD);
+ }
+ } // End of offensive mood
- if (random_item)
- {
- index--;
- item_num = index - WEAPONS;
- }
- }
+ if (mood >= 2.5) {
- if ((item_num == ITEM_TELEPORT) || (item_num == ITEM_SWAPPER))
- index = 0;
+ // defensive type, go through hard shields
+ if ( ( ni[ITEM_LGT_SHIELD]
+ <= (item[ITEM_LGT_SHIELD].amt * ai_level) )
+ && (money >= (item[ITEM_LGT_SHIELD].cost * 2.0)) ) {
-// // do not use teleport without a parachute
-// if ( (item_num == ITEM_TELEPORT) && (! ni[ITEM_PARACHUTE]))
-// index = 0;
+ DEBUG_LOG_FIN(name, "Pre-selecting Light Shield", 0)
+ return (WEAPONS + ITEM_LGT_SHIELD);
+ }
- // do not self destruct
- if ( (item_num == ITEM_VENGEANCE)
- || (item_num == ITEM_DYING_WRATH)
- || (item_num == ITEM_FATAL_FURY))
- index = 0;
+ if ( ( ni[ITEM_MED_SHIELD]
+ <= (item[ITEM_MED_SHIELD].amt * ai_level) )
+ && (money >= (item[ITEM_MED_SHIELD].cost * 1.75)) ) {
- if (index > 0)
- index = item_num + WEAPONS;
- return index;
-}
+ DEBUG_LOG_FIN(name, "Pre-selecting Medium Shield", 0)
+ return (WEAPONS + ITEM_MED_SHIELD);
+ }
-// This function takes one off the player's time to fire.
-// If the player runs out of time, the function returns TRUE.
-// If the player has time left, or no time clock is being used,
-// then the function returns FALSE.
-int PLAYER::Reduce_Time_Clock()
-{
- if (! time_left_to_fire) // not using clock
- return FALSE;
-
- time_left_to_fire--;
- if (! time_left_to_fire) // ran out of time
- {
- tank->shots_fired++; // pretend we fired
- time_left_to_fire = _global->max_fire_time;
- return TRUE;
- }
- return FALSE;
-}
+ if ( ( ni[ITEM_HVY_SHIELD]
+ <= (item[ITEM_HVY_SHIELD].amt * ai_level) )
+ && (money >= (item[ITEM_HVY_SHIELD].cost * 1.5)) ) {
-// The damage provided is not the weapon damage, but what the bot calculated!
-int PLAYER::getBlastValue (TANK * aTarget, int aDamage, int aWeapon, double aDamageMod)
-{
- int iHealth = 0; // Health of the evaluated tank
- int iTeam; // Team of the evaluated tank
- double dDistance; // Distance of the target to the evaluated tank
- double dXdist, dYdist; // needed to calculate dDistance
- int iDmgValue = 0; // The value of the blast
- int iBlastDamage = 0; // The damage the weapon is doing right there
- double dTeamMod = 1.0;// Teams react differently on TAs
- int iWeapon = 0; // Items (self destruc) count as CUTTERS for their blast radius!
- TANK * cOppTank;
-
- if (aWeapon < WEAPONS)
- iWeapon = aWeapon;
- else
- iWeapon = CUTTER;
-
- for (int i = 0; i < _global->numPlayers; i++)
- {
- cOppTank = _global->players[i]->tank;
- if (cOppTank && aTarget)
- {
- if (this == _global->players[i])
- // If we'd hit ourself, life is valued twice.
- iHealth = (cOppTank->l / 2) + cOppTank->sh;
- else
- iHealth = cOppTank->l + cOppTank->sh;
- iTeam = _global->players[i]->team;
- // Only count living targets that are not the official target
- if ( (iHealth > 0) && (cOppTank != aTarget))
- {
- dXdist = fabs (aTarget->x - cOppTank->x) - (TANKWIDTH / 2);
- dYdist = fabs (aTarget->y - cOppTank->y) - (TANKHEIGHT / 2);
- if (dXdist < 0) dXdist = 1.0;
- if (dYdist < 0) dYdist = 0.0;
- if ( (aWeapon >= SHAPED_CHARGE)
- && (aWeapon <= CUTTER)
- && ( (dYdist >= (weapon[iWeapon].radius / 20))
- || (dXdist <= (TANKWIDTH / 2))))
- dDistance = 2.0 * (double) weapon[iWeapon].radius; // out of the way!
- else
- dDistance = ABSDISTANCE(dXdist, dYdist, 0, 0) - ( (double) type * (defensive + 2.0));
-
- // Now see whether the tank is in weapon blast range:
- if (dDistance <= weapon[iWeapon].radius)
- {
- // Yep, it is!
- iBlastDamage = (int) ( (double) aDamage * (1.0 - ( (dDistance / (double) weapon[iWeapon].radius) / 2.0)));
- if ( ( (team != TEAM_NEUTRAL) && (team == iTeam))
- || (this == _global->players[i]))
- {
- // it is a fellow team mate, or ourself
- // teams act differently
- switch ( (int) team)
- {
- case TEAM_JEDI:
- dTeamMod = ( (double) type / 2.0) + 1.5; // 2.0 up to 4.0
- break;
- case TEAM_SITH:
- dTeamMod = ( (double) type / 4.0) + 0.75; // 1.0 up to 2.0
- break;
- default:
- dTeamMod = (double) type; // neutrals won't like to hit themselves at all!
- }
-
- if (iBlastDamage > iHealth)
- // We would kill our mate, or cost us too much health!
- iDmgValue -= (int) (dTeamMod * ( (double) aDamage / aDamageMod) * ( (defensive + 3.0) / 2.0));
- else
- // We don't kill, but count the blast at least
- iDmgValue -= (int) (dTeamMod * ( (double) iBlastDamage / aDamageMod) * ( (defensive + 3.0) / 2.0));
-
- // Note: The /=aDamageMod results in blast damage counted worse, if the aDamageMod is low
- }
- else
- {
- // Just Someone... see if we kill them
- if (iBlastDamage > iHealth)
- // Woohoo!
- iDmgValue += (int) ( ( (1.0 + (double) type / 10))
- * (double) (iBlastDamage + iHealth)
- * ( (defensive + 3.0) / 2.0));
- else
- // Well, at least...
- iDmgValue += (int) ( ( (1.0 + (double) type / 10))
- * aDamageMod
- * (double) (iBlastDamage)
- * ( (defensive - 3.0) / -2.0));
- }
- }
- }
- }
- }
-#ifdef DEBUG
- if ((iDmgValue != 0) && (aDamageMod != 1.0))
- cout << "(blast: " << iDmgValue << ") ";
-#endif
- return (iDmgValue);
-}
+ DEBUG_LOG_FIN(name, "Pre-selecting Heavy Shield", 0)
+ return (WEAPONS + ITEM_HVY_SHIELD);
+ }
+ } // End of defensive mood
+ } // End of step 5
-int PLAYER::getMoneyToSave()
-{
- double dMoneyToSave = 0.0;
- int iAvgPref = 0;
- int iPrefLimit = 0;
- int iPrefCount = 0;
- int iInvCount = 0;
- int iInvMoney = 0;
- int iMaxCost = 0;
-
- // if the preferences are exceptionally low, a div by 0 might occur, so it has to be dynamized:
- for (int i = 0; i < WEAPONS; i++)
- {
- if (_weaponPreference[i] > iPrefLimit)
- iPrefLimit = (iPrefLimit + _weaponPreference[i]) / 2;
- }
- // Now it is guaranteed, that iPrefCount and iAvgPref will result in values > 0!
- for (int i = 0; i < WEAPONS; i++)
- {
- if (_weaponPreference[i] > iPrefLimit)
- {
- iPrefCount++;
- iAvgPref += _weaponPreference[i];
- }
- }
- // we are running into divide by zero here.
- if (iPrefCount < 1)
- iPrefCount = 1;
- iAvgPref /= iPrefCount; // Now the average of all relevant weapon preferences
-
- // Now we search for everything over this average and count costs and inventory value:
- for (int i = 0; i < WEAPONS; i++)
- {
- if (_weaponPreference[i] > iAvgPref)
- {
- // This weapon is (char *)"wanted"
- dMoneyToSave += weapon[i].cost;
- if (weapon[i].cost > iMaxCost)
- iMaxCost = weapon[i].cost;
- if (nm[i])
- {
- // We have (some) if this already!
- iInvCount++;
- iInvMoney += (int) ( ( (double) nm[i] / (double) weapon[i].amt) * (double) weapon[i].cost);
- }
- }
- }
- // The Maximum amount is (type * 2) times the most expensive weapon we like:
- iMaxCost *= (int) type * 2;
- // As of writing this, this means a maximum of:
- // Armageddon: 100,000 credits
- // Deadly Bot: (int)type == 5
- // iMaxCost = 100,000 * 5 * 2 = 1,000,000 credits maximum!
-
- dMoneyToSave = (double)iMaxCost * 0.25;
-#ifdef DEBUG
- printf( (char *)"% 4d Prefs, worth % 6d\n", iPrefCount, iAvgPref);
- printf( (char *)"% 4d Items, worth % 6d\n", iInvCount, iInvMoney);
- printf( (char *)"MoneyToSave: % 6d\n", (int)dMoneyToSave);
- printf( (char *)"Money: % 6d -> MaxCost: %6d\n", (int)money, iMaxCost);
-#endif // DEBUG
- // Whenever dMoneyToSave is less than the money owned, iBoostItemsBought is resetted
- if (money > (int)dMoneyToSave)
- iBoostItemsBought = 0; // Let's go!
- return ( (int) dMoneyToSave);
-}
+ // Step 7: Slick / Dimpled Projectiles
+ if ( (ni[ITEM_SLICKP] + ni[ITEM_DIMPLEP]) < 100 ) {
+ if ( (ni[ITEM_DIMPLEP] < 50)
+ && (money >= item[ITEM_DIMPLEP].cost) ) {
+ DEBUG_LOG_FIN(name, "Pre-selecting Dimpled Projectiles", 0)
+ return (WEAPONS + ITEM_DIMPLEP);
+ }
-// if we have some shield strength at the end of the round, then
-// reclaim this shield back into our inventory
+ if ( (ni[ITEM_SLICKP] < 50)
+ && (money >= item[ITEM_SLICKP].cost) ) {
-int PLAYER::Reclaim_Shield()
-{
- if (! tank)
- return FALSE;
- if (! last_shield_used)
- return FALSE;
-
- if (tank->sh > 0)
- ni[last_shield_used] += 1;
-
- last_shield_used = 0;
- return TRUE;
-}
+ DEBUG_LOG_FIN(name, "Pre-selecting Slick Projectiles", 0)
+ return (WEAPONS + ITEM_SLICKP);
+ }
+ }
+ return -1;
+}
+eControl PLAYER::controlTank (AICore* aicore, bool allow_fire)
+{
+ // Handle User input, this is read for providing the ingame menu
+ // even when no human player is active. Otherwise the player would
+ // not be able to enter the ingame menu whenever an AI player is
+ // active.
+ k = 0;
+ K = 0;
+ if (key_shifts & KB_CTRL_FLAG) has_ctrl_pressed = true;
+ else has_ctrl_pressed = false;
+ if (key_shifts & KB_SHIFT_FLAG) has_shift_pressed = true;
+ else has_shift_pressed = false;
+ if (keypressed() ) {
+ k = readkey ();
+ K = k >> 8;
+
+ // Enter ingame menu?
+ if ( (KEY_ESC == K) || (KEY_P == K) ) {
+ int32_t mm = env.ingamemenu ();
+
+ global.make_update (0, 0, env.screenWidth, env.screenHeight);
+ global.make_bgupdate (0, 0, env.screenWidth, env.screenHeight);
+
+ switch (mm) {
+ case 1:
+ // Main Menu
+ global.set_command(GLOBAL_COMMAND_MENU);
+ return CONTROL_QUIT;
+ case 2:
+ // Quit the game
+ global.set_command(GLOBAL_COMMAND_QUIT);
+ return CONTROL_QUIT;
+ case 3:
+ // Skip AI
+ if (STAGE_SCOREBOARD > global.stage)
+ return CONTROL_SKIP;
+ default:
+ break;
+ }
+ }
+
+ // check for number key being pressed
+ if ( (K >= KEY_0) && (K <= KEY_9) ) {
+ int32_t value = K - KEY_0;
+ K = 0;
+
+ // make sure the value is within range
+ if ( (value < env.numGamePlayers)
+ && env.players[value] ) {
+
+ TANK *my_tank = env.players[value]->tank;
+
+ if (my_tank) {
+ snprintf(global.tank_status, 127,
+ "%s: %d + %d -- Team: %s",
+ env.players[value]->name,
+ my_tank->l,
+ my_tank->sh,
+ env.players[value]->getTeamName() );
+ global.tank_status_colour = env.players[value]->color;
+ global.updateMenu = 1;
+ } else
+ memset(global.tank_status, 0, sizeof(char) * 128);
+ }
+ } // end of check status keys
+
+ // Check for scorboard toggle key
+ if ( (K == KEY_TILDE) || (K == KEY_SLASH) ) {
+ K = 0;
+ global.showScoreBoard = !global.showScoreBoard;
+ if (!global.showScoreBoard) {
+ // erase it:
+ global.make_update (0, MENUHEIGHT, 300,
+ (env.maxNumTanks + 1) * env.fontHeight );
+ global.make_bgupdate(0, MENUHEIGHT, 300,
+ (env.maxNumTanks + 1) * env.fontHeight );
+ }
+ }
+
+ // Handle volume control
+ if (KEY_V == K) {
+ K = 0;
+ if (has_shift_pressed)
+ env.increaseVolume();
+ else
+ env.decreaseVolume();
+ }
+ } // End of handling all time possible key presses.
+
+
+ if (key[KEY_F1]) {
+ int32_t nr = 0;
+ do {
+ snprintf(path_buf, PATH_MAX, "screenshot_%03d.bmp", ++nr);
+ } while (!access(path_buf, F_OK));
+
+ if (nr < 1000)
+ save_bmp(path_buf, global.canvas, nullptr);
+ }
+
+
+ if (has_ctrl_pressed && ctrlUsedUp) {
+ if ( !(key[KEY_LEFT]
+ || key[KEY_RIGHT]
+ || key[KEY_UP]
+ || key[KEY_DOWN]
+ || key[KEY_PGUP]
+ || key[KEY_PGDN]
+ || key[KEY_A]
+ || key[KEY_D]
+ //additional control
+ || key[KEY_W]
+ || key[KEY_S]
+ || key[KEY_R]
+ || key[KEY_F]) )
+ ctrlUsedUp = false;
+ } else
+ ctrlUsedUp = false;
+
+
+ // A) HUMAN
+ if ( (HUMAN_PLAYER == type) || !tank)
+ return humanControls (aicore);
+
+ // B) Network Client
#ifdef NETWORK
+ else if (type == NETWORK_CLIENT)
+ return executeNetCmd(true, aicore);
+#endif // NETWORK
+ // C) AI Player
+ else if (global.stage == STAGE_AIM)
+ return computerControls(aicore, allow_fire);
-// Removes the newline character and anything after it
-// from a string.
-void PLAYER::Trim_Newline(char *line)
-{
- int index = 0;
-
- while ( line[index] )
- {
- if ( (line[index] == '\n') || (line[index] == '\r') )
- line[index] = '\0';
- else
- index++;
- }
+ return CONTROL_NONE;
}
-// This function checks for incoming data from a client.
-// If data is coming in, we put the incoming data in the net_command
-// variable. If the socket connection is broken, then we will
-// close the socket and hand control over to the AI.
-int PLAYER::Get_Network_Command()
+void PLAYER::drawIndicator(int32_t x, int32_t y, int32_t h)
{
- int status;
-
- status = Check_For_Incoming_Data(server_socket);
- if (status)
- {
- // we have something coming down the pipe
- memset(net_command, '\0', NET_COMMAND_SIZE); // clear buffer
- status = read(server_socket, net_command, NET_COMMAND_SIZE);
- if (! status) // connection is broken
- {
- close(server_socket);
- type = DEADLY_PLAYER;
- printf("%s lost network connection. Returning control to AI.\n", _name);
- return FALSE;
- }
- else // we got data
- {
- net_command[NET_COMMAND_SIZE - 1] = '\0';
- Trim_Newline(net_command);
- }
- }
- return TRUE;
+ if (HUMAN_PLAYER == type) {
+ int32_t radius = ROUND(static_cast<double>(h) / 2.) - 2;
+ circlefill (global.canvas, x + radius + 2, y + radius + 2, radius,
+ makecol (200, 100, 255));
+ circle (global.canvas, x + radius + 2, y + radius + 2, radius,
+ BLACK);
+ } else {
+ rectfill (global.canvas, x, y + 2, x + 15, y + h - 1, BLACK);
+ for (int32_t i = 0; i < type; ++i)
+ rectfill(global.canvas,
+ x + (3 * i) + 1, y + 3,
+ x + (3 * i) + 2, y + h - 2,
+ makecol (100, 255, 100));
+ }
}
+#ifdef NETWORK
// This function gets called during a round when a networked
// player gets to act. The function checks to see if anything
// is in the net_command variable. If there is, it handles
@@ -4548,12 +967,12 @@ int PLAYER::Get_Network_Command()
//
// We should have some time keeping in here before this goes live
// to avoid hanging the game.
-
-int PLAYER::Execute_Network_Command(int my_turn)
+eControl PLAYER::executeNetCmd(bool my_turn, AICore* aicore)
{
char buffer[NET_COMMAND_SIZE];
- static int player_index = -1;
+ static int playerindex = -1;
static int fire_delay = 0, net_delay = 0;
+ int32_t towrite, written;
if (my_turn)
{
@@ -4561,10 +980,9 @@ int PLAYER::Execute_Network_Command(int my_turn)
// if enough time has passed, we give up and turn control over to the AI
if (fire_delay >= NET_DELAY)
{
- setComputerValues();
type = VERY_PART_TIME_BOT;
fire_delay = 0;
- return ( computerControls() );
+ return computerControls(aicore, true);
}
}
@@ -4579,27 +997,20 @@ int PLAYER::Execute_Network_Command(int my_turn)
type = VERY_PART_TIME_BOT;
strcpy(buffer, "PING");
write(server_socket, buffer, strlen(buffer));
- return (computerControls());
+ return computerControls();
}
- else*/
+ else*/
if (net_delay >= NET_DELAY_SHORT)
- {
// prompt the client to respond
- strcpy(buffer, "PING");
- write(server_socket, buffer, strlen(buffer));
- return 0;
- }
- return 0;
+ SAFE_WRITE(server_socket, "%s", "PING")
+ return CONTROL_NONE;
} // we did not get a command to process
else
net_delay = 0; // we got something, so reset timer
if (! strncmp(net_command, "VERSION", 7) )
- {
- sprintf(buffer, "SERVERVERSION %s", VERSION);
- write(server_socket, buffer, strlen(buffer) );
- }
+ SAFE_WRITE(server_socket, "SERVERVERSION %s", VERSION)
else if (! strncmp(net_command, "CLOSE", 5) )
{
close(server_socket);
@@ -4608,63 +1019,53 @@ int PLAYER::Execute_Network_Command(int my_turn)
else if (! strncmp(net_command, "BOXED", 5) )
{
char buffer[32];
- if (_global->bIsBoxed)
- strcpy(buffer, "BOXED 1");
- else
- strcpy(buffer, "BOXED 0");
- write(server_socket, buffer, strlen(buffer));
+ SAFE_WRITE(server_socket, "BOXED %d", env.isBoxed ? 1 : 0);
}
else if (! strncmp(net_command, "GOSSIP", 6) )
{
- snprintf(_global->tank_status, 128, "%s", & (net_command[7]) );
- _global->updateMenu = TRUE;
+ snprintf(global.tank_status, 127, "%s", & (net_command[7]) );
+ global.updateMenu = TRUE;
}
else if (! strncmp(net_command, "HEALTH", 6) )
{
- int tank_index;
+ int tankindex;
char buffer[64];
- sscanf( &(net_command[7]), "%d", &tank_index );
- if ( (tank_index >= 0) && (tank_index < _global->numPlayers) )
+ sscanf( &(net_command[7]), "%d", &tankindex );
+ if ( (tankindex >= 0) && (tankindex < env.numGamePlayers) )
{
- if (_global->players[tank_index]->tank)
- {
- snprintf(buffer, 64, "HEALTH %d %d %d %d", tank_index,
- _global->players[tank_index]->tank->l,
- _global->players[tank_index]->tank->sh,
- _global->players[tank_index]->tank->sht);
- write(server_socket, buffer, strlen(buffer));
- }
+ if (env.players[tankindex]->tank)
+ SAFE_WRITE(server_socket, "HEALTH %d %d %d %d", tankindex,
+ env.players[tankindex]->tank->l,
+ env.players[tankindex]->tank->sh,
+ env.players[tankindex]->tank->sht)
}
}
else if (! strncmp(net_command, "ITEM", 4) )
{
char buffer[32];
- int item_index;
- sscanf( &(net_command[5]), "%d", &item_index);
- if ( (item_index >= 0) && (item_index < ITEMS) )
- {
- sprintf(buffer, "ITEM %d %d", item_index, ni[item_index]);
- write(server_socket, buffer, strlen(buffer));
- }
+ int itemindex;
+ sscanf( &(net_command[5]), "%d", &itemindex);
+ if ( (itemindex >= 0) && (itemindex < ITEMS) )
+ SAFE_WRITE(server_socket, "ITEM %d %d", itemindex, ni[itemindex])
}
else if (! strncmp(net_command, "MOVE", 4) )
{
- if (! my_turn) return 0;
+ if (! my_turn) return CONTROL_NONE;
if (tank)
{
if (strstr(net_command, "LEFT") )
- tank->Move_Tank(DIR_LEFT);
+ tank->moveTank(DIR_LEFT);
else
- tank->Move_Tank(DIR_RIGHT);
- _global->updateMenu = 1;
+ tank->moveTank(DIR_RIGHT);
+ global.updateMenu = 1;
}
}
- else if (! strncmp(net_command, "FIRE", 4) )
+ else if (! strncmp(net_command, "FIRE", 4) )
{
int angle = 180, power = 1000, item = 0;
- if (! my_turn) return 0;
+ if (! my_turn) return CONTROL_NONE;
sscanf( & (net_command[5]), "%d %d %d", &item, &angle, &power);
fire_delay = 0;
if (tank)
@@ -4687,7 +1088,6 @@ int PLAYER::Execute_Network_Command(int my_turn)
tank->p = power;
if (tank->p > 2000) tank->p = 2000;
else if (tank->p < 0) tank->p = 0;
- tank->simActivateCurrentSelection();
gloating = false;
net_command[0] = '\0';
return CONTROL_FIRE;
@@ -4700,54 +1100,49 @@ int PLAYER::Execute_Network_Command(int my_turn)
bool found = false;
char buffer[128];
- while ( (player_index < _global->numPlayers) && (! found) )
+ while ( (playerindex < env.numGamePlayers) && (! found) )
{
- if ( _global->players[player_index] == this )
+ if ( env.players[playerindex] == this )
{
found = true;
- sprintf(buffer, "YOUARE %d", player_index);
+ SAFE_WRITE(server_socket, "YOUARE %d", playerindex)
}
else
- player_index++;
+ playerindex++;
}
// check to see if something went very wrong
if (! found)
- strcpy(buffer, "YOUARE -1");
- write(server_socket, buffer, strlen(buffer));
+ SAFE_WRITE(server_socket, "YOUARE %d", -1)
}
// return wind speed
else if (! strncmp(net_command, "WIND", 4) )
{
char buffer[64];
- sprintf(buffer, "WIND %f", _env->wind);
- write(server_socket, buffer, strlen(buffer));
+ SAFE_WRITE(server_socket, "WIND %f", global.wind)
}
// find out how many players we have
else if (! strncmp(net_command, "NUMPLAYERS", 10) )
{
char buffer[32];
- sprintf(buffer, "NUMPLAYERS %d", _global->numPlayers);
- write(server_socket, buffer, strlen(buffer));
+ SAFE_WRITE(server_socket, "NUMPLAYERS %d", env.numGamePlayers)
}
else if (! strncmp(net_command, "PLAYERNAME", 10) )
{
int my_number;
char buffer[128];
sscanf( &(net_command[11]), "%d", &my_number);
- if ( (my_number >= 0) && (my_number < _global->numPlayers) )
- {
- sprintf(buffer, "PLAYERNAME %d %s", my_number, _global->players[my_number]->getName() );
- write(server_socket, buffer, strlen(buffer));
- }
- }
+ if ( (my_number >= 0) && (my_number < env.numGamePlayers) )
+ SAFE_WRITE(server_socket, "PLAYERNAME %d %s", my_number,
+ env.players[my_number]->getName())
+ }
// how many rounds are we playing
else if (! strncmp(net_command, "ROUNDS", 6) )
{
char buffer[64];
- sprintf(buffer, "ROUNDS %d %d", (int) _global->rounds, _global->currentround);
- write(server_socket, buffer, strlen(buffer));
+ SAFE_WRITE(server_socket, "ROUNDS %d %d", env.rounds,
+ global.currentround);
}
// send back the position of each tank
else if (! strncmp(net_command, "TANKPOSITION", 12) )
@@ -4756,35 +1151,34 @@ int PLAYER::Execute_Network_Command(int my_turn)
int count;
sscanf( &(net_command[13]), "%d", &count);
- if ( (count >= 0) && (count < _global->numPlayers) )
- {
- if (_global->players[count]->tank)
- {
- sprintf(buffer, "TANKPOSITION %d %d %d", count, (int) _global->players[count]->tank->x,
- (int) _global->players[count]->tank->y);
- write(server_socket, buffer, strlen(buffer));
- }
- }
+ if ( (count >= 0) && (count < env.numGamePlayers)
+ && (env.players[count]->tank) )
+ SAFE_WRITE(server_socket, "TANKPOSITION %d %d %d", count,
+ (int) env.players[count]->tank->x,
+ (int) env.players[count]->tank->y)
}
-
+
// send back the surface height of the dirt
else if (! strncmp(net_command, "SURFACE", 7) )
{
- char buffer[32];
- int x;
-
- sscanf( &(net_command[8]), "%d", &x);
- if ( (x >= 0) && (x < _global->screenWidth) )
- {
- sprintf(buffer, "SURFACE %d %d", x, _env->surface[x]);
- write(server_socket, buffer, strlen(buffer));
- }
- }
+ char buffer[32];
+ int x;
+
+ sscanf( &(net_command[8]), "%d", &x);
+ if ( (x >= 0) && (x < env.screenWidth) )
+#if defined(ATANKS_IS_BSD)
+ SAFE_WRITE(server_socket, "SURFACE %d %d", x,
+ global.surface[x].load())
+#else
+ SAFE_WRITE(server_socket, "SURFACE %d %ld", x,
+ global.surface[x].load())
+#endif // BSD
+ }
else if (! strncmp(net_command, "SCREEN", 6) )
{
char buffer[64];
- sprintf(buffer, "SCREEN %d %d", _global->screenWidth, _global->screenHeight);
- write(server_socket, buffer, strlen(buffer));
+ SAFE_WRITE(server_socket, "SCREEN %d %d",
+ env.screenWidth, env.screenHeight)
}
else if (! strncmp(net_command, "TEAMS", 5) )
{
@@ -4792,17 +1186,14 @@ int PLAYER::Execute_Network_Command(int my_turn)
char buffer[32];
sscanf( &(net_command[6]), "%d", &count);
- if ( (count < _global->numPlayers) && (count >= 0) )
- {
- sprintf(buffer, "TEAM %d %d", count, (int) _global->players[count]->team);
- write(server_socket, buffer, strlen(buffer));
- }
+ if ( (count < env.numGamePlayers) && (count >= 0) )
+ SAFE_WRITE(server_socket, "TEAM %d %d", count,
+ (int) env.players[count]->team)
}
else if (! strncmp(net_command, "WALLTYPE", 8) )
{
char buffer[32];
- sprintf(buffer, "WALLTYPE %d", _env->current_wallType);
- write(server_socket, buffer, strlen(buffer));
+ SAFE_WRITE(server_socket, "WALLTYPE %d", env.current_wallType)
}
else if (! strncmp(net_command, "WEAPON", 6) )
{
@@ -4810,18 +1201,1794 @@ int PLAYER::Execute_Network_Command(int my_turn)
int weapon_number;
sscanf( &(net_command[7]), "%d", &weapon_number);
if ( (weapon_number >= 0) && (weapon_number < WEAPONS) )
- {
- sprintf(buffer, "WEAPON %d %d", weapon_number, nm[weapon_number]);
- write(server_socket, buffer, strlen(buffer));
- }
+ SAFE_WRITE(server_socket, "WEAPON %d %d", weapon_number, nm[weapon_number])
}
net_command[0] = '\0';
- return 0;
+
+ return CONTROL_NONE;
+}
+#endif // NETWORK
+
+
+void PLAYER::exitShop()
+{
+ double tmpDM = (ni[ITEM_INTENSITY_AMP] * item[ITEM_INTENSITY_AMP].vals[0])
+ + (ni[ITEM_VIOLENT_FORCE] * item[ITEM_VIOLENT_FORCE].vals[0]);
+
+ damageMultiplier = 1.0;
+
+ if (tmpDM > 0)
+ damageMultiplier += std::pow(tmpDM, 0.6);
+
+ // All players need small missiles:
+ int32_t max_small_missiles = 500 + (rand() % 500); // max 999
+ nm[SML_MIS] += 50 + (rand() % 50);
+ if (nm[SML_MIS] > max_small_missiles)
+ nm[SML_MIS] = max_small_missiles;
+}
+
+
+/// @brief fill the list of desired items and return the number of damaging weapons
+int32_t PLAYER::generateDesiredList()
+{
+ int32_t result = 0;
+
+ memset(desired, 0, sizeof(int32_t) * THINGS);
+
+ for (int32_t i = 1; i < THINGS; ++i) {
+ if (env.isItemAvailable(i)) {
+ desired[i] = i;
+ currPref[i] = weapPref[i];
+
+ // No negative prefs:
+ if (currPref[i] < 0)
+ currPref[i] = 0;
+
+ // Count weapons:
+ if ( (i < WEAPONS) && nm[i] )
+ ++result;
+ } else
+ desired[i] = 0;
+ // Unavailable items will not be inserted and
+ // the slot is filled with a 0 (small missile)
+ // that will be sorted away.
+ }
+
+ return result;
+}
+
+
+void PLAYER::generatePreferences()
+{
+ double baseProb = static_cast<double>(MAX_WEAP_PROBABILITY) / 2.;
+ int32_t currItem = 0;
+ double worth = 0.;
+ bool isWarhead = false;
+ int32_t maxWeapPref = 0;
+ int32_t maxItemPref = 0;
+ double ai_rate = static_cast<double>(type) / 2. + .5;
+
+ /* --------------------------------------
+ * --- Generate basic characteristics ---
+ * --------------------------------------
+ */
+ defensive = (static_cast<double>(rand() % 10001) / 5000.)
+ - 1.; // [-1;+1]
+ vengeful = 1 + (rand () % 100); // [1;100]
+ vengeanceThreshold = 0.05
+ + (static_cast<double>(rand () % 901)
+ / 1000.); // [0.05;0.95]
+ selfPreservation = static_cast<double>(rand () % 3001) / 1000; // [0;3]
+ painSensitivity = static_cast<double>(rand () % 3001) / 1000; // [0;3]
+
+ // Now 'defensive' can be modified by team:
+ if (team == TEAM_JEDI) {
+ defensive += static_cast<double>(rand() % 501) / 1000.;
+ if (defensive > 1.25)
+ defensive = 1.25; // + 1.25 is Super Defensive
+ }
+ else if (team == TEAM_SITH) {
+ defensive -= static_cast<double>(rand() % 501) / 1000.;
+ if (defensive < -1.25)
+ defensive = -1.25; // - 1.25 is Super Aggressive
+ }
+
+ /* --------------------------------------------
+ * --- Generate weapon and item preferences ---
+ * --------------------------------------------
+ */
+ if (strcmp(name, "New Player")) {
+ DEBUG_LOG_EMO(name, "Generating preferences (defensive %lf)", defensive)
+ DEBUG_LOG_EMO(name, "---------------------------------------", 0)
+ }
+
+ weapPref[0] = 0; // small missiles are always zero!
+
+ for (int32_t i = 1; i < THINGS; ++i) {
+ worth = baseProb / -2.;
+ isWarhead = false;
+
+ if (i < WEAPONS) {
+ // Talking about weapons
+ currItem = i;
+ if (weapon[i].warhead
+ || ( (currItem >= SML_METEOR)
+ && (currItem <= LRG_LIGHTNING)))
+ isWarhead = true;
+ // Warheads are ignored, this way naturals
+ // are taken out automatically.
+ else {
+
+ int32_t warheads = weapon[currItem].spread;
+
+ // === 1. Damage: ===
+ //--------------------
+ if (weapon[currItem].numSubmunitions > 0) {
+
+ warheads = weapon[currItem].numSubmunitions;
+
+ // Use the total damage for clusters
+ worth = weapon[weapon[currItem].submunition].damage * warheads;
+
+ if ( ( (currItem >= SML_NAPALM) && (currItem <= LRG_NAPALM))
+ || ( (currItem >= FUNKY_BOMB) && (currItem <= FUNKY_DEATH)) )
+ worth /= (defensive + 2. + ai_rate) / 2.;
+ // These weapons are too unpredictable to be counted full.
+ // But a true offensive useless bot divides only by 1.0
+ // (so not all all, they do not mind) and a true
+ // defensive deadly bot divides by 2.5
+
+ // Napalm Jellies doe damage over time. So their worth
+ // has to reflect that.
+ if ( (currItem >= SML_NAPALM) && (currItem <= LRG_NAPALM))
+ worth *= static_cast<double>(EXPLOSIONFRAMES) / 2.
+ / static_cast<double>(type);
+
+ if (worth > baseProb)
+ // Or Large Napalm will always be everybody favourite
+ worth = baseProb;
+ } else
+ // Otherwise use spread value with damage. For non-spread
+ // weapons this value is always 1.
+ worth = weapon[currItem].damage * (warheads * 2)
+ * weapon[currItem].getDelayDiv();
+ // Note: warheads are counted twice, because otherwise spread
+ // weapons get a by far too low score!
+
+ // 1 Damage is worth 0.5%o of the base probability.
+ worth *= baseProb * 0.0005;
+
+ // === 2. Defensiveness multiplier ===
+ //-------------------------------------
+ // As said above, defensive players avoid spread/cluster
+ // weapons that are too unpredictable. Thus they rate
+ // non-spreads higher:
+ if (warheads == 1)
+ worth *= (defensive + 1.5) * ai_rate;
+
+ // === 3. Dirt weapons ===
+ //-------------------------
+ // Dirt balls and such weapons do no damage and have to be rated
+ // by defensiveness value. Further more the higher the self
+ // preservation value of a bot, the more likely they will try
+ // to bury main damage dealers for one or two rounds of bought
+ // silence.
+ if ( (currItem >= DIRT_BALL) && (currItem <= SMALL_DIRT_SPREAD) )
+ worth = warheads * weapon[currItem].radius
+ * ai_rate * (defensive + 2.)
+ * selfPreservation;
+
+ // === 4. Debuff weapons ===
+ //---------------------------
+ // These are the opposite of dirt weapons, they are for the
+ // offensive type with high self preservation.
+ // Note: Although the percent bomb is not a de-buff weapon,
+ // it can hardly be rated any other way, as it has no set yield.
+ if ( (currItem >= PERCENT_BOMB) && (currItem <= REDUCER) )
+ worth = 300. * ai_rate
+ * -(defensive - 2.)
+ * selfPreservation;
+
+ // === 5. Shaped weapons are deadly but limited ===
+ //--------------------------------------------------
+ if ( ( (currItem >= SHAPED_CHARGE) && (currItem <= CUTTER) )
+ || ( DRILLER == currItem ) )
+ worth *= 1.0 - ( ((2. * ai_rate) + (defensive * 5.0)) / 20.0);
+ // useless, full offensive: * 1.15
+ // deadly, full defensive : * 0.45
+
+ // === 6. Rollers and penetrators ===
+ //------------------------------------
+ // These are modified by type, as they *are* useful
+ if ( ( (currItem >= SML_ROLLER) && (currItem <= DTH_ROLLER) )
+ || ( (currItem >= BURROWER) && (currItem <= PENETRATOR) ) )
+ worth *= 1.0 + (ai_rate / 5.) + (defensive / 2.);
+
+ // === 7. Tectonics need to be raised! ===
+ //-----------------------------------------
+ // These are nice to damage multiple buried enemies where
+ // penetrators can only reach one.
+ if ( (currItem >= TREMOR) && (currItem <= TECTONIC) )
+ worth *= 2.0 + (ai_rate / 5.) + (defensive / 3.);
+
+ // finally dWorth must not be greater than the 3/4 of MAX_WEAPON_PROBABILITY
+ if (worth > (MAX_WEAP_PROBABILITY * 0.75))
+ worth = MAX_WEAP_PROBABILITY * 0.75;
+ } // End of "not a warhead"
+ } else {
+ // Talking about items
+ currItem = i - WEAPONS;
+
+ /* Theory:
+ * The more offensive a bot is, the more likely they go for
+ * damage amps and repulsor shields.
+ * The more defensive they are, the more likely they go for
+ * armour and hard shields.
+ */
+
+ switch (currItem) {
+ case ITEM_TELEPORT:
+ worth = (defensive - 1.5) * (baseProb / -5.00) * selfPreservation;
+ break;
+ case ITEM_SWAPPER:
+ worth = (defensive - 1.5) * (baseProb / -3.75) * selfPreservation;
+ break;
+ case ITEM_MASS_TELEPORT:
+ worth = (defensive - 1.5) * (baseProb / -1.50) * selfPreservation;
+ break;
+ case ITEM_FAN:
+ worth = 0.0; // useless things!
+ break;
+ case ITEM_VENGEANCE:
+ case ITEM_DYING_WRATH:
+ case ITEM_FATAL_FURY:
+ worth = (defensive + 1.5)
+ * static_cast<double>(weapon[(int)item[currItem].vals[0]].damage)
+ * item[currItem].vals[1];
+ break;
+ case ITEM_ARMOUR:
+ case ITEM_PLASTEEL:
+ worth = baseProb * ( item[currItem].vals[0] / item[ITEM_PLASTEEL].vals[0])
+ * ( defensive + 1.25 );
+ break;
+ case ITEM_LGT_SHIELD:
+ case ITEM_MED_SHIELD:
+ case ITEM_HVY_SHIELD:
+ worth = baseProb * ( item[currItem].vals[0] / item[ITEM_HVY_SHIELD].vals[0])
+ * ( defensive + 1.25 );
+ break;
+ case ITEM_INTENSITY_AMP:
+ case ITEM_VIOLENT_FORCE:
+ worth = baseProb * ( item[currItem].vals[0] / item[ITEM_VIOLENT_FORCE].vals[0])
+ * ( (-1. * defensive) + 1.25 );
+ break;
+ case ITEM_LGT_REPULSOR_SHIELD:
+ case ITEM_MED_REPULSOR_SHIELD:
+ case ITEM_HVY_REPULSOR_SHIELD:
+ worth = baseProb * ( item[currItem].vals[0] / item[ITEM_HVY_REPULSOR_SHIELD].vals[0])
+ * ( (-1. * defensive) + 1.25 );
+ break;
+ case ITEM_REPAIRKIT:
+ worth = (baseProb / 12. * ai_rate)
+ * (defensive + 2.25 + (selfPreservation / 2.));
+ break;
+ case ITEM_PARACHUTE:
+ worth = (baseProb / 10. * ai_rate)
+ * ( (defensive + 1.5) / 1.5);
+ // Parachutes *are* popular! :)
+ break;
+ case ITEM_SLICKP:
+ worth = baseProb / 25. * ai_rate;
+ break;
+ case ITEM_DIMPLEP:
+ worth = baseProb / 15. * ai_rate;
+ break;
+ case ITEM_FUEL:
+ worth = -5000; // Bots don't need fuel
+ isWarhead = true; // Yes, it's a lie. ;-)
+ break;
+ case ITEM_ROCKET:
+ worth = -5000; // Bots don't use rockets
+ isWarhead = true; // The cake is a lie!
+ break;
+ case ITEM_SDI:
+ worth = (baseProb / 13. * ai_rate)
+ * ( (defensive + 2.25 + selfPreservation) / 1.25);
+ break;
+ default:
+ cerr << "Error: Unhandled item " << currItem;
+ cerr << " in generatePreferences()!" << endl;
+ worth = baseProb / ai_rate;
+ }
+
+
+ // worth must not be greater than the half of MAX_WEAPON_PROBABILITY
+ if (worth > (MAX_WEAP_PROBABILITY / 2))
+ worth = MAX_WEAP_PROBABILITY / 2;
+ }
+
+ // Boost the tiny ones:
+ if (worth < (MAX_WEAP_PROBABILITY / 25.0))
+ worth = MAX_WEAP_PROBABILITY / 25.0; // Which is very very little...
+ if (worth < (MAX_WEAP_PROBABILITY / 8))
+ // allow to double (more or less)
+ worth += static_cast<double>(rand() % static_cast<int32_t>(std::abs(worth)));
+
+ // But don't overdo either:
+ if (worth > MAX_WEAP_PROBABILITY)
+ worth = MAX_WEAP_PROBABILITY;
+
+ if (isWarhead)
+ weapPref[i] = 0; // It will not get any slot!
+ else
+ weapPref[i] = ROUND(worth);
+
+ // Count statistical values
+ if ((i < WEAPONS) && (weapPref[i] > maxWeapPref))
+ maxWeapPref = weapPref[i];
+ if ((i >= WEAPONS) && (weapPref[i] > maxItemPref))
+ maxItemPref = weapPref[i];
+
+ if (strcmp(name, "New Player")) {
+ DEBUG_LOG_EMO(name, "%23s (%6s): %5d",
+ i < WEAPONS ? weapon[i].getName() : item[i - WEAPONS].getName(),
+ i < WEAPONS ? "weapon" : "item",
+ weapPref[i])
+ }
+ } // end of looping THINGS
+
+ // If the maximum preferences are too low, they have to be augmented
+ if (maxWeapPref < MAX_WEAP_PROBABILITY) {
+ worth = static_cast<double>(MAX_WEAP_PROBABILITY)
+ / static_cast<double>(maxWeapPref);
+
+ for (int32_t i = 1; i < WEAPONS; ++i) {
+ if (weapPref[i] > (MAX_WEAP_PROBABILITY / 100.0)) {
+ weapPref[i] = ROUND(worth * weapPref[i]);
+ if (strcmp(name, "New Player")) {
+ DEBUG_LOG_EMO(name, "%23s (%6s) amplified to: %5d",
+ weapon[i].getName(), "weapon", weapPref[i])
+ }
+ }
+ }
+ }
+
+ if (maxItemPref < (MAX_WEAP_PROBABILITY * 0.75) ) {
+ worth = static_cast<double>(MAX_WEAP_PROBABILITY) * 0.75
+ / static_cast<double>(maxItemPref);
+
+ for (int32_t i = WEAPONS; i < THINGS; ++i) {
+ if (weapPref[i] > (MAX_WEAP_PROBABILITY / 100.0)) {
+ weapPref[i] = ROUND(worth * weapPref[i]);
+ if (strcmp(name, "New Player")) {
+ DEBUG_LOG_EMO(name, "%23s (%6s) amplified to: %5d",
+ item[i - WEAPONS].getName(), "item", weapPref[i])
+ }
+ }
+ }
+ }
+
+ if (strcmp(name, "New Player"))
+ DEBUG_LOG_EMO(name, "=======================================", 0)
+}
+
+
+int PLAYER::getAmpValue()
+{
+ double amp_val = ni[ITEM_INTENSITY_AMP] * item[ITEM_INTENSITY_AMP].vals[0];
+ double vio_val = ni[ITEM_VIOLENT_FORCE] * item[ITEM_VIOLENT_FORCE].vals[0];
+ return ROUNDu( (amp_val + vio_val)
+ / static_cast<double>(item[ITEM_VIOLENT_FORCE].vals[0]));
+}
+
+
+int PLAYER::getArmourValue()
+{
+ double arm_val = ni[ITEM_ARMOUR] * item[ITEM_ARMOUR].vals[0];
+ double pla_val = ni[ITEM_PLASTEEL] * item[ITEM_PLASTEEL].vals[0];
+ return ROUNDu( (arm_val + pla_val)
+ / static_cast<double>(item[ITEM_PLASTEEL].vals[0]) );
+}
+
+
+int PLAYER::getBoostValue()
+{
+ return (getAmpValue() + getArmourValue());
+}
+
+
+/// @brief return the item preference of item @a idx or -1 if @a idx is out of
+/// range
+/// Note: This uses the static weapPref instead of the adapted currPref,
+/// because it is used by AICore for point calculation.
+int32_t PLAYER::getItemPref(int32_t idx)
+{
+ if ( (idx > -1) && (idx < ITEMS) )
+ return weapPref[WEAPONS + idx];
+ return -1;
+}
+
+
+int32_t PLAYER::getMoneyToSave(bool first_look)
+{
+ // If this is the first look in a shopping round,
+ // the list of items to save money for must be built:
+ if (first_look) {
+ int32_t avgPref = 0;
+ int32_t prefCount = 0;
+ int32_t prefLimit = 0;
+ memset(saveMoneyFor, 0, sizeof(int32_t) * THINGS);
+
+ // if the preferences are exceptionally low, a div by 0
+ // might occur, so it has to be made dynamic:
+ for (int32_t i = 0; i < THINGS; ++i) {
+ if (currPref[i] > prefLimit) {
+ prefLimit += currPref[i];
+ prefCount++;
+ }
+ }
+
+ prefLimit /= prefCount ? prefCount : 1;
+ prefCount = 0;
+
+ // Now it is guaranteed, that prefCount and avgPref
+ // will result in values > 0.
+ for (int32_t i = 0; i < THINGS; ++i) {
+ if (currPref[i] > prefLimit) {
+ prefCount++;
+ avgPref += currPref[i];
+ }
+ }
+
+ // Complete the average preference of the most valuable weapons:
+ avgPref /= prefCount ? prefCount : 1;
+
+ // Now go through the list and add everything above the
+ // average into the save money list, if the amount in stock
+ // is too low:
+ for (int32_t i = 0; i < THINGS; ++i) {
+ int32_t j = i - WEAPONS; // short cut
+ if ( (currPref[i] > avgPref)
+ && ( ( (i < WEAPONS) && (nm[i] < weapon[i].amt) )
+ || ( (j == ITEM_VIOLENT_FORCE) && needAmp)
+ || ( (j == ITEM_PLASTEEL) && needArmour) ) ) {
+ saveMoneyFor[i] = i < WEAPONS ? weapon[i].cost : item[j].cost;
+ DEBUG_LOG_FIN(name, " => Save money for %s!",
+ i < WEAPONS
+ ? weapon[i].getName()
+ : item[j].getName())
+ } // End of having a big enough preference
+ } // End of looping THINGS
+ } // End of building safe-for-list
+
+
+ // moneyToSafe can be easily generated (and regenerated)
+ // by walking through the list of things currently saved
+ // money for:
+ double moneyToSave = 0.;
+ double wanted = 0.;
+ double max_cost = 0.; // The most expensive item is counted twice
+ for (int32_t i = 0; i < THINGS; ++i) {
+ int32_t j = i - WEAPONS; // short cut
+
+ if (saveMoneyFor[i] > 0) {
+ // Still needed?
+ if ( ( (i < WEAPONS) && (nm[i] < weapon[i].amt) )
+ || ( (j == ITEM_VIOLENT_FORCE) && needAmp)
+ || ( (j == ITEM_PLASTEEL) && needArmour) ) {
+ moneyToSave += saveMoneyFor[i];
+ wanted += 1.;
+ if (saveMoneyFor[i] > max_cost)
+ max_cost = saveMoneyFor[i];
+ } else
+ // nope...
+ saveMoneyFor[i] = 0;
+ }
+ }
+
+ // If anything is wanted, the most expensive item is counted twice.
+ // This is done so the bots do not consider having enough money
+ // too early, just like humans would.
+ if (max_cost > 1.) {
+ moneyToSave += max_cost;
+ wanted += 1.;
+
+ // The average money to save modified by the player type
+ // is the result:
+ moneyToSave = (moneyToSave / wanted)
+ * (1. + (static_cast<double>(LAST_PLAYER_TYPE - type) / 10.));
+ }
+
+ /* Results for Armageddon only @ 100k credits:
+ * (wanted is 1 in this test case)
+ * Useless: 100,000 * (1 + ( (6 - 1) / 10)) = 100,000 * 1.5 = 150,000
+ * Deadly : 100,000 * (1 + ( (6 - 5) / 10)) = 100,000 * 1.1 = 110,000
+ */
+
+ // Whenever moneyToSave is less than the money owned, boostBought is reset
+ if (first_look && (money > ROUND(moneyToSave)) ) {
+ boostBought = 0; // Let's go!
+ shieldBought = 0;
+ }
+
+ return ROUND(moneyToSave);
+}
+
+
+// return the player name
+const char* PLAYER::getName () const
+{
+ return name;
+}
+
+
+// This function checks for incoming data from a client.
+// If data is coming in, we put the incoming data in the net_command
+// variable. If the socket connection is broken, then we will
+// close the socket and hand control over to the AI.
+bool PLAYER::getNetCmd()
+{
+#ifdef NETWORK
+ if (Check_For_Incoming_Data(server_socket)) {
+ // we have something coming down the pipe
+ memset(net_command, '\0', NET_COMMAND_SIZE); // clear buffer
+ size_t status = read(server_socket, net_command, NET_COMMAND_SIZE);
+ if (! status) {
+ // connection is broken
+ close(server_socket);
+ type = DEADLY_PLAYER;
+ printf("%s lost network connection. Returning control to AI.\n", name);
+ return false;
+ } else {
+ // we got data
+ net_command[NET_COMMAND_SIZE - 1] = '\0';
+ Trim_Newline(net_command);
+ }
+ }
+#endif // NETWORK
+ return true;
+}
+
+
+/** @brief Get one entry of the opponent memory or the last one attacked
+ * @param[in] idx Index of the opponent memory to get, or -1 to get the last attacked.
+**/
+sOpponent* PLAYER::getOppMem(int32_t idx)
+{
+ // regular memory
+ if ( (idx > -1) && (idx < oppCount) )
+ return &opponents[idx];
+
+ // or the last attacked
+ else if (-1 == idx)
+ return last_opponent;
+
+ // or invalid.
+ return nullptr;
+}
+
+
+// returns a static string to the player's team name
+const char* PLAYER::getTeamName() const
+{
+ static char team_name[9] = { 0 };
+
+ switch (team) {
+ case TEAM_JEDI:
+ snprintf(team_name, 8, "%s", "Jedi");
+ break;
+ case TEAM_NEUTRAL:
+ snprintf(team_name, 8, "%s", "Neutral");
+ break;
+ case TEAM_SITH:
+ snprintf(team_name, 8, "%s", "Sith");
+ break;
+ case TEAM_COUNT:
+ default:
+ snprintf(team_name, 8, "%s", "* N/A *");
+ break;
+ }
+
+ return team_name;
+}
+
+
+/// @brief return the weapon preference of weapon @a idx or -1 if @a idx is out
+/// of range.
+/// Note: This uses the static weapPref instead of the adapted currPref,
+/// because it is used by AICore for point calculation.
+int32_t PLAYER::getWeapPref(int32_t idx)
+{
+ if ( (idx > -1) && (idx < WEAPONS) )
+ return weapPref[idx];
+ return -1;
+}
+
+
+eControl PLAYER::humanControls (AICore* aicore)
+{
+ bool moved = false;
+ eControl status = CONTROL_NONE;
+
+ // Keyboard control in aim stage
+ if ( (global.stage == STAGE_AIM) && tank) {
+ if ( (key[KEY_LEFT] || key[KEY_A])
+ && !ctrlUsedUp && (tank->a < 270) ) {
+ if (has_shift_pressed)
+ tank->a = std::min(tank->a + 5, 270);
+ else
+ tank->a++;
+ global.updateMenu = 1;
+ if (has_ctrl_pressed)
+ ctrlUsedUp = true;
+ }
+
+ if ( (key[KEY_RIGHT] || key[KEY_D])
+ && !ctrlUsedUp && (tank->a > 90) ) {
+ if (has_shift_pressed)
+ tank->a = std::max(tank->a - 5, 90);
+ else
+ tank->a--;
+ global.updateMenu = 1;
+ if (has_ctrl_pressed)
+ ctrlUsedUp = true;
+ }
+
+ if ( (key[KEY_DOWN] || key[KEY_S])
+ && !ctrlUsedUp && (tank->p > 0) ) {
+ if (has_shift_pressed)
+ tank->p = std::max(tank->p - 25, 0);
+ else
+ tank->p -= 5;
+ global.updateMenu = 1;
+ if (has_ctrl_pressed)
+ ctrlUsedUp = true;
+ }
+
+ if ( (key[KEY_UP] || key[KEY_W])
+ && !ctrlUsedUp && (tank->p < MAX_POWER) ) {
+ if (has_shift_pressed)
+ tank->p = std::min(tank->p + 25, MAX_POWER);
+ else
+ tank->p += 5;
+ global.updateMenu = 1;
+ if (has_ctrl_pressed)
+ ctrlUsedUp = true;
+ }
+
+ if ( (key[KEY_PGUP] || key[KEY_R])
+ && !ctrlUsedUp && (tank->p < MAX_POWER) ) {
+ tank->p += 100;
+ if (tank->p > MAX_POWER)
+ tank->p = MAX_POWER;
+ global.updateMenu = 1;
+ if (has_ctrl_pressed)
+ ctrlUsedUp = true;
+ }
+
+ if ( (key[KEY_PGDN] || key[KEY_F])
+ && !ctrlUsedUp && (tank->p > 0) ) {
+ tank->p -= 100;
+ if (tank->p < 0)
+ tank->p = 0;
+ global.updateMenu = 1;
+ if (has_ctrl_pressed)
+ ctrlUsedUp = true;
+ }
+ }
+
+ // See whether there is a new key press
+ if (! k) {
+ if ( keypressed() ) {
+ k = readkey();
+ K = k >> 8;
+ }
+ }
+
+ // If anything is newly there, make it happen
+ if (K) {
+ status = CONTROL_OTHER;
+
+ if ( (global.stage == STAGE_AIM) && tank) {
+ if (K == KEY_N) {
+ tank->a = 180;
+ global.updateMenu = 1;
+ K = 0;
+ }
+
+ if ( (K == KEY_TAB) || (K == KEY_C) ) {
+ global.updateMenu = 1;
+ bool done = false;
+ while (!done) {
+ if (++tank->cw >= THINGS)
+ tank->cw = 0;
+
+ if ( ( (tank->cw < WEAPONS)
+ && tank->player->nm[tank->cw])
+ || ( (tank->cw >= WEAPONS)
+ && item[tank->cw - WEAPONS].selectable
+ && tank->player->ni[tank->cw - WEAPONS] ) )
+ done = true;
+ }
+ changed_weapon = false;
+ K = 0;
+ }
+
+ if ( (K == KEY_BACKSPACE) || (K == KEY_Z) ) {
+ global.updateMenu = 1;
+ bool done = false;
+ while (!done) {
+ if (--tank->cw < 0)
+ tank->cw = THINGS - 1;
+
+ if ( ( (tank->cw < WEAPONS)
+ && tank->player->nm[tank->cw])
+ || ( (tank->cw >= WEAPONS)
+ && item[tank->cw - WEAPONS].selectable
+ && tank->player->ni[tank->cw - WEAPONS] ) )
+ done = true;
+ }
+ changed_weapon = false;
+ K = 0;
+ }
+
+ // put the tank under computer control
+ if (K == KEY_F10) {
+ type = PART_TIME_BOT;
+ K = 0;
+ return (computerControls(aicore, false));
+ }
+
+ // move the tank
+ if ( (K == KEY_COMMA) || (K == KEY_H) )
+ moved = tank->moveTank(DIR_LEFT);
+ if ( (K == KEY_STOP) || (K == KEY_J) )
+ moved = tank->moveTank(DIR_RIGHT);
+
+ if (moved) {
+ global.updateMenu = 1;
+ K = 0;
+ }
+
+ // Fire Weapon
+ if ( (K == KEY_SPACE)
+ && ( ( (tank->cw < WEAPONS)
+ && (tank->player->nm[tank->cw]))
+ || ( (tank->cw >= WEAPONS)
+ && (tank->player->ni[tank->cw - WEAPONS]) ) ) ) {
+
+ gloating = false;
+ status = CONTROL_FIRE;
+ changed_weapon = false;
+ K = 0;
+ }
+ } // end of being in aim satge and having a tank
+ } // End of havig a key
+
+ return status;
+}
+
+
+void PLAYER::initialise (bool loaded_game)
+{
+ // Initialize basic values if this is not loaded
+ if (!loaded_game) {
+ nm[0] = MAX_ITEMS_IN_STOCK;
+ memset(nm, 0, sizeof(int32_t) * WEAPONS);
+ memset(ni, 0, sizeof(int32_t) * ITEMS);
+
+ kills = 0;
+ killed = 0;
+ score = 0;
+ }
+
+ last_opponent = nullptr;
+ tank = nullptr;
+}
+
+
+/// @brief read player data from a dump file.
+bool PLAYER::load_from_file (FILE *file)
+{
+ if (!file)
+ return false;
+
+ char line[MAX_CONFIG_LINE + 1] = { 0 };
+ char field[MAX_CONFIG_LINE + 1] = { 0 };
+ char value[MAX_CONFIG_LINE + 1] = { 0 };
+ char* result = nullptr;
+
+ setlocale(LC_NUMERIC, "C");
+
+ // read until we hit line "*PLAYER*" or "***" or EOF
+ do {
+ result = fgets(line, MAX_CONFIG_LINE, file);
+ if ( !result
+ || !strncmp(line, "***", 3) )
+ // eof OR end of record
+ return false;
+ } while ( strncmp(line, "*PLAYER*", 8) );
+
+ bool done = false;
+
+ while (result && !done) {
+ // read a line
+ memset(line, '\0', MAX_CONFIG_LINE);
+ if ( ( result = fgets(line, MAX_CONFIG_LINE, file) ) ) {
+
+ // if we hit end of the record, stop
+ if (! strncmp(line, "***", 3) ) {
+ done = true;
+ continue; // This exits the loop as well
+ }
+
+ // strip newline character
+ int32_t line_length = strlen(line);
+ while ( line[line_length - 1] == '\n') {
+ line[line_length - 1] = '\0';
+ line_length--;
+ }
+
+ // find equal sign
+ int32_t equal_position = 1;
+ while ( ( equal_position < line_length )
+ && ( line[equal_position] != '=' ) )
+ equal_position++;
+
+ // make sure the equal sign position is valid
+ if (line[equal_position] != '=')
+ continue; // Go to next line
+
+ // separate field from value
+ memset(field, '\0', MAX_CONFIG_LINE);
+ memset(value, '\0', MAX_CONFIG_LINE);
+ strncpy(field, line, equal_position);
+ strncpy(value, &( line[equal_position + 1] ), MAX_CONFIG_LINE);
+
+ // check which field we have and process value
+ if (!strcasecmp(field, "NAME") )
+ strncpy(name, value, NAME_LEN);
+ else if (!strcasecmp(field, "COLOR") ) {
+ sscanf(value, "%d", &color);
+ } else if (!strcasecmp(field, "DEFENSIVE") )
+ sscanf(value, "%lf", &defensive);
+ else if (!strcasecmp(field, "PAINSENSITIVITY") )
+ sscanf(value, "%lf", &painSensitivity);
+ else if (!strcasecmp(field, "PLAYED") )
+ sscanf(value, "%u", &played);
+ else if (!strcasecmp(field, "PREFTYPE") ) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ if ( (val >= 0) && (val <= ALWAYS_PREF))
+ preftype = static_cast<playerPrefType>(val);
+ } else if (!strcasecmp(field, "SELFPRESERVATION") )
+ sscanf(value, "%lf", &selfPreservation);
+ else if (!strcasecmp(field, "TANK_BITMAP") )
+ sscanf(value, "%d", &tankbitmap);
+ else if (!strcasecmp(field, "TEAM") ) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ if ( (val >= 0) && (val <= TEAM_JEDI) )
+ team = static_cast<eTeamTypes>(val);
+ } else if (!strcasecmp(field, "TYPE") ) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+
+ if ( (val >= HUMAN_PLAYER) && (val <= LAST_PLAYER_TYPE))
+ type = static_cast<playerType>(val);
+
+ // make sure previous human players are restored as humans
+ if (type == PART_TIME_BOT)
+ type = HUMAN_PLAYER;
+
+ } else if (!strcasecmp(field, "TYPESAVED") ) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val);
+ if ( (val >= HUMAN_PLAYER) && (val <= LAST_PLAYER_TYPE)) {
+ type_saved = static_cast<playerType>(val);
+ if (type_saved > HUMAN_PLAYER)
+ type = type_saved;
+ }
+ } else if (!strcasecmp(field, "VENGEANCETHRESHOLD") ) {
+ sscanf(value, "%lf", &vengeanceThreshold);
+ // fix old configs
+ if (vengeanceThreshold < 0.05)
+ vengeanceThreshold = 0.05
+ + (static_cast<double>(rand () % 901)
+ / 1000.); // [0.05;0.95]
+ if (vengeanceThreshold > 0.95)
+ vengeanceThreshold = 0.95;
+ } else if (!strcasecmp(field, "VENGEFUL") ) {
+ sscanf(value, "%d", &vengeful);
+ // fix old configs
+ if (vengeful < 1)
+ vengeful = 1 + (rand () % 100); // [1;100]
+ if (vengeful > 100)
+ vengeful = 100;
+ } else if (!strcasecmp(field, "WON") )
+ sscanf(value, "%u", &won);
+ else if (!strcasecmp(field, "WEAPONPREFERENCES") ) {
+ int32_t wp_index = -1;
+ int32_t wp_value = -1;
+ sscanf(value, "%d %d", &wp_index, &wp_value);
+ if ( (wp_index < THINGS) && (wp_index >= 0) )
+ weapPref[wp_index] = wp_value;
+ } // end of valid data line
+ } // end of if we read a line properly
+ } // end of while not done
+
+ return true;
+}
+
+
+void PLAYER::load_game_data(FILE* file)
+{
+ if (!file)
+ return;
+
+ char line[MAX_CONFIG_LINE + 1] = { 0 };
+ char field[MAX_CONFIG_LINE + 1] = { 0 };
+ char value[MAX_CONFIG_LINE + 1] = { 0 };
+ char* result = nullptr;
+ bool done = false;
+ bool has_pref_loaded = false;
+
+ setlocale(LC_NUMERIC, "C");
+
+
+ do {
+ // read a line
+ memset(line, '\0', MAX_CONFIG_LINE);
+ if ( ( result = fgets(line, MAX_CONFIG_LINE, file) ) ) {
+
+ // if we hit end of the record, stop
+ if (! strncmp(line, "***", 3) ) {
+ done = true;
+ continue; // This exits the loop as well
+ }
+
+ // strip newline character
+ int32_t line_length = strlen(line);
+ while ( line[line_length - 1] == '\n') {
+ line[line_length - 1] = '\0';
+ line_length--;
+ }
+
+ // find equal sign
+ int32_t equal_position = 1;
+ while ( ( equal_position < line_length )
+ && ( line[equal_position] != '=' ) )
+ equal_position++;
+
+ // make sure the equal sign position is valid
+ if (line[equal_position] != '=')
+ continue; // Go to next line
+
+ // separate field from value
+ memset(field, '\0', MAX_CONFIG_LINE);
+ memset(value, '\0', MAX_CONFIG_LINE);
+ strncpy(field, line, equal_position);
+ strncpy(value, &( line[equal_position + 1] ), MAX_CONFIG_LINE);
+
+ // check which field we have and process value
+ if (!strcasecmp(field, "DEFENSIVE") )
+ sscanf(value, "%lf", &defensive);
+ else if (!strcasecmp(field, "PAINSENSITIVITY") )
+ sscanf(value, "%lf", &painSensitivity);
+ else if (!strcasecmp(field, "KILLED") )
+ sscanf(value, "%d", &killed );
+ else if (!strcasecmp(field, "KILLS") )
+ sscanf(value, "%d", &kills );
+ else if (!strcasecmp(field, "MONEY") )
+ sscanf(value, "%d", &money );
+ else if (!strcasecmp(field, "SCORE") )
+ sscanf(value, "%d", &score );
+ else if (!strcasecmp(field, "SELFPRESERVATION") )
+ sscanf(value, "%lf", &selfPreservation);
+ else if (!strcasecmp(field, "TYPE") ) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val );
+ if ( (val >= HUMAN_PLAYER) && (val < LAST_PLAYER_TYPE) )
+ type = static_cast<playerType>(val);
+ } else if (!strcasecmp(field, "TYPESAVED") ) {
+ int32_t val = 0;
+ sscanf(value, "%d", &val );
+ if ( (val >= HUMAN_PLAYER) && (val < LAST_PLAYER_TYPE) )
+ type_saved = static_cast<playerType>(val);
+ } else if (!strcasecmp(field, "VENGEANCETHRESHOLD") ) {
+ sscanf(value, "%lf", &vengeanceThreshold);
+ // fix old configs
+ if (vengeanceThreshold < 0.05)
+ vengeanceThreshold = 0.05
+ + (static_cast<double>(rand () % 901)
+ / 1000.); // [0.05;0.95]
+ if (vengeanceThreshold > 0.95)
+ vengeanceThreshold = 0.95;
+ } else if (!strcasecmp(field, "VENGEFUL") ) {
+ sscanf(value, "%d", &vengeful);
+ // fix old configs
+ if (vengeful < 1)
+ vengeful = 1 + (rand () % 100); // [1;100]
+ if (vengeful > 100)
+ vengeful = 100;
+ }
+
+ // Preferences - saved if "PERPLAY_PREF" - type player.
+ else if (!strcasecmp(field, "WEAPONPREFERENCES")) {
+ int32_t prf_idx = -1;
+ int32_t prf_val = -1;
+ sscanf(value, "%d %d", &prf_idx, &prf_val);
+ if ( (prf_idx > -1) && (prf_idx < THINGS) ) {
+ weapPref[prf_idx] = prf_val;
+ has_pref_loaded = true; // to separate old versus new save games
+ }
+ }
+
+ // Inventory of the weapons
+ else if (!strcasecmp(field, "WEAPON")) {
+ int32_t weap_idx = -1;
+ int32_t weap_val = -1;
+ sscanf(value, "%d %d", &weap_idx, &weap_val);
+ if ( (weap_idx > -1) && (weap_idx < WEAPONS) )
+ nm[weap_idx] = weap_val;
+ }
+
+ // Inventory of the items
+ else if (!strcasecmp(field, "ITEM")) {
+ int32_t item_idx = -1;
+ int32_t item_val = -1;
+ sscanf(value, "%d %d", &item_idx, &item_val);
+ if ( (item_idx > -1) && (item_idx < ITEMS) )
+ ni[item_idx] = item_val;
+ }
+
+ // Opponents Memory
+ else if (!strcasecmp(field, "OPPCOUNT") ) {
+ int32_t safed_count = 0;
+ sscanf(value, "%d", &safed_count );
+
+ // prepare the memory
+ if (opponents) {
+ delete [] opponents;
+ opponents = nullptr;
+ }
+
+ if (safed_count) {
+ try {
+ oppCount = safed_count;
+ opponents = new opp_t[oppCount];
+ } catch (std::exception &e) {
+ cerr << "ERROR: Unable to allocate ";
+ cerr << (sizeof(opp_t) * oppCount);
+ cerr << " bytes for opponents array!" << endl;
+ cerr << "ERROR: " << e.what() << endl;
+ oppCount = 0;
+ }
+ } else
+ oppCount = 0;
+ } // end of oppcount handling
+ else if (!strcasecmp(field, "OPPMEM_INDX") ) {
+ int32_t opp_idx = -1;
+ int32_t opp_val = -1;
+ sscanf(value, "%d %d", &opp_idx, &opp_val);
+ if ( (opp_idx > -1) && (opp_idx < oppCount) ) {
+ opponents[opp_idx].index = opp_val;
+ opponents[opp_idx].opponent = env.allPlayers[opp_val];
+ }
+ } else if (!strcasecmp(field, "OPPMEM_DDEA") ) {
+ int32_t opp_idx = -1;
+ int32_t opp_val = -1;
+ sscanf(value, "%d %d", &opp_idx, &opp_val);
+ if ( (opp_idx > -1) && (opp_idx < oppCount) )
+ opponents[opp_idx].damage_from = opp_val;
+ } else if (!strcasecmp(field, "OPPMEM_DDON") ) {
+ int32_t opp_idx = -1;
+ int32_t opp_val = -1;
+ sscanf(value, "%d %d", &opp_idx, &opp_val);
+ if ( (opp_idx > -1) && (opp_idx < oppCount) )
+ opponents[opp_idx].damage_to = opp_val;
+ } else if (!strcasecmp(field, "OPPMEM_FEAR") ) {
+ int32_t opp_idx = -1;
+ double opp_val = 0.;
+ sscanf(value, "%d %lf", &opp_idx, &opp_val);
+ if ( (opp_idx > -1) && (opp_idx < oppCount) )
+ opponents[opp_idx].fear = opp_val;
+ } else if (!strcasecmp(field, "OPPMEM_KIME") ) {
+ int32_t opp_idx = -1;
+ int32_t opp_val = -1;
+ sscanf(value, "%d %d", &opp_idx, &opp_val);
+ if ( (opp_idx > -1) && (opp_idx < oppCount) )
+ opponents[opp_idx].killed_me = opp_val;
+ } else if (!strcasecmp(field, "OPPMEM_KITH") ) {
+ int32_t opp_idx = -1;
+ int32_t opp_val = -1;
+ sscanf(value, "%d %d", &opp_idx, &opp_val);
+ if ( (opp_idx > -1) && (opp_idx < oppCount) )
+ opponents[opp_idx].killed_them = opp_val;
+ }
+
+
+ } // End of having a line
+ } while (result && !done);
+ // End of reading player section
+
+
+ // For backwards compatibility the preferences must be generated
+ // if this is a PERPLAY type player but no preferences got saved.
+ // This might be the case for very old save games.
+ if (!has_pref_loaded
+ && (PERPLAY_PREF == preftype)
+ && (type != HUMAN_PLAYER) )
+ generatePreferences();
+}
+
+
+/// @brief reserve memory for the opponents array and fill it
+void PLAYER::newGame()
+{
+ if (env.numGamePlayers) {
+
+ if (opponents) {
+ delete [] opponents;
+ opponents = nullptr;
+ }
+
+ try {
+ oppCount = env.numGamePlayers;
+ opponents = new opp_t[oppCount];
+ } catch (std::exception &e) {
+ cerr << "ERROR: Unable to allocate " << (sizeof(opp_t) * oppCount);
+ cerr << " bytes for opponents array!" << endl;
+ cerr << "ERROR: " << e.what() << endl;
+ oppCount = 0;
+ }
+ }
+
+ if (oppCount) {
+ for (int32_t i = 0; i < oppCount; ++i) {
+ opponents[i].opponent = env.players[i];
+ opponents[i].index = env.players[i]->index;
+ }
+ }
+}
+
+
+// run this at the begining of each turn
+void PLAYER::newRound()
+{
+ // if the player is under computer control, give it back to the player
+ if ( type == PART_TIME_BOT )
+ type = HUMAN_PLAYER;
+
+ if (!tank) {
+ try {
+ tank = new TANK();
+ tank->player = this;
+ } catch (std::exception &e) {
+ cerr << "FATAL: Error allocating memory for TANK in player.cpp:";
+ cerr << __LINE__ << " : " << e.what() << endl;
+ global.set_command(GLOBAL_COMMAND_QUIT);
+ }
+ }
+ // tank->newRound() doesn't need to be called, because
+ // the game loop will do that on tank placement.
+
+ // if we are playing in a campaign, raise the AI level for every 20% played
+ // rounds, so that useless players become deadly at 80% played rounds
+ if (env.campaign_mode
+ && (global.currentround < env.nextCampaignRound)
+ && (type > HUMAN_PLAYER)
+ && (type < DEADLY_PLAYER) )
+ ++type;
+
+ // reset some basic values
+ changed_weapon = false;
+ time_left_to_fire = env.maxFireTime;
+ skip_me = false;
+ last_shield_used = 0;
+
+ // Save damage from opponents if there was some not processed.
+ // Although this would be done automatically once the AI takes
+ // this player over the next time, lingering damage from the
+ // last round can lead to panic actions and/or revenge actions
+ // against players, who haven't fired, yet.
+ // Noting the damage will raise the probability, but only once
+ // the opponent had their first shot.
+ for (int32_t i = 0; i < oppCount; ++i) {
+ if (opponents[i].damage_last > 0) {
+ opponents[i].damage_from += opponents[i].damage_last;
+ opponents[i].damage_last = 0;
+ }
+ }
+}
+
+
+void PLAYER::noteDamageFrom(PLAYER* opponent, int32_t damage, bool destroyed)
+{
+ if (opponent) {
+ int32_t idx = oppCount;
+ int32_t max_score = 0;
+
+ for (int32_t i = 0; i < oppCount; ++i) {
+ if (opponents[i].revenge_dmg > max_score)
+ max_score = opponents[i].revenge_dmg;
+ if (opponents[i].opponent == opponent)
+ idx = i;
+ }
+
+ if ( idx < oppCount ) {
+ opponents[idx].damage_last += damage;
+ if (destroyed)
+ opponents[idx].killed_me++;
+
+ // If this one has the new top score and is not
+ // the current revenge player, get a message out
+ int32_t rev_dmg = opponents[idx].damage_last
+ + opponents[idx].revenge_dmg;
+
+ if ( (opponents[idx].opponent != this)
+ && (opponents[idx].opponent != revenge)
+ && (rev_dmg > (vengeanceThreshold * tank->getMaxLife()))
+ && (rev_dmg > max_score) ) {
+
+ revenge = opponents[idx].opponent;
+
+ if (!global.skippingComputerPlay ) {
+ try {
+ new FLOATTEXT(selectRevengePhrase(),
+ tank->x, tank->y - 30, .0, -.4, color,
+ CENTRE, TS_NO_SWAY, 300);
+ } catch (...) {
+ perror ( "player.cpp: Failed to allocate memory for"
+ " revenge text in noteDamageFrom().");
+ }
+ }
+ }
+ } // end of having the opponent
+ } // end of having any opponent
+}
+
+
+void PLAYER::noteDamageTo(PLAYER* opponent, int32_t damage, bool destroyed)
+{
+ if (opponent) {
+ int32_t idx = 0;
+
+ while ((idx < oppCount) && (opponent != opponents[idx].opponent))
+ ++idx;
+
+ if ( idx < oppCount ) {
+ opponents[idx].damage_to += damage;
+ if (destroyed)
+ opponents[idx].killed_them++;
+ }
+ }
+}
+
+
+// if we have some shield strength at the end of the round, then
+// reclaim this shield back into our inventory
+void PLAYER::reclaimShield()
+{
+ if (tank && last_shield_used && (tank->sh > 0))
+ ni[last_shield_used] += 1;
+ last_shield_used = 0;
+}
+
+
+// This function takes one off the player's time to fire.
+// If the player runs out of time, the function returns true.
+// If the player has time left, or no time clock is being used,
+// then the function returns false.
+bool PLAYER::reduceClock()
+{
+ if (! time_left_to_fire)
+ // not using clock
+ return false;
+
+ if (0 == --time_left_to_fire) {
+ time_left_to_fire = env.maxFireTime;
+ return true;
+ }
+
+ return false;
+}
+
+
+/// @brief save game relevant data to @a file
+void PLAYER::save_game_data(FILE* file)
+{
+ fprintf(file, "KILLED=%d\n", killed);
+ fprintf(file, "KILLS=%d\n", kills);
+ fprintf(file, "MONEY=%d\n", money);
+ fprintf(file, "SCORE=%d\n", score);
+ fprintf(file, "TYPE=%d\n", type);
+ fprintf(file, "TYPESAVED=%d\n", type_saved);
+
+ // Preferences, needed for "PERPLAY_PREF" - players
+ if ( (PERPLAY_PREF == preftype) && (HUMAN_PLAYER != type) ) {
+ // Note: "ALWAYS_PREF" - players do not need this here, but in
+ // save_to_file(), as the preferences are generated only once.
+ fprintf(file, "DEFENSIVE=%lf\n", defensive);
+ fprintf(file, "PAINSENSITIVITY=%lf\n", painSensitivity);
+ fprintf(file, "SELFPRESERVATION=%lf\n", selfPreservation);
+ fprintf(file, "VENGEANCETHRESHOLD=%lf\n", vengeanceThreshold);
+ fprintf(file, "VENGEFUL=%d\n", vengeful);
+ for (int32_t i = 0; i < THINGS; ++i)
+ fprintf (file, "WEAPONPREFERENCES=%d %d\n", i, weapPref[i]);
+ }
+
+ // Inventory of the weapons
+ for (int32_t i = 0; i < WEAPONS; ++i)
+ fprintf(file, "WEAPON=%d %d\n", i, nm[i]);
+
+ // Inventory of the items
+ for (int32_t i = 0; i < ITEMS; ++i)
+ fprintf(file, "ITEM=%d %d\n", i, ni[i]);
+
+ // Opponents memory
+ fprintf(file, "OPPCOUNT=%d\n", oppCount);
+ for (int32_t i = 0; i < oppCount; ++i) {
+ int32_t idx = opponents[i].index; // Just a shortcut
+
+ // Save damage from last turn if any is still there:
+ if (opponents[i].damage_last > 0) {
+ opponents[i].damage_from += opponents[i].damage_last;
+ opponents[i].damage_last = 0;
+ }
+ fprintf(file, "OPPMEM_INDX=%d %d\n", i, idx);
+ fprintf(file, "OPPMEM_DDEA=%d %d\n", i, opponents[i].damage_from);
+ fprintf(file, "OPPMEM_DDON=%d %d\n", i, opponents[i].damage_to);
+ fprintf(file, "OPPMEM_FEAR=%d %lf\n", i, opponents[i].fear);
+ fprintf(file, "OPPMEM_KIME=%d %d\n", i, opponents[i].killed_me);
+ fprintf(file, "OPPMEM_KITH=%d %d\n", i, opponents[i].killed_them);
+ }
+
+ fprintf(file, "***\n");
+}
+
+
+/// @brief dump full player data to @a file
+void PLAYER::save_to_file (FILE *file)
+{
+ if (! file)
+ return;
+
+ // start section with "*PLAYER*"
+ fprintf (file, "*PLAYER*\n");
+ fprintf (file, "NAME=%s\n", name); // Set first for easier debugging
+ fprintf (file, "COLOR=%d\n", color);
+ fprintf (file, "DEFENSIVE=%lf\n", defensive);
+ fprintf (file, "PAINSENSITIVITY=%lf\n", painSensitivity);
+ fprintf (file, "PLAYED=%u\n", played);
+ fprintf (file, "PREFTYPE=%d\n", preftype);
+ fprintf (file, "SELFPRESERVATION=%lf\n", selfPreservation);
+ fprintf (file, "TANK_BITMAP=%d\n", tankbitmap);
+ fprintf (file, "TEAM=%d\n", team);
+ fprintf (file, "TYPE=%d\n", type);
+ fprintf (file, "TYPESAVED=%d\n", type_saved);
+ fprintf (file, "VENGEANCETHRESHOLD=%lf\n", vengeanceThreshold);
+ fprintf (file, "VENGEFUL=%d\n", vengeful);
+ fprintf (file, "WON=%u\n", won);
+
+ // Preferences, needed for "ALWAYS_PREF" - players
+ if (ALWAYS_PREF == preftype) {
+ // Note: "PERPLAY_PREF" - players do not need this here, but in
+ // save_game_data(), as the preferences are different in each game.
+ for (int32_t i = 0; i < THINGS; ++i)
+ fprintf (file, "WEAPONPREFERENCES=%d %d\n", i, weapPref[i]);
+ }
+
+ fprintf (file, "***\n");
+}
+
+
+const char* PLAYER::selectGloatPhrase ()
+{
+ return env.gloat->Get_Random_Line();
+}
+
+
+/// @return a constructed panic phrase which must be freed!
+const char *PLAYER::selectPanicPhrase (PLAYER* shocker)
+{
+ if (! shocker)
+ return nullptr;
+
+ const char* line = env.panic->Get_Random_Line();
+ size_t tLen = strlen(shocker->getName()) + strlen(line);
+ char* pText = (char *)calloc(tLen + 1, sizeof (char));
+
+ if (!pText)
+ return nullptr;
+
+ snprintf(pText, tLen, line, shocker->getName());
+
+ return pText;
+}
+
+
+const char *PLAYER::selectKamikazePhrase ()
+{
+ return env.kamikaze->Get_Random_Line();
+}
+
+
+/// @return a constructed retaliation phrase which must be freed!
+const char *PLAYER::selectRetaliationPhrase ()
+{
+ if (! revenge)
+ return nullptr;
+
+ const char* line = env.retaliation->Get_Random_Line();
+ const char* rname = revenge->getName();
+ size_t tLen = strlen(rname) + 4 + strlen(line);
+ char* pText = (char *)calloc(tLen + 1, sizeof (char));
+
+ if (pText)
+ atanks_snprintf(pText, tLen, "%s%s !!!", line, rname);
+
+ return pText;
+}
+
+
+const char* PLAYER::selectRevengePhrase ()
+{
+ return env.revenge->Get_Random_Line();
+}
+
+
+const char *PLAYER::selectSuicidePhrase ()
+{
+ return env.suicide->Get_Random_Line();
+}
+
+
+/// @brief store @a last_opp to be remembered as the current/last target
+void PLAYER::setLastOpponent(sOpponent* last_opp)
+{
+ last_opponent = last_opp;
+}
+
+
+void PLAYER::setName (const char* name_)
+{
+ if (!name_ || strncmp(name, name_, NAME_LEN - 1)) {
+ memset(name, 0, NAME_LEN);
+ if (name_)
+ strncpy (name, name_, NAME_LEN - 1);
+ }
+}
+
+
+/// @brief fill in the list of desired items and update their preferences
+void PLAYER::updatePreferences(int32_t max_boost, int32_t max_score)
+{
+ // 1.: Fill cart and preference array.
+ // The preferences are copied, as they might get boosted this round
+ int32_t weapons_in_stock = generateDesiredList();
+ int32_t ai_level = static_cast<int32_t>(type);
+
+ // 2.: Amplify wish list by current boost and score situation
+ needAmp = false;
+ needArmour = false;
+ needDamage = false;
+
+ // Check whether boosting armour / amps is wanted:
+ if (getBoostValue() < (max_boost / ai_level)) {
+ // Yes. which ?
+ if (defensive < 0.) {
+ DEBUG_LOG_FIN(name, "updPref: Need to boost amps (%d / %d)",
+ getBoostValue(), max_boost / ai_level)
+ needAmp = true; // Try to come back with more damage output
+ } else {
+ DEBUG_LOG_FIN(name, "updPref: Need to boost armour (%d / %d)",
+ getBoostValue(), max_boost / ai_level)
+ needArmour = true; // Try to come back with more endurance
+ }
+ }
+
+ // Fallen behind? Need more weapons?
+ if ( (score <= (max_score / (ai_level + 1)))
+ && (weapons_in_stock < (2 * ai_level)) ) {
+ DEBUG_LOG_FIN(name, "updPref: Need to boost weapons (%d / %d)",
+ score, max_score / (ai_level + 1))
+ needDamage = true;
+ }
+
+
+ // 3.: Boost preferences if wanted and lower weapon/item
+ // preferences if there are enough in stock already.
+ // Further note down items to sell.
+ boostPrefences(needArmour, needAmp, needDamage);
+
+
+ // 4.: Sort these items by preferences
+ bool isSorted = false;
+ while (!isSorted) {
+ isSorted = true;
+
+ for (int32_t i = 1; i < THINGS; ++i) {
+ int32_t idx_l = desired[i - 1];
+ int32_t idx_r = desired[i];
+
+ if ( (currPref[idx_l] < currPref[idx_r])
+ // sort SML_MIS to the back, too
+ || ( (0 == idx_l) && idx_r) ) {
+ isSorted = false;
+ desired[i] = idx_l;
+ desired[i - 1] = idx_r;
+ }
+ }
+ }
+
+#ifdef ATANKS_DEBUG_FINANCE
+ // Get out the top twenty
+ for (int32_t i = 0; i < THINGS; ++i) {
+ DEBUG_LOG_FIN(name, "%2d. preference: %6d - %s",
+ i + 1, currPref[desired[i]],
+ desired[i] < WEAPONS
+ ? weapon[desired[i]].getName()
+ : item[desired[i] - WEAPONS].getName())
+ }
+#endif // ATANKS_DEBUG_FINANCE
+}
+
+
+/// @brief mini ctor to pacify Visual C++
+PLAYER_mini::PLAYER_mini()
+{
+ memset(name, 0, sizeof(char) * NAME_LEN);
+ strncpy(name, "New Player", NAME_LEN);
+}
+
+
+/// @brief backup a players editable data
+void PLAYER_mini::copy_from(PLAYER* source)
+{
+ if (source) {
+ assert( (source->index > -1) && "INDEX ERROR on PLAYER!");
+ color = source->color;
+ index = source->index;
+ strncpy(name, source->getName(), NAME_LEN);
+ played = source->played;
+ player = source;
+ preftype = source->preftype;
+ tankbitmap = source->tankbitmap;
+ team = source->team;
+ type = source->type;
+ won = source->won;
+ }
+}
+
+
+/// @brief copy backed up values back to the source player
+void PLAYER_mini::write_back(PLAYER* target)
+{
+ if (target)
+ player = target;
+ if (player) {
+ player->color = color;
+ player->setName(name);
+ // played is read only.
+ player->preftype = preftype;
+ player->tankbitmap = tankbitmap;
+ player->team = team;
+ player->type = type;
+ // won is read only.
+ }
}
-#endif
+/// @brief action function to display the edit player screen
+int32_t edit_player(PLAYER** target, int32_t)
+{
+ int32_t result = 0;
+
+ assert(target && "ERROR: target must be set");
+ assert(*target && "ERROR: target must point to something valid!");
+
+ if (!target || !(*target))
+ return -1;
+
+ int32_t menuMid = 300;
+ int32_t itemLeft = menuMid - 75;
+ int32_t itemHeight = env.fontHeight + 2;
+ int32_t itemPadding = 2;
+ int32_t itemFullHeight = itemHeight + itemPadding;
+ int32_t itemY = itemFullHeight * 3;
+ int32_t btnHeight = env.misc[7]->h + itemPadding;
+ int32_t menuHeight = env.menuEndY - env.menuBeginY; // Raw height
+
+
+ // Use "Mini-Player" struct to be able to cancel player editing
+ PLAYER_mini player_bak;
+ player_bak.copy_from(*target);
+
+ // The "Are you sure" screen when deleting a player
+ Menu areyousure(MC_AREYOUSURE,
+ env.halfWidth - menuMid, env.menuBeginY);
+ areyousure.addButton( 1, nullptr, PE_CONFIRM_DEL,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid + 50,
+ menuHeight- btnHeight - 6, 0, 0, itemPadding);
+ areyousure.addButton( 2, nullptr, PE_BACK,
+ env.misc[7], nullptr, env.misc[8], false,
+ menuMid - env.misc[7]->w - 50,
+ menuHeight- btnHeight - 6, 0, 0, itemPadding);
+
+ // The menu, but with the player name as title
+ Menu menu(MC_PLAYER, env.halfWidth - menuMid, env.menuBeginY);
+ menu.setTitle(player_bak.name, false);
+
+ // "Name"
+ menu.addText(player_bak.name, 1, NAME_LEN, player_bak.color, "%s",
+ itemLeft, itemY, 150, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Colour"
+ menu.addColor(&player_bak.color, 2, itemLeft, itemY, 150, 50, 25, itemPadding);
+ itemY += 50 + itemPadding;
+
+ // "Type"
+ menu.addValue(&player_bak.type, 3, nullptr, BLACK,
+ TC_PLAYERTYPE, static_cast<int32_t>(DEADLY_PLAYER),
+ itemLeft, itemY, 150, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Team"
+ menu.addValue(&player_bak.team, 4, nullptr, BLACK,
+ TC_PLAYERTEAM, static_cast<int32_t>(TEAM_JEDI),
+ itemLeft, itemY, 150, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Generate Pref"
+ menu.addValue(&player_bak.preftype, 5, nullptr, BLACK,
+ TC_PLAYERPREF, static_cast<int32_t>(ALWAYS_PREF),
+ itemLeft, itemY, 150, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Played"
+ menu.addText(&player_bak.played, 6, BLACK, "% 8u",
+ itemLeft, itemY, 150, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Won"
+ menu.addText(&player_bak.won, 7, BLACK, "% 8u",
+ itemLeft, itemY, 150, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Tank Type"
+ menu.addValue(&player_bak.tankbitmap, 8, nullptr, BLACK,
+ TC_TANKTYPE, static_cast<int32_t>(TT_MINI),
+ itemLeft, itemY, 150, 35, itemPadding,
+ display_tank_desc);
+ itemY += 35 + itemPadding;
+
+ // "Delete This Player"
+ menu.addMenu(&areyousure, 9, RED, itemLeft, itemY, 150, itemFullHeight, itemPadding);
+
+ // "Okay" and "Back"
+ menu.addButton(10, nullptr, PE_CONFIRM_EDIT, env.misc[7], nullptr,
+ env.misc[8], false,
+ menuMid + 50,
+ menuHeight- btnHeight - 6, 0, 0, itemPadding);
+ menu.addButton(11, nullptr, PE_BACK, env.misc[7], nullptr,
+ env.misc[8], false,
+ menuMid - env.misc[7]->w - 50,
+ menuHeight- btnHeight - 6, 0, 0, itemPadding);
+
+ result = menu();
+
+ // If the editing is confirmed, the backup must be written back
+ if (PE_CONFIRM_EDIT & result)
+ player_bak.write_back();
+
+ // If the player shall be deleted, the player index must be added
+ if (PE_CONFIRM_DEL & result)
+ result |= player_bak.index;
+
+ return result;
+}
+
+static PLAYER_mini player_new; //!< Used by new_player to keep previous settings
+/// @brief action function to display the edit player screen
+int32_t new_player(PLAYER** target, int32_t)
+{
+ int32_t result = 0;
+
+ assert(target && "ERROR: target must be set");
+ assert((nullptr == *target)
+ && "ERROR: *target must nullptr!");
+
+ if (!target || *target)
+ return -1;
+
+ int32_t menuMid = 300;
+ int32_t itemLeft = menuMid - 75;
+ int32_t itemHeight = env.fontHeight + 2;
+ int32_t itemPadding = 2;
+ int32_t itemFullHeight = itemHeight + itemPadding;
+ int32_t itemY = itemFullHeight * 3;
+ int32_t btnHeight = env.misc[7]->h + itemPadding;
+ int32_t menuHeight = env.menuEndY - env.menuBeginY; // Raw height
+
+ // The menu, with title from the menu class
+ Menu menu(MC_PLAYER, env.halfWidth - menuMid, env.menuBeginY);
+
+ // "Name"
+ menu.addText(player_new.name, 1, NAME_LEN, player_new.color, "%s",
+ itemLeft, itemY, 150, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Colour"
+ menu.addColor(&player_new.color, 2, itemLeft, itemY, 150, 50, 25, itemPadding);
+ itemY += 50 + itemPadding;
+
+ // "Type"
+ menu.addValue(&player_new.type, 3, nullptr, BLACK,
+ TC_PLAYERTYPE, static_cast<int32_t>(DEADLY_PLAYER),
+ itemLeft, itemY, 150, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Team"
+ menu.addValue(&player_new.team, 4, nullptr, BLACK,
+ TC_PLAYERTEAM, static_cast<int32_t>(TEAM_JEDI),
+ itemLeft, itemY, 150, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Generate Pref"
+ menu.addValue(&player_new.preftype, 5, nullptr, BLACK,
+ TC_PLAYERPREF, static_cast<int32_t>(ALWAYS_PREF),
+ itemLeft, itemY, 150, itemHeight, itemPadding);
+ itemY += itemFullHeight;
+
+ // "Played" and "Won" do not make sense here
+
+ // "Tank Type"
+ menu.addValue(&player_new.tankbitmap, 8, nullptr, BLACK,
+ TC_TANKTYPE, static_cast<int32_t>(TT_MINI),
+ itemLeft, itemY, 150, 35, itemPadding,
+ display_tank_desc);
+ itemY += 35 + itemPadding;
+
+ // "Delete This Player" is surely not needed
+
+ // "Okay" and "Back"
+ menu.addButton(10, nullptr, PE_CONFIRM_NEW, env.misc[7], nullptr,
+ env.misc[8], false,
+ menuMid + 50,
+ menuHeight- btnHeight - 6, 0, 0, itemPadding);
+ menu.addButton(11, nullptr, PE_BACK, env.misc[7], nullptr,
+ env.misc[8], false,
+ menuMid - env.misc[7]->w - 50,
+ menuHeight- btnHeight - 6, 0, 0, itemPadding);
+
+ while (!result) {
+ char existsMessage[200];
+ result = menu();
+
+ // If the player is to be created, two things must happen.
+ // First, ensure that the name is unique
+ // Second, create the real player
+ if (PE_CONFIRM_NEW & result) {
+ if (-1 == env.getPlayerByName(player_new.name)) {
+ *target = env.createNewPlayer(player_new.name);
+ if (*target)
+ player_new.write_back(*target);
+ } else {
+ snprintf(existsMessage, 199, "The player \"%s\" already exists!",
+ player_new.name);
+ errorMessage = existsMessage;
+ errorX = env.halfWidth - text_length(font, errorMessage) / 2;
+ errorY = env.menuBeginY + itemFullHeight;
+ result = 0;
+ }
+ }
+ } // End of !result
+ return result;
+}
diff --git a/src/player.h b/src/player.h
index cb82708..216fe67 100644
--- a/src/player.h
+++ b/src/player.h
@@ -18,190 +18,240 @@
* 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.
- * */
+ *
+ */
-#include "main.h"
-#include "menu.h"
+#include "player_types.h"
+#include "globaltypes.h"
+#define MAX_WEAP_PROBABILITY 10000
+#define BURIED_LEVEL 135
+#define BURIED_LEVEL_HALF 68
-enum playerType
-{
- HUMAN_PLAYER,
- USELESS_PLAYER,
- GUESSER_PLAYER,
- RANGEFINDER_PLAYER,
- TARGETTER_PLAYER,
- DEADLY_PLAYER,
- LAST_PLAYER_TYPE,
- PART_TIME_BOT, // normally a human, but acting as a deadly computer
- VERY_PART_TIME_BOT, // just fires one shot
- NETWORK_CLIENT
-};
-//player weapon preference type
-//ALWAYS_PREF - only choose weapon preferences once on player creation
-//PERPLAY_PREF - choose weapon preferences once per game
-enum playerPrefType
+#define NET_COMMAND_SIZE 64
+// if we do not get a command after this amount of seconds,
+// turn control over to the computer for a moment
+#define NET_DELAY 1000
+#define NET_DELAY_SHORT 500
+
+class TANK;
+class PLAYER;
+class AICore;
+
+/// @brief minimal struct to allow AI players to keep track of friend and foe.
+struct sOpponent
{
- PERPLAY_PREF,
- ALWAYS_PREF
+ int32_t damage_from = 0; //!< How much damage the opponent did to the player.
+ int32_t damage_last = 0; //!< How much damage the opponent did in this turn.
+ int32_t damage_to = 0; //!< How much damage the player did to the opponent.
+ double fear = 0.; //!< How likely evasive manoeuvres are started.
+ double fear_shock = 0.; //!< The highest shock value determines the shocker.
+ int32_t index = -1; //!< Needed for saving/loading to work.
+ int32_t killed_me = 0; //!< How many times this opponent has killed this player.
+ int32_t killed_them = 0; //!< How many times this opponent was killed by this player.
+ PLAYER* opponent = nullptr; //!< The PLAYER memorized here.
+ int32_t revenge_dmg = 0; //!< Summed up damage to determine when it is time for revenge.
};
-enum turnStages
+
+
+/** @class PLAYER
+ * @brief All data concerning human and A players
+**/
+class PLAYER
{
- SELECT_WEAPON,
- SELECT_TARGET,
- CALCULATE_ATTACK,
- AIM_WEAPON,
- FIRE_WEAPON
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ explicit PLAYER ();
+ ~PLAYER ();
+
+ // no copying, no assignments
+ PLAYER(const PLAYER&) =delete;
+ PLAYER &operator=(const PLAYER&) =delete;
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ void checkOppMem ();
+ int32_t chooseItemToBuy (int32_t max_boost, int32_t &last_idx);
+ eControl controlTank (AICore* aicore, bool allow_fire);
+ void drawIndicator (int32_t x, int32_t y, int32_t h);
+#ifdef NETWORK
+ eControl executeNetCmd (bool my_turn, AICore* aicore);
+#endif // NETWORK
+ void exitShop ();
+ void generatePreferences ();
+ int32_t getBoostValue ();
+ int32_t getItemPref (int32_t idx);
+ int32_t getMoneyToSave (bool first_look);
+ const char* getName () const;
+ bool getNetCmd ();
+ sOpponent* getOppMem (int32_t idx);
+ const char* getTeamName () const;
+ int32_t getWeapPref (int32_t idx);
+ void initialise (bool loaded_game);
+ bool load_from_file (FILE* file);
+ void load_game_data (FILE* file);
+ void newGame ();
+ void newRound ();
+ void noteDamageFrom (PLAYER* opponent, int32_t damage, bool destroyed);
+ void noteDamageTo (PLAYER* opponent, int32_t damage, bool destroyed);
+ void reclaimShield (); // restore unused shield
+ bool reduceClock ();
+ void save_game_data (FILE* file);
+ void save_to_file (FILE* file);
+ const char* selectGloatPhrase ();
+ const char* selectPanicPhrase (PLAYER* shocker);
+ const char* selectKamikazePhrase();
+ const char* selectRetaliationPhrase();
+ const char* selectRevengePhrase ();
+ const char* selectSuicidePhrase ();
+ void setLastOpponent (sOpponent* last_opp);
+ void setName (const char* name_);
+ void updatePreferences (int32_t max_boost, int32_t max_score);
+
+
+ /* ----------------------
+ * --- Public members ---
+ * ----------------------
+ */
+
+ int32_t color = BLACK;
+ double damageMultiplier = 1.;
+ double defensive = 0.; // [-1.0;1.0], offensive - defensive
+ double errorMultiplier = 0.;
+ bool changed_weapon = false;
+ double focusRate = 0.;
+ bool gloating = false;
+ int32_t index = -1; // To note where in allPlayers this player is saved
+ int32_t killed = 0;
+ int32_t kills = 0;
+ int32_t last_shield_used = 0;
+ double painSensitivity = .5; // How sensitive to damage
+ uint32_t played = 0;
+ playerPrefType preftype = PERPLAY_PREF;
+ playerType previous_type = HUMAN_PLAYER;
+ int32_t money = 15000;
+ int32_t ni[ITEMS];
+ int32_t nm[WEAPONS];
+ PLAYER* revenge = nullptr;
+ int32_t score = 0;
+ abool_t sdi_has_fired; // Only one shot per frame
+ int32_t sdiShots = 0;
+ bool selected = false;
+ double selfPreservation = .5; // Lengths gone to to avoid self-harm
+ bool skip_me = false;
+ TANK* tank = nullptr;
+ int32_t tankbitmap = TT_NORMAL;
+ eTeamTypes team = TEAM_NEUTRAL;
+ int32_t time_left_to_fire = 0;
+ playerType type = HUMAN_PLAYER;
+ playerType type_saved = HUMAN_PLAYER;
+ double vengeanceThreshold = .5; // Damage required to warrant revenge
+ int32_t vengeful = 50; // 0-100 chance of retaliation
+ uint32_t won = 0;
+#ifdef NETWORK
+ int32_t server_socket = 0;
+ char net_command[NET_COMMAND_SIZE] = { 0 };
+#endif // NETWORK
+
+
+private:
+
+ typedef ePlayerStages plStage_t;
+ typedef sOpponent opp_t;
+
+
+ /* -----------------------
+ * --- Private methods ---
+ * -----------------------
+ */
+
+ void boostPrefences (bool boostArmour, bool boostAmps,
+ bool boostWeapons);
+ bool buy_item (int32_t itemindex, int32_t max_boost);
+ eControl computerControls (AICore *aicore, bool allow_fire);
+ int32_t computerSelectPreBuyItem (int32_t max_boost);
+ int32_t generateDesiredList ();
+ int32_t getAmpValue ();
+ int32_t getArmourValue ();
+ eControl humanControls (AICore* aicore);
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ int32_t boostBought = -1;
+ int32_t currPref[THINGS]; // current preferences, calculated for each round
+ int32_t desired[THINGS]; // Shopping wish list
+ int32_t saveMoneyFor[THINGS]; // List of items the AI wants to buy
+ opp_t* last_opponent = nullptr;
+ char name[NAME_LEN + 1];
+ bool needAmp = false;
+ bool needArmour = false;
+ bool needDamage = false;
+ int32_t oppCount = 0;
+ opp_t* opponents = nullptr;
+ plStage_t plStage = PS_SELECT_WEAPON;
+ int32_t shieldBought = -1;
+ int32_t weapPref[THINGS]; // Static preferences, generated once
};
-#define TANK_TYPES 9
-#define NUM_ROULETTE_SLOTS (THINGS * 100)
-#define MAX_WEAP_PROBABILITY 10000
-#define BURIED_LEVEL 135
+// For headers including player.h to know that the class is there:
+#define HAS_PLAYER 1
+// Note: Due to circular dependencies, some headers might need to forward
+// the player class.
-#define NET_COMMAND_SIZE 64
-// if we do not get a command after this amount of seconds,
-// turn control over to the computer for a moment
-#define NET_DELAY 1000
-#define NET_DELAY_SHORT 500
+/** @struct PLAYER_mini
+ * @brief Minimum dataset of editable values.
+ *
+ * This minimal struct is used for the player editing menu.
+ * - When adding a new player it allows to cancel the addition without
+ * atanks to first call new and then delete. Further it keeps the last
+ * settings so adding many new players with the same settings but different
+ * names becomes very easy.
+ **/
+struct PLAYER_mini
+{
+ int32_t color = GREEN;
+ int32_t index = -1;
+ char name[NAME_LEN];
+ uint32_t played = 0;
+ PLAYER* player = nullptr;
+ playerPrefType preftype = ALWAYS_PREF;
+ int32_t tankbitmap = TT_NORMAL;
+ eTeamTypes team = TEAM_NEUTRAL;
+ playerType type = HUMAN_PLAYER;
+ uint32_t won = 0;
-// values the control functions return ot the main game loop
-#define CONTROL_PRESSED 2 // some key pressed, but not to shoot
-#define CONTROL_FIRE 1
-#define CONTROL_QUIT -1
-#define CONTROL_SKIP -2
+ // a ctor, needed by VisualC++ for the name.
+ explicit PLAYER_mini();
+ // "Backup a player"
+ void copy_from(PLAYER* source);
+
+ // Write back the values
+ void write_back(PLAYER* target = nullptr);
+};
+
+#define HAS_PLAYER_MINI 1
+
+// Helper functions to be used as action function with ET_BUTTON entries
+int32_t edit_player(PLAYER** target, int32_t);
+int32_t new_player (PLAYER** target, int32_t);
-class TANK;
-class PLAYER
- {
-
- char _name[NAME_LENGTH];
- int _currTank;
- GLOBALDATA *_global;
- ENVIRONMENT *_env;
- char _turnStage;
- TANK *_target;
- TANK *_oldTarget;
- int _targetAngle;
- int _targetPower;
- int _overshoot;
- int _weaponPreference[THINGS];
- int _rouletteWheel[NUM_ROULETTE_SLOTS];
- int _targetX;
- int _targetY;
- int iBoostItemsBought;
- int iTargettingRound;
- int computerSelectPreBuyItem (int aMaxBoostValue = 0);
- TANK * computerSelectTarget (int aPreferredWeapon, bool aRotationMode = false);
- char * selectKamikazePhrase();
- char * selectRetaliationPhrase();
- int getBlastValue (TANK * aTarget, int aDamage, int aWeapon, double aDamageMod = 1.0);
- int getUnburyingTool();
- int adjustOvershoot(int &aOvershoot, double aReachedX, double aReachedY);
- int getAdjustedTargetX(TANK * aTarget = NULL);
- int calculateAttack(TANK *aTarget = NULL);
- void calculateAttackValues(int &aDistanceX, int aDistanceY, int &aRawAngle, int &aAdjAngle, int &aPower, bool aAllowFlip);
- int traceShellTrajectory (double aTargetX, double aTargetY, double aVelocityX, double aVelocityY, double &aReachedX, double &aReachedY);
- int rangeFind (int &aAngle, int &aPower);
-
- public:
- double type, type_saved, previous_type;
- double preftype;
- char* preftypeText[ALWAYS_PREF + 1];
- char *tank_type[8];
- char *teamText[3];
-
- PLAYER *revenge;
- int vengeful; // 0-100 chance of retaliation
- double vengeanceThreshold; // Damage required to warrant revenge
- // (% of max armour)
- double defensive; // -1.0 - 1.0, offense - defense
- double annoyanceFactor;
- double selfPreservation; // Lengths gone to to avoid self-harm
- double painSensitivity; // How sensitive to damage
- int rangeFindAttempts;
- int retargetAttempts;
- double focusRate;
- double errorMultiplier;
- int score;
- double played, won;
- int kills, killed;
- double selected;
- int money;
- double damageMultiplier;
- TANK *tank;
- int color;
- void *pColor;
- int color2;
- int nm[WEAPONS], ni[ITEMS];
- MENUENTRY *menuopts;
- MENUDESC *menudesc;
- char *typeText[LAST_PLAYER_TYPE];
- bool changed_weapon;
- bool gloating;
- double tank_bitmap; // which type of tank do we have
- double team;
- int time_left_to_fire;
- bool skip_me;
- int last_shield_used;
- #ifdef NETWORK
- int server_socket;
- char net_command[NET_COMMAND_SIZE];
- #endif
-
- PLAYER (GLOBALDATA *global, ENVIRONMENT *env);
- // PLAYER (GLOBALDATA *global, ENVIRONMENT *env, ifstream &ifsFile, bool file);
- ~PLAYER ();
- void setName (char *name);
- char *getName ()
- {
- return (_name);
- };
- int calculateDirectAngle (int dx, int dy);
- void exitShop ();
- void newRound ();
- void initialise ();
- TANK *nextTank ();
- TANK *currTank ();
- int controlTank ();
- int humanControls ();
- int computerControls ();
- double calcDefenseValue (TANK *ctank, TANK *ltank);
- double selectTarget (); // select x to aim for
- double selectTarget (int *targetXCoord, int *targetYCoord); // select x to aim for
- double Select_Target (int *target_X, int *target_Y); // select x to aim for
- int computerSelectItem (); // Choose weapon to fire
- int chooseItemToBuy (int aMaxBoostValue = 0);
- void generatePreferences ();
- void setComputerValues (int aOffset = 0);
- int getMoneyToSave();
- int getBoostValue();
- int selectRandomItem ();
- char *selectRevengePhrase ();
- char *selectGloatPhrase ();
- char *selectSuicidePhrase ();
- int saveToFile_Text (FILE *file);
- int saveToFile (ofstream &ofsFile);
- int loadFromFile_Text (FILE *file);
- int loadFromFile (ifstream &ifsFile);
- void initMenuDesc ();
- char *Get_Team_Name();
- int Select_Random_Weapon();
- int Select_Random_Item();
- int Reduce_Time_Clock();
- int Buy_Something(int item_index); // purchases the selected item
- int Reclaim_Shield(); // restore unused shield
-
- #ifdef NETWORK
- void Trim_Newline(char *line); // sanitize input
- int Get_Network_Command();
- int Execute_Network_Command(int my_turn);
- #endif
- };
#endif
diff --git a/src/player_types.cpp b/src/player_types.cpp
new file mode 100644
index 0000000..7c45767
--- /dev/null
+++ b/src/player_types.cpp
@@ -0,0 +1,150 @@
+#include "player_types.h"
+
+playerType &operator+=(playerType &src, int32_t val)
+{
+ int32_t cur = static_cast<int32_t>(src) + val;
+ if (cur > 0)
+ cur %= LAST_PLAYER_TYPE;
+ if (cur < 0)
+ cur = LAST_PLAYER_TYPE - ((-1 * cur) % LAST_PLAYER_TYPE);
+ src = static_cast<playerType>(cur);
+ return src;
+}
+
+playerType &operator-=(playerType &src, int32_t val)
+{
+ return src += -1 * val;
+}
+
+playerType &operator++(playerType &src)
+{
+ return src += 1;
+}
+
+playerType operator++(playerType &src, int32_t)
+{
+ playerType old_val = src;
+ src += 1;
+ return old_val;
+}
+
+
+
+playerPrefType &operator+=(playerPrefType &src, int32_t val)
+{
+ int32_t cur = static_cast<int32_t>(src) + val;
+ if (cur > 0)
+ cur %= PREF_COUNT;
+ if (cur < 0)
+ cur = PREF_COUNT - ((-1 * cur) % PREF_COUNT);
+ src = static_cast<playerPrefType>(cur);
+ return src;
+}
+
+playerPrefType &operator-=(playerPrefType &src, int32_t val)
+{
+ return src += -1 * val;
+}
+
+playerPrefType &operator++(playerPrefType &src)
+{
+ return src += 1;
+}
+
+playerPrefType operator++(playerPrefType &src, int32_t)
+{
+ playerPrefType old_val = src;
+ src += 1;
+ return old_val;
+}
+
+
+
+ePlayerStages &operator+=(ePlayerStages &src, int32_t val)
+{
+ int32_t cur = static_cast<int32_t>(src) + val;
+ if (cur > 0)
+ cur %= PS_STAGE_COUNT;
+ if (cur < 0)
+ cur = PS_STAGE_COUNT - ((-1 * cur) % PS_STAGE_COUNT);
+ src = static_cast<ePlayerStages>(cur);
+ return src;
+}
+
+ePlayerStages &operator-=(ePlayerStages &src, int32_t val)
+{
+ return src += -1 * val;
+}
+
+ePlayerStages &operator++(ePlayerStages &src)
+{
+ return src += 1;
+}
+
+ePlayerStages operator++(ePlayerStages &src, int32_t)
+{
+ ePlayerStages old_val = src;
+ src += 1;
+ return old_val;
+}
+
+
+
+eTeamTypes &operator+=(eTeamTypes &src, int32_t val)
+{
+ int32_t cur = static_cast<int32_t>(src) + val;
+ if (cur > 0)
+ cur %= TEAM_COUNT;
+ if (cur < 0)
+ cur = TEAM_COUNT - ((-1 * cur) % TEAM_COUNT);
+ src = static_cast<eTeamTypes>(cur);
+ return src;
+}
+
+eTeamTypes &operator-=(eTeamTypes &src, int32_t val)
+{
+ return src += -1 * val;
+}
+
+eTeamTypes &operator++(eTeamTypes &src)
+{
+ return src += 1;
+}
+
+eTeamTypes operator++(eTeamTypes &src, int32_t)
+{
+ eTeamTypes old_val = src;
+ src += 1;
+ return old_val;
+}
+
+
+
+eTankTypes &operator+=(eTankTypes &src, int32_t val)
+{
+ int32_t cur = static_cast<int32_t>(src) + val;
+ if (cur > 0)
+ cur %= TT_TANK_COUNT;
+ if (cur < 0)
+ cur = TT_TANK_COUNT - ((-1 * cur) % TT_TANK_COUNT);
+ src = static_cast<eTankTypes>(cur);
+ return src;
+}
+
+eTankTypes &operator-=(eTankTypes &src, int32_t val)
+{
+ return src += -1 * val;
+}
+
+eTankTypes &operator++(eTankTypes &src)
+{
+ return src += 1;
+}
+
+eTankTypes operator++(eTankTypes &src, int32_t)
+{
+ eTankTypes old_val = src;
+ src += 1;
+ return old_val;
+}
+
diff --git a/src/player_types.h b/src/player_types.h
new file mode 100644
index 0000000..0f54f1e
--- /dev/null
+++ b/src/player_types.h
@@ -0,0 +1,130 @@
+#pragma once
+#ifndef ATANKS_SRC_PLAYER_TYPES_H_INCLUDED
+#define ATANKS_SRC_PLAYER_TYPES_H_INCLUDED
+
+/** @file player_types.h
+ * @brief used enums plus operators for players and tanks
+**/
+
+#include "main.h"
+
+enum ePlayerStages
+{
+ PS_AI_IS_IDLE = 0, //!< AI has nothing to do and is free to get work
+ PS_AI_INITIALIZE, //!< AI is initializing its data
+ PS_SELECT_TARGET, //!< AI is selecting a target
+ PS_SELECT_WEAPON, //!< AI is selecting a weapon or item
+ PS_CALCULATE, //!< AI calculates its basic attack values
+ PS_AIM, //!< AI aims the current selection to hit its target
+ PS_FIRE, //!< AI is ready to have the current weapon/item fired
+ PS_CLEANUP, //!< AI is cleaning up
+ PS_STAGE_COUNT
+};
+
+ePlayerStages &operator+=(ePlayerStages &src, int32_t val);
+ePlayerStages &operator-=(ePlayerStages &src, int32_t val);
+ePlayerStages &operator++(ePlayerStages &src); // pre
+ePlayerStages operator++(ePlayerStages &src, int32_t); // post
+
+enum playerType
+{
+ HUMAN_PLAYER = 0,
+ USELESS_PLAYER,
+ GUESSER_PLAYER,
+ RANGEFINDER_PLAYER,
+ TARGETTER_PLAYER,
+ DEADLY_PLAYER,
+ LAST_PLAYER_TYPE,
+ PART_TIME_BOT, // normally a human, but acting as a deadly computer
+ VERY_PART_TIME_BOT, // just fires one shot
+ NETWORK_CLIENT,
+ SDI_PREDICTOR // Used so missile mind shots from the SDI won't
+ // trigger another SDI check, trigger another SDI
+ // check, trigger another...
+};
+
+playerType &operator+=(playerType &src, int32_t val);
+playerType &operator-=(playerType &src, int32_t val);
+playerType &operator++(playerType &src); // pre
+playerType operator++(playerType &src, int32_t); // post
+
+// player weapon preference type
+// ALWAYS_PREF - only choose weapon preferences once on player creation
+// PERPLAY_PREF - choose weapon preferences once per game
+enum playerPrefType
+{
+ PERPLAY_PREF = 0,
+ ALWAYS_PREF,
+ PREF_COUNT
+};
+
+playerPrefType &operator+=(playerPrefType &src, int32_t val);
+playerPrefType &operator-=(playerPrefType &src, int32_t val);
+playerPrefType &operator++(playerPrefType &src); // pre
+playerPrefType operator++(playerPrefType &src, int32_t); // post
+
+
+/** @enum ePlayerEdit
+ * @brief return codes used by the sub menus when editing players
+**/
+enum ePlayerEdit
+{
+ PE_BACK = 1, //!< User opted out. No new player, no edit and no deletion.
+ PE_CONFIRM_NEW = 0x010000, //!< Adding a new player was confirmed
+ PE_CONFIRM_EDIT = 0x020000, //!< Changes to a player have been confirmed
+ PE_CONFIRM_DEL = 0x040000 //!< Deleting a player was confirmed
+ // Note: The values allow to use the last 16 bit for key code bit masks.
+};
+
+/** @enum eTeamTypes
+ * @brief determines the team a player belongs to
+**/
+enum eTeamTypes
+{
+ TEAM_SITH = 0,
+ TEAM_NEUTRAL,
+ TEAM_JEDI,
+ TEAM_COUNT
+};
+
+eTeamTypes &operator+=(eTeamTypes &src, int32_t val);
+eTeamTypes &operator-=(eTeamTypes &src, int32_t val);
+eTeamTypes &operator++(eTeamTypes &src); // pre
+eTeamTypes operator++(eTeamTypes &src, int32_t); // post
+
+/** @enum eTankOffsets
+ * @brief Centrally store the bitmap offsets of the tank images
+**/
+enum eTankOffsets
+{
+ TO_TURRET = 0,
+ TO_TANK = 7
+};
+
+
+/** @enum eTankTypes
+ * @brief the tanks currently known, TT_TANK_COUNT is the number of tanks
+**/
+enum eTankTypes
+{
+ TT_NORMAL = 0,
+ TT_CLASSIC,
+ TT_BIGGREY,
+ TT_T34,
+ TT_HEAVY,
+ TT_FUTURE,
+ TT_UFO,
+ TT_SPIDER,
+ TT_BIGFOOT,
+ TT_MINI,
+ TT_TANK_COUNT
+};
+
+eTankTypes &operator+=(eTankTypes &src, int32_t val);
+eTankTypes &operator-=(eTankTypes &src, int32_t val);
+eTankTypes &operator++(eTankTypes &src); // pre
+eTankTypes operator++(eTankTypes &src, int32_t); // post
+
+
+#endif // ATANKS_SRC_PLAYER_TYPES_H_INCLUDED
+
diff --git a/src/resource.h b/src/resource.h
new file mode 100755
index 0000000..fb1b95b
--- /dev/null
+++ b/src/resource.h
@@ -0,0 +1,1569 @@
+//{{NO_DEPENDENCIES}}
+// Von Microsoft Visual C++ generierte Includedatei.
+// Verwendet durch atanks.rc
+//
+#define SW_HIDE 0
+#define HIDE_WINDOW 0
+#define WM_NULL 0x0000
+#define WA_INACTIVE 0
+#define HTNOWHERE 0
+#define SMTO_NORMAL 0x0000
+#define ICON_SMALL 0
+#define SIZE_RESTORED 0
+#define BN_CLICKED 0
+#define BST_UNCHECKED 0x0000
+#define HDS_HORZ 0x0000
+#define TBSTYLE_BUTTON 0x0000
+#define TBS_HORZ 0x0000
+#define TBS_BOTTOM 0x0000
+#define TBS_RIGHT 0x0000
+#define LVS_ICON 0x0000
+#define LVS_ALIGNTOP 0x0000
+#define TCS_TABS 0x0000
+#define TCS_SINGLELINE 0x0000
+#define TCS_RIGHTJUSTIFY 0x0000
+#define DTS_SHORTDATEFORMAT 0x0000
+#define PGS_VERT 0x00000000
+#define LANG_NEUTRAL 0x00
+#define SUBLANG_NEUTRAL 0x00
+#define SORT_DEFAULT 0x0
+#define SORT_JAPANESE_XJIS 0x0
+#define SORT_CHINESE_BIG5 0x0
+#define SORT_CHINESE_PRCP 0x0
+#define SORT_KOREAN_KSC 0x0
+#define SORT_HUNGARIAN_DEFAULT 0x0
+#define SORT_GEORGIAN_TRADITIONAL 0x0
+#define _USE_DECLSPECS_FOR_SAL 0
+#define _USE_ATTRIBUTES_FOR_SAL 0
+#define __drv_typeConst 0
+#define WINAPI_PARTITION_APP 1
+#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
+#define MINIMUM_RESERVED_MANIFEST_RESOURCE_ID 1
+#define SW_SHOWNORMAL 1
+#define SW_NORMAL 1
+#define SHOW_OPENWINDOW 1
+#define SW_PARENTCLOSING 1
+#define VK_LBUTTON 0x01
+#define WM_CREATE 0x0001
+#define WA_ACTIVE 1
+#define PWR_OK 1
+#define PWR_SUSPENDREQUEST 1
+#define NFR_ANSI 1
+#define UIS_SET 1
+#define UISF_HIDEFOCUS 0x1
+#define XBUTTON1 0x0001
+#define WMSZ_LEFT 1
+#define HTCLIENT 1
+#define SMTO_BLOCK 0x0001
+#define MA_ACTIVATE 1
+#define ICON_BIG 1
+#define SIZE_MINIMIZED 1
+#define MK_LBUTTON 0x0001
+#define TME_HOVER 0x00000001
+#define CS_VREDRAW 0x0001
+#define CF_TEXT 1
+#define SCF_ISSECURE 0x00000001
+#define IDOK 1
+#define BN_PAINT 1
+#define BST_CHECKED 0x0001
+#define TBSTYLE_SEP 0x0001
+#define TTS_ALWAYSTIP 0x01
+#define TBS_AUTOTICKS 0x0001
+#define UDS_WRAP 0x0001
+#define PBS_SMOOTH 0x01
+#define LWS_TRANSPARENT 0x0001
+#define LVS_REPORT 0x0001
+#define TVS_HASBUTTONS 0x0001
+#define TVS_EX_NOSINGLECOLLAPSE 0x0001
+#define TCS_SCROLLOPPOSITE 0x0001
+#define ACS_CENTER 0x0001
+#define MCS_DAYSTATE 0x0001
+#define DTS_UPDOWN 0x0001
+#define PGS_HORZ 0x00000001
+#define NFS_EDIT 0x0001
+#define BCSIF_GLYPH 0x0001
+#define BCSS_NOSPLIT 0x0001
+#define LANG_ARABIC 0x01
+#define SUBLANG_DEFAULT 0x01
+#define SUBLANG_AFRIKAANS_SOUTH_AFRICA 0x01
+#define SUBLANG_ALBANIAN_ALBANIA 0x01
+#define SUBLANG_ALSATIAN_FRANCE 0x01
+#define SUBLANG_AMHARIC_ETHIOPIA 0x01
+#define SUBLANG_ARABIC_SAUDI_ARABIA 0x01
+#define SUBLANG_ARMENIAN_ARMENIA 0x01
+#define SUBLANG_ASSAMESE_INDIA 0x01
+#define SUBLANG_AZERI_LATIN 0x01
+#define SUBLANG_AZERBAIJANI_AZERBAIJAN_LATIN 0x01
+#define SUBLANG_BANGLA_INDIA 0x01
+#define SUBLANG_BASHKIR_RUSSIA 0x01
+#define SUBLANG_BASQUE_BASQUE 0x01
+#define SUBLANG_BELARUSIAN_BELARUS 0x01
+#define SUBLANG_BENGALI_INDIA 0x01
+#define SUBLANG_BRETON_FRANCE 0x01
+#define SUBLANG_BULGARIAN_BULGARIA 0x01
+#define SUBLANG_CATALAN_CATALAN 0x01
+#define SUBLANG_CENTRAL_KURDISH_IRAQ 0x01
+#define SUBLANG_CHEROKEE_CHEROKEE 0x01
+#define SUBLANG_CHINESE_TRADITIONAL 0x01
+#define SUBLANG_CORSICAN_FRANCE 0x01
+#define SUBLANG_CZECH_CZECH_REPUBLIC 0x01
+#define SUBLANG_CROATIAN_CROATIA 0x01
+#define SUBLANG_DANISH_DENMARK 0x01
+#define SUBLANG_DARI_AFGHANISTAN 0x01
+#define SUBLANG_DIVEHI_MALDIVES 0x01
+#define SUBLANG_DUTCH 0x01
+#define SUBLANG_ENGLISH_US 0x01
+#define SUBLANG_ESTONIAN_ESTONIA 0x01
+#define SUBLANG_FAEROESE_FAROE_ISLANDS 0x01
+#define SUBLANG_FILIPINO_PHILIPPINES 0x01
+#define SUBLANG_FINNISH_FINLAND 0x01
+#define SUBLANG_FRENCH 0x01
+#define SUBLANG_FRISIAN_NETHERLANDS 0x01
+#define SUBLANG_GALICIAN_GALICIAN 0x01
+#define SUBLANG_GEORGIAN_GEORGIA 0x01
+#define SUBLANG_GERMAN 0x01
+#define SUBLANG_GREEK_GREECE 0x01
+#define SUBLANG_GREENLANDIC_GREENLAND 0x01
+#define SUBLANG_GUJARATI_INDIA 0x01
+#define SUBLANG_HAUSA_NIGERIA_LATIN 0x01
+#define SUBLANG_HAWAIIAN_US 0x01
+#define SUBLANG_HEBREW_ISRAEL 0x01
+#define SUBLANG_HINDI_INDIA 0x01
+#define SUBLANG_HUNGARIAN_HUNGARY 0x01
+#define SUBLANG_ICELANDIC_ICELAND 0x01
+#define SUBLANG_IGBO_NIGERIA 0x01
+#define SUBLANG_INDONESIAN_INDONESIA 0x01
+#define SUBLANG_INUKTITUT_CANADA 0x01
+#define SUBLANG_ITALIAN 0x01
+#define SUBLANG_JAPANESE_JAPAN 0x01
+#define SUBLANG_KANNADA_INDIA 0x01
+#define SUBLANG_KAZAK_KAZAKHSTAN 0x01
+#define SUBLANG_KHMER_CAMBODIA 0x01
+#define SUBLANG_KICHE_GUATEMALA 0x01
+#define SUBLANG_KINYARWANDA_RWANDA 0x01
+#define SUBLANG_KONKANI_INDIA 0x01
+#define SUBLANG_KOREAN 0x01
+#define SUBLANG_KYRGYZ_KYRGYZSTAN 0x01
+#define SUBLANG_LAO_LAO 0x01
+#define SUBLANG_LATVIAN_LATVIA 0x01
+#define SUBLANG_LITHUANIAN 0x01
+#define SUBLANG_LUXEMBOURGISH_LUXEMBOURG 0x01
+#define SUBLANG_MACEDONIAN_MACEDONIA 0x01
+#define SUBLANG_MALAY_MALAYSIA 0x01
+#define SUBLANG_MALAYALAM_INDIA 0x01
+#define SUBLANG_MALTESE_MALTA 0x01
+#define SUBLANG_MAORI_NEW_ZEALAND 0x01
+#define SUBLANG_MAPUDUNGUN_CHILE 0x01
+#define SUBLANG_MARATHI_INDIA 0x01
+#define SUBLANG_MOHAWK_MOHAWK 0x01
+#define SUBLANG_MONGOLIAN_CYRILLIC_MONGOLIA 0x01
+#define SUBLANG_NEPALI_NEPAL 0x01
+#define SUBLANG_NORWEGIAN_BOKMAL 0x01
+#define SUBLANG_OCCITAN_FRANCE 0x01
+#define SUBLANG_ODIA_INDIA 0x01
+#define SUBLANG_ORIYA_INDIA 0x01
+#define SUBLANG_PASHTO_AFGHANISTAN 0x01
+#define SUBLANG_PERSIAN_IRAN 0x01
+#define SUBLANG_POLISH_POLAND 0x01
+#define SUBLANG_PORTUGUESE_BRAZILIAN 0x01
+#define SUBLANG_PUNJABI_INDIA 0x01
+#define SUBLANG_QUECHUA_BOLIVIA 0x01
+#define SUBLANG_ROMANIAN_ROMANIA 0x01
+#define SUBLANG_ROMANSH_SWITZERLAND 0x01
+#define SUBLANG_RUSSIAN_RUSSIA 0x01
+#define SUBLANG_SAKHA_RUSSIA 0x01
+#define SUBLANG_SAMI_NORTHERN_NORWAY 0x01
+#define SUBLANG_SANSKRIT_INDIA 0x01
+#define SUBLANG_SCOTTISH_GAELIC 0x01
+#define SUBLANG_SERBIAN_CROATIA 0x01
+#define SUBLANG_SINDHI_INDIA 0x01
+#define SUBLANG_SINHALESE_SRI_LANKA 0x01
+#define SUBLANG_SOTHO_NORTHERN_SOUTH_AFRICA 0x01
+#define SUBLANG_SLOVAK_SLOVAKIA 0x01
+#define SUBLANG_SLOVENIAN_SLOVENIA 0x01
+#define SUBLANG_SPANISH 0x01
+#define SUBLANG_SWAHILI_KENYA 0x01
+#define SUBLANG_SWEDISH 0x01
+#define SUBLANG_SYRIAC_SYRIA 0x01
+#define SUBLANG_TAJIK_TAJIKISTAN 0x01
+#define SUBLANG_TAMIL_INDIA 0x01
+#define SUBLANG_TATAR_RUSSIA 0x01
+#define SUBLANG_TELUGU_INDIA 0x01
+#define SUBLANG_THAI_THAILAND 0x01
+#define SUBLANG_TIBETAN_PRC 0x01
+#define SUBLANG_TIGRINYA_ETHIOPIA 0x01
+#define SUBLANG_TSWANA_SOUTH_AFRICA 0x01
+#define SUBLANG_TURKISH_TURKEY 0x01
+#define SUBLANG_TURKMEN_TURKMENISTAN 0x01
+#define SUBLANG_UIGHUR_PRC 0x01
+#define SUBLANG_UKRAINIAN_UKRAINE 0x01
+#define SUBLANG_UPPER_SORBIAN_GERMANY 0x01
+#define SUBLANG_URDU_PAKISTAN 0x01
+#define SUBLANG_UZBEK_LATIN 0x01
+#define SUBLANG_VIETNAMESE_VIETNAM 0x01
+#define SUBLANG_WELSH_UNITED_KINGDOM 0x01
+#define SUBLANG_WOLOF_SENEGAL 0x01
+#define SUBLANG_XHOSA_SOUTH_AFRICA 0x01
+#define SUBLANG_YAKUT_RUSSIA 0x01
+#define SUBLANG_YI_PRC 0x01
+#define SUBLANG_YORUBA_NIGERIA 0x01
+#define SUBLANG_ZULU_SOUTH_AFRICA 0x01
+#define SORT_INVARIANT_MATH 0x1
+#define SORT_JAPANESE_UNICODE 0x1
+#define SORT_CHINESE_UNICODE 0x1
+#define SORT_KOREAN_UNICODE 0x1
+#define SORT_GERMAN_PHONE_BOOK 0x1
+#define SORT_HUNGARIAN_TECHNICAL 0x1
+#define SORT_GEORGIAN_MODERN 0x1
+#define __drv_typeCond 1
+#define VS_VERSION_INFO 1
+#define VFFF_ISSHAREDFILE 0x0001
+#define VFF_CURNEDEST 0x0001
+#define VIFF_FORCEINSTALL 0x0001
+#define WINAPI_FAMILY_PC_APP 2
+#define ISOLATIONAWARE_MANIFEST_RESOURCE_ID 2
+#define SW_SHOWMINIMIZED 2
+#define SHOW_ICONWINDOW 2
+#define SW_OTHERZOOM 2
+#define VK_RBUTTON 0x02
+#define WM_DESTROY 0x0002
+#define WA_CLICKACTIVE 2
+#define PWR_SUSPENDRESUME 2
+#define NFR_UNICODE 2
+#define UIS_CLEAR 2
+#define UISF_HIDEACCEL 0x2
+#define XBUTTON2 0x0002
+#define WMSZ_RIGHT 2
+#define HTCAPTION 2
+#define SMTO_ABORTIFHUNG 0x0002
+#define MA_ACTIVATEANDEAT 2
+#define ICON_SMALL2 2
+#define SIZE_MAXIMIZED 2
+#define MK_RBUTTON 0x0002
+#define TME_LEAVE 0x00000002
+#define CS_HREDRAW 0x0002
+#define CF_BITMAP 2
+#define IDCANCEL 2
+#define BN_HILITE 2
+#define BST_INDETERMINATE 0x0002
+#define HDS_BUTTONS 0x0002
+#define TBSTYLE_CHECK 0x0002
+#define TTS_NOPREFIX 0x02
+#define TBS_VERT 0x0002
+#define UDS_SETBUDDYINT 0x0002
+#define LWS_IGNORERETURN 0x0002
+#define LVS_SMALLICON 0x0002
+#define TVS_HASLINES 0x0002
+#define TVS_EX_MULTISELECT 0x0002
+#define TCS_BOTTOM 0x0002
+#define TCS_RIGHT 0x0002
+#define ACS_TRANSPARENT 0x0002
+#define MCS_MULTISELECT 0x0002
+#define DTS_SHOWNONE 0x0002
+#define PGS_AUTOSCROLL 0x00000002
+#define NFS_STATIC 0x0002
+#define BCSIF_IMAGE 0x0002
+#define BCSS_STRETCH 0x0002
+#define LANG_BULGARIAN 0x02
+#define SUBLANG_SYS_DEFAULT 0x02
+#define SUBLANG_ARABIC_IRAQ 0x02
+#define SUBLANG_AZERI_CYRILLIC 0x02
+#define SUBLANG_AZERBAIJANI_AZERBAIJAN_CYRILLIC 0x02
+#define SUBLANG_BANGLA_BANGLADESH 0x02
+#define SUBLANG_BENGALI_BANGLADESH 0x02
+#define SUBLANG_CHINESE_SIMPLIFIED 0x02
+#define SUBLANG_DUTCH_BELGIAN 0x02
+#define SUBLANG_ENGLISH_UK 0x02
+#define SUBLANG_FRENCH_BELGIAN 0x02
+#define SUBLANG_FULAH_SENEGAL 0x02
+#define SUBLANG_GERMAN_SWISS 0x02
+#define SUBLANG_INUKTITUT_CANADA_LATIN 0x02
+#define SUBLANG_IRISH_IRELAND 0x02
+#define SUBLANG_ITALIAN_SWISS 0x02
+#define SUBLANG_KASHMIRI_SASIA 0x02
+#define SUBLANG_KASHMIRI_INDIA 0x02
+#define SUBLANG_LOWER_SORBIAN_GERMANY 0x02
+#define SUBLANG_MALAY_BRUNEI_DARUSSALAM 0x02
+#define SUBLANG_MONGOLIAN_PRC 0x02
+#define SUBLANG_NEPALI_INDIA 0x02
+#define SUBLANG_NORWEGIAN_NYNORSK 0x02
+#define SUBLANG_PORTUGUESE 0x02
+#define SUBLANG_PULAR_SENEGAL 0x02
+#define SUBLANG_PUNJABI_PAKISTAN 0x02
+#define SUBLANG_QUECHUA_ECUADOR 0x02
+#define SUBLANG_SAMI_NORTHERN_SWEDEN 0x02
+#define SUBLANG_SERBIAN_LATIN 0x02
+#define SUBLANG_SINDHI_PAKISTAN 0x02
+#define SUBLANG_SINDHI_AFGHANISTAN 0x02
+#define SUBLANG_SPANISH_MEXICAN 0x02
+#define SUBLANG_SWEDISH_FINLAND 0x02
+#define SUBLANG_TAMAZIGHT_ALGERIA_LATIN 0x02
+#define SUBLANG_TAMIL_SRI_LANKA 0x02
+#define SUBLANG_TIGRIGNA_ERITREA 0x02
+#define SUBLANG_TIGRINYA_ERITREA 0x02
+#define SUBLANG_TSWANA_BOTSWANA 0x02
+#define SUBLANG_URDU_INDIA 0x02
+#define SUBLANG_UZBEK_CYRILLIC 0x02
+#define SUBLANG_VALENCIAN_VALENCIA 0x02
+#define SORT_CHINESE_PRC 0x2
+#define __drv_typeBitset 2
+#define VFF_FILEINUSE 0x0002
+#define VIFF_DONTDELETEOLD 0x0002
+#define WINAPI_FAMILY_PHONE_APP 3
+#define ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID 3
+#define SW_SHOWMAXIMIZED 3
+#define SW_MAXIMIZE 3
+#define SHOW_FULLSCREEN 3
+#define SW_PARENTOPENING 3
+#define VK_CANCEL 0x03
+#define WM_MOVE 0x0003
+#define PWR_CRITICALRESUME 3
+#define NF_QUERY 3
+#define UIS_INITIALIZE 3
+#define WMSZ_TOP 3
+#define HTSYSMENU 3
+#define MA_NOACTIVATE 3
+#define SIZE_MAXSHOW 3
+#define CF_METAFILEPICT 3
+#define IDABORT 3
+#define BN_UNHILITE 3
+#define LVS_LIST 0x0003
+#define LVS_TYPEMASK 0x0003
+#define LANG_CATALAN 0x03
+#define LANG_VALENCIAN 0x03
+#define SUBLANG_CUSTOM_DEFAULT 0x03
+#define SUBLANG_ARABIC_EGYPT 0x03
+#define SUBLANG_CHINESE_HONGKONG 0x03
+#define SUBLANG_ENGLISH_AUS 0x03
+#define SUBLANG_FRENCH_CANADIAN 0x03
+#define SUBLANG_GERMAN_AUSTRIAN 0x03
+#define SUBLANG_QUECHUA_PERU 0x03
+#define SUBLANG_SAMI_NORTHERN_FINLAND 0x03
+#define SUBLANG_SERBIAN_CYRILLIC 0x03
+#define SUBLANG_SPANISH_MODERN 0x03
+#define SORT_CHINESE_BOPOMOFO 0x3
+#define __drv_typeExpr 3
+#define SW_SHOWNOACTIVATE 4
+#define SHOW_OPENNOACTIVATE 4
+#define SW_OTHERUNZOOM 4
+#define VK_MBUTTON 0x04
+#define NF_REQUERY 4
+#define UISF_ACTIVE 0x4
+#define WMSZ_TOPLEFT 4
+#define HTGROWBOX 4
+#define MA_NOACTIVATEANDEAT 4
+#define SIZE_MAXHIDE 4
+#define MK_SHIFT 0x0004
+#define CF_SYLK 4
+#define IDRETRY 4
+#define BN_DISABLE 4
+#define BST_PUSHED 0x0004
+#define HDS_HOTTRACK 0x0004
+#define TBSTYLE_GROUP 0x0004
+#define TBS_TOP 0x0004
+#define TBS_LEFT 0x0004
+#define UDS_ALIGNRIGHT 0x0004
+#define PBS_VERTICAL 0x04
+#define LWS_NOPREFIX 0x0004
+#define LVS_SINGLESEL 0x0004
+#define TVS_LINESATROOT 0x0004
+#define TVS_EX_DOUBLEBUFFER 0x0004
+#define TCS_MULTISELECT 0x0004
+#define ACS_AUTOPLAY 0x0004
+#define MCS_WEEKNUMBERS 0x0004
+#define DTS_LONGDATEFORMAT 0x0004
+#define PGS_DRAGNDROP 0x00000004
+#define NFS_LISTCOMBO 0x0004
+#define BCSIF_STYLE 0x0004
+#define BCSS_ALIGNLEFT 0x0004
+#define LANG_CHINESE 0x04
+#define LANG_CHINESE_SIMPLIFIED 0x04
+#define SUBLANG_CUSTOM_UNSPECIFIED 0x04
+#define SUBLANG_ARABIC_LIBYA 0x04
+#define SUBLANG_CHINESE_SINGAPORE 0x04
+#define SUBLANG_CROATIAN_BOSNIA_HERZEGOVINA_LATIN 0x04
+#define SUBLANG_ENGLISH_CAN 0x04
+#define SUBLANG_FRENCH_SWISS 0x04
+#define SUBLANG_GERMAN_LUXEMBOURG 0x04
+#define SUBLANG_SAMI_LULE_NORWAY 0x04
+#define SUBLANG_SPANISH_GUATEMALA 0x04
+#define SUBLANG_TAMAZIGHT_MOROCCO_TIFINAGH 0x04
+#define SORT_JAPANESE_RADICALSTROKE 0x4
+#define SORT_CHINESE_RADICALSTROKE 0x4
+#define VFF_BUFFTOOSMALL 0x0004
+#define SW_SHOW 5
+#define VK_XBUTTON1 0x05
+#define WM_SIZE 0x0005
+#define WMSZ_TOPRIGHT 5
+#define HTMENU 5
+#define CF_DIF 5
+#define IDIGNORE 5
+#define BN_DOUBLECLICKED 5
+#define LANG_CZECH 0x05
+#define SUBLANG_UI_CUSTOM_DEFAULT 0x05
+#define SUBLANG_ARABIC_ALGERIA 0x05
+#define SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_LATIN 0x05
+#define SUBLANG_CHINESE_MACAU 0x05
+#define SUBLANG_ENGLISH_NZ 0x05
+#define SUBLANG_FRENCH_LUXEMBOURG 0x05
+#define SUBLANG_GERMAN_LIECHTENSTEIN 0x05
+#define SUBLANG_SAMI_LULE_SWEDEN 0x05
+#define SUBLANG_SPANISH_COSTA_RICA 0x05
+#define SW_MINIMIZE 6
+#define VK_XBUTTON2 0x06
+#define WM_ACTIVATE 0x0006
+#define WMSZ_BOTTOM 6
+#define HTHSCROLL 6
+#define CF_TIFF 6
+#define IDYES 6
+#define BN_SETFOCUS 6
+#define LANG_DANISH 0x06
+#define SUBLANG_ARABIC_MOROCCO 0x06
+#define SUBLANG_ENGLISH_EIRE 0x06
+#define SUBLANG_FRENCH_MONACO 0x06
+#define SUBLANG_SAMI_SOUTHERN_NORWAY 0x06
+#define SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_LATIN 0x06
+#define SUBLANG_SPANISH_PANAMA 0x06
+#define SW_SHOWMINNOACTIVE 7
+#define WM_SETFOCUS 0x0007
+#define WMSZ_BOTTOMLEFT 7
+#define HTVSCROLL 7
+#define CF_OEMTEXT 7
+#define IDNO 7
+#define BN_KILLFOCUS 7
+#define LANG_GERMAN 0x07
+#define SUBLANG_ARABIC_TUNISIA 0x07
+#define SUBLANG_ENGLISH_SOUTH_AFRICA 0x07
+#define SUBLANG_SAMI_SOUTHERN_SWEDEN 0x07
+#define SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_CYRILLIC 0x07
+#define SUBLANG_SPANISH_DOMINICAN_REPUBLIC 0x07
+#define SW_SHOWNA 8
+#define VK_BACK 0x08
+#define WM_KILLFOCUS 0x0008
+#define WMSZ_BOTTOMRIGHT 8
+#define HTMINBUTTON 8
+#define SMTO_NOTIMEOUTIFNOTHUNG 0x0008
+#define MK_CONTROL 0x0008
+#define CS_DBLCLKS 0x0008
+#define CF_DIB 8
+#define IDCLOSE 8
+#define BST_FOCUS 0x0008
+#define HDS_HIDDEN 0x0008
+#define TBSTYLE_DROPDOWN 0x0008
+#define TBS_BOTH 0x0008
+#define UDS_ALIGNLEFT 0x0008
+#define PBS_MARQUEE 0x08
+#define LWS_USEVISUALSTYLE 0x0008
+#define LVS_SHOWSELALWAYS 0x0008
+#define TVS_EDITLABELS 0x0008
+#define TVS_EX_NOINDENTSTATE 0x0008
+#define TCS_FLATBUTTONS 0x0008
+#define ACS_TIMER 0x0008
+#define MCS_NOTODAYCIRCLE 0x0008
+#define NFS_BUTTON 0x0008
+#define BCSIF_SIZE 0x0008
+#define BCSS_IMAGE 0x0008
+#define LANG_GREEK 0x08
+#define SUBLANG_ARABIC_OMAN 0x08
+#define SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_CYRILLIC 0x08
+#define SUBLANG_ENGLISH_JAMAICA 0x08
+#define SUBLANG_SAMI_SKOLT_FINLAND 0x08
+#define SUBLANG_SPANISH_VENEZUELA 0x08
+#define SW_RESTORE 9
+#define VK_TAB 0x09
+#define HTMAXBUTTON 9
+#define CF_PALETTE 9
+#define IDHELP 9
+#define DTS_TIMEFORMAT 0x0009
+#define LANG_ENGLISH 0x09
+#define SUBLANG_ARABIC_YEMEN 0x09
+#define SUBLANG_ENGLISH_CARIBBEAN 0x09
+#define SUBLANG_SAMI_INARI_FINLAND 0x09
+#define SUBLANG_SERBIAN_SERBIA_LATIN 0x09
+#define SUBLANG_SPANISH_COLOMBIA 0x09
+#define SW_SHOWDEFAULT 10
+#define WM_ENABLE 0x000A
+#define HTLEFT 10
+#define CF_PENDATA 10
+#define IDTRYAGAIN 10
+#define HELP_CONTEXTMENU 0x000a
+#define LANG_SPANISH 0x0a
+#define SUBLANG_ARABIC_SYRIA 0x0a
+#define SUBLANG_ENGLISH_BELIZE 0x0a
+#define SUBLANG_SERBIAN_SERBIA_CYRILLIC 0x0a
+#define SUBLANG_SPANISH_PERU 0x0a
+#define SW_FORCEMINIMIZE 11
+#define SW_MAX 11
+#define WM_SETREDRAW 0x000B
+#define HTRIGHT 11
+#define CF_RIFF 11
+#define IDCONTINUE 11
+#define HELP_FINDER 0x000b
+#define LANG_FINNISH 0x0b
+#define SUBLANG_ARABIC_JORDAN 0x0b
+#define SUBLANG_ENGLISH_TRINIDAD 0x0b
+#define SUBLANG_SERBIAN_MONTENEGRO_LATIN 0x0b
+#define SUBLANG_SPANISH_ARGENTINA 0x0b
+#define VK_CLEAR 0x0C
+#define WM_SETTEXT 0x000C
+#define HTTOP 12
+#define CF_WAVE 12
+#define HELP_WM_HELP 0x000c
+#define DTS_SHORTDATECENTURYFORMAT 0x000C
+#define LANG_FRENCH 0x0c
+#define SUBLANG_ARABIC_LEBANON 0x0c
+#define SUBLANG_ENGLISH_ZIMBABWE 0x0c
+#define SUBLANG_SERBIAN_MONTENEGRO_CYRILLIC 0x0c
+#define SUBLANG_SPANISH_ECUADOR 0x0c
+#define VK_RETURN 0x0D
+#define WM_GETTEXT 0x000D
+#define HTTOPLEFT 13
+#define CF_UNICODETEXT 13
+#define HELP_SETPOPUP_POS 0x000d
+#define LANG_HEBREW 0x0d
+#define SUBLANG_ARABIC_KUWAIT 0x0d
+#define SUBLANG_ENGLISH_PHILIPPINES 0x0d
+#define SUBLANG_SPANISH_CHILE 0x0d
+#define WM_GETTEXTLENGTH 0x000E
+#define HTTOPRIGHT 14
+#define CF_ENHMETAFILE 14
+#define LANG_HUNGARIAN 0x0e
+#define SUBLANG_ARABIC_UAE 0x0e
+#define SUBLANG_SPANISH_URUGUAY 0x0e
+#define WM_PAINT 0x000F
+#define HTBOTTOM 15
+#define CF_HDROP 15
+#define LANG_ICELANDIC 0x0f
+#define SUBLANG_ARABIC_BAHRAIN 0x0f
+#define SUBLANG_SPANISH_PARAGUAY 0x0f
+#define MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID 16
+#define VK_SHIFT 0x10
+#define WM_CLOSE 0x0010
+#define HTBOTTOMLEFT 16
+#define WVR_ALIGNTOP 0x0010
+#define MK_MBUTTON 0x0010
+#define TME_NONCLIENT 0x00000010
+#define CF_LOCALE 16
+#define HELP_TCARD_DATA 0x0010
+#define TBSTYLE_AUTOSIZE 0x0010
+#define TTS_NOANIMATE 0x10
+#define TBS_NOTICKS 0x0010
+#define UDS_AUTOBUDDY 0x0010
+#define PBS_SMOOTHREVERSE 0x10
+#define LWS_USECUSTOMTEXT 0x0010
+#define LVS_SORTASCENDING 0x0010
+#define TVS_DISABLEDRAGDROP 0x0010
+#define TVS_EX_RICHTOOLTIP 0x0010
+#define TCS_FORCEICONLEFT 0x0010
+#define MCS_NOTODAY 0x0010
+#define DTS_APPCANPARSE 0x0010
+#define NFS_ALL 0x0010
+#define LANG_ITALIAN 0x10
+#define SUBLANG_ARABIC_QATAR 0x10
+#define SUBLANG_ENGLISH_INDIA 0x10
+#define SUBLANG_SPANISH_BOLIVIA 0x10
+#define VK_CONTROL 0x11
+#define WM_QUERYENDSESSION 0x0011
+#define HTBOTTOMRIGHT 17
+#define CF_DIBV5 17
+#define HELP_TCARD_OTHER_CALLER 0x0011
+#define LANG_JAPANESE 0x11
+#define SUBLANG_ENGLISH_MALAYSIA 0x11
+#define SUBLANG_SPANISH_EL_SALVADOR 0x11
+#define VK_MENU 0x12
+#define WM_QUIT 0x0012
+#define HTBORDER 18
+#define CF_MAX 18
+#define LANG_KOREAN 0x12
+#define SUBLANG_ENGLISH_SINGAPORE 0x12
+#define SUBLANG_SPANISH_HONDURAS 0x12
+#define VK_PAUSE 0x13
+#define WM_QUERYOPEN 0x0013
+#define HTOBJECT 19
+#define LANG_DUTCH 0x13
+#define SUBLANG_SPANISH_NICARAGUA 0x13
+#define VK_CAPITAL 0x14
+#define WM_ERASEBKGND 0x0014
+#define HTCLOSE 20
+#define LANG_NORWEGIAN 0x14
+#define SUBLANG_SPANISH_PUERTO_RICO 0x14
+#define _SAL_VERSION 20
+#define VK_KANA 0x15
+#define VK_HANGEUL 0x15
+#define VK_HANGUL 0x15
+#define WM_SYSCOLORCHANGE 0x0015
+#define HTHELP 21
+#define LANG_POLISH 0x15
+#define SUBLANG_SPANISH_US 0x15
+#define WM_ENDSESSION 0x0016
+#define LANG_PORTUGUESE 0x16
+#define VK_JUNJA 0x17
+#define LANG_ROMANSH 0x17
+#define RT_MANIFEST 24
+#define VK_FINAL 0x18
+#define WM_SHOWWINDOW 0x0018
+#define LANG_ROMANIAN 0x18
+#define VK_HANJA 0x19
+#define VK_KANJI 0x19
+#define LANG_RUSSIAN 0x19
+#define WM_WININICHANGE 0x001A
+#define LANG_BOSNIAN 0x1a
+#define LANG_CROATIAN 0x1a
+#define LANG_SERBIAN 0x1a
+#define VK_ESCAPE 0x1B
+#define WM_DEVMODECHANGE 0x001B
+#define LANG_SLOVAK 0x1b
+#define VK_CONVERT 0x1C
+#define WM_ACTIVATEAPP 0x001C
+#define LANG_ALBANIAN 0x1c
+#define VK_NONCONVERT 0x1D
+#define WM_FONTCHANGE 0x001D
+#define LANG_SWEDISH 0x1d
+#define VK_ACCEPT 0x1E
+#define WM_TIMECHANGE 0x001E
+#define LANG_THAI 0x1e
+#define VK_MODECHANGE 0x1F
+#define WM_CANCELMODE 0x001F
+#define LANG_TURKISH 0x1f
+#define VK_SPACE 0x20
+#define WM_SETCURSOR 0x0020
+#define SMTO_ERRORONEXIT 0x0020
+#define WVR_ALIGNLEFT 0x0020
+#define MK_XBUTTON1 0x0020
+#define CS_OWNDC 0x0020
+#define TBSTYLE_NOPREFIX 0x0020
+#define TTS_NOFADE 0x20
+#define TBS_ENABLESELRANGE 0x0020
+#define UDS_ARROWKEYS 0x0020
+#define LWS_RIGHT 0x0020
+#define LVS_SORTDESCENDING 0x0020
+#define TVS_SHOWSELALWAYS 0x0020
+#define TVS_EX_AUTOHSCROLL 0x0020
+#define TCS_FORCELABELLEFT 0x0020
+#define DTS_RIGHTALIGN 0x0020
+#define NFS_USEFONTASSOC 0x0020
+#define LANG_URDU 0x20
+#define VK_PRIOR 0x21
+#define WM_MOUSEACTIVATE 0x0021
+#define LANG_INDONESIAN 0x21
+#define VK_NEXT 0x22
+#define WM_CHILDACTIVATE 0x0022
+#define LANG_UKRAINIAN 0x22
+#define VK_END 0x23
+#define WM_QUEUESYNC 0x0023
+#define LANG_BELARUSIAN 0x23
+#define VK_HOME 0x24
+#define WM_GETMINMAXINFO 0x0024
+#define LANG_SLOVENIAN 0x24
+#define VK_LEFT 0x25
+#define LANG_ESTONIAN 0x25
+#define VK_UP 0x26
+#define WM_PAINTICON 0x0026
+#define LANG_LATVIAN 0x26
+#define VK_RIGHT 0x27
+#define WM_ICONERASEBKGND 0x0027
+#define LANG_LITHUANIAN 0x27
+#define VK_DOWN 0x28
+#define WM_NEXTDLGCTL 0x0028
+#define LANG_TAJIK 0x28
+#define VK_SELECT 0x29
+#define LANG_FARSI 0x29
+#define LANG_PERSIAN 0x29
+#define VK_PRINT 0x2A
+#define WM_SPOOLERSTATUS 0x002A
+#define LANG_VIETNAMESE 0x2a
+#define VK_EXECUTE 0x2B
+#define WM_DRAWITEM 0x002B
+#define LANG_ARMENIAN 0x2b
+#define VK_SNAPSHOT 0x2C
+#define WM_MEASUREITEM 0x002C
+#define LANG_AZERI 0x2c
+#define LANG_AZERBAIJANI 0x2c
+#define VK_INSERT 0x2D
+#define WM_DELETEITEM 0x002D
+#define LANG_BASQUE 0x2d
+#define VK_DELETE 0x2E
+#define WM_VKEYTOITEM 0x002E
+#define LANG_LOWER_SORBIAN 0x2e
+#define LANG_UPPER_SORBIAN 0x2e
+#define VK_HELP 0x2F
+#define WM_CHARTOITEM 0x002F
+#define LANG_MACEDONIAN 0x2f
+#define WM_SETFONT 0x0030
+#define WM_GETFONT 0x0031
+#define WM_SETHOTKEY 0x0032
+#define LANG_TSWANA 0x32
+#define WM_GETHOTKEY 0x0033
+#define LANG_XHOSA 0x34
+#define LANG_ZULU 0x35
+#define LANG_AFRIKAANS 0x36
+#define WM_QUERYDRAGICON 0x0037
+#define LANG_GEORGIAN 0x37
+#define LANG_FAEROESE 0x38
+#define WM_COMPAREITEM 0x0039
+#define LANG_HINDI 0x39
+#define LANG_MALTESE 0x3a
+#define LANG_SAMI 0x3b
+#define LANG_IRISH 0x3c
+#define WM_GETOBJECT 0x003D
+#define LANG_MALAY 0x3e
+#define LANG_KAZAK 0x3f
+#define WVR_ALIGNBOTTOM 0x0040
+#define MK_XBUTTON2 0x0040
+#define CS_CLASSDC 0x0040
+#define HDS_DRAGDROP 0x0040
+#define BTNS_SHOWTEXT 0x0040
+#define TTS_BALLOON 0x40
+#define TBS_FIXEDLENGTH 0x0040
+#define UDS_HORZ 0x0040
+#define LVS_SHAREIMAGELISTS 0x0040
+#define TVS_RTLREADING 0x0040
+#define TVS_EX_FADEINOUTEXPANDOS 0x0040
+#define TCS_HOTTRACK 0x0040
+#define MCS_NOTRAILINGDATES 0x0040
+#define LANG_KYRGYZ 0x40
+#define WM_COMPACTING 0x0041
+#define LANG_SWAHILI 0x41
+#define LANG_TURKMEN 0x42
+#define LANG_UZBEK 0x43
+#define WM_COMMNOTIFY 0x0044
+#define LANG_TATAR 0x44
+#define LANG_BANGLA 0x45
+#define LANG_BENGALI 0x45
+#define WM_WINDOWPOSCHANGING 0x0046
+#define LANG_PUNJABI 0x46
+#define WM_WINDOWPOSCHANGED 0x0047
+#define LANG_GUJARATI 0x47
+#define WM_POWER 0x0048
+#define LANG_ODIA 0x48
+#define LANG_ORIYA 0x48
+#define LANG_TAMIL 0x49
+#define WM_COPYDATA 0x004A
+#define LANG_TELUGU 0x4a
+#define WM_CANCELJOURNAL 0x004B
+#define LANG_KANNADA 0x4b
+#define LANG_MALAYALAM 0x4c
+#define LANG_ASSAMESE 0x4d
+#define WM_NOTIFY 0x004E
+#define LANG_MARATHI 0x4e
+#define LANG_SANSKRIT 0x4f
+#define WM_INPUTLANGCHANGEREQUEST 0x0050
+#define LANG_MONGOLIAN 0x50
+#define WM_INPUTLANGCHANGE 0x0051
+#define LANG_TIBETAN 0x51
+#define WM_TCARD 0x0052
+#define LANG_WELSH 0x52
+#define WM_HELP 0x0053
+#define LANG_KHMER 0x53
+#define WM_USERCHANGED 0x0054
+#define LANG_LAO 0x54
+#define WM_NOTIFYFORMAT 0x0055
+#define LANG_GALICIAN 0x56
+#define LANG_KONKANI 0x57
+#define LANG_MANIPURI 0x58
+#define LANG_SINDHI 0x59
+#define LANG_SYRIAC 0x5a
+#define VK_LWIN 0x5B
+#define LANG_SINHALESE 0x5b
+#define VK_RWIN 0x5C
+#define LANG_CHEROKEE 0x5c
+#define VK_APPS 0x5D
+#define LANG_INUKTITUT 0x5d
+#define LANG_AMHARIC 0x5e
+#define VK_SLEEP 0x5F
+#define LANG_TAMAZIGHT 0x5f
+#define VK_NUMPAD0 0x60
+#define LANG_KASHMIRI 0x60
+#define VK_NUMPAD1 0x61
+#define LANG_NEPALI 0x61
+#define VK_NUMPAD2 0x62
+#define LANG_FRISIAN 0x62
+#define VK_NUMPAD3 0x63
+#define LANG_PASHTO 0x63
+#define WINAPI_FAMILY_DESKTOP_APP 100
+#define VK_NUMPAD4 0x64
+#define LANG_FILIPINO 0x64
+#define VS_USER_DEFINED 100
+#define VK_NUMPAD5 0x65
+#define LANG_DIVEHI 0x65
+#define VK_NUMPAD6 0x66
+#define VK_NUMPAD7 0x67
+#define LANG_FULAH 0x67
+#define LANG_PULAR 0x67
+#define VK_NUMPAD8 0x68
+#define LANG_HAUSA 0x68
+#define VK_NUMPAD9 0x69
+#define VK_MULTIPLY 0x6A
+#define LANG_YORUBA 0x6a
+#define VK_ADD 0x6B
+#define LANG_QUECHUA 0x6b
+#define VK_SEPARATOR 0x6C
+#define LANG_SOTHO 0x6c
+#define VK_SUBTRACT 0x6D
+#define LANG_BASHKIR 0x6d
+#define VK_DECIMAL 0x6E
+#define LANG_LUXEMBOURGISH 0x6e
+#define VK_DIVIDE 0x6F
+#define LANG_GREENLANDIC 0x6f
+#define VK_F1 0x70
+#define LANG_IGBO 0x70
+#define VK_F2 0x71
+#define VK_F3 0x72
+#define VK_F4 0x73
+#define LANG_TIGRIGNA 0x73
+#define LANG_TIGRINYA 0x73
+#define VK_F5 0x74
+#define VK_F6 0x75
+#define LANG_HAWAIIAN 0x75
+#define VK_F7 0x76
+#define VK_F8 0x77
+#define VK_F9 0x78
+#define WHEEL_DELTA 120
+#define LANG_YI 0x78
+#define VK_F10 0x79
+#define VK_F11 0x7A
+#define LANG_MAPUDUNGUN 0x7a
+#define VK_F12 0x7B
+#define WM_CONTEXTMENU 0x007B
+#define VK_F13 0x7C
+#define WM_STYLECHANGING 0x007C
+#define LANG_MOHAWK 0x7c
+#define VK_F14 0x7D
+#define WM_STYLECHANGED 0x007D
+#define VK_F15 0x7E
+#define WM_DISPLAYCHANGE 0x007E
+#define LANG_BRETON 0x7e
+#define VK_F16 0x7F
+#define WM_GETICON 0x007F
+#define LANG_INVARIANT 0x7f
+#define VK_F17 0x80
+#define WM_SETICON 0x0080
+#define WVR_ALIGNRIGHT 0x0080
+#define CS_PARENTDC 0x0080
+#define CF_OWNERDISPLAY 0x0080
+#define HDS_FULLDRAG 0x0080
+#define BTNS_WHOLEDROPDOWN 0x0080
+#define TTS_CLOSE 0x80
+#define TBS_NOTHUMB 0x0080
+#define UDS_NOTHOUSANDS 0x0080
+#define LVS_NOLABELWRAP 0x0080
+#define TVS_NOTOOLTIPS 0x0080
+#define TVS_EX_PARTIALCHECKBOXES 0x0080
+#define TCS_VERTICAL 0x0080
+#define MCS_SHORTDAYSOFWEEK 0x0080
+#define LANG_UIGHUR 0x80
+#define VK_F18 0x81
+#define WM_NCCREATE 0x0081
+#define CF_DSPTEXT 0x0081
+#define LANG_MAORI 0x81
+#define VK_F19 0x82
+#define WM_NCDESTROY 0x0082
+#define CF_DSPBITMAP 0x0082
+#define LANG_OCCITAN 0x82
+#define VK_F20 0x83
+#define WM_NCCALCSIZE 0x0083
+#define CF_DSPMETAFILEPICT 0x0083
+#define LANG_CORSICAN 0x83
+#define VK_F21 0x84
+#define WM_NCHITTEST 0x0084
+#define LANG_ALSATIAN 0x84
+#define VK_F22 0x85
+#define WM_NCPAINT 0x0085
+#define LANG_SAKHA 0x85
+#define LANG_YAKUT 0x85
+#define VK_F23 0x86
+#define WM_NCACTIVATE 0x0086
+#define LANG_KICHE 0x86
+#define VK_F24 0x87
+#define WM_GETDLGCODE 0x0087
+#define LANG_KINYARWANDA 0x87
+#define WM_SYNCPAINT 0x0088
+#define LANG_WOLOF 0x88
+#define LANG_DARI 0x8c
+#define CF_DSPENHMETAFILE 0x008E
+#define VK_NUMLOCK 0x90
+#define VK_SCROLL 0x91
+#define LANG_SCOTTISH_GAELIC 0x91
+#define VK_OEM_NEC_EQUAL 0x92
+#define VK_OEM_FJ_JISHO 0x92
+#define LANG_CENTRAL_KURDISH 0x92
+#define VK_OEM_FJ_MASSHOU 0x93
+#define VK_OEM_FJ_TOUROKU 0x94
+#define VK_OEM_FJ_LOYA 0x95
+#define VK_OEM_FJ_ROYA 0x96
+#define VK_LSHIFT 0xA0
+#define WM_NCMOUSEMOVE 0x00A0
+#define VK_RSHIFT 0xA1
+#define WM_NCLBUTTONDOWN 0x00A1
+#define VK_LCONTROL 0xA2
+#define WM_NCLBUTTONUP 0x00A2
+#define VK_RCONTROL 0xA3
+#define WM_NCLBUTTONDBLCLK 0x00A3
+#define VK_LMENU 0xA4
+#define WM_NCRBUTTONDOWN 0x00A4
+#define VK_RMENU 0xA5
+#define WM_NCRBUTTONUP 0x00A5
+#define VK_BROWSER_BACK 0xA6
+#define WM_NCRBUTTONDBLCLK 0x00A6
+#define VK_BROWSER_FORWARD 0xA7
+#define WM_NCMBUTTONDOWN 0x00A7
+#define VK_BROWSER_REFRESH 0xA8
+#define WM_NCMBUTTONUP 0x00A8
+#define VK_BROWSER_STOP 0xA9
+#define WM_NCMBUTTONDBLCLK 0x00A9
+#define VK_BROWSER_SEARCH 0xAA
+#define VK_BROWSER_FAVORITES 0xAB
+#define WM_NCXBUTTONDOWN 0x00AB
+#define VK_BROWSER_HOME 0xAC
+#define WM_NCXBUTTONUP 0x00AC
+#define VK_VOLUME_MUTE 0xAD
+#define WM_NCXBUTTONDBLCLK 0x00AD
+#define VK_VOLUME_DOWN 0xAE
+#define VK_VOLUME_UP 0xAF
+#define VK_MEDIA_NEXT_TRACK 0xB0
+#define EM_GETSEL 0x00B0
+#define VK_MEDIA_PREV_TRACK 0xB1
+#define EM_SETSEL 0x00B1
+#define VK_MEDIA_STOP 0xB2
+#define EM_GETRECT 0x00B2
+#define VK_MEDIA_PLAY_PAUSE 0xB3
+#define EM_SETRECT 0x00B3
+#define VK_LAUNCH_MAIL 0xB4
+#define EM_SETRECTNP 0x00B4
+#define VK_LAUNCH_MEDIA_SELECT 0xB5
+#define EM_SCROLL 0x00B5
+#define VK_LAUNCH_APP1 0xB6
+#define EM_LINESCROLL 0x00B6
+#define VK_LAUNCH_APP2 0xB7
+#define EM_SCROLLCARET 0x00B7
+#define EM_GETMODIFY 0x00B8
+#define EM_SETMODIFY 0x00B9
+#define VK_OEM_1 0xBA
+#define EM_GETLINECOUNT 0x00BA
+#define VK_OEM_PLUS 0xBB
+#define EM_LINEINDEX 0x00BB
+#define VK_OEM_COMMA 0xBC
+#define EM_SETHANDLE 0x00BC
+#define VK_OEM_MINUS 0xBD
+#define EM_GETHANDLE 0x00BD
+#define VK_OEM_PERIOD 0xBE
+#define EM_GETTHUMB 0x00BE
+#define VK_OEM_2 0xBF
+#define VK_OEM_3 0xC0
+#define EM_LINELENGTH 0x00C1
+#define EM_REPLACESEL 0x00C2
+#define EM_GETLINE 0x00C4
+#define EM_LIMITTEXT 0x00C5
+#define EM_CANUNDO 0x00C6
+#define EM_UNDO 0x00C7
+#define EM_FMTLINES 0x00C8
+#define EM_LINEFROMCHAR 0x00C9
+#define EM_SETTABSTOPS 0x00CB
+#define EM_SETPASSWORDCHAR 0x00CC
+#define EM_EMPTYUNDOBUFFER 0x00CD
+#define EM_GETFIRSTVISIBLELINE 0x00CE
+#define EM_SETREADONLY 0x00CF
+#define EM_SETWORDBREAKPROC 0x00D0
+#define EM_GETWORDBREAKPROC 0x00D1
+#define EM_GETPASSWORDCHAR 0x00D2
+#define EM_SETMARGINS 0x00D3
+#define EM_GETMARGINS 0x00D4
+#define EM_GETLIMITTEXT 0x00D5
+#define EM_POSFROMCHAR 0x00D6
+#define EM_CHARFROMPOS 0x00D7
+#define EM_SETIMESTATUS 0x00D8
+#define EM_GETIMESTATUS 0x00D9
+#define VK_OEM_4 0xDB
+#define VK_OEM_5 0xDC
+#define VK_OEM_6 0xDD
+#define VK_OEM_7 0xDE
+#define VK_OEM_8 0xDF
+#define VK_OEM_AX 0xE1
+#define VK_OEM_102 0xE2
+#define VK_ICO_HELP 0xE3
+#define VK_ICO_00 0xE4
+#define VK_PROCESSKEY 0xE5
+#define VK_ICO_CLEAR 0xE6
+#define VK_PACKET 0xE7
+#define VK_OEM_RESET 0xE9
+#define VK_OEM_JUMP 0xEA
+#define VK_OEM_PA1 0xEB
+#define VK_OEM_PA2 0xEC
+#define VK_OEM_PA3 0xED
+#define VK_OEM_WSCTRL 0xEE
+#define VK_OEM_CUSEL 0xEF
+#define VK_OEM_ATTN 0xF0
+#define BM_GETCHECK 0x00F0
+#define VK_OEM_FINISH 0xF1
+#define BM_SETCHECK 0x00F1
+#define VK_OEM_COPY 0xF2
+#define BM_GETSTATE 0x00F2
+#define VK_OEM_AUTO 0xF3
+#define BM_SETSTATE 0x00F3
+#define VK_OEM_ENLW 0xF4
+#define BM_SETSTYLE 0x00F4
+#define VK_OEM_BACKTAB 0xF5
+#define BM_CLICK 0x00F5
+#define VK_ATTN 0xF6
+#define BM_GETIMAGE 0x00F6
+#define VK_CRSEL 0xF7
+#define BM_SETIMAGE 0x00F7
+#define VK_EXSEL 0xF8
+#define BM_SETDONTCLICK 0x00F8
+#define VK_EREOF 0xF9
+#define VK_PLAY 0xFA
+#define VK_ZOOM 0xFB
+#define VK_NONAME 0xFC
+#define VK_PA1 0xFD
+#define VK_OEM_CLEAR 0xFE
+#define WM_INPUT_DEVICE_CHANGE 0x00FE
+#define SUBVERSION_MASK 0x000000FF
+#define WM_INPUT 0x00FF
+#define WM_KEYFIRST 0x0100
+#define WM_KEYDOWN 0x0100
+#define WVR_HREDRAW 0x0100
+#define HDS_FILTERBAR 0x0100
+#define TBSTYLE_TOOLTIPS 0x0100
+#define RBS_TOOLTIPS 0x00000100
+#define TTS_USEVISUALSTYLE 0x100
+#define SBARS_SIZEGRIP 0x0100
+#define TBS_TOOLTIPS 0x0100
+#define UDS_HOTTRACK 0x0100
+#define LVS_AUTOARRANGE 0x0100
+#define TVS_CHECKBOXES 0x0100
+#define TVS_EX_EXCLUSIONCHECKBOXES 0x0100
+#define TCS_BUTTONS 0x0100
+#define MCS_NOSELCHANGEONNAV 0x0100
+#define WM_KEYUP 0x0101
+#define WM_CHAR 0x0102
+#define WM_DEADCHAR 0x0103
+#define WM_SYSKEYDOWN 0x0104
+#define WM_SYSKEYUP 0x0105
+#define WM_SYSCHAR 0x0106
+#define WM_SYSDEADCHAR 0x0107
+#define WM_UNICHAR 0x0109
+#define WM_KEYLAST 0x0109
+#define WM_IME_STARTCOMPOSITION 0x010D
+#define WM_IME_ENDCOMPOSITION 0x010E
+#define WM_IME_COMPOSITION 0x010F
+#define WM_IME_KEYLAST 0x010F
+#define WM_INITDIALOG 0x0110
+#define WM_COMMAND 0x0111
+#define WM_SYSCOMMAND 0x0112
+#define WM_TIMER 0x0113
+#define WM_HSCROLL 0x0114
+#define WM_VSCROLL 0x0115
+#define WM_INITMENU 0x0116
+#define WM_INITMENUPOPUP 0x0117
+#define WM_GESTURE 0x0119
+#define WM_GESTURENOTIFY 0x011A
+#define WM_MENUSELECT 0x011F
+#define WM_MENUCHAR 0x0120
+#define WM_ENTERIDLE 0x0121
+#define WM_MENURBUTTONUP 0x0122
+#define WM_MENUDRAG 0x0123
+#define WM_MENUGETOBJECT 0x0124
+#define WM_UNINITMENUPOPUP 0x0125
+#define WM_MENUCOMMAND 0x0126
+#define WM_CHANGEUISTATE 0x0127
+#define WM_UPDATEUISTATE 0x0128
+#define WM_QUERYUISTATE 0x0129
+#define WM_CTLCOLORMSGBOX 0x0132
+#define WM_CTLCOLOREDIT 0x0133
+#define WM_CTLCOLORLISTBOX 0x0134
+#define WM_CTLCOLORBTN 0x0135
+#define WM_CTLCOLORDLG 0x0136
+#define WM_CTLCOLORSCROLLBAR 0x0137
+#define WM_CTLCOLORSTATIC 0x0138
+#define MN_GETHMENU 0x01E1
+#define _WIN32_IE_IE20 0x0200
+#define WM_MOUSEFIRST 0x0200
+#define WM_MOUSEMOVE 0x0200
+#define WVR_VREDRAW 0x0200
+#define CS_NOCLOSE 0x0200
+#define CF_PRIVATEFIRST 0x0200
+#define HDS_FLAT 0x0200
+#define TBSTYLE_WRAPABLE 0x0200
+#define RBS_VARHEIGHT 0x00000200
+#define TBS_REVERSED 0x0200
+#define LVS_EDITLABELS 0x0200
+#define TVS_TRACKSELECT 0x0200
+#define TVS_EX_DIMMEDCHECKBOXES 0x0200
+#define TCS_MULTILINE 0x0200
+#define WM_LBUTTONDOWN 0x0201
+#define WM_LBUTTONUP 0x0202
+#define WM_LBUTTONDBLCLK 0x0203
+#define WM_RBUTTONDOWN 0x0204
+#define WM_RBUTTONUP 0x0205
+#define WM_RBUTTONDBLCLK 0x0206
+#define WM_MBUTTONDOWN 0x0207
+#define WM_MBUTTONUP 0x0208
+#define WM_MBUTTONDBLCLK 0x0209
+#define WM_MOUSEWHEEL 0x020A
+#define WM_XBUTTONDOWN 0x020B
+#define WM_XBUTTONUP 0x020C
+#define WM_XBUTTONDBLCLK 0x020D
+#define WM_MOUSEHWHEEL 0x020E
+#define WM_MOUSELAST 0x020E
+#define WM_PARENTNOTIFY 0x0210
+#define WM_ENTERMENULOOP 0x0211
+#define WM_EXITMENULOOP 0x0212
+#define WM_NEXTMENU 0x0213
+#define WM_SIZING 0x0214
+#define WM_CAPTURECHANGED 0x0215
+#define WM_MOVING 0x0216
+#define WM_POWERBROADCAST 0x0218
+#define WM_DEVICECHANGE 0x0219
+#define WM_MDICREATE 0x0220
+#define WM_MDIDESTROY 0x0221
+#define WM_MDIACTIVATE 0x0222
+#define WM_MDIRESTORE 0x0223
+#define WM_MDINEXT 0x0224
+#define WM_MDIMAXIMIZE 0x0225
+#define WM_MDITILE 0x0226
+#define WM_MDICASCADE 0x0227
+#define WM_MDIICONARRANGE 0x0228
+#define WM_MDIGETACTIVE 0x0229
+#define WM_MDISETMENU 0x0230
+#define WM_ENTERSIZEMOVE 0x0231
+#define WM_EXITSIZEMOVE 0x0232
+#define WM_DROPFILES 0x0233
+#define WM_MDIREFRESHMENU 0x0234
+#define WM_POINTERDEVICECHANGE 0x238
+#define WM_POINTERDEVICEINRANGE 0x239
+#define WM_POINTERDEVICEOUTOFRANGE 0x23A
+#define WM_TOUCH 0x0240
+#define WM_NCPOINTERUPDATE 0x0241
+#define WM_NCPOINTERDOWN 0x0242
+#define WM_NCPOINTERUP 0x0243
+#define WM_POINTERUPDATE 0x0245
+#define WM_POINTERDOWN 0x0246
+#define WM_POINTERUP 0x0247
+#define WM_POINTERENTER 0x0249
+#define WM_POINTERLEAVE 0x024A
+#define WM_POINTERACTIVATE 0x024B
+#define WM_POINTERCAPTURECHANGED 0x024C
+#define WM_TOUCHHITTESTING 0x024D
+#define WM_POINTERWHEEL 0x024E
+#define WM_POINTERHWHEEL 0x024F
+#define DM_POINTERHITTEST 0x0250
+#define WM_IME_SETCONTEXT 0x0281
+#define WM_IME_NOTIFY 0x0282
+#define WM_IME_CONTROL 0x0283
+#define WM_IME_COMPOSITIONFULL 0x0284
+#define WM_IME_SELECT 0x0285
+#define WM_IME_CHAR 0x0286
+#define WM_IME_REQUEST 0x0288
+#define WM_IME_KEYDOWN 0x0290
+#define WM_IME_KEYUP 0x0291
+#define WM_NCMOUSEHOVER 0x02A0
+#define WM_MOUSEHOVER 0x02A1
+#define WM_NCMOUSELEAVE 0x02A2
+#define WM_MOUSELEAVE 0x02A3
+#define WM_WTSSESSION_CHANGE 0x02B1
+#define WM_TABLET_FIRST 0x02c0
+#define WM_TABLET_LAST 0x02df
+#define WM_DPICHANGED 0x02E0
+#define CF_PRIVATELAST 0x02FF
+#define _WIN32_IE_IE30 0x0300
+#define WM_CUT 0x0300
+#define CF_GDIOBJFIRST 0x0300
+#define WM_COPY 0x0301
+#define _WIN32_IE_IE302 0x0302
+#define WM_PASTE 0x0302
+#define WM_CLEAR 0x0303
+#define WM_UNDO 0x0304
+#define WM_RENDERFORMAT 0x0305
+#define WM_RENDERALLFORMATS 0x0306
+#define WM_DESTROYCLIPBOARD 0x0307
+#define WM_DRAWCLIPBOARD 0x0308
+#define WM_PAINTCLIPBOARD 0x0309
+#define WM_VSCROLLCLIPBOARD 0x030A
+#define WM_SIZECLIPBOARD 0x030B
+#define WM_ASKCBFORMATNAME 0x030C
+#define WM_CHANGECBCHAIN 0x030D
+#define WM_HSCROLLCLIPBOARD 0x030E
+#define WM_QUERYNEWPALETTE 0x030F
+#define WM_PALETTEISCHANGING 0x0310
+#define WM_PALETTECHANGED 0x0311
+#define WM_HOTKEY 0x0312
+#define WM_PRINT 0x0317
+#define WM_PRINTCLIENT 0x0318
+#define WM_APPCOMMAND 0x0319
+#define WM_THEMECHANGED 0x031A
+#define WM_CLIPBOARDUPDATE 0x031D
+#define WM_DWMCOMPOSITIONCHANGED 0x031E
+#define WM_DWMNCRENDERINGCHANGED 0x031F
+#define WM_DWMCOLORIZATIONCOLORCHANGED 0x0320
+#define WM_DWMWINDOWMAXIMIZEDCHANGE 0x0321
+#define WM_DWMSENDICONICTHUMBNAIL 0x0323
+#define WM_DWMSENDICONICLIVEPREVIEWBITMAP 0x0326
+#define WM_GETTITLEBARINFOEX 0x033F
+#define WM_HANDHELDFIRST 0x0358
+#define WM_HANDHELDLAST 0x035F
+#define WM_AFXFIRST 0x0360
+#define WM_AFXLAST 0x037F
+#define WM_PENWINFIRST 0x0380
+#define WM_PENWINLAST 0x038F
+#define WM_DDE_FIRST 0x03E0
+#define CF_GDIOBJLAST 0x03FF
+#define _WIN32_WINNT_NT4 0x0400
+#define _WIN32_IE_IE40 0x0400
+#define WM_USER 0x0400
+#define WVR_VALIDRECTS 0x0400
+#define HDS_CHECKBOXES 0x0400
+#define TBSTYLE_ALTDRAG 0x0400
+#define RBS_BANDBORDERS 0x00000400
+#define TBS_DOWNISLEFT 0x0400
+#define LVS_OWNERDRAWFIXED 0x0400
+#define TVS_SINGLEEXPAND 0x0400
+#define TVS_EX_DRAWIMAGEASYNC 0x0400
+#define TCS_FIXEDWIDTH 0x0400
+#define ctlFirst 0x0400
+#define psh1 0x0400
+#define _WIN32_IE_IE401 0x0401
+#define psh2 0x0401
+#define psh3 0x0402
+#define psh4 0x0403
+#define psh5 0x0404
+#define psh6 0x0405
+#define psh7 0x0406
+#define psh8 0x0407
+#define psh9 0x0408
+#define psh10 0x0409
+#define psh11 0x040a
+#define psh12 0x040b
+#define psh13 0x040c
+#define psh14 0x040d
+#define psh15 0x040e
+#define psh16 0x040f
+#define _WIN32_WINDOWS 0x0410
+#define chx1 0x0410
+#define chx2 0x0411
+#define chx3 0x0412
+#define chx4 0x0413
+#define chx5 0x0414
+#define chx6 0x0415
+#define chx7 0x0416
+#define chx8 0x0417
+#define chx9 0x0418
+#define chx10 0x0419
+#define chx11 0x041a
+#define chx12 0x041b
+#define chx13 0x041c
+#define chx14 0x041d
+#define chx15 0x041e
+#define chx16 0x041f
+#define rad1 0x0420
+#define rad2 0x0421
+#define rad3 0x0422
+#define rad4 0x0423
+#define rad5 0x0424
+#define rad6 0x0425
+#define rad7 0x0426
+#define rad8 0x0427
+#define rad9 0x0428
+#define rad10 0x0429
+#define rad11 0x042a
+#define rad12 0x042b
+#define rad13 0x042c
+#define rad14 0x042d
+#define rad15 0x042e
+#define rad16 0x042f
+#define grp1 0x0430
+#define grp2 0x0431
+#define grp3 0x0432
+#define grp4 0x0433
+#define frm1 0x0434
+#define frm2 0x0435
+#define frm3 0x0436
+#define frm4 0x0437
+#define rct1 0x0438
+#define rct2 0x0439
+#define rct3 0x043a
+#define rct4 0x043b
+#define ico1 0x043c
+#define ico2 0x043d
+#define ico3 0x043e
+#define ico4 0x043f
+#define stc1 0x0440
+#define stc2 0x0441
+#define stc3 0x0442
+#define stc4 0x0443
+#define stc5 0x0444
+#define stc6 0x0445
+#define stc7 0x0446
+#define stc8 0x0447
+#define stc9 0x0448
+#define stc10 0x0449
+#define stc11 0x044a
+#define stc12 0x044b
+#define stc13 0x044c
+#define stc14 0x044d
+#define stc15 0x044e
+#define stc16 0x044f
+#define stc17 0x0450
+#define stc18 0x0451
+#define stc19 0x0452
+#define stc20 0x0453
+#define stc21 0x0454
+#define stc22 0x0455
+#define stc23 0x0456
+#define stc24 0x0457
+#define stc25 0x0458
+#define stc26 0x0459
+#define stc27 0x045a
+#define stc28 0x045b
+#define stc29 0x045c
+#define stc30 0x045d
+#define stc31 0x045e
+#define stc32 0x045f
+#define lst1 0x0460
+#define lst2 0x0461
+#define lst3 0x0462
+#define lst4 0x0463
+#define lst5 0x0464
+#define lst6 0x0465
+#define lst7 0x0466
+#define lst8 0x0467
+#define lst9 0x0468
+#define lst10 0x0469
+#define lst11 0x046a
+#define lst12 0x046b
+#define lst13 0x046c
+#define lst14 0x046d
+#define lst15 0x046e
+#define lst16 0x046f
+#define cmb1 0x0470
+#define cmb2 0x0471
+#define cmb3 0x0472
+#define cmb4 0x0473
+#define cmb5 0x0474
+#define cmb6 0x0475
+#define cmb7 0x0476
+#define cmb8 0x0477
+#define cmb9 0x0478
+#define cmb10 0x0479
+#define cmb11 0x047a
+#define cmb12 0x047b
+#define cmb13 0x047c
+#define cmb14 0x047d
+#define cmb15 0x047e
+#define cmb16 0x047f
+#define edt1 0x0480
+#define edt2 0x0481
+#define edt3 0x0482
+#define edt4 0x0483
+#define edt5 0x0484
+#define edt6 0x0485
+#define edt7 0x0486
+#define edt8 0x0487
+#define edt9 0x0488
+#define edt10 0x0489
+#define edt11 0x048a
+#define edt12 0x048b
+#define edt13 0x048c
+#define edt14 0x048d
+#define edt15 0x048e
+#define edt16 0x048f
+#define scr1 0x0490
+#define scr2 0x0491
+#define scr3 0x0492
+#define scr4 0x0493
+#define scr5 0x0494
+#define scr6 0x0495
+#define scr7 0x0496
+#define scr8 0x0497
+#define ctl1 0x04A0
+#define ctlLast 0x04ff
+#define _WIN32_WINNT_WIN2K 0x0500
+#define _WIN32_IE_IE50 0x0500
+#define _WIN32_WINNT_WINXP 0x0501
+#define _WIN32_IE_IE501 0x0501
+#define _WIN32_WINNT_WS03 0x0502
+#define _WIN32_IE_IE55 0x0550
+#define _WIN32_WINNT_WIN6 0x0600
+#define _WIN32_WINNT_VISTA 0x0600
+#define _WIN32_WINNT_WS08 0x0600
+#define _WIN32_WINNT_LONGHORN 0x0600
+#define _WIN32_IE_IE60 0x0600
+#define FILEOPENORD 1536
+#define _WIN32_WINNT_WIN7 0x0601
+#define _WIN32_IE_IE60SP1 0x0601
+#define MULTIFILEOPENORD 1537
+#define _WIN32_WINNT_WIN8 0x0602
+#define _WIN32_IE_WS03 0x0602
+#define PRINTDLGORD 1538
+#define _WIN32_WINNT_WINBLUE 0x0603
+#define _WIN32_IE_IE60SP2 0x0603
+#define _WIN32_WINNT 0x0603
+#define PRNSETUPDLGORD 1539
+#define FINDDLGORD 1540
+#define REPLACEDLGORD 1541
+#define FONTDLGORD 1542
+#define FORMATDLGORD31 1543
+#define FORMATDLGORD30 1544
+#define RUNDLGORD 1545
+#define PAGESETUPDLGORD 1546
+#define NEWFILEOPENORD 1547
+#define PRINTDLGEXORD 1549
+#define PAGESETUPDLGORDMOTIF 1550
+#define COLORMGMTDLGORD 1551
+#define NEWFILEOPENV2ORD 1552
+#define NEWFILEOPENV3ORD 1553
+#define NEWFORMATDLGWITHLINK 1591
+#define IDC_MANAGE_LINK 1592
+#define _WIN32_IE_IE70 0x0700
+#define _WIN32_IE_IE80 0x0800
+#define CS_SAVEBITS 0x0800
+#define HDS_NOSIZING 0x0800
+#define TBSTYLE_FLAT 0x0800
+#define RBS_FIXEDORDER 0x00000800
+#define SBARS_TOOLTIPS 0x0800
+#define SBT_TOOLTIPS 0x0800
+#define TBS_NOTIFYBEFOREMOVE 0x0800
+#define LVS_ALIGNLEFT 0x0800
+#define TVS_INFOTIP 0x0800
+#define TCS_RAGGEDRIGHT 0x0800
+#define _WIN32_IE_IE90 0x0900
+#define _WIN32_IE_IE100 0x0A00
+#define _WIN32_IE 0x0A00
+#define LVS_ALIGNMASK 0x0c00
+#define CS_BYTEALIGNCLIENT 0x1000
+#define HDS_OVERFLOW 0x1000
+#define TBSTYLE_LIST 0x1000
+#define RBS_REGISTERDROP 0x00001000
+#define TBS_TRANSPARENTBKGND 0x1000
+#define LVS_OWNERDATA 0x1000
+#define TVS_FULLROWSELECT 0x1000
+#define TCS_FOCUSONBUTTONDOWN 0x1000
+#define CS_BYTEALIGNWINDOW 0x2000
+#define TBSTYLE_CUSTOMERASE 0x2000
+#define RBS_AUTOSIZE 0x00002000
+#define LVS_NOSCROLL 0x2000
+#define TVS_NOSCROLL 0x2000
+#define TCS_OWNERDRAWFIXED 0x2000
+#define CS_GLOBALCLASS 0x4000
+#define TBSTYLE_REGISTERDROP 0x4000
+#define RBS_VERTICALGRIPPER 0x00004000
+#define LVS_NOCOLUMNHEADER 0x4000
+#define TVS_NONEVENHEIGHT 0x4000
+#define TCS_TOOLTIPS 0x4000
+#define IDH_NO_HELP 28440
+#define IDH_MISSING_CONTEXT 28441
+#define IDH_GENERIC_HELP_BUTTON 28442
+#define IDH_OK 28443
+#define IDH_CANCEL 28444
+#define IDH_HELP 28445
+#define LANG_BOSNIAN_NEUTRAL 0x781a
+#define LANG_CHINESE_TRADITIONAL 0x7c04
+#define LANG_SERBIAN_NEUTRAL 0x7c1a
+#define IDTIMEOUT 32000
+#define OCR_NORMAL 32512
+#define OIC_SAMPLE 32512
+#define IDI_APPLICATION 32512
+#define OCR_IBEAM 32513
+#define OIC_HAND 32513
+#define IDI_HAND 32513
+#define OCR_WAIT 32514
+#define OIC_QUES 32514
+#define IDI_QUESTION 32514
+#define OCR_CROSS 32515
+#define OIC_BANG 32515
+#define IDI_EXCLAMATION 32515
+#define OCR_UP 32516
+#define OIC_NOTE 32516
+#define IDI_ASTERISK 32516
+#define OIC_WINLOGO 32517
+#define IDI_WINLOGO 32517
+#define OIC_SHIELD 32518
+#define IDI_SHIELD 32518
+#define OCR_SIZE 32640
+#define OCR_ICON 32641
+#define OCR_SIZENWSE 32642
+#define OCR_SIZENESW 32643
+#define OCR_SIZEWE 32644
+#define OCR_SIZENS 32645
+#define OCR_SIZEALL 32646
+#define OCR_ICOCUR 32647
+#define OCR_NO 32648
+#define OCR_HAND 32649
+#define OCR_APPSTARTING 32650
+#define OBM_LFARROWI 32734
+#define OBM_RGARROWI 32735
+#define OBM_DNARROWI 32736
+#define OBM_UPARROWI 32737
+#define OBM_COMBO 32738
+#define OBM_MNARROW 32739
+#define OBM_LFARROWD 32740
+#define OBM_RGARROWD 32741
+#define OBM_DNARROWD 32742
+#define OBM_UPARROWD 32743
+#define OBM_RESTORED 32744
+#define OBM_ZOOMD 32745
+#define OBM_REDUCED 32746
+#define OBM_RESTORE 32747
+#define OBM_ZOOM 32748
+#define OBM_REDUCE 32749
+#define OBM_LFARROW 32750
+#define OBM_RGARROW 32751
+#define OBM_DNARROW 32752
+#define OBM_UPARROW 32753
+#define OBM_CLOSE 32754
+#define OBM_OLD_RESTORE 32755
+#define OBM_OLD_ZOOM 32756
+#define OBM_OLD_REDUCE 32757
+#define OBM_BTNCORNERS 32758
+#define OBM_CHECKBOXES 32759
+#define OBM_CHECK 32760
+#define OBM_BTSIZE 32761
+#define OBM_OLD_LFARROW 32762
+#define OBM_OLD_RGARROW 32763
+#define OBM_OLD_DNARROW 32764
+#define OBM_OLD_UPARROW 32765
+#define OBM_SIZE 32766
+#define OBM_OLD_CLOSE 32767
+#define WM_APP 0x8000
+#define HELP_TCARD 0x8000
+#define TBSTYLE_TRANSPARENT 0x8000
+#define RBS_DBLCLKTOGGLE 0x00008000
+#define LVS_NOSORTHEADER 0x8000
+#define TVS_NOHSCROLL 0x8000
+#define TCS_FOCUSNEVER 0x8000
+#define SC_SIZE 0xF000
+#define SC_SEPARATOR 0xF00F
+#define SC_MOVE 0xF010
+#define SC_MINIMIZE 0xF020
+#define SC_MAXIMIZE 0xF030
+#define SC_NEXTWINDOW 0xF040
+#define SC_PREVWINDOW 0xF050
+#define SC_CLOSE 0xF060
+#define SC_VSCROLL 0xF070
+#define SC_HSCROLL 0xF080
+#define SC_MOUSEMENU 0xF090
+#define SC_KEYMENU 0xF100
+#define SC_ARRANGE 0xF110
+#define SC_RESTORE 0xF120
+#define SC_TASKLIST 0xF130
+#define SC_SCREENSAVE 0xF140
+#define SC_HOTKEY 0xF150
+#define SC_DEFAULT 0xF160
+#define SC_MONITORPOWER 0xF170
+#define SC_CONTEXTHELP 0xF180
+#define LVS_TYPESTYLEMASK 0xfc00
+#define SPVERSION_MASK 0x0000FF00
+#define HTERROR -2
+#define PWR_FAIL -1
+#define UNICODE_NOCHAR 0xFFFF
+#define HTTRANSPARENT -1
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 101
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1000
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/src/satellite.cpp b/src/satellite.cpp
index 3b998c9..e18a6f8 100644
--- a/src/satellite.cpp
+++ b/src/satellite.cpp
@@ -3,78 +3,60 @@
#include "beam.h"
-SATELLITE::SATELLITE(GLOBALDATA *global, ENVIRONMENT *env):_global(global),_env(env)
-{ }
-
-SATELLITE::~SATELLITE()
+SATELLITE::SATELLITE() :
+ x(env.screenWidth / 2)
{
- _env = NULL;
- _global = NULL;
+ prev_x = x;
}
-void SATELLITE::Init()
+void SATELLITE::draw()
{
- x = _global->screenWidth / 2;
- previous_x = x;
- y = 35;
- xv = -2;
+ drawing_mode(DRAW_MODE_SOLID, NULL, 0, 0);
+ draw_sprite(global.canvas, env.misc[SATELLITE_IMAGE], x, y);
+ global.make_update(x - 20, y, 80, 60);
+ global.make_update(prev_x, y, 80, 60);
}
-void SATELLITE::Move()
+void SATELLITE::move()
{
- if (x < -5)
- xv += 1;
- else if (x > (_global->screenWidth - 10) )
- xv -= 1;
-
- previous_x = x;
- x += xv;
+ // Be sure an owned beam is valid
+ if (beam && beam->destroy)
+ beam = nullptr;
+
+ // reverse movement if the satellite reaches the screen borders
+ if (x < -5)
+ xv += 1;
+ else if (x > (env.screenWidth - 20) )
+ xv -= 1;
+
+ prev_x = x;
+ x += xv;
+
+ // If the satellite is firing, move the beam
+ if (beam)
+ beam->moveStart(xv < 0 ? x + 10: x + 40, y + 20);
}
-
-void SATELLITE::Draw(BITMAP *dest)
+void SATELLITE::shoot()
{
- drawing_mode(DRAW_MODE_SOLID, NULL, 0, 0);
- draw_sprite(dest, (BITMAP *) _global->misc[SATELLITE_IMAGE],
- x, y);
-
- _env->make_update(x - 20, y, 80, 60);
- _env->make_update(previous_x, y, 80, 60);
-}
-
-
-
-void SATELLITE::Shoot()
-{
- int chance;
- BEAM *my_beam;
- int angle, laser_type;
-
- if (_env->satellite == LASER_NONE)
- return;
-
- if (_env->naturals_since_last_shot >= 3)
- return;
-
- chance = rand() % 100;
- if (! chance) // !% chance to fire
- {
- if (_env->satellite == LASER_WEAK) laser_type = SML_LAZER;
- else if (_env->satellite == LASER_STRONG) laser_type = MED_LAZER;
- else if (_env->satellite == LASER_SUPER) laser_type = LRG_LAZER;
- else return;
-
- angle = rand() % 30;
- my_beam = new BEAM(_global, _env, (xv < 0) ? x + 10: x + 40,
- y + 20, angle, laser_type);
- if (! my_beam)
- return;
- my_beam->player = NULL;
- _env->naturals_since_last_shot++;
- }
+ if ( (SL_NONE != env.satellite)
+ && (global.naturals_activated < 4)
+ && (nullptr == beam)
+ // 1% chance to fire
+ && (!(rand() % 100)) ) {
+ try {
+ beam = new BEAM(nullptr, xv < 0 ? x + 10: x + 40, y + 20,
+ rand() % 35 + (xv < 0 ? 320 : 5),
+ SML_LAZER + (rand() % env.satellite),
+ BT_NATURAL);
+ global.naturals_activated++;
+ } catch (...) {
+ // No problem... fire another time. ;)
+ }
+ }
}
diff --git a/src/satellite.h b/src/satellite.h
index 3c53829..d273dae 100644
--- a/src/satellite.h
+++ b/src/satellite.h
@@ -4,28 +4,50 @@
#include "environment.h"
#include "globaldata.h"
-#include "virtobj.h"
#define SATELLITE_IMAGE 16
#define CHANCE_TO_SHOOT 100
+#ifndef BEAM_DEFINE
+class BEAM;
+#endif // BEAM_DEFINE
class SATELLITE
- {
- public:
- int x, y;
- int xv, previous_x;
- GLOBALDATA *_global;
- ENVIRONMENT *_env;
-
- SATELLITE(GLOBALDATA *global, ENVIRONMENT *env);
- ~SATELLITE();
- void Init();
- void Move();
- void Draw(BITMAP *dest);
- void Shoot();
-
- };
+{
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ explicit SATELLITE();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ void draw();
+ void move();
+ void shoot();
+
+
+private:
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ BEAM* beam = nullptr;
+ int32_t x = 0;
+ int32_t y = MENUHEIGHT + 5;
+ int32_t xv = -2;
+ int32_t prev_x = 0;
+
+};
#endif
diff --git a/src/shop.cpp b/src/shop.cpp
new file mode 100644
index 0000000..4907894
--- /dev/null
+++ b/src/shop.cpp
@@ -0,0 +1,1002 @@
+#include "shop.h"
+#include "player.h"
+#include "files.h"
+#include "gameloop.h" // For the LevelCreator declaration.
+#include "text.h" // for draw_text_in_box()
+
+#define SHOP_BAR_HEIGHT 29
+
+
+/// ==== helper functions ====
+static int32_t calcPotentialDmg (int32_t weapNum);
+static void divide_team_money();
+static void do_ai_shopping (PLAYER* pl, int32_t maxBoost, int32_t maxScore);
+static void draw_shop (PLAYER* pl);
+static void draw_weapon_list (PLAYER* pl, int32_t* trolley,
+ int32_t scroll_old, int32_t scroll_new,
+ int32_t over_old, int32_t over_new);
+
+/// ==== Helper values ====
+static int32_t btps = 0;
+
+
+/// ==== External functions used ====
+void draw_simple_bg(bool drawImage);
+
+bool shop(LevelCreator* lvl_creator)
+{
+ bool performed_save_game = false;
+ char buf[50] = { 0 };
+ char description[1024] = { 0x20, 0x0 };
+ const
+ int32_t scrollArrowPos = env.screenWidth - STUFF_BAR_WIDTH - 30;
+
+ draw_shop (nullptr);
+
+ // Determine btps:
+ btps = ROUNDu((env.screenHeight - SHOP_BAR_HEIGHT) / STUFF_BAR_HEIGHT);
+
+ // Init global for drawing the shop:
+ global.do_updates();
+ global.stopwindow = true;
+
+ // before we do anything else, put a cap on money
+ for (int32_t z = 0; z < env.numGamePlayers; ++z) {
+ if (env.players[z]->money > 1000000000)
+ env.players[z]->money = 1000000000;
+ if (env.players[z]->money < 0)
+ env.players[z]->money = 0;
+ }
+
+
+ if (env.isGameLoaded)
+ // after the first shopping loop the game isn't fresh any more
+ env.isGameLoaded = false;
+ else
+ // Money dividing within the non-neutral teams only happens if no game
+ // was loaded. Game saving is done after that rounds money dividing.
+ divide_team_money();
+
+
+ // Determine maximum boost value and score
+ int32_t maxBoost = 0;
+ int32_t maxScore = 0;
+ for (int32_t z = 0; z < env.numGamePlayers;++z) {
+ int32_t boostValue = env.players[z]->getBoostValue();
+ if (boostValue > maxBoost)
+ maxBoost = boostValue;
+ if (env.players[z]->score > maxScore)
+ maxScore = env.players[z]->score;
+ }
+
+ // If this is demo mode, raise the max boost level, as there
+ // are no human players to define a maximum value
+ if (global.demo_mode)
+ maxBoost += env.rounds - global.currentround;
+
+ // Loop all players to let them do their shopping
+ for (int32_t pl = 0; pl < env.numGamePlayers; pl++) {
+ // computer players have their own function for their shopping
+ if ( HUMAN_PLAYER != env.players[pl]->type ) {
+ do_ai_shopping(env.players[pl], maxBoost, maxScore);
+ continue; // next one.
+ }
+
+ // Be sure no input from previous human players or from pressing
+ // the "Play" button on the player selection screen carry over:
+ flush_inputs();
+
+ int32_t money = env.players[pl]->money;
+ int32_t trolley[THINGS] = { 0 };
+ bool done = false;
+ bool need_draw = false;
+ int32_t scroll = 1;
+ int32_t scroll_old = -1;
+ int32_t pressed = -1;
+ int32_t prev_wheel = mouse_z;
+ int32_t curr_wheel = 0;
+ int32_t lastMouse_b = 0;
+ int32_t lastMouse_x = 0;
+ int32_t lastMouse_y = 0;
+ int32_t lb = 0;
+ int32_t itemindex = 1;
+ int32_t hoverOver = -1;
+ int32_t hoverOver_old = -1;
+ int32_t cost, amt, inInv; // short cuts
+ BOX area(20, 60, 300, 400);
+
+ env.mouseclock = 0;
+
+ draw_shop (env.players[pl]);
+
+ while (!done) {
+ while (!done && !need_draw) {
+ if (global.isCloseBtnPressed()) {
+ done = true;
+ continue;
+ }
+
+ if ( (lastMouse_x != mouse_x) || (lastMouse_y != mouse_y) ) {
+ lastMouse_x = mouse_x;
+ lastMouse_y = mouse_y;
+ if (!env.osMouse)
+ need_draw = true;
+ }
+
+ int32_t newlyOver = -1;
+
+ // Check mouse button
+ if (!lb && (mouse_b & 1)) {
+ // Check close shop button:
+ if ( (mouse_x >= (env.halfWidth - 100))
+ && (mouse_x < (env.halfWidth + 100))
+ && (mouse_y >= (env.screenHeight - 50))
+ && (mouse_y < (env.screenHeight - 25)) )
+ done = true;
+ env.mouseclock = 0;
+ }
+ lb = (mouse_b & 1) ? 1 : 0;
+
+ /* ========================
+ * === Keyboard control ===
+ * ========================
+ */
+ if ( keypressed() ) {
+ k = readkey();
+ K = k >> 8;
+ } else
+ k = K = 0;
+
+ // Move up the list
+ if ( (K == KEY_UP) || (K == KEY_W) ) {
+ if (itemindex > 1)
+ itemindex--;
+ if (itemindex < scroll)
+ scroll = itemindex;
+ need_draw = true;
+ } else if ( (K == KEY_PGUP) || (K == KEY_R) ) {
+ itemindex -= btps;
+ if (itemindex < 1)
+ itemindex = 1;
+ if (itemindex < scroll)
+ scroll = itemindex;
+ need_draw = true;
+ }
+
+ // Move down the list
+ else if ( (K == KEY_DOWN) || (K == KEY_S) ) {
+ if (itemindex < (env.numAvailable - 1))
+ itemindex++;
+ if ( (itemindex - scroll) >= btps )
+ scroll = itemindex - (btps - 1);
+ need_draw = true;
+ } else if ( ( (K == KEY_PGDN) || (K == KEY_F) )
+ && (scroll <= (env.numAvailable - btps) ) ) {
+ itemindex += btps;
+ if (itemindex > env.numAvailable - 1)
+ itemindex = env.numAvailable - 1;
+ if ( (itemindex - scroll) >= btps)
+ scroll = itemindex - (btps - 1);
+ need_draw = true;
+ }
+
+ // make sure the selected item is on the visible screen
+ if (itemindex < scroll)
+ itemindex = scroll;
+ else if ( itemindex >= (scroll + btps) )
+ itemindex = scroll + btps - 1;
+
+ // buy or sell an item
+ if ( (K == KEY_RIGHT) || (K == KEY_D) ) {
+ pressed = env.availableItems[itemindex];
+ if (pressed >= WEAPONS) {
+ cost = item[pressed - WEAPONS].cost;
+ amt = item[pressed - WEAPONS].amt;
+ inInv = env.players[pl]->ni[pressed - WEAPONS];
+ } else {
+ cost = weapon[pressed].cost;
+ amt = weapon[pressed].amt;
+ inInv = env.players[pl]->nm[pressed];
+ }
+
+ if (key[KEY_LCONTROL] || key[KEY_RCONTROL]) {
+ cost *= 10;
+ amt *= 10;
+ }
+
+ if ( (money >= cost)
+ && ( (inInv + trolley[pressed]) < (MAX_ITEMS_IN_STOCK - amt)) ) {
+ if (trolley[pressed] <= -amt) {
+ if (env.sellpercent > 0.01) {
+ money -= ROUNDu(cost * env.sellpercent);
+ trolley[pressed] += amt;
+ need_draw = true;
+ }
+ } else {
+ money -= cost;
+ trolley[pressed] += amt;
+ need_draw = true;
+ if (inInv + trolley[pressed] > MAX_ITEMS_IN_STOCK)
+ trolley[pressed] = MAX_ITEMS_IN_STOCK;
+ }
+ }
+ pressed = -1;
+ } // end of buying
+
+ else if ( (K == KEY_LEFT) || (K == KEY_A) ) {
+ pressed = env.availableItems[itemindex];
+ if (pressed >= WEAPONS) {
+ cost = item[pressed - WEAPONS].cost;
+ amt = item[pressed - WEAPONS].amt;
+ inInv = env.players[pl]->ni[pressed - WEAPONS];
+ } else {
+ cost = weapon[pressed].cost;
+ amt = weapon[pressed].amt;
+ inInv = env.players[pl]->nm[pressed];
+ }
+
+ if (key[KEY_LCONTROL] || key[KEY_RCONTROL]) {
+ cost *= 10;
+ amt *= 10;
+ }
+
+ if (inInv + trolley[pressed] >= amt) {
+ if (trolley[pressed] >= amt) {
+ money += cost;
+ trolley[pressed] -= amt;
+ need_draw = true;
+ } else {
+ if (env.sellpercent > 0.01) {
+ money += ROUNDu(cost * env.sellpercent);
+ trolley[pressed] -= amt;
+ need_draw = true;
+ }
+ }
+ }
+ pressed = -1;
+ } // end of selling
+
+
+ // check for adding or removing rounds
+ else if ( (K == KEY_PLUS_PAD) || (K == KEY_EQUALS) ) {
+ if ( (env.rounds < MAX_ROUNDS) && (! env.mouseclock) ) {
+ env.rounds++;
+ global.currentround++;
+ need_draw = true;
+ }
+ } else if ( (K == KEY_MINUS_PAD) || (K == KEY_MINUS) ) {
+ if ( (env.rounds > 1)
+ && (global.currentround > 1)
+ && (! env.mouseclock) ) {
+ env.rounds--;
+ global.currentround--;
+ need_draw = true;
+ }
+ }
+
+ // check for saving the game
+ else if ( K == KEY_F10 ) {
+ if (!performed_save_game
+ && Save_Game() )
+ performed_save_game = true;
+ if (performed_save_game)
+ snprintf(description, 64,
+ "%s \"%s\".",
+ env.ingame->Get_Line(17),
+ env.game_name);
+ else
+ strncpy(description, env.ingame->Get_Line(41), 1023);
+ draw_text_in_box (&area, description, true);
+ need_draw = true;
+ }
+
+ // Keyboard exit shop:
+ if (K == KEY_ENTER)
+ done = true;
+
+ /* ========================
+ * === Mouse control ===
+ * ========================
+ */
+
+ // check mouse wheel
+ curr_wheel = mouse_z;
+ if (curr_wheel < prev_wheel) {
+ if (++scroll >= (env.numAvailable - btps) )
+ scroll = env.numAvailable - btps;
+ if (scroll > itemindex)
+ itemindex = scroll;
+ need_draw = true;
+ } else if (curr_wheel > prev_wheel) {
+ if (--scroll < 1)
+ scroll = 1;
+ if (itemindex >= (scroll + btps) )
+ itemindex = scroll + btps - 1;
+ need_draw = true;
+ }
+ prev_wheel = curr_wheel;
+
+ // Ensure the description shows what is selected
+ newlyOver = env.availableItems[itemindex];
+
+ // check mouse over items
+ if ( (mouse_x >= (env.screenWidth - STUFF_BAR_WIDTH))
+ && (mouse_x < env.screenWidth) ) {
+ bool isOver = false;
+ int32_t zzz = scroll;
+
+ for (int32_t z = 1; (z <= btps) && !isOver; ++z) {
+ if ( (mouse_y >= ( z * STUFF_BAR_HEIGHT))
+ && (mouse_y < ( (z * STUFF_BAR_HEIGHT) + 30) ) )
+ isOver = true;
+ else
+ ++zzz;
+ }
+
+ if (isOver && (hoverOver != env.availableItems[zzz])) {
+ newlyOver = env.availableItems[zzz];
+ itemindex = zzz;
+ need_draw = true;
+ }
+ } // End of mouse_x in stuff bar
+
+ // Switch description if necessary
+ if (hoverOver != newlyOver) {
+ if (newlyOver > -1) {
+ if (newlyOver < WEAPONS) {
+ WEAPON *weap = &weapon[newlyOver];
+ snprintf (description, 1023,
+ "Radius: %d\nYield: %d\n\n%s",
+ weap->radius,
+ calcPotentialDmg (newlyOver) * weap->spread,
+ weap->getDesc());
+ } else {
+ int32_t itemNum = newlyOver - WEAPONS;
+ ITEM *it = &item[itemNum];
+ if ( (itemNum >= ITEM_VENGEANCE)
+ && (itemNum <= ITEM_FATAL_FURY) ) {
+ double potDmg = calcPotentialDmg(it->vals[0])
+ * it->vals[1];
+ snprintf (description, 1023,
+ "Potential Damage: %d\n\n%s",
+ ROUND(potDmg), it->getDesc());
+ } else
+ snprintf (description, 1023, "%s",
+ it->getDesc());
+ }
+ } else
+ description[0] = 0;
+ hoverOver = newlyOver;
+ need_draw = true;
+
+ draw_text_in_box (&area, description, true);
+ } // end of hovering on a different item
+
+ // Check mouse buttons against scrolling, buying and selling.
+ if ( (mouse_b & 1) && !env.mouseclock) {
+ if ( (mouse_x >= scrollArrowPos)
+ && (mouse_x < (scrollArrowPos + 24)) ) {
+
+ // Fast up
+ if ( (mouse_y >= (env.halfHeight - 50))
+ && (mouse_y < (env.halfHeight - 25))
+ && (scroll > 1) ) {
+ scroll -= btps / 2;
+ if (scroll < 1)
+ scroll = 1;
+ need_draw = true;
+ }
+
+ // Up one item
+ if ( (mouse_y >= (env.halfHeight - 24))
+ && (mouse_y < env.halfHeight)
+ && (scroll > 1) ) {
+ --scroll;
+ need_draw = true;
+ }
+
+ // Down one item
+ if ( (mouse_y >= (env.halfHeight + 1))
+ && (mouse_y < (env.halfHeight + 25))
+ && (scroll < (env.numAvailable - btps)) ) {
+ ++scroll;
+ need_draw = true;
+ }
+
+ // Fast down
+ if ( (mouse_y >= (env.halfHeight + 25))
+ && (mouse_y < (env.halfHeight + 50))
+ && (scroll < (env.numAvailable)) ) {
+ scroll += btps / 2;
+ if (scroll >= env.numAvailable - btps)
+ scroll = env.numAvailable - btps;
+ need_draw = true;
+ }
+ }
+ if (itemindex < scroll)
+ itemindex = scroll;
+ else if ( itemindex > (scroll + btps) )
+ itemindex = scroll + btps - 1;
+ }
+
+ // Check mouse buttons for clicks
+ if ( ( (mouse_b & 1) || (mouse_b & 2) )
+ && (mouse_x >= (env.screenWidth - STUFF_BAR_WIDTH))
+ && (mouse_x < env.screenWidth) )
+ pressed = env.availableItems[itemindex];
+
+ // Only do the buying / selling when the mouse button is
+ // released. This way users can pull the mouse off the item
+ if ( (pressed > -1) && !((mouse_b & 1) || (mouse_b & 2)) ) {
+ if (pressed < WEAPONS) {
+ cost = weapon[pressed].cost;
+ amt = weapon[pressed].amt;
+ inInv = env.players[pl]->nm[pressed];
+ } else {
+ cost = item[pressed - WEAPONS].cost;
+ amt = item[pressed - WEAPONS].amt;
+ inInv = env.players[pl]->ni[pressed - WEAPONS];
+ }
+
+ if (key[KEY_LCONTROL] || key[KEY_RCONTROL]) {
+ cost *= 10;
+ amt *= 10;
+ }
+
+ // RMB sells items and takes precedence over LMB
+ if (lastMouse_b & 2) {
+ if ( (inInv + trolley[pressed]) >= amt) {
+ if (trolley[pressed] >= amt) {
+ money += cost;
+ trolley[pressed] -= amt;
+ need_draw = true;
+ } else if (env.sellpercent > 0.01) {
+ money += ROUNDu(cost * env.sellpercent);
+ trolley[pressed] -= amt;
+ need_draw = true;
+ }
+ }
+ } else if ( (money >= cost)
+ && ( (inInv + trolley[pressed])
+ < (MAX_ITEMS_IN_STOCK - amt)) ) {
+ if (trolley[pressed] <= -amt) {
+ if (env.sellpercent > 0.01) {
+ money -= ROUNDu(cost * env.sellpercent);
+ trolley[pressed] += amt;
+ need_draw = true;
+ }
+ } else {
+ money -= cost;
+ trolley[pressed] += amt;
+ need_draw = true;
+ if ( (inInv + trolley[pressed]) > MAX_ITEMS_IN_STOCK)
+ trolley[pressed] = MAX_ITEMS_IN_STOCK;
+ }
+ }
+ pressed = -1;
+ } // End of mouse buttons released with item selected
+ env.mouseclock++;
+ if (env.mouseclock > 5)
+ env.mouseclock = 0;
+ lastMouse_b = mouse_b;
+
+ // Sleep a bit if nothing happened
+ if (!done && !need_draw)
+ LINUX_SLEEP
+ } // End of input handling loop
+
+ // Update display if anything happened:
+ if (need_draw) {
+ need_draw = false;
+
+ // No hardware mouse while drawing
+ SHOW_MOUSE(nullptr)
+
+ global.make_update(env.halfWidth - 200, 0,
+ env.gfxData.stuff_bar[0]->w,
+ env.gfxData.stuff_bar[0]->h);
+
+ draw_sprite (global.canvas, env.gfxData.stuff_bar[0],
+ env.halfWidth - 200, 0);
+ textprintf_ex (global.canvas, font, env.halfWidth - 190, 0, BLACK,
+ -1, "%s %d: %s", env.ingame->Get_Line(10), pl + 1,
+ env.players[pl]->getName ());
+ textprintf_ex (global.canvas, font, env.halfWidth - 190, 14, BLACK,
+ -1, "%s: $%s", env.ingame->Get_Line(11),
+ Add_Comma(money));
+ snprintf (buf, 49, "%s: %d/%d", env.ingame->Get_Line(12),
+ env.rounds - global.currentround, env.rounds);
+ textout_ex (global.canvas, font, buf,
+ env.halfWidth + 170 - text_length(font, buf),
+ 0, BLACK, -1);
+ snprintf (buf, 49, "%s: %d", env.ingame->Get_Line(13),
+ env.players[pl]->score);
+ textout_ex (global.canvas, font, buf,
+ env.halfWidth + 155 - text_length(font, buf),
+ 14, BLACK, -1);
+
+ draw_weapon_list(env.players[pl], trolley, scroll_old, scroll,
+ hoverOver_old, hoverOver);
+
+ // Update non-OS mouse movements
+ SHOW_MOUSE(global.canvas)
+
+ global.do_updates ();
+ hoverOver_old = hoverOver;
+ scroll_old = scroll;
+ } // End of drawing
+ } // End of player shopping loop
+
+ // Now write back bought/sold items and remaining money
+ for (int tItem = 0; tItem < WEAPONS; tItem++)
+ env.players[pl]->nm[tItem] += trolley[tItem];
+ for (int tItem = WEAPONS; tItem < THINGS; tItem++)
+ env.players[pl]->ni[tItem - WEAPONS] += trolley[tItem];
+ env.players[pl]->money = money;
+ } // End of player handling
+
+ // Eventually give all players interest on their remaining money
+ if (!global.isCloseBtnPressed()) {
+ for (int32_t z = 0; z < env.numGamePlayers; z++) {
+ int32_t money = env.players[z]->money;
+ int32_t interest = 0;
+ double intPerc = .0;
+ int32_t intLevel = 0;
+ int32_t intSum = 0; // The summed up interest
+ DEBUG_LOG_FIN(env.players[z]->getName(),
+ "======================================================", 0)
+ DEBUG_LOG_FIN(env.players[z]->getName(),
+ "%2d.: %s enters the bank to get interest:", (z+1),
+ env.players[z]->getName())
+ DEBUG_LOG_FIN(env.players[z]->getName(),
+ " Starting Account: %10d", env.players[z]->money)
+ DEBUG_LOG_FIN(env.players[z]->getName(),
+ "------------------------------------------------------", 0)
+ while (money && (intLevel++ < 5)) {
+ // Enter next level
+ intPerc = (env.interest - 1.0) / intLevel;
+ interest = static_cast<double>(money) * intPerc;
+
+ // The limit is only applicable on the first four levels,
+ // in the fifth level interest is fully applied!
+ if ((interest > MAX_INTEREST_AMOUNT) && (intLevel < 5))
+ interest = MAX_INTEREST_AMOUNT;
+
+ // Now sum the interest up and substract the counted money!
+ intSum += interest;
+ money -= static_cast<double>(interest) / intPerc;
+
+ DEBUG_LOG_FIN(env.players[z]->getName(),
+ " Level %1d: %8d credits are rated,",
+ intLevel,
+ static_cast<int32_t>(interest / intPerc))
+ DEBUG_LOG_FIN(env.players[z]->getName(),
+ " Interest: %8d credits. (%5.2f%%)",
+ interest, intPerc * 100.)
+
+ // To get rid of (possible) rounding errors, add a security check:
+ if ( (money < (4 * intLevel)) || (interest < 1) )
+ money = 0; // With less there won't be any more interest anyway!
+
+ DEBUG_LOG_FIN(env.players[z]->getName(),
+ " Unrated : %8d credits left.", money)
+ }
+
+ // Now give them their money:
+ DEBUG_LOG_FIN(env.players[z]->getName(),
+ " Sum: %8d credits.", intSum)
+ DEBUG_LOG_FIN(env.players[z]->getName(),
+ "------------------------------------------------------", 0)
+ env.players[z]->money += intSum;
+ DEBUG_LOG_FIN(env.players[z]->getName(),
+ " Final Account : %10d", env.players[z]->money)
+ } // End of looping players
+ } // End of close button not pressed
+
+
+ // If the close button was pressed, the creator thread must end
+ // ASAP so we can get out of here.
+ if (global.isCloseBtnPressed())
+ lvl_creator->die_now();
+
+ // The LevelCreator, if not finished, can work alone, now:
+ if (!lvl_creator->is_finished())
+ lvl_creator->work_alone();
+
+ // Wait until the level creator is done
+ while (!lvl_creator->is_finished()) {
+ MSLEEP(20)
+ if ( lvl_creator->has_progress() ) {
+ // Hide custom mouse pointer
+ SHOW_MOUSE(nullptr)
+
+ lvl_creator->print_state();
+
+ // Draw custom mouse cursor
+ SHOW_MOUSE(global.canvas)
+
+ global.do_updates();
+ }
+ }
+
+ return !global.isCloseBtnPressed();
+}
+
+
+/*
+ * Calculate the potential damage for a given weapon.
+ * Recursively add the damage of sub-munitions.
+ */
+static int32_t calcPotentialDmg (int32_t weapNum)
+{
+ WEAPON *weap = &weapon[weapNum];
+ int32_t total = 0;
+
+ if ( (weap->submunition >= 0) && (weap->numSubmunitions > 0) )
+ total += calcPotentialDmg (weap->submunition)
+ * weap->numSubmunitions;
+ else
+ total += weap->damage;
+
+ return total;
+}
+
+
+// If configured to do so, this method divides team money to help out team mates
+static void divide_team_money()
+{
+ if (!env.divide_money)
+ return;
+
+ int32_t jediMoney = 0;
+ int32_t jediCount = 0;
+ int32_t sithMoney = 0;
+ int32_t sithCount = 0;
+ int32_t teamFee = 0;
+
+ for (int32_t z = 0; z < env.numGamePlayers; ++z) {
+ // Sum up team money:
+ if (env.players[z]->team == TEAM_JEDI) {
+ teamFee = static_cast<double>(env.players[z]->money) / 4.;
+ if (teamFee > MAX_TEAM_AMOUNT)
+ teamFee = MAX_TEAM_AMOUNT;
+ jediMoney += teamFee;
+ jediCount++;
+ } else if (env.players[z]->team == TEAM_SITH) {
+ teamFee = static_cast<double>(env.players[z]->money) / 4;
+ if (teamFee > MAX_TEAM_AMOUNT)
+ teamFee = MAX_TEAM_AMOUNT;
+ sithMoney += teamFee;
+ sithCount++;
+ }
+ // Note: The team Fee is not docked, yet, as it is not clear
+ // whether there is more than one team member.
+ }
+
+ DEBUG_LOG_FIN("Overview", "Jedi Count: %d - Sith Count: %d", jediCount, sithCount)
+
+ // Now apply the team money (if any):
+ if (jediCount > 1) {
+ DEBUG_LOG_FIN("Overview",
+ "The Jedi summed up a pool of %13d credits!",
+ jediMoney)
+ jediMoney = static_cast<double>(jediMoney) * .90 / jediCount;
+ DEBUG_LOG_FIN("Overview",
+ "Every Jedi will receive %10d credits out of the pool!",
+ jediMoney)
+ for (int32_t z = 0; z < env.numGamePlayers; ++z) {
+ if (TEAM_JEDI == env.players[z]->team) {
+ teamFee = static_cast<double>(env.players[z]->money) / 4;
+ if (teamFee > MAX_TEAM_AMOUNT)
+ teamFee = MAX_TEAM_AMOUNT;
+ env.players[z]->money -= teamFee;
+ env.players[z]->money += jediMoney;
+ }
+ }
+ }
+
+ if (sithCount > 1) {
+ DEBUG_LOG_FIN("Overview",
+ "The Sith summed up a pool of %13d credits!",
+ sithMoney)
+ sithMoney = static_cast<double>(sithMoney) * .90 / sithCount;
+ DEBUG_LOG_FIN("Overview",
+ "Every Sith will receive %10d credits out of the pool!",
+ sithMoney)
+ for (int32_t z = 0; z < env.numGamePlayers; ++z) {
+ if (TEAM_SITH == env.players[z]->team) {
+ teamFee = static_cast<double>(env.players[z]->money) / 4;
+ if (teamFee > MAX_TEAM_AMOUNT)
+ teamFee = MAX_TEAM_AMOUNT;
+ env.players[z]->money -= teamFee;
+ env.players[z]->money += sithMoney;
+ }
+ }
+ }
+}
+
+
+/// @brief dedicated function for AI shopping.
+void do_ai_shopping(PLAYER* player, int32_t maxBoost, int32_t maxScore)
+{
+ // Print player info and inventory
+#ifdef ATANKS_DEBUG_FINANCE
+ DEBUG_LOG_FIN(player->getName(),
+ "Starting to buy: (Defensiveness: %4.2lf)",
+ player->defensive)
+
+
+ DEBUG_LOG_FIN(player->getName(), " --- Inventory --- ", 0)
+ DEBUG_LOG_FIN(player->getName(), "-------------------", 0)
+ for (int32_t i = 1; i < WEAPONS; ++i) {
+ if (player->nm[i])
+ DEBUG_LOG_FIN(player->getName(), "% 4d x %s",
+ player->nm[i] / weapon[i].getDelayDiv(),
+ weapon[i].getName())
+ }
+ DEBUG_LOG_FIN(player->getName(), " - - - - - - - - - ", 0)
+ for (int32_t i = 1; i < ITEMS; ++i) {
+ if (player->ni[i])
+ DEBUG_LOG_FIN(player->getName(), "% 4d x %s",
+ player->ni[i], item[i].getName())
+ }
+ DEBUG_LOG_FIN(player->getName(), "-------------------", 0)
+
+ int32_t oldMoneyToSave = -1; // So the same message isn't repeated over and over again.
+#endif // ATANKS_DEBUG_FINANCE
+
+ player->updatePreferences(maxBoost, maxScore);
+
+ // money saving will be made possible when:
+ // 1. It's not the first three rounds
+ // 2. It's not the last 5 rounds
+ // and, dynamically:
+ // 3. We have at least 10 parachutes or no gravity
+ // 4. We have at least 2 damage dealing (not small missile) weapon
+ // per AI level.
+
+ // Check for a minimum of damage dealing weapons and parachutes,
+ // then buy until 'moneyToSave' is reached.
+ int32_t pressed = -1;
+ int32_t ai_level = static_cast<int32_t>(player->type);
+ int32_t buy_count = 0;
+ int32_t last_buy_idx = 0; // Used to "remember" where the AI was in its cart.
+ do {
+ int32_t moneyToSave = 0; // How much money will the player save?
+
+ // The AI does not save up money in the first three or last five rounds
+ if ( (global.currentround > 5)
+ && ((env.rounds - global.currentround) > 3) ) {
+ moneyToSave = player->getMoneyToSave(!buy_count);
+#ifdef ATANKS_DEBUG_FINANCE
+ if (oldMoneyToSave != moneyToSave) {
+ DEBUG_LOG_FIN(player->getName(),
+ "Maximum Money to save: %d (I have %d)",
+ moneyToSave, player->money)
+ oldMoneyToSave = moneyToSave;
+ }
+ } else
+ DEBUG_LOG_FIN(player->getName(),
+ "No money to save this round!", 0);
+#else
+ }
+#endif // ATANKS_DEBUG_FINANCE
+
+ int32_t numPara = player->ni[ITEM_PARACHUTE];
+ int32_t numDmgWeaps = 0;
+
+ for (int32_t i = 1; i < WEAPONS; ++i) {
+ // start from 1, as 0 is the small missile
+ if (weapon[i].damage > 0)
+ numDmgWeaps += player->nm[i] / weapon[i].getDelayDiv();
+ }
+
+ // Try to chose something to buy if enough money is there or either
+ // the number of parachutes or damage dealing weapons is too low.
+ if ( (player->money > moneyToSave)
+ || ( (numPara < ai_level) && (env.landSlideType > SLIDE_NONE) )
+ || (numDmgWeaps < (ai_level * 2)) )
+ pressed = player->chooseItemToBuy(maxBoost, last_buy_idx);
+ else
+ pressed = -1; // Forced to end.
+
+ DEBUG_LOG_FIN(player->getName(),
+ "I have %s%s%s%d credits left%s",
+ pressed > -1 ? "bought: " : "finished, with ",
+ pressed > -1
+ ? pressed < WEAPONS
+ ? weapon[pressed].getName()
+ : item[pressed - WEAPONS].getName()
+ : "",
+ pressed < 0 ? " " : " (",
+ player->money,
+ pressed < 0 ? "" : ")")
+ buy_count++;
+ } while ( (pressed != -1) && (buy_count < 1000) );
+
+ DEBUG_LOG_FIN(player->getName(),
+ "============================================", 0)
+}
+
+
+static void draw_shop(PLAYER *pl)
+{
+ global.make_update (0, 0, env.screenWidth, env.screenHeight);
+ global.lockLand();
+ SHOW_MOUSE(nullptr)
+ draw_simple_bg(false);
+
+ if (pl) {
+ draw_sprite (global.canvas, env.misc[DONE_IMAGE],
+ env.halfWidth - 100, env.screenHeight - 50);
+ draw_sprite (global.canvas, env.misc[FAST_UP_ARROW_IMAGE],
+ env.screenWidth - STUFF_BAR_WIDTH - 30, env.halfHeight - 50);
+ draw_sprite (global.canvas, env.misc[UP_ARROW_IMAGE],
+ env.screenWidth - STUFF_BAR_WIDTH - 30, env.halfHeight - 25);
+ draw_sprite (global.canvas, env.misc[DOWN_ARROW_IMAGE],
+ env.screenWidth - STUFF_BAR_WIDTH - 30, env.halfHeight);
+ draw_sprite (global.canvas, env.misc[FAST_DOWN_ARROW_IMAGE],
+ env.screenWidth - STUFF_BAR_WIDTH - 30, env.halfHeight + 25);
+ }
+
+ drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
+ global.current_drawing_mode = DRAW_MODE_TRANS;
+
+ if (pl) {
+ double left = env.halfWidth - 200; // short cut
+ int32_t right = env.screenWidth - 1; // another short cut.
+
+ for (int32_t z = 0; z < env.halfWidth - 200; z++) {
+ set_trans_blender (0, 0, 0, ROUNDu(static_cast<double>(z) / left * 240) + 15);
+ vline (global.canvas, z, 0, SHOP_BAR_HEIGHT, pl->color);
+ vline (global.canvas, right - z, 0, SHOP_BAR_HEIGHT, pl->color);
+ } // End of drawing player colour blending
+ } // End of having a player
+
+ solid_mode ();
+ global.current_drawing_mode = DRAW_MODE_SOLID;
+
+ if (pl) {
+ textout_ex(global.canvas, font, env.ingame->Get_Line(14), 20, 420, WHITE, -1);
+ textout_ex(global.canvas, font, env.ingame->Get_Line(15), 20, 450, WHITE, -1);
+ textout_ex(global.canvas, font, env.ingame->Get_Line(16), 20, 465, WHITE, -1);
+ }
+
+ global.unlockLand();
+ fi = 1;
+}
+
+
+void draw_simple_bg(bool drawImage)
+{
+ if (!env.drawBackground)
+ rectfill(global.canvas, 0, 0, env.screenWidth - 1, env.screenHeight - 1, BLACK);
+ else if ( drawImage && env.misc[17] )
+ stretch_blit(env.misc[17], global.canvas, 0, 0,
+ env.misc[17]->w, env.misc[17]->h, 0, 0,
+ env.screenWidth, env.screenHeight);
+ else
+ rectfill(global.canvas, 0, 0, env.screenWidth - 1, env.screenHeight - 1, DARK_GREEN);
+}
+
+
+static void draw_weapon_list(PLAYER* pl, int32_t* trolley,
+ int32_t scroll_old, int32_t scroll_new,
+ int32_t over_old, int32_t over_new)
+{
+ // Some pre-calculations and settings.
+ int32_t startX = env.screenWidth - STUFF_BAR_WIDTH;
+ int32_t halfBar = STUFF_BAR_HEIGHT / 2;
+ static int32_t qtyTxtLen = 0;
+ BITMAP* imgReleased = env.gfxData.stuff_bar[0];
+ BITMAP* imgPressed = env.gfxData.stuff_bar[1];
+ int32_t col_add = YELLOW; // Bought items
+ int32_t col_sub = makecol(176, 0, 0); // Sold items
+ static char buf_cost[50] = { 0 };
+ static char buf_amt[50] = { 0 };
+ bool full_redraw = (scroll_new != scroll_old);
+
+ memset(buf_cost, 0, sizeof(char) * 50);
+ memset(buf_amt, 0, sizeof(char) * 50);
+
+ if (0 == qtyTxtLen) {
+ qtyTxtLen = text_length(font, "Qty. in inventory: ddd");
+ }
+
+ // Erase top gap:
+ if (full_redraw) {
+ global.lockLand();
+ rectfill(global.canvas, startX, STUFF_BAR_HEIGHT - 5,
+ startX + env.gfxData.stuff_icon_base->w, STUFF_BAR_HEIGHT,
+ makecol(8, 110, 24));
+ global.unlockLand();
+ global.make_update(startX, STUFF_BAR_HEIGHT - 5,
+ env.gfxData.stuff_icon_base->w, 5);
+ }
+
+ // go through all items and draw them on the screen with
+ // the amount of items in the trolley
+ for (int32_t slot = 1; slot <= btps; ++slot) {
+ int32_t itemIdx = slot + scroll_new - 1;
+ int32_t itemNum = env.availableItems[itemIdx];
+ int32_t startY = slot * STUFF_BAR_HEIGHT;
+ const char* name = nullptr;
+ int32_t amt = 0;
+ int32_t d_div = 1;
+
+ // Only actually draw the slot, if it has changed:
+ if ( !full_redraw
+ && (over_old != itemNum)
+ && (over_new != itemNum) )
+ continue;
+
+ // Get text values:
+ if (itemNum < WEAPONS) {
+ d_div = weapon[itemNum].getDelayDiv();
+ name = weapon[itemNum].getName();
+ amt = pl->nm[itemNum] / d_div;
+ snprintf (buf_cost, 49, "$%s", Add_Comma( weapon[itemNum].cost ) );
+ snprintf (buf_amt, 49, "for %d", weapon[itemNum].amt / d_div);
+ } else {
+ name = item[itemNum - WEAPONS].getName();
+ amt = pl->ni[itemNum - WEAPONS];
+ snprintf (buf_cost, 49, "$%s",
+ Add_Comma( item[itemNum - WEAPONS].cost ) );
+ snprintf (buf_amt, 49, "for %d", item[itemNum - WEAPONS].amt);
+ }
+
+ global.lockLand();
+
+ // Draw the background sprites
+ draw_sprite(global.canvas,
+ (over_new == itemNum) ? imgPressed : imgReleased,
+ startX, startY);
+ draw_sprite(global.canvas, env.gfxData.stuff_icon_base, startX, startY);
+ draw_sprite(global.canvas, env.stock[itemNum], startX, startY - 5);
+ global.make_update(startX, startY, STUFF_BAR_WIDTH, STUFF_BAR_HEIGHT + 5);
+
+ // Draw the text:
+ textout_ex (global.canvas, font, name,
+ startX + 45, startY - 1, BLACK, -1);
+ textprintf_ex(global.canvas, font,
+ startX + 45, startY + halfBar - 4, BLACK, -1,
+ "%s: %d",
+ env.ingame->Get_Line(40), amt);
+ if (trolley[itemNum])
+ textprintf_ex(global.canvas, font, startX + 45 + qtyTxtLen,
+ startY + halfBar - 4,
+ trolley[itemNum] > 0 ? col_add : col_sub, -1,
+ "%+d", trolley[itemNum] / d_div);
+ textout_ex (global.canvas, font, buf_cost,
+ env.screenWidth - 45 - text_length(font, buf_cost),
+ startY - 1, BLACK, -1);
+ textout_ex (global.canvas, font, buf_amt,
+ env.screenWidth - 45 - text_length(font, buf_amt),
+ startY + halfBar - 4, BLACK, -1);
+ global.unlockLand();
+
+ // Break up if done:
+ // (This should not be triggered ever, as scroll is controlled by shop()
+ // to never be more than env.numAvailable-btps.)
+ if ( (itemIdx >= (env.numAvailable - 1)) && (slot < btps) )
+ slot = btps + 1;
+ }
+
+ fi = 1;
+}
+
+
+/** @brief Executes a fast and simple transition from global.canvas to the screen.
+**/
+void quickChange(bool clearerror)
+{
+ if (errorMessage) {
+ textout_ex (global.canvas, font, errorMessage, errorX, errorY,
+ makecol (255, 0, 0), -1);
+ if (clearerror)
+ errorMessage = nullptr;
+ }
+
+ blit (global.canvas, screen, 0, 0, 0, 0, env.screenWidth, env.screenHeight);
+}
+
diff --git a/src/shop.h b/src/shop.h
new file mode 100644
index 0000000..ab41c52
--- /dev/null
+++ b/src/shop.h
@@ -0,0 +1,12 @@
+#pragma once
+#ifndef ATANKS_SRC_SHOP_H_INCLUDED
+#define ATANKS_SRC_SHOP_H_INCLUDED
+
+class LevelCreator; // From gameloop.h
+
+// give people the chance to buy items
+bool shop(LevelCreator* lvl_creator);
+
+
+#endif // ATANKS_SRC_SHOP_H_INCLUDED
+
diff --git a/src/sky.cpp b/src/sky.cpp
index 685f68b..12499db 100644
--- a/src/sky.cpp
+++ b/src/sky.cpp
@@ -29,16 +29,29 @@ TODO
+ Add clouds?
*/
-#include "globaldata.h"
+#include "externs.h"
#include "main.h"
#include <vector>
#include "environment.h"
#include "sky.h"
#include "files.h"
+#include "gfxData.h"
+#include "gameloop.h"
-/* Here's a function that could move nicely out of atanks.cc :) */
-int gradientColorPoint (const gradient *grad, double length, double line);
+/*****************************************************************************
+Static temp sky bitmap for faster sky creation
+*****************************************************************************/
+static BITMAP* temp_sky = nullptr;
+
+
+/*****************************************************************************
+Static helper function prototypes
+*****************************************************************************/
+static double central_rand (double u);
+static int32_t clamped_int (int32_t m, int32_t a, int32_t z);
+static double coverage (double distance, double radius);
+static void draw_moons (LevelCreator* lcr, int32_t width, int32_t height);
/*============================================================================
@@ -47,66 +60,90 @@ struct moon
A simple data structure to store the parameters of a moon for easy passing.
============================================================================*/
struct moon
- {
- int radius;
- int x;
- int y;
- double lambda;
- int octaves;
- double smoothness;
- double xoffset;
- double yoffset;
- int col1;
- int col2;
- };
+{
+ BITMAP* bitmap;
+ int32_t col1;
+ int32_t col2;
+ double lambda;
+ int32_t octaves;
+ int32_t radius;
+ double smoothness;
+ int32_t x;
+ double xoffset;
+ int32_t y;
+ double yoffset;
+
+ // Simple ctor:
+ explicit moon(int32_t scrnw, int32_t scrnh) :
+ col1 (makecol(rand() % 255, rand() % 255, rand() % 255)),
+ col2 (makecol(rand() % 255, rand() % 255, rand() % 255)),
+ lambda (((rand() % 60) + 30) / 100.),
+ octaves ((rand() % 4) + 6),
+ radius (static_cast<int32_t>(central_rand(scrnw / 8) + .5)),
+ smoothness((rand() % 20) + 3),
+ x (rand() % scrnw),
+ xoffset (rand()),
+ y (rand() % scrnh),
+ yoffset (rand())
+ {
+ bitmap = create_bitmap (radius * 2, radius * 2);
+ }
+
+ // Simple dtor to get rid of the temp bitmap
+ ~moon()
+ {
+ if (bitmap)
+ destroy_bitmap(bitmap);
+ }
+};
+
/*############################################################################
ZBuffer
Acts a a simple, 1bpp zbuffer. For each pixel location, the ZBuffer can
-remember if something is (char *)"popping up" at that location.
+remember if something is "popping up" at that location.
############################################################################*/
class ZBuffer
- {
- private:
- // empty ctor, copy-ctor and assign operator are private, so the compiler won't create implicit ones!
- inline ZBuffer () { }
- inline ZBuffer (ZBuffer &sourceZBuf _UNUSED)_UNUSED;
- inline const ZBuffer& operator= (const ZBuffer &sourceZBuf) { return(sourceZBuf); }
-
- std::vector< bool > z;
- int shiftamt;
+{
+public:
+ // No copies:
+ ZBuffer() = delete;
+ ZBuffer& operator=(const ZBuffer&) = delete;
- public:
/*************************************************************************
ctor
- Construct a ZBuffer object capable of storing (char *)"popup" values for a
+ Construct a ZBuffer object capable of storing "popup" values for a
w by h grid. All cells in the ZBuffer start out lowered.
*************************************************************************/
- ZBuffer( int w, int h )
- {
- shiftamt = 0;
- while ( w )
- {
- w >>= 1;
- ++shiftamt;
- }
- z.resize( h << shiftamt );
- }
+ ZBuffer( int32_t w, int32_t h )
+ {
+ int32_t width = w;
+ while ( width ) {
+ width >>= 1;
+ ++shiftamt;
+ }
+ z.resize( (h << shiftamt) | w );
+ }
+
/*************************************************************************
test
- Returns true iff the cell at location (x,y) is raised. Behavior is
+ Returns true if the cell at location (x,y) is raised. Behaviour is
undefined if x does not fall in the range [0,w) or if y does not fall in
the range [0,h); w and h being the parameters to the ctor.
*************************************************************************/
- bool test( int x, int y ) const
- {
- return z[ (y<<shiftamt) | x ];
- }
+ bool test( int32_t x, int32_t y ) const
+ {
+ try {
+ return z.at((y << shiftamt) | x);
+ } catch (...) {
+ return false;
+ }
+ }
/*************************************************************************
@@ -115,11 +152,29 @@ class ZBuffer
Causes a cell in the ZBuffer to become raised. Follows the same
conditions on x and y as the test function does.
*************************************************************************/
- void set( int x, int y )
- {
- z[ (y<<shiftamt) | x ] = true ;
- }
- };
+ void set( int32_t x, int32_t y )
+ {
+ try {
+ z.at((y<<shiftamt) | x) = true ;
+ } catch (...) { /* nothing can be done here... */ }
+ }
+
+private:
+ std::vector< bool > z;
+ int32_t shiftamt = 0;
+};
+
+
+/*****************************************************************************
+Static function prototypes that need either moon or ZBuffer
+*****************************************************************************/
+static void draw_amoon (LevelCreator* lcr, const moon &mn,
+ int32_t x0, int32_t y0, int32_t x1, int32_t y1,
+ bool darkside, ZBuffer &zbuffer);
+static void paint_moonpix(int32_t x, int32_t y,
+ const moon &mn, double xval, double yval,
+ double blend);
+
/*****************************************************************************
@@ -130,10 +185,11 @@ are preferred.
Basic on a simple cubic function.
*****************************************************************************/
-static double central_rand( double u )
+static double central_rand(double u)
{
- const double x = ( (double)rand( ) / RAND_MAX ) - 0.5; // [-.5,+.5]
- return u * (0.5 - (x*x*x)*4.0) ;
+ const double x = static_cast<double>(rand()) / static_cast<double>(RAND_MAX)
+ - 0.5; // [-.5,+.5]
+ return u * (0.5 - (x*x*x)*4.0) ;
}
@@ -143,37 +199,9 @@ clamped_int
Clamps an integer value, m, into a range specified by [a,z]. Returns the
clamped value.
*****************************************************************************/
-static int clamped_int( int m, int a, int z )
+static int32_t clamped_int(int32_t m, int32_t a, int32_t z)
{
- return (m<a) ? a : ( (m>z) ? z : m );
-}
-
-
-/*****************************************************************************
-generate_moon
-
-Returns a moon structure with appropriately randomized variables.
-*****************************************************************************/
-static moon generate_moon( int scrnw, int scrnh )
-{
- moon m;
-
- m.smoothness = (rand () % 20) + 3;
- m.octaves = rand () % 4 + 6;
- m.lambda = (rand () % 60 + 30) * (1.00/100);
-
- //m.radius = (int)((rand () % 100 / 200.0) * (rand () % 100 / 200.0) * scrnw);
- m.radius = (int)central_rand( scrnw/8 ) ;
- m.x = rand () % scrnw;
- m.y = rand () % scrnh;
-
- m.xoffset = rand ();
- m.yoffset = rand ();
-
- m.col1 = makecol (rand () % 255, rand () % 255, rand () % 255);
- m.col2 = makecol (rand () % 255, rand () % 255, rand () % 255);
-
- return m;
+ return ( m < a ? a : ( m > z ? z : m ) );
}
@@ -181,24 +209,89 @@ static moon generate_moon( int scrnw, int scrnh )
coverage
Compute the percent coverage of a pixel by a sphere given the pixel's
-distance from the center and the sphere's radius.
+distance from the centre and the sphere's radius.
*****************************************************************************/
-static double coverage( double distance, double fradius )
+static double coverage(double distance, double radius)
{
- if ( distance <= fradius )
- return 1.0 ;
- return 1 - (distance - fradius);
+ if ( distance > radius )
+ return 1 - (distance - radius);
+ return 1. ;
}
/*****************************************************************************
-fract_clamp - unused
+draw_amoon
-Clamp a fraction down to the range [0.0,1.0]
+Renders a single moon onto a bitmap. Assumes transparent drawing is enabled.
+Obeys the given bounding box, which may be smaller than the moon itself.
+Uses the darkside parameter to decide which side of the moon should be
+dark. Obeys and updates the z-buffer.
+
+The current implementation of this function is begging for some
+simplifications. And again, what about those [xy]offset variables?
*****************************************************************************/
-/*static double fract_clamp( double x ) {
- return (x < 0.0) ? 0.0 : ( (x>1.0) ? 1.0 : x );
-}*/
+static void draw_amoon(LevelCreator* lcr, const moon& mn,
+ int32_t x0, int32_t y0, int32_t x1, int32_t y1,
+ bool darkside, ZBuffer& zbuffer)
+{
+ int32_t startX = std::min(x0, x1);
+ int32_t endX = std::max(x0, x1);
+ int32_t startY = std::min(y0, y1);
+ int32_t endY = std::max(y0, y1);
+
+ clear_to_color(mn.bitmap, BLACK);
+ blit(temp_sky, mn.bitmap, startX, startY, 0, 0, mn.radius * 2, mn.radius * 2);
+
+ for (int32_t y = startY; (y < endY) && lcr->can_work(); ++y ) {
+ bool hityet = false;
+
+ for (int32_t x = startX; (x < endX) && lcr->can_work(); ++x ) {
+ /* Occupied? */
+ if ( zbuffer.test(x,y) )
+ continue ;
+
+ /* Find distance from this moon */
+ int32_t xdist = mn.x - x;
+ int32_t ydist = mn.y - y;
+
+ /* Compute some other nice circle values */
+ const
+ double radius = mn.radius ;
+ double xval = static_cast<double>(xdist) / radius;
+ double yval = static_cast<double>(ydist) / radius;
+ double distance2 = (xdist * xdist) + (ydist * ydist);
+ double distance = std::sqrt(distance2);
+
+ /* A bound check -> are we in the circle? */
+ if ( distance > (radius + 1) ) {
+ if (hityet) // If we've already been inside at this y...
+ break; // then skip ahead to the next y
+ continue ; // Otherwise stay at this y, and skip to the next x
+ }
+
+ /* Edges use lighter blending */
+ const double edgeval = coverage(distance, radius);
+
+ /* Now, should we paint this side of the moon? */
+ if (xval && ( (xval < 0) == darkside) ) {
+ lcr->yield();
+ paint_moonpix(x - startX, y - startY, mn, fabs(xval), yval, edgeval);
+ }
+
+ /* Mark this pixel as occupied */
+ zbuffer.set(x,y);
+ hityet = true ;
+ }
+ }
+
+ // Put the moon on the sky bitmap:
+ global.lockLand();
+ drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
+ blit (mn.bitmap, temp_sky, 0, 0,
+ startX, startY, mn.radius * 2, mn.radius * 2);
+ drawing_mode(global.current_drawing_mode, NULL, 0, 0);
+ global.unlockLand();
+}
@@ -214,138 +307,58 @@ Parameters:
The moon to paint. The val's give the percentage along the moon.
Percentages must fall within [0,1].
* blend
- Controls how much (char *)"paint" is used. Must be in the range [0,1]. Higher
+ Controls how much "paint" is used. Must be in the range [0,1]. Higher
values cause stronger painting. Used for anti-aliasing.
*****************************************************************************/
-static void paint_moonpix( BITMAP* bmp, int x, int y,
- const moon& mn, double xval, double yval,
- double blend )
+static void paint_moonpix(int32_t x, int32_t y,
+ const moon &mn, double xval, double yval,
+ double blend)
{
- const double thetax = asin (xval) * 180 / PI;
- const double thetay = acos (yval) * 180 / PI;
-
- const double offset = (perlin2DPoint (1.0, mn.smoothness,
- mn.xoffset + mn.x + thetax,
- mn.yoffset + mn.y + thetay,
- mn.lambda, mn.octaves) + 1) / 2;
- const double percentVal = (perlin2DPoint (1.0, mn.smoothness,
- mn.xoffset + mn.x * 1000 + thetax,
- mn.yoffset + mn.y * 1000 + thetay,
- mn.lambda, mn.octaves) + 1) / 2;
-
- set_add_blender (0, 0, 0, (int)(blend * xval * percentVal * offset * 255));
-#ifdef THREADS
- drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
-#endif
- putpixel (bmp, x, y, mn.col1);
- set_add_blender (0, 0, 0, (int)(blend * xval * (1 - percentVal) * offset * 255));
- putpixel (bmp, x, y, mn.col2);
-#ifdef THREADS
- solid_mode();
-#endif
+ const double thetax = RAD2DEG(asin(xval));
+ const double thetay = RAD2DEG(acos(yval));
+ const double offset = (perlin2DPoint (1., mn.smoothness,
+ mn.xoffset + mn.x + thetax,
+ mn.yoffset + mn.y + thetay,
+ mn.lambda, mn.octaves) + 1.) / 2.;
+ const double percVal = (perlin2DPoint (1.0, mn.smoothness,
+ mn.xoffset + mn.x * 1000 + thetax,
+ mn.yoffset + mn.y * 1000 + thetay,
+ mn.lambda, mn.octaves) + 1) / 2;
+
+ set_add_blender (0, 0, 0, blend * xval * percVal * offset * 255);
+ drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
+ putpixel (mn.bitmap, x, y, mn.col1);
+ set_add_blender (0, 0, 0, blend * xval * (1. - percVal) * offset * 255);
+ putpixel (mn.bitmap, x, y, mn.col2);
+ drawing_mode (global.current_drawing_mode, NULL, 0, 0);
}
-
-/*****************************************************************************
-draw_amoon
-
-Renders a single moon onto a bitmap. Assumes transparent drawing is enabled.
-Abeys the given bounding box, which may be smaller than the moon itself.
-Uses the darkside parameter to decide which side of the moon should be
-dark. Obeys and updates the z-buffer.
-
-The current implementation of this function is beggins for some
-simplifications. And again, what about thoes [xy]offset variables?
-*****************************************************************************/
-static void draw_amoon( BITMAP* bmp,
- const moon& mn, int x0, int y0, int x1, int y1,
- bool darkside, ZBuffer& zbuffer )
-{
- for ( int y = y0; y != y1; ++y )
- {
- bool hityet = false;
-
- for ( int x = x0; x != x1; ++x )
- {
- /* Occupied? */
- if ( zbuffer.test(x,y) )
- continue ;
-
- /* Find distance from this moon */
- int xdist = mn.x - x;
- int ydist = mn.y - y;
-
- /* Compute some other nice circle values */
- const double fradius = (double) mn.radius ;
- double xval = (double)xdist / fradius;
- double yval = (double)ydist / fradius;
- double distance2 = (xdist * xdist) + (ydist * ydist);
- double distance = sqrt (distance2);
-
- /* A bound check -> are we in the circle? */
- if ( distance > fradius + 1 )
- {
- if ( hityet ) /* If we've already been inside at this y... */
- break; /* then skip ahead to the next y */
- else /* Otherwise... */
- continue ; /* Stay at this y, and skip to the next x */
- }
-
- /* Edges use lighter blending */
- const double edgeval = coverage( distance, fradius );
-
- /* Now, should we paint this side of the moon? */
- if ( xval && ((xval<0) == darkside) )
- {
- paint_moonpix(
- bmp, x, y,
- mn, fabs(xval), yval,
- //fract_clamp( fabs(xval) ),
- //fract_clamp( fabs(yval) ),
- edgeval ) ;
- }
-
- /* Mark this pixel as occupied */
- zbuffer.set(x,y);
- hityet = true ;
- }
- }
-}
-
-
-
/*****************************************************************************
draw_moons
Renders a set of moons over a given bitmap. The bitmap to draw of and the
appropriate dimensions must be given.
*****************************************************************************/
-void draw_moons (BITMAP* bmp, int width, int height)
+static void draw_moons (LevelCreator* lcr, int32_t width, int32_t height)
{
- const bool darkside = rand() > (RAND_MAX/2+1) ;
- ZBuffer zbuffer( width, height ) ;
-
- drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
- for ( int numMoons = (int)central_rand( 14.0 );
- numMoons; --numMoons )
- {
- int x0, y0, x1, y1;
-
- /* Make up a moon */
- const moon mn = generate_moon( width, height );
-
- /* Where is it? */
- x0 = clamped_int( mn.x - mn.radius -1, 0, width ) ;
- y0 = clamped_int( mn.y - mn.radius -1, 0, height ) ;
- x1 = clamped_int( mn.x + mn.radius +1, 0, width ) ;
- y1 = clamped_int( mn.y + mn.radius +1, 0, height ) ;
-
- /* Draw it */
- draw_amoon( bmp, mn, x0, y0, x1, y1, darkside, zbuffer );
- }
- solid_mode ();
+ const bool darkside = rand() > (RAND_MAX / 2 + 1);
+ ZBuffer zbuffer( width, height ) ;
+
+ for (int32_t numMoons = central_rand( 14.0 ); numMoons; --numMoons) {
+ /* Make up a moon */
+ const moon mn(width, height);
+
+ /* Where is it? */
+ int32_t x0 = clamped_int(mn.x - mn.radius, 0, width );
+ int32_t y0 = clamped_int(mn.y - mn.radius, 0, height);
+ int32_t x1 = clamped_int(mn.x + mn.radius, 0, width );
+ int32_t y1 = clamped_int(mn.y + mn.radius, 0, height);
+
+ /* Draw it */
+ draw_amoon(lcr, mn, x0, y0, x1, y1, darkside, zbuffer);
+ }
}
@@ -355,83 +368,60 @@ generate_sky
Given some input parameters, renders a sky (with moons) onto a bitmap.
*****************************************************************************/
-void generate_sky (GLOBALDATA *global, BITMAP* bmp, const gradient* grad, int flags )
-{
- double messiness = (rand () % 100 / 1000.0 + 0.05);
- const int xoffset = rand( ); /* For perlin, random starting x */
- const int yoffset = rand( ); /* For perlin, random starting y */
-
- for (int x = 0; x < global->screenWidth; x++)
- {
- for (int y = 0; y < global->screenHeight - MENUHEIGHT; y++)
- {
- double offset = 0;
-
- if ( flags & GENSKY_DETAILED )
- offset += perlin2DPoint (1.0, 200, xoffset + x, yoffset + y, 0.3, 6) * ((global->screenHeight - MENUHEIGHT) * messiness);
-
- if ( flags & GENSKY_DITHERGRAD )
- offset += rand () % 10 - 5;
-
- while (y + offset < 0)
- offset /= 2;
- while (y + offset + 1 > (global->screenHeight - MENUHEIGHT))
- offset /= 2;
-
- #ifdef THREADS
- solid_mode();
- #endif
- putpixel (bmp, x, y,
- gradientColorPoint (grad, global->screenHeight - MENUHEIGHT, y + offset));
- #ifdef THREADS
- drawing_mode(global->env->current_drawing_mode, NULL, 0, 0);
- #endif
-
- }
- }
- draw_moons (bmp, global->screenWidth, global->screenHeight);
-}
-
-
-
-// This function should be a seperate thread which constantly generates
-// skies in the background so as to not to hang the game after the
-// buying screen.
-void *Generate_Sky_In_Background(void *new_env)
+void generate_sky(LevelCreator* lcr, const gradient* grad, int32_t flags )
{
- ENVIRONMENT *env = (ENVIRONMENT *) new_env;
- GLOBALDATA *my_global = env->Get_Globaldata();
- BITMAP *sky_in_progress = NULL;
-
- // do this constantly
- while ( my_global->get_command() != GLOBAL_COMMAND_QUIT )
- {
- // create a bitmap in waiting, if none exists
- if (! env->get_waiting_sky())
- {
- sky_in_progress = create_bitmap( my_global->screenWidth, my_global->screenHeight - MENUHEIGHT);
- generate_sky (my_global, sky_in_progress, env->my_sky_gradients[my_global->cursky],
- (my_global->ditherGradients ? GENSKY_DITHERGRAD : 0 ) |
- (my_global->detailedSky ? GENSKY_DETAILED : 0 ) );
-
- #ifdef THREADS
- env->lock(env->waiting_sky_lock);
- #endif
- env->waiting_sky = sky_in_progress;
- #ifdef THREADS
- env->unlock(env->waiting_sky_lock);
- #endif
- sky_in_progress = NULL;
- }
-
- LINUX_SLEEP;
- }
-
- #ifdef THREADS
- pthread_exit(NULL);
- return NULL; // we never hit this, but it keeps the compiler from complaining
- #else
- return NULL;
- #endif
+ double messiness = (static_cast<double>(rand () % 100) / 1000.0 + 0.05);
+ const int xoffset = rand( ); // For perlin, random starting x
+ const int yoffset = rand( ); // For perlin, random starting y
+
+ temp_sky = create_bitmap( env.sky->w, env.sky->h );
+ clear_to_color(temp_sky, BLACK);
+ clear_to_color(env.sky, BLACK);
+
+ for (int32_t x = 0
+ ; (!lcr || lcr->can_work()) && (x < env.screenWidth)
+ ; ++x) {
+ for (int32_t y = 0
+ ; (!lcr || lcr->can_work()) && (y < (env.screenHeight - MENUHEIGHT))
+ ; ++y) {
+
+ lcr->yield();
+
+ double offset = 0;
+
+ if ( flags & GENSKY_DETAILED )
+ offset += perlin2DPoint(1., 200, xoffset + x, yoffset + y, .3, 6)
+ * (static_cast<double>(env.screenHeight - MENUHEIGHT)
+ * messiness);
+
+ if ( flags & GENSKY_DITHERGRAD )
+ offset += (rand () % 10) - 5;
+
+ while ( ( (y + offset) < 0)
+ || ( (y + offset + 1) > (env.screenHeight - MENUHEIGHT) ) )
+ offset /= 2;
+
+ global.lockLand();
+ solid_mode();
+ putpixel (temp_sky, x, y,
+ gradientColorPoint(grad, env.screenHeight - MENUHEIGHT,
+ y + offset));
+ drawing_mode(global.current_drawing_mode, NULL, 0, 0);
+ global.unlockLand();
+ }
+ }
+ draw_moons (lcr, env.screenWidth, env.screenHeight - MENUHEIGHT);
+
+ // Put temp sky onto the real bitmap:
+ global.lockLand();
+ solid_mode();
+ blit(temp_sky, env.sky, 0, 0, 0, 0,
+ env.sky->w, env.sky->h);
+ global.unlockLand();
+
+ // clean up
+ if (temp_sky)
+ destroy_bitmap(temp_sky);
+ temp_sky = nullptr;
}
diff --git a/src/sky.h b/src/sky.h
index 53d0280..c432616 100644
--- a/src/sky.h
+++ b/src/sky.h
@@ -1,189 +1,185 @@
#ifndef SKY_HEADER_FILE__
#define SKY_HEADER_FILE__
-#ifdef THREADS
-#include <pthread.h>
-#endif
-
-
+#include "main.h"
+#define SKIES 8
+// The first SKIES are regular, the second are crispy.
// Sky gradients, first line is top of screen
const gradient sky_gradient1[] =
{
- {{255,255,255,0}, 0.0},
- {{100,100,100,0}, 0.1},
- {{ 80,100,100,0}, 0.5},
- {{0,0,0,0}, -1}
+ {{255,255,255,0}, 0.0},
+ {{100,100,100,0}, 0.1},
+ {{ 80,100,100,0}, 0.5},
+ {{0,0,0,0}, -1}
};
const gradient sky_gradient2[] =
{
- {{ 0, 0, 40,0}, 0.0},
- {{ 40, 40,100,0}, 0.5},
- {{ 80,80,100,0}, 0.75},
- {{0,0,0,0}, -1}
+ {{ 0, 0, 40,0}, 0.0},
+ {{ 40, 40,100,0}, 0.5},
+ {{ 80,80,100,0}, 0.75},
+ {{0,0,0,0}, -1}
};
const gradient sky_gradient3[] =
{
- {{240, 0, 40,0}, 0.0},
- {{140, 40,100,0}, 0.15},
- {{ 80, 80,100,0}, 0.75},
- {{0,0,0,0}, -1}
+ {{240, 0, 40,0}, 0.0},
+ {{140, 40,100,0}, 0.15},
+ {{ 80, 80,100,0}, 0.75},
+ {{0,0,0,0}, -1}
};
const gradient sky_gradient4[] =
{
- {{ 40, 40, 40,0}, 0.0},
- {{100, 40,100,0}, 0.2},
- {{ 80, 80,100,0}, 0.75},
- {{0,0,0,0}, -1}
+ {{ 40, 40, 40,0}, 0.0},
+ {{100, 40,100,0}, 0.2},
+ {{ 80, 80,100,0}, 0.75},
+ {{0,0,0,0}, -1}
};
const gradient sky_gradient5[] =
{
- {{ 0, 90, 40,0}, 0.0},
- {{ 0,120,100,0}, 0.2},
- {{ 40,200,100,0}, 0.75},
- {{0,0,0,0}, -1}
+ {{ 0, 90, 40,0}, 0.0},
+ {{ 0,120,100,0}, 0.2},
+ {{ 40,200,100,0}, 0.75},
+ {{0,0,0,0}, -1}
};
// Sunset
const gradient sky_gradient6[] =
{
- {{ 70,240,240,0}, 0.0},
- {{ 70,200,200,0}, 0.3},
- {{ 70,200,160,0}, 0.35},
- {{255,200, 70,0}, 0.6},
- {{255,255,128,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 70,240,240,0}, 0.0},
+ {{ 70,200,200,0}, 0.3},
+ {{ 70,200,160,0}, 0.35},
+ {{255,200, 70,0}, 0.6},
+ {{255,255,128,0}, 1.0},
+ {{0,0,0,0}, -1}
};
// Burning sky
const gradient sky_gradient7[] =
{
- {{ 20, 20, 20,0}, 0.0},
- {{255,200, 0,0}, 0.08},
- {{255,255, 0,0}, 0.13},
- {{ 20, 20, 20,0}, 0.16},
- {{255,200, 0,0}, 0.5},
- {{255,255, 0,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 20, 20, 20,0}, 0.0},
+ {{255,200, 0,0}, 0.08},
+ {{255,255, 0,0}, 0.13},
+ {{ 20, 20, 20,0}, 0.16},
+ {{255,200, 0,0}, 0.5},
+ {{255,255, 0,0}, 1.0},
+ {{0,0,0,0}, -1}
};
// Burning landscape, black skies
const gradient sky_gradient8[] =
{
- {{ 0, 0, 0,0}, 0.0},
- {{100, 0, 0,0}, 0.4},
- {{255,255,255,0}, 0.8},
- {{0,0,0,0}, -1}
+ {{ 0, 0, 0,0}, 0.0},
+ {{100, 0, 0,0}, 0.4},
+ {{255,255,255,0}, 0.8},
+ {{0,0,0,0}, -1}
};
// Sky gradients, first line is top of screen
// dark blue to darker blue
const gradient sky_gradient9[] =
{
- {{ 90, 90,255,0}, 0.0},
- {{ 60, 60,200,0}, 0.3},
- {{ 30, 30,150,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 90, 90,255,0}, 0.0},
+ {{ 60, 60,200,0}, 0.3},
+ {{ 30, 30,150,0}, 1.0},
+ {{0,0,0,0}, -1}
};
// dark blue to blue-grey
const gradient sky_gradient10[] =
{
- {{110,110,255,0}, 0.0},
- {{150,150,255,0}, 0.3},
- {{200,200,255,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{110,110,255,0}, 0.0},
+ {{150,150,255,0}, 0.3},
+ {{200,200,255,0}, 1.0},
+ {{0,0,0,0}, -1}
};
// white to grey-blue to dark blue
const gradient sky_gradient11[] =
{
- {{255,255,255,0}, 0.0},
- {{200,200,255,0}, 0.3},
- {{ 80, 80,180,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{255,255,255,0}, 0.0},
+ {{200,200,255,0}, 0.3},
+ {{ 80, 80,180,0}, 1.0},
+ {{0,0,0,0}, -1}
};
// simple purple: dark to light
const gradient sky_gradient12[] =
{
- {{133, 33,133,0}, 0.0},
- {{220,120,220,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{133, 33,133,0}, 0.0},
+ {{220,120,220,0}, 1.0},
+ {{0,0,0,0}, -1}
};
// night sky: black to dark purple
const gradient sky_gradient13[] =
{
- {{ 0, 0, 0,0}, 0.0},
- {{ 50, 0, 50,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 0, 0, 0,0}, 0.0},
+ {{ 50, 0, 50,0}, 1.0},
+ {{0,0,0,0}, -1}
};
// Sunset
const gradient sky_gradient14[] =
{
- {{ 0, 0, 0,0}, 0.0},
- {{ 50, 0, 50,0}, 0.1},
- {{ 90, 20, 0,0}, 0.3},
- {{180, 50, 0,0}, 0.7},
- {{255,150,150,0}, 0.9},
- {{255,255,100,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 0, 0, 0,0}, 0.0},
+ {{ 50, 0, 50,0}, 0.1},
+ {{ 90, 20, 0,0}, 0.3},
+ {{180, 50, 0,0}, 0.7},
+ {{255,150,150,0}, 0.9},
+ {{255,255,100,0}, 1.0},
+ {{0,0,0,0}, -1}
};
// Burning sky
const gradient sky_gradient15[] =
{
- {{185, 60, 60,0}, 0.0},
- {{240,110,110,0}, 0.5},
- {{255,110,110,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{185, 60, 60,0}, 0.0},
+ {{240,110,110,0}, 0.5},
+ {{255,110,110,0}, 1.0},
+ {{0,0,0,0}, -1}
};
// Burning landscape, black skies
const gradient sky_gradient16[] =
{
- {{ 0, 0, 0,0}, 0.0},
- {{170, 50, 50,0}, 0.5},
- {{220,110,110,0}, 1.0},
- {{0,0,0,0}, -1}
+ {{ 0, 0, 0,0}, 0.0},
+ {{170, 50, 50,0}, 0.5},
+ {{220,110,110,0}, 1.0},
+ {{0,0,0,0}, -1}
};
const gradient * const sky_gradients[] =
{
- sky_gradient1,
- sky_gradient2,
- sky_gradient3,
- sky_gradient4,
- sky_gradient5,
- sky_gradient6,
- sky_gradient7,
- sky_gradient8,
- sky_gradient9,
- sky_gradient10,
- sky_gradient11,
- sky_gradient12,
- sky_gradient13,
- sky_gradient14,
- sky_gradient15,
- sky_gradient16
+ sky_gradient1,
+ sky_gradient2,
+ sky_gradient3,
+ sky_gradient4,
+ sky_gradient5,
+ sky_gradient6,
+ sky_gradient7,
+ sky_gradient8,
+ sky_gradient9,
+ sky_gradient10,
+ sky_gradient11,
+ sky_gradient12,
+ sky_gradient13,
+ sky_gradient14,
+ sky_gradient15,
+ sky_gradient16
};
+#define GENSKY_DETAILED 1
+#define GENSKY_DITHERGRAD 2
-void draw_moons (BITMAP* bmp, int width, int height);
-
-void *Generate_Sky_In_Background(void *new_env);
-
+class LevelCreator;
+void generate_sky(LevelCreator* lcr, const gradient* grad, int32_t flags);
#endif
-
-
diff --git a/src/sound.cpp b/src/sound.cpp
new file mode 100644
index 0000000..e039d33
--- /dev/null
+++ b/src/sound.cpp
@@ -0,0 +1,174 @@
+#include "sound.h"
+
+// max volume factor: means that the interval 0% -> 100% is split in 5
+int32_t MAX_VOLUME_FACTOR = 5;
+
+// General helper that unifies the playing
+static void play_sound (eSounds sound, int32_t x, int32_t vol, int32_t freq);
+
+
+/** @brief play a weapon or item fire sample according to @a type, panned using @a x.
+ *
+ * This just plays what weapon[]/item[] globals hold for a sound.
+ *
+ * @param[in] type The weapon or item type.
+ * @param[in] x The x-coordinate where the sound is played.
+ * @param[in] vol The volume (0 - 255)
+ * @param[in] freq Frequency, 1000 is normal, 500 is half, 2000 is double and so on.
+**/
+void play_fire_sound(int32_t type, int32_t x, int32_t vol, int32_t freq)
+{
+ int32_t sndNum = -1;
+
+ if (type >= WEAPONS) {
+ if (item[type - WEAPONS].sound > -1)
+ sndNum = item[type - WEAPONS].sound;
+ } else {
+ if (weapon[type].sound > -1)
+ sndNum = weapon[type].sound;
+ }
+
+ if ( (sndNum > -1) && (sndNum < SND_COUNT) )
+ play_sound(static_cast<eSounds>(sndNum), x, vol, freq);
+}
+
+
+/** @brief play a weapon or item explosion sample according to @a type, panned using @a x.
+ *
+ * This plays the sound listed in weapon[]/item[] globals unless special rules
+ * apply.
+ *
+ * @param[in] type The weapon or item type.
+ * @param[in] x The x-coordinate where the sound is played.
+ * @param[in] vol The volume (0 - 255)
+ * @param[in] freq Frequency, 1000 is normal, 500 is half, 2000 is double and so on.
+**/
+void play_explosion_sound(int32_t type, int32_t x, int32_t vol, int32_t freq)
+{
+ int32_t sndNum = -1;
+
+ if (SHAPED_CHARGE == type)
+ sndNum = SND_EXPL_SHAPED_CHARGE;
+ else if (WIDE_BOY == type)
+ sndNum = SND_EXPL_WIDE_BOY;
+ else if (CUTTER == type)
+ sndNum = SND_EXPL_CUTTER;
+ else if ( (SML_NAPALM <= type) && (LRG_NAPALM >= type)) {
+ sndNum = SND_EXPL_NAPALM;
+ freq += 333 * (LRG_NAPALM - type);
+ } else if (NAPALM_JELLY == type) {
+ sndNum = SND_EXPL_NAPALM_BURN;
+ freq += (rand() % 200) - 100;
+ vol -= rand() % 64;
+ } else if (PERCENT_BOMB == type)
+ sndNum = SND_EXPL_PER_CENT_BOMB;
+ else if (REDUCER == type)
+ sndNum = SND_EXPL_REDUCER;
+ else if ( ((DIRT_BALL <= type) && (SMALL_DIRT_SPREAD >= type))
+ || ((RIOT_CHARGE <= type) && (RIOT_BLAST >= type)) )
+ sndNum = SND_EXPL_DIRT_BALL_BOMB;
+ else {
+ // Default handling
+ if (type >= WEAPONS)
+ sndNum = item[type - WEAPONS].sound + SND_EXPL_MISS_SML;
+ else
+ sndNum = weapon[type].sound + SND_EXPL_MISS_SML;
+ }
+
+ if ( (sndNum > -1) && (sndNum < SND_COUNT) )
+ play_sound(static_cast<eSounds>(sndNum), x, vol, freq);
+}
+
+
+/** @brief plays the currently set background music modified by set volume factor
+**/
+void play_music()
+{
+ if (env.loadBackgroundMusic())
+ play_sound(SND_BG_MUSIC, 128, 255, 1000);
+}
+
+
+/** @brief play a natural sample according to @a type, panned using @a x.
+ *
+ * @param[in] type The natural type.
+ * @param[in] x The x-coordinate where the sound is played.
+ * @param[in] vol The volume (0 - 255)
+ * @param[in] freq Frequency, 1000 is normal, 500 is half, 2000 is double and so on.
+**/
+
+void play_natural_sound(int32_t type, int32_t x, int32_t vol, int32_t freq)
+{
+ int32_t sndNum = -1;
+
+ if (SML_METEOR == type) {
+ sndNum = naturals[SML_METEOR - WEAPONS].sound;
+ vol -= rand() % 128;
+ freq += 100 + (rand() % 100);
+ } else if (MED_METEOR == type) {
+ sndNum = naturals[MED_METEOR - WEAPONS].sound;
+ vol -= rand() % 64;
+ freq += rand() % 100;
+ } else if (LRG_METEOR == type) {
+ sndNum = naturals[LRG_METEOR - WEAPONS].sound;
+ vol -= rand() % 64;
+ freq += rand() % 250;
+ } else if (SML_LIGHTNING == type) {
+ sndNum = naturals[SML_LIGHTNING - WEAPONS].sound;
+ vol -= rand() % 128;
+ freq += 100 + (rand() % 100);
+ } else if (MED_LIGHTNING == type) {
+ sndNum = naturals[MED_LIGHTNING - WEAPONS].sound;
+ vol -= rand() % 64;
+ freq += rand() % 100;
+ } else if (LRG_LIGHTNING == type) {
+ sndNum = naturals[LRG_LIGHTNING - WEAPONS].sound;
+ vol -= rand() % 64;
+ freq += rand() % 250;
+ } else if (DIRT_FRAGMENT == type) {
+ sndNum = SND_NATU_DIRT_FALL;
+ vol -= rand() % 64;
+ // freq is manipulated by the call
+ }
+
+ if ( (sndNum > -1) && (sndNum < SND_COUNT)
+ && ( (SND_NATU_DIRT_FALL != sndNum)
+ || (global.used_voices < (env.voices - 8))) )
+ play_sound(static_cast<eSounds>(sndNum), x, vol, freq);
+}
+
+
+/** @brief play an interface sample according to @a sound.
+ *
+ * @param[in] sound The sound to play.
+**/
+
+void play_interface_sound(eSounds sound)
+{
+ if (SND_INTE_BUTTON_CLICK == sound)
+ play_sound(sound, env.halfWidth, 128, 1000);
+}
+
+
+// Global helpers implementation
+static void play_sound(eSounds sound, int32_t x, int32_t vol, int32_t freq)
+{
+ if (global.used_voices < env.voices) {
+ int32_t xVol = (vol * env.volume_factor) / MAX_VOLUME_FACTOR;
+
+ if ( (sound < SND_BG_MUSIC)
+ && env.sounds[sound]
+ && (vol > 0)
+ && (freq > 0) ) {
+ play_sample(env.sounds[sound],
+ xVol,
+ x <= 0 ? 31
+ : x >= env.screenWidth ? 192
+ : 31 + (x * 191 / env.screenWidth),
+ freq, false);
+ } else if (SND_BG_MUSIC == sound)
+ play_sample(env.background_music, xVol, x, freq, true);
+
+ ++global.used_voices;
+ }
+}
diff --git a/src/imagedefs.h b/src/sound.h
similarity index 58%
rename from src/imagedefs.h
rename to src/sound.h
index f09fa5c..7dc3d00 100644
--- a/src/imagedefs.h
+++ b/src/sound.h
@@ -1,9 +1,9 @@
-#ifndef IMAGEDEFS_DEFINE
-#define IMAGEDEFS_DEFINE
+#pragma once
+#ifndef ATANKS_SRC_SOUNDS_H_INCLUDED
+#define ATANKS_SRC_SOUNDS_H_INCLUDED
/*
* atanks - obliterate each other with oversize weapons
- * Copyright (C) 2003 Thomas Hudson
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -12,18 +12,26 @@
*
* 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
+ * 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
+ * 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.
- * */
+ *
+ */
+
+#include "externs.h"
+
+/** @file sound.h
+ * @brief declare centralized functions to play sounds.
+**/
+
+void play_explosion_sound(int32_t type, int32_t x, int32_t vol, int32_t f_off);
+void play_fire_sound (int32_t type, int32_t x, int32_t vol, int32_t f_off);
+void play_music ();
+void play_natural_sound (int32_t type, int32_t x, int32_t vol, int32_t f_off);
+void play_interface_sound(eSounds sound);
-#define DONE_IMAGE 11
-#define FAST_UP_ARROW_IMAGE 12
-#define UP_ARROW_IMAGE 13
-#define DOWN_ARROW_IMAGE 14
-#define FAST_DOWN_ARROW_IMAGE 15
+#endif // ATANKS_SRC_SOUNDS_H_INCLUDED
-#endif // IMAGEDEFS_DEFINE
diff --git a/src/tank.cpp b/src/tank.cpp
index 28b10f6..1f29149 100644
--- a/src/tank.cpp
+++ b/src/tank.cpp
@@ -18,8 +18,6 @@
* */
-#include "environment.h"
-#include "globaldata.h"
#include "floattext.h"
#include "explosion.h"
#include "teleport.h"
@@ -27,1409 +25,1706 @@
#include "player.h"
#include "beam.h"
#include "tank.h"
+#include "sound.h"
+#include <cassert> // For some checks if _DEBUG is defined.
+#include <mutex> // Needed for lock_guard
-/*
-The deconstructor should be split into two pieces. One function for
-destroying a tank and another strictly for cleaning up a tank's memory
-whether it was destoryed or not.
--- Jesse
-*/
-TANK::~TANK ()
+TANK::TANK () :
+ PHYSICAL_OBJECT(false),
+ healthText(nullptr, -1, -1, 0., 0., WHITE, CENTRE, TS_NO_SWAY, -1),
+ nameText( nullptr, -1, -1, 0., 0., WHITE, CENTRE, TS_NO_SWAY, -1),
+ shieldText(nullptr, -1, -1, 0., 0., TURQUOISE, CENTRE, TS_NO_SWAY, -1)
{
-/*
- #ifdef NETWORK
- if (player) // we should always have a player, but better safe than sorry
- {
- int player_index = 0;
- bool found = false;
- char buffer[64];
- while ( (player_index < _global->numPlayers) && (!found) )
- {
- // get the player index
- if ( ( _global->players[player_index]->tank ) && (_global->players[player_index]->tank == this) )
- found = true;
- else
- player_index++;
- }
- if (found) // we should have found a match and now we send it to all clients
- {
- sprintf(buffer, "REMOVETANK %d", player_index);
- _global->Send_To_Clients(buffer);
- }
- }
- #endif
- if (_global && player)
- {
- int random_item;
- if (_global->violent_death)
- {
- random_item = rand() % VIOLENT_CHANCE;
- if ( ( random_item > _global->violent_death ) && (random_item <= VIOLENT_DEATH_HEAVY) )
- random_item = (int) _global->violent_death;
-
- switch (random_item)
- {
- case VIOLENT_DEATH_LIGHT:
- player->ni[ITEM_VENGEANCE] += 1;
- break;
- case VIOLENT_DEATH_MEDIUM:
- player->ni[ITEM_DYING_WRATH] += 1;
- break;
- case VIOLENT_DEATH_HEAVY:
- player->ni[ITEM_FATAL_FURY] += 1;
- break;
- } //end of switch
- }
-
- if (player->ni[ITEM_FATAL_FURY] > 0)
- {
- int numLaunch = (int)item[ITEM_FATAL_FURY].vals[SELFD_NUMBER];
- cw = (int)item[ITEM_FATAL_FURY].vals[SELFD_TYPE];
- player->ni[ITEM_FATAL_FURY]--;
- player->nm[cw] += numLaunch;
- for (int count = numLaunch; count > 0; count--)
- {
- a = rand () % 180 + 90;
- p = rand () % (MAX_POWER / 2);
- activateCurrentSelection ();
- }
- }
- else if (player->ni[ITEM_DYING_WRATH] > 0)
- {
- int numLaunch = (int)item[ITEM_DYING_WRATH].vals[SELFD_NUMBER];
- cw = (int)item[ITEM_DYING_WRATH].vals[SELFD_TYPE];
- player->ni[ITEM_DYING_WRATH]--;
- player->nm[cw] += numLaunch;
- for (int count = numLaunch; count > 0; count--)
- {
- a = rand () % 180 + 90;
- p = rand () % (MAX_POWER / 2);
- activateCurrentSelection ();
- }
- }
- else if (player->ni[ITEM_VENGEANCE] > 0)
- {
- int numLaunch = (int)item[ITEM_VENGEANCE].vals[SELFD_NUMBER];
- cw = (int)item[ITEM_VENGEANCE].vals[SELFD_TYPE];
- player->ni[ITEM_VENGEANCE]--;
- player->nm[cw] += numLaunch;
- for (int count = numLaunch; count > 0; count--)
- {
- a = rand () % 180 + 90;
- p = rand () % (MAX_POWER / 2);
- activateCurrentSelection ();
- }
- }
- }
- */
+ // The shield phase delta depends on currently set FPS
+ shld_delta /= static_cast<double>(env.frames_per_second);
- if (player)
- {
- player->tank = NULL;
- player = NULL;
- }
- if (_global)
- {
- _global->numTanks--;
- if (_global->currTank == this)
- _global->currTank = NULL;
- }
+ setTextPositions(false);
+
+ drag = 0.5;
+ mass = 3000;
+ a += rand () % 180;
- if (shieldText)
- delete (shieldText);
- if (healthText)
- delete (healthText);
- if (nameText)
- delete (nameText);
-
- if (_env)
- _env->removeObject(this);
-
- shieldText = NULL;
- healthText = NULL;
- nameText = NULL;
- _env = NULL;
- _global = NULL;
- creditTo = NULL;
- _target = NULL;
+ // Add to the chain:
+ global.addObject(this);
+ global.numTanks++;
}
-TANK::TANK (GLOBALDATA *global, ENVIRONMENT *env):PHYSICAL_OBJECT(),_target(NULL),creditTo(NULL),
- healthText(NULL),shieldText(NULL),nameText(NULL)
-{
- setEnvironment (env);
- _global = global;
- // Ask for memory
- healthText = new FLOATTEXT (global, env, NULL, 0, 0, WHITE, CENTRE);
- if (!healthText)
- {
- perror ( "tank.cc: Failed allocating memory for healthText in TANK::TANK");
- // exit (1);
- }
- shieldText = new FLOATTEXT (global, env, NULL, 0, 0, makecol (200, 200, 255), CENTRE);
- if (!shieldText)
- {
- perror ( "tank.cc: Failed allocating memory for shieldText in TANK::TANK");
- // exit (1);
- }
+/*
+The destructor only removes the tank and cleans up after it.
+Any tank destruction (with big badass explosions, vengeance and stuff) is done
+in TANK::explode().
+*/
+TANK::~TANK ()
+{
+ if (player) {
+ player->tank = nullptr;
+ player = nullptr;
+ }
+ creditTo = nullptr;
+
+ global.numTanks--;
+ if (global.get_curr_tank() == this)
+ global.set_curr_tank(nullptr);
+
+ // Take out of the chain:
+ global.removeObject(this);
+}
- if (global->name_above_tank)
- {
- nameText = new FLOATTEXT (global, env, NULL, 0, 0, WHITE, CENTRE);
- if (! nameText)
- {
- perror ( "tank.cc: Failed allocating memory for nameText in TANK::TANK");
- // exit(1);
- }
- }
- // Other initial pointers:
- _align = LEFT;
- _global->numTanks++;
+/// @brief Set texts to vertical bounce and initialize text positions
+void TANK::activate()
+{
+ shieldText.set_sway_type(TS_VERTICAL);
+ healthText.set_sway_type(TS_VERTICAL);
+ nameText.set_sway_type(TS_VERTICAL);
- player = NULL;
+ setTextPositions(true);
}
-void TANK::initialise ()
-{
- PHYSICAL_OBJECT::initialise ();
- drag = 0.5;
- mass = 3000;
- repulsion = 0;
- shieldColor = 0;
- shieldThickness = 0;
- t = 0;
- sh = 0;
- _targetX = -1;
- _targetY = -1;
- newRound ();
-}
-void TANK::newRound ()
+/// @brief activate (aka "fire" or "shoot") whatever is selected right now.
+/// Use this whenever a weapon is fired without the player hitting the trigger.
+/// For the trigger slamming use simActivateCurrentSelection().
+void TANK::activateCurrentSelection()
{
- char buf[10];
-
- cw = 0;
- damage = 0;
- pen = 0;
- para = 0;
- creditTo = NULL;
- p = MAX_POWER / 2;
- a = (rand () % 180) + 90;
- if (sh > 0 && sht > 0)
- if (player)
- player->ni[sht]++;
- sh = 0;
- repulsion = 0;
- // shPhase = rand () % 360;
- shPhase = 0;
- delta_phase = 0.1;
- sht = 0;
- l = 100;
- repair_rate = 0;
- if (player)
- {
- double tmpL = 0;
- tmpL += (int)(player->ni[ITEM_ARMOUR] * item[ITEM_ARMOUR].vals[0]);
- tmpL += (int)(player->ni[ITEM_PLASTEEL] * item[ITEM_PLASTEEL].vals[0]);
- repair_rate = Get_Repair_Rate();
- if (tmpL > 0)
- l += (int)pow (tmpL, 0.6);
-
- if (healthText)
- healthText->set_color( player->color );
- if (nameText)
- {
- nameText->set_text ( player->getName() );
- nameText->set_color ( player->color );
- }
- }
- maxLife = l;
- ds = 0;
- fs = 0;
- sprintf (buf, "%d", l);
- healthText->set_text (buf);
- // delay_fall = GRAVITY_DELAY;
- delay_fall = (int) labs(_env->landSlideDelay * 100);
- fire_another_shot = 0;
- shots_fired = 0;
-
+ // avoid firing weapons on exit in Windows
+ if ( (global.get_command() == GLOBAL_COMMAND_QUIT)
+ || (global.get_command() == GLOBAL_COMMAND_MENU) )
+ return;
+
+ // This must not be called outside fire stage.
+ assert( (STAGE_FIRE == global.stage)
+ && "ERROR: TANK::activateCurrentSelection() called outside STAGE_FIRE!");
+ if (STAGE_FIRE != global.stage)
+ return;
+
+ // remove status from top bar at next redraw
+ if (global.tank_status)
+ global.tank_status[0] = 0;
+
+ // reduce time to fall, but reset if already done
+ if (--env.time_to_fall < 0)
+ env.time_to_fall = (rand() % env.landSlideDelay) + 1;
+
+ /** ==============================
+ * === Case 1 : Fire a weapon ===
+ * ==============================
+ **/
+ if (cw < WEAPONS) {
+ player->changed_weapon = false;
+ if (cw)
+ player->nm[cw]--;
+
+ // --- Ballistics ---
+ //--------------------
+ if (cw < BALLISTICS) {
+ play_fire_sound(cw, x, 255, 1000);
+
+ // Loop over spread, that covers everything
+ for (int32_t z = 0; z < weapon[cw].spread; ++z) {
+ int32_t ca = a + ((SPREAD * z) - (SPREAD * (weapon[cw].spread - 1) / 2));
+ double dp = static_cast<double>(p); // Just a shortcut against further casts
+
+ // power must not be zero if this is a riot charge/blast.
+ // If it were, the calculation of the triangle goes nuts and
+ // sends the charge downwards, regardless of the set angle.
+ /// @todo : Fix the calculation so this extra check becomes obsolete
+ if ( (dp < 100.) && (RIOT_CHARGE <= cw) && (RIOT_BLAST >= cw) )
+ dp = 100.;
+
+ double mxv = env.slope[ca][0] * dp * env.FPS_mod / 100.;
+ double myv = env.slope[ca][1] * dp * env.FPS_mod / 100.;
+
+ try {
+ MISSILE* newmis = new MISSILE(player,
+ x + (env.slope[ca][0] * turr_off_x),
+ y + (env.slope[ca][1] * turr_off_x),
+ mxv, myv, cw, MT_WEAPON, 1);
+
+ // set up / check volley
+ if (weapon[cw].delay && (0 == fire_another_shot) )
+ fire_another_shot = weapon[cw].delay * env.volley_delay;
+
+ // Adapt missile drag if the player has dimpled/slick projectiles
+ if (player->ni[ITEM_DIMPLEP]) {
+ player->ni[ITEM_DIMPLEP]--;
+ newmis->drag *= item[ITEM_DIMPLEP].vals[0];
+ } else if (player->ni[ITEM_SLICKP]) {
+ player->ni[ITEM_SLICKP]--;
+ newmis->drag *= item[ITEM_SLICKP].vals[0];
+ }
+ } catch (...) {
+ perror ( "tank.cpp: Failed to allocate memory for new"
+ " missile in TANK::activateCurrentSelection()");
+ }
+
+ }
+ } // End of ballistics
+
+ // --- Beam weapons ---
+ //----------------------
+ else {
+ try {
+ new BEAM (player, x + (env.slope[a][0] * turr_off_x),
+ y + (env.slope[a][1] * turr_off_x),
+ a, cw, BT_WEAPON);
+ } catch (...) {
+ perror ( "tank.cpp: Failed to allocate memory for new"
+ " beam in TANK::activateCurrentSelection()");
+ }
+ }
+ } // End of weapons
+
+ /** =================================
+ * === Case 2 : Activate an item ===
+ * =================================
+ **/
+ else {
+ int32_t ci = cw - WEAPONS; // [c]urrent [i]tem
+
+ // If this is not a vengeance item, take it out of the inventory.
+ // Note: Vengeance items are reduced in explode() if triggered.
+ if ( (ITEM_VENGEANCE > ci) || (ITEM_FATAL_FURY < ci) )
+ player->ni[ci]--;
+
+ // --- Teleport ---
+ //-----------------
+ if (ITEM_TELEPORT == ci) {
+ int32_t right = env.screenWidth - (tank_dia * 2);
+ int32_t bottom = env.screenHeight - (tank_dia * 2) - MENUHEIGHT;
+ int32_t new_x = (rand() % right ) + tank_dia;
+ int32_t new_y = (rand() % bottom) + tank_dia + MENUHEIGHT;
+
+ // Be sure the tank does not end up too high in the sky
+ // or too deeply buried.
+ int32_t surf_y = global.surface[new_x].load(ATOMIC_READ);
+ if (new_y < (surf_y - 150) )
+ new_y = surf_y - 150;
+ else if (new_y > (surf_y + 100) )
+ new_y = surf_y + 100;
+
+ try {
+ new TELEPORT (this, new_x, new_y, tank_dia, 120, ci);
+ addDamage(player, 0.); // Fall is a self hit.
+ isTeleported = true;
+ } catch(...) {
+ perror ( "tank.cpp: Failed to allocate memory for teleport"
+ " in TANK::activateCurrentSelection()");
+ }
+ }
+
+ // --- Swapper ---
+ //-----------------
+ else if (ITEM_SWAPPER == ci) {
+ TANK* other = nullptr;
+
+ while (!other) {
+ global.getHeadOfClass(CLASS_TANK, &other);
+
+ // If there are only two tanks, just take the other
+ if (2 == global.numTanks) {
+ if (other == this)
+ other->getNext(&other);
+ } else {
+ // Otherwise select one by random
+ int32_t rtn = rand() % (global.numTanks - 1);
+ while (rtn--)
+ other->getNext(&other);
+
+ // If the selection ended up with this tank, chose the next one
+ if (other == this)
+ other->getNext(&other);
+ }
+
+ assert(other && "ERROR: Swapper selected nullptr!");
+ assert( (other != this) && "ERROR: Swapper selected this as other!");
+
+ if (other == this)
+ other = nullptr;
+ } // End of selecting other
+
+ this->addDamage( player, 0.); // Own falling damage
+ this->isTeleported = true;
+ other->addDamage(player, 0.); // Their falling damage
+ other->isTeleported = true;
+
+ try {
+ // create a teleport object for this tank
+ new TELEPORT (this, other->x, other->y, tank_dia, 120, ci);
+ // create a teleport object for the other tank
+ new TELEPORT (other, x, y, other->tank_dia, 120, ci);
+ } catch (...) {
+ perror ( "tank.cpp: Failed to allocate memory for swap teleports"
+ " in TANK::activateCurrentSelection()");
+ }
+ }
+
+ // --- Mass Teleport ---
+ //-----------------------
+ else if (ITEM_MASS_TELEPORT == ci) {
+ int32_t right = env.screenWidth - (tank_dia * 2);
+ int32_t bottom = env.screenHeight - (tank_dia * 2) - MENUHEIGHT;
+ TANK* lt = nullptr;
+
+ global.getHeadOfClass(CLASS_TANK, <);
+ while ( lt ) {
+ int32_t new_x = (rand() % right ) + tank_dia;
+ int32_t new_y = (rand() % bottom) + tank_dia + MENUHEIGHT;
+
+ // Like with the normal teleport, ensure a sane y value.
+ int32_t surf_y = global.surface[new_x].load(ATOMIC_READ);
+ if (new_y < (surf_y - 150) )
+ new_y = surf_y - 150;
+ else if (new_y > (surf_y + 100) )
+ new_y = surf_y + 100;
+ try {
+ new TELEPORT (lt, new_x, new_y, lt->tank_dia, 120, ci);
+ lt->addDamage(player, 0.); // They fall, we earn. Cool.
+ lt->isTeleported = true;
+
+ } catch(...) {
+ perror ( "tank.cpp: Failed to allocate memory for teleport"
+ " in TANK::activateCurrentSelection()");
+ }
+ lt->getNext(<);
+ }
+ }
+
+ // --- Rocket ("I beliiiieve Ay Can Flaaaaaaaayy") ---
+ //-----------------------------------------------------
+ else if (ITEM_ROCKET == ci) {
+ yv = -10;
+ y -= 10;
+ if (a < 180)
+ xv += 0.3;
+ else if (a > 180)
+ xv -= 0.3;
+ // If this leads to falling damage, make sure it is a self hit:
+ addDamage(player, 0.);
+ isTeleported = true;
+ applyPhysics();
+ }
+
+ // --- Fan (aka "The most useless item there is") ---
+ //----------------------------------------------------
+ else if (ITEM_FAN == ci) {
+ play_fire_sound(ITEM_FAN + WEAPONS, x, 255, 1000);
+
+ if (a < 180)
+ // move wind to the right
+ global.wind += (p / 20);
+ else
+ // wind to the left
+ global.wind -= (p / 20);
+
+ // make sure wind is not too strong
+ if (global.wind < (-env.windstrength / 2) )
+ global.wind = -env.windstrength / 2;
+ else if (global.wind > (env.windstrength / 2) )
+ global.wind = env.windstrength / 2;
+
+ global.lastwind = global.wind;
+ }
+ // --- Vengeance (BOOM BABY!) ---
+ //--------------------------------
+ else if ( (ITEM_VENGEANCE <= ci) && (ITEM_FATAL_FURY >= ci) ) {
+ // Just preparation. The tank explodes, and the vengeance goes
+ // off automatically as selected. ;-)
+ this->player->reclaimShield();
+ this->addDamage(nullptr, l + sh + repair_rate + 1);
+ this->applyDamage();
+ }
+ } // End of items
+
+ player->time_left_to_fire = env.maxFireTime;
}
-
-void TANK::Destroy()
+/// @brief adds damage, sets creditTo and handles pending damage
+void TANK::addDamage(PLAYER* damageFrom, double damage_)
{
- #ifdef NETWORK
- if (player) // we should always have a player, but better safe than sorry
- {
- int player_index = 0;
- bool found = false;
- char buffer[64];
- while ( (player_index < _global->numPlayers) && (!found) )
- {
- // get the player index
- if ( ( _global->players[player_index]->tank ) && (_global->players[player_index]->tank == this) )
- found = true;
- else
- player_index++;
- }
- if (found) // we should have found a match and now we send it to all clients
- {
- sprintf(buffer, "REMOVETANK %d", player_index);
- _global->Send_To_Clients(buffer);
- }
- }
- #endif
-
- if (_global && player)
- {
- int random_item;
- if (_global->violent_death)
- {
- random_item = rand() % VIOLENT_CHANCE;
- if ( ( random_item > _global->violent_death ) && (random_item <= VIOLENT_DEATH_HEAVY) )
- random_item = (int) _global->violent_death;
-
- switch (random_item)
- {
- case VIOLENT_DEATH_LIGHT:
- player->ni[ITEM_VENGEANCE] += 1;
- break;
- case VIOLENT_DEATH_MEDIUM:
- player->ni[ITEM_DYING_WRATH] += 1;
- break;
- case VIOLENT_DEATH_HEAVY:
- player->ni[ITEM_FATAL_FURY] += 1;
- break;
- } //end of switch
- }
-
- if (player->ni[ITEM_FATAL_FURY] > 0)
- {
- int numLaunch = (int)item[ITEM_FATAL_FURY].vals[SELFD_NUMBER];
- cw = (int)item[ITEM_FATAL_FURY].vals[SELFD_TYPE];
- player->ni[ITEM_FATAL_FURY]--;
- player->nm[cw] += numLaunch;
- for (int count = numLaunch; count > 0; count--)
- {
- a = rand () % 180 + 90;
- p = rand () % (MAX_POWER / 2);
- activateCurrentSelection ();
- }
- }
- else if (player->ni[ITEM_DYING_WRATH] > 0)
- {
- int numLaunch = (int)item[ITEM_DYING_WRATH].vals[SELFD_NUMBER];
- cw = (int)item[ITEM_DYING_WRATH].vals[SELFD_TYPE];
- player->ni[ITEM_DYING_WRATH]--;
- player->nm[cw] += numLaunch;
- for (int count = numLaunch; count > 0; count--)
- {
- a = rand () % 180 + 90;
- p = rand () % (MAX_POWER / 2);
- activateCurrentSelection ();
- }
- }
- else if (player->ni[ITEM_VENGEANCE] > 0)
- {
- int numLaunch = (int)item[ITEM_VENGEANCE].vals[SELFD_NUMBER];
- cw = (int)item[ITEM_VENGEANCE].vals[SELFD_TYPE];
- player->ni[ITEM_VENGEANCE]--;
- player->nm[cw] += numLaunch;
- for (int count = numLaunch; count > 0; count--)
- {
- a = rand () % 180 + 90;
- p = rand () % (MAX_POWER / 2);
- activateCurrentSelection ();
- }
- }
- }
-}
+ // Clear pending damage if the 'deliverer' changes
+ if (damageFrom != creditTo) {
+ applyDamage();
+ damage = 0.;
+ // Update creditTo first
+ creditTo = damageFrom;
+ newDamager = true;
+ }
+ assert( (damage_ >= 0.) && "ERROR: Negative damage?");
-void TANK::update ()
-{
- VIRTUAL_OBJECT::update ();
+ // Raise damage
+ if (damage_ > 0.)
+ damage += damage_;
}
-void TANK::requireUpdate ()
-{
- VIRTUAL_OBJECT::requireUpdate ();
-}
void TANK::applyDamage ()
{
- char buf[10];
- FLOATTEXT *damageText;
- FLOATTEXT *revengeText;
- FLOATTEXT *gloatText;
- FLOATTEXT *suicideText;
- char *temp_text;
- bool killed = false;
-
- if (damage > sh + l)
- {
- damage = sh + l;
- killed = true;
- player->killed++;
- }
- sh -= (int)damage;
- if (creditTo)
- {
- if (player != creditTo) //enemy hit ++
- {
- if ( killed )
- creditTo->kills++;
- creditTo->money += (int)(damage * _global->scoreHitUnit);
- if (creditTo->tank)
- {
- char the_money[64];
- sprintf(the_money, "$%s", Add_Comma( (int) (damage * _global->scoreHitUnit) ) );
- // show how much the shooter gets
- FLOATTEXT *moneyText = new FLOATTEXT(_global, _env, the_money,
- (int) creditTo->tank->x, (int) creditTo->tank->y - 30,
- makecol(0, 255, 0), CENTRE);
- if (moneyText)
- {
- // moneyText->xv = 0;
- // moneyText->yv = -0.5;
- moneyText->set_speed(0.0, -0.5);
- moneyText->maxAge = 200;
- }
-
- }
- // this tank is destroyed, the attacker gloats
- if ( (killed) && (! creditTo->gloating) )
- {
- // avoid trying to print victory message over a dead tank
- if (creditTo->tank)
- {
- temp_text = creditTo->selectGloatPhrase();
- gloatText = new FLOATTEXT (_global, _env,
- // creditTo->selectGloatPhrase( (double) damage / maxLife),
- temp_text,
- (int) creditTo->tank->x, (int) creditTo->tank->y - 30,
- creditTo->color, CENTRE);
- if (gloatText)
- {
- // gloatText->xv = 0;
- // gloatText->yv = -0.4;
- gloatText->set_speed(0.0, -0.4);
- gloatText->maxAge = 300;
- }
- else
- perror ( "tank.cc: Failed allocating memory for gloatText in applyDamage.");
- creditTo->gloating = true;
- }
- }
-
- if ((int)player->type != HUMAN_PLAYER)
- {
- if (player->revenge == creditTo)
- {
- player->annoyanceFactor += damage;
- }
- else
- {
- player->annoyanceFactor = damage;
- }
- if ( (player->annoyanceFactor > (player->vengeanceThreshold * maxLife) )
- &&((rand() % 100) <= player->vengeful) )
- {
- player->revenge = creditTo;
- temp_text = player->selectRevengePhrase();
- revengeText = new FLOATTEXT (_global, _env,
- // player->selectRevengePhrase ((double)damage / maxLife),
- temp_text,
- (int)x, (int)y - 30,
- player->color, CENTRE);
- if (revengeText)
- {
- // revengeText->xv = 0;
- // revengeText->yv = -0.4;
- revengeText->set_speed(0.0, -0.4);
- revengeText->maxAge = 300;
- }
- else
- perror ( "tank.cc: Failed allocating memory for revengeText in applyDamage");
- }
- }
- }
- else //self hit --
- {
- if ( (creditTo->money - (damage * _global->scoreSelfHit)) < 0)
- creditTo->money = 0;
- else
- creditTo->money -= (int)(damage * _global->scoreSelfHit);
-
- if (damage >= (l + sh) )
- {
- temp_text = player->selectSuicidePhrase();
- suicideText = new FLOATTEXT (_global, _env,
- player->selectSuicidePhrase(),
- (int) x, (int) y - 30,
- player->color, CENTRE);
- if (suicideText)
- {
- // suicideText->xv = 0;
- // suicideText->yv = -0.4;
- suicideText->set_speed(0.0, -0.4);
- suicideText->maxAge = 300;
- }
- else
- perror ( "tank.cc: Failed allocating memory for suicideText in applyDamage.");
- }
- }
- }
-
- if (sh < 0)
- l += sh;
- if (l < 0)
- l = 0;
- if (sh <= 0)
- {
- repulsion = 0;
- shieldColor = 0;
- shieldThickness = 0;
- sh = 0;
- }
-
- if ((int)damage > 0)
- {
- flashdamage = 1;
- sprintf (buf, "%d", (int)damage);
- damageText = new FLOATTEXT (_global, _env,
- NULL, (int)x, (int)y,
- makecol (255, 0, 0), CENTRE);
- if (damageText)
- {
- damageText->set_text(buf);
- // damageText->xv = 0;
- // damageText->yv = -0.2;
- damageText->set_speed(0.0, -0.2);
- damageText->maxAge = 300;
- // damageText->sway = NORMAL_SWAY;
- }
- else
- perror ( "tank.cc: Failed allocating memory for damageText in TANK::TANK");
- }
- if (sh > 0)
- {
- sprintf (buf, "%d", sh);
- shieldText->set_text (buf);
- }
- else
- {
- shieldText->set_text (NULL);
- }
- sprintf (buf, "%d", l);
- healthText->set_text (buf);
+ // Only *one* call to applyDamage() at any time!
+ std::lock_guard<CSpinLock> apply_damage_lock(damage_lock);
+
+ // Before taking any action, damage must be at least 1
+ if (destroy || (damage < 1.)) {
+ newDamager = false;
+ return;
+ }
+
+ // See if the pending damage destroys the tank:
+ int32_t full_damage = ROUND(damage);
+
+ if (full_damage >= (sh + l) ) {
+ destroy = true;
+ damage = sh + l; // Don't overdo
+ full_damage = damage;
+ player->killed++;
+ }
+
+ // Damage is applied to the shield, and negative shield later
+ // added to life. This saves some if/then/else constructs
+ int32_t old_sh = sh; // For repulsor shield blast through
+ sh -= full_damage;
+
+ /* -----------------------------------------------------------
+ * --- If a damage dealer is set, they must be awarded ---
+ * --- their reward or, in case of self/team hit, penalty. ---
+ * -----------------------------------------------------------
+ */
+ if (creditTo) {
+ int32_t award = full_damage;
+ bool self_hit = creditTo == player;
+ bool team_hit = !self_hit
+ && (TEAM_NEUTRAL != creditTo->team)
+ && (player->team == creditTo->team);
+
+ // Award kill point if no suicide
+ if (destroy && !self_hit)
+ creditTo->kills++;
+
+ // note damage in own and opponents memory
+ player->noteDamageFrom(creditTo, full_damage, destroy);
+ creditTo->noteDamageTo(player, full_damage, destroy);
+
+ // The award must be adapted to the situation
+ award *= self_hit ? env.scoreSelfHit :
+ team_hit ? env.scoreTeamHit :
+ env.scoreHitUnit;
+
+ // The kill bonus is only applied if this is no team kill
+ if (destroy && !team_hit)
+ award += self_hit
+ ? env.scoreUnitSelfDestroy
+ : env.scoreUnitDestroyBonus;
+
+ // Money can not go negative.
+ if ((self_hit || team_hit) && (award > creditTo->money))
+ award = creditTo->money;
+
+ // If there is an award now, get the money text out
+ // and the register to ring.
+ if (award > 0) {
+ if (creditTo->tank && !global.skippingComputerPlay) {
+ static char the_money[16] = { 0x0 };
+ snprintf(the_money, 15, "%s$%s",
+ (team_hit || self_hit) ? "-" : "",
+ Add_Comma( award ) );
+ // show how much the shooter gets
+ try {
+ new FLOATTEXT(the_money,
+ creditTo->tank->x, creditTo->tank->y - 30,
+ .0, -.5, team_hit ? PURPLE : self_hit ? RED : GREEN,
+ CENTRE,
+ env.swayingText ? TS_HORIZONTAL : TS_NO_SWAY,
+ 200);
+ if (global.stage < STAGE_SCOREBOARD)
+ global.updateMenu = true;
+ } catch (...) {
+ perror ( "tank.cpp: Failed allocating memory for money text"
+ " in applyDamage().");
+ }
+ }
+ creditTo->money += ( (team_hit || self_hit) ? -1 : 1) * award;
+ } // End of applying damage award
+
+ // If the tank is destroyed, and it was neither self nor team hit,
+ // the damager might spawn a gloating message
+ if ( destroy && !creditTo->gloating && !team_hit && !self_hit
+ && creditTo->tank && !creditTo->tank->destroy
+ && !global.skippingComputerPlay) {
+
+ creditTo->gloating = true;
+
+ try {
+ new FLOATTEXT(creditTo->selectGloatPhrase(),
+ creditTo->tank->x, creditTo->tank->y - 30,
+ .0, -.4, creditTo->color, CENTRE, TS_NO_SWAY, 200);
+ } catch (...) {
+ perror ( "tank.cpp: Failed to allocate memory for"
+ " gloating text in applyDamage().");
+ }
+ } // End of spawning gloating text
+
+ // Issue a suicide message if the player applied for a darwin award
+ if (self_hit && destroy && !global.skippingComputerPlay) {
+ try {
+ new FLOATTEXT(player->selectSuicidePhrase(),
+ x, y - 30, .0, -.4, player->color,
+ CENTRE, TS_NO_SWAY, 300);
+ } catch (...) {
+ perror ( "tank.cpp: Failed allocate memory for suicide"
+ " text in applyDamage().");
+ }
+ }
+ } // End of handling damager texts and awards
+
+ // -----------------------------------
+ // --- Eventually apply the damage ---
+ // -----------------------------------
+ int32_t old_life = l;
+ if (destroy) {
+ sh = 0;
+ l = 0;
+ repulsion = 0;
+ } else {
+ if ( (ITEM_LGT_REPULSOR_SHIELD <= sht)
+ && (ITEM_HVY_REPULSOR_SHIELD >= sht) ) {
+ // Repulsor shields do not take the full damage but
+ // let half of it blasted through.
+ int32_t sh_dmg = full_damage / 2;
+ int32_t t_dmg = (full_damage / 2) + (full_damage % 2);
+ if (sh_dmg >= old_sh) {
+ t_dmg += sh_dmg - old_sh;
+ sh_dmg = old_sh;
+ }
+
+ sh = old_sh - sh_dmg;
+ l -= t_dmg;
+
+ }
+
+ // normal shield-is-empty-check:
+ if (sh < 0.5) {
+ l += sh; // For non-repulsor shields.
+ sh = 0;
+ repulsion = 0;
+ }
+ }
+
+ // --------------------------------
+ // --- Display the damage value ---
+ // --------------------------------
+ if (full_damage > 0) {
+ static char buf[10] = { 0x0 };
+ flashdamage = 1;
+
+ if (!global.skippingComputerPlay) {
+ snprintf (buf, 9, "%d", full_damage);
+ try {
+ new FLOATTEXT(buf, x, y - 30, .0, -.3, RED, CENTRE,
+ env.swayingText ? TS_HORIZONTAL : TS_NO_SWAY, 300);
+ } catch (...) {
+ perror ( "tank.cpp: Failed to allocate memory for damage"
+ " text in applyDamage().");
+ }
+ }
+
+ // If shield remains, the shield text has to be regenerated
+ if (sh > 0) {
+ snprintf (buf, 9, "%d", sh);
+ shieldText.set_text (buf);
+ } else
+ shieldText.set_text (nullptr);
+
+ // If life points were taken, the life text is to be regenerated
+ if (old_life != l) {
+ snprintf (buf, 9, "%d", l);
+ healthText.set_text (buf);
+ }
+ } // End of having damage
+
+ damage = 0.; // all applied
}
-void TANK::framelyAccounting ()
+
+// Thanks to the rockets, tanks can 'fly', and thanks to ... uhm ...
+// everything, tanks might fall down.
+void TANK::applyPhysics ()
{
- /*
- if (shPhase < 0)
- shPhase = rand () % 360;
- shPhase += (int)(item[sht].vals[SHIELD_ENERGY] / sh) + 10;
- while (shPhase >= 360)
- shPhase -= 360;
- */
- shPhase = shPhase + delta_phase;
- if ( ( shPhase > 5) || (shPhase < -2) )
- delta_phase = -delta_phase;
+ // Do nothing if this tank was destroyed
+ if (destroy)
+ return;
+
+ double old_y = y;
+
+ // Special handling when rocketing upwards first:
+ if ( yv < 0. ) {
+ // Although activating a rocket instantly pushes the tank
+ // upwards, it stops there if the tank is buried
+ if ( howBuried(nullptr, nullptr) ) {
+ xv = 0;
+ yv = 0;
+ }
+
+ /// @todo : Is this really a problem?
+ // make sure the tank does not leave the screen when flying
+ else if ( (y < tank_off_y) )
+ yv = 0;
+
+ // Otherwise apply rocket x movement
+ else
+ x += xv * 4.;
+ } // End of special rocketing handling
+
+ // General movement is only applied while no damage flashes.
+ // Note: This means that damage application halts all movement.
+ if (flashdamage)
+ ++flashdamage; // Frame counted
+ else {
+ bool on_tank = tank_on_tank();
+ int32_t bottom = env.screenHeight - tank_off_y; // shortcut
+ int32_t pix_col = getpixel(global.terrain, x, y + tank_off_y - tank_sag);
+
+ // Hitting a wall only bounces the tank.
+ if ( ((x + xv) < 1) || ((x + xv) > (env.screenWidth - 1)) )
+ xv *= -1.;
+
+ // Check whether a previous fall just ends:
+ if ( (yv > 0.) && ( (y >= bottom) || (PINK != pix_col) || on_tank ) ) {
+ if (isTeleported) {
+ addDamage(creditTo, yv * 10.);
+ isTeleported = false;
+ } else
+ addDamage(nullptr, yv * 10.);
+
+ // 10 points of damage are 'free' when falling
+ // Note: This negates any damage when parachuting as well.
+ if (damage >= 10.)
+ damage -= 10.;
+ else
+ damage = 0.;
+
+ // Stop movement
+ xv = 0.;
+ yv = 0.;
+
+ // fix y if the tank was fast enough to push through the floor
+ if (y > bottom)
+ y = bottom;
+
+ // Reset falling delay and apply damage at once
+ delay_fall = env.landSlideDelay * 100;
+ applyDamage();
+ } // End of fall stop
+
+ // Check whether the tank currently is falling
+ else if ( (y < bottom) && (PINK == pix_col) && !on_tank
+ && (env.landSlideType > SLIDE_NONE) ) {
+
+ // If this is set to cartoon falling, decrease delay and exit.
+ if ( (SLIDE_CARTOON == env.landSlideType)
+ && (--delay_fall > 0) )
+ return;
+
+ yv += env.gravity * env.FPS_mod;
+
+ // Check for parachute opening
+ if (para) {
+ if (para < 3)
+ ++para;
+
+ // With a parachute, wind can blow the tank away
+ xv += (global.wind - xv) / mass
+ * (drag + 0.35) * env.viscosity;
+
+ // Limit yv, we have a parachute!
+ if (yv > 0.5 )
+ yv = 0.5;
+ } else {
+ // If we have parachutes, deploy one:
+ if ((player->ni[ITEM_PARACHUTE]) && (yv >= 1.0)) {
+ para = 1;
+ player->ni[ITEM_PARACHUTE]--;
+ }
+ }
+
+ x += xv;
+ y += yv;
+ } // End of fall start / continue
+
+ // Nothing? Then make sure no parachute is shown
+ else
+ para = 0;
+
+ // If there is no damage flashing, apply what is there
+ if (!flashdamage) {
+ applyDamage();
+ }
+
+ requireUpdate();
+ } // End of regular movement
+
+ setTextPositions(old_y != y);
}
-int TANK::applyPhysics ()
+
+/// @brief Test if the current weapon is available. Find another one,
+/// preferably stronger, if the current is empty.
+void TANK::check_weapon()
{
- int stable = 0;
- int landed_on_tank;
-
- // if we are buried, rockets shouldn't work
- if ( howBuried() )
- {
- xv = 0;
- if (yv < 0)
- yv = 0;
- }
-
- // make sure the tank does not leave the screen when flying
- if ( (yv < 0) && (y < TANKHEIGHT) )
- yv = 0;
-
- if (yv < 0)
- x += xv * 4;
-
- if (!flashdamage)
- {
- if (x + xv < 1 || x + xv > (_global->screenWidth-1))
- xv = -xv; //bounce on the border
- int pixelCol = getpixel (_env->terrain, (int)x, (int)y + (TANKHEIGHT - TANKSAG));
-
- // check to see if we have landed on another tank -- Jesse
- landed_on_tank = tank_on_tank ( _global );
-
- // we are falling and have hit the bottom of the screen or fallen onto dirt or on a tank
- if ((l > 0) && (yv > 0) && ((y >= _global->screenHeight - TANKHEIGHT) ||
- (pixelCol != PINK) ||
- (landed_on_tank) ))
- {
- //count damage and add money
- damage = (int) (yv * 10);
- if (damage >= 10)
- damage -= 10;
- else damage = 0;
- creditTo = NULL;
- yv = 0;
- xv = 0;
- // if we passed the bottom, then stop on bottom
- if (y > _global->screenHeight - TANKHEIGHT)
- y = _global->screenHeight - TANKHEIGHT;
-
- // delay_fall = GRAVITY_DELAY;
- delay_fall = (int) _env->landSlideDelay * 100;
-
- }
-
- // the tank is falling
- else if ((y < _global->screenHeight - TANKHEIGHT) && (pixelCol == PINK) && (l > 0) &&
- (! landed_on_tank) && (_env->landSlideType > LANDSLIDE_NONE) )
- {
- delay_fall--;
- if ( (delay_fall > 0) && (_env->landSlideType == LANDSLIDE_CARTOON) )
- return stable;
-
- #ifdef OLD_GAMELOOP
- if (para && para < 3 && !_env->pclock)
- para++;
- #else
- if ( (para) && (para < 3) )
- para++;
- #endif
- if (!para)
- {
- yv += _env->gravity * (100.0 / _global->frames_per_second);
- y += yv;
- }
- else
- {
- double accel = (_env->wind - xv) / mass * (drag + 0.35) * _env->viscosity;
- xv += accel;
- yv += _env->gravity * (100.0 / _global->frames_per_second);
- if (yv > 0.5 )
- yv = 0.5;
- x += xv;
- y += yv;
- }
- // falling, deploy parachute
- if (!para)
- {
- if ((player->ni[ITEM_PARACHUTE]) && (yv >= 1.0))
- {
- _env->pclock = 1;
- para = 1;
- player->ni[ITEM_PARACHUTE]--;
- }
-
- }
- requireUpdate ();
- }
- else
- {
- stable = 1;
- if (damage && !pen)
- {
- applyDamage ();
-
- pen = 1;
- }
- para = 0;
- }
- }
- else
- {
- flashdamage++;
- }
- return (stable);
+ if ( (cw < 0) || (cw > WEAPONS) )
+ cw = 0;
+
+ if (player && !player->nm[cw] ) {
+ // Try upwards first:
+ int32_t old_cw = cw++;
+ for ( ; (cw < WEAPONS) && !player->nm[cw] ; ++cw) ;
+
+ // If this wasn't successful, go downwards:
+ if (WEAPONS == cw) {
+ cw = old_cw - 1;
+ for ( ; (cw > 0) && !player->nm[cw] ; --cw) ;
+ }
+
+ // This ends up with Small Missile if no other weapons are available.
+ player->changed_weapon = true;
+ }
}
-void TANK::explode ()
+
+/// @brief Deactivate vertical bounce and reset text positions
+void TANK::deactivate()
{
- FLOATTEXT *revengeText;
- EXPLOSION *explosion;
- char *temp_text;
-
- if ((int)player->type != HUMAN_PLAYER)
- {
- player->revenge = creditTo;
- temp_text = player->selectRevengePhrase();
- revengeText = new FLOATTEXT (_global, _env,
- temp_text,
- (int)x, (int)y - 30,
- player->color, CENTRE);
- if (revengeText)
- {
- // revengeText->xv = 0;
- // revengeText->yv = -0.4;
- revengeText->set_speed(0.0, -0.4);
- revengeText->maxAge = 300;
- }
- else
- perror ( "tank.cc: Failed allocating memory for revengeText in TANK::explode");
- }
- explosion = new EXPLOSION (_global, _env, x, y, 1);
- if (explosion)
- {
- explosion->player = player;
- explosion->bIsWeaponExplosion = false;
- }
- else
- perror ( "tank.cc: Failed allocating memory for explosion in TANK::explode");
-
-
- destroy = TRUE;
- play_sample ((SAMPLE *)_global->sounds[WEAPONSOUNDS], _env->scaleVolume(255), 128, 1000, 0);
+ shieldText.set_sway_type(TS_NO_SWAY);
+ healthText.set_sway_type(TS_NO_SWAY);
+ nameText.set_sway_type(TS_NO_SWAY);
+
+ setTextPositions(true);
}
-void TANK::repulse (double xpos, double ypos, double *xaccel, double *yaccel, int aWeaponType)
+
+void TANK::draw()
{
- if (repulsion != 0)
- {
- double xdist = xpos - x;
- double ydist = -1.0 * fabs(ypos - y);
-
- if ((xdist < 0.1) && (xdist > -0.1)) xdist = 0.1;
- if ((ydist < 0.1) && (ydist > -0.1)) ydist = -0.1; // Assume missile comes from above
-
- if ( (aWeaponType >= BURROWER) && (aWeaponType <= PENETRATOR)
- &&(ypos > y) )
- ydist *= -1.0; // they normally come from below!
-
- double distance2 = (xdist * xdist) + (ydist * ydist);
- double distance = sqrt (distance2);
-
- if (distance < (60.0 + sqrt ((double)repulsion)))
- {
- *xaccel = repulsion * (xdist / distance) / distance2;
- *yaccel = repulsion * (ydist / distance) / distance2;
- }
- }
+ // check for foggy weather
+ if ( ( env.fog ) && ( global.get_curr_tank() != this ) ) {
+ addUpdateArea (x - tank_off_x - 3, y - 25, 35, 46);
+ requireUpdate ();
+ return;
+ }
+
+ // get bitmap for tank
+ if ( (use_tankbitmap < 0) || (use_turretbitmap < 0) ) {
+ setBitmap();
+
+ assert ((use_tankbitmap >= 0) && (use_turretbitmap >= 0)
+ && "ERROR: Unable to set tank/turret bitmap!");
+
+ // No bitmap, no fun
+ if ( (use_tankbitmap < 0) || (use_turretbitmap < 0) )
+ return;
+ }
+
+ // Put a coloured rectangle below the tank.
+ rectfill (global.canvas, x - tank_off_x, y + tank_off_y,
+ x + tank_off_x, y + tank_off_y + 2,
+ this->player->color);
+
+ // Draw shield first if any
+ if (sh > 0) {
+ // Make sure the values are set. (Client may not have set them)
+ assert((BLACK != shld_col_outer) && shld_thickness
+ && "ERROR: Did client() forget to set shield values?");
+
+ if (BLACK == shld_col_outer)
+ shld_col_outer = makecol(item[sht].vals[SHIELD_RED],
+ item[sht].vals[SHIELD_GREEN],
+ item[sht].vals[SHIELD_BLUE]);
+ if (!shld_thickness)
+ shld_thickness = item[sht].vals[SHIELD_THICKNESS];
+
+ // Adapt shield phase:
+ double str_mod = static_cast<double>(sh) / item[sht].vals[SHIELD_ENERGY];
+ // The weaker the shield, the faster the phase
+ shld_phase += shld_delta / str_mod;
+ // Don't overdo
+ while (shld_phase > 360.)
+ shld_phase -= 360.;
+
+ // Set basic values
+ int32_t move_x = ROUNDu(x);
+ int32_t move_y = ROUNDu(y) - turr_off_y + shld_rad_y;
+ int32_t rad_x = shld_rad_x;
+ int32_t rad_y = shld_rad_y;
+ int32_t half_th = shld_thickness / 2;
+
+ // Whether it is done over x or y depends on the type of shield
+ if (sht < ITEM_LGT_REPULSOR_SHIELD) {
+ rad_x += static_cast<int32_t>(env.slope[ROUNDu(shld_phase)][0] * 3.);
+ rad_y += half_th;
+ move_x -= half_th;
+ } else {
+ rad_x += half_th;
+ rad_y += shld_phase * 6. / 360.;
+ move_y -= half_th;
+ }
+
+ drawing_mode(DRAW_MODE_TRANS, nullptr, 0, 0);
+ global.current_drawing_mode = DRAW_MODE_TRANS;
+ set_trans_blender (0, 0, 0, 50);
+ ellipsefill (global.canvas, move_x, move_y, rad_x, rad_y, shld_col_inner);
+ set_trans_blender (0, 0, 0, ROUND(200. * str_mod) + 25);
+
+ if (sht < ITEM_LGT_REPULSOR_SHIELD) {
+ for (int32_t i = 0; i < shld_thickness; ++i)
+ ellipse (global.canvas, move_x + i, move_y, rad_x, rad_y, shld_col_outer);
+ } else {
+ for (int32_t i = 0; i < shld_thickness; ++i)
+ ellipse (global.canvas, move_x, move_y + i, rad_x, rad_y, shld_col_outer);
+ }
+
+ drawing_mode(DRAW_MODE_SOLID, nullptr, 0, 0);
+ global.current_drawing_mode = DRAW_MODE_SOLID;
+ setUpdateArea (move_x - shld_thickness - rad_x,
+ move_y - shld_thickness - rad_y,
+ (rad_x + shld_thickness) * 2,
+ (rad_y + shld_thickness) * 2);
+ } // End of drawing shield
+
+ // Without a shield, the update area can be smaller
+ else
+ setUpdateArea (x - turr_off_x - 1, y - turr_off_x - 1,
+ (turr_off_x * 2) + 2, tank_off_y + turr_off_x + 20);
+
+ // Now draw the tank sprite
+ draw_sprite (global.canvas, env.tank[use_tankbitmap], x - tank_off_x, y);
+ rotate_sprite (global.canvas, env.tankgun[use_turretbitmap],
+ x - turr_off_x, y - turr_off_y,
+ itofix( (90 - a) * 256 / 360) );
+
+ // when using rockets, show flame
+ if (yv < 0)
+ /// @todo : This looks silly. We sure can do better, can't we?
+ rectfill(global.canvas, x - tank_off_x, y + tank_off_y,
+ x + tank_off_x, y + tank_off_y + 10, ORANGE);
+
+ // Eventually draw the parachute
+ if (para) {
+ draw_sprite (global.canvas, env.tank[para], x - tank_off_x - 3, y - 25);
+ addUpdateArea (x - tank_off_x - 3, y - 25, 35, 66);
+ }
+
+ setTextPositions(false);
+ requireUpdate ();
}
-void TANK::printHealth (int offset)
+
+/// @brief Create explosion and sound if a tank is destroyed. If available
+/// and/or set, stage a violent death.
+void TANK::explode (bool allow_vengeance)
{
- int textpos = -5;
-
- shieldText->set_pos ((int)x, (int)y - TANKHEIGHT + textpos + offset);
- if (sh > 0)
- textpos -= 10;
- healthText->set_pos ((int)x, (int)y - TANKHEIGHT + textpos + offset);
-
- // display player name
- if (nameText)
- {
- textpos -= 10;
- nameText->set_pos ((int)x, (int)y - TANKHEIGHT + textpos + offset);
- }
+ if (!destroy)
+ return;
+
+ // Note: player->revenge and revenge texts are handled in applyDamage()
+
+ try {
+ new EXPLOSION (player, x, y, 0., env.screenHeight / 10., MED_MIS, false);
+ } catch (...) {
+ perror ( "tank.cpp: Failed to allocate memory for explosion"
+ " in TANK::explode()");
+ }
+
+ play_explosion_sound(MED_MIS, x, 255, 1000);
+
+ // Violent death can only trigger if there is a player.
+ // Note: There should be, always, so assert as well. Calling this method
+ // after player was set to nullptr is a bug.
+ assert (player && "ERROR: explode() called with nullptr player!");
+ if (nullptr == player)
+ return;
+
+#ifdef NETWORK
+ int32_t playerindex = 0;
+ bool found = false;
+ static char buffer[15] = { 0x0 };
+
+ // get the player index
+ while ( (playerindex < env.numGamePlayers) && (!found) ) {
+ if ( env.players[playerindex]
+ && (env.players[playerindex]->tank == this) )
+ found = true;
+ else
+ playerindex++;
+ }
+
+ // we should have found a match and now we send it to all clients
+ if (found) {
+ snprintf(buffer, 14, "REMOVETANK %d", playerindex);
+ env.sendToClients(buffer);
+ }
+#endif // NETWORK
+
+ // If vengeance is not allowed, break up here.
+ if (!allow_vengeance)
+ return;
+
+ // If violent death is enabled to be automatically,
+ // possibly sponsor one for the player unless they
+ // already have something better.
+ // But only if it is not the first 3 rounds.
+ if (env.violent_death && ( (env.rounds - global.currentround) > 3) ) {
+ int32_t ri = rand() % VIOLENT_CHANCE;
+
+ // Limit ri to the value of violent_death.
+ // This makes it less probable to trigger anything on lower settings.
+ if ( (ri <= VD_HEAVY) && (ri > env.violent_death) )
+ ri = env.violent_death;
+
+ // The entry is, that the player either has no fatal fury
+ // or is about to get one sponsored.
+ if (ri && (!player->ni[ITEM_FATAL_FURY] || (VD_HEAVY == ri)) ) {
+ if (VD_HEAVY == ri)
+ ++player->ni[ITEM_FATAL_FURY];
+ else if (!player->ni[ITEM_DYING_WRATH] || (VD_MEDIUM == ri)) {
+ if (VD_MEDIUM == ri)
+ ++player->ni[ITEM_DYING_WRATH];
+ else if (VD_LIGHT == ri)
+ ++player->ni[ITEM_VENGEANCE];
+ }
+ } // end of applying sponsorship to big boom
+ } // End of violent death sponsorship
+
+ // If the player has something to trigger in their last moment,
+ // do so now:
+ int32_t numLaunch = 0;
+ int32_t min_power = 0;
+ int32_t del_power = 0;
+
+ if (player->ni[ITEM_FATAL_FURY] > 0) {
+ numLaunch = item[ITEM_FATAL_FURY].vals[SELFD_NUMBER];
+ cw = item[ITEM_FATAL_FURY].vals[SELFD_TYPE];
+ player->nm[cw] += numLaunch;
+ player->ni[ITEM_FATAL_FURY]--;
+ min_power = MAX_POWER / 3;
+ del_power = MAX_POWER / 3;
+ } else if (player->ni[ITEM_DYING_WRATH] > 0) {
+ numLaunch = item[ITEM_DYING_WRATH].vals[SELFD_NUMBER];
+ cw = item[ITEM_DYING_WRATH].vals[SELFD_TYPE];
+ player->nm[cw] += numLaunch;
+ player->ni[ITEM_DYING_WRATH]--;
+ min_power = MAX_POWER / 4;
+ del_power = MAX_POWER / 3;
+ } else if (player->ni[ITEM_VENGEANCE] > 0) {
+ numLaunch = item[ITEM_VENGEANCE].vals[SELFD_NUMBER];
+ cw = item[ITEM_VENGEANCE].vals[SELFD_TYPE];
+ player->nm[cw] += numLaunch;
+ player->ni[ITEM_VENGEANCE]--;
+ min_power = MAX_POWER / 5;
+ del_power = MAX_POWER / 3;
+ }
+
+ // If there is anything to launch, do it
+ if (numLaunch) {
+ // Expensive equipment like this should come with a certain quality.
+ // The most important detail (right after actually going off and not
+ // being a dud) is that the bucks won't be blasted the wrong way.
+ TANK* tank = nullptr;
+ int32_t med_x = 0;
+ int32_t tanks = 0;
+
+ global.getHeadOfClass(CLASS_TANK, &tank);
+
+ while (tank) {
+ if ( (tank != this) && !tank->destroy
+ && ( (TEAM_NEUTRAL == player->team)
+ || (player->team != tank->player->team) ) ) {
+ ++tanks;
+ med_x += tank->x;
+ }
+ tank->getNext(&tank);
+ }
+
+ // Get the medium x position of all tanks (or the middle of the
+ // screen if this was the last tank... for the firework...)
+ if (tanks)
+ med_x /= tanks;
+ else
+ med_x = env.halfWidth;
+
+ int32_t start_a = 45;
+ int32_t mod_a = 90;
+
+ if (med_x < (x - 50) ) {
+ start_a = 30;
+ mod_a = 55;
+ } else if (med_x > (x + 50) ) {
+ start_a = 95;
+ mod_a = 55;
+ }
+
+ // Before the violent death is applied, halve the players
+ // damage multiplier:
+ assert(player && "ERROR: explode Tank without player?");
+ player->damageMultiplier = player->damageMultiplier > 1.
+ ? 1. + ((player->damageMultiplier - 1.) / 2.)
+ : .75;
+
+ // Now go for it!
+ int32_t cur_stage = global.stage;
+ global.stage = STAGE_FIRE;
+ for (int32_t i = numLaunch; i > 0; --i) {
+ a = 180 - (start_a + (rand() % mod_a) - 90);
+ p = min_power + (rand () % del_power);
+ activateCurrentSelection ();
+ }
+ global.stage = cur_stage;
+ }
}
-void TANK::drawTank (BITMAP *dest, int healthOffset)
+
+/// @return The tanks bottom coordinate as used in collision detection.
+int32_t TANK::getBottom()
{
- int turretAngle;
-
- // check for foggy weather
- if ( ( _env->fog ) && ( _global->currTank != this ) )
- {
- addUpdateArea ((int)(x - TANKWIDTH) - 3, (int)y - 25, 35, 46);
- requireUpdate ();
- return;
- }
+ return y + tank_off_y - tank_sag;
+}
- // get bitmap for tank
- if (player)
- {
- switch ( (int) player->tank_bitmap)
- {
- case CLASSIC_TANK:
- use_tank_bitmap = 8;
- use_turret_bitmap = 1;
- turret_x = x;
- turret_y = y + (TANKHEIGHT / 2);
- break;
- case BIGGREY_TANK:
- use_tank_bitmap = 9;
- use_turret_bitmap = 2;
- turret_y = y;
- turret_x = x;
- break;
- case T34_TANK:
- use_tank_bitmap = 10;
- use_turret_bitmap = 3;
- turret_y = y;
- turret_x = x;
- break;
- case HEAVY_TANK:
- use_tank_bitmap = 11;
- use_turret_bitmap = 4;
- turret_y = y;
- turret_x = x;
- break;
- case FUTURE_TANK:
- use_tank_bitmap = 12;
- use_turret_bitmap = 5;
- turret_y = y;
- turret_x = x;
- break;
- case UFO_TANK:
- use_tank_bitmap = 13;
- use_turret_bitmap = 6;
- turret_y = y;
- turret_x = x;
- break;
- case SPIDER_TANK:
- use_tank_bitmap = 14;
- use_turret_bitmap = 7;
- turret_y = y;
- turret_x = x;
- break;
- case BIGFOOT_TANK:
- use_tank_bitmap = 15;
- use_turret_bitmap = 8;
- turret_y = y;
- turret_x = x;
- break;
- case MINI_TANK:
- use_tank_bitmap = 16;
- use_turret_bitmap = 9;
- turret_y = y;
- turret_x = x;
- break;
- default:
- use_tank_bitmap = 0;
- use_turret_bitmap = 0;
- turret_x = x;
- turret_y = y;
- break;
- }
- }
- rectfill (dest, (int) x - (TANKWIDTH - 1),
- (int) (y + TANKHEIGHT) - 2,
- (int) (x + TANKWIDTH) - 2,
- (int) (y + TANKHEIGHT),
- this->player->color);
- // draw_sprite (dest, (BITMAP *) _global->gfxData.T[use_tank_bitmap].dat, (int) x - TANKWIDTH, (int) y);
+/// @return The calculated tank diameter
+double TANK::getDiameter()
+{
+ return tank_dia;
+}
-/*
- Drawing shields this way seems to cause a crash on multi-cpu
- systems. Taking this out and creating new shield
- drawing routine below. -- Jesse
-
- if (sh > 0)
- {
- int phaseValue = 1;
-
- drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
- _env->current_drawing_mode = DRAW_MODE_TRANS;
- set_trans_blender (0, 0, 0, (int)50);
- // avoid sub-index
- if ( shPhase < 0 )
- shPhase = 0;
- if (sht >= ITEM_LGT_SHIELD && sht <= ITEM_HVY_SHIELD)
- {
- phaseValue = (int)(_global->slope[(int)shPhase][0] * 3);
- }
- else if (sht >= ITEM_LGT_REPULSOR_SHIELD && sht <= ITEM_HVY_REPULSOR_SHIELD)
- {
- phaseValue = (int)(shPhase / 360 * 6);
- }
-
- ellipsefill (dest, (int) x, (int) y, TANKWIDTH + 6 + phaseValue, TANKHEIGHT - 1, shieldColor);
- set_trans_blender (0, 0, 0, (int)
- (50 + (((float) sh / (float) (item[sht]).vals[SHIELD_ENERGY]) * (float) 150)));
- for (int thicknessCount = 0; thicknessCount < shieldThickness; thicknessCount++)
- {
- ellipse (dest, (int) x - (shieldThickness / 2) + thicknessCount, (int) y,
- TANKWIDTH + 6 + phaseValue, TANKHEIGHT - 1, shieldColor);
- }
- drawing_mode(DRAW_MODE_SOLID, NULL, 0, 0);
- _env->current_drawing_mode = DRAW_MODE_SOLID;
- } // end of drawing shield
-*/
- if (sh > 0)
- {
- int thickness = 2;
- int counter;
- int wobble = (int) shPhase;
-
- if ( (sht == ITEM_LGT_SHIELD) || (sht == ITEM_LGT_REPULSOR_SHIELD) )
- thickness = 1;
- else if ( (sht == ITEM_HVY_REPULSOR_SHIELD) || (sht == ITEM_HVY_SHIELD) )
- thickness = 3;
-
- if (! shieldColor) // client may not have set colour
- {
- shieldColor = makecol ((int)item[sht].vals[SHIELD_RED],
- (int)item[sht].vals[SHIELD_GREEN],
- (int)item[sht].vals[SHIELD_BLUE]);
- }
- for (counter = 0; counter < thickness; counter++)
- circle(dest, (int) x, (int) y,
- TANKHEIGHT + counter + wobble, shieldColor);
- }
-
- draw_sprite (dest, (BITMAP *) _global->tank[use_tank_bitmap], (int) x - TANKWIDTH, (int) y);
- turretAngle = (int) ((float) (90 - a) * ((float) 256 / (float) 360));
- rotate_sprite (dest, (BITMAP *) _global->tankgun[use_turret_bitmap],
- (int) turret_x - GUNLENGTH, (int) turret_y - (GUNLENGTH - 2), itofix (turretAngle));
-
-
- // when using rockets, show flame
- if (yv < 0)
- {
- rectfill(dest, (int) x - TANKWIDTH, (int) y + TANKHEIGHT, x + TANKWIDTH, y + TANKHEIGHT + 10, makecol(250, 150, 0) );
- }
-
- if (sh > 0)
- setUpdateArea ((int)x - TANKWIDTH - 15, (int)y - TANKHEIGHT,
- ((TANKWIDTH + 15) * 2) + 1, (TANKHEIGHT * 2) + 20);
- else
- setUpdateArea ((int)x - GUNLENGTH - 1, (int)y - GUNLENGTH - 1,
- (GUNLENGTH * 2) + 2, TANKHEIGHT + GUNLENGTH + 20);
- if (para)
- {
- draw_sprite (dest, (BITMAP *) _global->tank[para],
- (int) (x - TANKWIDTH) - 3, (int) y - 25);
- addUpdateArea ((int)(x - TANKWIDTH) - 3, (int)y - 25, 35, 66);
- }
+/// Sets @a top_x and @a top_y to the coordinates of the cannon tip
+void TANK::getGuntop(int32_t angle_, double &top_x, double &top_y)
+{
+ top_x = x + (env.slope[angle_][0] * turr_off_x);
+ top_y = y + (env.slope[angle_][1] * turr_off_y);
- printHealth (healthOffset);
- requireUpdate ();
+ // top_y must not be lower than tank y. (Lower means greater in value)
+ if (top_y > y)
+ top_y = y;
}
-int TANK::get_heaviest_shield ()
+
+/// @return the current maxLife value
+int32_t TANK::getMaxLife()
{
- if (player->ni[ITEM_HVY_REPULSOR_SHIELD])
- {
- return ITEM_HVY_REPULSOR_SHIELD;
- }
- if (player->ni[ITEM_HVY_SHIELD])
- {
- return ITEM_HVY_SHIELD;
- }
- if (player->ni[ITEM_MED_REPULSOR_SHIELD])
- {
- return ITEM_MED_REPULSOR_SHIELD;
- }
- if (player->ni[ITEM_MED_SHIELD])
- {
- return ITEM_MED_SHIELD;
- }
- if (player->ni[ITEM_LGT_REPULSOR_SHIELD])
- {
- return ITEM_LGT_REPULSOR_SHIELD;
- }
- if (player->ni[ITEM_LGT_SHIELD])
- {
- return ITEM_LGT_SHIELD;
- }
- return ITEM_NO_SHIELD;
+ return maxLife;
}
-
-
-void TANK::simActivateCurrentSelection ()
+/// @brief return true if a repulsor shield is up and running
+bool TANK::hasRepulsorActivated()
{
- char buf[16];
+ return (repulsion != 0);
+}
- if (_global->turntype != TURN_SIMUL)
- {
- activateCurrentSelection();
- if (fire_another_shot)
- fire_another_shot--;
- }
- else
- {
- _env->stage = 1;
- }
- // allow naturals to happen again
- _env->naturals_since_last_shot = 0;
+/// @brief return the number of pixels a tanks canon is buried
+/// If @a left and or @a right are given, they will receive the buried
+/// level on that side only.
+int32_t TANK::howBuried (int32_t* left, int32_t* right)
+{
+ int32_t result = 0;
+ int32_t old_x = 0;
+ int32_t old_y = 0;
+ int32_t cur_x = 0;
+ int32_t cur_y = 0;
+ double angles_seen = 0.;
+
+ // Angles are from right (90) to left (270) counter clockwise
+ for (int32_t ta = 90; ta < 270; ++ta) {
+ cur_x = x + (env.slope[ta][0] * turr_off_x);
+ cur_y = y + (env.slope[ta][1] * turr_off_y);
+
+ if ( (cur_x != old_x) || (cur_y != old_y) ) {
+ if (PINK != getpixel(global.terrain, cur_x, cur_y))
+ ++result;
+ old_x = cur_x;
+ old_y = cur_y;
+ angles_seen += 1.;
+ }
+
+ if (180 == ta) {
+ if (right) *right = result;
+ if (left) *left = -result;
+ }
+ }
+
+ // Add full result to right to negate left half and count only right half
+ if (left) {
+ *left += result;
+ if (*left < 0)
+ *left = 0;
+ }
+
+ // The result must be adapted according how many *real* angles,
+ // meaning "missile starting points" are there.
+ // If boxed mode is on, the level is doubled, as the ceiling makes it
+ // very difficult to aim otherwise.
+ double angle_mod = 180. / angles_seen;
+
+ if (left) *left = ROUNDu(*left * angle_mod / 2.);
+ if (right) *right = ROUNDu(*right * angle_mod / 2.);
+
+ result *= angle_mod * (env.isBoxed ? 1.25 : 1.);
+
+ return ROUNDu(result);
+}
- // apply repairs
- l += repair_rate;
- if (l > maxLife)
- l = maxLife;
- sprintf (buf, "%d", l);
- healthText->set_text(buf);
- // avoid having key presses read in next turn
- clear_keybuf();
+/// @return true if the tank is moving up or downwards (rocket / fall / glide)
+bool TANK::isFlying()
+{
+ return (yv < 0.) || (yv > 0.);
}
+/// @return true if the tank is within the box defined by the given coordinates.
+bool TANK::isInBox(int32_t x1, int32_t y1, int32_t x2, int32_t y2)
+{
+ double gun_x, gun_y;
+ getGuntop(a, gun_x, gun_y);
+ return ( (std::min(x1, x2) < std::max(x + tank_off_x, gun_x) )
+ && (std::max(x1, x2) > std::min(x - tank_off_x, gun_x) )
+ && (std::min(y1, y2) < (y + tank_off_y) )
+ && (std::max(y1, y2) > std::min(y, gun_y) ) );
+}
-void TANK::activateCurrentSelection ()
+/** @return true if the tank is within the given ellipse.
+ * @param[in] ex Explosion x coordinate
+ * @param[in] ey Explosion y coordinate
+ * @param[in] rx Explosion x radius
+ * @param[in] ry Explosion y radius
+ * @param[out] in_rate_x The rate [0.;1.] of the tank x axis being in the ellipse.
+ * @param[out] in_rate_y The rate [0.;1.] of the tank y axis being in the ellipse.
+**/
+bool TANK::isInEllipse(double ex, double ey, double rx, double ry,
+ double &in_rate_x, double &in_rate_y)
{
- int z;
-
- // avoid firing weapons on exit in Windows
- if ( (_global->get_command() == GLOBAL_COMMAND_QUIT) ||
- (_global->get_command() == GLOBAL_COMMAND_MENU) )
- return;
-
- // remove status from top bar at next redraw
- if (_global->tank_status)
- _global->tank_status[0] = 0;
-
- _env->time_to_fall--;
- if (_env->time_to_fall < 0)
- // _env->time_to_fall = (rand() % MAX_GRAVITY_DELAY) + 1;
- _env->time_to_fall = (rand() % (int)_env->landSlideDelay) + 1;
-
- if (cw < WEAPONS)
- {
- player->changed_weapon = false;
- if (cw)
- player->nm[cw]--;
-
- _env->stage = 1;
- _env->am = weapon[cw].spread;
- _env->realm = _env->am;
-
- if (cw < BALLISTICS)
- {
- play_sample ((SAMPLE *) _global->sounds[weapon[cw].sound], _env->scaleVolume(255), 128, 1000, 0);
- for (z = 0; z < _env->am; z++)
- {
- MISSILE *newmis;
- double mxv,myv;
- int ca;
-
- ca = a + ((SPREAD * z) - (SPREAD * (_env->am - 1) / 2));
- double dPower = (double)p;
- if ((dPower < 200.0) && ((cw == RIOT_CHARGE) || (cw == RIOT_BLAST)))
- dPower = 200.0;
-
- mxv = _global->slope[ca][0] * dPower * (100.0 / _global->frames_per_second) / 100.0;
- myv = _global->slope[ca][1] * dPower * (100.0 / _global->frames_per_second) / 100.0;
-
- newmis = new MISSILE(_global, _env,
- turret_x + (_global->slope[ca][0] * GUNLENGTH) /*- mxv*/,
- turret_y + (_global->slope[ca][1] * GUNLENGTH) /*- myv*/,
- mxv, myv, cw);
- if (newmis)
- {
- newmis->physics = 0;
- newmis->age = 0;
- newmis->player = player;
- }
- else
- perror ( "tank.cc: Failed allocating memory for newmis in TANK::activateCurrentSelection");
- // set up volley
- if (! fire_another_shot)
- {
- fire_another_shot = weapon[cw].delay * VOLLY_DELAY;
- if ( weapon[cw].delay )
- {
- shots_fired = shots_fired - (weapon[cw].delay - 1);
- }
- }
-
- if (player->ni[ITEM_DIMPLEP])
- {
- player->ni[ITEM_DIMPLEP]--;
- newmis->drag *= item[ITEM_DIMPLEP].vals[0];
- }
- else if (player->ni[ITEM_SLICKP])
- {
- player->ni[ITEM_SLICKP]--;
- newmis->drag *= item[ITEM_SLICKP].vals[0];
- }
- }
- }
- else // BEAM weapon
- {
- play_sample ((SAMPLE *) _global->sounds[weapon[cw].sound], _env->scaleVolume(255), 128, 1000, 0);
- for (z = 0; z < _env->am; z++)
- {
- BEAM *newbeam;
- int ca;
-
- ca = a + ((SPREAD * z) - (SPREAD * (_env->am - 1) / 2));
-
- newbeam = new BEAM (_global, _env,
- turret_x + (_global->slope[ca][0] * GUNLENGTH),
- turret_y + (_global->slope[ca][1] * GUNLENGTH),
- ca, cw);
- if (newbeam)
- {
- newbeam->physics = 0;
- newbeam->age = 0;
- newbeam->player = player;
- }
- else
- perror ( "tank.cc: Failed allocating memory for newbeam in TANK::activateCurrentSelection");
- }
-
- }
- }
- else // activate an item
- {
- int itemNum = cw - WEAPONS;
- if (itemNum < ITEM_VENGEANCE || itemNum > ITEM_FATAL_FURY)
- player->ni[itemNum]--;
- _env->stage = 1;
- if (itemNum == ITEM_TELEPORT)
- {
- int teleXDest = (rand () % (_global->screenWidth - TANKWIDTH * 2)) + TANKWIDTH;
- int teleYDest = (rand () % (_global->screenHeight - TANKHEIGHT * 2)) + TANKHEIGHT;
- TELEPORT *teleport;
- creditTo = player;
- teleport = new TELEPORT (_global, _env, this, teleXDest, teleYDest, TANKHEIGHT * 4 + GUNLENGTH, 120);
- if (!teleport)
- {
- perror ( "tank.cc: Failed allocating memory for teleport in TANK::activateCurrentSelection");
- // exit (1);
- }
- }
- else if (itemNum == ITEM_SWAPPER)
- {
- int random_tank_number;
- TANK *other_tank;
-
- // pick a random tank (not us)
- random_tank_number = rand() % _global->numTanks;
- other_tank = _env->order[random_tank_number];
- while ( (! other_tank) || (other_tank == this) )
- {
- random_tank_number++;
- if (random_tank_number > _global->maxNumTanks)
- random_tank_number = 0;
- other_tank = _env->order[random_tank_number];
- }
- creditTo = player;
- // create a teleport ojbect for this tank
- new TELEPORT (_global, _env, this, (int) other_tank->x, (int) other_tank->y, TANKHEIGHT * 4 + GUNLENGTH, 120);
- // create a teleport object for the other tank
- new TELEPORT (_global, _env, other_tank, (int) x, (int) y, TANKHEIGHT * 4 + GUNLENGTH, 120);
-
- }
- else if (itemNum == ITEM_MASS_TELEPORT)
- {
- int count;
- int XDest, YDest;
- TANK *current_tank;
-
- for (count = 0; count < _global->numPlayers; count++)
- {
- current_tank = _global->players[count]->tank;
- if (current_tank)
- {
- XDest = (rand () % (_global->screenWidth - TANKWIDTH * 2)) + TANKWIDTH;
- YDest = (rand () % (_global->screenHeight - TANKHEIGHT * 2)) + TANKHEIGHT;
- creditTo = player;
- new TELEPORT(_global, _env, current_tank, XDest, YDest, TANKHEIGHT * 4 + GUNLENGTH, 120);
- }
- }
-
- }
-
- else if ( itemNum == ITEM_ROCKET )
- {
- yv = -10;
- y -= 10;
- if (a < 180)
- {
- xv += 0.3;
- }
- else if (a > 180)
- {
- xv -= 0.3;
- }
-
- applyPhysics();
- }
-
- else if ( itemNum == ITEM_FAN )
- {
- // play wind sound
- play_sample ((SAMPLE *) _global->sounds[2], _env->scaleVolume(255), 128, 1000, 0);
- if (a < 180) // move wind to the right
- _env->wind += (p / 20);
- else // wind to the left
- _env->wind -= (p / 20);
-
- // make sure wind is not too strong
- if (_env->wind < (-_env->windstrength / 2) )
- _env->wind = -_env->windstrength / 2;
- else if (_env->wind > (_env->windstrength / 2) )
- _env->wind = _env->windstrength / 2;
-
- _env->lastwind = _env->wind;
-
- }
- else if ((itemNum >= ITEM_VENGEANCE) &&
- (itemNum <= ITEM_FATAL_FURY))
- {
- creditTo = player;
- damage = l + sh;
- }
+ in_rate_x = 0.;
+ in_rate_y = 0.;
+
+ // The real gun tip height:
+ double gun_y_off = env.slope[a][1] * turr_off_y;
+
+ // But not below the tank:
+ if (gun_y_off > 0.)
+ gun_y_off = 0.;
+
+ // For the tank the real centre position and the radii must be known.
+ double ox = static_cast<double>(tank_off_x);
+ double oy = static_cast<double>(tank_off_y - gun_y_off) / 2.;
+ double tx = x; // For consistencies sake...
+ double ty = y + (static_cast<double>(tank_off_y + gun_y_off) / 2.);
+
+ double simpDist = FABSDISTANCE2(ex, ey, tx, ty);
+
+ // First handle a special case: The explosion takes place within the
+ // tank defined ellipse. This is a full direct hit.
+ if (simpDist <= (tank_dia / 2.)) {
+ in_rate_x = 1.;
+ in_rate_y = 1.;
+ DEBUG_LOG_PHY("Full Direct Hit", "=== T %d/%d (%dx%d) versus E %d/%d (%dx%d) ===",
+ ROUND(tx), ROUND(ty), ROUND(ox), ROUND(oy),
+ ROUND(ex), ROUND(ey), ROUND(rx), ROUND(ry))
+ DEBUG_LOG_PHY("Full Direct Hit", "Distance %5.2lf <= Dia half %5.2lf",
+ simpDist, tank_dia / 2.)
+ return true;
}
- // if we are out of this type of weapon
- // then switch to another
- if (! player->nm[cw] )
- {
- cw = WEAPONS - 1;
- while ( (cw > 0) && (! player->nm[cw] ) )
- cw--;
- player->changed_weapon = true;
- }
- shots_fired++;
- player->time_left_to_fire = _global->max_fire_time;
+ /* The ideal solution would be to calculate the intersection of the two
+ * ellipses which would enable us to tell the absolute correct value of how
+ * much of the tank surface is covered by the explosion.
+ *
+ * Unfortunately this involves a 4th order equation to allow a numerical
+ * solution. (I have found a very nice example written in JavaScript. It
+ * uses several functions and has ~600 Lines. A bit much for a game, right?)
+ *
+ * The second best solution would be to determine the position on the rim of
+ * each ellipse that is on a line between the two centres and then use their
+ * distance. That is, if you blame this file and look up the commits, what I
+ * tried first.
+ * Unfortunately the exact location on the rim is almost as complex to
+ * determine as the intersection. I did not see this in the beginning,
+ * because I simply reused an algorithm of mine to check spheres in a 3D
+ * space for collisions. But the hard truth is, that 3D spheres and 2D
+ * ellipses are very different.
+ *
+ * So here is solution three. Still good enough for a game though. Just
+ * check how much the x and y dimensions overlap and do a rough intersection
+ * in two dimensions by reverse calculating the other axis. (This allows a
+ * finer control of the damage for special shots like shaped charges,
+ * though)
+ */
+
+
+ // If shields are on, they must be taken into account
+ if (sh > 0.) {
+ ox = shld_rad_x;
+ oy = shld_rad_y;
+ }
+
+ // Determine start and end positions of explosion/tank intersections.
+ // The check is needed so hit/end are in the correct direction.
+ double hit_x = ex > tx ? std::min(ex + rx, tx + ox) : std::max(ex - rx, tx - ox);
+ double end_x = ex > tx ? std::max(ex - rx, tx - ox) : std::min(ex + rx, tx + ox);
+ double hit_y = ey > ty ? std::min(ey + ry, ty + oy) : std::max(ey - ry, ty - oy);
+ double end_y = ey > ty ? std::max(ey - ry, ty - oy) : std::min(ey + ry, ty + oy);
+ /* Check:
+ * ex = 100, tx = 150, rx = 40, ox = 20
+ * hit_x = 100 > 150 ? ... : max(100 - 40, 150 - 20) = max(60, 130) = 130
+ * end_x = 100 > 150 ? ... : min(100 + 40, 150 + 20) = min(140, 170) = 140
+ * ex/tx swapped:
+ * hit_x = 150 > 100 ? min(150 + 40, 100 + 20) = min(190, 120) = 120 : ...
+ * end_x = 150 > 100 ? max(150 - 40, 100 - 20) = max(110, 80) = 110 : ...
+ *
+ * This means start to end is always from explosion to tank.
+ */
+
+
+ // Check whether the tank is in the explosion if both were boxes.
+ if ( (SIGN(ex - tx) != SIGN(hit_x - end_x))
+ || (SIGN(ey - ty) != SIGN(hit_y - end_y)) )
+ return false;
+
+
+ // Handle another special case: The tank is fully within the explosion:
+ if ( (std::min(hit_x, end_x) <= (tx - ox))
+ && (std::min(hit_y, end_y) <= (ty - oy))
+ && (std::max(hit_x, end_x) >= (tx + ox))
+ && (std::max(hit_y, end_y) >= (ty + oy)) ) {
+ in_rate_x = ((rx - std::abs(tx - ex) ) / rx) + 0.5;
+ in_rate_y = ((ry - std::abs(ty - ey) ) / ry) + 0.5;
+ if (in_rate_x > 1.0)
+ in_rate_x = 1.0;
+ if (in_rate_y > 1.0)
+ in_rate_y = 1.0;
+
+ // The full in_rate in "full washing over" must not be lower than 1/3
+ if (in_rate_x < 0.578)
+ in_rate_x = 0.578;
+ if (in_rate_y < 0.578)
+ in_rate_y = 0.578;
+
+ // Note: This value is taken, because 0.578 * 0.578 = 0.3341 (~1/3)
+
+ DEBUG_LOG_PHY("Full Indirect Hit", "=== T %d/%d (%dx%d) versus E %d/%d (%dx%d) ===",
+ ROUND(tx), ROUND(ty), ROUND(ox), ROUND(oy),
+ ROUND(ex), ROUND(ey), ROUND(rx), ROUND(ry))
+ DEBUG_LOG_PHY("Full Indirect Hit", "Left %d <= %d",
+ ROUNDu(std::min(hit_x, end_x)), ROUNDu(tx - ox))
+ DEBUG_LOG_PHY("Full Indirect Hit", "Top %d <= %d",
+ ROUNDu(std::min(hit_y, end_y)), ROUNDu(ty - oy))
+ DEBUG_LOG_PHY("Full Indirect Hit", "Right %d >= %d",
+ ROUNDu(std::max(hit_x, end_x)), ROUNDu(tx + ox))
+ DEBUG_LOG_PHY("Full Indirect Hit", "Bottom %d >= %d",
+ ROUNDu(std::max(hit_y, end_y)), ROUNDu(ty + oy))
+ DEBUG_LOG_PHY("Full Indirect Hit", "in_rate_x : %5.2lf", in_rate_x)
+ DEBUG_LOG_PHY("Full Indirect Hit", "in_rate_y : %5.2lf", in_rate_y)
+ DEBUG_LOG_PHY("Full Indirect Hit", "Distance %5.2lf > Dia half %5.2lf",
+ simpDist, tank_dia / 2.)
+
+ return true;
+ }
+
+ // Get the middle of the x range and the explosions and tanks y rim there
+ double mid_x = (hit_x + end_x) / 2.;
+ double e_off_y = std::abs(ry * std::sin(std::acos((ex - mid_x) / rx)));
+ double t_off_y = std::abs(oy * std::sin(std::acos((tx - mid_x) / ox)));
+ double rng_y = std::min(ey + e_off_y, ty + t_off_y)
+ - std::max(ey - e_off_y, ty - t_off_y);
+ /* Note:
+ * If the explosion is within the tank range:
+ * ey+off < ty+off, ey-off > ty-off => rng = (ey+off) - (ey+off) => rng > 0
+ * If the explosion is above the tank but reaches in:
+ * ey+off < ty+off, ey-off < ty-off => rng = (ey+off) - (ty-off) => rng > 0
+ * If the explosion is below the tank but reaches is:
+ * ey+off > ty+off, ey-off > ty-off => rng = (ty+off) - (ey-off) => rng > 0
+ * If the tank is fully within the explosion:
+ * ey+off > ty+off, ey-off < ty-off => rng = (ty+off) - (ty-off) => rng > 0
+ * If the explosion is fully above the tank:
+ * ey+off < ty+off, ey-off < ty-off => rng = (ey+off) - (ty+off) => rng < 0
+ * If the explosion is fully below the tank:
+ * ey+off > ty+off, ey-off > ty-off => rng = (ty+off) - (ey-off) => rng < 0
+ */
+
+
+ // Opt out if the explosions rim y does not fit the tanks rim y
+ if ( rng_y < .01 ) {
+ DEBUG_LOG_PHY("Opt Out Y", "=== T %d/%d (%dx%d) versus E %d/%d (%dx%d) ===",
+ ROUND(tx), ROUND(ty), ROUND(ox), ROUND(oy),
+ ROUND(ex), ROUND(ey), ROUND(rx), ROUND(ry))
+ DEBUG_LOG_PHY("Opt Out Y", "ey - ty (%d - %d = %d / %5.3lf)",
+ ROUND(ey), ROUND(ty), ROUND(ey - ty), rng_y)
+ DEBUG_LOG_PHY("Opt Out Y", "Exp_y (%d to %d = %d)",
+ ROUND(ey - e_off_y), ROUND(ey + e_off_y), ROUND(2 * e_off_y))
+ DEBUG_LOG_PHY("Opt Out Y", "Tnk_y (%d to %d = %d)",
+ ROUND(ty - t_off_y), ROUND(ty + t_off_y), ROUND(2 * t_off_y))
+ return false;
+ }
+
+ // Get the middle of the y range and the explosions x rim there
+ double mid_y = (hit_y + end_y) / 2.;
+ double e_off_x = std::abs(rx * std::cos(std::asin((ey - mid_y) / ry)));
+ double t_off_x = std::abs(ox * std::cos(std::asin((ty - mid_y) / oy)));
+ double rng_x = std::min(ex + e_off_x, tx + t_off_x)
+ - std::max(ex - e_off_x, tx - t_off_x);
+
+ // Opt out if the explosions rim x does not fit the tanks rim x
+ if ( rng_x < .01 ) {
+ DEBUG_LOG_PHY("Opt Out X", "=== T %d/%d (%dx%d) versus E %d/%d (%dx%d) ===",
+ ROUND(tx), ROUND(ty), ROUND(ox), ROUND(oy),
+ ROUND(ex), ROUND(ey), ROUND(rx), ROUND(ry))
+ DEBUG_LOG_PHY("Opt Out X", "ex - tx (%d - %d = %d / %5.3lf)",
+ ROUND(ex), ROUND(tx), ROUND(ex - tx), rng_x)
+ DEBUG_LOG_PHY("Opt Out X", "Exp_x (%d to %d = %d)",
+ ROUND(ex - e_off_x), ROUND(ex + e_off_x), ROUND(2 * e_off_x))
+ DEBUG_LOG_PHY("Opt Out X", "Tnk_x (%d to %d = %d)",
+ ROUND(tx - t_off_x), ROUND(tx + t_off_x), ROUND(2 * t_off_x))
+ return false;
+ }
+
+
+ // Being here means that both ranges lie within the tanks ellipse.
+ in_rate_x = rng_x / ( ox * 2. );
+ in_rate_y = rng_y / ( oy * 2. );
+
+ assert ( (in_rate_x > 0.) && (in_rate_y > 0.) && "RANGE ERROR");
+
+ DEBUG_LOG_PHY("Overlapping", "=== T %d/%d (%dx%d) versus E %d/%d (%dx%d) ===",
+ ROUND(tx), ROUND(ty), ROUND(ox), ROUND(oy),
+ ROUND(ex), ROUND(ey), ROUND(rx), ROUND(ry))
+ DEBUG_LOG_PHY("Overlapping", "Hit: %4d/%4d ; End: %4d/%4d",
+ ROUNDu(std::min(hit_x, end_x)), ROUNDu(std::min(hit_y, end_y)),
+ ROUNDu(std::max(hit_x, end_x)), ROUNDu(std::max(hit_y, end_y)) )
+ DEBUG_LOG_PHY("Overlapping", "Exp: %4d/%4d ; Tnk %4d/%4d ; Rng %4d/%4d",
+ ROUNDu(e_off_x), ROUNDu(e_off_y),
+ ROUNDu(t_off_y), ROUNDu(t_off_y),
+ ROUNDu(rng_x), ROUNDu(rng_y) )
+ DEBUG_LOG_PHY("Overlapping", "in_rate_x : %5.2lf", in_rate_x)
+ DEBUG_LOG_PHY("Overlapping", "in_rate_y : %5.2lf", in_rate_y)
+
+ // distances over 50% radius are taken to reduce the rates further
+ double dist_x_mod = ((rx - std::abs(tx - ex) ) / rx) + 0.5;
+ double dist_y_mod = ((ry - std::abs(ty - ey) ) / ry) + 0.5;
+
+ // Limit to 0.578 to 1.0 (See full indirect hit why 0.578 is used)
+ if (dist_x_mod > 1.0)
+ dist_x_mod = 1.0;
+ if (dist_x_mod < 0.578)
+ dist_x_mod = 0.578;
+ if (dist_y_mod > 1.0)
+ dist_y_mod = 1.0;
+ if (dist_y_mod < 0.578)
+ dist_y_mod = 0.578;
+
+ DEBUG_LOG_PHY("Overlapping", "dist_x_mod : %5.2lf", dist_x_mod)
+ DEBUG_LOG_PHY("Overlapping", "dist_y_mod : %5.2lf", dist_y_mod)
+
+ in_rate_x *= dist_x_mod;
+ in_rate_y *= dist_y_mod;
+
+ // Trim both rates to 1.0
+ if (in_rate_x > 1.0)
+ in_rate_x = 1.0;
+ if (in_rate_y > 1.0)
+ in_rate_y = 1.0;
+
+ DEBUG_LOG_PHY("Overlapping", "Final x : %5.2lf", in_rate_x)
+ DEBUG_LOG_PHY("Overlapping", "Final y : %5.2lf", in_rate_y)
+
+ return true;
+}
- // if not performing a volly and the player is AI then randomly select next weapon
- // else if ( ( player->type > HUMAN_PLAYER ) && (! fire_another_shot) )
- // cw = player->Select_Random_Weapon();
+/** @brief move the tank one unit
+ * This function tries to move the tank either left or right one
+ * unit.
+ *
+ * @param[in] direction The direction to move, DIR_RIGHT or DIR_LEFT.
+ *
+ * @return true if the tank was moved, false otherwise
+**/
+bool TANK::moveTank(int32_t direction)
+{
+ // Return false now if there is no fuel or the tank is currently flying
+ if ( (player->ni[ITEM_FUEL] < 1 ) || (yv < 0.) || (yv > 0.) )
+ return false;
+
+ // Safety: assert DIR_LEFT/RIGHT
+ assert ( ((DIR_LEFT == direction) || (DIR_RIGHT == direction))
+ && "ERROR: Call moveTank with either DIR_LEFT or DIR_RIGHT!");
+ if ( (DIR_LEFT != direction) && (DIR_RIGHT != direction))
+ return false;
+
+ // Check whether the target pixel is beyond the border or occupied
+ int32_t next_x = x + direction;
+ int32_t min_y = y + tank_off_y - tank_sag - 4 ;
+
+ if ( (next_x < 1)
+ || (next_x >= env.screenWidth)
+ || (env.landType == LAND_NONE) )
+ return false;
+
+ if (PINK != getpixel(global.terrain, next_x, min_y))
+ return false;
+
+ // move tank
+ x += direction;
+ player->ni[ITEM_FUEL]--;
+
+ // Allow the tank to move up a bit if necessary
+ y = min_y + 5;
+ if (y > env.screenHeight - 1)
+ y = env.screenHeight - 1;
+
+ while ( ( y > min_y ) && (PINK != getpixel(global.terrain, x, y)))
+ --y;
+ // Now fix y back to normal coordinate
+ y -= tank_off_y - tank_sag;
+
+ // But secure y
+ if (y > (env.screenHeight - tank_off_y))
+ y = env.screenHeight - tank_off_y;
+
+ return true;
}
-void TANK::boost_up_shield ()
+void TANK::newRound (int32_t pos_x, int32_t pos_y)
{
- char buf[10];
- int s = get_heaviest_shield ();
-
- if ((s != ITEM_NO_SHIELD) && (player->ni[s] > 0))
- {
- player->ni[s]--;
- sh = (int)item[s].vals[SHIELD_ENERGY];
- repulsion = (int)item[s].vals[SHIELD_REPULSION];
- shieldColor = makecol ((int)item[s].vals[SHIELD_RED],
- (int)item[s].vals[SHIELD_GREEN],
- (int)item[s].vals[SHIELD_BLUE]);
- shieldThickness = (int)item[s].vals[SHIELD_THICKNESS];
- sht = s;
- ds = sht;
- player->last_shield_used = s;
- }
- if (sh)
- {
- sprintf (buf, "%d", sh);
- shieldText->set_text (buf);
- }
- else
- {
- shieldText->set_text (NULL);
- }
+ // A new round without set player is futile.
+ assert(player && "ERROR: TANK::newRound called with nullptr player");
+ if (nullptr == player)
+ return;
+
+ static char buf[10] = { 0x0 };
+
+ // Reclaim shield if there is one left from end of last round
+ player->reclaimShield();
+
+ // Reset all values
+ cw = 0;
+ damage = 0.;
+ para = 0;
+ creditTo = nullptr;
+ p = MAX_POWER / 2;
+ a = (rand () % 150) + 105;
+ sh = 0;
+ sht = ITEM_NO_SHIELD;
+ repulsion = 0;
+ delay_fall = env.landSlideDelay * 100;
+
+ // Re-calculate max life
+ double tmpL = (player->ni[ITEM_ARMOUR] * item[ITEM_ARMOUR].vals[0])
+ + (player->ni[ITEM_PLASTEEL] * item[ITEM_PLASTEEL].vals[0]);
+ maxLife = 100 + (tmpL > 0. ? static_cast<int32_t>(std::pow(tmpL, .6)) : 0);
+ l = maxLife;
+
+ // (re)-init health text
+ snprintf (buf, 9, "%d", l);
+ healthText.set_text (buf);
+ healthText.set_color(player->color);
+
+ // Re-calculate repair rate
+ int32_t num_kits = player->ni[ITEM_REPAIRKIT];
+ int32_t increase_amount = 5;
+ repair_rate = 0;
+ while (num_kits-- > 0) {
+ repair_rate += increase_amount;
+ if (increase_amount > 1)
+ --increase_amount;
+ }
+
+ // (re-)init name text
+ if (env.nameAboveTank) {
+ nameText.set_text ( player->getName() );
+ nameText.set_color( player->color );
+ }
+
+ fire_another_shot = 0;
+
+ // Set used bitmaps, determine offsets and place tank
+ x = pos_x;
+ y = pos_y;
+ use_tankbitmap = -1;
+ use_turretbitmap = -1;
+ setBitmap();
}
+
+
void TANK::reactivate_shield ()
{
- if (!sh) //if no shield remains, try to reload
- {
- boost_up_shield ();
- }
+ // if no shield remains, try to reload
+ if (sh > 0)
+ return;
+
+ static char buf[5] = { 0x0 };
+
+ sht = ITEM_NO_SHIELD;
+
+ // Try to set the most heavy shield there is available:
+ if (player->ni[ITEM_HVY_REPULSOR_SHIELD])
+ sht = ITEM_HVY_REPULSOR_SHIELD;
+ else if (player->ni[ITEM_HVY_SHIELD])
+ sht = ITEM_HVY_SHIELD;
+ else if (player->ni[ITEM_MED_REPULSOR_SHIELD])
+ sht = ITEM_MED_REPULSOR_SHIELD;
+ else if (player->ni[ITEM_MED_SHIELD])
+ sht = ITEM_MED_SHIELD;
+ else if (player->ni[ITEM_LGT_REPULSOR_SHIELD])
+ sht = ITEM_LGT_REPULSOR_SHIELD;
+ else if (player->ni[ITEM_LGT_SHIELD])
+ sht = ITEM_LGT_SHIELD;
+
+ if (ITEM_NO_SHIELD != sht) {
+ player->ni[sht]--;
+ sh = item[sht].vals[SHIELD_ENERGY];
+ repulsion = item[sht].vals[SHIELD_REPULSION];
+ shld_col_outer = makecol (item[sht].vals[SHIELD_RED],
+ item[sht].vals[SHIELD_GREEN],
+ item[sht].vals[SHIELD_BLUE]);
+ shld_thickness = item[sht].vals[SHIELD_THICKNESS];
+
+ player->last_shield_used = sht;
+ shld_phase = 0.; // Start neutral.
+ snprintf (buf, 4, "%d", sh);
+ shieldText.set_text (buf);
+ setTextPositions(true);
+ }
}
-int TANK::howBuried ()
+
+bool TANK::repulse (double xpos, double ypos, double* xa, double* ya,
+ ePhysType phys_type)
{
- int turrAngle;
- int buryCount = 0;
+ // If there is no repulsion or the physics type is
+ // not sensitive to repulsion, return at once.
+ if ( !repulsion
+ || (PT_FUNKY_FLOAT == phys_type)
+ || (PT_NONE == phys_type)
+ || (PT_ROLLING == phys_type) )
+ return false;
+
+ double xdist = xpos - x;
+ double ydist = ypos - (y - turr_off_y + shld_rad_y);
+
+ // Apply a minimum distance so no extreme catapult shots happen
+ if (std::abs(xdist) < 0.25) xdist = 0.25 * SIGNd(xdist);
+ if (std::abs(ydist) < 0.25) ydist = 0.25 * SIGNd(ydist);
+
+ // Unless this is a burying type that currently comes from below,
+ // repulsion is done upwards, assuming the projectile comes from above.
+ if ( ( (PT_DIGGING == phys_type) && (ydist < 0.) )
+ || ( (PT_DIGGING != phys_type) && (ydist > 0.) ) )
+ ydist *= -1.0; // Missiles normally come from above, diggers from below.
+
+ double distance2 = (xdist * xdist) + (ydist * ydist);
+ double distance = sqrt (distance2);
+
+ if (distance < (5. * std::sqrt(static_cast<double>(repulsion))) ) {
+ double rep_mod = PT_DIGGING == phys_type ? 0.15
+ : PT_DIRTBOUNCE == phys_type ? 0.66
+ : PT_SMOKE == phys_type ? 0.75
+ : 1.;
+ *xa = (repulsion * (xdist / distance) / distance2) * rep_mod * 0.75;
+ *ya = (repulsion * (ydist / distance) / distance2) * rep_mod * 1.50;
+ return true;
+ }
+
+ return false;
+}
- for (turrAngle = 90; turrAngle < 270; turrAngle++)
- {
- if (getpixel (_env->terrain, (int)(x + (_global->slope[turrAngle][0] * GUNLENGTH)), (int)(y + (_global->slope[turrAngle][1] * GUNLENGTH))) != PINK)
- buryCount++;
- }
- return (buryCount);
-}
-int TANK::shootClearance (int targetAngle, int minimumClearance)
+/// @brief Resets flash_damage and applies damage if flash_damage
+/// is greater than half the FPS (meaning ~0.5 seconds) or the tank is dead.
+void TANK::resetFlashDamage()
{
- int clearance = 2;
- int iXpos, iYpos;
- do
- {
- iXpos = (int)(x + (_global->slope[targetAngle][0] * (GUNLENGTH + clearance)));
- iYpos = (int)(y + (_global->slope[targetAngle][1] * (GUNLENGTH + clearance)));
- if ((iYpos <= MENUHEIGHT) || (iXpos <= 1) || (iXpos >= (_global->screenWidth - 2)))
- clearance = minimumClearance; // done it! There can't be dirt any more!
- else
- clearance++;
- }
- while ((clearance < minimumClearance) && (getpixel(_env->terrain, iXpos, iYpos) == PINK));
-
- // If we are going for a particular minimumClearance (< screenWidth), it is important whether we hit a wall
- if (minimumClearance < _global->screenWidth)
- {
- int iWall = _env->current_wallType;
- if ( _global->bIsBoxed && (iYpos <= MENUHEIGHT)
- &&((iWall == WALL_STEEL) || (iWall == WALL_WRAP)) )
- clearance = -1; // Wall hit on ceiling
- if ( ((iXpos <= 1) || (iXpos >= (_global->screenWidth - 2)))
- &&(iWall == WALL_STEEL) )
- clearance = -1; // Wall hit on sides
- }
-
- return (clearance >= minimumClearance?1:0);
+ if ( (flashdamage > (env.frames_per_second / 2)) || destroy ) {
+ flashdamage = 0;
+ if (ROUND(damage) > 0)
+ applyDamage();
+ requireUpdate();
+ }
}
-int TANK::isSubClass (int classNum)
+
+void TANK::setBitmap()
{
- if (classNum == TANK_CLASS)
- return (TRUE);
- else
- return (FALSE);
- //return (PHYSICAL_OBJECT::isSubClass (classNum));
+ if (!player)
+ return;
+
+ bool had_offsets = ((use_tankbitmap > -1) && (use_turretbitmap > -1));
+
+ if (TT_NORMAL == player->tankbitmap) {
+ use_tankbitmap = 0;
+ use_turretbitmap = 0;
+ } else {
+ use_tankbitmap = player->tankbitmap + TO_TANK;
+ use_turretbitmap = player->tankbitmap + TO_TURRET;
+ }
+
+ // Set needed offsets
+ tank_off_x = ROUNDu( env.tank[use_tankbitmap]->w / 2);
+ tank_off_y = env.tank[use_tankbitmap]->h;
+ tank_sag = ROUNDu(static_cast<double>(tank_off_y) / 2.66);
+ turr_off_x = ROUNDu( env.tankgun[use_turretbitmap]->w / 2);
+ turr_off_y = ROUNDu(env.tankgun[use_turretbitmap]->h / 2) - 2;
+ shld_rad_x = tank_off_x + (turr_off_x / 2) + 1;
+ shld_rad_y = ((tank_off_y + turr_off_y) / 2) + 1;
+
+ tank_dia = FABSDISTANCE2(2. * std::max(tank_off_x, turr_off_x),
+ tank_off_y + (std::min(turr_off_x, turr_off_y) / 2.),
+ 0., 0.);
+
+ // If these are new offsets, the position of the tank is too low and must be fixed:
+ if (!had_offsets)
+ y -= (tank_off_y - tank_sag);
+
+ // Be sure the placement is sane:
+ assert( ((x - tank_off_x) > 2) && "Placement too far left");
+ assert( ((x + tank_off_x) < (env.screenWidth - 3)) && "Placement too far right");
+
+ // Without debug mode, this must be fixed:
+ if ( (x - tank_off_x) < 3)
+ x = tank_off_x + 3;
+ if ( (x + tank_off_x) > (env.screenWidth - 4) )
+ x = env.screenWidth - 4 - tank_off_x;
}
-
-/*
-This function checks to see if there is a tank directly below this
-one. This is to determine if we landed on someone.
-The function returns TRUE if we landed on another tank and
-FALSE if we did not.
--- Jesse
-*/
-int TANK::tank_on_tank( GLOBALDATA *global )
+void TANK::setTextPositions(bool renew_colour)
{
- int found_tank = FALSE;
- int player_count = 0;
- int delta_x, delta_y;
-
- while ( ( player_count < global->numPlayers ) && (! found_tank) )
- {
- // check to make sure this player is alive
- if ( global->players[player_count]->tank )
- {
- // make sure this isn't our own tank
- if ( ( global->players[player_count]->tank->x != x ) || (global->players[player_count]->tank->y != y ) )
- {
- // check to see if tanks are within TANK_WIDTH of each other's x
- delta_x = (int) (x - global->players[player_count]->tank->x);
- delta_y = (int) (y - global->players[player_count]->tank->y);
-
- if ( ( abs(delta_x) <= TANKWIDTH ) && ( (delta_y < 0) && (delta_y >= -TANKHEIGHT) ) )
- found_tank = TRUE;
-
- } // end of this is our own tank
- }
- player_count++;
- }
-
- return found_tank;
+ int32_t textpos = -12 - turr_off_x;
+
+ if (sh > 0) {
+ shieldText.set_pos (x, y + textpos);
+ textpos -= 14;
+ if (renew_colour)
+ shieldText.set_color(TURQUOISE);
+ } else
+ shieldText.set_pos(-1, -1);
+
+ healthText.set_pos (x, y + textpos);
+ textpos -= 14;
+ if (renew_colour)
+ healthText.set_color(player ? player->color : WHITE);
+
+ if (env.nameAboveTank) {
+ nameText.set_pos (x, y + textpos);
+ if (renew_colour)
+ shieldText.set_color(player ? player->color : WHITE);
+ }
}
-
-/*
-This function figures out how many points a tank
-will repair itself each turn. This is based
-on the number of repair kits a player has.
-The amount is returned as an int.
--- Jesse
-*/
-int TANK::Get_Repair_Rate()
+bool TANK::shootClearance (int32_t targetAngle, int32_t minimumClearance,
+ bool &crashed)
{
- int num_kits;
- int repair_units = 0;
- int increase_amount = 5;
-
- num_kits = player->ni[ITEM_REPAIRKIT];
- while (num_kits > 0)
- {
- repair_units += increase_amount;
- if (increase_amount > 1)
- increase_amount--;
- num_kits--;
- }
-
- return repair_units;
+ int32_t clearance = 2;
+ double xmov = env.slope[targetAngle][0];
+ double ymov = env.slope[targetAngle][1];
+ double xpos = x + (xmov * (turr_off_x + clearance));
+ double ypos = y + (ymov * (turr_off_x + clearance));
+ bool done = false;
+ crashed = false;
+
+ while (!done) {
+ xpos += xmov;
+ ypos += ymov;
+
+ if ( (ypos <= MENUHEIGHT)
+ || (xpos < 2) || (xpos > (env.screenWidth - 2) ) ) {
+ clearance = minimumClearance; // done it! There can't be dirt any more!
+ done = true;
+ } else {
+ if (++clearance >= minimumClearance)
+ done = true;
+ else {
+ if (PINK != getpixel(global.terrain, xpos, ypos))
+ done = true;
+ } // End of having to check a pixel
+ } // End of being within screen bounds
+ } // End of checking the path
+
+ // If a minimum clearance lower than the screen width is sought,
+ // check whether this results in a wall/ceiling hit.
+ if ( (minimumClearance < env.screenWidth)
+ && ( ( env.isBoxed && (ypos <= MENUHEIGHT)
+ && ( (WALL_STEEL == env.current_wallType)
+ || (WALL_WRAP == env.current_wallType) ) )
+ || ( ( WALL_STEEL == env.current_wallType)
+ && ( (xpos < 2 ) || (xpos > (env.screenWidth - 3)) ) ) ) ) {
+ clearance = -1;
+ crashed = true;
+ }
+
+ return (clearance >= minimumClearance);
}
-
-/*
-This function tries to move the tank either
-left or right one unit. The direction is passed
-in.
-The function returns TRUE if the tank is moved and
-FALSE if something is in the way or the
-tank cannot be moved for some reason.
--- Jesse
-*/
-int TANK::Move_Tank(int direction)
+/// @brief this is used whenever a weapon really is triggered.
+/// In simultaneous play this does not actually mean it is fired.
+/// To fire another shot without the trigger action, call
+/// activateCurrentSelection().
+void TANK::simActivateCurrentSelection ()
{
- int pixel;
- int destination_x;
-
- // do we have fuel?
- if ( player->ni[ITEM_FUEL] < 1 )
- return FALSE;
-
- // see where we want to go
- if (direction == DIR_RIGHT)
- destination_x = (int) (x + TANKWIDTH - 2);
- else
- destination_x = (int) (x - (TANKWIDTH + 1) );
-
- if (_env->landType != LANDTYPE_NONE)
- {
- // check for something in the way
- pixel = getpixel (_env->terrain, destination_x, (int) (y + (TANKHEIGHT / 2)) );
- if (pixel != PINK)
- return FALSE;
- }
+ static char buf[6] = { 0x0 };
- // move tank
- if (direction == DIR_RIGHT)
- x++;
- else
- x--;
+ if (env.turntype != TURN_SIMUL) {
+ activateCurrentSelection();
- player->ni[ITEM_FUEL]--;
+ if (fire_another_shot)
+ fire_another_shot--;
+ }
- return TRUE;
-}
+ // allow naturals to happen again
+ global.naturals_activated = 0;
-void TANK::setEnvironment(ENVIRONMENT *env)
-{
- if (!_env || (_env != env))
- {
- _env = env;
- _index = _env->addObject (this);
- }
+ // apply repairs
+ l += repair_rate;
+ if (l > maxLife)
+ l = maxLife;
+
+ snprintf (buf, 5, "%d", l);
+ healthText.set_text(buf);
+
+ // avoid having key presses read in next turn
+ clear_keybuf();
}
-/*
-Give credit to the tank who killed us.
-*/
-void TANK::Give_Credit(GLOBALDATA *global)
+/** @brief return true if this tank landed on another tank
+ *
+ * This function checks to see if there is a tank directly below this
+ * one. This is to determine if we landed on someone.
+ *
+ * @return true if the tank landed on another one, false otherwise
+**/
+bool TANK::tank_on_tank()
{
- if (creditTo)
- {
- // we shot ourselves
- if (creditTo == player)
- creditTo->money -= (int) global->scoreUnitSelfDestroy;
- else // we were killed by someone else
- creditTo->money += (int) global->scoreUnitDestroyBonus;
-
- // avoid over-flow
- if (creditTo->money < 0)
- creditTo->money = 0;
- creditTo = NULL;
- }
+ TANK* lt = nullptr;
+ bool found_tank = false;
+
+ global.getHeadOfClass(CLASS_TANK, <);
+ while (lt && !found_tank) {
+ if ( (lt != this)
+ && (std::abs(lt->x - x) < tank_off_x)
+ && (lt->y > y)
+ && ((lt->y - y) < tank_off_y) )
+ found_tank = true;
+ else
+ lt->getNext(<);
+ }
+
+ return found_tank;
}
-
diff --git a/src/tank.h b/src/tank.h
index cd27602..7705662 100644
--- a/src/tank.h
+++ b/src/tank.h
@@ -22,79 +22,128 @@
#include "physobj.h"
-
-#define TELETIME 120
+#include "floattext.h"
#define DIR_RIGHT 1
-#define DIR_LEFT 2
+#define DIR_LEFT -1
#define VIOLENT_CHANCE 6
class PLAYER;
class EXPLOSION;
-class FLOATTEXT;
class TANK: public PHYSICAL_OBJECT
- {
- protected:
- int _targetX,_targetY;
- int _prevTargetX,_prevTargetY;
- TANK *_target;
-
- public:
- int bestPower, bestAngle;
- int smallestOvershoot;
- int t, a, p, cw, l, sh, sht, fs, ds, para, pen;
- int maxLife; // amount awarded at beginning of round
- double damage;
- int repair_rate;
- int repulsion;
- int shieldColor, shieldThickness;
- int flashdamage, hit;
- int timer;
- int teleTimer;
- int teleXDest, teleYDest;
- double shPhase, delta_phase;
- PLAYER *creditTo;
- FLOATTEXT *healthText, *shieldText;//, *damageText;
- FLOATTEXT *nameText;
- int use_tank_bitmap, use_turret_bitmap;
- double turret_x, turret_y;
- int delay_fall; // time the tank will hover
- int fire_another_shot;
- int shots_fired;
-
- ~TANK ();
- TANK (GLOBALDATA *global, ENVIRONMENT *env);
- void initialise ();
- void Destroy();
- int get_heaviest_shield ();
- void boost_up_shield ();
- void reactivate_shield ();
- void drawTank (BITMAP *dest, int healthOffset);
- void printHealth (int offset);
- void update ();
- void requireUpdate ();
- void explode ();
- void simActivateCurrentSelection();
- void activateCurrentSelection ();
- int isSubClass (int classNum);
- inline virtual int getClass ()
- {
- return (TANK_CLASS);
- }
- virtual void setEnvironment(ENVIRONMENT *env);
- void applyDamage ();
- int applyPhysics ();
- void repulse (double xpos, double ypos, double *xaccel, double *yaccel, int aWeaponType);
- int howBuried ();
- int shootClearance (int targetAngle, int minimumClearance = MAX_OVERSHOOT);
- void newRound ();
- void framelyAccounting ();
- int tank_on_tank(GLOBALDATA *gd); //is this tank on top of another?
- int Get_Repair_Rate(); // how many units a tank will repair itself per round
- int Move_Tank(int direction);
- void Give_Credit(GLOBALDATA *global); // give credit to killer
- };
+{
+
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ explicit TANK ();
+ ~TANK ();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ void activate();
+ void activateCurrentSelection ();
+ void addDamage(PLAYER* damageFrom, double damage_);
+ void applyDamage();
+ void applyPhysics();
+ void check_weapon();
+ void deactivate();
+ void draw ();
+ void explode(bool allow_vengeance);
+ int32_t getBottom();
+ double getDiameter();
+ void getGuntop(int32_t angle_, double &top_x, double &top_y);
+ int32_t getMaxLife();
+ bool hasRepulsorActivated();
+ int32_t howBuried(int32_t* left, int32_t* right);
+ bool isFlying();
+ bool isInBox(int32_t x1, int32_t y1, int32_t x2, int32_t y2);
+ bool isInEllipse(double ex, double ey, double rx, double ry,
+ double &in_rate_x, double &in_rate_y);
+ bool moveTank(int32_t direction);
+ void newRound (int32_t pos_x, int32_t pos_y);
+ void reactivate_shield ();
+ bool repulse (double xpos, double ypos, double* xa, double* ya,
+ ePhysType phys_type);
+ void resetFlashDamage();
+ bool shootClearance (int32_t targetAngle, int32_t minimumClearance,
+ bool &crashed);
+ void simActivateCurrentSelection();
+
+ eClasses getClass() { return CLASS_TANK; }
+
+
+ /* ----------------------
+ * --- Public members ---
+ * ----------------------
+ */
+
+ int32_t a = 90; // [a]ngle
+ int32_t cw = SML_MIS; // [c]urrent [w]eapon
+ int32_t fire_another_shot = 0;
+ FLOATTEXT healthText;
+ int32_t l = 100; // [l]ive
+ FLOATTEXT nameText;
+ int32_t p = MAX_POWER / 2; // [p]ower
+ int32_t sh = 0; // [sh]ield
+ FLOATTEXT shieldText;
+ int32_t sht = 0; // [sh]ield [t]ype
+
+private:
+
+ /* -----------------------
+ * --- Private methods ---
+ * -----------------------
+ */
+
+ void setBitmap();
+ void setTextPositions(bool renew_colour);
+ bool tank_on_tank(); // is this tank on top of another?
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ PLAYER* creditTo = nullptr;
+ double damage = 0.;
+ CSpinLock damage_lock;
+ int32_t delay_fall = env.landSlideDelay * 100; // time the tank will hover
+ int32_t flashdamage = 0;
+ bool isTeleported = false; // Set to true if a teleport occurs to award falling damage.
+ int32_t maxLife = 100; // amount awarded at beginning of round
+ bool newDamager = false;
+ int32_t para = 0;
+ int32_t repair_rate = 0;
+ int32_t repulsion = 0;
+ int32_t shld_col_inner = BLACK;
+ int32_t shld_col_outer = BLACK;
+ double shld_delta = 360.; // divided by FPS in ctor
+ double shld_phase = 0.; // Neutral
+ int32_t shld_rad_x = 0; // Determined by the used bitmap
+ int32_t shld_rad_y = 0; // Determined by the used bitmap
+ int32_t shld_thickness = 0;
+ double tank_dia = 1.; // Tank diameter, determined by the used bitmap
+ int32_t tank_off_x = 0; // Determined by the used bitmap
+ int32_t tank_off_y = 0; // Determined by the used bitmap
+ int32_t tank_sag = 0; // Determined by the used bitmap
+ int32_t turr_off_x = 0; // Determined by the used bitmap
+ int32_t turr_off_y = 0; // Determined by the used bitmap
+ int32_t use_tankbitmap = -1;
+ int32_t use_turretbitmap = -1;
+};
+
+#define HAS_TANK 1
#endif
diff --git a/src/team.cpp b/src/team.cpp
deleted file mode 100644
index 598ba62..0000000
--- a/src/team.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-#include "tank.h"
-#include "team.h"
-#include "player.h"
-
-// check to see if a team has won.
-int Team_Won(GLOBALDATA *global)
-{
- bool all_jedi = true;
- bool all_sith = true;
- int current_player = 0;
- int my_team = 1;
- int player_count = 0;
-
- while ((all_jedi || all_sith) && ( current_player < global->numPlayers) )
- {
- if ( (global->players[current_player]->tank) &&
- (global->players[current_player]->tank->l) )
- {
- my_team = (int)global->players[current_player]->team;
- if ( (my_team == TEAM_JEDI) || (my_team == TEAM_NEUTRAL) )
- all_sith = false;
-
- if ( (my_team == TEAM_SITH) || (my_team == TEAM_NEUTRAL) )
- all_jedi = false;
- player_count++;
- }
- current_player++;
- }
-
- if (! player_count) return NO_WIN;
- if (all_jedi) return JEDI_WIN;
- else if (all_sith) return SITH_WIN;
- else return NO_WIN;
-}
-
diff --git a/src/team.h b/src/team.h
deleted file mode 100644
index 1a99671..0000000
--- a/src/team.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
-This file contains team related functions that
-do not fit anywhere else.
-*/
-
-#ifndef TEAM_HEADER_FILE__
-#define TEAM_HEADER_FILE__
-
-#include "globaldata.h"
-
-#define NO_WIN 0
-#define JEDI_WIN 100
-#define SITH_WIN 200
-
-// This function checks to see if one
-// team (Jedi or Sith) has won. Neutral
-// is not considered a team.
-// The function return the following:
-// 0 - no team won
-// JEDI_WON - Jedi team won
-// SITH_WON - Sith team won
-
-int Team_Won(GLOBALDATA *global);
-
-#endif
-
diff --git a/src/teleport.cpp b/src/teleport.cpp
index 6e8aed6..a9c3bc8 100644
--- a/src/teleport.cpp
+++ b/src/teleport.cpp
@@ -21,191 +21,205 @@
#include "environment.h"
#include "globaldata.h"
#include "teleport.h"
+#include "tank.h"
+#include "sound.h"
+
+#ifdef NETWORK
+# include "player.h"
+#endif
TELEPORT::~TELEPORT ()
{
- requireUpdate();
- update();
- _env->make_bgupdate (_current.x, _current.y, _current.w, _current.h);
- _env->make_bgupdate (_old.x, _old.y, _old.w, _old.h);
- _env->removeObject (this);
- _env = NULL;
- _global = NULL;
- object = NULL;
- remote = NULL;
+ requireUpdate();
+ update();
+ if (dim_cur.w > 0)
+ global.make_bgupdate (dim_cur.x, dim_cur.y, dim_cur.w, dim_cur.h);
+ if (dim_old.w > 0)
+ global.make_bgupdate (dim_old.x, dim_old.y, dim_old.w, dim_old.h);
+
+ if (remote) {
+ remote->destroy = true;
+ remote->remote = nullptr;
+ }
+
+ object = nullptr;
+ remote = nullptr;
+
+ // Take out of the chain:
+ global.removeObject(this);
}
-TELEPORT::TELEPORT (GLOBALDATA *global, ENVIRONMENT *env, VIRTUAL_OBJECT *targetObj, int destinationX, int destinationY,
- int objRadius, int duration):VIRTUAL_OBJECT(),clock(duration),startClock(duration),peaked(0),
- object(NULL),remote(NULL)
+TELEPORT::TELEPORT (VIRTUAL_OBJECT *targetObj,
+ int32_t destinationX, int32_t destinationY,
+ int32_t objRadius, int32_t duration, int32_t type):
+ VIRTUAL_OBJECT(),
+ clock(duration),
+ object(targetObj),
+ radius(objRadius),
+ startClock(duration)
{
- setEnvironment (env);
- player = NULL;
- _align = LEFT;
- _global = global;
- object = targetObj;
- radius = objRadius;
- remote = new TELEPORT (global, env, this, destinationX, destinationY);
- if (!remote)
- {
- perror ( "teleport.cc: Failed allocating memory for remote in TELEPORT::TELEPORT");
- }
- play_sample ((SAMPLE *) _global->sounds[item[ITEM_TELEPORT].sound], _env->scaleVolume(255), 128, 1000, 0);
-
- #ifdef NETWORK
- // this seems to be the teleport we usually use
- char buffer[64];
- int player_index = 0;
- bool found = false;
- TANK *the_tank = (TANK*) targetObj;
- // match the player with the tank
- while ( (player_index < global->numPlayers) && (! found) )
- {
- if ( ( global->players[player_index]->tank ) && (global->players[player_index]->tank == the_tank) )
- found = true;
- else
- player_index++;
- }
- if (found)
- {
- sprintf(buffer, "TELEPORT %d %d %d", player_index, destinationX, destinationY);
- global->Send_To_Clients(buffer);
- }
- #endif
+
+ if (object) {
+ x = object->x;
+ y = object->y;
+ }
+
+ // Ensure the destination is not occupied by another tank:
+ bool need_check = (type != ITEM_SWAPPER);
+ while (need_check) {
+ TANK* lt = nullptr;
+ need_check = false;
+
+ global.getHeadOfClass(CLASS_TANK, <);
+ while ( lt ) {
+ if ( (std::abs(lt->x - destinationX) < objRadius)
+ && (lt->y > destinationY)
+ && ((lt->y - destinationY) < objRadius) ) {
+ need_check = true;
+
+ // Maybe move left
+ if ( ( (destinationX > (objRadius * 2))
+ && (destinationX <= lt->x) )
+ || (destinationX >= (env.screenWidth - (objRadius * 2))) )
+ destinationX -= std::abs(lt->x - destinationX);
+ // Or move right
+ else if (destinationX < (env.screenWidth - (objRadius * 2)))
+ destinationX += std::abs(lt->x - destinationX);
+
+ // Maybe move up
+ if ( ( (destinationY > (MENUHEIGHT + (objRadius * 2)) )
+ && (destinationY <= lt->y) )
+ || (destinationY >= (env.screenHeight - (objRadius * 2))) )
+ destinationY -= std::abs(lt->y - destinationY);
+ // Or move down
+ else if (destinationY < (env.screenHeight - (objRadius * 2)))
+ destinationY += std::abs(lt->y - destinationY);
+ }
+
+
+ lt->getNext(<);
+ }
+ } // end of needing to check the destination
+
+ try {
+ remote = new TELEPORT (this, destinationX, destinationY);
+ } catch(std::bad_alloc &e) {
+ std::cerr << "Error creating TELEPORT: " << e.what() << std::endl;
+ perror ( "teleport.cc: Failed allocating memory for remote in TELEPORT::TELEPORT");
+ }
+
+ play_fire_sound(ITEM_TELEPORT + WEAPONS, x, 255, 1000);
+
+#ifdef NETWORK
+ // this seems to be the teleport we usually use
+ int playerindex = 0;
+ bool found = false;
+ TANK* the_tank = static_cast<TANK*>(targetObj);
+
+ // match the player with the tank
+ while ( (playerindex < env.numGamePlayers) && (! found) ) {
+ if ( (env.players[playerindex]->tank)
+ && (env.players[playerindex]->tank == the_tank) )
+ found = true;
+ else
+ ++playerindex;
+ }
+
+ if (found) {
+ char buffer[64] = { 0x0 };
+ snprintf(buffer, 63, "TELEPORT %d %d %d",
+ playerindex, destinationX, destinationY);
+ env.sendToClients(buffer);
+ }
+#endif // NETWORK
+
+ // Add to the chain:
+ global.addObject(this);
}
-TELEPORT::TELEPORT (GLOBALDATA *global, ENVIRONMENT *env, TELEPORT *remoteEnd, int destX, int destY):VIRTUAL_OBJECT(),
- clock(remoteEnd->startClock),startClock(remoteEnd->startClock),peaked(0)
+TELEPORT::TELEPORT (TELEPORT *remoteEnd, int32_t destX, int32_t destY) :
+ VIRTUAL_OBJECT(),
+ remote(remoteEnd)
{
- remote = remoteEnd;
- setEnvironment (env);
- player = NULL;
- _align = LEFT;
- _global = global;
- object = NULL;
- x = destX;
- y = destY;
- radius = remote->radius;
+ this->x = destX;
+ this->y = destY;
+ if (remote) {
+ clock = remoteEnd->startClock;
+ radius = remoteEnd->radius;
+ startClock = remoteEnd->startClock;
+ }
+
+ // Add to the chain:
+ global.addObject(this);
}
-void TELEPORT::initialise ()
+void TELEPORT::applyPhysics ()
{
- VIRTUAL_OBJECT::initialise ();
- peaked = 0;
- clock = startClock;
+ if (object) {
+ if (!clock) {
+ object->x = remote->x;
+ object->y = remote->y;
+ remote->object = object;
+ object = nullptr;
+ remote->clock--;
+ }
+ } else
+ clock = remote->clock;
+
+ if (clock-- < -startClock / 2)
+ destroy = true;
}
-int TELEPORT::applyPhysics ()
+
+void TELEPORT::draw ()
{
- if (object)
- {
- x = object->x;
- y = object->y;
- }
- else
- {
- clock = remote->clock;
- }
- if (clock < 1)
- {
- if (clock == 0)
- {
- if (object)
- {
- object->x = remote->x;
- object->y = remote->y;
- remote->object = object;
- object = NULL;
- remote->clock--;
- }
- }
- if (clock < -startClock / 2)
- destroy = TRUE;
- }
- clock--;
- return (0);
-}
+ if (!remote)
+ return;
+ double pClock = clock;
+ int32_t blobSize = 8;
+ int32_t pRadius = radius;
+ int32_t maxblobs = 1;
-// new telport version
-void TELEPORT::draw (BITMAP *dest)
-{
- BITMAP *tempBitmap;
- double pClock = clock;
- int blobSize, pRadius, maxblobs;
-
- if (pClock < 1.0)
- pClock = 1.0 + (1.0 - (pClock * 2.0));
- blobSize = 8 - (int)round(8 / (startClock / pClock)) + 1;
- pRadius = radius - (int)round(radius / (startClock / pClock)) + 1;
- maxblobs = 1 + (pRadius * 4);
-
- tempBitmap = create_bitmap (radius * 2, radius * 2);
- blit (dest, tempBitmap, remote->x - radius, remote->y - radius, 0, 0, radius * 2, radius * 2);
-
- if (object)
- remote->draw(dest);
-
- drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
- set_trans_blender (0, 0, 0, 255 - (int)((pClock / startClock) * 255));
- for (int count = (int)round(maxblobs + pClock); count > pClock; count--)
- {
- int xOff = (int)(perlin2DPoint (1.0, 200, 1278 + x + count * 100, pClock, 0.25, 6) * pRadius);
- int yOff = (int)(perlin2DPoint (1.0, 200, 9734 + y + count * 100, pClock, 0.25, 6) * pRadius);
- circlefill (dest, x + xOff, y + yOff, blobSize, getpixel (tempBitmap, pRadius + xOff, pRadius + yOff));
- }
- drawing_mode (DRAW_MODE_SOLID, NULL, 0, 0);
- setUpdateArea (x - pRadius - blobSize, y - pRadius - blobSize, (pRadius + blobSize) * 2, (pRadius + blobSize) * 2);
- requireUpdate ();
-
- destroy_bitmap (tempBitmap);
-}
+ // When the teleporting finishes, the blobs enlarge and disperse
+ // using this then growing factor:
+ if (pClock < 1.0)
+ pClock = 1.0 + (1.0 - (pClock * 2.0));
+ int32_t transMod = 255 - (pClock / startClock * 255);
+ if (transMod > 255) transMod = 255;
+ else if (transMod < 0) transMod = 0;
-/*
- * Old version
-void TELEPORT::draw (BITMAP *dest)
-{
- BITMAP *tempBitmap;
- int blobSize = 8;
- int pClock = clock;
- if (pClock < 0)
- {
- pClock = 0;
- blobSize = (startClock / 2) / -clock;
- }
-
- tempBitmap = create_bitmap (radius * 2, radius * 2);
- blit (_env->db, tempBitmap, (int)remote->x - radius, (int)remote->y - radius, 0, 0, radius * 2, radius * 2);
-
- if (object)
- remote->draw (dest);
-
- drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
- set_trans_blender (0, 0, 0, 255 - (int)((pClock / startClock) * 255));
- for (int count = (radius * radius * 4 / (8 * 8)); count > pClock; count--)
- {
- int xOff = (int)(perlin2DPoint (1.0, 200, 1278 + (int)x + count * 100, clock, 0.25, 6) * (radius));
- int yOff = (int)(perlin2DPoint (1.0, 200, 9734 + (int)y + count * 100, clock, 0.25, 6) * (radius));
- circlefill (dest, (int)x + xOff, (int)y + yOff, blobSize, getpixel (tempBitmap, radius + xOff, radius + yOff));
- }
- drawing_mode (DRAW_MODE_SOLID, NULL, 0, 0);
- setUpdateArea ((int)x - radius - blobSize, (int)y - radius - blobSize, (radius + blobSize) * 2, (radius + blobSize) * 2);
- requireUpdate ();
-
- destroy_bitmap (tempBitmap);
-}
-*/
+ blobSize -= round(8 / (startClock / pClock)) + 1;
+ pRadius -= round(radius / (startClock / pClock)) + 1;
+ maxblobs += pRadius * 4;
+ BITMAP* tempBitmap = create_bitmap (radius * 2, radius * 2);
+ blit (global.canvas, tempBitmap,
+ remote->x - radius, remote->y - radius, 0, 0,
+ radius * 2, radius * 2);
-int TELEPORT::isSubClass (int classNum)
-{
- if (classNum == TELEPORT_CLASS)
- return (TRUE);
- else
- return (FALSE);
- //return (PHYSICAL_OBJECT::isSubClass (classNum));
+ if (object && remote)
+ remote->draw();
+
+ drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
+ set_trans_blender (0, 0, 0, transMod);
+
+ for (int32_t i = round(maxblobs + pClock); i > pClock; --i) {
+ int32_t xOff = perlin2DPoint (1.0, 200, 1278 + x + (i * 100), pClock, 0.25, 6) * pRadius;
+ int32_t yOff = perlin2DPoint (1.0, 200, 9734 + y + (i * 100), pClock, 0.25, 6) * pRadius;
+ int32_t t_col = getpixel(tempBitmap, pRadius + xOff, pRadius + yOff);
+ circlefill (global.canvas, x + xOff, y + yOff, blobSize, t_col);
+ }
+
+ drawing_mode (DRAW_MODE_SOLID, NULL, 0, 0);
+
+ setUpdateArea (x - pRadius - blobSize, y - pRadius - blobSize,
+ (pRadius + blobSize) * 2, (pRadius + blobSize) * 2);
+ requireUpdate ();
+
+ destroy_bitmap (tempBitmap);
}
diff --git a/src/teleport.h b/src/teleport.h
index c4b2293..2e624ce 100644
--- a/src/teleport.h
+++ b/src/teleport.h
@@ -20,40 +20,56 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* */
-
-#include "main.h"
+#include "globaltypes.h"
#include "virtobj.h"
class TELEPORT: public VIRTUAL_OBJECT
- {
+{
public:
- int radius;
- int clock;
- int startClock;
- int peaked;
- int dispersing;
- VIRTUAL_OBJECT *object;
- TELEPORT *remote;
-
- virtual ~TELEPORT ();
- TELEPORT (GLOBALDATA *global, ENVIRONMENT *env, VIRTUAL_OBJECT *targetObj, int destinationX, int destinationY, int objRadius, int duration);
- TELEPORT (GLOBALDATA *global, ENVIRONMENT *env, TELEPORT *remoteEnd, int destX, int destY);
- void initialise ();
- void draw (BITMAP *dest);
- int applyPhysics ();
- int isSubClass (int classNum);
- inline virtual int getClass ()
- {
- return (TELEPORT_CLASS);
- }
- inline virtual void setEnvironment(ENVIRONMENT *env)
- {
- if (!_env || (_env != env))
- {
- _env = env;
- _index = _env->addObject (this);
- }
- }
- };
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+ // Source constructor
+ TELEPORT (VIRTUAL_OBJECT *targetObj,
+ int32_t destinationX, int32_t destinationY,
+ int32_t objRadius, int32_t duration, int32_t type);
+ virtual ~TELEPORT ();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ void applyPhysics ();
+ void draw ();
+
+ eClasses getClass() { return CLASS_TELEPORT; }
+
+
+private:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ // Target constructor
+ TELEPORT (TELEPORT *remoteEnd, int32_t destX, int32_t destY);
+
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ int32_t clock = 0;
+ VIRTUAL_OBJECT* object = nullptr;
+ int32_t radius = 0;
+ TELEPORT* remote = nullptr;
+ int32_t startClock = 0;
+};
#endif
diff --git a/src/text.cpp b/src/text.cpp
index 2005792..8120799 100644
--- a/src/text.cpp
+++ b/src/text.cpp
@@ -1,227 +1,307 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cmath>
+#include <algorithm>
#include "text.h"
+#include "main.h"
// Basic constructor to kick things off
TEXTBLOCK::TEXTBLOCK()
-{
- complete_text = NULL;
- total_lines = current_line = 0;
-}
+{ /* nothing to do here */ }
// A constructor that also loads text from a file
-TEXTBLOCK::TEXTBLOCK(char *filename)
+TEXTBLOCK::TEXTBLOCK(const char* filename)
{
- int status;
-
- complete_text = NULL;
- total_lines = current_line = 0;
- if (filename)
- {
- status = Load_File(filename);
- if (! status)
- printf("Something went wrong loading text from file: %s\n", filename);
- }
+ if (filename && !Load_File(filename)) {
+ cerr << "Something went wrong loading text from file:";
+ cerr << filename << " !" << endl;
+ }
}
// clean everything
TEXTBLOCK::~TEXTBLOCK()
{
- int count;
-
- if (complete_text)
- {
- for (count = 0; count < total_lines; count++)
- {
- if ( complete_text[count] )
- free(complete_text[count]);
- }
- free(complete_text);
- }
-
+ destroy();
}
+/// @brief unified method to delete the present text
+void TEXTBLOCK::destroy()
+{
+ if (complete_text) {
+ for (int32_t i = 0; i < total_lines; ++i) {
+ if ( complete_text[i] )
+ free(complete_text[i]);
+ }
+ free(complete_text);
+ complete_text = nullptr;
+ }
+}
-void TEXTBLOCK::Trim_Newline(char *line)
+// This function display all lines of text. Optionally, it
+// will also print line numbers before each line.
+// The number of lines printed is returned.
+int32_t TEXTBLOCK::Display_All(bool show_line_numbers)
{
- int index = 0;
-
- while ( line[index] )
- {
- if ( (line[index] == '\n') || (line[index] == '\r') )
- line[index] = '\0';
- else
- index++;
+ int32_t i = 0;
+ for ( ; i < total_lines; ++i) {
+ if (show_line_numbers)
+ printf("%d. ", i);
+ printf("%s\n", complete_text[i]);
}
+ return i;
}
-
-// This function does most of the work. It loads an entire text
-// file into memory. Returns TRUE on success or FALSE if
-// somethign goes wrong.
-int TEXTBLOCK::Load_File(char *filename)
+/// @brief Draw @a text in the box @a region with border and blue background
+/// if @a with_box is true.
+/// This method releases the display and can therefore be used in parallel.
+void draw_text_in_box (BOX* region, const char* text, bool with_box)
{
- FILE *phile;
- int lines_loaded = 0;
- int we_have_space = 10;
- char line[MAX_LINE_LENGTH], *status;
-
- // open the file
- phile = fopen(filename, "r");
- if (! phile)
- return FALSE;
-
- // give us some space
- complete_text = (char **) calloc(we_have_space, sizeof(char *));
- if (! complete_text)
- {
- fclose(phile);
- return FALSE;
- }
+ if (with_box) {
+ global.lockLand();
+ rectfill (global.canvas, region->x, region->y, region->w, region->h,
+ makecol (0,0,128));
+ rect (global.canvas, region->x, region->y, region->w, region->h,
+ makecol (128,128,255));
+ global.unlockLand();
+ }
+
+ char buffer[1024] = { 0 };
+ uint32_t lastSpace = 0;
+ uint32_t lineBegin = 0;
+ uint32_t lineCount = 0;
+ int32_t lineWidth = region->w - 27;
+ uint32_t textLength = static_cast<uint32_t>(strlen(text));
+
+ while (lineBegin < textLength) {
+ uint32_t charCount = 0;
+ uint32_t buffCount = 0;
+ int32_t buffWidth = 0;
+ memset(buffer, 0, sizeof(char) * 1024);
+
+ // Fill buffer until a line break is found or the maximum width
+ // is exceeded. For the latter case remember the last space found.
+ do {
+ buffer[buffCount] = text[lineBegin + charCount];
+
+ if (buffer[buffCount] == ' ')
+ lastSpace = 0;
+ else
+ ++lastSpace;
+
+ if (buffer[buffCount] != '\n')
+ buffCount++;
+
+ charCount++;
+
+ buffWidth = text_length(font, buffer);
+ } while ( text[lineBegin + charCount]
+ && (buffWidth < lineWidth)
+ && (buffer[buffCount] != '\n') );
+
+ // If the line width got exceeded, revert to last space seen
+ if (lastSpace && (buffWidth >= lineWidth) ) {
+ charCount -= lastSpace;
+ buffer[buffCount - lastSpace - 1] = 0;
+ } else
+ buffer[buffCount] = 0;
+
+ // Print out the result:
+ if (buffer[0]) {
+ textout_ex (global.canvas, font, buffer, region->x + 5,
+ region->y + (lineCount * env.fontHeight) + 5, WHITE, -1);
+ }
+ lineBegin += charCount;
+ lineCount++;
+ }
+
+ global.make_update (region->x, region->y, region->w, region->h);
+ fi = 1;
+}
- // time to load some text!
- status = fgets(line, MAX_LINE_LENGTH, phile);
- while ( (status) && (lines_loaded < MAX_LINES_IN_FILE) && (complete_text) )
- {
- Trim_Newline(line);
- complete_text[lines_loaded] = (char *) calloc( strlen(line) + 1, sizeof(char));
- if ( complete_text[lines_loaded] )
- {
- strcpy( complete_text[lines_loaded], line );
- lines_loaded++;
- }
- if (lines_loaded >= we_have_space)
- {
- we_have_space += 10;
- complete_text = (char **) realloc( complete_text, we_have_space * sizeof(char *) );
- }
- status = fgets(line, MAX_LINE_LENGTH, phile);
- } // all done loading
-
- if (complete_text)
- complete_text = (char **) realloc( complete_text, lines_loaded * sizeof(char *) );
- fclose(phile);
- total_lines = lines_loaded;
- current_line = 0;
- return TRUE;
+
+// Returns the current line
+const char* TEXTBLOCK::Get_Current_Line() const
+{
+ return complete_text[current_line];
}
+/// @brief Return a specific line or nullptr if @a index is out of bounds
+const char* TEXTBLOCK::Get_Line(int32_t index) const
+{
+ if ( (index > 0) && (index < total_lines))
+ return complete_text[index];
+ return nullptr;
+}
+
// Find a random line and return it
-char *TEXTBLOCK::Get_Random_Line()
+const char* TEXTBLOCK::Get_Random_Line() const
{
- int my_line;
- char *my_text;
-
- my_line = rand() % total_lines;
- my_text = complete_text[my_line];
- return my_text;
+ return complete_text[rand() % total_lines];
}
-// Returns the current line
-char *TEXTBLOCK::Get_Current_Line()
+// This function does most of the work. It loads an entire text
+// file into memory. Returns true on success or false if
+// something goes wrong.
+bool TEXTBLOCK::Load_File(const char* filename)
{
- char *my_text;
+ char line[MAX_LINE_LENGTH] = { 0 };
+ int32_t lines_loaded = 0;
+ int32_t we_have_space = 10;
+
+ // open the file
+ FILE* fIn = fopen(filename, "r");
+ if (!fIn)
+ return false;
+
+ // give us some space
+ destroy();
+ complete_text = (char**)calloc(we_have_space, sizeof(char*));
+ if (!complete_text) {
+ fclose(fIn);
+ return false;
+ }
- my_text = complete_text[current_line];
- return my_text;
+ // time to load some text!
+ while ( fgets(line, MAX_LINE_LENGTH, fIn)
+ && (lines_loaded < MAX_LINES_IN_FILE)
+ && complete_text) {
+
+ // Reserve more space if full
+ if (lines_loaded == we_have_space) {
+ we_have_space += 10;
+
+ char** new_text = (char**)realloc(complete_text, we_have_space * sizeof(char*));
+
+ if (new_text)
+ complete_text = new_text;
+ else {
+ destroy();
+ fclose(fIn);
+ return false;
+ }
+ }
+
+ // Store the line:
+ Trim_Newline(line);
+ complete_text[lines_loaded] = (char*)calloc(strlen(line) + 1, sizeof(char));
+
+ if (complete_text[lines_loaded])
+ strncpy(complete_text[lines_loaded++], line, strlen(line));
+ } // End of loading text from a file
+
+ fclose(fIn);
+ total_lines = lines_loaded;
+ current_line = 0;
+
+ return true;
}
+int32_t TEXTBLOCK::Lines() const
+{
+ return total_lines;
+}
// Move the line counter ahead, if possible
-// Returns FALSE if we hit the end of lines, or
+// Returns false if we hit the end of lines, or
// true if everything is OK
-int TEXTBLOCK::Next_Line()
+bool TEXTBLOCK::Next_Line()
{
- current_line++;
- if (current_line >= total_lines)
- {
- current_line = total_lines - 1;
- return FALSE;
- }
- return TRUE;
+ if (++current_line >= total_lines) {
+ current_line = total_lines - 1;
+ return false;
+ }
+ return true;
}
-
// Go to the previous line. The function returns
-// TRUE is everything is OK. If we were already
-// at the beginning, then FALSE is returned.
-int TEXTBLOCK::Previous_Line()
+// true is everything is OK. If we were already
+// at the beginning, then false is returned.
+bool TEXTBLOCK::Previous_Line()
{
- if (current_line > 0)
- {
- current_line--;
- return TRUE;
+ if (--current_line < 0) {
+ current_line = 0;
+ return false;
}
- return FALSE;
+ return true;
}
-// This function display all lines of text. Optionally, it
-// will also print line numbers before each line.
-// The number of lines printed is returned.
-int TEXTBLOCK::Display_All(int line_numbers)
+// This method renders a part of the text to global.canvas
+void TEXTBLOCK::Render_Lines(int32_t scrollOffset, int32_t spacing,
+ int32_t top, int32_t bottom)
{
- int counter = 0;
-
- while (counter < total_lines)
- {
- if (line_numbers)
- printf("%d. ", counter);
- printf("%s\n", complete_text[counter]);
- counter++;
- }
- return counter;
+ int32_t txtheight = env.fontHeight * spacing ;
+ int32_t xPos = env.halfWidth;
+ int32_t yPos = env.halfHeight + scrollOffset;
+ for ( int32_t i = 0
+ ; (i < total_lines) && (yPos < env.screenHeight)
+ ; ++i) {
+
+ if ( (yPos > (top - txtheight)) && (yPos < (bottom + txtheight)) ) {
+ textout_centre_ex (global.canvas, font, complete_text[i],
+ xPos + 2, yPos + 2, BLACK, -1);
+ textout_centre_ex (global.canvas, font, complete_text[i],
+ xPos, yPos, WHITE, -1);
+ }
+ yPos += txtheight;
+ }
}
+// This is a free floating function
+const char* Add_Comma(int32_t number)
+{
+ static char return_value[128] = { 0 };
+ memset(return_value, 0, 128);
+ // MAX_MONEY_IN_WALLET is: 1,000,000,000 = 13 characters
+ char buffer[15] = { 0 };
+ snprintf(buffer, 14, "%d", number);
+ int32_t index = strlen(buffer); // start from the end
+ int32_t returnindex = index + (index / 3) - 1;
+ int32_t th_count = 0;
+ if (0 == (index % 3) )
+ returnindex--;
+ while (--index >= 0) {
+ return_value[returnindex--] = buffer[index];
+ th_count++;
-// This is a free floating function
-char *Add_Comma(int number)
+ if ( (th_count == 3) && (returnindex > 0) ) {
+ th_count = 0;
+ return_value[returnindex] = ',';
+ returnindex--;
+ }
+ }
+
+ return return_value;
+}
+
+
+void Trim_Newline(char *line)
{
- char buffer[64];
- static char return_value[128];
- int index, return_index;
- int place_counter = 0;
-
- // we won't worry about over-flow with such a big buffer
- sprintf(buffer, "%d", number);
- memset(return_value, '\0', 128);
- index = strlen(buffer); // start from the end
- return_index = index + (index / 3);
- return_index -= 1;
- if (! (index % 3) )
- return_index--;
- index -= 1;
- while (index >= 0)
- {
- return_value[return_index] = buffer[index];
- return_index--;
- index--;
- place_counter++;
- if ( (place_counter == 3) && (return_index > 0) )
- {
- place_counter = 0;
- return_value[return_index] = ',';
- return_index--;
- }
- }
- return return_value;
+ int32_t index = 0;
+
+ while ( line[index] ) {
+ if ( (line[index] == '\n') || (line[index] == '\r') )
+ line[index] = '\0';
+ else
+ ++index;
+ }
}
diff --git a/src/text.h b/src/text.h
index 97055e7..eb38fbe 100644
--- a/src/text.h
+++ b/src/text.h
@@ -11,36 +11,72 @@
* -- Jesse
*/
-#ifndef TRUE
-#define TRUE 1
-#endif
-#ifndef FALSE
-#define FALSE 0
-#endif
+#include <cstdint>
#define MAX_LINE_LENGTH 512
#define MAX_LINES_IN_FILE 1024
+struct BOX;
+
+/// @brief alignment of texts
+enum alignType {
+ CENTRE = 0,
+ LEFT,
+ RIGHT
+};
+
class TEXTBLOCK
{
public:
- int total_lines, current_line;
- char **complete_text;
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+
+ TEXTBLOCK ();
+ TEXTBLOCK (const char* filename);
+ ~TEXTBLOCK();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ int32_t Display_All (bool show_line_numbers); // display all text
+ const char* Get_Current_Line() const; // return current line
+ const char* Get_Line (int32_t index) const; // return a specific line
+ const char* Get_Random_Line () const; // give us a random line
+ bool Load_File (const char *filename); // load lines from a file
+ int32_t Lines () const; // Return number of total lines
+ bool Next_Line (); // advance the current line
+ bool Previous_Line (); // move the current line back
+ void Render_Lines (int32_t scrollOffset,
+ int32_t spacing,
+ int32_t top,
+ int32_t bottom); // Render to global.canvas
+
+
+private:
+
+ /* -----------------------
+ * --- Private methods ---
+ * -----------------------
+ */
+
+ void destroy();
- TEXTBLOCK();
- TEXTBLOCK(char *filename);
- ~TEXTBLOCK();
- void Trim_Newline(char *line); // hack the newline off a string
- int Load_File(char *filename); // load lines from a file
- char *Get_Random_Line(); // give us a random line
- char *Get_Current_Line(); // return current line
- int Next_Line(); // advance the current line counter one
- int Previous_Line(); // move the current line counter back one
- int Display_All(int line_numbers); // display all text
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+ int32_t total_lines = 0;
+ int32_t current_line = 0;
+ char** complete_text = nullptr;
};
@@ -50,10 +86,13 @@ public:
// This function returns a string with
// comma characters between thousands positions
-// There is no need to free the returned string.
-char *Add_Comma(int number);
+// You *MUST* *NOT* free the returned string.
+const char* Add_Comma(int32_t number);
+void draw_text_in_box(BOX* region, const char* text, bool with_box);
+// hack the newline off a string
+void Trim_Newline(char *line);
#endif
diff --git a/src/update.cpp b/src/update.cpp
index e7966ee..d7d35db 100644
--- a/src/update.cpp
+++ b/src/update.cpp
@@ -1,105 +1,104 @@
-#ifdef NETWORK
-#ifdef THREADS
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <pthread.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netdb.h>
-#include <unistd.h>
-
-#include "update.h"
-
-
-
-
-
-
-char *Check_For_Update(char *server_name, char *remote_file, char *host_name, char *current_version)
-{
- struct update_data *my_update;
- pthread_t update_thread;
-
- my_update = (struct update_data *) calloc(1, sizeof(struct update_data));
- if (!my_update)
- return NULL;
- my_update->server_name = server_name;
- my_update->remote_file = remote_file;
- my_update->host_name = host_name;
- my_update->current_version = current_version;
- my_update->update_string = (char *) calloc(UPDATE_STR_LENGTH, sizeof(char));
- if (! my_update->update_string)
- return NULL;
-
- pthread_create( &update_thread, NULL, Get_Latest_Version, (void *) my_update);
- return my_update->update_string;
-}
-
-
-
-void *Get_Latest_Version(void *new_data)
-{
- struct update_data *my_data = (struct update_data *) new_data;
- // set up socket
- int socket_num, port_number = 80;
- struct sockaddr_in server_address;
- struct hostent *server;
- char buffer[1024];
- char *found = NULL;
- int got_bytes;
- double this_version, web_version;
-
-
- socket_num = socket(AF_INET, SOCK_STREAM, 0);
- if (socket_num < 0)
- pthread_exit(NULL);
- server = gethostbyname(my_data->server_name);
- if (! server)
- pthread_exit(NULL);
- bzero((char *) &server_address, sizeof(server_address));
- server_address.sin_family = AF_INET;
- bcopy((char *) server->h_addr,
- (char *) &server_address.sin_addr.s_addr,
- server->h_length);
- server_address.sin_port = htons(port_number);
-
- // try to connect
- if ( connect(socket_num, (sockaddr *)&server_address, sizeof(server_address)) < 0)
- pthread_exit(NULL);
-
-
- // get HTTP data
- snprintf(buffer, 1024, "GET /%s HTTP/1.1\nHost: %s\n\n", my_data->remote_file, my_data->host_name);
- write(socket_num, buffer, strlen(buffer));
- got_bytes = read(socket_num, buffer, 1024);
-
- // search for version number in return data
- if (got_bytes > 1)
- found = strstr(buffer, "Version: ");
- while ( (got_bytes > 1) && (! found) )
- {
- got_bytes = read(socket_num, buffer, 1024);
- if (got_bytes > 1)
- found = strstr(buffer, "Version: ");
- }
-
- // compare version number
- if (found)
- {
- found += 9;
- found[5] = '\0';
- sscanf(found, "%lf", &web_version);
- sscanf(my_data->current_version, "%lf", &this_version);
- if (web_version > this_version)
- sprintf(my_data->update_string, "A new version, %2.1lf, is ready for download.", web_version);
- }
-
- close(socket_num);
- pthread_exit(NULL);
-}
-
-#endif
-#endif
+#include "debug.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <sys/types.h>
+#include <cerrno>
+
+#if defined(ATANKS_IS_MSVC)
+/// What is needed here?
+#else
+# include <sys/socket.h>
+# include <netinet/in.h>
+# include <netdb.h>
+# include <unistd.h>
+#endif // MSVC++ versus gcc/clang
+
+#include "update.h"
+#include "network.h"
+#include "externs.h"
+
+/// @brief update_data default ctor
+update_data::update_data(const char* server_, const char* remote_,
+ const char* host_, const char* version_) :
+ server_name (server_ ? strdup(server_) : strdup("")),
+ host_name (host_ ? strdup(host_) : strdup("")),
+ remote_file (remote_ ? strdup(remote_) : strdup("")),
+ current_version(version_ ? strdup(version_) : strdup(""))
+{
+ memset(update_string, 0, sizeof(char) * 1024);
+}
+
+/// @brief update_data default dtor
+update_data::~update_data()
+{
+ if (server_name) free (server_name);
+ if (host_name) free (host_name);
+ if (remote_file) free (remote_file);
+ if (current_version) free (current_version);
+}
+
+
+void update_data::operator()()
+{
+#ifdef NETWORK
+ if (env.check_for_updates) {
+ // set up socket
+ int socket_num, port_number = 80;
+ struct sockaddr_in server_address;
+ struct hostent* server;
+ char buffer[1024];
+ char* found = nullptr;
+ int got_bytes;
+ double this_version, web_version;
+ int32_t towrite, written;
+
+
+ socket_num = socket(AF_INET, SOCK_STREAM, 0);
+ if (socket_num < 0)
+ return;
+ server = gethostbyname(server_name);
+ if (! server)
+ return;
+ bzero((char*) &server_address, sizeof(server_address));
+ server_address.sin_family = AF_INET;
+ bcopy((char*) server->h_addr,
+ (char*) &server_address.sin_addr.s_addr,
+ server->h_length);
+ server_address.sin_port = htons(port_number);
+
+ // try to connect
+ if ( connect(socket_num, (sockaddr*)&server_address, sizeof(server_address)) < 0)
+ return;
+
+
+ // get HTTP data
+ SAFE_WRITE(socket_num, "GET /%s HTTP/1.1\nHost: %s\n\n",
+ remote_file, host_name)
+
+ got_bytes = read(socket_num, buffer, 1024);
+
+ // search for version number in return data
+ if (got_bytes > 1)
+ found = strstr(buffer, "Version: ");
+ while ( (got_bytes > 1) && (! found) ) {
+ got_bytes = read(socket_num, buffer, 1024);
+ if (got_bytes > 1)
+ found = strstr(buffer, "Version: ");
+ }
+
+ // compare version number
+ if (found) {
+ found += 9;
+ found[5] = '\0';
+ sscanf(found, "%lf", &web_version);
+ sscanf(current_version, "%lf", &this_version);
+ if (web_version > this_version)
+ snprintf(update_string, 1024, "A new version, %2.1lf, is ready for download.", web_version);
+ }
+
+ close(socket_num);
+ }
+#endif // NETWORK
+}
diff --git a/src/update.h b/src/update.h
index 2f5ec4b..058843b 100644
--- a/src/update.h
+++ b/src/update.h
@@ -1,43 +1,25 @@
-#ifndef UPDATE_HEADER_FILE__
-#define UPDATE_HEADER_FILE__
-
-
-#ifdef NETWORK
-#ifdef THREADS
-
-
-#define UPDATE_STR_LENGTH 256
-
-
-struct update_data
-{
- char *server_name, *host_name;
- char *remote_file;
- char *update_string;
- char *current_version;
-};
-
-
-
-// this function kicks it all off
-// returns the address of a string containing
-// update notice. The string will have a notice
-// if update is ready or be 0-length with no update.
-// The function is threaded and may change the
-// string after the function returns.
-// The returning string is allocated and should
-// be freed at the end of the calling process.
-// On error, NULL is returned.
-char *Check_For_Update(char *server_name, char *remote_file, char *host_name, char *current_version);
-
-
-
-// The function/thread that checks for new updates.
-void *Get_Latest_Version(void *data);
-
-
-#endif
-#endif
-
-#endif
-
+#ifndef UPDATE_HEADER_FILE__
+#define UPDATE_HEADER_FILE__
+
+
+#define UPDATE_STR_LENGTH 256
+
+
+// rewritten struct to be used with C++11 threads.
+struct update_data
+{
+ char* server_name = nullptr;
+ char* host_name = nullptr;
+ char* remote_file = nullptr;
+ char update_string[1024];
+ char* current_version = nullptr;
+
+ explicit update_data(const char* server_, const char* remote_,
+ const char* host_, const char* version_);
+ ~update_data();
+
+ void operator()();
+};
+
+#endif
+
diff --git a/src/virtobj.cpp b/src/virtobj.cpp
index 239d409..4397b9c 100644
--- a/src/virtobj.cpp
+++ b/src/virtobj.cpp
@@ -17,196 +17,170 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* */
+#include <cassert>
#include "virtobj.h"
#include "environment.h"
+VIRTUAL_OBJECT::VIRTUAL_OBJECT() :
+ needsUpdate(ATOMIC_VAR_INIT(false))
+{ /* nothing to do */ }
-VIRTUAL_OBJECT::VIRTUAL_OBJECT()
- {
- _bitmap = NULL;
- _env = NULL;
- _global = NULL;
- x = 0;
- y = 0;
- xv = yv = 0;
- angle = 0;
- destroy = FALSE;
- age = 0;
- maxAge = -1;
- physics = 0;
- _current.x = 0;
- _current.y = 0;
- _current.w = 0;
- _current.h = 0;
- _old.x = 0;
- _old.y = 0;
- _old.w = 0;
- _old.h = 0;
- }
+VIRTUAL_OBJECT::~VIRTUAL_OBJECT ()
+{
+ bitmap = nullptr;
+}
-VIRTUAL_OBJECT::~VIRTUAL_OBJECT ()
- {
- _bitmap = NULL;
- // player, _env and _global must be handled by descendants!
- }
+void VIRTUAL_OBJECT::addUpdateArea (int32_t left, int32_t top,
+ int32_t width, int32_t height)
+{
+ if (left < dim_cur.x)
+ dim_cur.x = left;
+ if (top < dim_cur.y)
+ dim_cur.y = top;
+ /* This is prone to the following error:
+ If left is greater than dim_cur.x but the width is
+ smaller than dim_cur.w, (left + width) can
+ nevertheless end up right of (dim_cur.x + dim_cur.w).
+ Setting dim_cur.w to 'width' in that case makes
+ the update area smaller not larger.
+ The same applies to the height.
+ - Sven
+ if ((left + width) > (dim_cur.x + dim_cur.w))
+ dim_cur.w = width;
+ if ((top + height) > (dim_cur.y + dim_cur.h))
+ dim_cur.h = height;
+ */
+ int32_t new_r = left + width;
+ int32_t new_b = top + height;
+ int32_t old_r = dim_cur.x + dim_cur.w;
+ int32_t old_b = dim_cur.y + dim_cur.h;
+ if (new_r > old_r)
+ dim_cur.w = new_r - dim_cur.x + 1;
+ if (new_b > old_b)
+ dim_cur.h = new_b - dim_cur.y + 1;
+}
+void VIRTUAL_OBJECT::applyPhysics ()
+{
+ x += xv;
+ y += yv;
+}
+
+
+void VIRTUAL_OBJECT::draw ()
+{
+ assert(bitmap && "ERROR: VIRTUAL_OBJECT::draw() called without bitmap!");
+
+ if (!destroy && bitmap) {
+
+ rotate_sprite (global.canvas, bitmap,
+ x - (width / 2),
+ y - (height / 2),
+ itofix (angle));
+
+ // The update area depends on the rotation state (aka angle)
+ if (angle) {
+ int32_t length = std::max(width, height)
+ + (std::min(width, height) / 2);
+ setUpdateArea(x - (length/2),
+ y - (length/2),
+ length, length);
+ } else
+ setUpdateArea(x - (width / 2) - 1,
+ y - (height / 2) - 1,
+ width + 2, height + 2);
+ requireUpdate ();
+ }
+}
+
+
+void VIRTUAL_OBJECT::initialise ()
+{
+ age = 0;
+ maxAge = -1;
+ x = 0;
+ y = 0;
+ xv = 0;
+ yv = 0;
+ destroy = false;
+ dim_cur = dim_old = BOX();
+}
+
+
+/// @brief Set a new bitmap and store width and height for easy drawing.
+void VIRTUAL_OBJECT::setBitmap(BITMAP* bitmap_)
+{
+ if (bitmap_ != bitmap) {
+ bitmap = bitmap_;
+
+ if (bitmap) {
+ height = bitmap->h;
+ width = bitmap->w;
+ } else {
+ height = 0;
+ width = 0;
+ }
+ }
+}
+
+
+void VIRTUAL_OBJECT::setUpdateArea (int32_t left, int32_t top,
+ int32_t width, int32_t height)
+{
+ dim_cur.x = left;
+ dim_cur.y = top;
+ dim_cur.w = width;
+ dim_cur.h = height;
+}
+
/** @brief update
*
- * This method triggers an update of the canvas (aka drawing area) with the dimensions and position
- * of this object.
+ * This method triggers an update of the canvas (aka drawing area) with the
+ * dimensions and position of this object.
*/
void VIRTUAL_OBJECT::update()
{
- if (_requireUpdate)
- {
- int changed;
-
- if (_current.w)
- {
- if (_align == LEFT)
- {
- _env->make_update (_current.x, _current.y,
- _current.w, _current.h);
- }
- else if (_align == RIGHT)
- {
- _env->make_update (_current.x - _current.w,
- _current.y - _current.h,
- _current.w, _current.h);
- }
- else
- {
- _env->make_update (_current.x - (_current.w / 2),
- _current.y - (_current.h / 2),
- _current.w + 2, _current.h + 2);
- }
- }
-
- if ((_old.x == _current.x) &&
- (_old.y == _current.y) &&
- (_old.w == _current.w) &&
- (_old.h == _current.h))
- changed = FALSE;
- else
- changed = TRUE;
-
- if (changed)
- {
- if (_old.w)
- {
- if (_align == LEFT)
- {
- _env->make_update (_old.x, _old.y,
- _old.w, _old.h);
- }
- else if (_align == RIGHT)
- {
- _env->make_update (_old.x - _old.w,
- _old.y - _old.h,
- _old.w, _old.h);
- }
- else
- {
- _env->make_update (_old.x - (_old.w / 2),
- _old.y - (_old.h / 2),
- _old.w + 2, _old.h + 2);
- }
- }
- _old.x = _current.x;
- _old.y = _current.y;
- _old.w = _current.w;
- _old.h = _current.h;
- }
- _requireUpdate = FALSE;
- }
+ if (!needsUpdate.load(ATOMIC_READ))
+ return;
+
+ // Add update area for the current dimension
+ if (dim_cur.w > 0) {
+ int32_t left = LEFT == align ? dim_cur.x
+ : RIGHT == align ? dim_cur.x - dim_cur.w
+ : dim_cur.x - (dim_cur.w / 2);
+ int32_t top = LEFT == align ? dim_cur.y
+ : RIGHT == align ? dim_cur.y - dim_cur.h
+ : dim_cur.y - (dim_cur.h / 2);
+ int32_t right = std::min(env.screenWidth, left + dim_cur.w + 2);
+ int32_t bottom = std::min(env.screenHeight, top + dim_cur.h + 2);
+
+ if ( (right > left) && (bottom > top) )
+ global.make_update(left, top, right - left, bottom - top);
+ } // End of updating current area
+
+ // If the dimensions changed, the old area needs an update, too
+ if ( (dim_old.w > 0) && (dim_old != dim_cur) ) {
+ int32_t left = LEFT == align ? dim_old.x
+ : RIGHT == align ? dim_old.x - dim_old.w
+ : dim_old.x - (dim_old.w / 2);
+ int32_t top = LEFT == align ? dim_old.y
+ : RIGHT == align ? dim_old.y - dim_old.h
+ : dim_old.y - (dim_old.h / 2);
+ int32_t right = std::min(env.screenWidth, left + dim_old.w + 2);
+ int32_t bottom = std::min(env.screenHeight, top + dim_old.h + 2);
+
+ if ( (right > left) && (bottom > top) )
+ global.make_update(left, top, right - left, bottom - top);
+ } // End of updating old area
+
+ dim_old = dim_cur;
+
+ needsUpdate.store(false, ATOMIC_WRITE);
}
-
-void VIRTUAL_OBJECT::initialise ()
- {
- age = 0;
- maxAge = -1;
- x = 0;
- y = 0;
- xv = 0;
- yv = 0;
- destroy = false;
- _current.x = 0;
- _current.y = 0;
- _current.w = 0;
- _current.h = 0;
- _old.x = 0;
- _old.y = 0;
- _old.w = 0;
- _old.h = 0;
- }
-
-
-
-int VIRTUAL_OBJECT::applyPhysics ()
- {
- x += xv;
- y += yv;
-
- return (0);
- }
-
-
-
-void VIRTUAL_OBJECT::draw (BITMAP *dest)
- {
- if (!destroy)
- {
- rotate_sprite (dest, _bitmap,
- (int)x - (_bitmap->w / 2),
- (int)y - (_bitmap->h / 2),
- itofix (angle));
- if (angle == 0)
- {
- setUpdateArea ((int)x - (_bitmap->w / 2 + 1),
- (int)y - (_bitmap->h / 2 + 1),
- _bitmap->w + 2, _bitmap->h + 2);
- }
- else
- {
- int length = MAX (_bitmap->w, _bitmap->h);
- length += MIN (_bitmap->w, _bitmap->h) / 2;
- setUpdateArea ((int)x - (length/2),
- (int)y - (length/2),
- length, length);
- }
- requireUpdate ();
- }
- }
-
-
-
-
-void VIRTUAL_OBJECT::setUpdateArea (int left, int top, int width, int height)
- {
- _current.x = left;
- _current.y = top;
- _current.w = width;
- _current.h = height;
- }
-
-
-
-void VIRTUAL_OBJECT::addUpdateArea (int left, int top, int width, int height)
- {
- if (left < _current.x)
- _current.x = left;
- if (top < _current.y)
- _current.y = top;
- if (left + width > _current.x + _current.w)
- _current.w = width;
- if (top + height > _current.y + _current.h)
- _current.h = height;
- }
-
diff --git a/src/virtobj.h b/src/virtobj.h
index e8fc9a4..fd2e064 100644
--- a/src/virtobj.h
+++ b/src/virtobj.h
@@ -24,70 +24,139 @@
#define _PURE =0
#endif // _PURE
-enum alignType { CENTRE, LEFT, RIGHT };
-
#include "main.h"
+#include "text.h"
+
+
+/// @enum ePhysType
+/// @brief Determine which kind of physics should be used
+enum ePhysType
+{
+ PT_NORMAL = 0, //!< No special processing, just a normal curve shot and impact.
+ PT_FUNKY_FLOAT, //!< Funky bomb-lets ignore gravitation.
+ PT_DIGGING, //!< Burrowers and the like dig through dirt in a reverse curve.
+ PT_ROLLING, //!< Roll over the surface.
+ PT_DIRTBOUNCE, //!< Dirt debris bounces off of dirt and all walls.
+ PT_SMOKE, //!< Smoke reacts on nothing but repulsor shields.
+ PT_NONE, //!< Special values for age caused detonation triggering.
+};
+
-#include "player.h"
+#ifndef HAS_PLAYER
+class PLAYER;
+#endif // HAS_PLAYER
class VIRTUAL_OBJECT
- {
- public:
- BOX _current, _old;
- alignType _align;
- BITMAP *_bitmap;
- ENVIRONMENT *_env;
- GLOBALDATA *_global;
- int _requireUpdate;
- int _index;
- PLAYER *player;
- double x, y;
- double xv, yv;
- int angle;
- int destroy;
- int age, maxAge;
- int physics; // Special physics processing?
-
- /* --- constructor --- */
- VIRTUAL_OBJECT();
-
- /* --- destructor --- */
- virtual ~VIRTUAL_OBJECT ();
-
- /* --- non-inline methods --- */
- void update();
-
- /* --- pure virtual (abstract) methods --- */
- virtual int isSubClass (int classNum)_PURE;
- virtual int getClass ()_PURE;
- virtual void setEnvironment(ENVIRONMENT *env)_PURE; // This is virtual, so objects add themselves to _env!
-
- /* --- inline methods --- */
- void requireUpdate ()
- {
- _requireUpdate = TRUE;
- }
-
- virtual void initialise ();
-
- virtual int applyPhysics ();
-
- inline void setGlobalData (GLOBALDATA *global)
- {
- if (!_global || (_global != global))
- _global = global;
- }
-
-virtual void draw (BITMAP *dest);
-
-void setUpdateArea (int left, int top, int width, int height);
-
-void addUpdateArea (int left, int top, int width, int height);
-
- inline int getIndex ()
- {
- return (_index);
- }
- };
+{
+public:
+
+ /* -----------------------------------
+ * --- Constructors and destructor ---
+ * -----------------------------------
+ */
+ explicit VIRTUAL_OBJECT();
+ virtual ~VIRTUAL_OBJECT();
+
+
+ /* ----------------------
+ * --- Public methods ---
+ * ----------------------
+ */
+
+ /* --- non-inline methods --- */
+ void addUpdateArea (int32_t left, int32_t top,
+ int32_t width, int32_t height);
+ virtual void applyPhysics ();
+ virtual void draw ();
+ virtual void initialise ();
+ void setUpdateArea (int32_t left, int32_t top,
+ int32_t width, int32_t height);
+ void update ();
+
+ /* --- inline methods --- */
+ void requireUpdate () { needsUpdate.store(true, ATOMIC_WRITE); }
+
+ /* --- pure virtual (abstract) methods --- */
+ virtual eClasses getClass () _PURE;
+
+
+ /* ------------------------------
+ * --- templated list getters ---
+ * ------------------------------
+ */
+
+ /// @brief If not nullptr, set @a prev_ to the predecessor of this.
+ template<typename obj_T>
+ void getPrev(obj_T** prev_)
+ {
+ obj_T* prev_obj = static_cast<obj_T*>(prev);
+ if (prev_)
+ *prev_ = prev_obj;
+ }
+
+ /// @brief If not nullptr, set @a next_ to the successor of this.
+ template<typename obj_T>
+ void getNext(obj_T** next_)
+ {
+ obj_T* next_obj = static_cast<obj_T*>(next);
+ if (next_)
+ *next_ = next_obj;
+ }
+
+ /* ----------------------
+ * --- Public members ---
+ * ----------------------
+ */
+
+ bool destroy = false;
+ VIRTUAL_OBJECT* next = nullptr;
+ PLAYER* player = nullptr;
+ VIRTUAL_OBJECT* prev = nullptr;
+ double x = 0.;
+ double y = 0.;
+
+protected:
+
+
+ /* -------------------------
+ * --- Protected methods ---
+ * -------------------------
+ */
+
+ BITMAP* getBitmap() const { return bitmap; }
+ bool hasBitmap() const { return (bitmap != nullptr); }
+ void setBitmap(BITMAP* bitmap_);
+
+
+ /* -------------------------
+ * --- Protected members ---
+ * -------------------------
+ */
+
+ int32_t age = 0;
+ alignType align = LEFT;
+ int32_t angle = 0;
+ BOX dim_cur;
+ BOX dim_old;
+ int32_t height = 0;
+ int32_t maxAge = -1;
+ ePhysType physType = PT_NORMAL; // Special physics processing?
+ int32_t width = 0;
+ double xv = 0.;
+ double yv = 0.;
+
+private:
+
+ /* -----------------------
+ * --- Private members ---
+ * -----------------------
+ */
+
+ BITMAP* bitmap = nullptr;
+ abool_t needsUpdate;
+};
+
+/// === Shorten the usage of virtual objects ===
+typedef VIRTUAL_OBJECT vobj_t;
#endif // VIRTOBJ_DEFINE
diff --git a/src/winclock.h b/src/winclock.h
new file mode 100644
index 0000000..34d1b6e
--- /dev/null
+++ b/src/winclock.h
@@ -0,0 +1,105 @@
+#pragma once
+#ifndef ATANKS_SRC_WINCLOCK_H_INCLUDED
+#define ATANKS_SRC_WINCLOCK_H_INCLUDED
+
+/* Workaround for the buggy <chrono> implementation of VS12.
+ *
+ * I do not know whether this is more embarrassing for me or Microsoft.
+ * For me, because I didn't realize this before it was too late, I guess.
+ * The steady_clock is not steady, and the high_resolution_clock is not
+ * high resolution.
+ * This will be fixed in VS13, but while writing this only a community
+ * preview has been published. This preview is noted to be not production
+ * ready and should not be installed on a system that can't be formatted.
+ *
+ * So until I have a full production ready VS13 (Visual Studio 2015), this
+ * header works around the buggy <chrono> implementation.
+ *
+ * For more information see:
+ * https://connect.microsoft.com/VisualStudio/feedback/details/858357/
+ * and
+ * https://connect.microsoft.com/VisualStudio/feedback/details/753115/
+ */
+
+#if defined(ATANKS_IS_MSVC)
+#include "main.h"
+
+#ifdef USE_MUTEX_INSTEAD_OF_SPINLOCK
+# include <mutex>
+# define CSpinLock std::mutex
+#endif // USE_MUTEX_INSTEAD_OF_SPINLOCK
+
+// timer variable and locker
+bool has_win_clock = false;
+CSpinLock win_clock_lock;
+volatile
+int32_t win_clock = 0;
+
+// additional functions:
+void win_clock_add()
+{
+ ++win_clock;
+}
+END_OF_FUNCTION(win_clock_add)
+
+void win_clock_deinit()
+{
+ if ( has_win_clock ) {
+ remove_int(win_clock_add);
+ has_win_clock = false;
+ }
+ win_clock = 0;
+}
+
+inline int32_t win_clock_get()
+{
+ win_clock_lock.lock();
+ int32_t result = win_clock;
+ win_clock = 0;
+ win_clock_lock.unlock();
+ return result;
+}
+
+void win_clock_init()
+{
+ win_clock = 0;
+ if ( !has_win_clock ) {
+ LOCK_VARIABLE(win_clock)
+ LOCK_FUNCTION(win_clock_add)
+ install_int_ex(win_clock_add, MSEC_TO_TIMER(1));
+ has_win_clock = true;
+ }
+}
+
+// Re-implement the millisecond sensitive functions:
+int32_t game_us_get()
+{
+ return win_clock_get() * 1000;
+}
+
+
+void game_us_reset()
+{
+ win_clock_lock.lock();
+ win_clock = 0;
+ win_clock_lock.unlock();
+}
+
+
+int32_t menu_ms_get()
+{
+ return win_clock_get();
+}
+
+
+void menu_ms_reset()
+{
+ win_clock_lock.lock();
+ win_clock = 0;
+ win_clock_lock.unlock();
+}
+
+#endif // defined(ATANKS_IS_MSVC)
+
+#endif // ATANKS_SRC_WINCLOCK_H_INCLUDED
+
diff --git a/tankgun/2.bmp b/tankgun/2.bmp
index c2b6829..586fa88 100644
Binary files a/tankgun/2.bmp and b/tankgun/2.bmp differ
diff --git a/tankgun/4.bmp b/tankgun/4.bmp
index db4b30c..f22c46b 100644
Binary files a/tankgun/4.bmp and b/tankgun/4.bmp differ
diff --git a/tankgun/5.bmp b/tankgun/5.bmp
index 7b36a8c..5242789 100644
Binary files a/tankgun/5.bmp and b/tankgun/5.bmp differ
diff --git a/tankgun/7.bmp b/tankgun/7.bmp
index 0162b14..04a44c3 100644
Binary files a/tankgun/7.bmp and b/tankgun/7.bmp differ
diff --git a/text/panic.pt_BR.txt b/text/panic.pt_BR.txt
new file mode 100644
index 0000000..44f5ee7
--- /dev/null
+++ b/text/panic.pt_BR.txt
@@ -0,0 +1,8 @@
+%s WANTS ME DEAD!
+%s IS COMING FOR ME!
+HELP ME AGAINST %s!
+NO, %s, NO!
+%s ONLY AIMS AT ME!
+%s IS TAKING ME DOWN!
+%s IS RIPPING ME APART!
+NOW YOU SHOOT ME, %s, YEAH?
diff --git a/text/panic.txt b/text/panic.txt
new file mode 100644
index 0000000..44f5ee7
--- /dev/null
+++ b/text/panic.txt
@@ -0,0 +1,8 @@
+%s WANTS ME DEAD!
+%s IS COMING FOR ME!
+HELP ME AGAINST %s!
+NO, %s, NO!
+%s ONLY AIMS AT ME!
+%s IS TAKING ME DOWN!
+%s IS RIPPING ME APART!
+NOW YOU SHOOT ME, %s, YEAH?
diff --git a/text/panic_ES.txt b/text/panic_ES.txt
new file mode 100644
index 0000000..44f5ee7
--- /dev/null
+++ b/text/panic_ES.txt
@@ -0,0 +1,8 @@
+%s WANTS ME DEAD!
+%s IS COMING FOR ME!
+HELP ME AGAINST %s!
+NO, %s, NO!
+%s ONLY AIMS AT ME!
+%s IS TAKING ME DOWN!
+%s IS RIPPING ME APART!
+NOW YOU SHOOT ME, %s, YEAH?
diff --git a/text/panic_de.txt b/text/panic_de.txt
new file mode 100644
index 0000000..952552a
--- /dev/null
+++ b/text/panic_de.txt
@@ -0,0 +1,8 @@
+%s WILL MICH TOT SEHEN!
+%s IST HINTER MIR HER!
+HELFT MIR GEGEN %s!
+NEIN, %s, OH NEIN!
+%s ZIELT NUR AUF MICH!
+%s NIMMT MICH AUSEINANDER!
+%s ZERFETZT MICH GERADE!
+JETZT SCHIESST DU, %s, JA?
diff --git a/text/panic_fr.txt b/text/panic_fr.txt
new file mode 100644
index 0000000..44f5ee7
--- /dev/null
+++ b/text/panic_fr.txt
@@ -0,0 +1,8 @@
+%s WANTS ME DEAD!
+%s IS COMING FOR ME!
+HELP ME AGAINST %s!
+NO, %s, NO!
+%s ONLY AIMS AT ME!
+%s IS TAKING ME DOWN!
+%s IS RIPPING ME APART!
+NOW YOU SHOOT ME, %s, YEAH?
diff --git a/text/panic_it.txt b/text/panic_it.txt
new file mode 100644
index 0000000..44f5ee7
--- /dev/null
+++ b/text/panic_it.txt
@@ -0,0 +1,8 @@
+%s WANTS ME DEAD!
+%s IS COMING FOR ME!
+HELP ME AGAINST %s!
+NO, %s, NO!
+%s ONLY AIMS AT ME!
+%s IS TAKING ME DOWN!
+%s IS RIPPING ME APART!
+NOW YOU SHOOT ME, %s, YEAH?
diff --git a/text/panic_ru.txt b/text/panic_ru.txt
new file mode 100644
index 0000000..44f5ee7
--- /dev/null
+++ b/text/panic_ru.txt
@@ -0,0 +1,8 @@
+%s WANTS ME DEAD!
+%s IS COMING FOR ME!
+HELP ME AGAINST %s!
+NO, %s, NO!
+%s ONLY AIMS AT ME!
+%s IS TAKING ME DOWN!
+%s IS RIPPING ME APART!
+NOW YOU SHOOT ME, %s, YEAH?
diff --git a/text/panic_sk.txt b/text/panic_sk.txt
new file mode 100644
index 0000000..44f5ee7
--- /dev/null
+++ b/text/panic_sk.txt
@@ -0,0 +1,8 @@
+%s WANTS ME DEAD!
+%s IS COMING FOR ME!
+HELP ME AGAINST %s!
+NO, %s, NO!
+%s ONLY AIMS AT ME!
+%s IS TAKING ME DOWN!
+%s IS RIPPING ME APART!
+NOW YOU SHOOT ME, %s, YEAH?
diff --git a/text/weapons.pt_BR.txt b/text/weapons.pt_BR.txt
index 484f817..fea87f7 100644
--- a/text/weapons.pt_BR.txt
+++ b/text/weapons.pt_BR.txt
@@ -1,343 +1,258 @@
*WEAPONS*
Míssil Pequeno
Produces a low impact explosion
-1500 10 10 0.2 25 0 2 30 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Míssil Médio
Low yield, small area explosion
-2000 5 15 0.2 40 0 2 40 0 1 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Míssil Grande
Medium yield, medium area explosion
-4000 3 20 0.2 60 1 3 60 0 2 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Small Nuke
High yield, large area explosion
-16000 2 40 0.2 100 2 4 100 0 3 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Nuke
Very high yield, huge explosion
-22000 1 60 0.2 150 2 5 150 0 4 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Death Head
Massive, deadly explosion
-30000 1 80 0.2 200 3 6 200 0 5 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Spread
5 Small missiles in a single shot
-5000 3 10 0.2 25 0 2 30 0 0 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Medium Spread
5 Medium missiles in a single shot
-11000 2 15 0.2 40 0 2 40 0 1 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Large Spread
5 Large missiles in a single shot
-17000 1 20 0.2 60 1 3 60 0 2 5 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Super Spread
4 Small Nukes in a single shot
-25000 1 40 0.2 100 2 4 100 0 3 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Death Spread
3 Nukes in a single shot
-50000 1 60 0.2 150 2 5 150 0 4 3 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Armageddon
3 Death Heads in a single shot
-100000 1 80 0.2 200 3 6 200 0 5 3 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Chain Missile
Fires a volly of three small missiles
-4000 3 10 0.2 25 0 2 30 0 0 1 3 0 0 0 0 -1 0 0 0 0 0 -1 0
Chain Gun
Fires a volly of five small missiles
-6000 10 10 0.2 25 0 2 30 0 0 1 5 0 0 0 0 -1 0 0 0 0 0 -1 0
Jack Hammer
Fires a volly of eight small missiles
-8000 16 10 0.2 25 0 2 30 0 0 1 8 0 0 0 0 -1 0 0 0 0 0 -1 0
Shaped Charge
High yield horizontal explosion. All of the energy is focused out to the sides, increasing the damage done but reducing the overall area of the explosion.
-10000 5 10 0.2 100 2 3 100 0 17 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Wide Boy
Devastating horizontal explosion. All of the energy is focused out to the sides, increasing the damage done but reducing the overall area of the explosion.
-15000 2 10 0.2 200 2 4 200 0 18 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cutter
Horizontal explosion that will obliterate anything that gets in the way. All of the energy is focused out to the sides, increasing the damage done but reducing the overall area of the explosion in comparison to a similar sized uncontrolled blast.
-20000 1 10 0.2 300 3 5 300 0 19 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Small Roller
Medium explosive which rolls downhill until it hits something
-2000 3 20 0.2 40 0 2 40 0 6 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Large Roller
Large explosive which rolls downhill until it hits something
-8000 1 30 0.2 60 1 3 60 0 6 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Death Roller
Huge explosive which rolls downhill until it hits something
-14000 1 40 0.2 100 2 4 100 0 6 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small MIRV
Drops a group of large missiles on the ground from above
-20000 1 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 2 1.0 120 0.0 1.0 0 -1 0
Armour Piercing
A small, powerful shell that knocks out tanks
-2500 5 30 0.2 15 0 2 80 0 27 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Cluster Bomb
Scatters medium yield bomblets on impact
-10000 3 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 60 0 3.5 0 -1 0
Super Cluster
Scatters high yield bomblets on impact
-20000 2 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 30 0.0 60 0 3.5 0 -1 0
Funky Bomb
Unpredictable and highly destructive
-30000 3 50 0.2 200 0 0 200 0 9 1 0 0 3 0 10 27 0.0 360 1.0 0.5 0.1 -1 0
Funky Death
Unpredictable and extremely destructive
-50000 1 90 0.2 300 2 1 300 0 9 1 0 0 4 0 10 28 0.0 360 1.0 0.5 0.1 -1 0
Funky Bomblet
Medium yield explosive warhead
-7000 3 5 0.01 40 0 0 50 0 10 1 0 1 1 1 0 -1 0 0 0 0 0 125 1.0
Funky Deathlet
High yield explosive warhead
-9000 2 9 0.01 100 2 1 90 0 10 1 0 1 3 1 0 -1 0 0 0 0 0 175 1.0
Bomblet
Medium yield explosive warhead
-3500 3 20 0.2 40 0 2 40 0 8 1 0 0 1 1 0 -1 0 0 0 0 0 -1 0
Super Bomblet
High yield explosive warhead
-5000 2 40 0.2 60 1 2 65 0 8 1 0 0 3 1 0 -1 0 0 0 0 0 -1 0
Burrower
Burrows back up to the surface before exploding, good against buried tanks. The Burrower is designed for below-ground use and suffers from high air-resistance.
-7500 3 100 0.4 50 0 2 50 0 25 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Penetrator
Penetrates below ground before rising to the surface. The Penetrator will either explode on exit from the ground or on contact with buried objects. This missile is designed for burrowing and as a result suffers from high air-resistance.
-20000 2 150 0.4 80 0 2 100 0 26 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Napalm Bomb
Scatters small quantities of intensely burning napalm on impact
-6000 3 80 0.2 80 1 1 80 0 9 1 0 0 3 0 10 36 0.0 90 0.2 2.5 1.0 -1 0
Medium Napalm Bomb
Scatters intensely burning napalm on impact
-14000 2 200 0.2 150 1 1 200 0 9 1 0 0 4 0 25 36 0.0 90 0.2 3 1.0 -1 0
Large Napalm Bomb
Covers the surrounding area with intensely burning napalm on impact
-22000 1 400 0.2 200 1 2 500 0 9 1 0 0 5 0 60 36 0.0 90 0.2 5 1.0 -1 0
Napalm Jelly
Intensely burning chemical jelly
-2000 5 5 0.5 10 1 10 30 0 0 1 0 0 2 1 0 -1 0 0 0 0 0 -1 0
Driller
Vertical explosion that will do little damage, but create a deep hole in the ground.
-5000 2 10 0.2 300 3 5 10 0 28 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Tremor
Produce a small earthquake on impact
-3000 5 30 0.2 40 0 2 10 0 14 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Shock Wave
Produce a large earthquake on impact
-10000 2 60 0.2 100 2 3 20 0 15 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Tectonic Shift
Produce a huge earthquake on impact
-30000 1 100 0.2 150 2 4 30 0 16 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Riot Bomb
Destroy a small volume of dirt without damaging anything else
-2000 5 15 0.2 25 0 2 0 0 23 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Heavy Riot Bomb
Destroy a large volume of dirt without damaging anything else
-3000 2 30 0.2 100 1 2 0 0 24 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Riot Charge
Cuts through a cone of dirt directly in front of the gun
-1000 5 15 0.2 50 0 2 0 0 11 1 0 1 1 0 0 -1 0 0 0 0 0 0 0
Riot Blast
Cuts through a large cone of dirt directly in front of the gun
-2000 2 30 0.2 150 1 2 0 0 12 1 0 1 3 0 0 -1 0 0 0 0 0 0 0
Dirt Ball
Produce a small sphere of material to bury your opponents
-3000 5 15 0.2 25 0 2 0 0 11 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Large Dirt Ball
Produce a large sphere of material to bury your opponents
-7000 2 30 0.2 60 1 2 0 0 12 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Super Dirt Ball
Produce a huge ball of material to bury your opponents
-10000 1 60 0.2 100 2 2 0 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Dirt Spread
Drop little piles of dirt on your enemies
-4000 2 30 0.2 40 1 2 0 0 12 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cluster MIRV
Releases a cluster of small missiles on its way down
-10000 2 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 45 0 2.0 0 -1 0
Per Cent Bomb
Destroys half of the target's armour
-12000 2 30 0.2 30 2 4 0 0 29 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Reducer
Lowers the explosive power of an enemy's missiles.
-6000 3 25 0.3 30 2 4 0 0 30 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Laser
A 50kW laser beam.
-5000 5 1 0.0 2 0 0 30 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Medium Laser
A 100kW laser beam
-10000 3 1 0.0 5 0 0 65 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Large Laser
A powerful 200kW laser beam
-15000 2 1 0.0 9 0 0 150 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
*NATURALS*
Small Meteor
A small chunk of rock from the skies
-0 1 15 0.2 25 0 0 5 0 20 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Medium Meteor
A medium chunk of rock from the skies
-0 1 20 0.2 40 0 0 10 0 21 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Large Meteor
A large chunk of rock from the skies
-0 1 25 0.2 60 1 1 20 0 22 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Small Lightning Bolt
A weak bolt of lightning.
-0 1 1 0.0 1 0 0 5 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Medium Lightning Bolt
A bolt of lightning
-0 1 1 0.0 4 0 0 15 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Large Lightning Bolt
A powerful bolt of lightning
-0 1 1 0.0 7 0 0 35 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
*ITEMS*
Teleport
Teleports the tank to a random location
-5000 2 1 3 11
Swapper
Swap places with another tank
-10000 2 1 4 11
Mass Teleport
Teleports all tanks on the screen
-15000 2 1 4 11
Fan
Change the wind strength and direction
-2500 3 1 3 2
Vengeance
A small self-destruct or auto-destruct device, if you've got to go, why not take someone with you?
-20000 1 1 2 0 0 21
Dying Wrath
A large self/auto-destruct device. Mutually Assured Destruction.
-40000 1 1 3 0 21 3
Fatal Fury
End it all in style
-60000 1 1 4 0 22 3
Light Shield
A small amount of protection from damage
-10000 3 0 1 0 50 0 0 255 0 1
Medium Shield
Protects against damage
-20000 2 0 2 0 100 0 64 255 0 3
Heavy Shield
A large amount of protection from damage
-30000 2 0 4 0 150 0 128 255 64 6
Light Repulsor Shield
Lightly repels enemy missiles
-10000 3 0 2 0 10 250 128 0 255 1
Medium Repulsor Shield
Repels enemy missiles
-20000 2 0 3 0 20 500 192 64 255 3
Heavy Repulsor Shield
Strongly repels enemy missiles
-40000 1 0 5 0 40 1000 255 128 255 6
Armour Plating
Permanently add a small increase to the damage your tanks can take. Each additional purchase adds a slightly smaller amount to your tank's armour.
-20000 1 0 2 0 300
Plasteel Plating
Permanently increase the damage your tanks can take. Each additional purchase adds a slightly smaller amount to your tank's armour.
-40000 1 0 4 0 2155
Intensity Amplifier
A small permanent increase to the damage done by your weapons. The efficiency decreases and therefore each additional purchase has a reduced affect.
-21000 1 0 3 0 0.10
Violent Force
Permanently increase the damage done by your weapons. The efficiency decreases and as a result each additional purchase provides a smaller increase.
-50000 1 0 3 0 0.30
Slick Projectiles
A Teflon coating for projectiles to reduce drag and the affect of the wind
-1000 50 0 3 0 0.5
Dimpled Projectiles
Small dimples in the skin of projectiles for massive reduction in drag
-2000 50 0 4 0 0.1
Parachute
Allows the tank to float gently to the ground
-5000 10 0 0 0 0 0
Auto-repair kit
Repairs the tank a little each turn. Each additional kit provides a slightly smaller increase to your armour.
-10000 1 0 2 0 0 0
Fuel
Allows the tank to move across level terran.
-1000 10 0 2 0 0 0
Rocket
Launches the tank into the air.
-2000 2 1 4 0 0 0
SDI Missile Defense
Offers some protection against incoming missiles.
-10000 1 0 5 0 0
diff --git a/text/weapons.txt b/text/weapons.txt
index cb5b8eb..055a26c 100644
--- a/text/weapons.txt
+++ b/text/weapons.txt
@@ -1,277 +1,277 @@
*WEAPONS*
Small Missile
Produces a low impact explosion
-1500 10 10 0.2 25 0 2 30 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
+1500 10 10 0.2 25 0 2 30 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Medium Missile
Low yield, small area explosion
-2000 5 15 0.2 40 0 2 40 0 1 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
+2000 5 15 0.2 40 1 2 40 1 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Large Missile
Medium yield, medium area explosion
-4000 3 20 0.2 60 1 3 60 0 2 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
+4000 3 20 0.2 60 2 3 60 2 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Small Nuke
High yield, large area explosion
-16000 2 40 0.2 100 2 4 100 0 3 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
+16000 2 40 0.2 100 3 4 100 3 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Nuke
Very high yield, huge explosion
-22000 1 60 0.2 150 2 5 150 0 4 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
+22000 1 60 0.2 150 3 5 150 4 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Death Head
Massive, deadly explosion
-30000 1 80 0.2 200 3 6 200 0 5 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
+30000 1 80 0.2 200 4 6 200 5 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Spread
5 Small missiles in a single shot
-5000 3 10 0.2 25 0 2 30 0 0 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
+5000 3 10 0.2 25 0 2 30 0 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Medium Spread
5 Medium missiles in a single shot
-11000 2 15 0.2 40 0 2 40 0 1 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
+11000 2 15 0.2 40 1 2 40 1 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Large Spread
5 Large missiles in a single shot
-17000 1 20 0.2 60 1 3 60 0 2 5 0 0 2 0 0 -1 0 0 0 0 0 -1 0
+17000 1 20 0.2 60 2 3 60 2 5 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Super Spread
4 Small Nukes in a single shot
-25000 1 40 0.2 100 2 4 100 0 3 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
+25000 1 40 0.2 100 3 4 100 3 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Death Spread
3 Nukes in a single shot
-50000 1 60 0.2 150 2 5 150 0 4 3 0 0 4 0 0 -1 0 0 0 0 0 -1 0
+50000 1 60 0.2 150 3 5 150 4 3 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Armageddon
3 Death Heads in a single shot
-100000 1 80 0.2 200 3 6 200 0 5 3 0 0 5 0 0 -1 0 0 0 0 0 -1 0
+100000 1 80 0.2 200 4 6 200 5 3 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Chain Missile
Fires a volly of three small missiles
-4000 3 10 0.2 25 0 2 30 0 0 1 3 0 0 0 0 -1 0 0 0 0 0 -1 0
+4000 12 10 0.2 25 0 2 30 0 1 3 0 0 0 0 -1 0 0 0 0 0 -1 0
Chain Gun
Fires a volly of five small missiles
-6000 10 10 0.2 25 0 2 30 0 0 1 5 0 0 0 0 -1 0 0 0 0 0 -1 0
+6000 15 10 0.2 25 0 2 30 0 1 5 0 0 0 0 -1 0 0 0 0 0 -1 0
Jack Hammer
Fires a volly of eight small missiles
-8000 16 10 0.2 25 0 2 30 0 0 1 8 0 0 0 0 -1 0 0 0 0 0 -1 0
+8000 16 10 0.2 25 0 2 30 0 1 8 0 0 0 0 -1 0 0 0 0 0 -1 0
Shaped Charge
High yield horizontal explosion. All of the energy is focused out to the sides, increasing the damage done but reducing the overall area of the explosion.
-10000 5 10 0.2 100 2 3 100 0 17 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
+10000 5 10 0.2 100 3 3 100 17 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Wide Boy
Devastating horizontal explosion. All of the energy is focused out to the sides, increasing the damage done but reducing the overall area of the explosion.
-15000 2 10 0.2 200 2 4 200 0 18 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
+15000 2 10 0.2 200 3 4 200 18 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cutter
Horizontal explosion that will obliterate anything that gets in the way. All of the energy is focused out to the sides, increasing the damage done but reducing the overall area of the explosion in comparison to a similar sized uncontrolled blast.
-20000 1 10 0.2 300 3 5 300 0 19 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
+20000 1 10 0.2 300 4 5 300 19 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Small Roller
Medium explosive which rolls downhill until it hits something
-2000 3 20 0.2 40 0 2 40 0 6 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
+2000 3 20 0.2 40 1 2 40 6 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Large Roller
Large explosive which rolls downhill until it hits something
-8000 1 30 0.2 60 1 3 60 0 6 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
+8000 1 30 0.2 60 2 3 60 6 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Death Roller
Huge explosive which rolls downhill until it hits something
-14000 1 40 0.2 100 2 4 100 0 6 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
+14000 1 40 0.2 100 3 4 100 6 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small MIRV
Drops a group of large missiles on the ground from above
-20000 1 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 2 1.0 120 0.0 1.0 0 -1 0
+16000 2 200 0.2 250 2 2 300 7 1 0 0 4 0 5 2 1.0 120 0.0 1.0 0 -1 0
Armour Piercing
A small, powerful shell that knocks out tanks
-2500 5 30 0.2 15 0 2 80 0 27 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
+2500 5 30 0.2 15 1 2 80 27 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Cluster Bomb
Scatters medium yield bomblets on impact
-10000 3 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 60 0 3.5 0 -1 0
+10000 3 100 0.2 150 1 2 200 7 1 0 0 2 0 5 29 0.0 60 0 3.5 0 -1 0
Super Cluster
Scatters high yield bomblets on impact
-20000 2 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 30 0.0 60 0 3.5 0 -1 0
+20000 2 200 0.2 250 2 2 300 7 1 0 0 4 0 5 30 0.0 60 0 3.5 0 -1 0
Funky Bomb
Unpredictable and highly destructive
-30000 3 50 0.2 200 0 0 200 0 9 1 0 0 3 0 10 27 0.0 360 1.0 0.5 0.1 -1 0
+30000 3 50 0.2 200 2 0 200 9 1 0 0 3 0 10 27 0.0 360 1.0 0.5 0.1 -1 0
Funky Death
Unpredictable and extremely destructive
-50000 1 90 0.2 300 2 1 300 0 9 1 0 0 4 0 10 28 0.0 360 1.0 0.5 0.1 -1 0
+50000 1 90 0.2 300 3 1 300 9 1 0 0 4 0 10 28 0.0 360 1.0 0.5 0.1 -1 0
Funky Bomblet
Medium yield explosive warhead
-7000 3 5 0.01 40 0 0 50 0 10 1 0 1 1 1 0 -1 0 0 0 0 0 125 1.0
+7000 3 5 0.01 40 1 0 50 10 1 0 1 1 1 0 -1 0 0 0 0 0 125 1.0
Funky Deathlet
High yield explosive warhead
-9000 2 9 0.01 100 2 1 90 0 10 1 0 1 3 1 0 -1 0 0 0 0 0 175 1.0
+9000 2 9 0.01 100 3 1 90 10 1 0 1 3 1 0 -1 0 0 0 0 0 175 1.0
Bomblet
Medium yield explosive warhead
-3500 3 20 0.2 40 0 2 40 0 8 1 0 0 1 1 0 -1 0 0 0 0 0 -1 0
+3500 3 20 0.2 40 1 2 40 8 1 0 0 1 1 0 -1 0 0 0 0 0 -1 0
Super Bomblet
High yield explosive warhead
-5000 2 40 0.2 60 1 2 65 0 8 1 0 0 3 1 0 -1 0 0 0 0 0 -1 0
+5000 2 40 0.2 60 2 2 65 8 1 0 0 3 1 0 -1 0 0 0 0 0 -1 0
Burrower
Burrows back up to the surface before exploding, good against buried tanks. The Burrower is designed for below-ground use and suffers from high air-resistance.
-7500 3 100 0.4 50 0 2 50 0 25 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
+7500 3 100 0.4 50 1 2 50 25 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Penetrator
Penetrates below ground before rising to the surface. The Penetrator will either explode on exit from the ground or on contact with buried objects. This missile is designed for burrowing and as a result suffers from high air-resistance.
-20000 2 150 0.4 80 0 2 100 0 26 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
+20000 2 150 0.4 80 2 2 100 26 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Napalm Bomb
Scatters small quantities of intensely burning napalm on impact
-6000 3 80 0.2 80 1 1 80 0 9 1 0 0 3 0 10 36 0.0 90 0.2 2.5 1.0 -1 0
+6000 3 80 0.2 80 1 1 80 9 1 0 0 3 0 10 36 0.0 90 0.2 2.5 1.0 -1 0
Medium Napalm Bomb
Scatters intensely burning napalm on impact
-14000 2 200 0.2 150 1 1 200 0 9 1 0 0 4 0 25 36 0.0 90 0.2 3 1.0 -1 0
+14000 2 200 0.2 150 2 1 200 9 1 0 0 4 0 25 36 0.0 90 0.2 3 1.0 -1 0
Large Napalm Bomb
Covers the surrounding area with intensely burning napalm on impact
-22000 1 400 0.2 200 1 2 500 0 9 1 0 0 5 0 60 36 0.0 90 0.2 5 1.0 -1 0
+22000 1 400 0.2 200 3 2 500 9 1 0 0 5 0 60 36 0.0 90 0.2 5 1.0 -1 0
Napalm Jelly
Intensely burning chemical jelly
-2000 5 5 0.5 10 1 10 30 0 0 1 0 0 2 1 0 -1 0 0 0 0 0 -1 0
+2000 5 5 0.5 10 20 10 10 0 1 0 0 2 1 0 -1 0 0 0 0 0 -1 0
Driller
Vertical explosion that will do little damage, but create a deep hole in the ground.
-5000 2 10 0.2 300 3 5 10 0 28 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
+5000 2 10 0.2 300 3 5 10 28 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Tremor
Produce a small earthquake on impact
-3000 5 30 0.2 40 0 2 10 0 14 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
+3000 5 30 0.2 40 1 2 10 14 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Shock Wave
Produce a large earthquake on impact
-10000 2 60 0.2 100 2 3 20 0 15 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
+10000 2 60 0.2 100 2 3 20 15 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Tectonic Shift
Produce a huge earthquake on impact
-30000 1 100 0.2 150 2 4 30 0 16 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
+30000 1 100 0.2 150 3 4 30 16 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Riot Bomb
Destroy a small volume of dirt without damaging anything else
-2000 5 15 0.2 25 0 2 0 0 23 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
+2000 5 15 0.2 25 0 2 0 23 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Heavy Riot Bomb
Destroy a large volume of dirt without damaging anything else
-3000 2 30 0.2 100 1 2 0 0 24 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
+3000 2 30 0.2 100 1 2 0 24 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Riot Charge
Cuts through a cone of dirt directly in front of the gun
-1000 5 15 0.2 50 0 2 0 0 11 1 0 1 1 0 0 -1 0 0 0 0 0 0 0
+1000 5 15 0.2 50 0 2 0 11 1 0 1 1 0 0 -1 0 0 0 0 0 0 0
Riot Blast
Cuts through a large cone of dirt directly in front of the gun
-2000 2 30 0.2 150 1 2 0 0 12 1 0 1 3 0 0 -1 0 0 0 0 0 0 0
+2000 2 30 0.2 150 1 2 0 12 1 0 1 3 0 0 -1 0 0 0 0 0 0 0
Dirt Ball
Produce a small sphere of material to bury your opponents
-3000 5 15 0.2 25 0 2 0 0 11 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
+3000 5 15 0.2 25 0 2 0 11 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Large Dirt Ball
Produce a large sphere of material to bury your opponents
-7000 2 30 0.2 60 1 2 0 0 12 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
+7000 2 30 0.2 60 1 2 0 12 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Super Dirt Ball
Produce a huge ball of material to bury your opponents
-10000 1 60 0.2 100 2 2 0 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
+10000 1 60 0.2 100 2 2 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Dirt Spread
Drop little piles of dirt on your enemies
-4000 2 30 0.2 40 1 2 0 0 12 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
+4000 2 30 0.2 40 1 2 0 12 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cluster MIRV
Releases a cluster of small missiles on its way down
-10000 2 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 45 0 2.0 0 -1 0
+10000 2 100 0.2 150 0 2 200 7 1 0 0 2 0 5 29 0.0 45 0 2.0 0 -1 0
Per Cent Bomb
Destroys half of the target's armour
-12000 2 30 0.2 30 2 4 0 0 29 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
+12000 2 30 0.2 30 3 4 0 29 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Reducer
Lowers the explosive power of an enemy's missiles.
-6000 3 25 0.3 30 2 4 0 0 30 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
+6000 3 25 0.3 30 3 4 0 30 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Laser
A 50kW laser beam.
-5000 5 1 0.0 2 0 0 30 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
+5000 5 1 0.0 2 5 5 30 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Medium Laser
A 100kW laser beam
-10000 3 1 0.0 5 0 0 65 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
+10000 3 1 0.0 5 5 5 65 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Large Laser
A powerful 200kW laser beam
-15000 2 1 0.0 9 0 0 150 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
+15000 2 1 0.0 9 5 5 150 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
*NATURALS*
Small Meteor
A small chunk of rock from the skies
-0 1 15 0.2 25 0 0 5 0 20 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
+0 1 15 0.2 25 10 0 5 20 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Medium Meteor
A medium chunk of rock from the skies
-0 1 20 0.2 40 0 0 10 0 21 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
+0 1 20 0.2 40 11 0 10 21 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Large Meteor
A large chunk of rock from the skies
-0 1 25 0.2 60 1 1 20 0 22 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
+0 1 25 0.2 60 12 1 20 22 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Small Lightning Bolt
A weak bolt of lightning.
-0 1 1 0.0 1 0 0 5 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
+0 1 1 0.0 1 30 0 5 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Medium Lightning Bolt
A bolt of lightning
-0 1 1 0.0 4 0 0 15 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
+0 1 1 0.0 4 30 0 15 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Large Lightning Bolt
A powerful bolt of lightning
-0 1 1 0.0 7 0 0 35 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
+0 1 1 0.0 7 31 0 35 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
*ITEMS*
Teleport
Teleports the tank to a random location
-5000 2 1 3 11
+5000 2 1 3 6
Swapper
Swap places with another tank
-10000 2 1 4 11
+10000 2 1 4 6
Mass Teleport
Teleports all tanks on the screen
-15000 2 1 4 11
+15000 2 1 4 6
Fan
Change the wind strength and direction
-2500 3 1 3 2
+2500 3 1 3 7
Vengeance
A small self-destruct or auto-destruct device, if you've got to go, why not take someone with you?
-20000 1 1 2 0 0 21
+10000 1 1 2 2 1 21
Dying Wrath
A large self/auto-destruct device. Mutually Assured Destruction.
-40000 1 1 3 0 21 3
+25000 1 1 3 3 21 3
Fatal Fury
End it all in style
-60000 1 1 4 0 22 3
+50000 1 1 4 4 24 5
Light Shield
A small amount of protection from damage
@@ -339,5 +339,5 @@ Launches the tank into the air.
SDI Missile Defense
Offers some protection against incoming missiles.
-10000 1 0 5 0 0
+10000 1 0 5 5 0
diff --git a/text/weapons_ES.txt b/text/weapons_ES.txt
index a5c60f7..2e34a89 100644
--- a/text/weapons_ES.txt
+++ b/text/weapons_ES.txt
@@ -1,343 +1,258 @@
*WEAPONS*
Misil peque�o
Produce un bajo impacto de explosi�n
-1500 10 10 0.2 25 0 2 30 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Misil medio
Bajo rendimiento, peque�a �rea de explosi�n
-2000 5 15 0.2 40 0 2 40 0 1 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Misil largo
Rendimiento medio, �re media de explosi�n
-4000 3 20 0.2 60 1 3 60 0 2 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Peque�a arma nuclear
Alto rendimiento, �rea larga de explosi�n
-16000 2 40 0.2 100 2 4 100 0 3 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Arma nuclear
Muy alto rendimiento, enorme explosi�n
-22000 1 60 0.2 150 2 5 150 0 4 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cabeza de muerte
Masiva, explosi�n mortal
-30000 1 80 0.2 200 3 6 200 0 5 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Peque�a propagaci�n
5 Peque�os misiles en un �nico tiro
-5000 3 10 0.2 25 0 2 30 0 0 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Media propagaci�n
5 Misiles medios en un �nico disparo
-11000 2 15 0.2 40 0 2 40 0 1 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Larga propagaci�n
5 Largos misiles en un solo dispao
-17000 1 20 0.2 60 1 3 60 0 2 5 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Super propagaci�n
4 Peque�as armas nucleares en un solo disparo
-25000 1 40 0.2 100 2 4 100 0 3 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Propagaci�n de muerte
3 Armas nucleares en un solo disparo
-50000 1 60 0.2 150 2 5 150 0 4 3 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Armagedon
3 Cabezas de muerte en un solo disparo
-100000 1 80 0.2 200 3 6 200 0 5 3 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Cadena de misiles
Disparos de tres peque�os misiles
-4000 3 10 0.2 25 0 2 30 0 0 1 3 0 0 0 0 -1 0 0 0 0 0 -1 0
Ca�on de cadenas
Dispara 5 peque�os misiles
-6000 10 10 0.2 25 0 2 30 0 0 1 5 0 0 0 0 -1 0 0 0 0 0 -1 0
Jack Hammer
Disparo de 8 peque�os misiles
-8000 16 10 0.2 25 0 2 30 0 0 1 8 0 0 0 0 -1 0 0 0 0 0 -1 0
Carga formada
Alto rendimiento de explosi�n horizontal. Toda la energ�a es enfocada fuera de los lados, aumentado el da�o hecho pero reduciendo el area total de explosi�n.
-10000 5 10 0.2 100 2 3 100 0 17 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Chico amplio
Explosi�n devastadora horizontal. Toda la energ�a es enfocada fuera de los lados, aumentado el da�o hecho pero reduciendo el area total de explosi�n.
-15000 2 10 0.2 200 2 4 200 0 18 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cutter
Explosi�n Horizontal que arrasar� con algo del camino. Toda la energ�a es enfocada fuera de los lados, aumentado el da�o hecho pero reduciendo el area total de explosi�n en comparaci�n de una descontrolada explosi�n.
-20000 1 10 0.2 300 3 5 300 0 19 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Pequ�a apisonadora
Explosi�n media cuando la rueda golpee algo
-2000 3 20 0.2 40 0 2 40 0 6 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Larga Apisonadora
Larga explosi�n cuando la rueda golpee algo
-8000 1 30 0.2 60 1 3 60 0 6 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Apisonadora de muerte
Enorme explosi�n cuando la rueda golpee algo
-14000 1 40 0.2 100 2 4 100 0 6 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Peque�a MIRV
Caida de un grupo de misiles largos desde el suelo
-20000 1 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 2 1.0 120 0.0 1.0 0 -1 0
Armadura penetrante
Un peque�o, poderoso proyectil que noquea tanques
-2500 5 30 0.2 15 0 2 80 0 27 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Grupo de Bombas
Medio rendimiento de impacto y dispersaci�n de Bombas
-10000 3 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 60 0 3.5 0 -1 0
Super Bombas
Alto rendimiento de impacto y dispersaci�n de Bombas
-20000 2 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 30 0.0 60 0 3.5 0 -1 0
Bomba divertida
Impredecible y altamente destructiva
-30000 3 50 0.2 200 0 0 200 0 9 1 0 0 3 0 10 27 0.0 360 1.0 0.5 0.1 -1 0
Muerte divertida
Impredecible y extremadamente destructiva
-50000 1 90 0.2 300 2 1 300 0 9 1 0 0 4 0 10 28 0.0 360 1.0 0.5 0.1 -1 0
Divertido bombazo
Rendimiento medio de explosi�n de ojiva
-7000 3 5 0.01 40 0 0 50 0 10 1 0 1 1 1 0 -1 0 0 0 0 0 125 1.0
Mortalmente Divertido
Alto rendimiento de explosi�n de ojiva
-9000 2 9 0.01 100 2 1 90 0 10 1 0 1 3 1 0 -1 0 0 0 0 0 175 1.0
Bombazo
Medio rendimiento de explosi�n de ojiva
-3500 3 20 0.2 40 0 2 40 0 8 1 0 0 1 1 0 -1 0 0 0 0 0 -1 0
Super Bombazo
Alto rendimiento de explosi�n de ojiva
-5000 2 40 0.2 60 1 2 65 0 8 1 0 0 3 1 0 -1 0 0 0 0 0 -1 0
Burrower
Burrows llegan a la superficie antes de explotar, bien contra tanques. El Burrower est� dise�ado para estar abajo y salir a la superficie con alta resistencia.
-7500 3 100 0.4 50 0 2 50 0 25 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Penetrador
Penetradores van abajo antes de ascender a la superficie. El Penetrador explotar� al salir y tener contacto con cualquier objeto.
-20000 2 150 0.4 80 0 2 100 0 26 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Peque�a Bomba Napalm
Peque�as cantidades de dispersaci�n de Napales de intensamente impacto
-6000 3 80 0.2 80 1 1 80 0 9 1 0 0 3 0 10 36 0.0 90 0.2 2.5 1.0 -1 0
Media Bomba Napalm
Quemadura intensa de impacto de Napalm
-14000 2 200 0.2 150 1 1 200 0 9 1 0 0 4 0 25 36 0.0 90 0.2 3 1.0 -1 0
Larga Bomba Napalm
Cobertura circundante del area con intensamente ardimiento al impacto
-22000 1 400 0.2 200 1 2 500 0 9 1 0 0 5 0 60 36 0.0 90 0.2 5 1.0 -1 0
Pasta Napalm
Ardimiento Intensamente qu�mico
-2000 5 5 0.5 10 1 10 30 0 0 1 0 0 2 1 0 -1 0 0 0 0 0 -1 0
Taladror
Explosi�n vertical que hace un peque�o da�o, pero crea un profundo hoyo en el suelo.
-5000 2 10 0.2 300 3 5 10 0 28 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Temblor
Produce un peque�o terremoto al impacto
-3000 5 30 0.2 40 0 2 10 0 14 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Ola de impresi�n
Produce un largo temblor de impacto
-10000 2 60 0.2 100 2 3 20 0 15 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cambio Tect�nico
Produce un enorme temblor al impacto
-30000 1 100 0.2 150 2 4 30 0 16 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Boma Disturbios
Destruye un peque�o volumen de suciedad sin perjudicar algo m�s
-2000 5 15 0.2 25 0 2 0 0 23 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Pesada Bomba de Disturbios
Destruye un largo volumen de suciedad sin perjudicar algo m�s
-3000 2 30 0.2 100 1 2 0 0 24 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Carga de Disturbios
Cortes a trav�s de un cono que va directamente al frente del arma
-1000 5 15 0.2 50 0 2 0 0 11 1 0 1 1 0 0 -1 0 0 0 0 0 0 0
Explosi�n de Disturbios
Cortes a trav�s de un largo cono que va directamente al frente del arma
-2000 2 30 0.2 150 1 2 0 0 12 1 0 1 3 0 0 -1 0 0 0 0 0 0 0
Bola de suciedad
PProduce una peque�a esfera de material que da�a a tus oponentes
-3000 5 15 0.2 25 0 2 0 0 11 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Larga Bola de Suciedad Dirt Ball
Produce una larga esfera de material que da�a a tus oponentes
-7000 2 30 0.2 60 1 2 0 0 12 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Super Bola de Suciedad
Produce una enorme esfera de material que da�a a tus oponentes
-10000 1 60 0.2 100 2 2 0 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Peque�a propagaci�n de Suciedad
Peque�a gota de suciedad en tus enemigos
-4000 2 30 0.2 40 1 2 0 0 12 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Grupo MIRV
Lanzamiento de peque�os misiles en caidas
-10000 2 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 45 0 2.0 0 -1 0
Bomba Por Ciento
Destruye la mitad de protecci�n de las armaduras
-12000 2 30 0.2 30 2 4 0 0 29 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Reductor
Baja el poder de explosion de los misiles enemigos.
-6000 3 25 0.3 30 2 4 0 0 30 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Peque�o Laser
Emite 50kW de laser.
-5000 5 1 0.0 2 0 0 30 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Laser Medio
Emite 100kW de laser
-10000 3 1 0.0 5 0 0 65 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Laser Largo
Emite poderosos 200kW de laser
-15000 2 1 0.0 9 0 0 150 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
*NATURALS*
Peque�o Meteoro
Un peque�o trozo de roca de los cielos
-0 1 15 0.2 25 0 0 5 0 20 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Meteoro Medio
Un trozo mediano de roca de los cielos
-0 1 20 0.2 40 0 0 10 0 21 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Meteoro Largo
Un trozo largo de roca de los cielos
-0 1 25 0.2 60 1 1 20 0 22 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Peque�o rayo rel�mpago
Un d�bil rayo de rel�mpago.
-0 1 1 0.0 1 0 0 5 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Medio rayo rel�mpago
Un rayo de rel�mpago
-0 1 1 0.0 4 0 0 15 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Largo rayo rel�mpago
Un poderoso rayo de rel�mpago
-0 1 1 0.0 7 0 0 35 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
*ITEMS*
Teletransportador
Tele transporta el tanque a un lugar aleatorio
-5000 2 1 3 11
Cambiador
Cambia lugares con otro tanque
-10000 2 1 4 11
Gran Teletransportador
Tele transporta todos los tanques de la pantalla
-15000 2 1 4 11
Ventilador
Cambia la fuerza y direcci�n del viento
-2500 3 1 3 2
Venganza
Un peque�o dispositivo auto-destructor, si tienes que irte, por qu� no llevarte a alguien contigo?
-20000 1 1 2 0 0 21
Ira Moribunda
Un dispositivo largo de auto-destrucci�n. Destrucci�n Mutuamente Segura.
-40000 1 1 3 0 21 3
Furia Fatal
Acabalo todo con Estilo
-60000 1 1 4 0 22 3
Luz Protectora
Una peque�a cantidad de protecci�n de da�os
-10000 3 0 1 0 50 0 0 255 0 1
Escudo Medio
Protecci�n contra da�os
-20000 2 0 2 0 100 0 64 255 0 3
Escudo Pesado
Una larga cantidad de protecci�n de da�os
-30000 2 0 4 0 150 0 128 255 64 6
Escudo Repulsor de Luz
Repele ligeramente los misiles enemigos
-10000 3 0 2 0 10 250 128 0 255 1
Escudo Repulsor Medio
Repele los misiles enemigos
-20000 2 0 3 0 20 500 192 64 255 3
Pesado Escudo Repulsor
Repele fuertemente los misiles enemigos
-40000 1 0 5 0 40 1000 255 128 255 6
Armadura Blindada
Permanentemente a�ade un peque�o incremento al da�o que tus tanques pueden llevar. Cada compra adicional a�ade un peque�o monto a la armadura de tu tanque.
-20000 1 0 2 0 300
Plasteel Plating
Permanentemente a�ade un peque�o incremento al da�o que tus tanques pueden llevar. Cada compra adicional a�ade un peque�o monto a la armadura de tu tanque.
-40000 1 0 4 0 2155
Amplifcador de Intensidad
Un permanemte peque�o incremento al da�o hecho por tus armas. La eficiencia disminuye y por tanto cada compra adicional tiene un afecto reducido.
-21000 1 0 3 0 0.10
Fuerza Violenta
Un permanemte peque�o incremento al da�o hecho por tus armas. La eficiencia disminuye y como un resultado de compra adicional provee un peque�o incremento.
-50000 1 0 3 0 0.30
Proyectiles Mejorados
Una capa de Teflon para proyectiles que reduce el arrastre y afecta al viento
-1000 50 0 3 0 0.5
Proyectiles Perforados
Peque�os hoyuelos en la piel de los proyectiles para la reducci�n masica en el arrastre
-2000 50 0 4 0 0.1
Paraca�das
Permite al tanque flotar con delicadeza al suelo
-5000 10 0 0 0 0 0
Kit de Auto-Reparaci�n
Repara un poco el tanque en cada turno. Cada kit adicional provee un peque�o increment a tu armadura.
-10000 1 0 2 0 0 0
Combustible
Permite al tanque moverse en el terreno.
-1000 10 0 2 0 0 0
Cohete
Lanzamientos del tanque al aire.
-2000 2 1 4 0 0 0
SDI Missile Defensa
Ofrcece alguna protecci�n contra la llegada de misiles.
-10000 1 0 5 0 0
diff --git a/text/weapons_de.txt b/text/weapons_de.txt
index 7f98eb0..1a71db2 100644
--- a/text/weapons_de.txt
+++ b/text/weapons_de.txt
@@ -1,343 +1,258 @@
*WEAPONS*
Kleine Rakete
Kleine Explosion beim Einschlag.
-1500 10 10 0.2 25 0 2 30 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Mittlere Rakete
Kleine Wirkung, kleinflächige Explosion.
-2000 5 15 0.2 40 0 2 40 0 1 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Große Rakete
Mittlerer Wirkung, mittelflächige Explosion.
-4000 3 20 0.2 60 1 3 60 0 2 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Kleine Atombombe
Große Wirkung, großflächige Explosion.
-16000 2 40 0.2 100 2 4 100 0 3 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Atombombe
Sehr große Wirkung, riesige Explosion.
-22000 1 60 0.2 150 2 5 150 0 4 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Totenkopf
Massive, tötliche Explosion.
-30000 1 80 0.2 200 3 6 200 0 5 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Kleiner Streuer
5 Kleine Raketen mit einem Schuss.
-5000 3 10 0.2 25 0 2 30 0 0 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Mittlerer Streuer
5 Mittlere Raketen mit einem Schuss.
-11000 2 15 0.2 40 0 2 40 0 1 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Großer Streuer
5 Große Raketen mit einem Schuss.
-17000 1 20 0.2 60 1 3 60 0 2 5 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Super Streuer
4 Kleine Atombomben mit einem Schuss.
-25000 1 40 0.2 100 2 4 100 0 3 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Todesstreuer
3 Atombomben mit einem Schuss.
-50000 1 60 0.2 150 2 5 150 0 4 3 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Armageddon
3 Totenköpfe mit einem Schuss.
-100000 1 80 0.2 200 3 6 200 0 5 3 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Kleine Raktenkette
Feuert drei kleine Rakten nacheinander ab.
-4000 3 10 0.2 25 0 2 30 0 0 1 3 0 0 0 0 -1 0 0 0 0 0 -1 0
Raketenkette
Feuert fünf kleine Rakten nacheinander ab.
-6000 10 10 0.2 25 0 2 30 0 0 1 5 0 0 0 0 -1 0 0 0 0 0 -1 0
Jack Hammer
Feuert acht kleine Rakten nacheinander ab.
-8000 16 10 0.2 25 0 2 30 0 0 1 8 0 0 0 0 -1 0 0 0 0 0 -1 0
Shaped Charge
Hoch wirksame horizontale Explosion. Die gesamte Energie ist zu den Seiten fokusiert und erhöht den Schaden und verdampft die gesamte Umgebung im Bereich der Explosion.
-10000 5 10 0.2 100 2 3 100 0 17 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Wide Boy
Verheerende horizontale Explosion. Die gesamte Energie ist zu den Seiten fokusiert und erhöht den Schaden und verdampft die gesamte Umgebung im Bereich der Explosion.
-15000 2 10 0.2 200 2 4 200 0 18 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cutter
Horizontale Explosion die alles auslöscht, was in ihren Weg kommt. Die gesamte Energie ist zu den Seiten fokusiert und erhöht den Schaden und verdampft die gesamte Umgebung im Bereich der Explosion, vergleichbar mit einer ebensogrßen unkontrollierten Sprengung.
-20000 1 10 0.2 300 3 5 300 0 19 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Kleiner Roller
Mittel explosiver Sprengsatz, der den Berg hinunter rollt bis er etwas trifft.
-2000 3 20 0.2 40 0 2 40 0 6 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Großer Roller
Großer explosiver Sprengsatz, der den Berg hinunter rollt bis er etwas trifft.
-8000 1 30 0.2 60 1 3 60 0 6 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Todesroller
Riesiger explosiver Sprengsatz, der den Berg hinunter rollt bis er etwas trifft.
-14000 1 40 0.2 100 2 4 100 0 6 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Kleiner MIRV
Wirft eine Gruppe großer Raketen von oben auf das Zielgebiet.
-20000 1 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 2 1.0 120 0.0 1.0 0 -1 0
Rüstungsknacker
Eine kleine, wirksame Granatedie die Panzer zerstören kann.
-2500 5 30 0.2 15 0 2 80 0 27 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Cluster Bombe
Streut einen mittelwirksamen Bombentepich, der beim Aufschlag explodiert.
-10000 3 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 60 0 3.5 0 -1 0
Super Cluster
Streut einen hochwirksamen Bombentepich, der beim Aufschlag explodiert.
-20000 2 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 30 0.0 60 0 3.5 0 -1 0
Funky Bombe
Unberechenbar und hoch zerstörerisch.
-30000 3 50 0.2 200 0 0 200 0 9 1 0 0 3 0 10 27 0.0 360 1.0 0.5 0.1 -1 0
Funky Tod
Unberechenbar und extrem zerstörerisch.
-50000 1 90 0.2 300 2 1 300 0 9 1 0 0 4 0 10 28 0.0 360 1.0 0.5 0.1 -1 0
Funky Bombenteppich
Mittel wirksamer explosiver Sprengkopf.
-7000 3 5 0.01 40 0 0 50 0 10 1 0 1 1 1 0 -1 0 0 0 0 0 125 1.0
Funky Todesteppich
Hoch wirksamer explosiver Sprengkopf.
-9000 2 9 0.01 100 2 1 90 0 10 1 0 1 3 1 0 -1 0 0 0 0 0 175 1.0
Bombenteppich
Mittel wirksamer explosiver Sprengkopf.
-3500 3 20 0.2 40 0 2 40 0 8 1 0 0 1 1 0 -1 0 0 0 0 0 -1 0
Super Bombenteppich
Hoch wirksamer explosiver Sprengkopf.
-5000 2 40 0.2 60 1 2 65 0 8 1 0 0 3 1 0 -1 0 0 0 0 0 -1 0
Erdfresser
Bohrt sich zurück zur Oberfläche bevor er explodiert. Gut gegen unterirdische Panzer. Der Erdfresser ist für unterirdische Einsätze entwickelt und leidet unter hohem Luftwiderstand.
-7500 3 100 0.4 50 0 2 50 0 25 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Penetrator
Bohrt sich unter die Oberfläche bevor er wieder zur Oberfläche kommt. The Penetrator explodiert entweder beim erreichen der Oberfläche oder bei Kontakt mit einem verschütteten Objekt. Diese Rakete ist für unterirdische Einsätze entwickelt und leidet unter hohem Luftwiderstand.
-20000 2 150 0.4 80 0 2 100 0 26 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Kleine Napalmbombe
Streut beim Einschlag kleine Mengen intensiv brennenden Napalms.
-6000 3 80 0.2 80 1 1 80 0 9 1 0 0 3 0 10 36 0.0 90 0.2 2.5 1.0 -1 0
Mittlere Napalmbombe
Streut beim Einschlag intensiv brennendes Napalm.
-14000 2 200 0.2 150 1 1 200 0 9 1 0 0 4 0 25 36 0.0 90 0.2 3 1.0 -1 0
Große Napalmbombe
Überzieht die Umgebung beim Einschlag mit intensiv brennendem Napalm.
-22000 1 400 0.2 200 1 2 500 0 9 1 0 0 5 0 60 36 0.0 90 0.2 5 1.0 -1 0
Napalmgel
Intensiv brennendes chemisches Gel.
-2000 5 5 0.5 10 1 10 30 0 0 1 0 0 2 1 0 -1 0 0 0 0 0 -1 0
Bohrer
Vertikale Explosion, die kleinen Schaden anrichtet, aber ein tiefes Loch in die Oberfläche macht.
-5000 2 10 0.2 300 3 5 10 0 28 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Tremor
Erzeugt beim Einschlag ein kleines Erdbeben.
-3000 5 30 0.2 40 0 2 10 0 14 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Schockwelle
Erzeugt beim Einschlag ein großes Erdbeben.
-10000 2 60 0.2 100 2 3 20 0 15 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Tektonische Bombe
Erzeugt beim Einschlag ein riesiges Erdbeben.
-30000 1 100 0.2 150 2 4 30 0 16 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Rebellenbombe
Zerstört eine kleine Menge Erde ohne etwas anderes zu zerstören.
-2000 5 15 0.2 25 0 2 0 0 23 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Große Rebellenbombe
Zerstört eine große Menge Erde ohne etwas anderes zu zerstören.
-3000 2 30 0.2 100 1 2 0 0 24 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Kleiner Unruhestifter
Schneidet einen Kegel aus dem Untergrund, direkt vor der Mündung.
-1000 5 15 0.2 50 0 2 0 0 11 1 0 1 1 0 0 -1 0 0 0 0 0 0 0
Großer Unruhestifter
Schneidet einen großen Kegel aus dem Untergrund, direkt vor der Mündung.
-2000 2 30 0.2 150 1 2 0 0 12 1 0 1 3 0 0 -1 0 0 0 0 0 0 0
Dreckball
Erzeugt einen kleinen Hügel um den Gegner zu verschütten.
-3000 5 15 0.2 25 0 2 0 0 11 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Großer Dreckball
Erzeugt einen großen Hügel um den Gegner zu verschütten.
-7000 2 30 0.2 60 1 2 0 0 12 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Super Dreckball
Erzeugt einen riesen Hügel um den Gegner zu verschütten.
-10000 1 60 0.2 100 2 2 0 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Kleiner Dreckball
Drop little piles of dirt on your enemies
-4000 2 30 0.2 40 1 2 0 0 12 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cluster MIRV
Wirft eine Gruppe kleiner Raketen von oben auf das Zielgebiet.
-10000 2 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 45 0 2.0 0 -1 0
Prozent Bombe
Zerstört die Hälfte der gegnerischen Panzerung.
-12000 2 30 0.2 30 2 4 0 0 29 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Reduzierer
Vermindert die Kraft gegnerischer Waffen.
-6000 3 25 0.3 30 2 4 0 0 30 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Kleiner Laser
Ein 50kW Laserstrahl.
-5000 5 1 0.0 2 0 0 30 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Mittlerer Laser
Ein 100kW Laserstrahl.
-10000 3 1 0.0 5 0 0 65 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Großer Laser
Ein kraftvoller 200kW Laserstrahl.
-15000 2 1 0.0 9 0 0 150 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
*NATURALS*
Kleiner Meteor
Ein kleiner Felsbrocken vom Himmel.
-0 1 15 0.2 25 0 0 5 0 20 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Mittlerer Meteor
Ein mittlerer Felsbrocken vom Himmel.
-0 1 20 0.2 40 0 0 10 0 21 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Großer Meteor
Ein großer Felsbrocken vom Himmel.
-0 1 25 0.2 60 1 1 20 0 22 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Kleiner Blitz
Ein schwacher Blitz.
-0 1 1 0.0 1 0 0 5 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Mittlerer Blitz
Ein Blitz.
-0 1 1 0.0 4 0 0 15 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Großer Blitz - Kugelbllitz
Ein starker Blitz.
-0 1 1 0.0 7 0 0 35 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
*ITEMS*
Teleport
Teleportiert den Panzer an einen zufälligen Ort.
-5000 2 1 3 11
Tausch
Tauscht mit einem anderen Panzer den Platz.
-10000 2 1 4 11
Massenteleportation
Teleportiert alle Panzer auf dem Bildschirm.
-15000 2 1 4 11
Ventilator
Ändert Windstärke und -richtung.
-2500 3 1 3 2
Vengeance
Ein kleiner Selbstzerstörer. Wenn Du gehen musst, warum nicht noch jemanden mitnehmen?
-20000 1 1 2 0 0 21
Todeswut
Ein großer Selbstzerstörer. Gleichgewicht des Schreckens.
-40000 1 1 3 0 21 3
Tödliche Raserei
Beendet jedes Laben.
-60000 1 1 4 0 22 3
Schwaches Schild
Ein kleiner Schutz.
-10000 3 0 1 0 50 0 0 255 0 1
Mittleres Schild
Schutz gegen Schaden.
-20000 2 0 2 0 100 0 64 255 0 3
Schweres Schild
EIn starker Schutz.
-30000 2 0 4 0 150 0 128 255 64 6
Kleines Kraftfeld
Lässt feindliche Raketen leicht abprallen.
-10000 3 0 2 0 10 250 128 0 255 1
Mittleres Kraftfeld
Lässt feindliche Raketen abprallen.
-20000 2 0 3 0 20 500 192 64 255 3
Starkes Kraftfeld
Lässt feindliche Raketen stark abprallen.
-40000 1 0 5 0 40 1000 255 128 255 6
Verstärkte Rüstung
Erhöht die Treffer, die Dein Panzer einstecken kann ein wenig. Jeder weitere Kauf verstärkt die Rüstung Deines Panzers ein wenig.
-20000 1 0 2 0 300
Plastahl Rüstung
Erhöht die Treffer, die Dein Panzer einstecken kann ein wenig. Jeder weitere Kauf verstärkt die Rüstung Deines Panzers ein wenig.
-40000 1 0 4 0 2155
Intensiver Verstärker
Eine kleine permanente Erhöhung des Schadens, den Deine Waffe anrichtet. Mit jedem Kauf zeigt der Verstärker weniger Wirkung, so dass weiter Käufe weniger zusätzlichen Schaden bringen.
-21000 1 0 3 0 0.10
Brutaler Verstärker
Eine permanente Erhöhung des Schadens, den Deine Waffe anrichtet. Mit jedem Kauf zeigt der Verstärker weniger Wirkung, so dass weiter Käufe weniger zusätzlichen Schaden bringen.
-50000 1 0 3 0 0.30
Glatte Projektile
Eine Teflonbeschichtung reduziert den Luftwiderstand und den Effekt des Windes.
-1000 50 0 3 0 0.5
Genoppte Projektile
Kleine Noppen auf der Oberfläche der Projektile sorgen für eine massive Reduzierung des Luftwiderstandes.
-2000 50 0 4 0 0.1
Fallschirm
Erlaubt es dem Panzer sanft auf dem Boden zu landen.
-5000 10 0 0 0 0 0
Reparatursatz
Repariert den Panzer jede Runde ein wenig. Jeder weitere Reparatursatz unterstützt die Reparatur ein bisschen weniger.
-10000 1 0 2 0 0 0
Benzin
Erlaubt es dem Panzer, sich auf ebenem Gelände zu bewegen.
-1000 10 0 2 0 0 0
Rakete
Schießt den Panzer in die Luft.
-2000 2 1 4 0 0 0
SDI Missile Defense System
Bietet Schutz gegen ankommende Geschosse.
-10000 1 0 5 0 0
diff --git a/text/weapons_fr.txt b/text/weapons_fr.txt
index cb5b8eb..bfdc440 100644
--- a/text/weapons_fr.txt
+++ b/text/weapons_fr.txt
@@ -1,343 +1,258 @@
*WEAPONS*
Small Missile
Produces a low impact explosion
-1500 10 10 0.2 25 0 2 30 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Medium Missile
Low yield, small area explosion
-2000 5 15 0.2 40 0 2 40 0 1 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Large Missile
Medium yield, medium area explosion
-4000 3 20 0.2 60 1 3 60 0 2 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Small Nuke
High yield, large area explosion
-16000 2 40 0.2 100 2 4 100 0 3 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Nuke
Very high yield, huge explosion
-22000 1 60 0.2 150 2 5 150 0 4 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Death Head
Massive, deadly explosion
-30000 1 80 0.2 200 3 6 200 0 5 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Spread
5 Small missiles in a single shot
-5000 3 10 0.2 25 0 2 30 0 0 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Medium Spread
5 Medium missiles in a single shot
-11000 2 15 0.2 40 0 2 40 0 1 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Large Spread
5 Large missiles in a single shot
-17000 1 20 0.2 60 1 3 60 0 2 5 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Super Spread
4 Small Nukes in a single shot
-25000 1 40 0.2 100 2 4 100 0 3 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Death Spread
3 Nukes in a single shot
-50000 1 60 0.2 150 2 5 150 0 4 3 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Armageddon
3 Death Heads in a single shot
-100000 1 80 0.2 200 3 6 200 0 5 3 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Chain Missile
Fires a volly of three small missiles
-4000 3 10 0.2 25 0 2 30 0 0 1 3 0 0 0 0 -1 0 0 0 0 0 -1 0
Chain Gun
Fires a volly of five small missiles
-6000 10 10 0.2 25 0 2 30 0 0 1 5 0 0 0 0 -1 0 0 0 0 0 -1 0
Jack Hammer
Fires a volly of eight small missiles
-8000 16 10 0.2 25 0 2 30 0 0 1 8 0 0 0 0 -1 0 0 0 0 0 -1 0
Shaped Charge
High yield horizontal explosion. All of the energy is focused out to the sides, increasing the damage done but reducing the overall area of the explosion.
-10000 5 10 0.2 100 2 3 100 0 17 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Wide Boy
Devastating horizontal explosion. All of the energy is focused out to the sides, increasing the damage done but reducing the overall area of the explosion.
-15000 2 10 0.2 200 2 4 200 0 18 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cutter
Horizontal explosion that will obliterate anything that gets in the way. All of the energy is focused out to the sides, increasing the damage done but reducing the overall area of the explosion in comparison to a similar sized uncontrolled blast.
-20000 1 10 0.2 300 3 5 300 0 19 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Small Roller
Medium explosive which rolls downhill until it hits something
-2000 3 20 0.2 40 0 2 40 0 6 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Large Roller
Large explosive which rolls downhill until it hits something
-8000 1 30 0.2 60 1 3 60 0 6 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Death Roller
Huge explosive which rolls downhill until it hits something
-14000 1 40 0.2 100 2 4 100 0 6 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small MIRV
Drops a group of large missiles on the ground from above
-20000 1 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 2 1.0 120 0.0 1.0 0 -1 0
Armour Piercing
A small, powerful shell that knocks out tanks
-2500 5 30 0.2 15 0 2 80 0 27 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Cluster Bomb
Scatters medium yield bomblets on impact
-10000 3 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 60 0 3.5 0 -1 0
Super Cluster
Scatters high yield bomblets on impact
-20000 2 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 30 0.0 60 0 3.5 0 -1 0
Funky Bomb
Unpredictable and highly destructive
-30000 3 50 0.2 200 0 0 200 0 9 1 0 0 3 0 10 27 0.0 360 1.0 0.5 0.1 -1 0
Funky Death
Unpredictable and extremely destructive
-50000 1 90 0.2 300 2 1 300 0 9 1 0 0 4 0 10 28 0.0 360 1.0 0.5 0.1 -1 0
Funky Bomblet
Medium yield explosive warhead
-7000 3 5 0.01 40 0 0 50 0 10 1 0 1 1 1 0 -1 0 0 0 0 0 125 1.0
Funky Deathlet
High yield explosive warhead
-9000 2 9 0.01 100 2 1 90 0 10 1 0 1 3 1 0 -1 0 0 0 0 0 175 1.0
Bomblet
Medium yield explosive warhead
-3500 3 20 0.2 40 0 2 40 0 8 1 0 0 1 1 0 -1 0 0 0 0 0 -1 0
Super Bomblet
High yield explosive warhead
-5000 2 40 0.2 60 1 2 65 0 8 1 0 0 3 1 0 -1 0 0 0 0 0 -1 0
Burrower
Burrows back up to the surface before exploding, good against buried tanks. The Burrower is designed for below-ground use and suffers from high air-resistance.
-7500 3 100 0.4 50 0 2 50 0 25 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Penetrator
Penetrates below ground before rising to the surface. The Penetrator will either explode on exit from the ground or on contact with buried objects. This missile is designed for burrowing and as a result suffers from high air-resistance.
-20000 2 150 0.4 80 0 2 100 0 26 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Napalm Bomb
Scatters small quantities of intensely burning napalm on impact
-6000 3 80 0.2 80 1 1 80 0 9 1 0 0 3 0 10 36 0.0 90 0.2 2.5 1.0 -1 0
Medium Napalm Bomb
Scatters intensely burning napalm on impact
-14000 2 200 0.2 150 1 1 200 0 9 1 0 0 4 0 25 36 0.0 90 0.2 3 1.0 -1 0
Large Napalm Bomb
Covers the surrounding area with intensely burning napalm on impact
-22000 1 400 0.2 200 1 2 500 0 9 1 0 0 5 0 60 36 0.0 90 0.2 5 1.0 -1 0
Napalm Jelly
Intensely burning chemical jelly
-2000 5 5 0.5 10 1 10 30 0 0 1 0 0 2 1 0 -1 0 0 0 0 0 -1 0
Driller
Vertical explosion that will do little damage, but create a deep hole in the ground.
-5000 2 10 0.2 300 3 5 10 0 28 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Tremor
Produce a small earthquake on impact
-3000 5 30 0.2 40 0 2 10 0 14 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Shock Wave
Produce a large earthquake on impact
-10000 2 60 0.2 100 2 3 20 0 15 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Tectonic Shift
Produce a huge earthquake on impact
-30000 1 100 0.2 150 2 4 30 0 16 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Riot Bomb
Destroy a small volume of dirt without damaging anything else
-2000 5 15 0.2 25 0 2 0 0 23 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Heavy Riot Bomb
Destroy a large volume of dirt without damaging anything else
-3000 2 30 0.2 100 1 2 0 0 24 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Riot Charge
Cuts through a cone of dirt directly in front of the gun
-1000 5 15 0.2 50 0 2 0 0 11 1 0 1 1 0 0 -1 0 0 0 0 0 0 0
Riot Blast
Cuts through a large cone of dirt directly in front of the gun
-2000 2 30 0.2 150 1 2 0 0 12 1 0 1 3 0 0 -1 0 0 0 0 0 0 0
Dirt Ball
Produce a small sphere of material to bury your opponents
-3000 5 15 0.2 25 0 2 0 0 11 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Large Dirt Ball
Produce a large sphere of material to bury your opponents
-7000 2 30 0.2 60 1 2 0 0 12 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Super Dirt Ball
Produce a huge ball of material to bury your opponents
-10000 1 60 0.2 100 2 2 0 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Dirt Spread
Drop little piles of dirt on your enemies
-4000 2 30 0.2 40 1 2 0 0 12 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cluster MIRV
Releases a cluster of small missiles on its way down
-10000 2 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 45 0 2.0 0 -1 0
Per Cent Bomb
Destroys half of the target's armour
-12000 2 30 0.2 30 2 4 0 0 29 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Reducer
Lowers the explosive power of an enemy's missiles.
-6000 3 25 0.3 30 2 4 0 0 30 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Laser
A 50kW laser beam.
-5000 5 1 0.0 2 0 0 30 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Medium Laser
A 100kW laser beam
-10000 3 1 0.0 5 0 0 65 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Large Laser
A powerful 200kW laser beam
-15000 2 1 0.0 9 0 0 150 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
*NATURALS*
Small Meteor
A small chunk of rock from the skies
-0 1 15 0.2 25 0 0 5 0 20 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Medium Meteor
A medium chunk of rock from the skies
-0 1 20 0.2 40 0 0 10 0 21 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Large Meteor
A large chunk of rock from the skies
-0 1 25 0.2 60 1 1 20 0 22 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Small Lightning Bolt
A weak bolt of lightning.
-0 1 1 0.0 1 0 0 5 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Medium Lightning Bolt
A bolt of lightning
-0 1 1 0.0 4 0 0 15 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Large Lightning Bolt
A powerful bolt of lightning
-0 1 1 0.0 7 0 0 35 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
*ITEMS*
Teleport
Teleports the tank to a random location
-5000 2 1 3 11
Swapper
Swap places with another tank
-10000 2 1 4 11
Mass Teleport
Teleports all tanks on the screen
-15000 2 1 4 11
Fan
Change the wind strength and direction
-2500 3 1 3 2
Vengeance
A small self-destruct or auto-destruct device, if you've got to go, why not take someone with you?
-20000 1 1 2 0 0 21
Dying Wrath
A large self/auto-destruct device. Mutually Assured Destruction.
-40000 1 1 3 0 21 3
Fatal Fury
End it all in style
-60000 1 1 4 0 22 3
Light Shield
A small amount of protection from damage
-10000 3 0 1 0 50 0 0 255 0 1
Medium Shield
Protects against damage
-20000 2 0 2 0 100 0 64 255 0 3
Heavy Shield
A large amount of protection from damage
-30000 2 0 4 0 150 0 128 255 64 6
Light Repulsor Shield
Lightly repels enemy missiles
-10000 3 0 2 0 10 250 128 0 255 1
Medium Repulsor Shield
Repels enemy missiles
-20000 2 0 3 0 20 500 192 64 255 3
Heavy Repulsor Shield
Strongly repels enemy missiles
-40000 1 0 5 0 40 1000 255 128 255 6
Armour Plating
Permanently add a small increase to the damage your tanks can take. Each additional purchase adds a slightly smaller amount to your tank's armour.
-20000 1 0 2 0 300
Plasteel Plating
Permanently increase the damage your tanks can take. Each additional purchase adds a slightly smaller amount to your tank's armour.
-40000 1 0 4 0 2155
Intensity Amplifier
A small permanent increase to the damage done by your weapons. The efficiency decreases and therefore each additional purchase has a reduced affect.
-21000 1 0 3 0 0.10
Violent Force
Permanently increase the damage done by your weapons. The efficiency decreases and as a result each additional purchase provides a smaller increase.
-50000 1 0 3 0 0.30
Slick Projectiles
A Teflon coating for projectiles to reduce drag and the affect of the wind
-1000 50 0 3 0 0.5
Dimpled Projectiles
Small dimples in the skin of projectiles for massive reduction in drag
-2000 50 0 4 0 0.1
Parachute
Allows the tank to float gently to the ground
-5000 10 0 0 0 0 0
Auto-repair kit
Repairs the tank a little each turn. Each additional kit provides a slightly smaller increase to your armour.
-10000 1 0 2 0 0 0
Fuel
Allows the tank to move across level terran.
-1000 10 0 2 0 0 0
Rocket
Launches the tank into the air.
-2000 2 1 4 0 0 0
SDI Missile Defense
Offers some protection against incoming missiles.
-10000 1 0 5 0 0
diff --git a/text/weapons_it.txt b/text/weapons_it.txt
index 50356a1..71df070 100644
--- a/text/weapons_it.txt
+++ b/text/weapons_it.txt
@@ -1,343 +1,258 @@
*WEAPONS*
Missile Piccolo
Produce una piccola esplosione all'impatto
-1500 10 10 0.2 25 0 2 30 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Missile Medio
Basso rendimento, piccola area esplosione
-2000 5 15 0.2 40 0 2 40 0 1 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Missile Grande
Medio rendimento, media area esplosione
-4000 3 20 0.2 60 1 3 60 0 2 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Bomba Nucleare Piccola
Alto rendimento, larga area esplosione
-16000 2 40 0.2 100 2 4 100 0 3 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Bomba Nucleare
Altissimo rendimento, enorme area esplosione
-22000 1 60 0.2 150 2 5 150 0 4 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Testata della Morte
Massivo, esplosione mortale
-30000 1 80 0.2 200 3 6 200 0 5 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Piccola Diffusione
5 Piccoli missili in un sigolo colpo
-5000 3 10 0.2 25 0 2 30 0 0 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Media Diffusione
5 Medio missili in un sigolo colpo
-11000 2 15 0.2 40 0 2 40 0 1 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Grande Diffusione
5 Grandi missili in un sigolo colpo
-17000 1 20 0.2 60 1 3 60 0 2 5 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Super Diffusione
4 Piccole Bombe Nucleari in un sigolo colpo
-25000 1 40 0.2 100 2 4 100 0 3 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Mortale Diffusione
3 Bombe Nucleari in un sigolo colpo
-50000 1 60 0.2 150 2 5 150 0 4 3 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Armageddon
3 Testate della Morte in un sigolo colpo
-100000 1 80 0.2 200 3 6 200 0 5 3 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Missile Concatenato
Fuoco con un colpo di tre piccoli missili
-4000 3 10 0.2 25 0 2 30 0 0 1 3 0 0 0 0 -1 0 0 0 0 0 -1 0
Cannone Concatenato
Fuoco con un colpo di cinque piccoli missili
-6000 10 10 0.2 25 0 2 30 0 0 1 5 0 0 0 0 -1 0 0 0 0 0 -1 0
Jack Hammer
Fuoco con un colpo di otto piccoli missili
-8000 16 10 0.2 25 0 2 30 0 0 1 8 0 0 0 0 -1 0 0 0 0 0 -1 0
Shaped Charge
Esplosione orizzontale ad alto rendimento. Tutta l'energia e' focalizzata sui lati, incrementando la distruzione, ma riducendo l'area dell'esplosione.
-10000 5 10 0.2 100 2 3 100 0 17 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Wide Boy
Esplosione orizzontale devastante. Tutta l'energia e' focalizzata sui lati, incrementando la distruzione, ma riducendo l'area dell'esplosione.
-15000 2 10 0.2 200 2 4 200 0 18 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cutter
Esplosione orizzontale che cancellera' tutto quello che trovera' per la strada. Tutta l'energia e' focalizzata sui lati, incrementando la distruzione, ma riducendo l'area dell'esplosione similarmente a quanto ottenuto con uno scoppio incontrollato.
-20000 1 10 0.2 300 3 5 300 0 19 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Roller Piccolo
Esplosione Media con dispositivo rotolante che esplodera' quando tocchera' qualcosa.
-2000 3 20 0.2 40 0 2 40 0 6 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Roller Grande
Esplosione Grande con dispositivo rotolante che esplodera' quando tocchera' qualcosa.
-8000 1 30 0.2 60 1 3 60 0 6 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Roller della Morte
Esplosione Enorme con dispositivo rotolante che esplodera' quando tocchera' qualcosa.
-14000 1 40 0.2 100 2 4 100 0 6 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
MIRV Piccolo
Caduta un gruppo di grandi missili.
-20000 1 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 2 1.0 120 0.0 1.0 0 -1 0
Perforatore di Corazza
Un piccolo, potentissimo perforatore di corazza dei tank
-2500 5 30 0.2 15 0 2 80 0 27 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Bomba Cluster
Spargimenti a Medio rendimento di mazzi di bombe a impatto
-10000 3 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 60 0 3.5 0 -1 0
Super Cluster
Spargimenti ad Alto rendimento di mazzi di bombe a impatto
-20000 2 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 30 0.0 60 0 3.5 0 -1 0
Bomba Funky
Imprevedibile ed altamente distruttivo
-30000 3 50 0.2 200 0 0 200 0 9 1 0 0 3 0 10 27 0.0 360 1.0 0.5 0.1 -1 0
Funky Mortale
Imprevedibile ed estremamente distruttivo
-50000 1 90 0.2 300 2 1 300 0 9 1 0 0 4 0 10 28 0.0 360 1.0 0.5 0.1 -1 0
Proietto Funky
Testata esplosiva a medio rendimento
-7000 3 5 0.01 40 0 0 50 0 10 1 0 1 1 1 0 -1 0 0 0 0 0 125 1.0
Funky Quasi Mortale
Testata esplosiva ad alto rendimento
-9000 2 9 0.01 100 2 1 90 0 10 1 0 1 3 1 0 -1 0 0 0 0 0 175 1.0
Proietto
Testata esplosiva a medio rendimento
-3500 3 20 0.2 40 0 2 40 0 8 1 0 0 1 1 0 -1 0 0 0 0 0 -1 0
Super Proietto
Testata esplosiva ad alto rendimento
-5000 2 40 0.2 60 1 2 65 0 8 1 0 0 3 1 0 -1 0 0 0 0 0 -1 0
Scavatore
Scava nel terreno fino alla superficie prima dell'esplosione, buono contro i carri armati sepolti. Lo Scavatore � progettato per uso sotterraneo e soffre di conseguenza all'esposiizone all'aria.
-7500 3 100 0.4 50 0 2 50 0 25 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Penetratore
Penetra sepolto prima di sorgere alla superficie. Il Penetratore esploder� sia all'uscita dal suolo, sia al contatto con gli oggetti sepolti. Questo missile � disegnato per scavare e soffre di conseguenza all'esposiizone all'aria.
-20000 2 150 0.4 80 0 2 100 0 26 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Piccola Bomba al Napalm
Copre l'area circostante con piccole quantita' di napalm intensamente in fiamme dopo l'impatto
-6000 3 80 0.2 80 1 1 80 0 9 1 0 0 3 0 10 36 0.0 90 0.2 2.5 1.0 -1 0
Media Bomba al Napalm
Copre l'area circostante con napalm intensamente in fiamme dopo l'impatto
-14000 2 200 0.2 150 1 1 200 0 9 1 0 0 4 0 25 36 0.0 90 0.2 3 1.0 -1 0
Grande Bomba al Napalm
Copre l'area circostante con grandi quantita' di napalm intensamente in fiamme dopo l'impatto
-22000 1 400 0.2 200 1 2 500 0 9 1 0 0 5 0 60 36 0.0 90 0.2 5 1.0 -1 0
Napalm Gelatina
Gelatina chimica intensamente combustibile
-2000 5 5 0.5 10 1 10 30 0 0 1 0 0 2 1 0 -1 0 0 0 0 0 -1 0
Perforatrice
Esplosione Verticale che provoca un piccolo danno, ma crea un profondo buco nel terreno.
-5000 2 10 0.2 300 3 5 10 0 28 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Tremor
Produce un piccolo terremoto all'impatto
-3000 5 30 0.2 40 0 2 10 0 14 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Onda d'urto
Produce un grande terremoto all'impatto
-10000 2 60 0.2 100 2 3 20 0 15 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Spostamento tettonico
Produce un enorme terremoto all'impatto
-30000 1 100 0.2 150 2 4 30 0 16 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Bomba Anti-Sommossa
Distrugge un piccolo volume di sporcizia senza danneggiare nulla
-2000 5 15 0.2 25 0 2 0 0 23 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Pesante Bomba Anti-Sommossa
Distrugge un grande volume di sporcizia senza danneggiare nulla
-3000 2 30 0.2 100 1 2 0 0 24 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Multi Bomba Anti-Sommossa
Taglia un cono di sporcizia direttamente davanti al cannone
-1000 5 15 0.2 50 0 2 0 0 11 1 0 1 1 0 0 -1 0 0 0 0 0 0 0
Bomba Anti-Sommossa Esplodente
Taglia un grande cono di sporcizia direttamente davanti al cannone
-2000 2 30 0.2 150 1 2 0 0 12 1 0 1 3 0 0 -1 0 0 0 0 0 0 0
Palla Sporca
Produce una piccola sfera di materiale per seppellire i vostri avversari
-3000 5 15 0.2 25 0 2 0 0 11 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Grande Palla Sporca
Produce una grande sfera di materiale per seppellire i vostri avversari
-7000 2 30 0.2 60 1 2 0 0 12 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Super Palla Sporca
Produce una enorme sfera di materiale per seppellire i vostri avversari
-10000 1 60 0.2 100 2 2 0 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Sporcizia a Piccola Diffusione
Fa cadere dei piccoli mucchi di sporcizia sui vostri nemici
-4000 2 30 0.2 40 1 2 0 0 12 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cluster MIRV
Libera un mazzo di piccoli missili su quello che trova
-10000 2 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 45 0 2.0 0 -1 0
Bomba Percentuale
Distrugge meta' della corazza dell'obiettivo
-12000 2 30 0.2 30 2 4 0 0 29 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Riduttore
Abbassa la potenza esplosiva dei missili nemici.
-6000 3 25 0.3 30 2 4 0 0 30 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Laser Piccolo
Raggio Laser a 50kW.
-5000 5 1 0.0 2 0 0 30 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Laser Medio
Raggio Laser a 100kW.
-10000 3 1 0.0 5 0 0 65 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Laser Grande
Raggio Laser a 200kW.
-15000 2 1 0.0 9 0 0 150 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
*NATURALS*
Meteorite Piccolo
Un piccolo pezzo di roccia dallo spazio
-0 1 15 0.2 25 0 0 5 0 20 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Meteorite Medio
Un pezzo medio di roccia dallo spazio
-0 1 20 0.2 40 0 0 10 0 21 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Meteorite Grande
Un grande pezzo di roccia dallo spazio
-0 1 25 0.2 60 1 1 20 0 22 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Lampo Piccolo
Un piccolo fulmine dal cielo.
-0 1 1 0.0 1 0 0 5 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Lampo Medio
Un fulmine medio dal cielo.
-0 1 1 0.0 4 0 0 15 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Lampo Grande
Un gran fulmine dal cielo.
-0 1 1 0.0 7 0 0 35 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
*ITEMS*
Teletrasporto
Teletrasporta il tank in una posizione random.
-5000 2 1 3 11
Swapper
Scambia il posto con un altro tank
-10000 2 1 4 11
Teletrasporto Massiccio
Teletrasporta tutti i tank sullo schermo.
-15000 2 1 4 11
Fan
Cambia il vento in forza e direzione
-2500 3 1 3 2
Vendetta
Un piccolo dispositivo di auto-distruzione, se avete deciso di andarvene, perche' non prenderne qualcuno con voi?
-20000 1 1 2 0 0 21
Rabbia di Morte
Un gran dispositivo di auto-distruzione. Distruzione reciprocamente assicurata.
-40000 1 1 3 0 21 3
Furia Fatale
Fa finire tutto in grande stile
-60000 1 1 4 0 22 3
Scudo Leggero
Da una piccola protezione dalla distruzione
-10000 3 0 1 0 50 0 0 255 0 1
Scudo Medio
Protegge dalla distruzione
-20000 2 0 2 0 100 0 64 255 0 3
Scudo Pesante
Da una grande protezione dalla distruzione
-30000 2 0 4 0 150 0 128 255 64 6
Schermo di Repulsione Leggero
Respinge leggermente i missili nemici
-10000 3 0 2 0 10 250 128 0 255 1
Schermo di Repulsione Medio
Respinge i missili nemici
-20000 2 0 3 0 20 500 192 64 255 3
Schermo di Repulsione Pesante
Respinge pesantemente i missili nemici
-40000 1 0 5 0 40 1000 255 128 255 6
Rinforzo Corazzatura
Aggiunge permanentemente un piccolo aumento di resistenza alla corazza del tank diminuendo cosi' il danno inferto ai vostri tank.
-20000 1 0 2 0 300
Rinforzo Corazzatura in Acciaio
Aggiunge permanentemente un aumento di resistenza alla corazza del tank diminuendo cosi' il danno inferto ai vostri tank.
-40000 1 0 4 0 2155
Amplificatore di Intensita'
Permette un piccolo e permanente incremento ai danni fatti dalle vostre armi. L'efficienza diminuisce e quindi ogni acquisto supplementare ha un'influenza riduttrice.
-21000 1 0 3 0 0.10
Forza Violenta
Permette un permanente incremento ai danni fatti dalle vostre armi. L'efficienza diminuisce e quindi ogni acquisto supplementare ha un'influenza riduttrice.
-50000 1 0 3 0 0.30
Proiettili Lucidi
Un rivestimento di Teflon riduce la resistenza e l'influenza del vento sui vostri proiettili
-1000 50 0 3 0 0.5
Proiettili Scanalati
Piccole fossette nella superficie dei proiettili per una riduzione voluminosa della resistenza all'aria
-2000 50 0 4 0 0.1
Paracadute
Permette al tank di fluttuare lentamente a terra
-5000 10 0 0 0 0 0
Kit Auto-Riparazione
Ripara il tank un poco ogni volta. Ogni volta fornisce un piccolo aumento alla vostra corazza.
-10000 1 0 2 0 0 0
Benzina
Permette al tank di muoversi sul terreno.
-1000 10 0 2 0 0 0
Razzo
Solleva il tank nell'aria.
-2000 2 1 4 0 0 0
Difesa anti missile - SDI
Offre una certa protezione contro i missili ricevuti.
-10000 1 0 5 0 0
diff --git a/text/weapons_ru.txt b/text/weapons_ru.txt
index f265e23..088e582 100644
--- a/text/weapons_ru.txt
+++ b/text/weapons_ru.txt
@@ -1,343 +1,258 @@
*WEAPONS*
Ракета
Слабый взрыв, малая мощность. Достаточно для того, чтобы добить танк противника.
-1500 10 10 0.2 25 0 2 30 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Средняя ракета
Небольшая мощность, небольшая площадь поражения. Разумное качество по разумной цене.
-2000 5 15 0.2 40 0 2 40 0 1 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Большая ракета
Средняя мощность, средняя площадь поражения. Способна серьёзно задеть противника.
-4000 3 20 0.2 60 1 3 60 0 2 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Малая ядерная бомба
Мощный взрыв, большая площадь поражения. Неприятный сюрприз для большинства танков.
-16000 2 40 0.2 100 2 4 100 0 3 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Ядерная бомба
Очень мощный взрыв, огромная площадь поражения. Кто там не надел очки?
-22000 1 60 0.2 150 2 5 150 0 4 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Ядрен-батон (Death Head)
Офигенно мощный бабах. Достанет кого угодно.
-30000 1 80 0.2 200 3 6 200 0 5 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Малый веер
5 малых ракет за один выстрел. Удобно, если нужно накрыть нескольких раненых противников за раз.
-5000 3 10 0.2 25 0 2 30 0 0 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Средний веер
5 средних ракет за один выстрел. Уже повод для беспокойства.
-11000 2 15 0.2 40 0 2 40 0 1 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Большой веер
5 больших ракет за один выстрел. Опасное оружие в руках умелого танкиста.
-17000 1 20 0.2 60 1 3 60 0 2 5 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Супер-веер
4 малых ядерных бомбы за один выстрел. Смех злого гения прилагается.
-25000 1 40 0.2 100 2 4 100 0 3 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Смертельный веер
3 ядерных бомбы за один выстрел. Не забудьте надеть очки.
-50000 1 60 0.2 150 2 5 150 0 4 3 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Армагеддон
3 ядрен-батона за один выстрел. Гарантированное избавление от тараканов и клопов на поле боя!
-100000 1 80 0.2 200 3 6 200 0 5 3 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Очередь
Выстреливает 3 ракеты одну за другой. Для тех, кто хочет пощекотать противнику нервы.
-4000 3 10 0.2 25 0 2 30 0 0 1 3 0 0 0 0 -1 0 0 0 0 0 -1 0
Пулемет
Выстреливает 5 ракет одну за другой. Способен продолбить яму в грунте.
-6000 10 10 0.2 25 0 2 30 0 0 1 5 0 0 0 0 -1 0 0 0 0 0 -1 0
Молотилка
Выстреливает 8 ракет одну за другой. Начинающий набор танкиста-дятла.
-8000 16 10 0.2 25 0 2 30 0 0 1 8 0 0 0 0 -1 0 0 0 0 0 -1 0
Резачок
Мощный горизонтальный взрыв. Вся энергия фокусируется по сторонам, что увеличивает мощность, но уменьшает площадь поражения.
-10000 5 10 0.2 100 2 3 100 0 17 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Резак
Опустошительный горизонтальный взрыв. Вся энергия фокусируется по сторонам, что позволяет пробить любой щит, но уменьшает шансы зацепить врага.
-15000 2 10 0.2 200 2 4 200 0 18 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Потрошитель
Горизонтальный взрыв, сметающий всё на своем пути. Вся энергия фокусируется по сторонам, прорезая любую броню, бетон и грунт.
-20000 1 10 0.2 300 3 5 300 0 19 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Каток
Заряд средней мощности, который катится под горку, пока не столкнется с препятствием. Полезен против противника, который расположился на склоне.
-2000 3 20 0.2 40 0 2 40 0 6 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Большой каток
Заряд большой мощности, который катится под горку, пока не столкнется с препятствием. Многие танкисты предпочитают встречу с лавиной.
-8000 1 30 0.2 60 1 3 60 0 6 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Танкоукладчик
Заряд огромной мощности, способный укатать любой танк в прямом смысле этого слова.
-14000 1 40 0.2 100 2 4 100 0 6 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
РГЧ-ИН "Рогач"
Ракета с Разделяющейся Головной Частью. Обрушивает с высоты на наземную цель группу больших ракет. Превращает точку попадания в подобие лунной поверхности.
-20000 1 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 2 1.0 120 0.0 1.0 0 -1 0
Бронебойный снаряд
Небольшой, но очень мощный снаряд - сила взрыва концентрируется в одной точке, что позволяет пробить любую защиту.
-2500 5 30 0.2 15 0 2 80 0 27 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Кассетная бомба
При срабатывании выбрасывает 5 средних ракет. Эффективна против врагов, собравшихся в группы.
-10000 3 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 60 0 3.5 0 -1 0
Супер-кассетная бомба
При срабатывании выбрасывает 5 больших ракет. Разрушительный фейерверк в тылу врага.
-20000 2 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 30 0.0 60 0 3.5 0 -1 0
Шариковая бомба
Экспериментальная бомба, сделанная на заводе по производству шариков для пинг-понга. Непредсказуема и смертельна.
-30000 3 50 0.2 200 0 0 200 0 9 1 0 0 3 0 10 27 0.0 360 1.0 0.5 0.1 -1 0
Шариковая смерть
Улучшенная версия шариковой бомбы, сделанная на заводе по производству баскетбольных мячей. Непредсказуема и офигенно разрушительна.
-50000 1 90 0.2 300 2 1 300 0 9 1 0 0 4 0 10 28 0.0 360 1.0 0.5 0.1 -1 0
Воздушный шарик
Летающая боеголовка средней мощности.
-7000 3 5 0.01 40 0 0 50 0 10 1 0 1 1 1 0 -1 0 0 0 0 0 125 1.0
Бомбаэростат
Летающая боеголовка большой мощности.
-9000 2 9 0.01 100 2 1 90 0 10 1 0 1 3 1 0 -1 0 0 0 0 0 175 1.0
Бомбочка
Боеголовка средней мощности. Для использования в кассетных бомбах, авиаударах и фейерверках.
-3500 3 20 0.2 40 0 2 40 0 8 1 0 0 1 1 0 -1 0 0 0 0 0 -1 0
Супер-бомбочка
Боеголовка большой мощности. Руками не трогать.
-5000 2 40 0.2 60 1 2 65 0 8 1 0 0 3 1 0 -1 0 0 0 0 0 -1 0
Крот
Вонзается в поверхность и проходит некоторый путь под землёй по параболе, прежде чем взорваться, что удобно против закопанных танков. Крот предназначен для движения под землей и не слишком-то хорошо летает в воздухе.
-7500 3 100 0.4 50 0 2 50 0 25 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Большой крот
Движется под землей до выхода на поверхность или до попадания в зарытый танк. Большой крот расчитан на движение под землей, поэтому страдает от сильного сопротивления воздуха.
-20000 2 150 0.4 80 0 2 100 0 26 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Малый напалм
Снаряд с горючими веществами. При срабатывании разбрызгивает немного горящего напалма. Напалм почти не разрушает грунт, но подвержен влиянию ветра.
-6000 3 80 0.2 80 1 1 80 0 9 1 0 0 3 0 10 36 0.0 90 0.2 2.5 1.0 -1 0
Средний напалм
Снаряд с горючими веществами. Разбрызгивает горящий напалм при срабатывании. Более мощная и более эффективная модификация.
-14000 2 200 0.2 150 1 1 200 0 9 1 0 0 4 0 25 36 0.0 90 0.2 3 1.0 -1 0
Большой напалм
Снаряд с горючими веществами. Заливает напалмом всё в округе. Проверьте направление ветра перед выстрелом.
-22000 1 400 0.2 200 1 2 500 0 9 1 0 0 5 0 60 36 0.0 90 0.2 5 1.0 -1 0
Липкий напалм
Горючее искуственное желе.
-2000 5 5 0.5 10 1 10 30 0 0 1 0 0 2 1 0 -1 0 0 0 0 0 -1 0
Ямокопатель
При попадании снаряд создает глубокий вертикальный тоннель, почти не нанося повреждений. Первоначально использовался компаниями при поиске нефти и газа на планетах.
-5000 2 10 0.2 300 3 5 10 0 28 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Дрожь земли
При срабатывании вызывает слабое землетрясение. Малоэффективный, но назойливый снаряд.
-3000 5 30 0.2 40 0 2 10 0 14 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Ударная волна
Вызывает большое землетрясение. Опасен для неподготовленных танков.
-10000 2 60 0.2 100 2 3 20 0 15 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Тектонический сдвиг
Вызывает кошмарное землетрясение. Не кантовать.
-30000 1 100 0.2 150 2 4 30 0 16 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Уборщик (Riot Bomb)
Убирает немного грунта, не повреждая броню и технику. Используется для раскопок на поле боевых действий.
-2000 5 15 0.2 25 0 2 0 0 23 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Heavy Riot Bomb
Убирает уйму грунта, не повреждая броню и технику. Используется для обнаружения подземных вражеских лагерей.
-3000 2 30 0.2 100 1 2 0 0 24 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Малый дворник
Очищает от грязи небольшой просвет перед дулом пушки. Предмет первой необходимости на поле боя.
-1000 5 15 0.2 50 0 2 0 0 11 1 0 1 1 0 0 -1 0 0 0 0 0 0 0
Большой дворник
Очищает от грязи большую площадь перед дулом пушки. Улучшенная и в разы более эффективная модификация дворника.
-2000 2 30 0.2 150 1 2 0 0 12 1 0 1 3 0 0 -1 0 0 0 0 0 0 0
Комок грязи
Небольшой шар из грунта, способный закопать оппонента. Грязь легко уничтожается и расчищается.
-3000 5 15 0.2 25 0 2 0 0 11 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Большой комок грязи
Большой грунтовый шар для закапывания оппонентов. Полезен против тех, кто умеет откапывать танки только ракетами.
-7000 2 30 0.2 60 1 2 0 0 12 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Говномет
Уйма грязи на голову врага. Для полноты ощущений ещё и воняет.
-10000 1 60 0.2 100 2 2 0 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Small Dirt Spread
Drop little piles of dirt on your enemies
-4000 2 30 0.2 40 1 2 0 0 12 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Cluster MIRV
Releases a cluster of small missiles on its way down
-10000 2 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 45 0 2.0 0 -1 0
Per Cent Bomb
Destroys half of the target's armour
-12000 2 30 0.2 30 2 4 0 0 29 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Reducer
Lowers the explosive power of an enemy's missiles.
-6000 3 25 0.3 30 2 4 0 0 30 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Малый лазер
Луч мощностью 50КВт. Способен прожечь лист железа или грунт.
-5000 5 1 0.0 2 0 0 30 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Средний лазер
Луч мощностью 100КВт. Отличный способ прожечь броню врага.
-10000 3 1 0.0 5 0 0 65 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Большой лазер
200-киловаттный прожигатель. Злые гении, это ваш выбор!
-15000 2 1 0.0 9 0 0 150 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
*NATURALS*
Метеорит
Камешек с небес. Лучше его не ловить.
-0 1 15 0.2 25 0 0 5 0 20 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Метеор
Осколок небесной тверди. Несовместим с целым танком.
-0 1 20 0.2 40 0 0 10 0 21 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Большой метеор
Обломок небесной тверди. Гнев небес вобьет в грунт любого грешника.
-0 1 25 0.2 60 1 1 20 0 22 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Малая молния
Слабенький разряд. Мешает радиосвязи, добивает больных и ослабевших.
-0 1 1 0.0 1 0 0 5 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Молния
Обычный разряд молнии. Выжигает бортовую электронику. Не лови - убьет!
-0 1 1 0.0 4 0 0 15 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Большая молния
Убийственный разряд. Кто сказал, что бывают тугоплавкие металлы?
-0 1 1 0.0 7 0 0 35 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
*ITEMS*
Телепорт
Перебрасывает танк в случайную точку под землёй или в воздухе.
-5000 2 1 3 11
Обменник
Меняет танк местами с другим танком.
-10000 2 1 4 11
Миксер
Телепортирует в случайные точки все танки на поле боя.
-15000 2 1 4 11
Ветродуй
Меняет направление и силу ветра.
-2500 3 1 3 2
Завещание
Небольшой самоликвидатор или автоликвидатор: если уж тебя попросили на выход, почему бы не прихватить кого-нибудь с собой?
-20000 1 1 2 0 0 21
Месть покойника
Большой набор самоликвидации. Ты мне - я тебе.
-40000 1 1 3 0 21 3
Убийственная ярость
Закончим всё это красиво.
-60000 1 1 4 0 22 3
Малый щит
Противопульная дополнительная броня. Для тех, кому нужна дополнительная защита.
-10000 3 0 1 0 50 0 0 255 0 1
Средний щит
Хорошая дополнительная броня. Нужна защита от мощных ракет? Возьми этот модуль!
-20000 2 0 2 0 100 0 64 255 0 3
Тяжелый щит
Особо прочная дополнительная броня. Способна выдержать даже авиаудар.
-30000 2 0 4 0 150 0 128 255 64 6
Слабое силовое поле
Отталкивает вражеские снаряды. Не защищает от взрывной волны.
-10000 3 0 2 0 10 250 128 0 255 1
Силовое поле
Хорошо отталкивает вражеские снаряды, плохо защищает от взрывов.
-20000 2 0 3 0 20 500 192 64 255 3
Мощное силовое поле
Отлично отталкивает вражеские снаряды, направленные точно в танк.
-40000 1 0 5 0 40 1000 255 128 255 6
Навесная броня
Усиливает броню танка до конца игры. Каждая новая пластина дает чуть меньший эффект, нежели предыдущая.
-20000 1 0 2 0 300
Эгида
Особо прочная навесная броня. Усиливает броню танка до конца игры. Каждая новая пластина дает чуть меньший эффект, нежели предыдущая.
-40000 1 0 4 0 2155
Усилитель боевой мощи
До конца игры немного увеличивает убойную силу ваших снарядов. Каждый новый усилитель оказывает немного меньший эффект по сравнению с предыдущим.
-21000 1 0 3 0 0.10
Убийственный усилитель
До конца игры существенно увеличивает убойную силу ваших снарядов. Каждый новый усилитель оказывает немного меньший эффект по сравнению с предыдущим.
-50000 1 0 3 0 0.30
Тефлоновое покрытие
Тефлоновая оболочка зарядов уменьшает влияние ветра на траекторию выстрела.
-1000 50 0 3 0 0.5
Сверлёные снаряды
Небольшие отверстия в боеголовках существенно повышают их устойчивость к воздействию ветра.
-2000 50 0 4 0 0.1
Парашют
Оказавшись в воздухе или потеряв почву под ногами, танк не падает, а плавно опускается на землю. В полете можно управлять направлением, расходуя ТОПЛИВО, если оно есть.
-5000 10 0 0 0 0 0
Авторемонтник
С каждым ходом устраняет часть полученных повреждений. Каждый новый ремонтник работает чуть менее эффективно, чем предыдущий.
-10000 1 0 2 0 0 0
Топливо
Позволяет танку двигаться по ровной местности и планировать в сторону при парашютировании.
-1000 10 0 2 0 0 0
Двигатель "ВВП"
Поднимает танк в воздух, что позволяет перелетать с места на место.
-2000 2 1 4 0 0 0
SDI Missile Defense
Offers some protection against incoming missiles.
-10000 1 0 5 0 0
diff --git a/text/weapons_sk.txt b/text/weapons_sk.txt
index e854bd0..e175385 100644
--- a/text/weapons_sk.txt
+++ b/text/weapons_sk.txt
@@ -1,343 +1,258 @@
*WEAPONS*
Malá strela
Pri kontakte spôsobí malú explóziu
-1500 10 10 0.2 25 0 2 30 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Stredná strela
Malá ráž, malá veľkosť explózie
-2000 5 15 0.2 40 0 2 40 0 1 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Veľká strela
Stredná ráž, stredná veľkosť explózie
-4000 3 20 0.2 60 1 3 60 0 2 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Malá jadrová bomba
Veľká ráž, veľká explózia
-16000 2 40 0.2 100 2 4 100 0 3 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Jadrová bomba
Veľmi veľká ráž, obrovská explózia
-22000 1 60 0.2 150 2 5 150 0 4 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Vraždiaca bomba
Obrovská smrtiaca explózia
-30000 1 80 0.2 200 3 6 200 0 5 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Ľahké kobercové bombardovanie
5 malý striel v jednom výstrele
-5000 3 10 0.2 25 0 2 30 0 0 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Stredné kobercové bombardovanie
5 stredných striel v jednom výstrele
-11000 2 15 0.2 40 0 2 40 0 1 5 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Ťažké kobercové bombardovanie
5 veľkých striel v jednom výstrele
-17000 1 20 0.2 60 1 3 60 0 2 5 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Superkobercové bombardovanie
4 malé jadrové bomby v jednom výstrele
-25000 1 40 0.2 100 2 4 100 0 3 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Smrtiace kobercové bombardovanie
3 jadrové bomby v jednom výstrele
-50000 1 60 0.2 150 2 5 150 0 4 3 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Armagedon
3 smrtiace hlavice v jednom výstrele
-100000 1 80 0.2 200 3 6 200 0 5 3 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Reťazové strely
Vypáli skupinku troch malých striel
-4000 3 10 0.2 25 0 2 30 0 0 1 3 0 0 0 0 -1 0 0 0 0 0 -1 0
Samopal
Vypáli skupinku piatich malých striel
-6000 10 10 0.2 25 0 2 30 0 0 1 5 0 0 0 0 -1 0 0 0 0 0 -1 0
Pneumatické kladivo
Vypáli skupinku ôsmych malých striel
-8000 16 10 0.2 25 0 2 30 0 0 1 8 0 0 0 0 -1 0 0 0 0 0 -1 0
Kumulatívna nálož
Veľká horizontálna explózia. Všetka energia je rozprestrená do strán, čím sa zvyšuje škoda, no zmenšuje oblasť, na ktorú explózia pôsobí.
-10000 5 10 0.2 100 2 3 100 0 17 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Wide Boy
Zničujúca horizontálna explózia. Všetka energia je rozprestrená do strán, čím sa zvyšuje škoda, no zmenšuje oblasť, na ktorú explózia pôsobí.
-15000 2 10 0.2 200 2 4 200 0 18 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Orezávač
Horizontálna explózia, ktorá vyhladí všetko, čo jej príde do cesty. Všetka energia je rozprestrená do strán, čím sa zvyšuje škoda, no zmenšuje oblasť, na ktorú explózia pôsobí v porovnaní s neriadenou detonáciou podobnej sily.
-20000 1 10 0.2 300 3 5 300 0 19 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Malý valcovač
Stredná trhavina, ktorá sa valí z kopca až kým do niečo nenarazí
-2000 3 20 0.2 40 0 2 40 0 6 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Veľký valcovač
Silná trhavina, ktorá sa valí z kopca až kým do niečo nenarazí
-8000 1 30 0.2 60 1 3 60 0 6 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Smrtiaci valcovač
Veľmi silná trhavina, ktorá sa valí z kopca až kým do niečo nenarazí
-14000 1 40 0.2 100 2 4 100 0 6 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Malá MIRV
Spustí z neba skupinu veľkých striel
-20000 1 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 2 1.0 120 0.0 1.0 0 -1 0
Trhač brnenia
Malý silný náboj, ktorý dokáže zničiť tank
-2500 5 30 0.2 15 0 2 80 0 27 1 0 0 2 0 0 -1 0 0 0 0 0 -1 0
Trieštivá bomba
Rozhodí bombičky o strednej ráži, ktoré vybuchujú pri kontakte
-10000 3 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 60 0 3.5 0 -1 0
Supertrieštivá bomba
Rozhodí bombičky o veľkej ráži, ktoré vybuchujú pri kontakte
-20000 2 200 0.2 250 1 2 300 0 7 1 0 0 4 0 5 30 0.0 60 0 3.5 0 -1 0
Podivná bomba
Nepredvídateľná a vysoko deštruktívna
-30000 3 50 0.2 200 0 0 200 0 9 1 0 0 3 0 10 27 0.0 360 1.0 0.5 0.1 -1 0
Podivná smrť
Nepredvídateľná a extrémne deštruktívna
-50000 1 90 0.2 300 2 1 300 0 9 1 0 0 4 0 10 28 0.0 360 1.0 0.5 0.1 -1 0
Podivná bombička
Výbušná hlavica o strednej ráži
-7000 3 5 0.01 40 0 0 50 0 10 1 0 1 1 1 0 -1 0 0 0 0 0 125 1.0
Podivná smrtiaca bombička
Výbušná hlavica o veľkej ráži
-9000 2 9 0.01 100 2 1 90 0 10 1 0 1 3 1 0 -1 0 0 0 0 0 175 1.0
Bombička
Výbušná hlavica o strednej ráži
-3500 3 20 0.2 40 0 2 40 0 8 1 0 0 1 1 0 -1 0 0 0 0 0 -1 0
Superbombička
Výbušná hlavica o veľkej ráži
-5000 2 40 0.2 60 1 2 65 0 8 1 0 0 3 1 0 -1 0 0 0 0 0 -1 0
Zavŕtavač
Najprv sa zavŕta do povrchu, dobrá zbraň na zahrabané tanky. Zavŕtavač je určený pre použitie pod povrchom a nemá rád veľký odpor vzduchu.
-7500 3 100 0.4 50 0 2 50 0 25 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Prenikač
Podpovrchová zbraň, ktoré po čase začne stúpať na povrch. Prenikač exploduje buď pri kontakte s povrchom alebo pri kontakte so zahrabanými objektami. Táto strela je určená pre hrabanie a preto nemá rada veľký odpor vzduchu.
-20000 2 150 0.4 80 0 2 100 0 26 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Malá napalmová bomba
Rozhodí malé množstvo napalmu, ktorý pri kontakte intenzívne horí
-6000 3 80 0.2 80 1 1 80 0 9 1 0 0 3 0 10 36 0.0 90 0.2 2.5 1.0 -1 0
Stredná napalmová bomba
Rozhodí napalm, ktorý pri kontakte intenzívne horí
-14000 2 200 0.2 150 1 1 200 0 9 1 0 0 4 0 25 36 0.0 90 0.2 3 1.0 -1 0
Veľká napalmová bomba
Pokryje okolitú oblasť napalmom, ktorý pri kontakte intenzívne horí
-22000 1 400 0.2 200 1 2 500 0 9 1 0 0 5 0 60 36 0.0 90 0.2 5 1.0 -1 0
Napalmové želé
Intenzívne horiace chemické želé
-2000 5 5 0.5 10 1 10 30 0 0 1 0 0 2 1 0 -1 0 0 0 0 0 -1 0
Vŕtač
Vertikálna explózia, ktorý urobí malú škodu, ale vytvorí hlbokú dieru v zemi.
-5000 2 10 0.2 300 3 5 10 0 28 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Otras
Pri kontakte spôsobí malé zemetrasenie
-3000 5 30 0.2 40 0 2 10 0 14 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Tlaková vlna
Pri kontakte spôsobí veľké zemetrasenie
-10000 2 60 0.2 100 2 3 20 0 15 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Tektonický posun
Pri kontakte spôsobí obrovské zemetrasenie
-30000 1 100 0.2 150 2 4 30 0 16 1 0 0 5 0 0 -1 0 0 0 0 0 -1 0
Hlučná bomba
Zničí malé množstvo zeme bez toho, aby poškodila čokoľvek iné
-2000 5 15 0.2 25 0 2 0 0 23 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Veľká hlučná bomba
Zničí veľké množstvo zeminy bez toho, aby poškodila čokoľvek iné
-3000 2 30 0.2 100 1 2 0 0 24 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Hlučná nálož
Priamo pred zbraňou vyreže do zeminy kužeľ
-1000 5 15 0.2 50 0 2 0 0 11 1 0 1 1 0 0 -1 0 0 0 0 0 0 0
Hlučná detonácia
Priamo pred zbraňou vyreže do zeminy veľký kužeľ
-2000 2 30 0.2 150 1 2 0 0 12 1 0 1 3 0 0 -1 0 0 0 0 0 0 0
Guľa so zeminou
Vytvorí malú guľku materiálu pre pochovanie vašich súperov
-3000 5 15 0.2 25 0 2 0 0 11 1 0 0 1 0 0 -1 0 0 0 0 0 -1 0
Veľká guľa so zeminou
Vytvorí veľkú guľu materiálu pre pochovanie vašich súperov
-7000 2 30 0.2 60 1 2 0 0 12 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Superguľa so zeminou
Vytvorí obrovskú guľu materiálu pre pochovanie vašich súperov
-10000 1 60 0.2 100 2 2 0 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Slabý nános zeminy
Zhodí malé kôpky zeminy na vašich nepriateľov
-4000 2 30 0.2 40 1 2 0 0 12 4 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Trieštivé MIRV
Vypustí skupinku malých striel smerom dolu
-10000 2 100 0.2 150 0 2 200 0 7 1 0 0 2 0 5 29 0.0 45 0 2.0 0 -1 0
Percentuálna bomba
Zničí polovicu brnenia cieľa
-12000 2 30 0.2 30 2 4 0 0 29 1 0 0 3 0 0 -1 0 0 0 0 0 -1 0
Redukovač
Znižuje explozívnu silu nepriateľských striel.
-6000 3 25 0.3 30 2 4 0 0 30 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Malý laser
50kW laserový lúč
-5000 5 1 0.0 2 0 0 30 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Stredný laser
100kW laserový lúč
-10000 3 1 0.0 5 0 0 65 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
Veľký laser
Silný 200kW laserový lúč
-15000 2 1 0.0 9 0 0 150 0 13 1 0 0 4 0 0 -1 0 0 0 0 0 -1 0
*NATURALS*
Malý meteorit
Malé kusy skál z oblohy
-0 1 15 0.2 25 0 0 5 0 20 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Stredný meteorit
Stredne veľké kusy skál z oblohy
-0 1 20 0.2 40 0 0 10 0 21 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Veľký meteorit
Veľké kusy skál z oblohy
-0 1 25 0.2 60 1 1 20 0 22 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Slabý blesk
Slabý blesk
-0 1 1 0.0 1 0 0 5 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Stredný blesk
Stredne silný blesk
-0 1 1 0.0 4 0 0 15 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
Silný blesk
Silný blesk
-0 1 1 0.0 7 0 0 35 0 0 1 0 0 0 0 0 -1 0 0 0 0 0 -1 0
*ITEMS*
Teleport
Teleportuje tank na náhodné miesto
-5000 2 1 3 11
Výmena
Výmena miesta s iným tankom
-10000 2 1 4 11
Hromadný teleport
Teleportuje všetky tanky na obrazovke
-15000 2 1 4 11
Ventilátor
Zmení silu a smer vetra
-2500 3 1 3 2
Pomsta
Malé samo a autodeštrukčné zariadenie. Keď už musíte skončiť, tak prečo nevziať niekoho so sebou?
-20000 1 1 2 0 0 21
Smrteľný hnev
Veľké samo/autodeštrukčné zariadenie. Vzájomne zaistená deštrukcia.
-40000 1 1 3 0 21 3
Osudové zúrenie
Štýlové zakončenie
-60000 1 1 4 0 22 3
Ľahký štít
Malé množstvo ochrany pred poškodením
-10000 3 0 1 0 50 0 0 255 0 1
Stredný štít
Chráni pred poškodením
-20000 2 0 2 0 100 0 64 255 0 3
Ťažký štít
Veľké množstvo ochrany pred poškodením
-30000 2 0 4 0 150 0 128 255 64 6
Ľahký odkloňovač striel
Jemne odkloní nepriateľské strely
-10000 3 0 2 0 10 250 128 0 255 1
Stredný odkloňovač striel
Odkloní nepriateľské strely
-20000 2 0 3 0 20 500 192 64 255 3
Ťažký odkloňovač striel
Výrazne odkloní nepriateľské strely
-40000 1 0 5 0 40 1000 255 128 255 6
Bežné opancierovanie
Permanentne zvýši množstvo škody, ktorému váš tank dokáže odolať. Každý dodatočný nákup pridá o niečo menšie množstvo pancieru vášmu tanku.
-20000 1 0 2 0 300
Plasteelové opancierovanie
Permanentne zvýši množstvo škody, ktorému váš tank dokáže odolať. Každý dodatočný nákup pridá o niečo menšie množstvo pancieru vášmu tanku.
-40000 1 0 4 0 2155
Zosilnenie intenzity
Malé permanentné zvýšenie účinnosti vašich zbraní. Účinnosť má klesajúcu tendenciu a tak každý dodatočný nákup má stále slabší efekt.
-21000 1 0 3 0 0.10
Mohutná sila
Permanentné zvýšenie účinnosti vašich zbraní. Účinnosť má klesajúcu tendenciu a tak každý dodatočný nákup má stále slabší efekt.
-50000 1 0 3 0 0.30
Klzké projektily
Teflónová vrstva pre projektily, ktoré znížia odpor a účinok vetra
-1000 50 0 3 0 0.5
Dierkované projektily
Malé dierky v plášti projektilov pre veľké zníženie odporu
-2000 50 0 4 0 0.1
Padák
Umožní tanku pomaly klesať k zemi
-5000 10 0 0 0 0 0
Súprava pre opravu
Po každom ťahu opravuje tank. Každá ďalšia súprava poskytuje o niečo menšie brnenie.
-10000 1 0 2 0 0 0
Palivo
Umožňuje tanku pohybovať sa po vodorovnom teréne.
-1000 10 0 2 0 0 0
Raketa
Vypustí tank do vzduchu.
-2000 2 1 4 0 0 0
SDI raketová obrana
Poskytuje určitú ochranu proti prichádzajúcim strelám.
-10000 1 0 5 0 0
diff --git a/title/.directory b/title/.directory
deleted file mode 100644
index 43701d7..0000000
--- a/title/.directory
+++ /dev/null
@@ -1,3 +0,0 @@
-[Dolphin]
-ShowPreview=true
-Timestamp=2010,1,15,8,34,14
diff --git a/unicode.dat b/unicode.dat
index 331fc61..d0b6ea0 100644
Binary files a/unicode.dat and b/unicode.dat differ
diff --git a/vs12/README_allegro.txt b/vs12/README_allegro.txt
new file mode 100755
index 0000000..5180206
--- /dev/null
+++ b/vs12/README_allegro.txt
@@ -0,0 +1,14 @@
+To build atanks using Visual Studio 2013 you will need
+to adapt the include path settings to where your allegro
+includes are.
+Further replace alleg44.dll and alleg44.lib with the
+versions you want to use.
+
+Release 32bit uses alleg44.lib and alleg44.dll
+Release 64bit uses alleg44_64.lib and alleg44.dll
+Debug 32bit uses alleg44-debug.lib and alleg44-debug.dll
+Debug 64bit uses alleg44_64-debug.lib and alleg44_64-debug.dll
+
+As the windows build is originally not meant for debugging,
+only the 32bit release versions are included in git.
+You will need your own includes and libs!
diff --git a/vs12/atanks.sln b/vs12/atanks.sln
new file mode 100755
index 0000000..dd03f5b
--- /dev/null
+++ b/vs12/atanks.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.31206.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "atanks", "atanks.vcxproj", "{146B9823-15FD-45DC-8EA4-56670798D8D8}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Debug|x64 = Debug|x64
+ Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {146B9823-15FD-45DC-8EA4-56670798D8D8}.Debug|Win32.ActiveCfg = Debug|Win32
+ {146B9823-15FD-45DC-8EA4-56670798D8D8}.Debug|Win32.Build.0 = Debug|Win32
+ {146B9823-15FD-45DC-8EA4-56670798D8D8}.Debug|x64.ActiveCfg = Debug|x64
+ {146B9823-15FD-45DC-8EA4-56670798D8D8}.Debug|x64.Build.0 = Debug|x64
+ {146B9823-15FD-45DC-8EA4-56670798D8D8}.Release|Win32.ActiveCfg = Release|Win32
+ {146B9823-15FD-45DC-8EA4-56670798D8D8}.Release|Win32.Build.0 = Release|Win32
+ {146B9823-15FD-45DC-8EA4-56670798D8D8}.Release|x64.ActiveCfg = Release|x64
+ {146B9823-15FD-45DC-8EA4-56670798D8D8}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/vs12/atanks.vcxproj b/vs12/atanks.vcxproj
new file mode 100755
index 0000000..0910dec
--- /dev/null
+++ b/vs12/atanks.vcxproj
@@ -0,0 +1,285 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{146B9823-15FD-45DC-8EA4-56670798D8D8}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>atanks</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v120</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v120</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v120</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v120</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>$(SolutionDir)../</OutDir>
+ <IntDir>$(Configuration)_x86\</IntDir>
+ <TargetName>$(ProjectName)_d</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <TargetName>$(ProjectName)_64_d</TargetName>
+ <OutDir>$(SolutionDir)../</OutDir>
+ <IntDir>$(Configuration)_x64\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(SolutionDir)../</OutDir>
+ <IntDir>$(Configuration)_x86\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <TargetName>$(ProjectName)_64</TargetName>
+ <OutDir>$(SolutionDir)../</OutDir>
+ <IntDir>$(Configuration)_x64\</IntDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level2</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;VERSION="6.2-aiu1";ATANKS_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <SDLCheck>false</SDLCheck>
+ <AdditionalIncludeDirectories>$(SolutionDir)..\..\allegro\include;$(SolutionDir)..\..\allegro\build_x86_d\include;$(SolutionDir)..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <AdditionalUsingDirectories>
+ </AdditionalUsingDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>$(SolutionDir)..</AdditionalLibraryDirectories>
+ <AdditionalDependencies>alleg44.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level2</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;VERSION="6.2-aiu1";ATANKS_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <SDLCheck>false</SDLCheck>
+ <AdditionalIncludeDirectories>$(SolutionDir)..\..\allegro\include;$(SolutionDir)..\..\allegro\build_x86_d\include;$(SolutionDir)..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <AdditionalUsingDirectories>
+ </AdditionalUsingDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>$(SolutionDir)..</AdditionalLibraryDirectories>
+ <AdditionalDependencies>alleg44_64-debug.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level1</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;VERSION="6.2-aiu1";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <SDLCheck>false</SDLCheck>
+ <AdditionalIncludeDirectories>$(SolutionDir)..\..\allegro\include;$(SolutionDir)..\..\allegro\build_x86\include;$(SolutionDir)..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <OmitFramePointers>true</OmitFramePointers>
+ <AdditionalUsingDirectories>
+ </AdditionalUsingDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>$(SolutionDir)..</AdditionalLibraryDirectories>
+ <AdditionalDependencies>alleg44.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level1</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;VERSION="6.2-aiu1";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <SDLCheck>false</SDLCheck>
+ <AdditionalIncludeDirectories>$(SolutionDir)..\..\allegro\include;$(SolutionDir)..\..\allegro\build_x86\include;$(SolutionDir)..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <OmitFramePointers>true</OmitFramePointers>
+ <AdditionalUsingDirectories>
+ </AdditionalUsingDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>$(SolutionDir)..</AdditionalLibraryDirectories>
+ <AdditionalDependencies>alleg44_64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="..\src\aicore.h" />
+ <ClInclude Include="..\src\beam.h" />
+ <ClInclude Include="..\src\button.h" />
+ <ClInclude Include="..\src\client.h" />
+ <ClInclude Include="..\src\clock.h" />
+ <ClInclude Include="..\src\debris_pool.h" />
+ <ClInclude Include="..\src\debug.h" />
+ <ClInclude Include="..\src\decor.h" />
+ <ClInclude Include="..\src\environment.h" />
+ <ClInclude Include="..\src\explosion.h" />
+ <ClInclude Include="..\src\externs.h" />
+ <ClInclude Include="..\src\extern\dirent.h" />
+ <ClInclude Include="..\src\files.h" />
+ <ClInclude Include="..\src\floattext.h" />
+ <ClInclude Include="..\src\gameloop.h" />
+ <ClInclude Include="..\src\gfxData.h" />
+ <ClInclude Include="..\src\globaldata.h" />
+ <ClInclude Include="..\src\globals.h" />
+ <ClInclude Include="..\src\globaltypes.h" />
+ <ClInclude Include="..\src\imagedefs.h" />
+ <ClInclude Include="..\src\land.h" />
+ <ClInclude Include="..\src\main.h" />
+ <ClInclude Include="..\src\menu.h" />
+ <ClInclude Include="..\src\missile.h" />
+ <ClInclude Include="..\src\network.h" />
+ <ClInclude Include="..\src\optioncontent.h" />
+ <ClInclude Include="..\src\optionitem.h" />
+ <ClInclude Include="..\src\optionitembase.h" />
+ <ClInclude Include="..\src\optionitemcolour.h" />
+ <ClInclude Include="..\src\optionitemmenu.h" />
+ <ClInclude Include="..\src\optionitemplayer.h" />
+ <ClInclude Include="..\src\optionscreens.h" />
+ <ClInclude Include="..\src\optiontypes.h" />
+ <ClInclude Include="..\src\physobj.h" />
+ <ClInclude Include="..\src\player.h" />
+ <ClInclude Include="..\src\player_types.h" />
+ <ClInclude Include="..\src\resource.h" />
+ <ClInclude Include="..\src\satellite.h" />
+ <ClInclude Include="..\src\shop.h" />
+ <ClInclude Include="..\src\sky.h" />
+ <ClInclude Include="..\src\sound.h" />
+ <ClInclude Include="..\src\tank.h" />
+ <ClInclude Include="..\src\teleport.h" />
+ <ClInclude Include="..\src\text.h" />
+ <ClInclude Include="..\src\update.h" />
+ <ClInclude Include="..\src\virtobj.h" />
+ <ClInclude Include="..\src\winclock.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\src\aicore.cpp" />
+ <ClCompile Include="..\src\atanks.cpp" />
+ <ClCompile Include="..\src\beam.cpp" />
+ <ClCompile Include="..\src\button.cpp" />
+ <ClCompile Include="..\src\client.cpp" />
+ <ClCompile Include="..\src\clock.cpp" />
+ <ClCompile Include="..\src\debris_pool.cpp" />
+ <ClCompile Include="..\src\debug.cpp" />
+ <ClCompile Include="..\src\decor.cpp" />
+ <ClCompile Include="..\src\environment.cpp" />
+ <ClCompile Include="..\src\explosion.cpp" />
+ <ClCompile Include="..\src\extern\dirent.c" />
+ <ClCompile Include="..\src\files.cpp" />
+ <ClCompile Include="..\src\floattext.cpp" />
+ <ClCompile Include="..\src\gameloop.cpp" />
+ <ClCompile Include="..\src\gfxData.cpp" />
+ <ClCompile Include="..\src\globaldata.cpp" />
+ <ClCompile Include="..\src\globaltypes.cpp" />
+ <ClCompile Include="..\src\land.cpp" />
+ <ClCompile Include="..\src\main.cpp" />
+ <ClCompile Include="..\src\menu.cpp" />
+ <ClCompile Include="..\src\missile.cpp" />
+ <ClCompile Include="..\src\network.cpp" />
+ <ClCompile Include="..\src\optionitembase.cpp" />
+ <ClCompile Include="..\src\optionitemcolour.cpp" />
+ <ClCompile Include="..\src\optionitemmenu.cpp" />
+ <ClCompile Include="..\src\optionitemplayer.cpp" />
+ <ClCompile Include="..\src\optionscreens.cpp" />
+ <ClCompile Include="..\src\optiontypes.cpp" />
+ <ClCompile Include="..\src\perlin.cpp" />
+ <ClCompile Include="..\src\physobj.cpp" />
+ <ClCompile Include="..\src\player.cpp" />
+ <ClCompile Include="..\src\player_types.cpp" />
+ <ClCompile Include="..\src\satellite.cpp" />
+ <ClCompile Include="..\src\shop.cpp" />
+ <ClCompile Include="..\src\sky.cpp" />
+ <ClCompile Include="..\src\sound.cpp" />
+ <ClCompile Include="..\src\tank.cpp" />
+ <ClCompile Include="..\src\teleport.cpp" />
+ <ClCompile Include="..\src\text.cpp" />
+ <ClCompile Include="..\src\update.cpp" />
+ <ClCompile Include="..\src\virtobj.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="..\src\atanks.rc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/vs12/atanks.vcxproj.filters b/vs12/atanks.vcxproj.filters
new file mode 100755
index 0000000..597db3f
--- /dev/null
+++ b/vs12/atanks.vcxproj.filters
@@ -0,0 +1,293 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Quelldateien">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Headerdateien">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Ressourcendateien">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\src\aicore.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\beam.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\button.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\client.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\clock.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\debris_pool.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\debug.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\decor.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\environment.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\explosion.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\externs.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\files.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\floattext.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\gameloop.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\gfxData.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\globaldata.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\globals.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\globaltypes.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\imagedefs.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\land.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\main.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\menu.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\missile.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\network.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\optioncontent.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\optionitem.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\optionitembase.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\optionitemmenu.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\optionitemplayer.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\optionscreens.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\optiontypes.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\physobj.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\player.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\player_types.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\resource.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\satellite.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\shop.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\sky.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\sound.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\tank.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\teleport.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\text.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\update.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\virtobj.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\winclock.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\extern\dirent.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\optionitemcolour.h">
+ <Filter>Headerdateien</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\src\aicore.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\atanks.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\beam.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\button.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\client.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\clock.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\debris_pool.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\debug.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\decor.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\environment.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\explosion.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\files.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\floattext.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\gameloop.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\gfxData.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\globaldata.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\globaltypes.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\land.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\main.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\menu.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\missile.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\network.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\optionitembase.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\optionitemmenu.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\optionitemplayer.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\optionscreens.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\optiontypes.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\perlin.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\physobj.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\player.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\player_types.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\satellite.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\shop.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\sky.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\sound.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\tank.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\teleport.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\text.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\update.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\virtobj.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\extern\dirent.c">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\optionitemcolour.cpp">
+ <Filter>Quelldateien</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="..\src\atanks.rc">
+ <Filter>Ressourcendateien</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project>
\ No newline at end of file
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/atanks.git
More information about the Pkg-games-commits
mailing list