Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
1 MB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/AUTHORS b/AUTHORS
index 228380c..2747e95 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,55 +1,56 @@
Active developers
acme_pjz
Edward Lii
MCMic
odamite
oyvindln (SDL2 port)
Tedium (Cloudscape theme)
Former developers
Luka Horvat
O. Bahri Gordebak
Contributors
AapoRantalainen
ctdabomb (Testing, levelmaking)
davy
emarshall85
hasufell
Sauer2
squarecross
TermiT
worldcitizen
Yann Soubeyrand
Ports/Packaging
AapoRantalainen - Maemo port
acme_pjz - Windows version
amdmi3 - FreeBSD port
Artur_J - AmigaOS port
Edward Lii - linux binary, openSUSE packaging
hasufell - gentoo packaging
hcf - Xbox port
kirpken - Web port
Knitter - MacOS X port
mcobit - Pandora port
MCMic - Arch Linux packaging
odamite - Ubuntu packaging, Windows installer
petos - Mageia packaging
Translators
acme_pjz - Simplified Chinese
- Akien - French
+ Akien, Poussinou - French
BioHazardX - Italian
GunChleoc - Scottish Gaelic
- KroArtem - Russian
+ KroArtem, mesnevi - Russian
mdtrooper - Spanish
ming.yan2 - Traditional Chinese
odamite - Finnish
+ Petter Reinholdtsen, comradekingu - Norwegian Bokmål
SanskritFritz - Hungarian
Tedium - Dutch
Wuzzy (with help of Sauer2) - German
For an up to date list and contact information see:
http://meandmyshadow.sourceforge.net/wiki/index.php/Authors
diff --git a/data/Credits.txt b/data/Credits.txt
index 2483caa..3695ab8 100644
--- a/data/Credits.txt
+++ b/data/Credits.txt
@@ -1,159 +1,171 @@
The following list contains the data files in Me and My Shadow.
It should contain all files with their correct attribution and license information.
If something is missing please contact the developers.
font/
- Blokletters-Viltstift.ttf
Name: Blokletters Viltstift
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: http://lefly.vepar.nl/
- knewave.ttf
License: Open Font License <http://scripts.sil.org/OFL>
Attribution: Tyler Finck (http://www.sursly.com)
- DroidSansFallback.ttf
License: Apache License <http://www.apache.org/licenses/LICENSE-2.0>
Source: http://www.droidfonts.com
- DejaVuSansMono.ttf and DejaVuSansCondensed-Oblique.ttf
License: <https://dejavu-fonts.github.io/License.html>
Source: https://dejavu-fonts.github.io/
gfx/menu/
- clear-progress.png
- License: CC BY 3.0 <http://creativecommons.org/licenses/by/3.0/>
+ License: CC BY 3.0 <https://creativecommons.org/licenses/by/3.0/>
Attribution: Tedium
- credits.png
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
- moving.png
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
- questionmark.png
- License: GNU LGPL <http://www.gnu.org/licenses/lgpl>
- Source: Wine <http://www.winehq.org/>
+ License: GNU LGPL <https://www.gnu.org/licenses/lgpl>
+ Source: Wine <https://www.winehq.org/>
Note: Modified by acme_pjz
- selection.png
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
- statistics.png
- License: CC BY 3.0 <http://creativecommons.org/licenses/by/3.0/>
+ License: CC BY 3.0 <https://creativecommons.org/licenses/by/3.0/>
Attribution: odamite
- title.png
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
- toolbar.png
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: odamite, Tedium
Undo and redo icon in this file is copied from Inkscape icon
- License: GNU GPL v2.0 or later <http://www.gnu.org/licenses/gpl>
+ License: GNU GPL v2.0 or later <https://www.gnu.org/licenses/gpl>
Source: Inkscape <https://inkscape.org/>
+gfx/achievements
+ - *.png
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
+ Attribution: Wuzzy
+ Note: Contains work from the Cloudscape theme.
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
+ Note: Contains work from Openclipart <https://openclipart.org/>
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
+
+
gfx/
- actions.png
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
- addon0.png
- License: CC BY-SA 4.0 <http://creativecommons.org/licenses/by-sa/4.0/>
+ License: CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0/>
Attribution: hellcp
Website: https://forum.freegamedev.net/viewtopic.php?f=17&t=7974#p77218
Note: Modified by acme_pjz
- addon1.png
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: Tedium
Note: Contains work from the Cloudscape theme.
- addon2.png
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: Tedium
Note: Contains work from the Cloudscape theme.
- addon3.png
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: Tedium
Note: Contains work from the Cloudscape theme.
- dropshadow.png
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: Me and My Shadow <http://meandmyshadow.sf.net>
- emotions.png
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: Tedium, acme_pjz
Note: Another programmer art
- gui.png
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: Me and My Shadow <http://meandmyshadow.sf.net>
+ Note: contains work by Wuzzy
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
- medals.png
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: Tedium
- playbutton.png
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: Tedium
- recordings.png
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: Tedium
- screenshot.png
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
Note: Made using the knewave font.
- time.png
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: Tedium
levelpacks/
Note that the translations inside the levelpacks are all licensed under the GNU GPLv3.
For the copyright notice(s) of the individual translations see the relevant .po file's header.
- classic/
- License: GPLv3 <http://www.gnu.org/licenses/gpl>
+ License: GPLv3 <https://www.gnu.org/licenses/gpl>
Copyright: 2011 Luka Horvat
Copyright: 2011-2012 Me and My Shadow
Note: Levels originally made by Luka Horvat and extended and updated by Me and My Shadow.
- default/
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
- tutorial/
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
levels/
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
Note: All single levels distributed with Me and My Shadow are public domain.
locale/
For the copyright notice(s) of the translations see the header of the relevant .po file.
- messages.pot
- License: GPLv3 <http://www.gnu.org/licenses/gpl>
+ License: GPLv3 <https://www.gnu.org/licenses/gpl>
Copyright: 2012 Me and My Shadow <http://meandmyshadow.sf.net>
music/
See the header of the .music files for the license and attribution of the music files.
sfx/
- achievement.ogg
Name: UI Sound effects pack
- License: CC BY 3.0 <http://creativecommons.org/licenses/by/3.0/>
+ License: CC BY 3.0 <https://creativecommons.org/licenses/by/3.0/>
Attribution: David McKee (ViRiX) soundcloud.com/virix
- Source: http://opengameart.org/content/ui-sound-effects-pack
+ Source: https://opengameart.org/content/ui-sound-effects-pack
- checkpoint.wav
- License: GNU GPL <http://www.gnu.org/licenses/gpl>
- Copyright: XMoto <http://xmoto.tuxfamily.org>
+ License: GNU GPL <https://www.gnu.org/licenses/gpl>
+ Copyright: XMoto <https://xmoto.tuxfamily.org>
- collect.wav
- License: CC BY 3.0 <http://creativecommons.org/licenses/by/3.0/>
+ License: CC BY 3.0 <https://creativecommons.org/licenses/by/3.0/>
Attribution: odamite
- error.wav
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
- Source: http://opengameart.org/content/skweaks
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
+ Source: https://opengameart.org/content/skweaks
Note: converted to .wav
- hit.wav
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
Note: Made with sfxr <http://www.drpetter.se/project_sfxr.html>
- jump.wav
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
Note: Made with sfxr <http://www.drpetter.se/project_sfxr.html>
- swap.wav
Name: Ambient Pulse Noise
- License: CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>
+ License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
Attribution: Gobusto
- Source: http://opengameart.org/content/ambient-pulse-noise
+ Source: https://opengameart.org/content/ambient-pulse-noise
- toggle.wav
- License: CC0 <http://creativecommons.org/publicdomain/zero/1.0/>
- Source: http://www.opengameart.org/content/hit-sounds
+ License: CC0 <https://creativecommons.org/publicdomain/zero/1.0/>
+ Source: https://www.opengameart.org/content/hit-sounds
themes/
See the individual themes for their license(s).
diff --git a/data/gfx/achievements/UNUSED_hook.png b/data/gfx/achievements/UNUSED_hook.png
new file mode 100644
index 0000000..dd867f4
Binary files /dev/null and b/data/gfx/achievements/UNUSED_hook.png differ
diff --git a/data/gfx/achievements/UNUSED_programmer.png b/data/gfx/achievements/UNUSED_programmer.png
new file mode 100644
index 0000000..f2db98c
Binary files /dev/null and b/data/gfx/achievements/UNUSED_programmer.png differ
diff --git a/data/gfx/achievements/UNUSED_switch1k.png b/data/gfx/achievements/UNUSED_switch1k.png
new file mode 100644
index 0000000..42c00aa
Binary files /dev/null and b/data/gfx/achievements/UNUSED_switch1k.png differ
diff --git a/data/gfx/achievements/addicted.png b/data/gfx/achievements/addicted.png
new file mode 100644
index 0000000..3ce027d
Binary files /dev/null and b/data/gfx/achievements/addicted.png differ
diff --git a/data/gfx/achievements/cheat.png b/data/gfx/achievements/cheat.png
new file mode 100644
index 0000000..c089db1
Binary files /dev/null and b/data/gfx/achievements/cheat.png differ
diff --git a/data/gfx/achievements/constructor.png b/data/gfx/achievements/constructor.png
new file mode 100644
index 0000000..8a6770c
Binary files /dev/null and b/data/gfx/achievements/constructor.png differ
diff --git a/data/gfx/achievements/constructor2.png b/data/gfx/achievements/constructor2.png
new file mode 100644
index 0000000..737ee02
Binary files /dev/null and b/data/gfx/achievements/constructor2.png differ
diff --git a/data/gfx/achievements/die1000.png b/data/gfx/achievements/die1000.png
new file mode 100644
index 0000000..3fcb99b
Binary files /dev/null and b/data/gfx/achievements/die1000.png differ
diff --git a/data/gfx/achievements/die10in5.png b/data/gfx/achievements/die10in5.png
new file mode 100644
index 0000000..0aeb792
Binary files /dev/null and b/data/gfx/achievements/die10in5.png differ
diff --git a/data/gfx/achievements/die50.png b/data/gfx/achievements/die50.png
new file mode 100644
index 0000000..5b9a435
Binary files /dev/null and b/data/gfx/achievements/die50.png differ
diff --git a/data/gfx/achievements/die5in5.png b/data/gfx/achievements/die5in5.png
new file mode 100644
index 0000000..5d9b325
Binary files /dev/null and b/data/gfx/achievements/die5in5.png differ
diff --git a/data/gfx/achievements/doublekill.png b/data/gfx/achievements/doublekill.png
new file mode 100644
index 0000000..9979ef7
Binary files /dev/null and b/data/gfx/achievements/doublekill.png differ
diff --git a/data/gfx/achievements/experienced.png b/data/gfx/achievements/experienced.png
new file mode 100644
index 0000000..3a8a430
Binary files /dev/null and b/data/gfx/achievements/experienced.png differ
diff --git a/data/gfx/achievements/expert.png b/data/gfx/achievements/expert.png
new file mode 100644
index 0000000..df3a116
Binary files /dev/null and b/data/gfx/achievements/expert.png differ
diff --git a/data/gfx/achievements/forget.png b/data/gfx/achievements/forget.png
new file mode 100644
index 0000000..2291aa9
Binary files /dev/null and b/data/gfx/achievements/forget.png differ
diff --git a/data/gfx/achievements/helloworld.png b/data/gfx/achievements/helloworld.png
new file mode 100644
index 0000000..48a052b
Binary files /dev/null and b/data/gfx/achievements/helloworld.png differ
diff --git a/data/gfx/achievements/jit.png b/data/gfx/achievements/jit.png
new file mode 100644
index 0000000..4b56439
Binary files /dev/null and b/data/gfx/achievements/jit.png differ
diff --git a/data/gfx/achievements/load1k.png b/data/gfx/achievements/load1k.png
new file mode 100644
index 0000000..594b495
Binary files /dev/null and b/data/gfx/achievements/load1k.png differ
diff --git a/data/gfx/achievements/loadanddie.png b/data/gfx/achievements/loadanddie.png
new file mode 100644
index 0000000..6031f4c
Binary files /dev/null and b/data/gfx/achievements/loadanddie.png differ
diff --git a/data/gfx/achievements/loadanddie100.png b/data/gfx/achievements/loadanddie100.png
new file mode 100644
index 0000000..03b828e
Binary files /dev/null and b/data/gfx/achievements/loadanddie100.png differ
diff --git a/data/gfx/achievements/loyalfan.png b/data/gfx/achievements/loyalfan.png
new file mode 100644
index 0000000..9383d18
Binary files /dev/null and b/data/gfx/achievements/loyalfan.png differ
diff --git a/data/gfx/achievements/panicload.png b/data/gfx/achievements/panicload.png
new file mode 100644
index 0000000..36abe6a
Binary files /dev/null and b/data/gfx/achievements/panicload.png differ
diff --git a/data/gfx/achievements/panicsave.png b/data/gfx/achievements/panicsave.png
new file mode 100644
index 0000000..3a4e1e6
Binary files /dev/null and b/data/gfx/achievements/panicsave.png differ
diff --git a/data/gfx/achievements/programmer.png b/data/gfx/achievements/programmer.png
new file mode 100644
index 0000000..d6847a9
Binary files /dev/null and b/data/gfx/achievements/programmer.png differ
diff --git a/data/gfx/achievements/quickswap.png b/data/gfx/achievements/quickswap.png
new file mode 100644
index 0000000..b6903fc
Binary files /dev/null and b/data/gfx/achievements/quickswap.png differ
diff --git a/data/gfx/achievements/record100.png b/data/gfx/achievements/record100.png
new file mode 100644
index 0000000..ccc7282
Binary files /dev/null and b/data/gfx/achievements/record100.png differ
diff --git a/data/gfx/achievements/save1k.png b/data/gfx/achievements/save1k.png
new file mode 100644
index 0000000..c084481
Binary files /dev/null and b/data/gfx/achievements/save1k.png differ
diff --git a/data/gfx/achievements/squash1.png b/data/gfx/achievements/squash1.png
new file mode 100644
index 0000000..0ed493a
Binary files /dev/null and b/data/gfx/achievements/squash1.png differ
diff --git a/data/gfx/achievements/squash50.png b/data/gfx/achievements/squash50.png
new file mode 100644
index 0000000..50ea6f5
Binary files /dev/null and b/data/gfx/achievements/squash50.png differ
diff --git a/data/gfx/achievements/switch1k.png b/data/gfx/achievements/switch1k.png
new file mode 100644
index 0000000..59250a6
Binary files /dev/null and b/data/gfx/achievements/switch1k.png differ
diff --git a/data/gfx/achievements/tutorial.png b/data/gfx/achievements/tutorial.png
new file mode 100644
index 0000000..169613d
Binary files /dev/null and b/data/gfx/achievements/tutorial.png differ
diff --git a/data/gfx/achievements/tutorialgold.png b/data/gfx/achievements/tutorialgold.png
new file mode 100644
index 0000000..bc4d1c3
Binary files /dev/null and b/data/gfx/achievements/tutorialgold.png differ
diff --git a/data/gfx/achievements/withoutsave.png b/data/gfx/achievements/withoutsave.png
new file mode 100644
index 0000000..159048e
Binary files /dev/null and b/data/gfx/achievements/withoutsave.png differ
diff --git a/data/gfx/gui.png b/data/gfx/gui.png
index ebfaa49..d181a24 100644
Binary files a/data/gfx/gui.png and b/data/gfx/gui.png differ
diff --git a/data/levelpacks/classic/Credits.map b/data/levelpacks/classic/Credits.map
index db6a499..4202be8 100644
--- a/data/levelpacks/classic/Credits.map
+++ b/data/levelpacks/classic/Credits.map
@@ -1,131 +1,134 @@
"name"="Credits"
"size"="1050","750"
+time=192
+recordings=1
"tile"("Block","50","50")
"tile"("Block","50","100")
"tile"("Block","50","150")
"tile"("Block","50","200")
"tile"("Block","50","250")
"tile"("Block","50","300")
"tile"("Block","100","300")
"tile"("Block","150","300")
"tile"("Block","200","250")
"tile"("Block","200","200")
"tile"("Block","150","150")
"tile"("Block","200","100")
"tile"("Block","200","50")
"tile"("Block","150","0")
"tile"("Block","100","0")
"tile"("Block","300","0")
"tile"("Block","350","50")
"tile"("Block","400","100")
"tile"("Block","450","150")
"tile"("Block","500","0")
"tile"("Block","500","50")
"tile"("Block","500","100")
"tile"("Block","450","200")
"tile"("Block","450","250")
"tile"("Block","400","300")
"tile"("Block","100","150")
"tile"("Block","50","400")
"tile"("Block","50","450")
"tile"("Block","50","500")
"tile"("Block","50","550")
"tile"("Block","50","600")
"tile"("Block","50","650")
"tile"("Block","100","650")
"tile"("Block","150","650")
"tile"("Block","200","650")
"tile"("Block","300","400")
"tile"("Block","300","450")
"tile"("Block","300","500")
"tile"("Block","300","550")
"tile"("Block","300","600")
"tile"("Block","300","650")
"tile"("Block","350","650")
"tile"("Block","400","650")
"tile"("Block","450","650")
"tile"("Block","450","600")
"tile"("Block","450","550")
"tile"("Block","450","500")
"tile"("Block","450","450")
"tile"("Block","450","400")
"tile"("Block","550","400")
"tile"("Block","550","450")
"tile"("Block","550","500")
"tile"("Block","550","550")
"tile"("Block","550","600")
"tile"("Block","550","650")
"tile"("Block","600","550")
"tile"("Block","650","600")
"tile"("Block","700","650")
"tile"("Block","600","500")
"tile"("Block","650","500")
"tile"("Block","650","450")
"tile"("Block","700","450")
"tile"("Block","700","400")
"tile"("Block","800","650")
"tile"("Block","800","600")
"tile"("Block","800","550")
"tile"("Block","800","500")
"tile"("Block","800","450")
"tile"("Block","850","400")
"tile"("Block","850","400")
"tile"("Block","900","400")
"tile"("Block","950","450")
"tile"("Block","950","500")
"tile"("Block","950","550")
"tile"("Block","950","600")
"tile"("Block","950","650")
"tile"("Block","900","550")
"tile"("Block","850","550")
"tile"("Spikes","0","0")
"tile"("Spikes","0","50")
"tile"("Spikes","0","100")
"tile"("Spikes","0","150")
"tile"("Spikes","0","200")
"tile"("Spikes","0","250")
"tile"("Spikes","0","300")
"tile"("Spikes","0","350")
"tile"("Spikes","0","400")
"tile"("Spikes","0","450")
"tile"("Spikes","0","500")
"tile"("Spikes","0","550")
"tile"("Spikes","0","600")
"tile"("Spikes","0","650")
"tile"("Spikes","0","700")
"tile"("Spikes","50","700")
"tile"("Spikes","100","700")
"tile"("Spikes","150","700")
"tile"("Spikes","200","700")
"tile"("Spikes","250","700")
"tile"("Spikes","300","700")
"tile"("Spikes","350","700")
"tile"("Spikes","400","700")
"tile"("Spikes","450","700")
"tile"("Spikes","500","700")
"tile"("Spikes","550","700")
"tile"("Spikes","600","700")
"tile"("Spikes","650","700")
"tile"("Spikes","700","700")
"tile"("Spikes","750","700")
"tile"("Spikes","800","700")
"tile"("Spikes","850","700")
"tile"("Spikes","900","700")
"tile"("Spikes","950","700")
"tile"("Spikes","1000","700")
"tile"("Spikes","1000","650")
"tile"("Spikes","1000","600")
"tile"("Spikes","1000","550")
"tile"("Spikes","1000","500")
"tile"("Spikes","1000","450")
"tile"("Spikes","1000","400")
"tile"("Spikes","1000","350")
"tile"("Spikes","1000","300")
"tile"("Spikes","1000","250")
"tile"("Spikes","1000","200")
"tile"("Spikes","1000","150")
"tile"("Spikes","1000","100")
"tile"("Spikes","1000","50")
"tile"("Spikes","1000","0")
"tile"("PlayerStart","300","350")
"tile"("ShadowStart","550","350")
+"tile"("Exit","950","300")
diff --git a/data/levelpacks/classic/locale/de.po b/data/levelpacks/classic/locale/de.po
index f589a29..166f1e9 100644
--- a/data/levelpacks/classic/locale/de.po
+++ b/data/levelpacks/classic/locale/de.po
@@ -1,124 +1,124 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-04-01 17:56+0300\n"
-"PO-Revision-Date: 2018-09-09 08:23+0000\n"
+"PO-Revision-Date: 2018-09-21 08:31+0000\n"
"Last-Translator: Wuzzy <almikes@aol.com>\n"
"Language-Team: German <https://hosted.weblate.org/projects/me-and-my-shadow/"
"levels-classic/de/>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.2-dev\n"
#: classic\Tower.map:1
msgid "Tower"
msgstr "Turm"
#: classic\Credits.map:1
msgid "Credits"
msgstr "Abspann"
#: classic\Lab.map:1
msgid "Lab"
msgstr "Labor"
#: classic\Shadow.map:1
msgid "Shadow"
msgstr "Schatten"
#: classic\ShadowBlocks.map:1
msgid "ShadowBlocks"
msgstr "SchattenBlöcke"
#: classic\levels.lst:1
msgid "classic"
msgstr "Klassisch"
#: classic\Timing.map:1
msgid "Timing"
msgstr "Zeitgefühl"
#: classic\FreeFall.map:1
msgid "FreeFall"
msgstr "FreierFall"
#: classic\SomeSpikes.map:1
msgid "SomeSpikes"
msgstr "EinigeStacheln"
#: classic\BabySteps.map:1
msgid "BabySteps"
msgstr "BabySchritte"
#: classic\Tricky.map:1
msgid "Tricky"
msgstr "Knifflig"
#: classic\Road.map:1
msgid "Road"
msgstr "Straße"
#: classic\Jumper.map:1
msgid "Jumper"
msgstr "Springer"
#: classic\FirstSpikes.map:1
msgid "FirstSpikes"
msgstr "ErsteStacheln"
#: classic\Jumping.map:1
msgid "Jumping"
msgstr "Springen"
#: classic\levels.lst:2
msgid "Default level pack"
msgstr "Standard-Levelsammlung"
#: classic\LittleHelp.map:1
msgid "LittleHelp"
msgstr "EtwasHilfe"
#: classic\End.map:1
msgid "End"
msgstr "Ende"
#: classic\FreeFall2.map:1
msgid "FreeFall2"
msgstr "FeierFall2"
#: classic\Control.map:1
msgid "Control"
-msgstr "Steuerung"
+msgstr "Kontrolle"
#: classic\UpDown.map:1
msgid "UpDown"
msgstr "AufAb"
#: classic\Spiky.map:1
msgid "Spiky"
msgstr "Stachelig"
#: classic\Here.map:1
msgid "Here"
msgstr "Hier"
#: classic\Carry.map:1
msgid "Carry"
msgstr "Tragen"
#: classic\LeftRight.map:1
msgid "LeftRight"
msgstr "LinksRechts"
#: classic\Headache.map:1
msgid "Headache"
msgstr "Kopfschmerzen"
diff --git a/data/levelpacks/classic/locale/nb.po b/data/levelpacks/classic/locale/nb.po
index 96c265d..145443b 100644
--- a/data/levelpacks/classic/locale/nb.po
+++ b/data/levelpacks/classic/locale/nb.po
@@ -1,121 +1,125 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-04-01 17:56+0300\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
+"PO-Revision-Date: 2018-09-18 05:36+0000\n"
+"Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n"
+"Language-Team: Norwegian Bokmål <https://hosted.weblate.org/projects/"
+"me-and-my-shadow/levels-classic/nb/>\n"
"Language: nb\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 3.2-dev\n"
#: classic\Tower.map:1
msgid "Tower"
-msgstr ""
+msgstr "Tårn"
#: classic\Credits.map:1
msgid "Credits"
-msgstr ""
+msgstr "Bidragsytere"
#: classic\Lab.map:1
msgid "Lab"
-msgstr ""
+msgstr "Lab"
#: classic\Shadow.map:1
msgid "Shadow"
-msgstr ""
+msgstr "Skygge"
#: classic\ShadowBlocks.map:1
msgid "ShadowBlocks"
-msgstr ""
+msgstr "Skyggeblokk"
#: classic\levels.lst:1
msgid "classic"
-msgstr ""
+msgstr "klassisk"
#: classic\Timing.map:1
+#, fuzzy
msgid "Timing"
-msgstr ""
+msgstr "Tidsfølelse"
#: classic\FreeFall.map:1
msgid "FreeFall"
-msgstr ""
+msgstr "Frittfall"
#: classic\SomeSpikes.map:1
msgid "SomeSpikes"
-msgstr ""
+msgstr "NoenTagger"
#: classic\BabySteps.map:1
msgid "BabySteps"
-msgstr ""
+msgstr "BarneSteg"
#: classic\Tricky.map:1
msgid "Tricky"
-msgstr ""
+msgstr "Vanskelig"
#: classic\Road.map:1
msgid "Road"
-msgstr ""
+msgstr "Vei"
#: classic\Jumper.map:1
msgid "Jumper"
-msgstr ""
+msgstr "Hopper"
#: classic\FirstSpikes.map:1
msgid "FirstSpikes"
-msgstr ""
+msgstr "FørsteStaker"
#: classic\Jumping.map:1
msgid "Jumping"
-msgstr ""
+msgstr "Hopper"
#: classic\levels.lst:2
msgid "Default level pack"
-msgstr ""
+msgstr "Forvalgt brettsamling"
#: classic\LittleHelp.map:1
msgid "LittleHelp"
-msgstr ""
+msgstr "LitenHjelp"
#: classic\End.map:1
msgid "End"
-msgstr ""
+msgstr "Slutt"
#: classic\FreeFall2.map:1
msgid "FreeFall2"
-msgstr ""
+msgstr "Frittfall2"
#: classic\Control.map:1
msgid "Control"
-msgstr ""
+msgstr "Kontroll"
#: classic\UpDown.map:1
msgid "UpDown"
-msgstr ""
+msgstr "OppNed"
#: classic\Spiky.map:1
msgid "Spiky"
-msgstr ""
+msgstr "Taggete"
#: classic\Here.map:1
msgid "Here"
-msgstr ""
+msgstr "Her"
#: classic\Carry.map:1
msgid "Carry"
-msgstr ""
+msgstr "Bær"
#: classic\LeftRight.map:1
msgid "LeftRight"
-msgstr ""
+msgstr "VenstreHøyre"
#: classic\Headache.map:1
msgid "Headache"
-msgstr ""
+msgstr "Hodepine"
diff --git a/data/levelpacks/classic/locale/ru.po b/data/levelpacks/classic/locale/ru.po
index 5b9ac04..47cdf9e 100644
--- a/data/levelpacks/classic/locale/ru.po
+++ b/data/levelpacks/classic/locale/ru.po
@@ -1,125 +1,126 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
-#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-04-01 17:56+0300\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Jz Pan <acme.pjz@gmail.com>, 2018\n"
-"Language-Team: Russian (https://www.transifex.com/acmepjz/teams/88148/ru/)\n"
+"PO-Revision-Date: 2018-09-29 21:34+0000\n"
+"Last-Translator: mesnevi <shams@airpost.net>\n"
+"Language-Team: Russian <https://hosted.weblate.org/projects/me-and-my-shadow/"
+"levels-classic/ru/>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
-"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
-"%100>=11 && n%100<=14)? 2 : 3);\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
+"(n%100>=11 && n%100<=14)? 2 : 3);\n"
+"X-Generator: Weblate 3.2-dev\n"
#: classic\Tower.map:1
msgid "Tower"
msgstr "Башня"
#: classic\Credits.map:1
msgid "Credits"
msgstr "Благодарности"
#: classic\Lab.map:1
msgid "Lab"
msgstr "Лаборатория"
#: classic\Shadow.map:1
msgid "Shadow"
msgstr "Тень"
#: classic\ShadowBlocks.map:1
msgid "ShadowBlocks"
msgstr "Блоки для тени"
#: classic\levels.lst:1
msgid "classic"
-msgstr "классика"
+msgstr "классический"
#: classic\Timing.map:1
msgid "Timing"
msgstr "На время"
#: classic\FreeFall.map:1
msgid "FreeFall"
msgstr "Свободное падение"
#: classic\SomeSpikes.map:1
msgid "SomeSpikes"
-msgstr "Немного шипов"
+msgstr "Немножко шипов"
#: classic\BabySteps.map:1
msgid "BabySteps"
msgstr "Первые шаги"
#: classic\Tricky.map:1
msgid "Tricky"
-msgstr "Хитрый"
+msgstr "С подвохом"
#: classic\Road.map:1
msgid "Road"
msgstr "Дорога"
#: classic\Jumper.map:1
msgid "Jumper"
msgstr "Прыгун"
#: classic\FirstSpikes.map:1
msgid "FirstSpikes"
msgstr "Первые шипы"
#: classic\Jumping.map:1
msgid "Jumping"
-msgstr "Прыжки в ширину"
+msgstr "Прыг-скок"
#: classic\levels.lst:2
msgid "Default level pack"
msgstr "Набор уровней по умолчанию"
#: classic\LittleHelp.map:1
msgid "LittleHelp"
-msgstr "Немного помощи"
+msgstr "Мало пользы"
#: classic\End.map:1
msgid "End"
msgstr "Конец"
#: classic\FreeFall2.map:1
msgid "FreeFall2"
msgstr "Свободное падение-2"
#: classic\Control.map:1
msgid "Control"
msgstr "Контроль"
#: classic\UpDown.map:1
msgid "UpDown"
msgstr "Вверх-вниз"
#: classic\Spiky.map:1
msgid "Spiky"
msgstr "Шипастый"
#: classic\Here.map:1
msgid "Here"
msgstr "Здесь"
#: classic\Carry.map:1
msgid "Carry"
-msgstr "Носильщик"
+msgstr "Ноша"
#: classic\LeftRight.map:1
msgid "LeftRight"
msgstr "Влево-вправо"
#: classic\Headache.map:1
msgid "Headache"
-msgstr "Головная боль"
+msgstr "Неприятность"
diff --git a/data/levelpacks/default/locale/nb.po b/data/levelpacks/default/locale/nb.po
index 563a26e..143f6aa 100644
--- a/data/levelpacks/default/locale/nb.po
+++ b/data/levelpacks/default/locale/nb.po
@@ -1,101 +1,108 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-04-01 17:56+0300\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Automatically generated\n"
-"Language-Team: none\n"
+"PO-Revision-Date: 2018-09-18 05:36+0000\n"
+"Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n"
+"Language-Team: Norwegian Bokmål <https://hosted.weblate.org/projects/"
+"me-and-my-shadow/levels-default/nb/>\n"
"Language: nb\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 3.2-dev\n"
#: default\Regroup.map:1
msgid "Regroup"
-msgstr ""
+msgstr "Omgrupper"
#: default\3.map:1
msgid "Running in the Sky"
-msgstr ""
+msgstr "Løping i himmelen"
#: default\4.map:1
msgid "Both Up and Down"
-msgstr ""
+msgstr "Både opp og ned"
#: default\map02.map:1
msgid "Snail"
-msgstr ""
+msgstr "Snegle"
#: default\Towers.map:1
msgid "Towers"
-msgstr ""
+msgstr "Tårn"
#: default\map01.map:1
msgid "Simple"
-msgstr ""
+msgstr "Enkel"
#: default\map04.map:1
msgid "Double trouble"
-msgstr ""
+msgstr "Dobbel trøbbel"
#: default\levels.lst:1
+#, fuzzy
msgid "default"
-msgstr ""
+msgstr "forvalg"
#: default\Skyscrapers.map:1
msgid "Skyscrapers"
-msgstr ""
+msgstr "Skyskrapere"
#: default\QuickSwap.map:1
msgid "Quick swap"
-msgstr ""
+msgstr "Raskt bytte"
#: default\5.map:1
+#, fuzzy
msgid "stopping the spikes"
-msgstr ""
+msgstr "stopping av taggene"
#: default\levels.lst:2
msgid "Default"
-msgstr ""
+msgstr "Forvalg"
#: default\Remote.map:1
msgid "Remote control"
-msgstr ""
+msgstr "Fjernkontroll"
#: default\map03.map:1
msgid "Spiky travel"
-msgstr ""
+msgstr "Taggete reise"
#: default\2.map:1
msgid "Tricky Jumping"
-msgstr ""
+msgstr "Vanskelige hopp"
#: default\1.map:1
msgid "Building Teamwork"
-msgstr ""
+msgstr "Lagåndsbygging"
#: default\Timing.map:1
+#, fuzzy
msgid "Timing"
-msgstr ""
+msgstr "Tidsfølelse"
#: default\Volcano.map:1
msgid "Volcano"
-msgstr ""
+msgstr "Vulkan"
#: default\Sweeper.map:1
+#, fuzzy
msgid "Sweeper"
-msgstr ""
+msgstr "Koster"
#: default\Switches.map:1
msgid "Switches"
-msgstr ""
+msgstr "Brytere"
#: default\map05.map:1
msgid "Wall breaking"
-msgstr ""
+msgstr "Veggknusing"
diff --git a/data/levelpacks/default/locale/ru.po b/data/levelpacks/default/locale/ru.po
index 9e1727c..a671a83 100644
--- a/data/levelpacks/default/locale/ru.po
+++ b/data/levelpacks/default/locale/ru.po
@@ -1,105 +1,106 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
-#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-04-01 17:56+0300\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Jz Pan <acme.pjz@gmail.com>, 2018\n"
-"Language-Team: Russian (https://www.transifex.com/acmepjz/teams/88148/ru/)\n"
+"PO-Revision-Date: 2018-09-29 21:34+0000\n"
+"Last-Translator: mesnevi <shams@airpost.net>\n"
+"Language-Team: Russian <https://hosted.weblate.org/projects/me-and-my-shadow/"
+"levels-default/ru/>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
-"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
-"%100>=11 && n%100<=14)? 2 : 3);\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
+"(n%100>=11 && n%100<=14)? 2 : 3);\n"
+"X-Generator: Weblate 3.2-dev\n"
#: default\Regroup.map:1
msgid "Regroup"
-msgstr "Перегруппировка"
+msgstr "Перестройка"
#: default\3.map:1
msgid "Running in the Sky"
-msgstr "Пробегая по небу"
+msgstr "Беги по небу"
#: default\4.map:1
msgid "Both Up and Down"
msgstr "И вверх, и вниз"
#: default\map02.map:1
msgid "Snail"
msgstr "Улитка"
#: default\Towers.map:1
msgid "Towers"
msgstr "Башни"
#: default\map01.map:1
msgid "Simple"
-msgstr "Простой"
+msgstr "Раз плюнуть"
#: default\map04.map:1
msgid "Double trouble"
-msgstr "Двойные проблемы"
+msgstr "Дважды в дураках"
#: default\levels.lst:1
msgid "default"
-msgstr "обычный"
+msgstr "стандартный"
#: default\Skyscrapers.map:1
msgid "Skyscrapers"
msgstr "Небоскрёбы"
#: default\QuickSwap.map:1
msgid "Quick swap"
-msgstr "Быстрая замена"
+msgstr "Мигом"
#: default\5.map:1
msgid "stopping the spikes"
-msgstr "Останавливая шипы"
+msgstr "Шипам — нет"
#: default\levels.lst:2
msgid "Default"
-msgstr "По умолчанию"
+msgstr "Стандартный"
#: default\Remote.map:1
msgid "Remote control"
-msgstr "Удалённый контроль"
+msgstr "Дистанционное управление"
#: default\map03.map:1
msgid "Spiky travel"
-msgstr "Шипастое путешествие"
+msgstr "Прогулки по шипам"
#: default\2.map:1
msgid "Tricky Jumping"
msgstr "Хитрое прыганье"
#: default\1.map:1
msgid "Building Teamwork"
-msgstr "Налаживая командную работу"
+msgstr "Построй команду"
#: default\Timing.map:1
msgid "Timing"
msgstr "На время"
#: default\Volcano.map:1
msgid "Volcano"
msgstr "Вулкан"
#: default\Sweeper.map:1
msgid "Sweeper"
-msgstr "Чистильщик"
+msgstr "Дворник"
#: default\Switches.map:1
msgid "Switches"
-msgstr "Переключатели"
+msgstr "Выключатели"
#: default\map05.map:1
msgid "Wall breaking"
msgstr "Ломая стены"
diff --git a/data/levelpacks/tutorial/locale/nb.po b/data/levelpacks/tutorial/locale/nb.po
index 5813614..07acf3a 100644
--- a/data/levelpacks/tutorial/locale/nb.po
+++ b/data/levelpacks/tutorial/locale/nb.po
@@ -1,293 +1,397 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-04-01 17:56+0300\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Automatically generated\n"
-"Language-Team: none\n"
+"PO-Revision-Date: 2018-09-18 05:36+0000\n"
+"Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n"
+"Language-Team: Norwegian Bokmål <https://hosted.weblate.org/projects/"
+"me-and-my-shadow/levels-tut/nb/>\n"
"Language: nb\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 3.2-dev\n"
#: tutorial\tut12.map:17
msgid "Now you'll need to help your shadow cross.\n"
-msgstr ""
+msgstr "Nå må du hjelpe til med å få skyggen din over.\n"
#: tutorial\tut10.map:19
msgid "You can save your progress in a level with\ncheckpoints. You can restore them at any\ntime using the F3 button."
msgstr ""
+"Du kan lagre fremdriften ditt på et nivå med\n"
+"sjekkpunkter. Du kan gjenopprette dem\n"
+"når som helst med F3-tasten."
#: tutorial\tut18.map:14
+#, fuzzy
msgid "This trigger will deactivate the moving block.\nTry to stop it at the right moment.\nYou can only do this once, so if it fails you'll\nhave to reset the level with the 'R' key."
msgstr ""
+"Denne utløseren vil deaktivere den bevegende blokken.\n"
+"Du kan stoppe den på akkurat rett tid.\n"
+"Du kan kun gjøre dette én gang, slik at hvis det mislykkes\n"
+"vil du måtte tilbakestille nivået med R-tasten."
#: tutorial\tut21.map:49
msgid "If your shadow falls down here you will have to restart.\nPress 'R' to restart the level."
msgstr ""
+"Hvis skyggen din faller ned her vil du måtte starte på ny.\n"
+"Trykk \"R\" for å starte nivået på nytt."
#: tutorial\levels.lst:1
msgid "tutorial"
-msgstr ""
+msgstr "veiledning"
#: tutorial\tut23.map:1
msgid "Collecting Keys"
-msgstr ""
+msgstr "Nøkkelinnsamling"
#: tutorial\tut10.map:46
msgid "You've chosen the right way."
-msgstr ""
+msgstr "Du har valgt rett vei"
#: tutorial\tut22.map:1
msgid "Shadow swap"
-msgstr ""
+msgstr "Skyggebytte"
#: tutorial\tut20.map:187
msgid "A dead end, you'd better go back and choose\nthe other portal."
msgstr ""
+"En bomvei, du må gå tilbake og velge\n"
+"den andre portalen."
#: tutorial\tut03.map:13
msgid "See those gaps over there?\nReach the finish without falling."
msgstr ""
+"Ser du fallgruvene der borte?\n"
+"Nå slutten uten å falle."
#: tutorial\tut07.map:33
msgid "If your shadow dies you'll have to restart.\nRestart the game by pressing the 'R' key."
msgstr ""
+"Hvis skyggen din dør må du starte igjen.\n"
+"Start spillet igjen ved å trykke på R-tasten."
#: tutorial\tut02.map:18
+#, fuzzy
msgid "You can jump using the up key.\nTry jumping over these blocks."
msgstr ""
+"Du kan hoppe med ↑-tasten.\n"
+"Prøv å hoppe over disse blokkene."
#: tutorial\tut07.map:1
msgid "Shadow challenge"
-msgstr ""
+msgstr "Skyggeutfordring"
#: tutorial\tut20.map:1
msgid "Portal mayhem"
-msgstr ""
+msgstr "Portalkaos"
#: tutorial\tut02.map:1
msgid "First jumps"
-msgstr ""
+msgstr "Første hopp"
#: tutorial\tut20.map:16
msgid "Portals point to another portal or to nothing.\nTry to reach the exit in this portal mayhem."
msgstr ""
+"Portaler peker til en annen portal eller gjør ingenting.\n"
+"Prøv å nå slutten i dette portalkaoset."
#: tutorial\tut05.map:32
msgid "TIP:\nThink what moves your shadow has to make.\nThen let your character record those moves.\nYou can break it down into smaller recordings."
msgstr ""
+"TIPS:\n"
+"Tenk på hvilke bevegelser skyggen din må gjøre.\n"
+"La så karakteren din ta opp disse bevegelsene.\n"
+"Du kan bryte det ned i mindre opptak."
#: tutorial\tut11.map:17
msgid "Until now the levels were static.\nThere are however moving blocks."
msgstr ""
+"Til nå har nivåene vært statiske.\n"
+"Nå finnes det bevegelige blokker."
#: tutorial\tut10.map:32
msgid "Save your progress here."
-msgstr ""
+msgstr "Lagre din fremdrift her."
#: tutorial\tut13.map:1
msgid "Moving spikes"
-msgstr ""
+msgstr "Bevegelige tagger"
#: tutorial\tut13.map:18
msgid "Watch out for the moving spikes."
-msgstr ""
+msgstr "Se opp for de bevegelige taggene."
#: tutorial\tut16.map:1
msgid "The switch"
-msgstr ""
+msgstr "Bryteren"
#: tutorial\tut25.map:13
msgid "The very best of luck!"
-msgstr ""
+msgstr "Lykke til!"
#: tutorial\tut20.map:156
msgid "Now choose one of the two."
-msgstr ""
+msgstr "Velg én av de to."
#: tutorial\tut19.map:42
+#, fuzzy
msgid "NOTE:\nYou can go back by entering this portal.\nIt is however a bit different, you don't have to\npress the down key, it will activate when you walk in it."
msgstr ""
+"MERK:\n"
+"Du kan gå tilbake ved å gå inn i denne portalen.\n"
+"Det er dog litt annerledes, du må ikke\n"
+"trykke på ↓-tasten, den vil aktiveres når du går inn i den."
#: tutorial\tut15.map:20
msgid "This gap is impossible to jump over.\nStep on the button next of you."
msgstr ""
+"Fallgruven er umulig å hoppe over.\n"
+"Tre opp på knappen ved siden av deg."
#: tutorial\tut06.map:1
msgid "Shadow walk"
-msgstr ""
+msgstr "Skyggegange"
#: tutorial\tut10.map:54
+#, fuzzy
msgid "This is the wrong way.\nGo back to the previous checkpoint by pressing F3."
msgstr ""
+"Dette er feil vei.\n"
+"Gå tilbake til forrige sjekkpunkt ved å trykke F3."
#: tutorial\tut25.map:318
msgid "You have made it!"
-msgstr ""
+msgstr "Du har klart det!"
#: tutorial\tut17.map:1
msgid "Toggle trigger"
-msgstr ""
+msgstr "Veksle utløser"
#: tutorial\tut24.map:1
msgid "Warning"
-msgstr ""
+msgstr "Advarsel"
#: tutorial\tut05.map:1
msgid "Shadow"
-msgstr ""
+msgstr "Skygge"
#: tutorial\tut06.map:23
msgid "NOTE:\nAlthough you can't jump on those blocks\nyou can record the jumps for your shadow."
msgstr ""
+"MERK:\n"
+"Selv om du ikke kan hoppe på de blokkene\n"
+"kan du ta opp hoppene for skyggen din."
#: tutorial\tut19.map:39
msgid "Now it's time to check out the portals.\nTo get to the exit you'll have to take the portal.\nWalk to it and press the down arrow to\nactivate."
msgstr ""
+"Nå er det på tide å sjekke ut portalene.\n"
+"For å finne rett utgang må du ta portalen.\n"
+"Gå til den og trykk ↓-pilen for å aktivere."
#: tutorial\tut18.map:1
msgid "Stop trigger"
-msgstr ""
+msgstr "Stopper"
#: tutorial\tut01.map:1
msgid "Walk in the park"
-msgstr ""
+msgstr "En tur i parken"
#: tutorial\tut09.map:14
msgid "Those blocks are fragile.\nIf you step on them too often they'll break."
msgstr ""
+"Disse blokkene er skjøre.\n"
+"Hvis du trår på dem for ofte vil de gå istykker."
#: tutorial\tut19.map:1
msgid "First portals"
-msgstr ""
+msgstr "Første portaler"
#: tutorial\tut25.map:1
msgid "Final"
-msgstr ""
+msgstr "Finale"
#: tutorial\tut04.map:1
msgid "First spikes"
-msgstr ""
+msgstr "Første tagger"
#: tutorial\tut12.map:1
msgid "Moving shadow blocks"
-msgstr ""
+msgstr "Bevegelige skyggeblokker"
#: tutorial\tut22.map:22
+#, fuzzy
msgid "You need your shadow to reach the exit.\nMake use of the swapper to get him down (or \nto get yourself down)."
msgstr ""
+"Du trenger skyggen din for å nå utgangen.\n"
+"Gjør bruk av bytteren for å få den ned (eller \n"
+"for å få deg selv ned)."
#: tutorial\tut21.map:8
+#, fuzzy
msgid "Now it's time for something completely\ndifferent: swappoints. When you or your\nshadow activate them you'll swap places."
msgstr ""
+"Nå er det på tide med noe helt annet:\n"
+"Byttingspunkter. Når du eller din skygge\n"
+"aktiverer dem bytter dere sted."
#: tutorial\tut06.map:27
msgid "Only your shadow can stand on those shadow\nblocks. Try to get him up there."
msgstr ""
+"Kun skyggen din kan stå på de skyggeblokkene.\n"
+"Prøv å få den opp dit."
#: tutorial\tut17.map:14
+#, fuzzy
msgid "You've only seen triggers that activate other\nblocks, but they can also deactivate or toggle\nthem."
msgstr ""
+"Du har kun sett utløsere som aktiverer andre\n"
+"blokker, men de kan også deaktivere eller\n"
+"veksle dem."
#: tutorial\tut14.map:68
msgid "When standing on conveyor belts you'll\nmove without walking."
msgstr ""
+"Når du står på samlebåndet vil du\n"
+"flytte deg uten å gå."
#: tutorial\tut25.map:46
msgid "TIP:\nTry to get your shadow in front of the shadow\nwall. If he falls down you'd better restart."
msgstr ""
+"TIPS:\n"
+"Prøv å få skyggen din foran skyggeveggen.\n"
+"Hvis skyggen faller ned bør du starte på ny."
#: tutorial\tut22.map:33
+#, fuzzy
msgid "TIP:\nWhen your shadow is trapped stand on the\nright side of the shadow blocks. Now record \nthe down key and let your shadow mimic."
msgstr ""
+"TIPS:\n"
+"Når skyggen er fanget, stå på høyre\n"
+"siden av skyggeblokkene. Ta så opp\n"
+"↓-tasten og la din skygge etterligne."
#: tutorial\tut08.map:1
msgid "Teamwork"
-msgstr ""
+msgstr "Lagarbeid"
#: tutorial\tut14.map:71
msgid "Let the shadow finish the level by walking to\nthe finish. But don't stand still because your\nshadow will move all the way back."
msgstr ""
+"La skyggen fullføre nivået ved å gå til\n"
+"utgangen. Men ikke stå stille fordi\n"
+"da vil skyggen gå hele veien tilbake."
#: tutorial\tut21.map:1
msgid "Swappoints"
-msgstr ""
+msgstr "Byttingspunkter"
#: tutorial\tut23.map:18
+#, fuzzy
msgid "One thing you need to know before you're ready are keys.\nSometimes there are keys spread around the level.\nThe exit is locked until you get all the keys."
msgstr ""
+"En ting du må ta rede på før du er klar er nøkler.\n"
+"Noen ganger er nøkler spredt rundt omrking på nivået.\n"
+"Utgangen er låst til du finner alle."
#: tutorial\tut03.map:34
msgid "Good job!"
-msgstr ""
+msgstr "Godt gjort!"
#: tutorial\tut16.map:14
+#, fuzzy
msgid "There's another type of trigger: the switch.\nUse the switch to activate the elevator so that you\ncan reach the exit."
msgstr ""
+"Det finnes en annen type utløser: Bryteren.\n"
+"Bruk den for å aktivere heisen, slik at du kan\n"
+"nå utgangen."
#: tutorial\tut09.map:1
msgid "Fragile"
-msgstr ""
+msgstr "Skjør"
#: tutorial\tut24.map:18
msgid "That's all there is.\nNow it's time to put your skills to the test.\nEnter the exit to go to the last level.\nGood luck!"
msgstr ""
+"Det er alt.\n"
+"Nå er det på tide å sette ferdighetene dine ut i livet.\n"
+"Gå inn i utgangen for å gå til siste nivå.\n"
+"Lykke til!"
#: tutorial\tut25.map:459
msgid "Where could your shadow be?"
-msgstr ""
+msgstr "Hvor kan skyggen din være?"
#: tutorial\tut01.map:18
+#, fuzzy
msgid "Welcome to Me and My Shadow.\nYou can use the arrow keys to walk to the exit.\n\nGood luck!"
msgstr ""
+"Velkommen til meg og min skygge.\n"
+"Du kan bruke piltastene for å gå til utgangen.\n"
+"\n"
+"Lykke til!"
#: tutorial\tut15.map:1
msgid "Triggering"
-msgstr ""
+msgstr "Utløsing"
#: tutorial\tut03.map:1
msgid "Jumping around"
-msgstr ""
+msgstr "Hopping rund omkring"
#: tutorial\tut10.map:1
msgid "Checkpoints"
-msgstr ""
+msgstr "Sjekkpunkter"
#: tutorial\tut07.map:19
msgid "Spikes are not only deadly for you,\nbut also for your shadow."
msgstr ""
+"Tagger er ikke bare dødlig for deg,\n"
+"men også skyggen din."
#: tutorial\tut08.map:21
msgid "You and your shadow need to work together.\nMove your shadow towards the wall and jump\non him so that you can jump over the wall."
msgstr ""
+"Du og din skygge må jobbe sammen.\n"
+"Flytt den mot veggen og hopp på den,\n"
+"slik at du kan hoppe over veggen."
#: tutorial\tut04.map:19
msgid "Spikes are deadly.\nDon't touch them!"
msgstr ""
+"Tagger er dødlige.\n"
+"Ikke rør dem!"
#: tutorial\levels.lst:3
msgid "Step by step introduction"
-msgstr ""
+msgstr "Stegvis instroduksjon"
#: tutorial\tut11.map:1
msgid "Moving blocks"
-msgstr ""
+msgstr "Bevegelige blokker"
#: tutorial\tut14.map:1
msgid "Conveyor madness"
-msgstr ""
+msgstr "Samlebåndsgalskap"
#: tutorial\tut05.map:29
+#, fuzzy
msgid "You can't reach the exit, but your shadow can.\nPress space to record your moves.\nPress space once again to let your shadow\nmimic your recording."
msgstr ""
+"Du kan ikke nå utgangen, men skyggen din kan.\n"
+"Trykk mellomrom for å ta opp bevegelsene dine.\n"
+"Trykk mellomrom igjen for å la skyggen din\n"
+"etterligne opptaket ditt."
#: tutorial\levels.lst:2
msgid "You have finished the tutorial!"
-msgstr ""
+msgstr "Du har fullført veiledningen!"
diff --git a/data/levelpacks/tutorial/locale/ru.po b/data/levelpacks/tutorial/locale/ru.po
index 7a07e8d..3404bea 100644
--- a/data/levelpacks/tutorial/locale/ru.po
+++ b/data/levelpacks/tutorial/locale/ru.po
@@ -1,477 +1,478 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
-#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-04-01 17:56+0300\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Jz Pan <acme.pjz@gmail.com>, 2018\n"
-"Language-Team: Russian (https://www.transifex.com/acmepjz/teams/88148/ru/)\n"
+"PO-Revision-Date: 2018-09-29 22:36+0000\n"
+"Last-Translator: mesnevi <shams@airpost.net>\n"
+"Language-Team: Russian <https://hosted.weblate.org/projects/me-and-my-shadow/"
+"levels-tut/ru/>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
-"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
-"%100>=11 && n%100<=14)? 2 : 3);\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
+"(n%100>=11 && n%100<=14)? 2 : 3);\n"
+"X-Generator: Weblate 3.2-dev\n"
#: tutorial\tut12.map:17
msgid "Now you'll need to help your shadow cross.\n"
msgstr "Теперь необходимо помочь тени перебраться на другую сторону.\n"
#: tutorial\tut10.map:19
msgid ""
"You can save your progress in a level with\n"
"checkpoints. You can restore them at any\n"
"time using the F3 button."
msgstr ""
"Прогресс на уровне можно сохранять с помощью\n"
"чекпоинтов. Чтобы начать сначала,\n"
"следует нажать клавишу F3."
#: tutorial\tut18.map:14
msgid ""
"This trigger will deactivate the moving block.\n"
"Try to stop it at the right moment.\n"
"You can only do this once, so if it fails you'll\n"
"have to reset the level with the 'R' key."
msgstr ""
"Этот триггер деактивирует движущийся блок.\n"
"Необходимо остановить его в подходящий момент.\n"
"Это можно сделать только один раз, поэтому если\n"
"не получится - придётся перезапустить уровень с помощью клавиши 'R'."
#: tutorial\tut21.map:49
msgid ""
"If your shadow falls down here you will have to restart.\n"
"Press 'R' to restart the level."
msgstr ""
-"Если тень упадёт, придётся перезапустить уровень,\n"
+"Если тень упадёт, придётся начать уровень сначала\n"
"с помощью клавиши 'R'."
#: tutorial\levels.lst:1
msgid "tutorial"
msgstr "обучение"
#: tutorial\tut23.map:1
msgid "Collecting Keys"
-msgstr "Ключи"
+msgstr "Собери ключи"
#: tutorial\tut10.map:46
msgid "You've chosen the right way."
-msgstr "Правильным путём идёте..."
+msgstr "Правильным путём идёте, товарищи."
#: tutorial\tut22.map:1
msgid "Shadow swap"
msgstr "Поменяться с тенью"
#: tutorial\tut20.map:187
msgid ""
"A dead end, you'd better go back and choose\n"
"the other portal."
msgstr ""
-"Тупик, следует вернуться назад и выбрать\n"
+"Здесь тупик, следует вернуться назад и выбрать\n"
"другой портал."
#: tutorial\tut03.map:13
msgid ""
"See those gaps over there?\n"
"Reach the finish without falling."
msgstr ""
"Впереди - пропасти.\n"
-"Нужно добраться до финиша, не упав вниз."
+"Нужно добраться до финиша, не упав."
#: tutorial\tut07.map:33
msgid ""
"If your shadow dies you'll have to restart.\n"
"Restart the game by pressing the 'R' key."
msgstr ""
-"Если тень умрёт, придётся перезапустить уровень\n"
+"Если тень умрёт, уровень придётся начать сначала\n"
"с помощью клавиши 'R'."
#: tutorial\tut02.map:18
msgid ""
"You can jump using the up key.\n"
"Try jumping over these blocks."
msgstr ""
"Придётся перепрыгнуть через эти блоки.\n"
"Прыгать можно с помощью клавиши 'вверх'."
#: tutorial\tut07.map:1
msgid "Shadow challenge"
-msgstr "Испытание для тени"
+msgstr "Испытание тенью"
#: tutorial\tut20.map:1
msgid "Portal mayhem"
-msgstr "Хаос порталов"
+msgstr "Суматоха с порталами"
#: tutorial\tut02.map:1
msgid "First jumps"
msgstr "Первые прыжки"
#: tutorial\tut20.map:16
msgid ""
"Portals point to another portal or to nothing.\n"
"Try to reach the exit in this portal mayhem."
msgstr ""
-"Порталы указывают на другой портал или же вникуда.\n"
-"Придётся пробираться к выходу через этот хаос порталов."
+"Порталы ведут в другой портал (или на пустое место).\n"
+"Придётся пробираться к выходу через эти дебри."
#: tutorial\tut05.map:32
msgid ""
"TIP:\n"
"Think what moves your shadow has to make.\n"
"Then let your character record those moves.\n"
"You can break it down into smaller recordings."
msgstr ""
"Подсказка:\n"
"Следует спланировать действия для тени,\n"
"а затем записать эти движения.\n"
-"Последовательность действий можно разбить на несколько записей."
+"Можно разбить большую запись на несколько записей поменьше."
#: tutorial\tut11.map:17
msgid ""
"Until now the levels were static.\n"
"There are however moving blocks."
msgstr ""
"До сих пор все уровни были статичными.\n"
"Впрочем, существуют и подвижные блоки."
#: tutorial\tut10.map:32
msgid "Save your progress here."
msgstr "Здесь можно сохранить свой прогресс."
#: tutorial\tut13.map:1
msgid "Moving spikes"
-msgstr "Движущиеся шипы"
+msgstr "Шипы перемещаются"
#: tutorial\tut13.map:18
msgid "Watch out for the moving spikes."
msgstr "Остерегайся движущихся шипов."
#: tutorial\tut16.map:1
msgid "The switch"
msgstr "Выключатель"
#: tutorial\tut25.map:13
msgid "The very best of luck!"
msgstr "Удачи!"
#: tutorial\tut20.map:156
msgid "Now choose one of the two."
msgstr "Одно из двух."
#: tutorial\tut19.map:42
msgid ""
"NOTE:\n"
"You can go back by entering this portal.\n"
"It is however a bit different, you don't have to\n"
"press the down key, it will activate when you walk in it."
msgstr ""
-"Заметка:\n"
-"Можно вернуться обратно, зайдя в портал.\n"
-"Однако этот портал немного отличается - нет необходимости нажимать клавишу,\n"
-"он сам активируется, когда персонаж зайдёт в него."
+"КСТАТИ:\n"
+"Зайдя в этот портал, можно вернуться обратно.\n"
+"Он устроен по-другому: нажимать на клавишу 'вниз' не надо.\n"
+"Он сработает, когда персонаж зайдёт в него."
#: tutorial\tut15.map:20
msgid ""
"This gap is impossible to jump over.\n"
"Step on the button next of you."
msgstr ""
-"Через эту пропасть невозможно перепрыгнуть.\n"
-"Попробуйте нажать кнопку рядом."
+"Через этот провал нельзя перепрыгнуть.\n"
+"Попробуйте наступить на кнопку рядом."
#: tutorial\tut06.map:1
msgid "Shadow walk"
-msgstr "Прогулка тени"
+msgstr "Прогулка с тенью"
#: tutorial\tut10.map:54
msgid ""
"This is the wrong way.\n"
"Go back to the previous checkpoint by pressing F3."
msgstr ""
-"Это неправильный путь.\n"
+"Прохода нет!\n"
"Придётся вернуться предыдущему чекпоинту, нажав клавишу F3."
#: tutorial\tut25.map:318
msgid "You have made it!"
msgstr "Получилось!"
#: tutorial\tut17.map:1
msgid "Toggle trigger"
-msgstr "Переключающий триггер"
+msgstr "Включай, запускай"
#: tutorial\tut24.map:1
msgid "Warning"
-msgstr "Предупреждение"
+msgstr "Внимание"
#: tutorial\tut05.map:1
msgid "Shadow"
msgstr "Тень"
#: tutorial\tut06.map:23
msgid ""
"NOTE:\n"
"Although you can't jump on those blocks\n"
"you can record the jumps for your shadow."
msgstr ""
-"Заметка:\n"
-"Хоть на эти блоки невозможно запрыгнуть,\n"
+"КСТАТИ:\n"
+"Хоть на эти блоки и невозможно запрыгнуть,\n"
"эти прыжки можно записать для тени."
#: tutorial\tut19.map:39
msgid ""
"Now it's time to check out the portals.\n"
"To get to the exit you'll have to take the portal.\n"
"Walk to it and press the down arrow to\n"
"activate."
msgstr ""
"Теперь время взглянуть на порталы.\n"
"Чтобы добраться до выхода,\n"
-"необходимо воспользоваться порталом.\n"
+"придётся пройти через портал.\n"
"Они активируются с помощью клавиши 'вниз'."
#: tutorial\tut18.map:1
msgid "Stop trigger"
-msgstr "Останавливающий триггер"
+msgstr "Помешай запуску"
#: tutorial\tut01.map:1
msgid "Walk in the park"
msgstr "Прогулка по парку"
#: tutorial\tut09.map:14
msgid ""
"Those blocks are fragile.\n"
"If you step on them too often they'll break."
msgstr ""
"Эти блоки хрупкие.\n"
"Если на них часто наступать, они разрушатся."
#: tutorial\tut19.map:1
msgid "First portals"
msgstr "Первые порталы"
#: tutorial\tut25.map:1
msgid "Final"
-msgstr "Финал"
+msgstr "Финишная прямая"
#: tutorial\tut04.map:1
msgid "First spikes"
msgstr "Первые шипы"
#: tutorial\tut12.map:1
msgid "Moving shadow blocks"
msgstr "Движущиеся блоки для тени"
#: tutorial\tut22.map:22
msgid ""
"You need your shadow to reach the exit.\n"
"Make use of the swapper to get him down (or \n"
"to get yourself down)."
msgstr ""
"Нужно, чтобы тень добралась до выхода.\n"
"Поменяйтесь местами, чтобы спустить тень вниз\n"
"(или спустить вниз игрока)."
#: tutorial\tut21.map:8
msgid ""
"Now it's time for something completely\n"
"different: swappoints. When you or your\n"
"shadow activate them you'll swap places."
msgstr ""
"А теперь время для чего-то совершенно\n"
-"нового: точки перестановки. Игрок или тень\n"
-"тень активируют их, они меняются местами."
+"нового: точки перестановки. Если игрок или\n"
+"тень активируют их, они поменяются местами."
#: tutorial\tut06.map:27
msgid ""
"Only your shadow can stand on those shadow\n"
"blocks. Try to get him up there."
msgstr ""
"Только тень может стоять на этих блоках.\n"
-"Попробуйте переместить тень наверх."
+"Попробуйте взобраться на них тенью."
#: tutorial\tut17.map:14
msgid ""
"You've only seen triggers that activate other\n"
"blocks, but they can also deactivate or toggle\n"
"them."
msgstr ""
"Мы уже познакомились с триггерами, которые\n"
"активируют другие блоки, но они также могут\n"
"деактивировать или переключать их."
#: tutorial\tut14.map:68
msgid ""
"When standing on conveyor belts you'll\n"
"move without walking."
msgstr ""
"Конвеер постоянно перемещает всё,\n"
"что на нём находится."
#: tutorial\tut25.map:46
msgid ""
"TIP:\n"
"Try to get your shadow in front of the shadow\n"
"wall. If he falls down you'd better restart."
msgstr ""
"Подсказка:\n"
"Тенью необходимо стать перед стеной для тени.\n"
"Если тень упадёт, придётся начать сначала."
#: tutorial\tut22.map:33
msgid ""
"TIP:\n"
"When your shadow is trapped stand on the\n"
"right side of the shadow blocks. Now record \n"
"the down key and let your shadow mimic."
msgstr ""
"Подсказка:\n"
"Когда тень будет в ловушке, нужно стать справа\n"
"от блоков для тени, начать запись, нажать\n"
-"клавишу 'вниз' и затем воспроизвести запись тенью."
+"клавишу 'вниз' и пусть тень повторит действия."
#: tutorial\tut08.map:1
msgid "Teamwork"
-msgstr "Командная работа"
+msgstr "Мы — команда"
#: tutorial\tut14.map:71
msgid ""
"Let the shadow finish the level by walking to\n"
"the finish. But don't stand still because your\n"
"shadow will move all the way back."
msgstr ""
-"Пусть тень закончит уровень, добравшись до финиша.\n"
+"Пусть тень пройдёт уровень, добравшись до финиша.\n"
"Но стоять на месте тоже нельзя, потому что тень\n"
"будет смещаться назад на конвейере."
#: tutorial\tut21.map:1
msgid "Swappoints"
msgstr "Точки перестановки"
#: tutorial\tut23.map:18
msgid ""
"One thing you need to know before you're ready are keys.\n"
"Sometimes there are keys spread around the level.\n"
"The exit is locked until you get all the keys."
msgstr ""
-"Ещё одна вещь, которую следует знать, это ключи.\n"
-"Иногда ключи распределены по всему уровню.\n"
-"Выход закрыт до тех пор, пока удастся собрать все ключи."
+"Ещё одна вещь перед тем, как закончить: ключи.\n"
+"Иногда они распределены по всему уровню.\n"
+"Выход закрыт до тех пор, пока не получится собрать все ключи."
#: tutorial\tut03.map:34
msgid "Good job!"
-msgstr "Хорошая работа!"
+msgstr "Молодец!"
#: tutorial\tut16.map:14
msgid ""
"There's another type of trigger: the switch.\n"
"Use the switch to activate the elevator so that you\n"
"can reach the exit."
msgstr ""
"Есть ещё один вид триггера: переключатель.\n"
"Этот переключатель активирует подъёмник,\n"
"который поможет добраться до выхода."
#: tutorial\tut09.map:1
msgid "Fragile"
msgstr "Хрупкие блоки"
#: tutorial\tut24.map:18
msgid ""
"That's all there is.\n"
"Now it's time to put your skills to the test.\n"
"Enter the exit to go to the last level.\n"
"Good luck!"
msgstr ""
"Вот и всё.\n"
"Настало время проверить полученные навыки.\n"
"Нужно добраться до выхода, чтобы перейти на последний уровень.\n"
"Удачи!"
#: tutorial\tut25.map:459
msgid "Where could your shadow be?"
msgstr "Где же тень?"
#: tutorial\tut01.map:18
msgid ""
"Welcome to Me and My Shadow.\n"
"You can use the arrow keys to walk to the exit.\n"
"\n"
"Good luck!"
msgstr ""
"Добро пожаловать в игру \"Me and My Shadow\".\n"
"Используя стрелки для перемещения, необходимо до выхода.\n"
"\n"
"Удачи!"
#: tutorial\tut15.map:1
msgid "Triggering"
msgstr "Триггер"
#: tutorial\tut03.map:1
msgid "Jumping around"
msgstr "Прыгая тут и там"
#: tutorial\tut10.map:1
msgid "Checkpoints"
msgstr "Чекпоинты"
#: tutorial\tut07.map:19
msgid ""
"Spikes are not only deadly for you,\n"
"but also for your shadow."
msgstr ""
"Шипы смертельны не только для игрока,\n"
"но и для тени."
#: tutorial\tut08.map:21
msgid ""
"You and your shadow need to work together.\n"
"Move your shadow towards the wall and jump\n"
"on him so that you can jump over the wall."
msgstr ""
"Игроку и тени надо работать сообща.\n"
"Нужно поставить тень около стены и запрыгнуть\n"
"на неё, чтобы перепрыгнуть через стену."
#: tutorial\tut04.map:19
msgid ""
"Spikes are deadly.\n"
"Don't touch them!"
msgstr ""
-"Шипы смертельны.\n"
-"Не касайся их!"
+"Шипы смертельно опасны.\n"
+"Не прикасайся к ним!"
#: tutorial\levels.lst:3
msgid "Step by step introduction"
msgstr "Пошаговое введение"
#: tutorial\tut11.map:1
msgid "Moving blocks"
msgstr "Движущиеся блоки"
#: tutorial\tut14.map:1
msgid "Conveyor madness"
-msgstr "Конвейерное сумасшествие"
+msgstr "С ума сойти: конвейеры"
#: tutorial\tut05.map:29
msgid ""
"You can't reach the exit, but your shadow can.\n"
"Press space to record your moves.\n"
"Press space once again to let your shadow\n"
"mimic your recording."
msgstr ""
-"Игрок не может добраться до выхода, но это может сделать тень.\n"
+"Игрок не может добраться до выхода, а тень может.\n"
"Нажатие клавиши 'пробел' начнёт запись движений.\n"
"Повторное нажатие клавиши 'пробел' запустит\n"
"воспроизведение записи тенью."
#: tutorial\levels.lst:2
msgid "You have finished the tutorial!"
msgstr "Обучение завершено!"
diff --git a/data/levelpacks/tutorial/tut15.map b/data/levelpacks/tutorial/tut15.map
index 4c52890..f19465d 100755
--- a/data/levelpacks/tutorial/tut15.map
+++ b/data/levelpacks/tutorial/tut15.map
@@ -1,95 +1,95 @@
name=Triggering
recordings=0
size=1150,600
-time=80
+time=90
tile(Block,150,300)
tile(Block,200,300)
tile(Block,250,300)
tile(Block,300,300)
tile(Block,900,300)
tile(ShadowStart,200,250)
tile(PlayerStart,250,250)
tile(Button,350,300){
behaviour=on
id=0
}
tile(Block,950,300)
tile(Block,1000,300)
tile(Exit,950,250)
tile(NotificationBlock,300,250){
message="This gap is impossible to jump over.\nStep on the button next of you."
}
tile(MovingBlock,400,300){
MovingPosCount=1
disabled=1
id=0
loop=0
t0=25
x0=200
y0=0
}
tile(MovingBlock,400,300){
MovingPosCount=1
disabled=1
id=0
loop=0
t0=25
x0=150
y0=0
}
tile(MovingBlock,400,300){
MovingPosCount=1
disabled=1
id=0
loop=0
t0=25
x0=100
y0=0
}
tile(MovingBlock,400,300){
MovingPosCount=1
disabled=1
id=0
loop=0
t0=25
x0=50
y0=0
}
tile(Block,400,300)
tile(MovingBlock,850,300){
MovingPosCount=1
disabled=1
id=0
loop=0
t0=25
x0=-200
y0=0
}
tile(MovingBlock,850,300){
MovingPosCount=1
disabled=1
id=0
loop=0
t0=25
x0=-150
y0=0
}
tile(MovingBlock,850,300){
MovingPosCount=1
disabled=1
id=0
loop=0
t0=25
x0=-100
y0=0
}
tile(MovingBlock,850,300){
MovingPosCount=1
disabled=1
id=0
loop=0
t0=25
x0=-50
y0=0
}
tile(Block,850,300)
diff --git a/data/locale/de.po b/data/locale/de.po
index 1bba93d..3df8212 100644
--- a/data/locale/de.po
+++ b/data/locale/de.po
@@ -1,2123 +1,2121 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the meandmyshadow package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: meandmyshadow 0.5svn\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-09 16:18+0800\n"
-"PO-Revision-Date: 2018-09-10 15:20+0000\n"
+"PO-Revision-Date: 2018-10-02 13:04+0000\n"
"Last-Translator: Wuzzy <almikes@aol.com>\n"
"Language-Team: German <https://hosted.weblate.org/projects/me-and-my-shadow/"
"translations/de/>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.2-dev\n"
#: ../src/AchievementList.cpp:43
msgid "Newbie"
msgstr "Neuling"
#: ../src/AchievementList.cpp:43
msgid "Complete a level."
msgstr "Schließe einen Level ab."
#: ../src/AchievementList.cpp:44
msgid "Experienced player"
msgstr "Erfahrener Spieler"
#: ../src/AchievementList.cpp:44
msgid "Complete 50 levels."
msgstr "Schließe 50 Levels ab."
#: ../src/AchievementList.cpp:45
msgid "Good job!"
msgstr "Gut gemacht!"
#: ../src/AchievementList.cpp:45
msgid "Receive a gold medal."
msgstr "Gewinne eine Goldmedaillie."
#: ../src/AchievementList.cpp:46
msgid "Expert"
msgstr "Experte"
#: ../src/AchievementList.cpp:46
msgid "Earn 50 gold medal."
msgstr "Gewinne 50 Goldmedaillien."
#: ../src/AchievementList.cpp:48
msgid "Graduate"
msgstr "Absolvent"
#: ../src/AchievementList.cpp:48
msgid "Complete the tutorial level pack."
msgstr "Schließe die Einführungs-Levelsammlung ab."
#: ../src/AchievementList.cpp:49
msgid "Outstanding graduate"
msgstr "Herausragender Absolvent"
#: ../src/AchievementList.cpp:49
msgid "Complete the tutorial level pack with gold for all levels."
msgstr "Beende die Einführungs-Levelsammung mit Gold für alle Levels."
#: ../src/AchievementList.cpp:51
msgid "Hooked"
msgstr "Angebissen"
#: ../src/AchievementList.cpp:51
msgid "Play Me and My Shadow for more than 2 hours."
msgstr "Spiele Me and My Shadow für mehr als 2 Stunden."
#: ../src/AchievementList.cpp:52
msgid "Loyal fan of Me and My Shadow"
msgstr "Loyaler Fan von Me and My Shadow"
#: ../src/AchievementList.cpp:52
msgid "Play Me and My Shadow for more than 24 hours."
msgstr "Spiele Me and My Shadow für mehr als 24 Stunden."
#: ../src/AchievementList.cpp:54
msgid "Constructor"
msgstr "Baumeister"
#: ../src/AchievementList.cpp:54
msgid "Use the level editor for more than 2 hours."
msgstr "Benutze den Level-Editor für mehr als 2 Stunden."
#: ../src/AchievementList.cpp:55
msgid "The creator"
msgstr "Der Erschaffer"
#: ../src/AchievementList.cpp:55
msgid "Use the level editor for more than 24 hours."
msgstr "Benutze den Level-Editor für mehr als 24 Stunden."
#: ../src/AchievementList.cpp:57
msgid "Look, cute level!"
msgstr "Sieh mal, ein schöner Level!"
#: ../src/AchievementList.cpp:57
msgid "Create a level for the first time."
msgstr "Baue deinen ersten Level."
#: ../src/AchievementList.cpp:58
msgid "The level museum"
msgstr "Das Level-Museum"
#: ../src/AchievementList.cpp:58
msgid "Create 50 levels."
msgstr "Erstelle 50 Levels."
#: ../src/AchievementList.cpp:60
msgid "Hello, World!"
msgstr "Hallo, Welt!"
#: ../src/AchievementList.cpp:60
msgid "Write a script for the first time."
msgstr "Schreib dein erstes Skript."
#: ../src/AchievementList.cpp:62
msgid "Frog"
msgstr "Frosch"
#: ../src/AchievementList.cpp:62
msgid "Jump 1000 times."
msgstr "Spring 1000 mal."
#: ../src/AchievementList.cpp:64
msgid "Wanderer"
msgstr "Wanderer"
#: ../src/AchievementList.cpp:64
msgid "Travel 100 meters."
msgstr "Reise für 100 Meter."
#: ../src/AchievementList.cpp:65
msgid "Runner"
msgstr "Läufer"
#: ../src/AchievementList.cpp:65
msgid "Travel 1 kilometer."
msgstr "Reise 1 Kilometer."
#: ../src/AchievementList.cpp:66
msgid "Long distance runner"
msgstr "Langstreckenläufer"
#: ../src/AchievementList.cpp:66
msgid "Travel 10 kilometers."
msgstr "Reise für 10 Kilometer."
#: ../src/AchievementList.cpp:67
msgid "Marathon runner"
msgstr "Marathonläufer"
#: ../src/AchievementList.cpp:67
msgid "Travel 42,195 meters."
msgstr "Reise 42.195 Meter."
#: ../src/AchievementList.cpp:69
msgid "Be careful!"
msgstr "Sei vorsichtig!"
#: ../src/AchievementList.cpp:69
msgid "Die for the first time."
msgstr "Stirb zum ersten Mal."
#: ../src/AchievementList.cpp:70
msgid "It doesn't matter..."
msgstr "Es ist egal …"
#: ../src/AchievementList.cpp:70
msgid "Die 50 times."
msgstr "Stirb 50 mal."
#: ../src/AchievementList.cpp:71
msgid "Expert of trial and error"
msgstr "Experte im Rumprobieren"
#: ../src/AchievementList.cpp:71
msgid "Die 1000 times."
msgstr "Stirb 1000 mal."
#: ../src/AchievementList.cpp:73
msgid "Keep an eye for moving blocks!"
msgstr "Pass auf die sich bewegenden Blöcke auf!"
#: ../src/AchievementList.cpp:73
msgid "Get squashed for the first time."
msgstr "Werde zum ersten Mal zerquetscht."
#: ../src/AchievementList.cpp:74
msgid "Potato masher"
msgstr "Kartoffelstampfer"
#: ../src/AchievementList.cpp:74
msgid "Get squashed 50 times."
msgstr "Werde 50 mal zerquetscht."
#: ../src/AchievementList.cpp:76
msgid "Double kill"
msgstr "Doppelmord"
#: ../src/AchievementList.cpp:76
msgid "Get both the player and the shadow dead."
msgstr "Lass sowohl den Spieler als auch den Schatten sterben."
#: ../src/AchievementList.cpp:78
msgid "Bad luck"
msgstr "Pech"
#: ../src/AchievementList.cpp:78
msgid "Die 5 times in under 5 seconds."
msgstr "Stirb 5 mal in weniger als 5 Sekunden."
#: ../src/AchievementList.cpp:79
msgid "This level is too dangerous"
msgstr "Dieser Level ist zu gefährlich"
#: ../src/AchievementList.cpp:79
msgid "Die 10 times in under 5 seconds."
msgstr "Stirb 10 mal in weniger als 5 Sekunden."
#: ../src/AchievementList.cpp:81
msgid "You forgot your friend"
msgstr "Du hast deinen Freund vergessen"
#: ../src/AchievementList.cpp:81
msgid "Finish the level with the player or the shadow dead."
msgstr "Beende den Level, wenn der Spieler oder der Schatten tot ist."
#: ../src/AchievementList.cpp:82
msgid "Just in time"
msgstr "Rechtzeitig"
#: ../src/AchievementList.cpp:82
msgid "Reach the exit with the player and the shadow simultaneously."
msgstr "Erreiche den Ausgang mit dem Spieler und Schatten gleichzeitig."
#: ../src/AchievementList.cpp:84
msgid "Recorder"
msgstr "Aufzeichner"
#: ../src/AchievementList.cpp:84
msgid "Record 100 times."
msgstr "Mach 100 Aufzeichnungen."
#: ../src/AchievementList.cpp:85
msgid "Shadowmaster"
msgstr "Schattenmeister"
#: ../src/AchievementList.cpp:85
msgid "Record 1000 times."
msgstr "Mach 1000 Aufzeichnungen."
#: ../src/AchievementList.cpp:87
msgid "Switch puller"
msgstr "Schalterumleger"
#: ../src/AchievementList.cpp:87
msgid "Pull the switch 100 times."
msgstr "Leg den Schalter 100 mal um."
#: ../src/AchievementList.cpp:88
msgid "The switch is broken!"
msgstr "Der Schalter ist kaputt!"
#: ../src/AchievementList.cpp:88
msgid "Pull the switch 1000 times."
msgstr "Leg den Schalter 1000 mal um."
#: ../src/AchievementList.cpp:90
msgid "Swapper"
msgstr "Vertauscher"
#: ../src/AchievementList.cpp:90
msgid "Swap 100 times."
msgstr "Vertausche 100 mal."
#: ../src/AchievementList.cpp:91
msgid "Player to shadow to player to shadow..."
msgstr "Spieler zu Schatten zu Spieler zu Schatten …"
#: ../src/AchievementList.cpp:91
msgid "Swap 1000 times."
msgstr "Vertausche 1000 mal."
#: ../src/AchievementList.cpp:93
msgid "Play it save"
msgstr "Sicher ist sicher"
#: ../src/AchievementList.cpp:93
msgid "Save 1000 times."
msgstr "Speichere 1000 mal."
#: ../src/AchievementList.cpp:94
msgid "This game is too hard"
msgstr "Das Spiel ist zu schwer"
#: ../src/AchievementList.cpp:94
msgid "Load the game 1000 times."
msgstr "Lade das Spiel 1000 mal."
#: ../src/AchievementList.cpp:96
msgid "No, thanks"
msgstr "Nein danke"
#: ../src/AchievementList.cpp:96
msgid "Complete a level with checkpoint, but without saving."
msgstr "Beende ein Level mit Kontrollpunkt, aber, ohne zu speichern."
#: ../src/AchievementList.cpp:98
msgid "Panic save"
msgstr "Panikspeichern"
#: ../src/AchievementList.cpp:98
msgid "Save twice in 1 second."
msgstr "Speichere zwei mal in 1 Sekunde."
#: ../src/AchievementList.cpp:99
msgid "Panic load"
msgstr "Panikladen"
#: ../src/AchievementList.cpp:99
msgid "Load twice in 1 second."
msgstr "Lade zwei mal in 1 Sekunde."
#: ../src/AchievementList.cpp:101
msgid "Bad saving position"
msgstr "Schlechte Speicherposition"
#: ../src/AchievementList.cpp:101
msgid "Load the game and die within 1 second."
msgstr "Lade das Spiel und stirb innerhalb 1 Sekunde."
#: ../src/AchievementList.cpp:102
msgid "This level is too hard"
msgstr "Dieser Level ist zu schwer"
#: ../src/AchievementList.cpp:102
msgid "Load the same save and die 100 times."
msgstr "Lade den selben Spielstand und stirb 100 mal."
#: ../src/AchievementList.cpp:104
msgid "Quick swap"
msgstr "Schneller Tausch"
#: ../src/AchievementList.cpp:104
msgid "Swap twice in under a second."
msgstr "Vertausche zwei mal in weniger als einer Sekunde."
#: ../src/AchievementList.cpp:107
msgid "Horizontal confusion"
msgstr "Horizontale Verwirrung"
#: ../src/AchievementList.cpp:107
msgid "Press left and right simultaneously."
msgstr "Drücke gleichzeitig links und rechts."
#: ../src/AchievementList.cpp:109
msgid "Cheater"
msgstr "Cheater"
#: ../src/AchievementList.cpp:109
msgid "Cheat in game."
msgstr "Cheate im Spiel."
#: ../src/AchievementList.cpp:111
msgid "Programmer"
msgstr "Programmierer"
#: ../src/AchievementList.cpp:111
msgid "Play the development version of Me and My Shadow."
msgstr "Spiele die Entwicklerversion von Me and My Shadow."
#: ../src/Addons.cpp:44 ../src/LevelPackManager.cpp:108
msgid "Levels"
msgstr "Levels"
#: ../src/Addons.cpp:44
msgid "Single level which usually contain demanding puzzles"
msgstr "Einzelne Level, die üblicherweise knifflige Rätsel enthalten"
#: ../src/Addons.cpp:45
msgid "Levelpacks"
msgstr "Levelsammlungen"
#: ../src/Addons.cpp:45
msgid "Collection of levels with the same author or style"
msgstr "Sammlungen von Levels vom selben Autoren oder im selben Stil"
#: ../src/Addons.cpp:46
msgid "Themes"
msgstr "Themen"
#: ../src/Addons.cpp:46
msgid "Give every block and background a new look and feel"
msgstr "Geben jeden Block und dem Hintergrund ein neues Erscheinungsbild"
#: ../src/Addons.cpp:55 ../src/TitleMenu.cpp:46
msgid "Addons"
msgstr "Add-ons"
#: ../src/Addons.cpp:87
msgid "Unable to initialize addon menu:"
msgstr "Unfähig, das Add-ons-Menü zu initialisieren:"
#: ../src/Addons.cpp:95 ../src/Addons.cpp:158 ../src/Addons.cpp:662
#: ../src/Addons.cpp:690 ../src/CreditsMenu.cpp:89 ../src/LevelSelect.cpp:168
#: ../src/StatisticsScreen.cpp:159
msgid "Back"
msgstr "Zurück"
#: ../src/Addons.cpp:169
msgid "ERROR: unable to download addons file!"
msgstr "FEHLER: Konnte Add-ons-Datei nicht herunterladen!"
# TRANSLATORS: addon_list is the name of a file and should not be translated.
#: ../src/Addons.cpp:182
msgid "ERROR: unable to load addon_list file!"
msgstr "FEHLER: Konnte addon_list-Datei nicht herunterladen!"
#: ../src/Addons.cpp:193
msgid "ERROR: Invalid file format of addons file!"
msgstr "FEHLER: Ungültiges Dateiformat der Add-ons-Datei!"
#: ../src/Addons.cpp:205
msgid "ERROR: Addon list version is unsupported!"
msgstr "FEHLER: Add-on-Listen-Version ist nicht unterstützt!"
# TRANSLATORS: installed_addons is the name of a file and should not be
# translated.
#: ../src/Addons.cpp:226
msgid "ERROR: Unable to create the installed_addons file."
msgstr "FEHLER: Konnte die installed_addons-Datei nicht erzeugen."
#: ../src/Addons.cpp:238
msgid "ERROR: Invalid file format of the installed_addons!"
msgstr "FEHLER: Ungültiges Dateiformat der installed_addons!"
# TRANSLATORS: indicates the author of an addon.
#: ../src/Addons.cpp:389 ../src/Addons.cpp:621
#, c-format
msgid "by %s"
msgstr "von %s"
#: ../src/Addons.cpp:397
msgid "Installed"
msgstr "Installiert"
#: ../src/Addons.cpp:402
msgid "Updatable"
msgstr "Aktualisierbar"
#: ../src/Addons.cpp:412
msgid "Not installed"
msgstr "Nicht installiert"
#: ../src/Addons.cpp:625
#, c-format
msgid "Version: %d\n"
msgstr "Version: %d\n"
#: ../src/Addons.cpp:627
#, c-format
msgid "Installed version: %d\n"
msgstr "Installierte Version: %d\n"
#: ../src/Addons.cpp:630
#, c-format
msgid "License: %s\n"
msgstr "Lizenz: %s\n"
#: ../src/Addons.cpp:633
#, c-format
msgid "Website: %s\n"
msgstr "Webpräsenz: %s\n"
#: ../src/Addons.cpp:637
msgid "(No descriptions provided)"
msgstr "(Keine Beschreibungen vorhanden)"
#: ../src/Addons.cpp:657 ../src/Addons.cpp:684
msgid "Remove"
msgstr "Entfernen"
#: ../src/Addons.cpp:673
msgid "Update"
msgstr "Aktualisieren"
#: ../src/Addons.cpp:679
msgid "Install"
msgstr "Installieren"
#: ../src/Addons.cpp:774
#, c-format
msgid "This addon can't be removed because it's needed by %s."
msgstr "Dieses Add-on kann nicht entfernt werden, da es von %s benötigt wird."
#: ../src/Addons.cpp:774 ../src/Addons.cpp:1051
msgid "Dependency"
msgstr "Abhängigkeit"
#: ../src/Addons.cpp:803
#, c-format
msgid "WARNING: File '%s' appears to have been removed already."
msgstr "ACHTUNG: Datei „%s“ scheint bereits entfernt worden zu sein."
#: ../src/Addons.cpp:803 ../src/Addons.cpp:810 ../src/Addons.cpp:818
#: ../src/Addons.cpp:825 ../src/Addons.cpp:834 ../src/Addons.cpp:840
#: ../src/Addons.cpp:859 ../src/Addons.cpp:866 ../src/Addons.cpp:893
#: ../src/Addons.cpp:900 ../src/Addons.cpp:907 ../src/Addons.cpp:918
#: ../src/Addons.cpp:947 ../src/Addons.cpp:952 ../src/Addons.cpp:962
#: ../src/Addons.cpp:968 ../src/Addons.cpp:981 ../src/Addons.cpp:986
#: ../src/Addons.cpp:1008 ../src/Addons.cpp:1014 ../src/Addons.cpp:1044
msgid "Addon error"
msgstr "Add-on-Fehler"
#: ../src/Addons.cpp:810
#, c-format
msgid "ERROR: Unable to remove file '%s'!"
msgstr "FEHER: Konnte Datei „%s“ nicht entfernen!"
#: ../src/Addons.cpp:818
#, c-format
msgid "WARNING: Directory '%s' appears to have been removed already."
msgstr "ACHTUNG: Verzeichnis: „%s“ scheint bereits entfernt worden zu sein."
#: ../src/Addons.cpp:825
#, c-format
msgid "ERROR: Unable to remove directory '%s'!"
msgstr "FEHLER: Konnte Verzeichnis „%s“ nicht entfernen!"
#: ../src/Addons.cpp:834
#, c-format
msgid "WARNING: Level '%s' appears to have been removed already."
msgstr "ACHTUNG: Level „%s“ scheint bereits entfernt worden zu sein."
#: ../src/Addons.cpp:840
#, c-format
msgid "ERROR: Unable to remove level '%s'!"
msgstr "FEHER: Konnte Level „%s“ nicht entfernen!"
#: ../src/Addons.cpp:859
#, c-format
msgid "WARNING: Levelpack directory '%s' appears to have been removed already."
msgstr ""
"ACHTUNG: Levelsammlungsverzeichnis: „%s“ scheint bereits entfernt worden zu "
"sein."
#: ../src/Addons.cpp:866
#, c-format
msgid "ERROR: Unable to remove levelpack directory '%s'!"
msgstr "FEHLER: Konnte Levelsammlungsverzeichnis „%s“ nicht entfernen!"
#: ../src/Addons.cpp:893
#, c-format
msgid "ERROR: Unable to download addon file %s."
msgstr "FEHLER: Konnte Add-ons-Datei %s nicht herunterladen."
#: ../src/Addons.cpp:900
#, c-format
msgid "ERROR: Unable to extract addon file %s."
msgstr "FEHLER: Konnte Add-ons-Datei %s nicht extrahieren."
#: ../src/Addons.cpp:907
msgid "ERROR: Addon is missing metadata!"
msgstr "FEHLER: Dem Add-on fehlen die Metadaten!"
#: ../src/Addons.cpp:918
msgid "ERROR: Invalid file format for metadata file!"
msgstr "FEHLER: Ungültiges Dateiformat für die Metadaten-Datei!"
#: ../src/Addons.cpp:947
#, c-format
msgid "WARNING: File '%s' already exists, addon may be broken or not working!"
msgstr ""
"ACHTUNG: Datei „%s“ existiert bereits, das Add-on könnte kaputt sein oder "
"nicht funktionieren!"
#: ../src/Addons.cpp:952
#, c-format
msgid ""
"WARNING: Unable to copy file '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
"ACHTUNG: Konnte Datei „%s“ nicht nach „%s“ kopieren, das Add-on könnte "
"kaputt sein oder nicht funktionieren!"
#: ../src/Addons.cpp:962
#, c-format
msgid ""
"WARNING: Destination directory '%s' already exists, addon may be broken or "
"not working!"
msgstr ""
"ACHTUNG: Zielverzeichnis „%s“ existiert bereits, das Add-on könnte kaputt "
"sein oder nicht funktionieren!"
#: ../src/Addons.cpp:968 ../src/Addons.cpp:1014
#, c-format
msgid ""
"WARNING: Unable to move directory '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
"ACHTUNG: Konnte Datei „%s“ nicht nach „%s“ verschieben, das Add-on könnte "
"kaputt sein oder nicht funktionieren!"
#: ../src/Addons.cpp:981
#, c-format
msgid "WARNING: Level '%s' already exists, addon may be broken or not working!"
msgstr ""
"ACHTUNG: Level „%s“ existiert bereits, das Add-on könnte kaputt sein oder "
"nicht funktionieren!"
#: ../src/Addons.cpp:986
#, c-format
msgid ""
"WARNING: Unable to copy level '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
"ACHTUNG: Konnte Level „%s“ nicht nach „%s“ kopieren, das Add-on könnte "
"kaputt sein oder nicht funktionieren!"
#: ../src/Addons.cpp:1008
#, c-format
msgid ""
"WARNING: Levelpack directory '%s' already exists, addon may be broken or not "
"working!"
msgstr ""
"ACHTUNG: Levelsammlungsverzeichnis „%s“ existiert bereits, das Add-on könnte "
"kaputt sein oder nicht funktionieren!"
#: ../src/Addons.cpp:1044
#, c-format
msgid "ERROR: Addon requires another addon (%s) which can't be found!"
msgstr ""
"FEHLER: Das Add-on benötigt ein anderes Add-on (%s), welches nicht gefunden "
"werden kann!"
#: ../src/Addons.cpp:1051
#, c-format
msgid "The addon %s is needed and will be installed now."
msgstr "Das Add-on %s wird benötigt und wird jetzt installiert."
#: ../src/Block.cpp:822 ../src/LevelEditor.cpp:265
msgid "On"
msgstr "Ein"
#: ../src/Block.cpp:823 ../src/LevelEditor.cpp:266
msgid "Off"
msgstr "Aus"
#: ../src/CommandManager.cpp:41
#, c-format
msgid "Undo %s"
msgstr "Rückgängig: %s"
#: ../src/CommandManager.cpp:43
msgid "Can't undo"
msgstr "Kann nicht rückgängig machen"
#: ../src/CommandManager.cpp:49
#, c-format
msgid "Redo %s"
msgstr "Wiederherstellen: %s"
#: ../src/CommandManager.cpp:51
msgid "Can't redo"
msgstr "Kann nicht wiederherstellen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:190
msgid "Resize level"
msgstr "Levelgröße ändern"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:807
msgid "Modify level property"
msgstr "Leveleigenschaft ändern"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:919
#, c-format
msgid "Add scenery layer %s"
msgstr "Szenerieebene %s hinzufügen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:921
#, c-format
msgid "Delete scenery layer %s"
msgstr "Szenerieebene %s löschen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:951
#, c-format
msgid "Modify the property of scenery layer %s"
msgstr "Eigenschaft von Szenerieebene %s ändern"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:1040
#, c-format
msgid "Move %d object from layer %s to layer %s"
msgid_plural "Move %d objects from layer %s to layer %s"
msgstr[0] "%d Objekt von Ebene %s nach Ebene %s verschieben"
msgstr[1] "%d Objekte von Ebene %s nach Ebene %s verschieben"
#: ../src/CreditsMenu.cpp:35 ../src/TitleMenu.cpp:53
msgid "Credits"
msgstr "Abspann"
# TRANSLATORS: Font used in GUI:
# - Use "knewave" for languages using Latin and Latin-derived alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:569 ../src/Functions.cpp:570 ../src/Functions.cpp:571
#: ../src/Functions.cpp:588
msgid "knewave"
msgstr "knewave"
# TRANSLATORS: Font used for normal text:
# - Use "Blokletters-Viltstift" for languages using Latin and Latin-derived
# alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:575
msgid "Blokletters-Viltstift"
msgstr "Blokletters-Viltstift"
#: ../src/Functions.cpp:674
msgid "Loading..."
msgstr "Laden ..."
#: ../src/Functions.cpp:1243 ../src/Functions.cpp:1270
#: ../src/LevelEditor.cpp:559 ../src/LevelEditor.cpp:693
#: ../src/LevelEditor.cpp:758 ../src/LevelEditor.cpp:821
#: ../src/LevelEditor.cpp:908 ../src/LevelEditor.cpp:1033
#: ../src/LevelEditor.cpp:1083 ../src/LevelEditor.cpp:1180
#: ../src/LevelEditor.cpp:1244 ../src/LevelEditor.cpp:2923
#: ../src/LevelEditSelect.cpp:244 ../src/LevelEditSelect.cpp:277
#: ../src/LevelEditSelect.cpp:317
msgid "OK"
msgstr "OK"
#: ../src/Functions.cpp:1244 ../src/Functions.cpp:1256
#: ../src/Functions.cpp:1266 ../src/LevelEditor.cpp:565
#: ../src/LevelEditor.cpp:699 ../src/LevelEditor.cpp:764
#: ../src/LevelEditor.cpp:827 ../src/LevelEditor.cpp:914
#: ../src/LevelEditor.cpp:1039 ../src/LevelEditor.cpp:1089
#: ../src/LevelEditor.cpp:1186 ../src/LevelEditor.cpp:1250
#: ../src/LevelEditor.cpp:2929 ../src/LevelEditSelect.cpp:248
#: ../src/LevelEditSelect.cpp:281 ../src/LevelEditSelect.cpp:321
#: ../src/OptionsMenu.cpp:289
msgid "Cancel"
msgstr "Abbrechen"
#: ../src/Functions.cpp:1248
msgid "Abort"
msgstr "Abbrechen"
#: ../src/Functions.cpp:1249 ../src/Functions.cpp:1265
msgid "Retry"
msgstr "Neu versuchen"
#: ../src/Functions.cpp:1250
msgid "Ignore"
msgstr "Ignorieren"
#: ../src/Functions.cpp:1254 ../src/Functions.cpp:1260
msgid "Yes"
msgstr "Ja"
#: ../src/Functions.cpp:1255 ../src/Functions.cpp:1261
msgid "No"
msgstr "Nein"
# TRANSLATORS: Please do not remove %s or %d from your translation:
# - %d means the level number in a levelpack
# - %s means the name of current level
#: ../src/Game.cpp:280 ../src/Game.cpp:1236
#, c-format
msgid "Level %d %s"
msgstr "Level %d %s"
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:915
#, c-format
msgid "Press %s key to save the game."
-msgstr "Drücke die %s-Taste, um das Spiel zu speichern."
+msgstr "Drücke %s, um das Spiel zu speichern."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:920
#, c-format
msgid "Press %s key to swap the position of player and shadow."
-msgstr ""
-"Drücke die %s-Taste, um die Position von dir und deinem Schatten zu "
-"vertauschen."
+msgstr "Drücke %s, um die Position von dir und deinem Schatten zu vertauschen."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:925
#, c-format
msgid "Press %s key to activate the switch."
-msgstr "Drücke die %s-Taste, um den Schalter zu aktivieren."
+msgstr "Drücke %s, um den Schalter zu aktivieren."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:930
#, c-format
msgid "Press %s key to teleport."
-msgstr "Drücke die %s-Taste zum Teleportieren."
+msgstr "Drücke %s zum Teleportieren."
# TRANSLATORS: Please do not remove %s from your translation:
# - first %s means currently configured key to restart game
# - Second %s means configured key to load from last save
#: ../src/Game.cpp:972
#, c-format
msgid "Press %s to restart current level or press %s to load the game."
msgstr ""
-"Drücke %s, um den aktellen Level neuzustarten oder drücke %s, um das Spiel "
+"Drücke %s, um den aktuellen Level neuzustarten oder drücke %s, um das Spiel "
"zu laden."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with currently configured key to restart game
#: ../src/Game.cpp:983
#, c-format
msgid "Press %s to restart current level."
msgstr "Drücke %s, um den aktuellen Level neuzustarten."
#: ../src/Game.cpp:996
msgid "Your shadow has died."
msgstr "Dein Schatten ist gestorben."
#: ../src/Game.cpp:1052
#, c-format
msgid "%d recording"
msgid_plural "%d recordings"
msgstr[0] "%d Aufzeichnung"
msgstr[1] "%d Aufzeichnungen"
#: ../src/Game.cpp:1224
msgid "You've finished:"
msgstr "Abgeschlossen:"
# TRANSLATORS: Please do not remove %-.2f from your translation:
# - %-.2f means time in seconds
# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1291
#, c-format
msgid "Time: %-.2fs"
msgstr "Zeit: %-.2fs"
# TRANSLATORS: Please do not remove %-.2f from your translation:
# - %-.2f means time in seconds
# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1300
#, c-format
msgid "Best time: %-.2fs"
msgstr "Bestzeit: %-.2fs"
#: ../src/Game.cpp:1311
#, c-format
msgid "Target time: %-.2fs"
msgstr "Zielzeit: %-.2fs"
# TRANSLATORS: Please do not remove %d from your translation:
# - %d means the number of recordings user has made
#: ../src/Game.cpp:1332
#, c-format
msgid "Recordings: %d"
msgstr "Aufzeichnungen: %d"
# TRANSLATORS: Please do not remove %d from your translation:
# - %d means the number of recordings user has made
#: ../src/Game.cpp:1340
#, c-format
msgid "Best recordings: %d"
msgstr "Beste Aufzeichnungen: %d"
#: ../src/Game.cpp:1350
#, c-format
msgid "Target recordings: %d"
msgstr "Ziel: %d Aufzeichnungen"
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with name of a prize medal (gold, silver or bronze)
#: ../src/Game.cpp:1363
#, c-format
msgid "You earned the %s medal"
msgstr "Du hast die %smedaillie gewonnen"
#: ../src/Game.cpp:1363
msgid "GOLD"
msgstr "Gold"
#: ../src/Game.cpp:1363
msgid "SILVER"
msgstr "Silber"
#: ../src/Game.cpp:1363
msgid "BRONZE"
msgstr "Bronze"
# TRANSLATORS: used as return to the level selector menu
#: ../src/Game.cpp:1390
msgid "Menu"
msgstr "Menü"
# TRANSLATORS: used as restart level
#: ../src/Game.cpp:1397 ../src/InputManager.cpp:47
msgid "Restart"
msgstr "Neu versuchen"
# TRANSLATORS: used as next level
#: ../src/Game.cpp:1404
msgid "Next"
msgstr "Nächster"
#: ../src/Game.cpp:1430
msgid "Game replay is done."
msgstr "Wiederholung ist fertig."
#: ../src/Game.cpp:1430
msgid "Game Replay"
msgstr "Wiederholung"
#: ../src/Game.cpp:1767 ../src/Game.cpp:1769
msgid "Congratulations"
msgstr "Glückwunsch"
#: ../src/Game.cpp:1769
msgid "You have finished the levelpack!"
msgstr "Du hast diese Levelsammlung abgeschlossen!"
#: ../src/InputManager.cpp:46
msgid "Up (in menu)"
msgstr "Rauf (im Menü)"
#: ../src/InputManager.cpp:46
msgid "Down (in menu)"
msgstr "Runter (im Menü)"
#: ../src/InputManager.cpp:46
msgid "Left"
msgstr "Links"
#: ../src/InputManager.cpp:46
msgid "Right"
msgstr "Rechts"
#: ../src/InputManager.cpp:46
msgid "Jump"
msgstr "Sprung"
#: ../src/InputManager.cpp:46
msgid "Action"
msgstr "Aktion"
#: ../src/InputManager.cpp:46
msgid "Space (Record)"
msgstr "Leer (Aufzeichnen)"
#: ../src/InputManager.cpp:46
msgid "Cancel recording"
msgstr "Aufzeichnung abbrechen"
#: ../src/InputManager.cpp:47
msgid "Escape"
msgstr "Escape"
#: ../src/InputManager.cpp:47
msgid "Tab (View shadow/Level prop.)"
msgstr "Tab (Schatten ansehen/Leveleinst.)"
#: ../src/InputManager.cpp:47
msgid "Save game (in editor)"
msgstr "Spiel speichern (im Editor)"
#: ../src/InputManager.cpp:47
msgid "Load game"
msgstr "Spiel laden"
#: ../src/InputManager.cpp:47
msgid "Swap (in editor)"
-msgstr "Vertauscher (im Editor)"
+msgstr "Vertauschen (im Editor)"
#: ../src/InputManager.cpp:48
msgid "Teleport (in editor)"
msgstr "Teleportieren (im Editor)"
#: ../src/InputManager.cpp:48
msgid "Suicide (in editor)"
msgstr "Selbstmord (im Editor)"
#: ../src/InputManager.cpp:48
msgid "Shift (in editor)"
msgstr "Laufband (im Editor)"
#: ../src/InputManager.cpp:48
msgid "Next block type (in Editor)"
msgstr "Nächster Blocktyp (im Editor)"
#: ../src/InputManager.cpp:49
msgid "Previous block type (in editor)"
msgstr "Vorheriger Blocktyp (im Editor)"
#: ../src/InputManager.cpp:49
msgid "Select (in menu)"
msgstr "Auswählen (im Menü)"
# TRANSLAOTRS: This is used when the name of the key code is not found.
#: ../src/InputManager.cpp:156
#, c-format
msgid "(Key %d)"
msgstr "(Taste %d)"
#: ../src/InputManager.cpp:163
#, c-format
msgid "Joystick axis %d %s"
msgstr "Joystick-Achse %d %s"
#: ../src/InputManager.cpp:166
#, c-format
msgid "Joystick button %d"
msgstr "Joystick-Button %d"
#: ../src/InputManager.cpp:171
#, c-format
msgid "Joystick hat %d left"
msgstr "Joystick-D-Pad %d links"
#: ../src/InputManager.cpp:174
#, c-format
msgid "Joystick hat %d right"
msgstr "Joystick-D-Pad %d rechts"
#: ../src/InputManager.cpp:177
#, c-format
msgid "Joystick hat %d up"
msgstr "Joystick-D-Pad %d oben"
#: ../src/InputManager.cpp:180
#, c-format
msgid "Joystick hat %d down"
msgstr "Joystick-D-Pad %d unten"
# TRANSLAOTRS: This is used when the JOYSTICK_HAT value is invalid.
#: ../src/InputManager.cpp:185
#, c-format
msgid "Joystick hat %d %d"
msgstr "Joystick-D-Pad %d %d"
#: ../src/InputManager.cpp:202
msgid "OR"
msgstr "ODER"
#: ../src/InputManager.cpp:416
msgid "Select an item and press a key to change it."
msgstr "Wähle einen Eintrag und drücke eine Taste, um ihn zu ändern."
#: ../src/InputManager.cpp:419
msgid "Press backspace to clear the selected item."
msgstr "Drücke die Rücktaste, um den gewählten Eintrag zu leeren."
#: ../src/LevelEditor.cpp:56
msgid "Block"
msgstr "Block"
#: ../src/LevelEditor.cpp:56
msgid "Player Start"
msgstr "Spieler-Startpunkt"
#: ../src/LevelEditor.cpp:56
msgid "Shadow Start"
msgstr "Schatten-Startpunkt"
#: ../src/LevelEditor.cpp:57
msgid "Exit"
msgstr "Ausgang"
#: ../src/LevelEditor.cpp:57
msgid "Shadow Block"
msgstr "Schattenblock"
#: ../src/LevelEditor.cpp:57
msgid "Spikes"
msgstr "Stacheln"
#: ../src/LevelEditor.cpp:58
msgid "Checkpoint"
msgstr "Kontrollpunkt"
#: ../src/LevelEditor.cpp:58 ../src/LevelEditSelect.cpp:312
msgid "Swap"
msgstr "Vertauscher"
#: ../src/LevelEditor.cpp:58
msgid "Fragile"
msgstr "Brüchig"
#: ../src/LevelEditor.cpp:59
msgid "Moving Block"
-msgstr "Bewegender Block"
+msgstr "Beweglicher Block"
#: ../src/LevelEditor.cpp:59
msgid "Moving Shadow Block"
-msgstr "Bewegender Schattenblock"
+msgstr "Beweglicher Schattenblock"
#: ../src/LevelEditor.cpp:59
msgid "Moving Spikes"
-msgstr "Bewegende Stacheln"
+msgstr "Bewegliche Stacheln"
#: ../src/LevelEditor.cpp:60
msgid "Teleporter"
msgstr "Teleporter"
#: ../src/LevelEditor.cpp:60
msgid "Button"
msgstr "Taster"
#: ../src/LevelEditor.cpp:60
msgid "Switch"
msgstr "Schalter"
#: ../src/LevelEditor.cpp:61
msgid "Conveyor Belt"
msgstr "Fließband"
#: ../src/LevelEditor.cpp:61
msgid "Shadow Conveyor Belt"
msgstr "Schattenfließband"
#: ../src/LevelEditor.cpp:61
msgid "Notification Block"
msgstr "Hinweisblock"
#: ../src/LevelEditor.cpp:61
msgid "Collectable"
-msgstr "Einsammelbar"
+msgstr "Sammelobjekt"
#: ../src/LevelEditor.cpp:61
msgid "Pushable"
-msgstr "Drückbar"
+msgstr "Schiebeobjekt"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:310
msgid "Select"
msgstr "Auswählen"
#: ../src/LevelEditor.cpp:65
msgid "Add"
msgstr "Hinzufügen"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:311
msgid "Delete"
msgstr "Löschen"
#: ../src/LevelEditor.cpp:65 ../src/LevelPlaySelect.cpp:66
#: ../src/TitleMenu.cpp:43
msgid "Play"
msgstr "Spielen"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:2852
msgid "Level settings"
msgstr "Leveleinstellungen"
#: ../src/LevelEditor.cpp:65
msgid "Save level"
msgstr "Level speichern"
#: ../src/LevelEditor.cpp:65
msgid "Back to menu"
msgstr "Zurück zum Menü"
#: ../src/LevelEditor.cpp:65
msgid "Configure"
msgstr "Konfigurieren"
#: ../src/LevelEditor.cpp:84
#, c-format
msgid "%s (Scenery)"
msgstr "%s (Szenerie)"
#: ../src/LevelEditor.cpp:267
msgid "Toggle"
msgstr "Umschalten"
#: ../src/LevelEditor.cpp:270
msgid "Complete"
msgstr "ganz"
#: ../src/LevelEditor.cpp:271
msgid "One step"
msgstr "1. Stufe"
#: ../src/LevelEditor.cpp:272
msgid "Two steps"
msgstr "2. Stufe"
#: ../src/LevelEditor.cpp:273
msgid "Gone"
msgstr "zerbrochen"
#: ../src/LevelEditor.cpp:291
msgid "Negative infinity"
msgstr "Minus unendlich"
#: ../src/LevelEditor.cpp:293
msgid "Zero"
msgstr "Null"
#: ../src/LevelEditor.cpp:295
msgid "Level size"
msgstr "Levelgröße"
#: ../src/LevelEditor.cpp:297
msgid "Positive infinity"
msgstr "Plus unendlich"
#: ../src/LevelEditor.cpp:299
msgid "Default"
msgstr "Standard"
#: ../src/LevelEditor.cpp:308
msgid "Deselect"
msgstr "Abwählen"
#: ../src/LevelEditor.cpp:318 ../src/LevelEditor.cpp:1136
#, c-format
msgid "Horizontal repeat start: %s"
msgstr "Horizontale Wiederholung, Start: %s"
#: ../src/LevelEditor.cpp:320 ../src/LevelEditor.cpp:1137
#, c-format
msgid "Horizontal repeat end: %s"
msgstr "Horizontale Wiederholung, Ende: %s"
#: ../src/LevelEditor.cpp:322 ../src/LevelEditor.cpp:1138
#, c-format
msgid "Vertical repeat start: %s"
msgstr "Vertikale Wiederholung, Start: %s"
#: ../src/LevelEditor.cpp:324 ../src/LevelEditor.cpp:1139
#, c-format
msgid "Vertical repeat end: %s"
msgstr "Vertikale Wiederholung, Ende: %s"
#: ../src/LevelEditor.cpp:329 ../src/LevelEditor.cpp:1150
msgid "Custom scenery"
msgstr "Benutzerdefinierte Szenerie"
#: ../src/LevelEditor.cpp:335 ../src/LevelEditor.cpp:600
#: ../src/LevelEditor.cpp:602
msgid "Visible"
msgstr "Sichtbar"
#: ../src/LevelEditor.cpp:344
msgid "Link"
msgstr "Verbindung"
#: ../src/LevelEditor.cpp:345
msgid "Remove Links"
msgstr "Verbindungen entfernen"
#: ../src/LevelEditor.cpp:349 ../src/LevelEditor.cpp:624
#: ../src/LevelEditor.cpp:626
msgid "Automatic"
msgstr "Automatisch"
#: ../src/LevelEditor.cpp:359 ../src/LevelEditor.cpp:649
#, c-format
msgid "Behavior: %s"
msgstr "Verhalten: %s"
#: ../src/LevelEditor.cpp:362
msgid "Path"
msgstr "Pfad"
#: ../src/LevelEditor.cpp:363
msgid "Remove Path"
msgstr "Pfad entfernen"
#: ../src/LevelEditor.cpp:365 ../src/LevelEditor.cpp:371
#: ../src/LevelEditor.cpp:587 ../src/LevelEditor.cpp:589
msgid "Activated"
msgstr "Aktiviert"
#: ../src/LevelEditor.cpp:366 ../src/LevelEditor.cpp:612
#: ../src/LevelEditor.cpp:614
msgid "Looping"
msgstr "Schleife"
#: ../src/LevelEditor.cpp:372 ../src/LevelEditor.cpp:3526
msgid "Speed"
msgstr "Geschwindigkeit"
#: ../src/LevelEditor.cpp:378 ../src/LevelEditor.cpp:668
#, c-format
msgid "State: %s"
msgstr "Zustand: %s"
#: ../src/LevelEditor.cpp:382 ../src/LevelEditor.cpp:3511
msgid "Message"
msgstr "Nachricht"
#: ../src/LevelEditor.cpp:384 ../src/LevelEditor.cpp:1202
#: ../src/LevelEditor.cpp:3825
msgid "Appearance"
msgstr "Erscheinungsbild"
#: ../src/LevelEditor.cpp:389 ../src/LevelEditor.cpp:431
#: ../src/LevelEditor.cpp:715
msgid "Scripting"
msgstr "Skript"
#: ../src/LevelEditor.cpp:402 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Background layer: %s"
msgstr "Hintergrundebene: %s"
#: ../src/LevelEditor.cpp:409 ../src/LevelEditor.cpp:866
#: ../src/LevelEditor.cpp:884
msgid "Blocks layer"
msgstr "Blockebene"
#: ../src/LevelEditor.cpp:417 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Foreground layer: %s"
msgstr "Vordergrundebene: %s"
#: ../src/LevelEditor.cpp:423
msgid "Add new layer"
msgstr "Neue Ebene hinzufügen"
#: ../src/LevelEditor.cpp:424
msgid "Delete selected layer"
msgstr "Gewählte Ebene löschen"
#: ../src/LevelEditor.cpp:425
msgid "Configure selected layer"
msgstr "Gewählte Ebene konfigurieren"
#: ../src/LevelEditor.cpp:426
msgid "Move selected object to layer"
-msgstr "Gewähltes Objet zur Ebene verschieben"
+msgstr "Gewähltes Objekt zur Ebene verschieben"
#: ../src/LevelEditor.cpp:430 ../src/OptionsMenu.cpp:55
msgid "Settings"
msgstr "Einstellungen"
#: ../src/LevelEditor.cpp:463
msgid ""
"NOTE: the layers are sorted by name alphabetically.\n"
"The layer is background layer if its name is < 'f'\n"
"by dictionary order, otherwise it's foreground layer."
msgstr ""
"ANMERKUNG: Die Ebenen werden alphabetisch\n"
"nach Namen sortiert. Die Ebene ist eine Hinter-\n"
"grundebene falls ihr Name < „f“ nach Wörterbuch-\n"
"sortierung ist, ansonsten ist sie eine Vordegrund-\n"
"ebene."
#: ../src/LevelEditor.cpp:539
msgid "Notification block"
msgstr "Hinweisblock"
#: ../src/LevelEditor.cpp:545
msgid "Enter message here:"
msgstr "Gib die Nachricht hier ein:"
#: ../src/LevelEditor.cpp:646
msgid "Behavior"
msgstr "Verhalten"
#: ../src/LevelEditor.cpp:665
msgid "State"
msgstr "Zustand"
#: ../src/LevelEditor.cpp:673
msgid "Conveyor belt speed"
msgstr "Fließbandgeschwindigkeit"
#: ../src/LevelEditor.cpp:679
msgid "Enter speed here:"
msgstr "Geschwindigkeit:"
#: ../src/LevelEditor.cpp:690
msgid "NOTE: 1 Speed = 0.08 block/s"
-msgstr "ANMERKUNG: 1 Geschw.-einheit = 0,08 Blöcke/s"
+msgstr "ANMERKUNG: 1 Einheit = 0,08 Blöcke/s"
#: ../src/LevelEditor.cpp:721
msgid "Id:"
msgstr "ID:"
#: ../src/LevelEditor.cpp:787
msgid "Level Scripting"
msgstr "Level-Skript"
#: ../src/LevelEditor.cpp:892
msgid "Add layer"
msgstr "Ebene hinzufügen"
#: ../src/LevelEditor.cpp:898
msgid "Enter the layer name:"
msgstr "Ebenennamen eingeben:"
#: ../src/LevelEditor.cpp:943
#, c-format
msgid "Are you sure you want to delete layer '%s'?"
msgstr "Bist du sicher, dass du die Ebene „%s“ löschen willst?"
#: ../src/LevelEditor.cpp:944
msgid "Delete layer"
msgstr "Ebene löschen"
#: ../src/LevelEditor.cpp:968
msgid "Layer settings"
msgstr "Ebeneneinstellungen"
#: ../src/LevelEditor.cpp:974
msgid "Layer name:"
msgstr "Ebenenname:"
#: ../src/LevelEditor.cpp:989
msgid "Layer moving speed (1 speed = 0.8 block/s):"
-msgstr "Ebenenbewegungsgeschwindigkeit (1 Geschw.-einheit = 0.8 Blöcke/s):"
+msgstr "Ebenenbewegungsgeschwindigkeit (1 Einheit = 0,8 Blöcke/s):"
#: ../src/LevelEditor.cpp:1010
msgid "Speed of following camera:"
msgstr "Geschwindigkeit der Verfolgungskamera:"
#: ../src/LevelEditor.cpp:1062
msgid "Move to layer"
msgstr "Zur Ebene verschieben"
#: ../src/LevelEditor.cpp:1068
msgid "Enter the layer name (create new layer if necessary):"
msgstr "Ebenennamen eingeben (erstellt neue Ebene, falls nötig):"
#: ../src/LevelEditor.cpp:1132
msgid "Repeat mode"
msgstr "Wiederholungsmodus"
#: ../src/LevelEditor.cpp:1156
msgid "Custom scenery:"
msgstr "Benutzerdefinierte Szenerie:"
#: ../src/LevelEditor.cpp:1219
msgid "(Use the default appearance for this block)"
-msgstr "(Standarderscheinungsbild für diesen Block benutzen)"
+msgstr "(Standard für diesen Block benutzen)"
# TRANSLATORS: Block name
# TRANSLATORS: Context: Resize/Move ...
# TRANSLATORS: Context: Add/Remove ...
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1465 ../src/LevelEditor.cpp:1707
#: ../src/LevelEditor.cpp:1723 ../src/LevelEditor.cpp:1772
#: ../src/LevelEditor.cpp:4400
msgid "Custom scenery block"
msgstr "Benutzerdefinierter Szenierieblock"
#: ../src/LevelEditor.cpp:1673
msgid "Toolbox"
msgstr "Werkzeugkasten"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Resize %s"
msgstr "%s skalieren"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Move %s"
msgstr "%s verschieben"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1713
#, c-format
msgid "Move %d object"
msgid_plural "Move %d objects"
msgstr[0] "%d Objekt verschieben"
msgstr[1] "%d Objekte verschieben"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Add %s"
msgstr "%s hinzufügen"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Remove %s"
msgstr "%s entfernen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1729
#, c-format
msgid "Add %d object"
msgid_plural "Add %d objects"
msgstr[0] "%d Objekt hinzufügen"
msgstr[1] "%d Objekte hinzufügen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1731
#, c-format
msgid "Remove %d object"
msgid_plural "Remove %d objects"
msgstr[0] "%d Objekt entfernen"
msgstr[1] "%d Objekte entfernen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1739
#, c-format
msgid "Add path to %s"
msgstr "Pfad zu %s hinzufügen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1741
#, c-format
msgid "Remove a path point from %s"
msgstr "Einen Pfadpunkt von %s entfernen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1747
#, c-format
msgid "Remove all paths from %s"
msgstr "Alle Pfade von %s entfernen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1753
#, c-format
msgid "Add link from %s to %s"
msgstr "Verbindung von %s nach %s hinzufügen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1759
#, c-format
msgid "Remove all links from %s"
msgstr "Alle Verbindungen von %s entfernen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1766
msgid "Modify the %2 property of %1"
msgstr "Die Eigenschaft %2 von %1 ändern"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1818
#, c-format
msgid "Edit the script of %s"
msgstr "Skript von %s bearbeiten"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1821
msgid "Edit the script of level"
msgstr "Skript des Levels ändern"
#: ../src/LevelEditor.cpp:2146 ../src/LevelEditor.cpp:2226
msgid "The level has unsaved changes."
msgstr "Der Level hat nicht gespeicherte Änderungen."
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Are you sure you want to quit?"
msgstr "Bist du sicher, dass du gehen willst?"
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Quit prompt"
msgstr "Beenden-Frage"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
#, c-format
msgid "Level \"%s\" saved"
msgstr "Level „%s“ gespeichert"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
msgid "Saved"
msgstr "Gespeichert"
#: ../src/LevelEditor.cpp:2859 ../src/LevelEditSelect.cpp:208
msgid "Name:"
msgstr "Name:"
#: ../src/LevelEditor.cpp:2866
msgid "Theme:"
msgstr "Thema:"
#: ../src/LevelEditor.cpp:2873
msgid "Examples: %DATA%/themes/classic"
msgstr "Beispiele: %DATA%/themes/classic"
#: ../src/LevelEditor.cpp:2875
msgid "or %USER%/themes/Orange"
msgstr "oder %USER%/themes/Orange"
#: ../src/LevelEditor.cpp:2878
msgid "Music:"
msgstr "Musik:"
#: ../src/LevelEditor.cpp:2887
msgid "Target time (s):"
msgstr "Zielzeit (s):"
#: ../src/LevelEditor.cpp:2903
msgid "Target recordings:"
msgstr "Zielaufzeichnungen:"
#: ../src/LevelEditor.cpp:2919
msgid "Restart level editor is required"
msgstr "Neustart des Leveleditors erforderlich"
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3718
#: ../src/LevelEditor.cpp:3739
msgid "Please enter a layer name."
msgstr "Bitte Ebenennamen eingeben."
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3683
#: ../src/LevelEditor.cpp:3718 ../src/LevelEditor.cpp:3722
#: ../src/LevelEditor.cpp:3739 ../src/LevelEditor.cpp:3743
#: ../src/LevelEditSelect.cpp:644 ../src/LevelEditSelect.cpp:683
#: ../src/LevelEditSelect.cpp:688 ../src/LevelEditSelect.cpp:693
#: ../src/LevelEditSelect.cpp:698 ../src/LevelEditSelect.cpp:796
msgid "Error"
msgstr "Fehler"
#: ../src/LevelEditor.cpp:3683 ../src/LevelEditor.cpp:3722
#, c-format
msgid "The layer '%s' already exists."
msgstr "Die Ebene „%s“ existiert bereits."
#: ../src/LevelEditor.cpp:3743
msgid "Source and destination layers are the same."
msgstr "Quell- und Zielebenen sind identisch."
#: ../src/LevelEditor.cpp:3760
msgid "Scenery"
msgstr "Szenerie"
#: ../src/LevelEditor.cpp:4190 ../src/LevelEditor.cpp:4218
#, c-format
msgid "Speed: %d = %0.2f block/s"
msgstr "Geschwindigkeit: %d = %0.2f Blöcke/s"
#: ../src/LevelEditor.cpp:4203
msgid "Stop at this point"
msgstr "An diesem Punkt anhalten"
#: ../src/LevelEditor.cpp:4208
#, c-format
msgid "Pause: %d = %0.3fs"
msgstr "Pause: %d = %0.3f s"
#: ../src/LevelEditSelect.cpp:41 ../src/TitleMenu.cpp:45
msgid "Map Editor"
msgstr "Karteneditor"
#: ../src/LevelEditSelect.cpp:66
msgid "New Levelpack"
msgstr "Neue Levelsammlung"
#: ../src/LevelEditSelect.cpp:71
msgid "Pack Properties"
msgstr "Sammlungseinstellungen"
#: ../src/LevelEditSelect.cpp:76
msgid "Remove Pack"
msgstr "Sammlung löschen"
#: ../src/LevelEditSelect.cpp:81
msgid "Move Map"
msgstr "Karte verschieben"
#: ../src/LevelEditSelect.cpp:89
msgid "Remove Map"
msgstr "Karte löschen"
#: ../src/LevelEditSelect.cpp:94
msgid "Edit Map"
msgstr "Karte bearbeiten"
#: ../src/LevelEditSelect.cpp:205
msgid "Properties"
msgstr "Einstellungen"
#: ../src/LevelEditSelect.cpp:217
msgid "Description:"
msgstr "Beschreibung:"
#: ../src/LevelEditSelect.cpp:226
msgid "Congratulation text:"
msgstr "Glückwunschtext:"
#: ../src/LevelEditSelect.cpp:235
msgid "Music list:"
msgstr "Musikliste:"
#: ../src/LevelEditSelect.cpp:265 ../src/LevelEditSelect.cpp:485
msgid "Add level"
msgstr "Level hinzufügen"
#: ../src/LevelEditSelect.cpp:268
msgid "File name:"
msgstr "Dateiname:"
#: ../src/LevelEditSelect.cpp:293
msgid "Move level"
msgstr "Level verschieben"
#: ../src/LevelEditSelect.cpp:296
msgid "Level: "
msgstr "Level: "
#: ../src/LevelEditSelect.cpp:310
msgid "Before"
msgstr "Davor"
#: ../src/LevelEditSelect.cpp:311
msgid "After"
msgstr "Dahinter"
#: ../src/LevelEditSelect.cpp:368 ../src/LevelPlaySelect.cpp:124
msgid "Individual levels which are not contained in any level packs"
-msgstr "Einzelne Levels, die nicht Teil eines Levelsammlung sind"
+msgstr "Einzelne Levels, die nicht Teil einer Levelsammlung sind"
#: ../src/LevelEditSelect.cpp:577
#, c-format
msgid "Are you sure remove the level pack '%s'?"
msgstr "Willst du die Levelsammlung „%s“ wirklich löschen?"
#: ../src/LevelEditSelect.cpp:577 ../src/LevelEditSelect.cpp:607
msgid "Remove prompt"
msgstr "Löschabfrage"
#: ../src/LevelEditSelect.cpp:607
#, c-format
msgid "Are you sure remove the map '%s'?"
msgstr "Willst du die Karte „%s“ wirklich löschen?"
#: ../src/LevelEditSelect.cpp:644
msgid "Levelpack name cannot be empty."
msgstr "Levelsammlungsname kann nicht leer sein."
#: ../src/LevelEditSelect.cpp:683
#, c-format
msgid "The levelpack directory '%s' already exists!"
-msgstr "Die Ebene „%s“ existiert bereits!"
+msgstr "Das Levelsammlungsverzeichnis „%s“ existiert bereits!"
#: ../src/LevelEditSelect.cpp:688
#, c-format
msgid "Unable to create levelpack directory '%s'!"
msgstr "Konnte Levelsammlungsverzeichnis „%s“ nicht entfernen!"
#: ../src/LevelEditSelect.cpp:693
#, c-format
msgid "The levelpack file '%s' already exists!"
msgstr "Die Levelsammlungsdatei %s existiert bereits!"
#: ../src/LevelEditSelect.cpp:698
#, c-format
msgid "Unable to create levelpack file '%s'!"
msgstr "Konnte Levelsammlungsdatei „%s“ nicht entfernen!"
#: ../src/LevelEditSelect.cpp:758
msgid "No file name given for the new level."
msgstr "Es wurde kein Dateiname für den Level angegeben."
#: ../src/LevelEditSelect.cpp:758
msgid "Missing file name"
msgstr "Dateiname fehlt"
#: ../src/LevelEditSelect.cpp:796
#, c-format
msgid "The file %s already exists."
msgstr "Die Datei %s existiert bereits."
#: ../src/LevelEditSelect.cpp:849
msgid "The entered level number isn't valid!"
msgstr "Die eingegebene Levelnummer ist ungültig!"
#: ../src/LevelEditSelect.cpp:849
msgid "Illegal number"
msgstr "Ungültige Nummer"
#: ../src/LevelInfoRender.cpp:19
msgid "Choose a level"
msgstr "Wähle einen Level"
#: ../src/LevelInfoRender.cpp:20
msgid "Time:"
msgstr "Zeit:"
#: ../src/LevelInfoRender.cpp:21 ../src/StatisticsScreen.cpp:259
msgid "Recordings:"
msgstr "Aufzeichnungen:"
#: ../src/LevelPackManager.cpp:124
msgid "Custom Levels"
msgstr "Benutzerdefinierte Levels"
#: ../src/LevelPlaySelect.cpp:41
msgid "Select Level"
msgstr "Levelauswahl"
# TRANSLATORS: Used for button which clear any level progress like unlocked
# levels and highscores.
#: ../src/OptionsMenu.cpp:66
msgid "Clear Progress"
msgstr "Fortschritt löschen"
#: ../src/OptionsMenu.cpp:109
msgid "General"
msgstr "Allgemein"
#: ../src/OptionsMenu.cpp:110
msgid "Controls"
msgstr "Steuerung"
#: ../src/OptionsMenu.cpp:121
msgid "Music"
msgstr "Musik"
#: ../src/OptionsMenu.cpp:129
msgid "Sound"
msgstr "Toneffekte"
#: ../src/OptionsMenu.cpp:137
msgid "Resolution"
msgstr "Auflösung"
#: ../src/OptionsMenu.cpp:177
msgid "Language"
msgstr "Sprache"
# TRANSLATORS: as detect user's language automatically
#: ../src/OptionsMenu.cpp:185
msgid "Auto-Detect"
msgstr "automatisch erkennen"
#: ../src/OptionsMenu.cpp:209
msgid "Theme"
msgstr "Thema"
#: ../src/OptionsMenu.cpp:247
msgid "Internet proxy"
msgstr "Internet-Proxy"
#: ../src/OptionsMenu.cpp:256
msgid "Fullscreen"
msgstr "Vollbild"
#: ../src/OptionsMenu.cpp:261
msgid "Quick record"
msgstr "Schnellaufzeichnung"
#: ../src/OptionsMenu.cpp:266
msgid "Internet"
msgstr "Internet"
#: ../src/OptionsMenu.cpp:271
msgid "Fade transition"
-msgstr "Übergang"
+msgstr "Überblenden"
#: ../src/OptionsMenu.cpp:294
msgid "Save Changes"
msgstr "Änderungen speichern"
#: ../src/OptionsMenu.cpp:513
msgid "Do you really want to reset level progress?"
msgstr "Willst du wirklich den Levelfortschritt zurücksetzen?"
#: ../src/OptionsMenu.cpp:513
msgid "Warning"
msgstr "Warnung"
#: ../src/StatisticsManager.cpp:386
msgid "New achievement:"
msgstr "Neue Errungenschaft:"
#: ../src/StatisticsManager.cpp:394
#, c-format
msgid "Achieved on %s"
msgstr "Errungen am %s"
#: ../src/StatisticsManager.cpp:400
msgid "Unknown achievement"
msgstr "Unbekannte Errungenschaft"
#: ../src/StatisticsManager.cpp:406
#, c-format
msgid "Achieved %1.0f%%"
msgstr "%1.0f%% errungen"
#: ../src/StatisticsManager.cpp:410
msgid "Not achieved"
msgstr "Nicht errungen"
#: ../src/StatisticsScreen.cpp:57 ../src/TitleMenu.cpp:55
msgid "Achievements and Statistics"
msgstr "Errungenschaften und Statistik"
#: ../src/StatisticsScreen.cpp:166
msgid "Achievements"
msgstr "Errungenschaften"
#: ../src/StatisticsScreen.cpp:167
msgid "Statistics"
msgstr "Statistik"
#: ../src/StatisticsScreen.cpp:234
msgid "Total"
msgstr "Gesamt"
#: ../src/StatisticsScreen.cpp:246
msgid "Traveling distance (m)"
msgstr "Reisestrecke (m)"
#: ../src/StatisticsScreen.cpp:247
msgid "Jump times"
msgstr "Anzahl Sprünge"
#: ../src/StatisticsScreen.cpp:248
msgid "Die times"
msgstr "Anzahl Tode"
#: ../src/StatisticsScreen.cpp:249
msgid "Squashed times"
msgstr "Anzahl Zerquetschte"
#: ../src/StatisticsScreen.cpp:260
msgid "Switch pulled times:"
msgstr "Anzahl umgelegte Schalter:"
#: ../src/StatisticsScreen.cpp:261
msgid "Swap times:"
msgstr "Anzahl Vertauscher:"
#: ../src/StatisticsScreen.cpp:262
msgid "Save times:"
msgstr "Anzahl Speicherungen:"
#: ../src/StatisticsScreen.cpp:263
msgid "Load times:"
msgstr "Anzahl Ladevorgänge:"
#: ../src/StatisticsScreen.cpp:268
msgid "Completed levels:"
msgstr "Abgeschlossene Levels:"
#: ../src/StatisticsScreen.cpp:306
msgid "In-game time:"
msgstr "Spielzeit:"
#: ../src/StatisticsScreen.cpp:308
msgid "Level editing time:"
msgstr "Leveleditorzeit:"
#: ../src/StatisticsScreen.cpp:310
msgid "Created levels:"
msgstr "Erstellte Levels:"
#: ../src/TitleMenu.cpp:44
msgid "Options"
msgstr "Optionen"
#: ../src/TitleMenu.cpp:47
msgid "Quit"
msgstr "Beenden"
#: ../src/TitleMenu.cpp:131
msgid "Enable internet in order to install addons."
msgstr "Internetzugang erlauben, um Add-ons zu installieren."
#: ../src/TitleMenu.cpp:131
msgid "Internet disabled"
msgstr "Internetzugang nicht erlaubt"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Return"
msgstr "Eingabe"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Escape"
msgstr "Escape"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Backspace"
msgstr "Rücktaste"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Tab"
msgstr "Tab"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Space"
msgstr "Leertaste"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "CapsLock"
msgstr "Feststelltaste"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PrintScreen"
msgstr "Druck"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "ScrollLock"
msgstr "Rollen"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Pause"
msgstr "Pause"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Insert"
msgstr "Einfügen"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Home"
msgstr "Pos1"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageUp"
msgstr "Bild auf"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Delete"
msgstr "Entfernen"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "End"
msgstr "Ende"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageDown"
msgstr "Bild ab"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right"
msgstr "Rechts"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left"
msgstr "Links"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Down"
msgstr "Unten"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Up"
msgstr "Oben"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Numlock"
msgstr "Numlock"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "SysReq"
msgstr "S-Abf"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Ctrl"
msgstr "Strg links"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Shift"
msgstr "Umschalt links"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Alt"
msgstr "Alt links"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left GUI"
msgstr "GUI links"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Ctrl"
msgstr "Strg rechts"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Shift"
msgstr "Umschalt rechts"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Alt"
msgstr "Alt rechts"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right GUI"
msgstr "GUI rechts"
diff --git a/data/locale/fr.po b/data/locale/fr.po
index ade34f9..89040c8 100644
--- a/data/locale/fr.po
+++ b/data/locale/fr.po
@@ -1,2100 +1,2101 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the meandmyshadow package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
-#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: meandmyshadow 0.5svn\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-09 16:18+0800\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Jz Pan <acme.pjz@gmail.com>, 2018\n"
-"Language-Team: French (https://www.transifex.com/acmepjz/teams/88148/fr/)\n"
+"PO-Revision-Date: 2018-09-24 17:06+0000\n"
+"Last-Translator: Poussinou <francoisvincentmail-game@yahoo.fr>\n"
+"Language-Team: French <https://hosted.weblate.org/projects/me-and-my-shadow/"
+"translations/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 3.2-dev\n"
#: ../src/AchievementList.cpp:43
msgid "Newbie"
msgstr "Bizuth"
#: ../src/AchievementList.cpp:43
msgid "Complete a level."
msgstr "Compléter un niveau."
#: ../src/AchievementList.cpp:44
msgid "Experienced player"
msgstr "Joueur expérimenté"
#: ../src/AchievementList.cpp:44
msgid "Complete 50 levels."
msgstr "Compléter 50 niveaux."
#: ../src/AchievementList.cpp:45
msgid "Good job!"
msgstr "Bien joué !"
#: ../src/AchievementList.cpp:45
msgid "Receive a gold medal."
msgstr "Obtenir une médaille d'or."
#: ../src/AchievementList.cpp:46
msgid "Expert"
msgstr "Expert"
#: ../src/AchievementList.cpp:46
msgid "Earn 50 gold medal."
msgstr "Obtenir 50 médailles d'or."
#: ../src/AchievementList.cpp:48
msgid "Graduate"
msgstr "Diplômé"
#: ../src/AchievementList.cpp:48
msgid "Complete the tutorial level pack."
msgstr "Compléter tous les niveaux du tutoriel."
#: ../src/AchievementList.cpp:49
msgid "Outstanding graduate"
msgstr "Diplômé avec mention"
#: ../src/AchievementList.cpp:49
msgid "Complete the tutorial level pack with gold for all levels."
msgstr ""
"Compléter les niveaux du tutoriel avec la médaille d'or à chaque niveau."
#: ../src/AchievementList.cpp:51
msgid "Hooked"
msgstr "Accro"
#: ../src/AchievementList.cpp:51
msgid "Play Me and My Shadow for more than 2 hours."
msgstr "Jouer à Me and My Shadow pendant plus de 2 heures."
#: ../src/AchievementList.cpp:52
msgid "Loyal fan of Me and My Shadow"
msgstr "Fan fidèle de Me and My Shadow"
#: ../src/AchievementList.cpp:52
msgid "Play Me and My Shadow for more than 24 hours."
msgstr "Jouer à Me and My Shadow pendant plus de 24 heures."
#: ../src/AchievementList.cpp:54
msgid "Constructor"
msgstr "Bâtisseur"
#: ../src/AchievementList.cpp:54
msgid "Use the level editor for more than 2 hours."
msgstr "Utiliser l'éditeur de niveau pendant plus de 2 heures."
#: ../src/AchievementList.cpp:55
msgid "The creator"
msgstr "Le créateur"
#: ../src/AchievementList.cpp:55
msgid "Use the level editor for more than 24 hours."
msgstr "Utiliser l'éditeur de niveau pendant plus de 24 heures."
#: ../src/AchievementList.cpp:57
msgid "Look, cute level!"
msgstr "Regardez, un joli niveau !"
#: ../src/AchievementList.cpp:57
msgid "Create a level for the first time."
msgstr "Créer un niveau pour la première fois."
#: ../src/AchievementList.cpp:58
msgid "The level museum"
msgstr "Le musée des niveaux"
#: ../src/AchievementList.cpp:58
msgid "Create 50 levels."
msgstr "Créer 50 niveaux."
#: ../src/AchievementList.cpp:60
msgid "Hello, World!"
-msgstr ""
+msgstr "Bonjour tout le monde !"
#: ../src/AchievementList.cpp:60
msgid "Write a script for the first time."
-msgstr ""
+msgstr "Écrivez un script pour la première fois."
#: ../src/AchievementList.cpp:62
msgid "Frog"
msgstr "Grenouille"
#: ../src/AchievementList.cpp:62
msgid "Jump 1000 times."
msgstr "Sauter 1000 fois."
#: ../src/AchievementList.cpp:64
msgid "Wanderer"
msgstr "Vagabond"
#: ../src/AchievementList.cpp:64
msgid "Travel 100 meters."
msgstr "Parcourir 100 mètres."
#: ../src/AchievementList.cpp:65
msgid "Runner"
msgstr "Coureur"
#: ../src/AchievementList.cpp:65
msgid "Travel 1 kilometer."
msgstr "Parcourir 1 kilomètre."
#: ../src/AchievementList.cpp:66
msgid "Long distance runner"
msgstr "Coureur de fond"
#: ../src/AchievementList.cpp:66
msgid "Travel 10 kilometers."
msgstr "Parcourir 10 kilomètres."
#: ../src/AchievementList.cpp:67
msgid "Marathon runner"
msgstr "Coureur de marathon"
#: ../src/AchievementList.cpp:67
msgid "Travel 42,195 meters."
msgstr "Parcourir 42 195 mètres."
#: ../src/AchievementList.cpp:69
msgid "Be careful!"
msgstr "Sois prudent !"
#: ../src/AchievementList.cpp:69
msgid "Die for the first time."
msgstr "Mourir pour la première fois."
#: ../src/AchievementList.cpp:70
msgid "It doesn't matter..."
msgstr "C'est pas grave..."
#: ../src/AchievementList.cpp:70
msgid "Die 50 times."
msgstr "Mourir 50 fois."
#: ../src/AchievementList.cpp:71
msgid "Expert of trial and error"
msgstr "Expert de l'approche par tâtonnement"
#: ../src/AchievementList.cpp:71
msgid "Die 1000 times."
msgstr "Mourir 1000 fois."
#: ../src/AchievementList.cpp:73
msgid "Keep an eye for moving blocks!"
msgstr "Fais attention aux blocs mobiles !"
#: ../src/AchievementList.cpp:73
msgid "Get squashed for the first time."
msgstr "Se faire écraser pour la première fois."
#: ../src/AchievementList.cpp:74
msgid "Potato masher"
msgstr "Presse-purée"
#: ../src/AchievementList.cpp:74
msgid "Get squashed 50 times."
msgstr "Se faire écraser 50 fois."
#: ../src/AchievementList.cpp:76
msgid "Double kill"
msgstr "D'une pierre deux coups"
#: ../src/AchievementList.cpp:76
msgid "Get both the player and the shadow dead."
msgstr "Faire mourir à la fois le joueur et l'ombre."
#: ../src/AchievementList.cpp:78
msgid "Bad luck"
msgstr "Pas de bol"
#: ../src/AchievementList.cpp:78
msgid "Die 5 times in under 5 seconds."
msgstr "Mourir 5 fois en moins de 5 secondes."
#: ../src/AchievementList.cpp:79
msgid "This level is too dangerous"
msgstr "Ce niveau est trop dangereux"
#: ../src/AchievementList.cpp:79
msgid "Die 10 times in under 5 seconds."
msgstr "Mourir 10 fois en moins de 5 secondes."
#: ../src/AchievementList.cpp:81
msgid "You forgot your friend"
msgstr "Tu as oublié ton copain"
#: ../src/AchievementList.cpp:81
msgid "Finish the level with the player or the shadow dead."
msgstr "Finir le niveau alors que le joueur ou l'ombre sont morts."
#: ../src/AchievementList.cpp:82
msgid "Just in time"
msgstr "Juste à temps"
#: ../src/AchievementList.cpp:82
msgid "Reach the exit with the player and the shadow simultaneously."
msgstr "Atteindre la sortie avec le joueur et l'ombre au même moment."
#: ../src/AchievementList.cpp:84
msgid "Recorder"
msgstr "Enregistreur"
#: ../src/AchievementList.cpp:84
msgid "Record 100 times."
msgstr "Enregistrer 100 fois."
#: ../src/AchievementList.cpp:85
msgid "Shadowmaster"
msgstr "Maître de l'ombre"
#: ../src/AchievementList.cpp:85
msgid "Record 1000 times."
msgstr "Enregistrer 1000 fois."
#: ../src/AchievementList.cpp:87
msgid "Switch puller"
msgstr "Machiniste"
#: ../src/AchievementList.cpp:87
msgid "Pull the switch 100 times."
msgstr "Commuter l'interrupteur 100 fois."
#: ../src/AchievementList.cpp:88
msgid "The switch is broken!"
msgstr "L'interrupteur est cassé !"
#: ../src/AchievementList.cpp:88
msgid "Pull the switch 1000 times."
msgstr "Commuter l'interrupteur 1000 fois."
#: ../src/AchievementList.cpp:90
msgid "Swapper"
msgstr "Échangeur"
#: ../src/AchievementList.cpp:90
msgid "Swap 100 times."
msgstr "Échanger 100 fois de place."
#: ../src/AchievementList.cpp:91
msgid "Player to shadow to player to shadow..."
msgstr "Du joueur à l'ombre au joueur à l'ombre..."
#: ../src/AchievementList.cpp:91
msgid "Swap 1000 times."
msgstr "Échanger 1000 fois de place."
#: ../src/AchievementList.cpp:93
msgid "Play it save"
msgstr "Pas de prise de risque"
#: ../src/AchievementList.cpp:93
msgid "Save 1000 times."
msgstr "Sauvegarder 1000 fois."
#: ../src/AchievementList.cpp:94
msgid "This game is too hard"
msgstr "Ce jeu est trop dur"
#: ../src/AchievementList.cpp:94
msgid "Load the game 1000 times."
msgstr "Charger une sauvegarde 1000 fois."
#: ../src/AchievementList.cpp:96
msgid "No, thanks"
-msgstr ""
+msgstr "Non merci"
#: ../src/AchievementList.cpp:96
msgid "Complete a level with checkpoint, but without saving."
-msgstr ""
+msgstr "Terminez un niveau avec un point de contrôle, mais sans l'enregistrer."
#: ../src/AchievementList.cpp:98
msgid "Panic save"
msgstr "Sauvegarde panique"
#: ../src/AchievementList.cpp:98
msgid "Save twice in 1 second."
msgstr "Sauvegarder deux fois en une seconde."
#: ../src/AchievementList.cpp:99
msgid "Panic load"
msgstr "Chargement panique"
#: ../src/AchievementList.cpp:99
msgid "Load twice in 1 second."
msgstr "Chargement une sauvegarde deux fois en une seconde."
#: ../src/AchievementList.cpp:101
msgid "Bad saving position"
msgstr "Sauvegarde malchanceuse"
#: ../src/AchievementList.cpp:101
msgid "Load the game and die within 1 second."
msgstr "Charger une sauvegarde et mourir en moins d'une seconde."
#: ../src/AchievementList.cpp:102
msgid "This level is too hard"
msgstr "Ce niveau est trop dur"
#: ../src/AchievementList.cpp:102
msgid "Load the same save and die 100 times."
msgstr "Charger la même sauvegarde et mourir 100 fois."
#: ../src/AchievementList.cpp:104
msgid "Quick swap"
msgstr "Échange éclair"
#: ../src/AchievementList.cpp:104
msgid "Swap twice in under a second."
msgstr "Échange deux fois de position en moins d'une seconde."
#: ../src/AchievementList.cpp:107
msgid "Horizontal confusion"
msgstr "Confusion horizontale"
#: ../src/AchievementList.cpp:107
msgid "Press left and right simultaneously."
msgstr "Appuyez sur gauche et droite simultanément."
#: ../src/AchievementList.cpp:109
msgid "Cheater"
-msgstr ""
+msgstr "Tricheur"
#: ../src/AchievementList.cpp:109
msgid "Cheat in game."
-msgstr ""
+msgstr "Triche dans le jeu."
#: ../src/AchievementList.cpp:111
msgid "Programmer"
msgstr "Programmeur"
#: ../src/AchievementList.cpp:111
msgid "Play the development version of Me and My Shadow."
msgstr "Jouer à la version de développement de Me and My Shadow."
#: ../src/Addons.cpp:44 ../src/LevelPackManager.cpp:108
msgid "Levels"
-msgstr ""
+msgstr "Niveaux"
#: ../src/Addons.cpp:44
msgid "Single level which usually contain demanding puzzles"
msgstr ""
#: ../src/Addons.cpp:45
msgid "Levelpacks"
msgstr ""
#: ../src/Addons.cpp:45
msgid "Collection of levels with the same author or style"
msgstr ""
#: ../src/Addons.cpp:46
msgid "Themes"
msgstr ""
#: ../src/Addons.cpp:46
msgid "Give every block and background a new look and feel"
msgstr ""
#: ../src/Addons.cpp:55 ../src/TitleMenu.cpp:46
msgid "Addons"
msgstr "Extensions"
#: ../src/Addons.cpp:87
msgid "Unable to initialize addon menu:"
msgstr "Impossible d'initialiser le menu des extensions :"
#: ../src/Addons.cpp:95 ../src/Addons.cpp:158 ../src/Addons.cpp:662
#: ../src/Addons.cpp:690 ../src/CreditsMenu.cpp:89 ../src/LevelSelect.cpp:168
#: ../src/StatisticsScreen.cpp:159
msgid "Back"
msgstr "Retour"
#: ../src/Addons.cpp:169
msgid "ERROR: unable to download addons file!"
msgstr "ERREUR : impossible de télécharger le fichier des extensions !"
# TRANSLATORS: addon_list is the name of a file and should not be translated.
#: ../src/Addons.cpp:182
msgid "ERROR: unable to load addon_list file!"
msgstr "ERREUR : impossible de charger le fichier addon_list !"
#: ../src/Addons.cpp:193
msgid "ERROR: Invalid file format of addons file!"
msgstr "ERREUR : Format invalide du ficher des extensions !"
#: ../src/Addons.cpp:205
msgid "ERROR: Addon list version is unsupported!"
msgstr ""
# TRANSLATORS: installed_addons is the name of a file and should not be
# translated.
#: ../src/Addons.cpp:226
msgid "ERROR: Unable to create the installed_addons file."
msgstr "ERREUR : Impossible de créer le fichier installed_addons."
#: ../src/Addons.cpp:238
msgid "ERROR: Invalid file format of the installed_addons!"
msgstr "ERREUR : Format invalide du fichier installed_addons !"
# TRANSLATORS: indicates the author of an addon.
#: ../src/Addons.cpp:389 ../src/Addons.cpp:621
#, c-format
msgid "by %s"
msgstr "par %s"
#: ../src/Addons.cpp:397
msgid "Installed"
msgstr "Installé"
#: ../src/Addons.cpp:402
msgid "Updatable"
msgstr "Mise à jour disponible"
#: ../src/Addons.cpp:412
msgid "Not installed"
msgstr "Non installé"
#: ../src/Addons.cpp:625
#, c-format
msgid "Version: %d\n"
msgstr ""
#: ../src/Addons.cpp:627
#, c-format
msgid "Installed version: %d\n"
msgstr ""
#: ../src/Addons.cpp:630
#, c-format
msgid "License: %s\n"
msgstr ""
#: ../src/Addons.cpp:633
#, c-format
msgid "Website: %s\n"
msgstr ""
#: ../src/Addons.cpp:637
msgid "(No descriptions provided)"
msgstr ""
#: ../src/Addons.cpp:657 ../src/Addons.cpp:684
msgid "Remove"
msgstr "Désinstaller"
#: ../src/Addons.cpp:673
msgid "Update"
msgstr "Mettre à jour"
#: ../src/Addons.cpp:679
msgid "Install"
msgstr "Installer"
#: ../src/Addons.cpp:774
#, c-format
msgid "This addon can't be removed because it's needed by %s."
msgstr ""
#: ../src/Addons.cpp:774 ../src/Addons.cpp:1051
msgid "Dependency"
msgstr ""
#: ../src/Addons.cpp:803
#, c-format
msgid "WARNING: File '%s' appears to have been removed already."
msgstr ""
#: ../src/Addons.cpp:803 ../src/Addons.cpp:810 ../src/Addons.cpp:818
#: ../src/Addons.cpp:825 ../src/Addons.cpp:834 ../src/Addons.cpp:840
#: ../src/Addons.cpp:859 ../src/Addons.cpp:866 ../src/Addons.cpp:893
#: ../src/Addons.cpp:900 ../src/Addons.cpp:907 ../src/Addons.cpp:918
#: ../src/Addons.cpp:947 ../src/Addons.cpp:952 ../src/Addons.cpp:962
#: ../src/Addons.cpp:968 ../src/Addons.cpp:981 ../src/Addons.cpp:986
#: ../src/Addons.cpp:1008 ../src/Addons.cpp:1014 ../src/Addons.cpp:1044
msgid "Addon error"
msgstr ""
#: ../src/Addons.cpp:810
#, c-format
msgid "ERROR: Unable to remove file '%s'!"
msgstr ""
#: ../src/Addons.cpp:818
#, c-format
msgid "WARNING: Directory '%s' appears to have been removed already."
msgstr ""
#: ../src/Addons.cpp:825
#, c-format
msgid "ERROR: Unable to remove directory '%s'!"
msgstr ""
#: ../src/Addons.cpp:834
#, c-format
msgid "WARNING: Level '%s' appears to have been removed already."
msgstr ""
#: ../src/Addons.cpp:840
#, c-format
msgid "ERROR: Unable to remove level '%s'!"
msgstr ""
#: ../src/Addons.cpp:859
#, c-format
msgid "WARNING: Levelpack directory '%s' appears to have been removed already."
msgstr ""
#: ../src/Addons.cpp:866
#, c-format
msgid "ERROR: Unable to remove levelpack directory '%s'!"
msgstr ""
#: ../src/Addons.cpp:893
#, c-format
msgid "ERROR: Unable to download addon file %s."
msgstr ""
#: ../src/Addons.cpp:900
#, c-format
msgid "ERROR: Unable to extract addon file %s."
msgstr ""
#: ../src/Addons.cpp:907
msgid "ERROR: Addon is missing metadata!"
msgstr ""
#: ../src/Addons.cpp:918
msgid "ERROR: Invalid file format for metadata file!"
msgstr ""
#: ../src/Addons.cpp:947
#, c-format
msgid "WARNING: File '%s' already exists, addon may be broken or not working!"
msgstr ""
#: ../src/Addons.cpp:952
#, c-format
msgid ""
"WARNING: Unable to copy file '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
#: ../src/Addons.cpp:962
#, c-format
msgid ""
"WARNING: Destination directory '%s' already exists, addon may be broken or "
"not working!"
msgstr ""
#: ../src/Addons.cpp:968 ../src/Addons.cpp:1014
#, c-format
msgid ""
"WARNING: Unable to move directory '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
#: ../src/Addons.cpp:981
#, c-format
msgid "WARNING: Level '%s' already exists, addon may be broken or not working!"
msgstr ""
#: ../src/Addons.cpp:986
#, c-format
msgid ""
"WARNING: Unable to copy level '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
#: ../src/Addons.cpp:1008
#, c-format
msgid ""
"WARNING: Levelpack directory '%s' already exists, addon may be broken or not "
"working!"
msgstr ""
#: ../src/Addons.cpp:1044
#, c-format
msgid "ERROR: Addon requires another addon (%s) which can't be found!"
msgstr ""
#: ../src/Addons.cpp:1051
#, c-format
msgid "The addon %s is needed and will be installed now."
msgstr ""
#: ../src/Block.cpp:822 ../src/LevelEditor.cpp:265
msgid "On"
msgstr "Activé"
#: ../src/Block.cpp:823 ../src/LevelEditor.cpp:266
msgid "Off"
msgstr "Désactivé"
#: ../src/CommandManager.cpp:41
#, c-format
msgid "Undo %s"
msgstr ""
#: ../src/CommandManager.cpp:43
msgid "Can't undo"
msgstr ""
#: ../src/CommandManager.cpp:49
#, c-format
msgid "Redo %s"
msgstr ""
#: ../src/CommandManager.cpp:51
msgid "Can't redo"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:190
msgid "Resize level"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:807
msgid "Modify level property"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:919
#, c-format
msgid "Add scenery layer %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:921
#, c-format
msgid "Delete scenery layer %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:951
#, c-format
msgid "Modify the property of scenery layer %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:1040
#, c-format
msgid "Move %d object from layer %s to layer %s"
msgid_plural "Move %d objects from layer %s to layer %s"
msgstr[0] ""
msgstr[1] ""
#: ../src/CreditsMenu.cpp:35 ../src/TitleMenu.cpp:53
msgid "Credits"
msgstr "Crédits"
# TRANSLATORS: Font used in GUI:
# - Use "knewave" for languages using Latin and Latin-derived alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:569 ../src/Functions.cpp:570 ../src/Functions.cpp:571
#: ../src/Functions.cpp:588
msgid "knewave"
msgstr "knewave"
# TRANSLATORS: Font used for normal text:
# - Use "Blokletters-Viltstift" for languages using Latin and Latin-derived
# alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:575
msgid "Blokletters-Viltstift"
msgstr "Blokletters-Viltstift"
#: ../src/Functions.cpp:674
msgid "Loading..."
msgstr "Chargement..."
#: ../src/Functions.cpp:1243 ../src/Functions.cpp:1270
#: ../src/LevelEditor.cpp:559 ../src/LevelEditor.cpp:693
#: ../src/LevelEditor.cpp:758 ../src/LevelEditor.cpp:821
#: ../src/LevelEditor.cpp:908 ../src/LevelEditor.cpp:1033
#: ../src/LevelEditor.cpp:1083 ../src/LevelEditor.cpp:1180
#: ../src/LevelEditor.cpp:1244 ../src/LevelEditor.cpp:2923
#: ../src/LevelEditSelect.cpp:244 ../src/LevelEditSelect.cpp:277
#: ../src/LevelEditSelect.cpp:317
msgid "OK"
msgstr "OK"
#: ../src/Functions.cpp:1244 ../src/Functions.cpp:1256
#: ../src/Functions.cpp:1266 ../src/LevelEditor.cpp:565
#: ../src/LevelEditor.cpp:699 ../src/LevelEditor.cpp:764
#: ../src/LevelEditor.cpp:827 ../src/LevelEditor.cpp:914
#: ../src/LevelEditor.cpp:1039 ../src/LevelEditor.cpp:1089
#: ../src/LevelEditor.cpp:1186 ../src/LevelEditor.cpp:1250
#: ../src/LevelEditor.cpp:2929 ../src/LevelEditSelect.cpp:248
#: ../src/LevelEditSelect.cpp:281 ../src/LevelEditSelect.cpp:321
#: ../src/OptionsMenu.cpp:289
msgid "Cancel"
msgstr "Annuler"
#: ../src/Functions.cpp:1248
msgid "Abort"
msgstr "Interrompre"
#: ../src/Functions.cpp:1249 ../src/Functions.cpp:1265
msgid "Retry"
msgstr "Réessayer"
#: ../src/Functions.cpp:1250
msgid "Ignore"
msgstr "Ignorer"
#: ../src/Functions.cpp:1254 ../src/Functions.cpp:1260
msgid "Yes"
msgstr "Oui"
#: ../src/Functions.cpp:1255 ../src/Functions.cpp:1261
msgid "No"
msgstr "Non"
# TRANSLATORS: Please do not remove %s or %d from your translation:
# - %d means the level number in a levelpack
# - %s means the name of current level
#: ../src/Game.cpp:280 ../src/Game.cpp:1236
#, c-format
msgid "Level %d %s"
msgstr "Niveau %d %s"
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:915
#, c-format
msgid "Press %s key to save the game."
msgstr "Appuyez sur la touche %s pour sauvegarder la partie."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:920
#, c-format
msgid "Press %s key to swap the position of player and shadow."
msgstr ""
"Appuyez sur la touche %s pour échanger la position du joueur et de l'ombre."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:925
#, c-format
msgid "Press %s key to activate the switch."
msgstr "Appuyez sur la touche %s pour activer le bouton."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:930
#, c-format
msgid "Press %s key to teleport."
msgstr "Appuyez sur la touche %s pour être téléporté."
# TRANSLATORS: Please do not remove %s from your translation:
# - first %s means currently configured key to restart game
# - Second %s means configured key to load from last save
#: ../src/Game.cpp:972
#, c-format
msgid "Press %s to restart current level or press %s to load the game."
msgstr ""
"Appuyez sur %s pour recommencer le niveau actuel ou sur %s pour charger la "
"sauvegarde."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with currently configured key to restart game
#: ../src/Game.cpp:983
#, c-format
msgid "Press %s to restart current level."
msgstr "Appuyez sur %s pour recommencer le niveau actuel."
#: ../src/Game.cpp:996
msgid "Your shadow has died."
msgstr "Votre ombre est morte."
#: ../src/Game.cpp:1052
#, c-format
msgid "%d recording"
msgid_plural "%d recordings"
msgstr[0] ""
msgstr[1] ""
#: ../src/Game.cpp:1224
msgid "You've finished:"
msgstr "Vous avez terminé :"
# TRANSLATORS: Please do not remove %-.2f from your translation:
# - %-.2f means time in seconds
# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1291
#, c-format
msgid "Time: %-.2fs"
msgstr "Temps : %-.2f s"
# TRANSLATORS: Please do not remove %-.2f from your translation:
# - %-.2f means time in seconds
# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1300
#, c-format
msgid "Best time: %-.2fs"
msgstr "Meilleur temps : %-.2f s"
#: ../src/Game.cpp:1311
#, c-format
msgid "Target time: %-.2fs"
msgstr "Objectif de temps : %-.2f s"
# TRANSLATORS: Please do not remove %d from your translation:
# - %d means the number of recordings user has made
#: ../src/Game.cpp:1332
#, c-format
msgid "Recordings: %d"
msgstr "Enregistrements : %d"
# TRANSLATORS: Please do not remove %d from your translation:
# - %d means the number of recordings user has made
#: ../src/Game.cpp:1340
#, c-format
msgid "Best recordings: %d"
msgstr "Meilleurs enregistrements : %d"
#: ../src/Game.cpp:1350
#, c-format
msgid "Target recordings: %d"
msgstr "Objectif d'enregistrements : %d"
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with name of a prize medal (gold, silver or bronze)
#: ../src/Game.cpp:1363
#, c-format
msgid "You earned the %s medal"
msgstr "Vous avez gagné la médaille %s"
#: ../src/Game.cpp:1363
msgid "GOLD"
msgstr "d'OR"
#: ../src/Game.cpp:1363
msgid "SILVER"
msgstr "d'ARGENT"
#: ../src/Game.cpp:1363
msgid "BRONZE"
msgstr "de BRONZE"
# TRANSLATORS: used as return to the level selector menu
#: ../src/Game.cpp:1390
msgid "Menu"
msgstr "Menu"
# TRANSLATORS: used as restart level
#: ../src/Game.cpp:1397 ../src/InputManager.cpp:47
msgid "Restart"
msgstr "Recommencer"
# TRANSLATORS: used as next level
#: ../src/Game.cpp:1404
msgid "Next"
msgstr "Suivant"
#: ../src/Game.cpp:1430
msgid "Game replay is done."
msgstr "Le replay de la partie est terminé."
#: ../src/Game.cpp:1430
msgid "Game Replay"
msgstr "Replay de la partie"
#: ../src/Game.cpp:1767 ../src/Game.cpp:1769
msgid "Congratulations"
msgstr "Félicitations"
#: ../src/Game.cpp:1769
msgid "You have finished the levelpack!"
msgstr "Vous avez complété le pack de niveaux !"
#: ../src/InputManager.cpp:46
msgid "Up (in menu)"
msgstr "Haut (dans le menu)"
#: ../src/InputManager.cpp:46
msgid "Down (in menu)"
msgstr "Bas (dans le menu)"
#: ../src/InputManager.cpp:46
msgid "Left"
msgstr "Gauche"
#: ../src/InputManager.cpp:46
msgid "Right"
msgstr "Droite"
#: ../src/InputManager.cpp:46
msgid "Jump"
msgstr "Sauter"
#: ../src/InputManager.cpp:46
msgid "Action"
msgstr "Action"
#: ../src/InputManager.cpp:46
msgid "Space (Record)"
msgstr "Enregistrement des mouvements"
#: ../src/InputManager.cpp:46
msgid "Cancel recording"
msgstr "Annuler l'enregistrement"
#: ../src/InputManager.cpp:47
msgid "Escape"
msgstr "Quitter"
#: ../src/InputManager.cpp:47
msgid "Tab (View shadow/Level prop.)"
msgstr "Voir l'ombre/les propriétés du niveau"
#: ../src/InputManager.cpp:47
msgid "Save game (in editor)"
msgstr "Sauvegarder la partie (éditeur)"
#: ../src/InputManager.cpp:47
msgid "Load game"
msgstr "Charger la sauvegarde"
#: ../src/InputManager.cpp:47
msgid "Swap (in editor)"
msgstr "Échanger les places (éditeur)"
#: ../src/InputManager.cpp:48
msgid "Teleport (in editor)"
msgstr "Téléporter (éditeur)"
#: ../src/InputManager.cpp:48
msgid "Suicide (in editor)"
msgstr "Suicide (éditeur)"
#: ../src/InputManager.cpp:48
msgid "Shift (in editor)"
msgstr "Shift (éditeur)"
#: ../src/InputManager.cpp:48
msgid "Next block type (in Editor)"
msgstr "Type de bloc suivant (éditeur)"
#: ../src/InputManager.cpp:49
msgid "Previous block type (in editor)"
msgstr "Type de bloc précédent (éditeur)"
#: ../src/InputManager.cpp:49
msgid "Select (in menu)"
msgstr "Sélectionner (dans le menu)"
# TRANSLAOTRS: This is used when the name of the key code is not found.
#: ../src/InputManager.cpp:156
#, c-format
msgid "(Key %d)"
msgstr ""
#: ../src/InputManager.cpp:163
#, c-format
msgid "Joystick axis %d %s"
msgstr ""
#: ../src/InputManager.cpp:166
#, c-format
msgid "Joystick button %d"
msgstr ""
#: ../src/InputManager.cpp:171
#, c-format
msgid "Joystick hat %d left"
msgstr ""
#: ../src/InputManager.cpp:174
#, c-format
msgid "Joystick hat %d right"
msgstr ""
#: ../src/InputManager.cpp:177
#, c-format
msgid "Joystick hat %d up"
msgstr ""
#: ../src/InputManager.cpp:180
#, c-format
msgid "Joystick hat %d down"
msgstr ""
# TRANSLAOTRS: This is used when the JOYSTICK_HAT value is invalid.
#: ../src/InputManager.cpp:185
#, c-format
msgid "Joystick hat %d %d"
msgstr ""
#: ../src/InputManager.cpp:202
msgid "OR"
msgstr "OU"
#: ../src/InputManager.cpp:416
msgid "Select an item and press a key to change it."
msgstr "Sélectionner un élément et appuyez sur une touche pour le changer."
#: ../src/InputManager.cpp:419
msgid "Press backspace to clear the selected item."
msgstr ""
"Appuyez sur la touche \"Retour arrière\" pour effacer l'élément sélectionné."
#: ../src/LevelEditor.cpp:56
msgid "Block"
msgstr "Bloc"
#: ../src/LevelEditor.cpp:56
msgid "Player Start"
msgstr "Départ du joueur"
#: ../src/LevelEditor.cpp:56
msgid "Shadow Start"
msgstr "Départ de l'ombre"
#: ../src/LevelEditor.cpp:57
msgid "Exit"
msgstr "Sortie"
#: ../src/LevelEditor.cpp:57
msgid "Shadow Block"
msgstr "Bloc d'ombre"
#: ../src/LevelEditor.cpp:57
msgid "Spikes"
msgstr "Piques"
#: ../src/LevelEditor.cpp:58
msgid "Checkpoint"
msgstr "Point de contrôle"
#: ../src/LevelEditor.cpp:58 ../src/LevelEditSelect.cpp:312
msgid "Swap"
msgstr "Échanger"
#: ../src/LevelEditor.cpp:58
msgid "Fragile"
msgstr "Fragile"
#: ../src/LevelEditor.cpp:59
msgid "Moving Block"
msgstr "Bloc mobile"
#: ../src/LevelEditor.cpp:59
msgid "Moving Shadow Block"
msgstr "Bloc d'ombre mobile"
#: ../src/LevelEditor.cpp:59
msgid "Moving Spikes"
msgstr "Piques mobiles"
#: ../src/LevelEditor.cpp:60
msgid "Teleporter"
msgstr "Téléporteur"
#: ../src/LevelEditor.cpp:60
msgid "Button"
msgstr "Bouton"
#: ../src/LevelEditor.cpp:60
msgid "Switch"
msgstr "Interrupteur"
#: ../src/LevelEditor.cpp:61
msgid "Conveyor Belt"
msgstr "Tapis roulant"
#: ../src/LevelEditor.cpp:61
msgid "Shadow Conveyor Belt"
msgstr "Tapis roulant d'ombre"
#: ../src/LevelEditor.cpp:61
msgid "Notification Block"
msgstr "Panneau d'information"
#: ../src/LevelEditor.cpp:61
msgid "Collectable"
msgstr "Collectable"
#: ../src/LevelEditor.cpp:61
msgid "Pushable"
msgstr "Déplaçable"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:310
msgid "Select"
msgstr "Sélectionner"
#: ../src/LevelEditor.cpp:65
msgid "Add"
msgstr "Ajouter"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:311
msgid "Delete"
msgstr "Supprimer"
#: ../src/LevelEditor.cpp:65 ../src/LevelPlaySelect.cpp:66
#: ../src/TitleMenu.cpp:43
msgid "Play"
msgstr "Jouer"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:2852
msgid "Level settings"
msgstr "Paramètres du niveau"
#: ../src/LevelEditor.cpp:65
msgid "Save level"
msgstr "Enregistrer le niveau"
#: ../src/LevelEditor.cpp:65
msgid "Back to menu"
msgstr "Retour au menu"
#: ../src/LevelEditor.cpp:65
msgid "Configure"
msgstr "Configurer"
#: ../src/LevelEditor.cpp:84
#, c-format
msgid "%s (Scenery)"
msgstr ""
#: ../src/LevelEditor.cpp:267
msgid "Toggle"
msgstr "Bascule"
#: ../src/LevelEditor.cpp:270
msgid "Complete"
msgstr "Complet"
#: ../src/LevelEditor.cpp:271
msgid "One step"
msgstr "Une étape"
#: ../src/LevelEditor.cpp:272
msgid "Two steps"
msgstr "Deux étapes"
#: ../src/LevelEditor.cpp:273
msgid "Gone"
msgstr "Disparu"
#: ../src/LevelEditor.cpp:291
msgid "Negative infinity"
msgstr ""
#: ../src/LevelEditor.cpp:293
msgid "Zero"
msgstr ""
#: ../src/LevelEditor.cpp:295
msgid "Level size"
msgstr ""
#: ../src/LevelEditor.cpp:297
msgid "Positive infinity"
msgstr ""
#: ../src/LevelEditor.cpp:299
msgid "Default"
msgstr ""
#: ../src/LevelEditor.cpp:308
msgid "Deselect"
msgstr "Déselectionner"
#: ../src/LevelEditor.cpp:318 ../src/LevelEditor.cpp:1136
#, c-format
msgid "Horizontal repeat start: %s"
msgstr ""
#: ../src/LevelEditor.cpp:320 ../src/LevelEditor.cpp:1137
#, c-format
msgid "Horizontal repeat end: %s"
msgstr ""
#: ../src/LevelEditor.cpp:322 ../src/LevelEditor.cpp:1138
#, c-format
msgid "Vertical repeat start: %s"
msgstr ""
#: ../src/LevelEditor.cpp:324 ../src/LevelEditor.cpp:1139
#, c-format
msgid "Vertical repeat end: %s"
msgstr ""
#: ../src/LevelEditor.cpp:329 ../src/LevelEditor.cpp:1150
msgid "Custom scenery"
msgstr ""
#: ../src/LevelEditor.cpp:335 ../src/LevelEditor.cpp:600
#: ../src/LevelEditor.cpp:602
msgid "Visible"
msgstr ""
#: ../src/LevelEditor.cpp:344
msgid "Link"
msgstr "Lier"
#: ../src/LevelEditor.cpp:345
msgid "Remove Links"
msgstr "Délier"
#: ../src/LevelEditor.cpp:349 ../src/LevelEditor.cpp:624
#: ../src/LevelEditor.cpp:626
msgid "Automatic"
msgstr "Automatique"
#: ../src/LevelEditor.cpp:359 ../src/LevelEditor.cpp:649
#, c-format
msgid "Behavior: %s"
msgstr ""
#: ../src/LevelEditor.cpp:362
msgid "Path"
msgstr "Parcours"
#: ../src/LevelEditor.cpp:363
msgid "Remove Path"
msgstr "Supprimer le parcours"
#: ../src/LevelEditor.cpp:365 ../src/LevelEditor.cpp:371
#: ../src/LevelEditor.cpp:587 ../src/LevelEditor.cpp:589
msgid "Activated"
msgstr ""
#: ../src/LevelEditor.cpp:366 ../src/LevelEditor.cpp:612
#: ../src/LevelEditor.cpp:614
msgid "Looping"
msgstr "Boucle"
#: ../src/LevelEditor.cpp:372 ../src/LevelEditor.cpp:3526
msgid "Speed"
msgstr "Vitesse"
#: ../src/LevelEditor.cpp:378 ../src/LevelEditor.cpp:668
#, c-format
msgid "State: %s"
msgstr ""
#: ../src/LevelEditor.cpp:382 ../src/LevelEditor.cpp:3511
msgid "Message"
msgstr "Message"
#: ../src/LevelEditor.cpp:384 ../src/LevelEditor.cpp:1202
#: ../src/LevelEditor.cpp:3825
msgid "Appearance"
msgstr ""
#: ../src/LevelEditor.cpp:389 ../src/LevelEditor.cpp:431
#: ../src/LevelEditor.cpp:715
msgid "Scripting"
msgstr "Script"
#: ../src/LevelEditor.cpp:402 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Background layer: %s"
msgstr ""
#: ../src/LevelEditor.cpp:409 ../src/LevelEditor.cpp:866
#: ../src/LevelEditor.cpp:884
msgid "Blocks layer"
msgstr ""
#: ../src/LevelEditor.cpp:417 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Foreground layer: %s"
msgstr ""
#: ../src/LevelEditor.cpp:423
msgid "Add new layer"
msgstr ""
#: ../src/LevelEditor.cpp:424
msgid "Delete selected layer"
msgstr ""
#: ../src/LevelEditor.cpp:425
msgid "Configure selected layer"
msgstr ""
#: ../src/LevelEditor.cpp:426
msgid "Move selected object to layer"
msgstr ""
#: ../src/LevelEditor.cpp:430 ../src/OptionsMenu.cpp:55
msgid "Settings"
msgstr "Paramètres"
#: ../src/LevelEditor.cpp:463
msgid ""
"NOTE: the layers are sorted by name alphabetically.\n"
"The layer is background layer if its name is < 'f'\n"
"by dictionary order, otherwise it's foreground layer."
msgstr ""
#: ../src/LevelEditor.cpp:539
msgid "Notification block"
msgstr "Panneau d'information"
#: ../src/LevelEditor.cpp:545
msgid "Enter message here:"
msgstr "Écrivez le message :"
#: ../src/LevelEditor.cpp:646
msgid "Behavior"
msgstr ""
#: ../src/LevelEditor.cpp:665
msgid "State"
msgstr ""
#: ../src/LevelEditor.cpp:673
msgid "Conveyor belt speed"
msgstr "Vitesse du tapis roulant"
#: ../src/LevelEditor.cpp:679
msgid "Enter speed here:"
msgstr "Définissez la vitesse :"
#: ../src/LevelEditor.cpp:690
msgid "NOTE: 1 Speed = 0.08 block/s"
msgstr ""
#: ../src/LevelEditor.cpp:721
msgid "Id:"
msgstr "Identifiant :"
#: ../src/LevelEditor.cpp:787
msgid "Level Scripting"
msgstr "Script du niveau"
#: ../src/LevelEditor.cpp:892
msgid "Add layer"
msgstr ""
#: ../src/LevelEditor.cpp:898
msgid "Enter the layer name:"
msgstr ""
#: ../src/LevelEditor.cpp:943
#, c-format
msgid "Are you sure you want to delete layer '%s'?"
msgstr ""
#: ../src/LevelEditor.cpp:944
msgid "Delete layer"
msgstr ""
#: ../src/LevelEditor.cpp:968
msgid "Layer settings"
msgstr ""
#: ../src/LevelEditor.cpp:974
msgid "Layer name:"
msgstr ""
#: ../src/LevelEditor.cpp:989
msgid "Layer moving speed (1 speed = 0.8 block/s):"
msgstr ""
#: ../src/LevelEditor.cpp:1010
msgid "Speed of following camera:"
msgstr ""
#: ../src/LevelEditor.cpp:1062
msgid "Move to layer"
msgstr ""
#: ../src/LevelEditor.cpp:1068
msgid "Enter the layer name (create new layer if necessary):"
msgstr ""
#: ../src/LevelEditor.cpp:1132
msgid "Repeat mode"
msgstr ""
#: ../src/LevelEditor.cpp:1156
msgid "Custom scenery:"
msgstr ""
#: ../src/LevelEditor.cpp:1219
msgid "(Use the default appearance for this block)"
msgstr ""
# TRANSLATORS: Block name
# TRANSLATORS: Context: Resize/Move ...
# TRANSLATORS: Context: Add/Remove ...
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1465 ../src/LevelEditor.cpp:1707
#: ../src/LevelEditor.cpp:1723 ../src/LevelEditor.cpp:1772
#: ../src/LevelEditor.cpp:4400
msgid "Custom scenery block"
msgstr ""
#: ../src/LevelEditor.cpp:1673
msgid "Toolbox"
msgstr ""
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Resize %s"
msgstr ""
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Move %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1713
#, c-format
msgid "Move %d object"
msgid_plural "Move %d objects"
msgstr[0] ""
msgstr[1] ""
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Add %s"
msgstr ""
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Remove %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1729
#, c-format
msgid "Add %d object"
msgid_plural "Add %d objects"
msgstr[0] ""
msgstr[1] ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1731
#, c-format
msgid "Remove %d object"
msgid_plural "Remove %d objects"
msgstr[0] ""
msgstr[1] ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1739
#, c-format
msgid "Add path to %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1741
#, c-format
msgid "Remove a path point from %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1747
#, c-format
msgid "Remove all paths from %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1753
#, c-format
msgid "Add link from %s to %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1759
#, c-format
msgid "Remove all links from %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1766
msgid "Modify the %2 property of %1"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1818
#, c-format
msgid "Edit the script of %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1821
msgid "Edit the script of level"
msgstr ""
#: ../src/LevelEditor.cpp:2146 ../src/LevelEditor.cpp:2226
msgid "The level has unsaved changes."
msgstr ""
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Are you sure you want to quit?"
msgstr "Êtes-vous sûr(e) de vouloir quitter ?"
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Quit prompt"
msgstr "Quitter"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
#, c-format
msgid "Level \"%s\" saved"
msgstr "Niveau \"%s\" enregistré"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
msgid "Saved"
msgstr "Enregistré"
#: ../src/LevelEditor.cpp:2859 ../src/LevelEditSelect.cpp:208
msgid "Name:"
msgstr "Nom :"
#: ../src/LevelEditor.cpp:2866
msgid "Theme:"
msgstr "Thème :"
#: ../src/LevelEditor.cpp:2873
msgid "Examples: %DATA%/themes/classic"
msgstr ""
#: ../src/LevelEditor.cpp:2875
msgid "or %USER%/themes/Orange"
msgstr ""
#: ../src/LevelEditor.cpp:2878
msgid "Music:"
msgstr ""
#: ../src/LevelEditor.cpp:2887
msgid "Target time (s):"
msgstr "Objectif de temps (s) :"
#: ../src/LevelEditor.cpp:2903
msgid "Target recordings:"
msgstr "Objectif d'enregistrements :"
#: ../src/LevelEditor.cpp:2919
msgid "Restart level editor is required"
msgstr ""
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3718
#: ../src/LevelEditor.cpp:3739
msgid "Please enter a layer name."
msgstr ""
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3683
#: ../src/LevelEditor.cpp:3718 ../src/LevelEditor.cpp:3722
#: ../src/LevelEditor.cpp:3739 ../src/LevelEditor.cpp:3743
#: ../src/LevelEditSelect.cpp:644 ../src/LevelEditSelect.cpp:683
#: ../src/LevelEditSelect.cpp:688 ../src/LevelEditSelect.cpp:693
#: ../src/LevelEditSelect.cpp:698 ../src/LevelEditSelect.cpp:796
msgid "Error"
msgstr "Erreur"
#: ../src/LevelEditor.cpp:3683 ../src/LevelEditor.cpp:3722
#, c-format
msgid "The layer '%s' already exists."
msgstr ""
#: ../src/LevelEditor.cpp:3743
msgid "Source and destination layers are the same."
msgstr ""
#: ../src/LevelEditor.cpp:3760
msgid "Scenery"
msgstr ""
#: ../src/LevelEditor.cpp:4190 ../src/LevelEditor.cpp:4218
#, c-format
msgid "Speed: %d = %0.2f block/s"
msgstr ""
#: ../src/LevelEditor.cpp:4203
msgid "Stop at this point"
msgstr ""
#: ../src/LevelEditor.cpp:4208
#, c-format
msgid "Pause: %d = %0.3fs"
msgstr ""
#: ../src/LevelEditSelect.cpp:41 ../src/TitleMenu.cpp:45
msgid "Map Editor"
msgstr "Éditeur de niveau"
#: ../src/LevelEditSelect.cpp:66
msgid "New Levelpack"
msgstr "Nouveau pack de niveaux"
#: ../src/LevelEditSelect.cpp:71
msgid "Pack Properties"
msgstr "Propriétés du pack"
#: ../src/LevelEditSelect.cpp:76
msgid "Remove Pack"
msgstr "Supprimer le pack"
#: ../src/LevelEditSelect.cpp:81
msgid "Move Map"
msgstr "Déplacer le niveau"
#: ../src/LevelEditSelect.cpp:89
msgid "Remove Map"
msgstr "Supprimer le niveau"
#: ../src/LevelEditSelect.cpp:94
msgid "Edit Map"
msgstr "Éditer le niveau"
#: ../src/LevelEditSelect.cpp:205
msgid "Properties"
msgstr "Propriétés"
#: ../src/LevelEditSelect.cpp:217
msgid "Description:"
msgstr "Description :"
#: ../src/LevelEditSelect.cpp:226
msgid "Congratulation text:"
msgstr "Texte de félicitation :"
#: ../src/LevelEditSelect.cpp:235
msgid "Music list:"
msgstr ""
#: ../src/LevelEditSelect.cpp:265 ../src/LevelEditSelect.cpp:485
msgid "Add level"
msgstr "Ajouter un niveau"
#: ../src/LevelEditSelect.cpp:268
msgid "File name:"
msgstr "Nom de fichier :"
#: ../src/LevelEditSelect.cpp:293
msgid "Move level"
msgstr "Déplacer le niveau"
#: ../src/LevelEditSelect.cpp:296
msgid "Level: "
msgstr "Niveau cible :"
#: ../src/LevelEditSelect.cpp:310
msgid "Before"
msgstr "Avant"
#: ../src/LevelEditSelect.cpp:311
msgid "After"
msgstr "Après"
#: ../src/LevelEditSelect.cpp:368 ../src/LevelPlaySelect.cpp:124
msgid "Individual levels which are not contained in any level packs"
msgstr ""
#: ../src/LevelEditSelect.cpp:577
#, c-format
msgid "Are you sure remove the level pack '%s'?"
msgstr ""
#: ../src/LevelEditSelect.cpp:577 ../src/LevelEditSelect.cpp:607
msgid "Remove prompt"
msgstr "Suppression"
#: ../src/LevelEditSelect.cpp:607
#, c-format
msgid "Are you sure remove the map '%s'?"
msgstr ""
#: ../src/LevelEditSelect.cpp:644
msgid "Levelpack name cannot be empty."
msgstr ""
#: ../src/LevelEditSelect.cpp:683
#, c-format
msgid "The levelpack directory '%s' already exists!"
msgstr ""
#: ../src/LevelEditSelect.cpp:688
#, c-format
msgid "Unable to create levelpack directory '%s'!"
msgstr ""
#: ../src/LevelEditSelect.cpp:693
#, c-format
msgid "The levelpack file '%s' already exists!"
msgstr ""
#: ../src/LevelEditSelect.cpp:698
#, c-format
msgid "Unable to create levelpack file '%s'!"
msgstr ""
#: ../src/LevelEditSelect.cpp:758
msgid "No file name given for the new level."
msgstr "Aucun nom de fichier n'a été spécifié pour le nouveau niveau."
#: ../src/LevelEditSelect.cpp:758
msgid "Missing file name"
msgstr "Nom de fichier manquant"
#: ../src/LevelEditSelect.cpp:796
#, c-format
msgid "The file %s already exists."
msgstr ""
#: ../src/LevelEditSelect.cpp:849
msgid "The entered level number isn't valid!"
msgstr "Le numéro de niveau entré n'est pas valide !"
#: ../src/LevelEditSelect.cpp:849
msgid "Illegal number"
msgstr "Nombre invalide"
#: ../src/LevelInfoRender.cpp:19
msgid "Choose a level"
msgstr "Choisissez un niveau"
#: ../src/LevelInfoRender.cpp:20
msgid "Time:"
msgstr "Temps :"
#: ../src/LevelInfoRender.cpp:21 ../src/StatisticsScreen.cpp:259
msgid "Recordings:"
msgstr "Enregistrements :"
#: ../src/LevelPackManager.cpp:124
msgid "Custom Levels"
msgstr ""
#: ../src/LevelPlaySelect.cpp:41
msgid "Select Level"
msgstr "Choix du niveau"
# TRANSLATORS: Used for button which clear any level progress like unlocked
# levels and highscores.
#: ../src/OptionsMenu.cpp:66
msgid "Clear Progress"
msgstr "Réinitialiser la progression"
#: ../src/OptionsMenu.cpp:109
msgid "General"
msgstr "Général"
#: ../src/OptionsMenu.cpp:110
msgid "Controls"
msgstr "Contrôles"
#: ../src/OptionsMenu.cpp:121
msgid "Music"
msgstr "Musique"
#: ../src/OptionsMenu.cpp:129
msgid "Sound"
msgstr "Son"
#: ../src/OptionsMenu.cpp:137
msgid "Resolution"
msgstr "Résolution"
#: ../src/OptionsMenu.cpp:177
msgid "Language"
msgstr "Langue"
# TRANSLATORS: as detect user's language automatically
#: ../src/OptionsMenu.cpp:185
msgid "Auto-Detect"
msgstr "Détection automatique"
#: ../src/OptionsMenu.cpp:209
msgid "Theme"
msgstr "Thème"
#: ../src/OptionsMenu.cpp:247
msgid "Internet proxy"
msgstr "Proxy Internet"
#: ../src/OptionsMenu.cpp:256
msgid "Fullscreen"
msgstr "Plein écran"
#: ../src/OptionsMenu.cpp:261
msgid "Quick record"
msgstr "Enregistrements rapides"
#: ../src/OptionsMenu.cpp:266
msgid "Internet"
msgstr "Accès à Internet"
#: ../src/OptionsMenu.cpp:271
msgid "Fade transition"
msgstr "Transition en fondu"
#: ../src/OptionsMenu.cpp:294
msgid "Save Changes"
msgstr "Enregistrer les changements"
#: ../src/OptionsMenu.cpp:513
msgid "Do you really want to reset level progress?"
msgstr "Souhaitez-vous vraiment réinitialiser votre progression ?"
#: ../src/OptionsMenu.cpp:513
msgid "Warning"
msgstr "Attention"
#: ../src/StatisticsManager.cpp:386
msgid "New achievement:"
msgstr "Nouveau succès :"
#: ../src/StatisticsManager.cpp:394
#, c-format
msgid "Achieved on %s"
msgstr "Obtenu le %s"
#: ../src/StatisticsManager.cpp:400
msgid "Unknown achievement"
msgstr "Succès inconnu"
#: ../src/StatisticsManager.cpp:406
#, c-format
msgid "Achieved %1.0f%%"
msgstr "Complété à %1.0f %%"
#: ../src/StatisticsManager.cpp:410
msgid "Not achieved"
msgstr "Non obtenu"
#: ../src/StatisticsScreen.cpp:57 ../src/TitleMenu.cpp:55
msgid "Achievements and Statistics"
msgstr "Succès et statistiques"
#: ../src/StatisticsScreen.cpp:166
msgid "Achievements"
msgstr "Succès"
#: ../src/StatisticsScreen.cpp:167
msgid "Statistics"
msgstr "Statistiques"
#: ../src/StatisticsScreen.cpp:234
msgid "Total"
msgstr "Total"
#: ../src/StatisticsScreen.cpp:246
msgid "Traveling distance (m)"
msgstr "Distance parcourue (m)"
#: ../src/StatisticsScreen.cpp:247
msgid "Jump times"
msgstr "Nombre de sauts"
#: ../src/StatisticsScreen.cpp:248
msgid "Die times"
msgstr "Nombre de morts"
#: ../src/StatisticsScreen.cpp:249
msgid "Squashed times"
msgstr "Nombre d'écrasements"
#: ../src/StatisticsScreen.cpp:260
msgid "Switch pulled times:"
msgstr "Nombre d'interrupteur utilisés :"
#: ../src/StatisticsScreen.cpp:261
msgid "Swap times:"
msgstr "Nombre d'échanges :"
#: ../src/StatisticsScreen.cpp:262
msgid "Save times:"
msgstr "Nombre de sauvegardes :"
#: ../src/StatisticsScreen.cpp:263
msgid "Load times:"
msgstr "Nombre de chargements :"
#: ../src/StatisticsScreen.cpp:268
msgid "Completed levels:"
msgstr "Niveaux complétés :"
#: ../src/StatisticsScreen.cpp:306
msgid "In-game time:"
msgstr "Durée en jeu :"
#: ../src/StatisticsScreen.cpp:308
msgid "Level editing time:"
msgstr "Durée dans l'éditeur de niveau :"
#: ../src/StatisticsScreen.cpp:310
msgid "Created levels:"
msgstr "Niveaux créés :"
#: ../src/TitleMenu.cpp:44
msgid "Options"
msgstr "Options"
#: ../src/TitleMenu.cpp:47
msgid "Quit"
msgstr "Quitter"
#: ../src/TitleMenu.cpp:131
msgid "Enable internet in order to install addons."
msgstr "Activer l'accès à Internet pour installer des extensions."
#: ../src/TitleMenu.cpp:131
msgid "Internet disabled"
msgstr "Accès à Internet désactivé"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Return"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Escape"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Backspace"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Tab"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Space"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "CapsLock"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PrintScreen"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "ScrollLock"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Pause"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Insert"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Home"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageUp"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Delete"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "End"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageDown"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Down"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Up"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Numlock"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "SysReq"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Ctrl"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Shift"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Alt"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left GUI"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Ctrl"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Shift"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Alt"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right GUI"
msgstr ""
diff --git a/data/locale/hu.po b/data/locale/hu.po
index bbebfcb..548414a 100644
--- a/data/locale/hu.po
+++ b/data/locale/hu.po
@@ -1,2106 +1,2106 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the meandmyshadow package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: meandmyshadow 0.5svn\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-09 16:18+0800\n"
-"PO-Revision-Date: 2018-09-16 03:57+0000\n"
+"PO-Revision-Date: 2018-09-21 08:31+0000\n"
"Last-Translator: SanskritFritz <SanskritFritz+github@gmail.com>\n"
"Language-Team: Hungarian <https://hosted.weblate.org/projects/"
"me-and-my-shadow/translations/hu/>\n"
"Language: hu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.2-dev\n"
#: ../src/AchievementList.cpp:43
msgid "Newbie"
msgstr "Újonc"
#: ../src/AchievementList.cpp:43
msgid "Complete a level."
msgstr "Szint teljesítése."
#: ../src/AchievementList.cpp:44
msgid "Experienced player"
msgstr "Tapasztalt játékos"
#: ../src/AchievementList.cpp:44
msgid "Complete 50 levels."
msgstr "50 szint teljesítése."
#: ../src/AchievementList.cpp:45
msgid "Good job!"
msgstr "Szép volt!"
#: ../src/AchievementList.cpp:45
msgid "Receive a gold medal."
msgstr "Nyerj aranyérmet."
#: ../src/AchievementList.cpp:46
msgid "Expert"
msgstr "Szakértő"
#: ../src/AchievementList.cpp:46
msgid "Earn 50 gold medal."
msgstr "Nyerj 50 aranyérmet."
#: ../src/AchievementList.cpp:48
msgid "Graduate"
msgstr "Végzős"
#: ../src/AchievementList.cpp:48
msgid "Complete the tutorial level pack."
msgstr "Teljesítsd a oktató szintcsomagot."
#: ../src/AchievementList.cpp:49
msgid "Outstanding graduate"
msgstr "Kiváló végzős"
#: ../src/AchievementList.cpp:49
msgid "Complete the tutorial level pack with gold for all levels."
msgstr "Teljesítsd az oktató szintcsomagot aranyéremmel minden szinten."
#: ../src/AchievementList.cpp:51
msgid "Hooked"
msgstr "Ráharaptál"
#: ../src/AchievementList.cpp:51
msgid "Play Me and My Shadow for more than 2 hours."
msgstr "Több mint 2 óra Me and My Shadow játék."
#: ../src/AchievementList.cpp:52
msgid "Loyal fan of Me and My Shadow"
msgstr "A Me and My Shadow hű rajongója"
#: ../src/AchievementList.cpp:52
msgid "Play Me and My Shadow for more than 24 hours."
msgstr "Több mint 24 óra Me and My Shadow játék."
#: ../src/AchievementList.cpp:54
msgid "Constructor"
msgstr "Építész"
#: ../src/AchievementList.cpp:54
msgid "Use the level editor for more than 2 hours."
msgstr "Több mint 2 óra a szintszerkeszőben."
#: ../src/AchievementList.cpp:55
msgid "The creator"
msgstr "Az akotó"
#: ../src/AchievementList.cpp:55
msgid "Use the level editor for more than 24 hours."
msgstr "Több mint 24 óra a szintszerkeszőben."
#: ../src/AchievementList.cpp:57
msgid "Look, cute level!"
msgstr "Nocsak, cuki szint!"
#: ../src/AchievementList.cpp:57
msgid "Create a level for the first time."
msgstr "Új szint léthehozása első alkalommal."
#: ../src/AchievementList.cpp:58
msgid "The level museum"
msgstr "A szintmúzeum"
#: ../src/AchievementList.cpp:58
msgid "Create 50 levels."
msgstr "50 szint létrehozása."
#: ../src/AchievementList.cpp:60
msgid "Hello, World!"
msgstr "Szia világ!"
#: ../src/AchievementList.cpp:60
msgid "Write a script for the first time."
msgstr "Szkript írása első alkalommal."
#: ../src/AchievementList.cpp:62
msgid "Frog"
msgstr "Béka"
#: ../src/AchievementList.cpp:62
msgid "Jump 1000 times."
msgstr "1000 ugrás."
#: ../src/AchievementList.cpp:64
msgid "Wanderer"
msgstr "Vándor"
#: ../src/AchievementList.cpp:64
msgid "Travel 100 meters."
msgstr "100 méternyi utazás."
#: ../src/AchievementList.cpp:65
msgid "Runner"
msgstr "Futó"
#: ../src/AchievementList.cpp:65
msgid "Travel 1 kilometer."
msgstr "1 kilóméter utazás."
#: ../src/AchievementList.cpp:66
msgid "Long distance runner"
msgstr "Hosszútávfutó"
#: ../src/AchievementList.cpp:66
msgid "Travel 10 kilometers."
msgstr "10 kilóméter utazás."
#: ../src/AchievementList.cpp:67
msgid "Marathon runner"
msgstr "Maratonfutó"
#: ../src/AchievementList.cpp:67
msgid "Travel 42,195 meters."
msgstr "42 195 méternyi utazás."
#: ../src/AchievementList.cpp:69
msgid "Be careful!"
msgstr "Óvatosan!"
#: ../src/AchievementList.cpp:69
msgid "Die for the first time."
msgstr "Az első halál."
#: ../src/AchievementList.cpp:70
msgid "It doesn't matter..."
msgstr "Nem számít..."
#: ../src/AchievementList.cpp:70
msgid "Die 50 times."
msgstr "50 halál."
#: ../src/AchievementList.cpp:71
msgid "Expert of trial and error"
msgstr "A próbálgatás szakértője"
#: ../src/AchievementList.cpp:71
msgid "Die 1000 times."
msgstr "1000 halál."
#: ../src/AchievementList.cpp:73
msgid "Keep an eye for moving blocks!"
msgstr "Figyelj a mozgó tömbökre!"
#: ../src/AchievementList.cpp:73
msgid "Get squashed for the first time."
msgstr "Szétlapítva első alkalommal."
#: ../src/AchievementList.cpp:74
msgid "Potato masher"
msgstr "Krumplipüréző"
#: ../src/AchievementList.cpp:74
msgid "Get squashed 50 times."
msgstr "Szétlapítva 50 alkalommal."
#: ../src/AchievementList.cpp:76
msgid "Double kill"
msgstr "Kettős halál"
#: ../src/AchievementList.cpp:76
msgid "Get both the player and the shadow dead."
msgstr "Haljon meg a játékos és az árnyéka is."
#: ../src/AchievementList.cpp:78
msgid "Bad luck"
msgstr "Pech"
#: ../src/AchievementList.cpp:78
msgid "Die 5 times in under 5 seconds."
msgstr "Halj meg 5-ször 5 másodpercen belül."
#: ../src/AchievementList.cpp:79
msgid "This level is too dangerous"
msgstr "Ez a szint túl veszélyes"
#: ../src/AchievementList.cpp:79
msgid "Die 10 times in under 5 seconds."
msgstr "Halj meg 10-szer 5 másodpercen belül."
#: ../src/AchievementList.cpp:81
msgid "You forgot your friend"
msgstr "Megfeledkeztél a barátodról"
#: ../src/AchievementList.cpp:81
msgid "Finish the level with the player or the shadow dead."
msgstr "A szint befejezése úgy, hogy a játékos, vagy az árnyéka meghal."
#: ../src/AchievementList.cpp:82
msgid "Just in time"
msgstr "Épp időben"
#: ../src/AchievementList.cpp:82
msgid "Reach the exit with the player and the shadow simultaneously."
msgstr "A játékos és az árnyéka egyszerre érjenek a kijárathoz."
#: ../src/AchievementList.cpp:84
msgid "Recorder"
msgstr "Felvevő"
#: ../src/AchievementList.cpp:84
msgid "Record 100 times."
msgstr "100 felvétel készítése."
#: ../src/AchievementList.cpp:85
msgid "Shadowmaster"
msgstr "Árnyékmester"
#: ../src/AchievementList.cpp:85
msgid "Record 1000 times."
msgstr "1000 felvétel készítése."
#: ../src/AchievementList.cpp:87
msgid "Switch puller"
msgstr "Váltókezelő"
#: ../src/AchievementList.cpp:87
msgid "Pull the switch 100 times."
msgstr "Váltó meghúzása 100 alkalommal."
#: ../src/AchievementList.cpp:88
msgid "The switch is broken!"
msgstr "Elromlott a váltó!"
#: ../src/AchievementList.cpp:88
msgid "Pull the switch 1000 times."
msgstr "Váltó meghúzása 1000 alkalommal."
#: ../src/AchievementList.cpp:90
msgid "Swapper"
msgstr "Cserélő"
#: ../src/AchievementList.cpp:90
msgid "Swap 100 times."
msgstr "Csere 100 alkalommal."
#: ../src/AchievementList.cpp:91
msgid "Player to shadow to player to shadow..."
msgstr "Játékosból árnyék, árnyékból játékos..."
#: ../src/AchievementList.cpp:91
msgid "Swap 1000 times."
msgstr "Csere 1000 alkalommal."
#: ../src/AchievementList.cpp:93
msgid "Play it save"
msgstr "Biztos ami biztos"
#: ../src/AchievementList.cpp:93
msgid "Save 1000 times."
msgstr "Mentés 1000 alkalommal."
#: ../src/AchievementList.cpp:94
msgid "This game is too hard"
msgstr "Ez a játék túl nehéz"
#: ../src/AchievementList.cpp:94
msgid "Load the game 1000 times."
msgstr "Játék betöltése 1000 alkalommal."
#: ../src/AchievementList.cpp:96
msgid "No, thanks"
msgstr "Kösz, nem"
#: ../src/AchievementList.cpp:96
msgid "Complete a level with checkpoint, but without saving."
msgstr "Szint befejezése ellenőrző ponttal, de mentés nélkül."
#: ../src/AchievementList.cpp:98
msgid "Panic save"
msgstr "Pánik mentés"
#: ../src/AchievementList.cpp:98
msgid "Save twice in 1 second."
msgstr "2 mentés 1 másodpercen belül."
#: ../src/AchievementList.cpp:99
msgid "Panic load"
msgstr "Pánik betöltés"
#: ../src/AchievementList.cpp:99
msgid "Load twice in 1 second."
msgstr "2 betöltés 1 másodpercen belül."
#: ../src/AchievementList.cpp:101
msgid "Bad saving position"
msgstr "Rossz mentési pozíció"
#: ../src/AchievementList.cpp:101
msgid "Load the game and die within 1 second."
msgstr "Játék betöltése és halál 1 másodpercen belül."
#: ../src/AchievementList.cpp:102
msgid "This level is too hard"
msgstr "Ez a szint túl nehéz"
#: ../src/AchievementList.cpp:102
msgid "Load the same save and die 100 times."
msgstr "Adott mentés betöltése és halál 100 alkalommal."
#: ../src/AchievementList.cpp:104
msgid "Quick swap"
msgstr "Gyorsváltás"
#: ../src/AchievementList.cpp:104
msgid "Swap twice in under a second."
msgstr "Váltás kétszer egy másodpercen belül."
#: ../src/AchievementList.cpp:107
msgid "Horizontal confusion"
msgstr "Vízszintes összezavarodás"
#: ../src/AchievementList.cpp:107
msgid "Press left and right simultaneously."
msgstr "Nyomd meg egyszerre a balra és a jobbra gombot."
#: ../src/AchievementList.cpp:109
msgid "Cheater"
msgstr "Csaló"
#: ../src/AchievementList.cpp:109
msgid "Cheat in game."
msgstr "Csalás a játékban."
#: ../src/AchievementList.cpp:111
msgid "Programmer"
msgstr "Programozó"
#: ../src/AchievementList.cpp:111
msgid "Play the development version of Me and My Shadow."
msgstr "Játék a Me and My Shadow fejlesztői változatával."
#: ../src/Addons.cpp:44 ../src/LevelPackManager.cpp:108
msgid "Levels"
msgstr "Szintek"
#: ../src/Addons.cpp:44
msgid "Single level which usually contain demanding puzzles"
msgstr "Egyetlen szint, mely általában igényes rejtvényeket tartalmaz"
#: ../src/Addons.cpp:45
msgid "Levelpacks"
msgstr "Szintcsomagok"
#: ../src/Addons.cpp:45
msgid "Collection of levels with the same author or style"
msgstr "Szintek gyűjteménye, adott szerzőtől vagy stílusban"
#: ../src/Addons.cpp:46
msgid "Themes"
msgstr "Témák"
#: ../src/Addons.cpp:46
msgid "Give every block and background a new look and feel"
msgstr "A tömbök és a háttér új külalakot kap"
#: ../src/Addons.cpp:55 ../src/TitleMenu.cpp:46
msgid "Addons"
msgstr "Bővítmények"
#: ../src/Addons.cpp:87
msgid "Unable to initialize addon menu:"
msgstr "Nem tudtam az alábbi bővítmény-menüt elindítani:"
#: ../src/Addons.cpp:95 ../src/Addons.cpp:158 ../src/Addons.cpp:662
#: ../src/Addons.cpp:690 ../src/CreditsMenu.cpp:89 ../src/LevelSelect.cpp:168
#: ../src/StatisticsScreen.cpp:159
msgid "Back"
msgstr "Vissza"
#: ../src/Addons.cpp:169
msgid "ERROR: unable to download addons file!"
msgstr "HIBA: a bővítmények letöltése sikertelen!"
# TRANSLATORS: addon_list is the name of a file and should not be translated.
#: ../src/Addons.cpp:182
msgid "ERROR: unable to load addon_list file!"
msgstr "HIBA: az addon_list fájl letöltése sikertelen!"
#: ../src/Addons.cpp:193
msgid "ERROR: Invalid file format of addons file!"
msgstr "HIBA: a bővítmény fájl formátuma nem jó!"
#: ../src/Addons.cpp:205
msgid "ERROR: Addon list version is unsupported!"
msgstr "HIBA: nem támogatott addon_list verzió!"
# TRANSLATORS: installed_addons is the name of a file and should not be translated.
#: ../src/Addons.cpp:226
msgid "ERROR: Unable to create the installed_addons file."
msgstr "HIBA: az installed_addons fájl létrehozása sikertelen."
#: ../src/Addons.cpp:238
msgid "ERROR: Invalid file format of the installed_addons!"
msgstr "HIBA: az installed_addons fájl formátuma nem jó!"
# TRANSLATORS: indicates the author of an addon.
#: ../src/Addons.cpp:389 ../src/Addons.cpp:621
#, c-format
msgid "by %s"
msgstr "írta: %s"
#: ../src/Addons.cpp:397
msgid "Installed"
msgstr "Telepítve"
#: ../src/Addons.cpp:402
msgid "Updatable"
msgstr "Frissíthető"
#: ../src/Addons.cpp:412
msgid "Not installed"
msgstr "Nincs telepítve"
#: ../src/Addons.cpp:625
#, c-format
msgid "Version: %d\n"
msgstr "Verzió: %d\n"
#: ../src/Addons.cpp:627
#, c-format
msgid "Installed version: %d\n"
msgstr "Telepített verzió: %d\n"
#: ../src/Addons.cpp:630
#, c-format
msgid "License: %s\n"
msgstr "Licensz: %s\n"
#: ../src/Addons.cpp:633
#, c-format
msgid "Website: %s\n"
msgstr "Weboldal: %s\n"
#: ../src/Addons.cpp:637
msgid "(No descriptions provided)"
msgstr "(Nincs hozzá leírás)"
#: ../src/Addons.cpp:657 ../src/Addons.cpp:684
msgid "Remove"
msgstr "Eltávolítás"
#: ../src/Addons.cpp:673
msgid "Update"
msgstr "Frissítés"
#: ../src/Addons.cpp:679
msgid "Install"
msgstr "Telepítés"
#: ../src/Addons.cpp:774
#, c-format
msgid "This addon can't be removed because it's needed by %s."
msgstr "Ez a bővítményt nem lehet eltávolítani, mert szükséges ehhez: %s."
#: ../src/Addons.cpp:774 ../src/Addons.cpp:1051
msgid "Dependency"
msgstr "Függőség"
#: ../src/Addons.cpp:803
#, c-format
msgid "WARNING: File '%s' appears to have been removed already."
msgstr "FIGYELMEZTETÉS: úgy tűnik, '%s' már el lett távolítva."
#: ../src/Addons.cpp:803 ../src/Addons.cpp:810 ../src/Addons.cpp:818
#: ../src/Addons.cpp:825 ../src/Addons.cpp:834 ../src/Addons.cpp:840
#: ../src/Addons.cpp:859 ../src/Addons.cpp:866 ../src/Addons.cpp:893
#: ../src/Addons.cpp:900 ../src/Addons.cpp:907 ../src/Addons.cpp:918
#: ../src/Addons.cpp:947 ../src/Addons.cpp:952 ../src/Addons.cpp:962
#: ../src/Addons.cpp:968 ../src/Addons.cpp:981 ../src/Addons.cpp:986
#: ../src/Addons.cpp:1008 ../src/Addons.cpp:1014 ../src/Addons.cpp:1044
msgid "Addon error"
msgstr "Bővítmény hiba"
#: ../src/Addons.cpp:810
#, c-format
msgid "ERROR: Unable to remove file '%s'!"
msgstr "HIBA: nem sikerült törölni a '%s' fájlt!"
#: ../src/Addons.cpp:818
#, c-format
msgid "WARNING: Directory '%s' appears to have been removed already."
msgstr "FIGYELMEZTETÉS: A '%s' könyvtárat már törölték."
#: ../src/Addons.cpp:825
#, c-format
msgid "ERROR: Unable to remove directory '%s'!"
msgstr "HIBA: Nem sikerült törölni a '%s' könyvtárat!"
#: ../src/Addons.cpp:834
#, c-format
msgid "WARNING: Level '%s' appears to have been removed already."
msgstr "FIGYELMEZTETÉS: A '%s' szint már törölve van."
#: ../src/Addons.cpp:840
#, c-format
msgid "ERROR: Unable to remove level '%s'!"
msgstr "HIBA: Nem sikerült törölni a '%s' szintet!"
#: ../src/Addons.cpp:859
#, c-format
msgid "WARNING: Levelpack directory '%s' appears to have been removed already."
msgstr "FIGYELMEZTETÉS: A szintcsomag '%s' könyvtára már törölve van."
#: ../src/Addons.cpp:866
#, c-format
msgid "ERROR: Unable to remove levelpack directory '%s'!"
msgstr "HIBA: Nem sikerült törölni a szintcsomag '%s' könyvtárat!"
#: ../src/Addons.cpp:893
#, c-format
msgid "ERROR: Unable to download addon file %s."
msgstr "HIBA: Nem sikerült letölteni a '%s' bővítmény fájlt."
#: ../src/Addons.cpp:900
#, c-format
msgid "ERROR: Unable to extract addon file %s."
msgstr "HIBA: Nem sikerült kibontani a '%s' bővítmény fáljt."
#: ../src/Addons.cpp:907
msgid "ERROR: Addon is missing metadata!"
msgstr "HIBA: A bővítmény nem tartalmaz meta-adatokat!"
#: ../src/Addons.cpp:918
msgid "ERROR: Invalid file format for metadata file!"
msgstr "HIBA: Érvénytelen meta-adat fáljformátum!"
#: ../src/Addons.cpp:947
#, c-format
msgid "WARNING: File '%s' already exists, addon may be broken or not working!"
msgstr ""
"FIGYELMEZTETÉS: A '%s' fájl már létezik, a bővitmény hibás, vagy nem működik!"
#: ../src/Addons.cpp:952
#, c-format
msgid ""
"WARNING: Unable to copy file '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
"FIGYELMEZTETÉS: A '%s' fájlt nem sikerült ide másolni: '%s', a bővítmény "
"hibás vagy nem működik!"
#: ../src/Addons.cpp:962
#, c-format
msgid ""
"WARNING: Destination directory '%s' already exists, addon may be broken or "
"not working!"
msgstr ""
"FIGYELMEZTETÉS: A '%s' célkönyvtár már létezik, a bővitmény hibás, vagy nem "
"működik!"
#: ../src/Addons.cpp:968 ../src/Addons.cpp:1014
#, c-format
msgid ""
"WARNING: Unable to move directory '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
"FIGYELMEZTETÉS: A '%s' könyvtárt nem sikerült ide másolni: '%s', a "
"bővítmény hibás vagy nem működik!"
#: ../src/Addons.cpp:981
#, c-format
msgid "WARNING: Level '%s' already exists, addon may be broken or not working!"
msgstr ""
"FIGYELMEZTETÉS: A '%s' szint már létezik, a bővitmény hibás, vagy nem "
"működik!"
#: ../src/Addons.cpp:986
#, c-format
msgid ""
"WARNING: Unable to copy level '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
"FIGYELMEZTETÉS: A '%s' szintet nem sikerült ide másolni: '%s', a bővítmény "
"hibás vagy nem működik!"
#: ../src/Addons.cpp:1008
#, c-format
msgid ""
"WARNING: Levelpack directory '%s' already exists, addon may be broken or not "
"working!"
msgstr ""
"FIGYELMEZTETÉS: A '%s' szintcsomag könyvtára már létezik, a bővitmény hibás, "
"vagy nem működik!"
#: ../src/Addons.cpp:1044
#, c-format
msgid "ERROR: Addon requires another addon (%s) which can't be found!"
msgstr "HIBA: A bővítmény másik bővítményt (%s) igényel, de az nem található!"
#: ../src/Addons.cpp:1051
#, c-format
msgid "The addon %s is needed and will be installed now."
msgstr "A %s bővítményre szükség van, és telepítve lesz most."
#: ../src/Block.cpp:822 ../src/LevelEditor.cpp:265
msgid "On"
msgstr "Be"
#: ../src/Block.cpp:823 ../src/LevelEditor.cpp:266
msgid "Off"
msgstr "Ki"
#: ../src/CommandManager.cpp:41
#, c-format
msgid "Undo %s"
msgstr "%s visszavonása"
#: ../src/CommandManager.cpp:43
msgid "Can't undo"
msgstr "Nem tudom visszavonni"
#: ../src/CommandManager.cpp:49
#, c-format
msgid "Redo %s"
msgstr "%s újra"
#: ../src/CommandManager.cpp:51
msgid "Can't redo"
msgstr "Nem lehet újra csinálni"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:190
msgid "Resize level"
msgstr "Szint átméretezése"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:807
msgid "Modify level property"
msgstr "Szint tulajdonság módosítása"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:919
#, c-format
msgid "Add scenery layer %s"
msgstr "Táj réteg %s hozzáadása"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:921
#, c-format
msgid "Delete scenery layer %s"
msgstr "Táj réteg %s törlése"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:951
#, c-format
msgid "Modify the property of scenery layer %s"
msgstr "Táj réteg tulajdonság %s módosítása"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:1040
#, c-format
msgid "Move %d object from layer %s to layer %s"
msgid_plural "Move %d objects from layer %s to layer %s"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d objektum átmozgatása %s rétegről %s rétegre"
+msgstr[1] "%d objektum átmozgatása %s rétegről %s rétegre"
#: ../src/CreditsMenu.cpp:35 ../src/TitleMenu.cpp:53
msgid "Credits"
msgstr ""
# TRANSLATORS: Font used in GUI:
# - Use "knewave" for languages using Latin and Latin-derived alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:569 ../src/Functions.cpp:570 ../src/Functions.cpp:571
#: ../src/Functions.cpp:588
msgid "knewave"
-msgstr "DejaVuSansCondensed-Oblique"
+msgstr "knewave"
# TRANSLATORS: Font used for normal text:
# - Use "Blokletters-Viltstift" for languages using Latin and Latin-derived alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:575
msgid "Blokletters-Viltstift"
-msgstr "DejaVuSansCondensed-Oblique"
+msgstr "Blokletters-Viltstift"
#: ../src/Functions.cpp:674
msgid "Loading..."
-msgstr ""
+msgstr "Betöltés..."
#: ../src/Functions.cpp:1243 ../src/Functions.cpp:1270
#: ../src/LevelEditor.cpp:559 ../src/LevelEditor.cpp:693
#: ../src/LevelEditor.cpp:758 ../src/LevelEditor.cpp:821
#: ../src/LevelEditor.cpp:908 ../src/LevelEditor.cpp:1033
#: ../src/LevelEditor.cpp:1083 ../src/LevelEditor.cpp:1180
#: ../src/LevelEditor.cpp:1244 ../src/LevelEditor.cpp:2923
#: ../src/LevelEditSelect.cpp:244 ../src/LevelEditSelect.cpp:277
#: ../src/LevelEditSelect.cpp:317
msgid "OK"
-msgstr ""
+msgstr "OK"
#: ../src/Functions.cpp:1244 ../src/Functions.cpp:1256
#: ../src/Functions.cpp:1266 ../src/LevelEditor.cpp:565
#: ../src/LevelEditor.cpp:699 ../src/LevelEditor.cpp:764
#: ../src/LevelEditor.cpp:827 ../src/LevelEditor.cpp:914
#: ../src/LevelEditor.cpp:1039 ../src/LevelEditor.cpp:1089
#: ../src/LevelEditor.cpp:1186 ../src/LevelEditor.cpp:1250
#: ../src/LevelEditor.cpp:2929 ../src/LevelEditSelect.cpp:248
#: ../src/LevelEditSelect.cpp:281 ../src/LevelEditSelect.cpp:321
#: ../src/OptionsMenu.cpp:289
msgid "Cancel"
-msgstr ""
+msgstr "Mégse"
#: ../src/Functions.cpp:1248
msgid "Abort"
-msgstr ""
+msgstr "Megszakít"
#: ../src/Functions.cpp:1249 ../src/Functions.cpp:1265
msgid "Retry"
-msgstr ""
+msgstr "Újra"
#: ../src/Functions.cpp:1250
msgid "Ignore"
-msgstr ""
+msgstr "Mellőz"
#: ../src/Functions.cpp:1254 ../src/Functions.cpp:1260
msgid "Yes"
-msgstr ""
+msgstr "Igen"
#: ../src/Functions.cpp:1255 ../src/Functions.cpp:1261
msgid "No"
-msgstr ""
+msgstr "Nem"
# TRANSLATORS: Please do not remove %s or %d from your translation:
# - %d means the level number in a levelpack
# - %s means the name of current level
#: ../src/Game.cpp:280 ../src/Game.cpp:1236
#, c-format
msgid "Level %d %s"
-msgstr ""
+msgstr "%d %s szint"
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:915
#, c-format
msgid "Press %s key to save the game."
-msgstr ""
+msgstr "Nyomd meg a %s gombot, hogy mentést készíts a játékról."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:920
#, c-format
msgid "Press %s key to swap the position of player and shadow."
-msgstr ""
+msgstr "Nyomd meg a %s gombot, hogy a játékos és árnyéka helyet cseréljen."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:925
#, c-format
msgid "Press %s key to activate the switch."
-msgstr ""
+msgstr "Nyomd meg a %s gombot, hogy a kapcsolót aktiváld."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:930
#, c-format
msgid "Press %s key to teleport."
-msgstr ""
+msgstr "Nyomd meg a %s gombot a teleportáláshoz."
# TRANSLATORS: Please do not remove %s from your translation:
# - first %s means currently configured key to restart game
# - Second %s means configured key to load from last save
#: ../src/Game.cpp:972
#, c-format
msgid "Press %s to restart current level or press %s to load the game."
msgstr ""
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with currently configured key to restart game
#: ../src/Game.cpp:983
#, c-format
msgid "Press %s to restart current level."
msgstr ""
#: ../src/Game.cpp:996
msgid "Your shadow has died."
msgstr ""
#: ../src/Game.cpp:1052
#, c-format
msgid "%d recording"
msgid_plural "%d recordings"
msgstr[0] ""
msgstr[1] ""
#: ../src/Game.cpp:1224
msgid "You've finished:"
msgstr ""
# TRANSLATORS: Please do not remove %-.2f from your translation:
# - %-.2f means time in seconds
# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1291
#, c-format
msgid "Time: %-.2fs"
msgstr ""
# TRANSLATORS: Please do not remove %-.2f from your translation:
# - %-.2f means time in seconds
# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1300
#, c-format
msgid "Best time: %-.2fs"
msgstr ""
#: ../src/Game.cpp:1311
#, c-format
msgid "Target time: %-.2fs"
msgstr ""
# TRANSLATORS: Please do not remove %d from your translation:
# - %d means the number of recordings user has made
#: ../src/Game.cpp:1332
#, c-format
msgid "Recordings: %d"
msgstr ""
# TRANSLATORS: Please do not remove %d from your translation:
# - %d means the number of recordings user has made
#: ../src/Game.cpp:1340
#, c-format
msgid "Best recordings: %d"
msgstr ""
#: ../src/Game.cpp:1350
#, c-format
msgid "Target recordings: %d"
msgstr ""
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with name of a prize medal (gold, silver or bronze)
#: ../src/Game.cpp:1363
#, c-format
msgid "You earned the %s medal"
msgstr ""
#: ../src/Game.cpp:1363
msgid "GOLD"
msgstr ""
#: ../src/Game.cpp:1363
msgid "SILVER"
msgstr ""
#: ../src/Game.cpp:1363
msgid "BRONZE"
msgstr ""
# TRANSLATORS: used as return to the level selector menu
#: ../src/Game.cpp:1390
msgid "Menu"
msgstr ""
# TRANSLATORS: used as restart level
#: ../src/Game.cpp:1397 ../src/InputManager.cpp:47
msgid "Restart"
msgstr ""
# TRANSLATORS: used as next level
#: ../src/Game.cpp:1404
msgid "Next"
msgstr ""
#: ../src/Game.cpp:1430
msgid "Game replay is done."
msgstr ""
#: ../src/Game.cpp:1430
msgid "Game Replay"
msgstr ""
#: ../src/Game.cpp:1767 ../src/Game.cpp:1769
msgid "Congratulations"
msgstr ""
#: ../src/Game.cpp:1769
msgid "You have finished the levelpack!"
msgstr ""
#: ../src/InputManager.cpp:46
msgid "Up (in menu)"
msgstr ""
#: ../src/InputManager.cpp:46
msgid "Down (in menu)"
msgstr ""
#: ../src/InputManager.cpp:46
msgid "Left"
msgstr ""
#: ../src/InputManager.cpp:46
msgid "Right"
msgstr ""
#: ../src/InputManager.cpp:46
msgid "Jump"
msgstr ""
#: ../src/InputManager.cpp:46
msgid "Action"
msgstr ""
#: ../src/InputManager.cpp:46
msgid "Space (Record)"
msgstr ""
#: ../src/InputManager.cpp:46
msgid "Cancel recording"
msgstr ""
#: ../src/InputManager.cpp:47
msgid "Escape"
msgstr ""
#: ../src/InputManager.cpp:47
msgid "Tab (View shadow/Level prop.)"
msgstr ""
#: ../src/InputManager.cpp:47
msgid "Save game (in editor)"
msgstr ""
#: ../src/InputManager.cpp:47
msgid "Load game"
msgstr ""
#: ../src/InputManager.cpp:47
msgid "Swap (in editor)"
msgstr ""
#: ../src/InputManager.cpp:48
msgid "Teleport (in editor)"
msgstr ""
#: ../src/InputManager.cpp:48
msgid "Suicide (in editor)"
msgstr ""
#: ../src/InputManager.cpp:48
msgid "Shift (in editor)"
msgstr ""
#: ../src/InputManager.cpp:48
msgid "Next block type (in Editor)"
msgstr ""
#: ../src/InputManager.cpp:49
msgid "Previous block type (in editor)"
msgstr ""
#: ../src/InputManager.cpp:49
msgid "Select (in menu)"
msgstr ""
# TRANSLAOTRS: This is used when the name of the key code is not found.
#: ../src/InputManager.cpp:156
#, c-format
msgid "(Key %d)"
msgstr ""
#: ../src/InputManager.cpp:163
#, c-format
msgid "Joystick axis %d %s"
msgstr ""
#: ../src/InputManager.cpp:166
#, c-format
msgid "Joystick button %d"
msgstr ""
#: ../src/InputManager.cpp:171
#, c-format
msgid "Joystick hat %d left"
msgstr ""
#: ../src/InputManager.cpp:174
#, c-format
msgid "Joystick hat %d right"
msgstr ""
#: ../src/InputManager.cpp:177
#, c-format
msgid "Joystick hat %d up"
msgstr ""
#: ../src/InputManager.cpp:180
#, c-format
msgid "Joystick hat %d down"
msgstr ""
# TRANSLAOTRS: This is used when the JOYSTICK_HAT value is invalid.
#: ../src/InputManager.cpp:185
#, c-format
msgid "Joystick hat %d %d"
msgstr ""
#: ../src/InputManager.cpp:202
msgid "OR"
msgstr ""
#: ../src/InputManager.cpp:416
msgid "Select an item and press a key to change it."
msgstr ""
#: ../src/InputManager.cpp:419
msgid "Press backspace to clear the selected item."
msgstr ""
#: ../src/LevelEditor.cpp:56
msgid "Block"
msgstr ""
#: ../src/LevelEditor.cpp:56
msgid "Player Start"
msgstr ""
#: ../src/LevelEditor.cpp:56
msgid "Shadow Start"
msgstr ""
#: ../src/LevelEditor.cpp:57
msgid "Exit"
msgstr ""
#: ../src/LevelEditor.cpp:57
msgid "Shadow Block"
msgstr ""
#: ../src/LevelEditor.cpp:57
msgid "Spikes"
msgstr ""
#: ../src/LevelEditor.cpp:58
msgid "Checkpoint"
msgstr ""
#: ../src/LevelEditor.cpp:58 ../src/LevelEditSelect.cpp:312
msgid "Swap"
msgstr ""
#: ../src/LevelEditor.cpp:58
msgid "Fragile"
msgstr ""
#: ../src/LevelEditor.cpp:59
msgid "Moving Block"
msgstr ""
#: ../src/LevelEditor.cpp:59
msgid "Moving Shadow Block"
msgstr ""
#: ../src/LevelEditor.cpp:59
msgid "Moving Spikes"
msgstr ""
#: ../src/LevelEditor.cpp:60
msgid "Teleporter"
msgstr ""
#: ../src/LevelEditor.cpp:60
msgid "Button"
msgstr ""
#: ../src/LevelEditor.cpp:60
msgid "Switch"
msgstr ""
#: ../src/LevelEditor.cpp:61
msgid "Conveyor Belt"
msgstr ""
#: ../src/LevelEditor.cpp:61
msgid "Shadow Conveyor Belt"
msgstr ""
#: ../src/LevelEditor.cpp:61
msgid "Notification Block"
msgstr ""
#: ../src/LevelEditor.cpp:61
msgid "Collectable"
msgstr ""
#: ../src/LevelEditor.cpp:61
msgid "Pushable"
msgstr ""
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:310
msgid "Select"
msgstr ""
#: ../src/LevelEditor.cpp:65
msgid "Add"
msgstr ""
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:311
msgid "Delete"
msgstr ""
#: ../src/LevelEditor.cpp:65 ../src/LevelPlaySelect.cpp:66
#: ../src/TitleMenu.cpp:43
msgid "Play"
msgstr ""
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:2852
msgid "Level settings"
msgstr ""
#: ../src/LevelEditor.cpp:65
msgid "Save level"
msgstr ""
#: ../src/LevelEditor.cpp:65
msgid "Back to menu"
msgstr ""
#: ../src/LevelEditor.cpp:65
msgid "Configure"
msgstr ""
#: ../src/LevelEditor.cpp:84
#, c-format
msgid "%s (Scenery)"
msgstr ""
#: ../src/LevelEditor.cpp:267
msgid "Toggle"
msgstr ""
#: ../src/LevelEditor.cpp:270
msgid "Complete"
msgstr ""
#: ../src/LevelEditor.cpp:271
msgid "One step"
msgstr ""
#: ../src/LevelEditor.cpp:272
msgid "Two steps"
msgstr ""
#: ../src/LevelEditor.cpp:273
msgid "Gone"
msgstr ""
#: ../src/LevelEditor.cpp:291
msgid "Negative infinity"
msgstr ""
#: ../src/LevelEditor.cpp:293
msgid "Zero"
msgstr ""
#: ../src/LevelEditor.cpp:295
msgid "Level size"
msgstr ""
#: ../src/LevelEditor.cpp:297
msgid "Positive infinity"
msgstr ""
#: ../src/LevelEditor.cpp:299
msgid "Default"
msgstr ""
#: ../src/LevelEditor.cpp:308
msgid "Deselect"
msgstr ""
#: ../src/LevelEditor.cpp:318 ../src/LevelEditor.cpp:1136
#, c-format
msgid "Horizontal repeat start: %s"
msgstr ""
#: ../src/LevelEditor.cpp:320 ../src/LevelEditor.cpp:1137
#, c-format
msgid "Horizontal repeat end: %s"
msgstr ""
#: ../src/LevelEditor.cpp:322 ../src/LevelEditor.cpp:1138
#, c-format
msgid "Vertical repeat start: %s"
msgstr ""
#: ../src/LevelEditor.cpp:324 ../src/LevelEditor.cpp:1139
#, c-format
msgid "Vertical repeat end: %s"
msgstr ""
#: ../src/LevelEditor.cpp:329 ../src/LevelEditor.cpp:1150
msgid "Custom scenery"
msgstr ""
#: ../src/LevelEditor.cpp:335 ../src/LevelEditor.cpp:600
#: ../src/LevelEditor.cpp:602
msgid "Visible"
msgstr ""
#: ../src/LevelEditor.cpp:344
msgid "Link"
msgstr ""
#: ../src/LevelEditor.cpp:345
msgid "Remove Links"
msgstr ""
#: ../src/LevelEditor.cpp:349 ../src/LevelEditor.cpp:624
#: ../src/LevelEditor.cpp:626
msgid "Automatic"
msgstr ""
#: ../src/LevelEditor.cpp:359 ../src/LevelEditor.cpp:649
#, c-format
msgid "Behavior: %s"
msgstr ""
#: ../src/LevelEditor.cpp:362
msgid "Path"
msgstr ""
#: ../src/LevelEditor.cpp:363
msgid "Remove Path"
msgstr ""
#: ../src/LevelEditor.cpp:365 ../src/LevelEditor.cpp:371
#: ../src/LevelEditor.cpp:587 ../src/LevelEditor.cpp:589
msgid "Activated"
msgstr ""
#: ../src/LevelEditor.cpp:366 ../src/LevelEditor.cpp:612
#: ../src/LevelEditor.cpp:614
msgid "Looping"
msgstr ""
#: ../src/LevelEditor.cpp:372 ../src/LevelEditor.cpp:3526
msgid "Speed"
msgstr ""
#: ../src/LevelEditor.cpp:378 ../src/LevelEditor.cpp:668
#, c-format
msgid "State: %s"
msgstr ""
#: ../src/LevelEditor.cpp:382 ../src/LevelEditor.cpp:3511
msgid "Message"
msgstr ""
#: ../src/LevelEditor.cpp:384 ../src/LevelEditor.cpp:1202
#: ../src/LevelEditor.cpp:3825
msgid "Appearance"
msgstr ""
#: ../src/LevelEditor.cpp:389 ../src/LevelEditor.cpp:431
#: ../src/LevelEditor.cpp:715
msgid "Scripting"
msgstr ""
#: ../src/LevelEditor.cpp:402 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Background layer: %s"
msgstr ""
#: ../src/LevelEditor.cpp:409 ../src/LevelEditor.cpp:866
#: ../src/LevelEditor.cpp:884
msgid "Blocks layer"
msgstr ""
#: ../src/LevelEditor.cpp:417 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Foreground layer: %s"
msgstr ""
#: ../src/LevelEditor.cpp:423
msgid "Add new layer"
msgstr ""
#: ../src/LevelEditor.cpp:424
msgid "Delete selected layer"
msgstr ""
#: ../src/LevelEditor.cpp:425
msgid "Configure selected layer"
msgstr ""
#: ../src/LevelEditor.cpp:426
msgid "Move selected object to layer"
msgstr ""
#: ../src/LevelEditor.cpp:430 ../src/OptionsMenu.cpp:55
msgid "Settings"
msgstr ""
#: ../src/LevelEditor.cpp:463
msgid ""
"NOTE: the layers are sorted by name alphabetically.\n"
"The layer is background layer if its name is < 'f'\n"
"by dictionary order, otherwise it's foreground layer."
msgstr ""
#: ../src/LevelEditor.cpp:539
msgid "Notification block"
msgstr ""
#: ../src/LevelEditor.cpp:545
msgid "Enter message here:"
msgstr ""
#: ../src/LevelEditor.cpp:646
msgid "Behavior"
msgstr ""
#: ../src/LevelEditor.cpp:665
msgid "State"
msgstr ""
#: ../src/LevelEditor.cpp:673
msgid "Conveyor belt speed"
msgstr ""
#: ../src/LevelEditor.cpp:679
msgid "Enter speed here:"
msgstr ""
#: ../src/LevelEditor.cpp:690
msgid "NOTE: 1 Speed = 0.08 block/s"
msgstr ""
#: ../src/LevelEditor.cpp:721
msgid "Id:"
msgstr ""
#: ../src/LevelEditor.cpp:787
msgid "Level Scripting"
msgstr ""
#: ../src/LevelEditor.cpp:892
msgid "Add layer"
msgstr ""
#: ../src/LevelEditor.cpp:898
msgid "Enter the layer name:"
msgstr ""
#: ../src/LevelEditor.cpp:943
#, c-format
msgid "Are you sure you want to delete layer '%s'?"
msgstr ""
#: ../src/LevelEditor.cpp:944
msgid "Delete layer"
msgstr ""
#: ../src/LevelEditor.cpp:968
msgid "Layer settings"
msgstr ""
#: ../src/LevelEditor.cpp:974
msgid "Layer name:"
msgstr ""
#: ../src/LevelEditor.cpp:989
msgid "Layer moving speed (1 speed = 0.8 block/s):"
msgstr ""
#: ../src/LevelEditor.cpp:1010
msgid "Speed of following camera:"
msgstr ""
#: ../src/LevelEditor.cpp:1062
msgid "Move to layer"
msgstr ""
#: ../src/LevelEditor.cpp:1068
msgid "Enter the layer name (create new layer if necessary):"
msgstr ""
#: ../src/LevelEditor.cpp:1132
msgid "Repeat mode"
msgstr ""
#: ../src/LevelEditor.cpp:1156
msgid "Custom scenery:"
msgstr ""
#: ../src/LevelEditor.cpp:1219
msgid "(Use the default appearance for this block)"
msgstr ""
# TRANSLATORS: Block name
# TRANSLATORS: Context: Resize/Move ...
# TRANSLATORS: Context: Add/Remove ...
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1465 ../src/LevelEditor.cpp:1707
#: ../src/LevelEditor.cpp:1723 ../src/LevelEditor.cpp:1772
#: ../src/LevelEditor.cpp:4400
msgid "Custom scenery block"
msgstr ""
#: ../src/LevelEditor.cpp:1673
msgid "Toolbox"
msgstr ""
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Resize %s"
msgstr ""
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Move %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1713
#, c-format
msgid "Move %d object"
msgid_plural "Move %d objects"
msgstr[0] ""
msgstr[1] ""
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Add %s"
msgstr ""
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Remove %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1729
#, c-format
msgid "Add %d object"
msgid_plural "Add %d objects"
msgstr[0] ""
msgstr[1] ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1731
#, c-format
msgid "Remove %d object"
msgid_plural "Remove %d objects"
msgstr[0] ""
msgstr[1] ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1739
#, c-format
msgid "Add path to %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1741
#, c-format
msgid "Remove a path point from %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1747
#, c-format
msgid "Remove all paths from %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1753
#, c-format
msgid "Add link from %s to %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1759
#, c-format
msgid "Remove all links from %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1766
msgid "Modify the %2 property of %1"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1818
#, c-format
msgid "Edit the script of %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1821
msgid "Edit the script of level"
msgstr ""
#: ../src/LevelEditor.cpp:2146 ../src/LevelEditor.cpp:2226
msgid "The level has unsaved changes."
msgstr ""
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Are you sure you want to quit?"
msgstr ""
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Quit prompt"
msgstr ""
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
#, c-format
msgid "Level \"%s\" saved"
msgstr ""
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
msgid "Saved"
msgstr ""
#: ../src/LevelEditor.cpp:2859 ../src/LevelEditSelect.cpp:208
msgid "Name:"
msgstr ""
#: ../src/LevelEditor.cpp:2866
msgid "Theme:"
msgstr ""
#: ../src/LevelEditor.cpp:2873
msgid "Examples: %DATA%/themes/classic"
msgstr ""
#: ../src/LevelEditor.cpp:2875
msgid "or %USER%/themes/Orange"
msgstr ""
#: ../src/LevelEditor.cpp:2878
msgid "Music:"
msgstr ""
#: ../src/LevelEditor.cpp:2887
msgid "Target time (s):"
msgstr ""
#: ../src/LevelEditor.cpp:2903
msgid "Target recordings:"
msgstr ""
#: ../src/LevelEditor.cpp:2919
msgid "Restart level editor is required"
msgstr ""
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3718
#: ../src/LevelEditor.cpp:3739
msgid "Please enter a layer name."
msgstr ""
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3683
#: ../src/LevelEditor.cpp:3718 ../src/LevelEditor.cpp:3722
#: ../src/LevelEditor.cpp:3739 ../src/LevelEditor.cpp:3743
#: ../src/LevelEditSelect.cpp:644 ../src/LevelEditSelect.cpp:683
#: ../src/LevelEditSelect.cpp:688 ../src/LevelEditSelect.cpp:693
#: ../src/LevelEditSelect.cpp:698 ../src/LevelEditSelect.cpp:796
msgid "Error"
msgstr ""
#: ../src/LevelEditor.cpp:3683 ../src/LevelEditor.cpp:3722
#, c-format
msgid "The layer '%s' already exists."
msgstr ""
#: ../src/LevelEditor.cpp:3743
msgid "Source and destination layers are the same."
msgstr ""
#: ../src/LevelEditor.cpp:3760
msgid "Scenery"
msgstr ""
#: ../src/LevelEditor.cpp:4190 ../src/LevelEditor.cpp:4218
#, c-format
msgid "Speed: %d = %0.2f block/s"
msgstr ""
#: ../src/LevelEditor.cpp:4203
msgid "Stop at this point"
msgstr ""
#: ../src/LevelEditor.cpp:4208
#, c-format
msgid "Pause: %d = %0.3fs"
msgstr ""
#: ../src/LevelEditSelect.cpp:41 ../src/TitleMenu.cpp:45
msgid "Map Editor"
msgstr ""
#: ../src/LevelEditSelect.cpp:66
msgid "New Levelpack"
msgstr ""
#: ../src/LevelEditSelect.cpp:71
msgid "Pack Properties"
msgstr ""
#: ../src/LevelEditSelect.cpp:76
msgid "Remove Pack"
msgstr ""
#: ../src/LevelEditSelect.cpp:81
msgid "Move Map"
msgstr ""
#: ../src/LevelEditSelect.cpp:89
msgid "Remove Map"
msgstr ""
#: ../src/LevelEditSelect.cpp:94
msgid "Edit Map"
msgstr ""
#: ../src/LevelEditSelect.cpp:205
msgid "Properties"
msgstr ""
#: ../src/LevelEditSelect.cpp:217
msgid "Description:"
msgstr ""
#: ../src/LevelEditSelect.cpp:226
msgid "Congratulation text:"
msgstr ""
#: ../src/LevelEditSelect.cpp:235
msgid "Music list:"
msgstr ""
#: ../src/LevelEditSelect.cpp:265 ../src/LevelEditSelect.cpp:485
msgid "Add level"
msgstr ""
#: ../src/LevelEditSelect.cpp:268
msgid "File name:"
msgstr ""
#: ../src/LevelEditSelect.cpp:293
msgid "Move level"
msgstr ""
#: ../src/LevelEditSelect.cpp:296
msgid "Level: "
msgstr ""
#: ../src/LevelEditSelect.cpp:310
msgid "Before"
msgstr ""
#: ../src/LevelEditSelect.cpp:311
msgid "After"
msgstr ""
#: ../src/LevelEditSelect.cpp:368 ../src/LevelPlaySelect.cpp:124
msgid "Individual levels which are not contained in any level packs"
msgstr ""
#: ../src/LevelEditSelect.cpp:577
#, c-format
msgid "Are you sure remove the level pack '%s'?"
msgstr ""
#: ../src/LevelEditSelect.cpp:577 ../src/LevelEditSelect.cpp:607
msgid "Remove prompt"
msgstr ""
#: ../src/LevelEditSelect.cpp:607
#, c-format
msgid "Are you sure remove the map '%s'?"
msgstr ""
#: ../src/LevelEditSelect.cpp:644
msgid "Levelpack name cannot be empty."
msgstr ""
#: ../src/LevelEditSelect.cpp:683
#, c-format
msgid "The levelpack directory '%s' already exists!"
msgstr ""
#: ../src/LevelEditSelect.cpp:688
#, c-format
msgid "Unable to create levelpack directory '%s'!"
msgstr ""
#: ../src/LevelEditSelect.cpp:693
#, c-format
msgid "The levelpack file '%s' already exists!"
msgstr ""
#: ../src/LevelEditSelect.cpp:698
#, c-format
msgid "Unable to create levelpack file '%s'!"
msgstr ""
#: ../src/LevelEditSelect.cpp:758
msgid "No file name given for the new level."
msgstr ""
#: ../src/LevelEditSelect.cpp:758
msgid "Missing file name"
msgstr ""
#: ../src/LevelEditSelect.cpp:796
#, c-format
msgid "The file %s already exists."
msgstr ""
#: ../src/LevelEditSelect.cpp:849
msgid "The entered level number isn't valid!"
msgstr ""
#: ../src/LevelEditSelect.cpp:849
msgid "Illegal number"
msgstr ""
#: ../src/LevelInfoRender.cpp:19
msgid "Choose a level"
msgstr ""
#: ../src/LevelInfoRender.cpp:20
msgid "Time:"
msgstr ""
#: ../src/LevelInfoRender.cpp:21 ../src/StatisticsScreen.cpp:259
msgid "Recordings:"
msgstr ""
#: ../src/LevelPackManager.cpp:124
msgid "Custom Levels"
msgstr ""
#: ../src/LevelPlaySelect.cpp:41
msgid "Select Level"
msgstr ""
# TRANSLATORS: Used for button which clear any level progress like unlocked levels and highscores.
#: ../src/OptionsMenu.cpp:66
msgid "Clear Progress"
msgstr ""
#: ../src/OptionsMenu.cpp:109
msgid "General"
msgstr ""
#: ../src/OptionsMenu.cpp:110
msgid "Controls"
msgstr ""
#: ../src/OptionsMenu.cpp:121
msgid "Music"
msgstr ""
#: ../src/OptionsMenu.cpp:129
msgid "Sound"
msgstr ""
#: ../src/OptionsMenu.cpp:137
msgid "Resolution"
msgstr ""
#: ../src/OptionsMenu.cpp:177
msgid "Language"
msgstr ""
# TRANSLATORS: as detect user's language automatically
#: ../src/OptionsMenu.cpp:185
msgid "Auto-Detect"
msgstr ""
#: ../src/OptionsMenu.cpp:209
msgid "Theme"
msgstr ""
#: ../src/OptionsMenu.cpp:247
msgid "Internet proxy"
msgstr ""
#: ../src/OptionsMenu.cpp:256
msgid "Fullscreen"
msgstr ""
#: ../src/OptionsMenu.cpp:261
msgid "Quick record"
msgstr ""
#: ../src/OptionsMenu.cpp:266
msgid "Internet"
msgstr ""
#: ../src/OptionsMenu.cpp:271
msgid "Fade transition"
msgstr ""
#: ../src/OptionsMenu.cpp:294
msgid "Save Changes"
msgstr ""
#: ../src/OptionsMenu.cpp:513
msgid "Do you really want to reset level progress?"
msgstr ""
#: ../src/OptionsMenu.cpp:513
msgid "Warning"
msgstr ""
#: ../src/StatisticsManager.cpp:386
msgid "New achievement:"
msgstr ""
#: ../src/StatisticsManager.cpp:394
#, c-format
msgid "Achieved on %s"
msgstr ""
#: ../src/StatisticsManager.cpp:400
msgid "Unknown achievement"
msgstr ""
#: ../src/StatisticsManager.cpp:406
#, c-format
msgid "Achieved %1.0f%%"
msgstr ""
#: ../src/StatisticsManager.cpp:410
msgid "Not achieved"
msgstr ""
#: ../src/StatisticsScreen.cpp:57 ../src/TitleMenu.cpp:55
msgid "Achievements and Statistics"
msgstr ""
#: ../src/StatisticsScreen.cpp:166
msgid "Achievements"
msgstr ""
#: ../src/StatisticsScreen.cpp:167
msgid "Statistics"
msgstr ""
#: ../src/StatisticsScreen.cpp:234
msgid "Total"
msgstr ""
#: ../src/StatisticsScreen.cpp:246
msgid "Traveling distance (m)"
msgstr ""
#: ../src/StatisticsScreen.cpp:247
msgid "Jump times"
msgstr ""
#: ../src/StatisticsScreen.cpp:248
msgid "Die times"
msgstr ""
#: ../src/StatisticsScreen.cpp:249
msgid "Squashed times"
msgstr ""
#: ../src/StatisticsScreen.cpp:260
msgid "Switch pulled times:"
msgstr ""
#: ../src/StatisticsScreen.cpp:261
msgid "Swap times:"
msgstr ""
#: ../src/StatisticsScreen.cpp:262
msgid "Save times:"
msgstr ""
#: ../src/StatisticsScreen.cpp:263
msgid "Load times:"
msgstr ""
#: ../src/StatisticsScreen.cpp:268
msgid "Completed levels:"
msgstr ""
#: ../src/StatisticsScreen.cpp:306
msgid "In-game time:"
msgstr ""
#: ../src/StatisticsScreen.cpp:308
msgid "Level editing time:"
msgstr ""
#: ../src/StatisticsScreen.cpp:310
msgid "Created levels:"
msgstr ""
#: ../src/TitleMenu.cpp:44
msgid "Options"
msgstr ""
#: ../src/TitleMenu.cpp:47
msgid "Quit"
msgstr ""
#: ../src/TitleMenu.cpp:131
msgid "Enable internet in order to install addons."
msgstr ""
#: ../src/TitleMenu.cpp:131
msgid "Internet disabled"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Return"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Escape"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Backspace"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Tab"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Space"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "CapsLock"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PrintScreen"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "ScrollLock"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Pause"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Insert"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Home"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageUp"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Delete"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "End"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageDown"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Down"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Up"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Numlock"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "SysReq"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Ctrl"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Shift"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Alt"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left GUI"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Ctrl"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Shift"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Alt"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right GUI"
msgstr ""
diff --git a/data/locale/nb.po b/data/locale/nb.po
index 468aeed..0a936b5 100644
--- a/data/locale/nb.po
+++ b/data/locale/nb.po
@@ -1,2091 +1,2151 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the meandmyshadow package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: meandmyshadow 0.5svn\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-09 16:18+0800\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Automatically generated\n"
-"Language-Team: none\n"
+"PO-Revision-Date: 2018-10-05 04:29+0000\n"
+"Last-Translator: Petter Reinholdtsen <pere-weblate@hungry.com>\n"
+"Language-Team: Norwegian Bokmål <https://hosted.weblate.org/projects/"
+"me-and-my-shadow/translations/nb/>\n"
"Language: nb\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 3.2-dev\n"
#: ../src/AchievementList.cpp:43
msgid "Newbie"
-msgstr ""
+msgstr "Nybegynner"
#: ../src/AchievementList.cpp:43
msgid "Complete a level."
-msgstr ""
+msgstr "Fullføre nivå."
#: ../src/AchievementList.cpp:44
msgid "Experienced player"
-msgstr ""
+msgstr "Erfaren spiller"
#: ../src/AchievementList.cpp:44
msgid "Complete 50 levels."
-msgstr ""
+msgstr "Fullføre 50 nivåer."
#: ../src/AchievementList.cpp:45
msgid "Good job!"
-msgstr ""
+msgstr "Godt gjort!"
#: ../src/AchievementList.cpp:45
msgid "Receive a gold medal."
-msgstr ""
+msgstr "Motta en gullmedalje."
#: ../src/AchievementList.cpp:46
msgid "Expert"
-msgstr ""
+msgstr "Ekspert"
#: ../src/AchievementList.cpp:46
msgid "Earn 50 gold medal."
-msgstr ""
+msgstr "Motta 50 gullmedaljer."
#: ../src/AchievementList.cpp:48
msgid "Graduate"
-msgstr ""
+msgstr "Avanser"
#: ../src/AchievementList.cpp:48
msgid "Complete the tutorial level pack."
-msgstr ""
+msgstr "Fullfør veiledningsnivåpakken."
#: ../src/AchievementList.cpp:49
msgid "Outstanding graduate"
-msgstr ""
+msgstr "Fremragende disippel"
#: ../src/AchievementList.cpp:49
msgid "Complete the tutorial level pack with gold for all levels."
-msgstr ""
+msgstr "Fullfør veiledningsnivåpakken med gull på alle nivåer."
#: ../src/AchievementList.cpp:51
msgid "Hooked"
-msgstr ""
+msgstr "Hektet"
#: ../src/AchievementList.cpp:51
+#, fuzzy
msgid "Play Me and My Shadow for more than 2 hours."
-msgstr ""
+msgstr "Spill Jeg og skyggen min i mer enn to timer."
#: ../src/AchievementList.cpp:52
msgid "Loyal fan of Me and My Shadow"
-msgstr ""
+msgstr "Lojal tilhenger av Jeg og skyggen min"
#: ../src/AchievementList.cpp:52
+#, fuzzy
msgid "Play Me and My Shadow for more than 24 hours."
-msgstr ""
+msgstr "Spill Jeg og skyggen min i mer enn 24 timer."
#: ../src/AchievementList.cpp:54
msgid "Constructor"
-msgstr ""
+msgstr "Byggmester"
#: ../src/AchievementList.cpp:54
msgid "Use the level editor for more than 2 hours."
-msgstr ""
+msgstr "Bruk nivåredigereren i mer enn to timer."
#: ../src/AchievementList.cpp:55
msgid "The creator"
-msgstr ""
+msgstr "Skaperen"
#: ../src/AchievementList.cpp:55
msgid "Use the level editor for more than 24 hours."
-msgstr ""
+msgstr "Bruk nivåredigereren i mer enn 24 timer."
#: ../src/AchievementList.cpp:57
msgid "Look, cute level!"
-msgstr ""
+msgstr "Se, søtt nivå!"
#: ../src/AchievementList.cpp:57
msgid "Create a level for the first time."
-msgstr ""
+msgstr "Lag et nivå for første gang."
#: ../src/AchievementList.cpp:58
msgid "The level museum"
-msgstr ""
+msgstr "Nivåmuseet"
#: ../src/AchievementList.cpp:58
msgid "Create 50 levels."
-msgstr ""
+msgstr "Lag 50 nivåer."
#: ../src/AchievementList.cpp:60
msgid "Hello, World!"
-msgstr ""
+msgstr "Hei, verden!"
#: ../src/AchievementList.cpp:60
msgid "Write a script for the first time."
-msgstr ""
+msgstr "Skriv et skript for første gang."
#: ../src/AchievementList.cpp:62
msgid "Frog"
-msgstr ""
+msgstr "Frosk"
#: ../src/AchievementList.cpp:62
msgid "Jump 1000 times."
-msgstr ""
+msgstr "Hopp 1000 ganger."
#: ../src/AchievementList.cpp:64
msgid "Wanderer"
-msgstr ""
+msgstr "Vandrer"
#: ../src/AchievementList.cpp:64
msgid "Travel 100 meters."
-msgstr ""
+msgstr "Reis 100 meter."
#: ../src/AchievementList.cpp:65
msgid "Runner"
-msgstr ""
+msgstr "Løper"
#: ../src/AchievementList.cpp:65
msgid "Travel 1 kilometer."
-msgstr ""
+msgstr "Reis én kilometer."
#: ../src/AchievementList.cpp:66
msgid "Long distance runner"
-msgstr ""
+msgstr "Langdistanseløper"
#: ../src/AchievementList.cpp:66
msgid "Travel 10 kilometers."
-msgstr ""
+msgstr "Reis ti kilometer."
#: ../src/AchievementList.cpp:67
msgid "Marathon runner"
-msgstr ""
+msgstr "Maratonløper"
#: ../src/AchievementList.cpp:67
msgid "Travel 42,195 meters."
-msgstr ""
+msgstr "Reis 42195 meter"
#: ../src/AchievementList.cpp:69
msgid "Be careful!"
-msgstr ""
+msgstr "Vær forsiktig!"
#: ../src/AchievementList.cpp:69
msgid "Die for the first time."
-msgstr ""
+msgstr "Dø for første gang."
#: ../src/AchievementList.cpp:70
msgid "It doesn't matter..."
-msgstr ""
+msgstr "Det gjør ikke noe…"
#: ../src/AchievementList.cpp:70
msgid "Die 50 times."
-msgstr ""
+msgstr "Dø 50 ganger."
#: ../src/AchievementList.cpp:71
msgid "Expert of trial and error"
-msgstr ""
+msgstr "Ekspert på prøving og feiling"
#: ../src/AchievementList.cpp:71
msgid "Die 1000 times."
-msgstr ""
+msgstr "Dø 1000 ganger."
#: ../src/AchievementList.cpp:73
msgid "Keep an eye for moving blocks!"
-msgstr ""
+msgstr "Hold øye med bevegelige blokker!"
#: ../src/AchievementList.cpp:73
msgid "Get squashed for the first time."
-msgstr ""
+msgstr "Bli most for første gang."
#: ../src/AchievementList.cpp:74
msgid "Potato masher"
-msgstr ""
+msgstr "Potetstamper"
#: ../src/AchievementList.cpp:74
msgid "Get squashed 50 times."
-msgstr ""
+msgstr "Bli most 50 ganger."
#: ../src/AchievementList.cpp:76
msgid "Double kill"
-msgstr ""
+msgstr "Dobbeltdrap"
#: ../src/AchievementList.cpp:76
msgid "Get both the player and the shadow dead."
-msgstr ""
+msgstr "Få både karakteren din og skyggen dens drept."
#: ../src/AchievementList.cpp:78
msgid "Bad luck"
-msgstr ""
+msgstr "Uflaks"
#: ../src/AchievementList.cpp:78
msgid "Die 5 times in under 5 seconds."
-msgstr ""
+msgstr "Dø fem ganger på under fem sekunder."
#: ../src/AchievementList.cpp:79
msgid "This level is too dangerous"
-msgstr ""
+msgstr "Dette nivået er for farlig"
#: ../src/AchievementList.cpp:79
msgid "Die 10 times in under 5 seconds."
-msgstr ""
+msgstr "Dø ti ganger på under fem sekunder."
#: ../src/AchievementList.cpp:81
msgid "You forgot your friend"
-msgstr ""
+msgstr "Du glemte vennen din"
#: ../src/AchievementList.cpp:81
msgid "Finish the level with the player or the shadow dead."
-msgstr ""
+msgstr "Fullfør nivået med spilleren eller skyggen dø."
#: ../src/AchievementList.cpp:82
msgid "Just in time"
-msgstr ""
+msgstr "Akkurat tidsnok"
#: ../src/AchievementList.cpp:82
msgid "Reach the exit with the player and the shadow simultaneously."
-msgstr ""
+msgstr "Nå utgangen med spilleren og skyggen samtidig."
#: ../src/AchievementList.cpp:84
msgid "Recorder"
-msgstr ""
+msgstr "Opptaker"
#: ../src/AchievementList.cpp:84
msgid "Record 100 times."
-msgstr ""
+msgstr "Ta opp 100 ganger."
#: ../src/AchievementList.cpp:85
msgid "Shadowmaster"
-msgstr ""
+msgstr "Skyggemester"
#: ../src/AchievementList.cpp:85
msgid "Record 1000 times."
-msgstr ""
+msgstr "Ta opp 1000 ganger."
#: ../src/AchievementList.cpp:87
msgid "Switch puller"
-msgstr ""
+msgstr "Brytervender"
#: ../src/AchievementList.cpp:87
msgid "Pull the switch 100 times."
-msgstr ""
+msgstr "Vend bryteren 100 ganger."
#: ../src/AchievementList.cpp:88
msgid "The switch is broken!"
-msgstr ""
+msgstr "Bryteren er kaputt!"
#: ../src/AchievementList.cpp:88
msgid "Pull the switch 1000 times."
-msgstr ""
+msgstr "Vend bryteren 1000 ganger."
#: ../src/AchievementList.cpp:90
+#, fuzzy
msgid "Swapper"
-msgstr ""
+msgstr "Stedveksler"
#: ../src/AchievementList.cpp:90
msgid "Swap 100 times."
-msgstr ""
+msgstr "Bytt sted 100 ganger."
#: ../src/AchievementList.cpp:91
msgid "Player to shadow to player to shadow..."
-msgstr ""
+msgstr "Spiller til skygge til spiller til skygge…"
#: ../src/AchievementList.cpp:91
+#, fuzzy
msgid "Swap 1000 times."
-msgstr ""
+msgstr "Bytt sted 1000 ganger."
#: ../src/AchievementList.cpp:93
msgid "Play it save"
-msgstr ""
+msgstr "Ro det i land"
#: ../src/AchievementList.cpp:93
msgid "Save 1000 times."
-msgstr ""
+msgstr "Lagre 1000 ganger."
#: ../src/AchievementList.cpp:94
msgid "This game is too hard"
-msgstr ""
+msgstr "Dette spilet er for vanskelig"
#: ../src/AchievementList.cpp:94
msgid "Load the game 1000 times."
-msgstr ""
+msgstr "Last spillet 1000 ganger."
#: ../src/AchievementList.cpp:96
msgid "No, thanks"
-msgstr ""
+msgstr "Nei takk"
#: ../src/AchievementList.cpp:96
msgid "Complete a level with checkpoint, but without saving."
-msgstr ""
+msgstr "Fullfør et nivå med sjekkpunkt, men uten å lagre."
#: ../src/AchievementList.cpp:98
msgid "Panic save"
-msgstr ""
+msgstr "Panikklagring"
#: ../src/AchievementList.cpp:98
msgid "Save twice in 1 second."
-msgstr ""
+msgstr "Lagre to ganger på ett sekund"
#: ../src/AchievementList.cpp:99
msgid "Panic load"
-msgstr ""
+msgstr "Panikklasting"
#: ../src/AchievementList.cpp:99
msgid "Load twice in 1 second."
-msgstr ""
+msgstr "Last inn to ganger på ett sekund."
#: ../src/AchievementList.cpp:101
msgid "Bad saving position"
-msgstr ""
+msgstr "Dårlig lagringsposisjon"
#: ../src/AchievementList.cpp:101
msgid "Load the game and die within 1 second."
-msgstr ""
+msgstr "Last spillet og dø på mindre enn ett sekund."
#: ../src/AchievementList.cpp:102
msgid "This level is too hard"
-msgstr ""
+msgstr "Dette nivået er for vanskelig"
#: ../src/AchievementList.cpp:102
msgid "Load the same save and die 100 times."
-msgstr ""
+msgstr "Last samme lagring og dø 100 ganger."
#: ../src/AchievementList.cpp:104
msgid "Quick swap"
-msgstr ""
+msgstr "Raskt bytte"
#: ../src/AchievementList.cpp:104
msgid "Swap twice in under a second."
-msgstr ""
+msgstr "Bytt sted to ganger på under ett sekund."
#: ../src/AchievementList.cpp:107
msgid "Horizontal confusion"
-msgstr ""
+msgstr "Vannrett forvirring"
#: ../src/AchievementList.cpp:107
msgid "Press left and right simultaneously."
-msgstr ""
+msgstr "Trykk ← og → samtidig."
#: ../src/AchievementList.cpp:109
msgid "Cheater"
-msgstr ""
+msgstr "Jukser"
#: ../src/AchievementList.cpp:109
msgid "Cheat in game."
-msgstr ""
+msgstr "Juks i spillet."
#: ../src/AchievementList.cpp:111
msgid "Programmer"
-msgstr ""
+msgstr "Programmerer"
#: ../src/AchievementList.cpp:111
msgid "Play the development version of Me and My Shadow."
-msgstr ""
+msgstr "Spill utviklingsversjonen av Jeg og skyggen min."
#: ../src/Addons.cpp:44 ../src/LevelPackManager.cpp:108
msgid "Levels"
-msgstr ""
+msgstr "Nivå"
#: ../src/Addons.cpp:44
msgid "Single level which usually contain demanding puzzles"
-msgstr ""
+msgstr "Enkeltnivå som vanligvis inneholder utfordrende gåter"
#: ../src/Addons.cpp:45
msgid "Levelpacks"
-msgstr ""
+msgstr "Nivåpakker"
#: ../src/Addons.cpp:45
msgid "Collection of levels with the same author or style"
-msgstr ""
+msgstr "Samling av nivåer laget av samme utvikler eller i samme stil"
#: ../src/Addons.cpp:46
msgid "Themes"
-msgstr ""
+msgstr "Drakter"
#: ../src/Addons.cpp:46
msgid "Give every block and background a new look and feel"
-msgstr ""
+msgstr "Gi hver blokk og bakgrunn nytt utseende og oppførsel"
#: ../src/Addons.cpp:55 ../src/TitleMenu.cpp:46
msgid "Addons"
-msgstr ""
+msgstr "Programtillegg"
#: ../src/Addons.cpp:87
msgid "Unable to initialize addon menu:"
-msgstr ""
+msgstr "Kunne ikke starte programtilleggsmeny:"
#: ../src/Addons.cpp:95 ../src/Addons.cpp:158 ../src/Addons.cpp:662
#: ../src/Addons.cpp:690 ../src/CreditsMenu.cpp:89 ../src/LevelSelect.cpp:168
#: ../src/StatisticsScreen.cpp:159
msgid "Back"
-msgstr ""
+msgstr "Tilbake"
#: ../src/Addons.cpp:169
msgid "ERROR: unable to download addons file!"
-msgstr ""
+msgstr "FEIL: Kunne ikke laste ned programtilleggsfil!"
# TRANSLATORS: addon_list is the name of a file and should not be translated.
#: ../src/Addons.cpp:182
msgid "ERROR: unable to load addon_list file!"
-msgstr ""
+msgstr "FEIL: Kunne ikke laste inn addon_list-fil!"
#: ../src/Addons.cpp:193
msgid "ERROR: Invalid file format of addons file!"
-msgstr ""
+msgstr "FEIL: Ugyldig filformat for programtilleggsfil!"
#: ../src/Addons.cpp:205
msgid "ERROR: Addon list version is unsupported!"
-msgstr ""
+msgstr "FEIL: Programtilleggsversjonen støttes ikke!"
# TRANSLATORS: installed_addons is the name of a file and should not be translated.
#: ../src/Addons.cpp:226
msgid "ERROR: Unable to create the installed_addons file."
-msgstr ""
+msgstr "FEIL: Kunne ikke opprette filen installed_addons."
#: ../src/Addons.cpp:238
msgid "ERROR: Invalid file format of the installed_addons!"
-msgstr ""
+msgstr "FEIL: Ugyldig filformat for programtilleggsfil for installasjoner."
# TRANSLATORS: indicates the author of an addon.
#: ../src/Addons.cpp:389 ../src/Addons.cpp:621
#, c-format
msgid "by %s"
-msgstr ""
+msgstr "av %s"
#: ../src/Addons.cpp:397
msgid "Installed"
-msgstr ""
+msgstr "Installert"
#: ../src/Addons.cpp:402
msgid "Updatable"
-msgstr ""
+msgstr "Oppdaterbar"
#: ../src/Addons.cpp:412
msgid "Not installed"
-msgstr ""
+msgstr "Ikke installert"
#: ../src/Addons.cpp:625
#, c-format
msgid "Version: %d\n"
-msgstr ""
+msgstr "Versjon: %d\n"
#: ../src/Addons.cpp:627
#, c-format
msgid "Installed version: %d\n"
-msgstr ""
+msgstr "Installert versjon: %d\n"
#: ../src/Addons.cpp:630
#, c-format
msgid "License: %s\n"
-msgstr ""
+msgstr "Lisens: %s\n"
#: ../src/Addons.cpp:633
#, c-format
msgid "Website: %s\n"
-msgstr ""
+msgstr "Nettside: %s\n"
#: ../src/Addons.cpp:637
msgid "(No descriptions provided)"
-msgstr ""
+msgstr "(Ingen beskrivelser medfølger)"
#: ../src/Addons.cpp:657 ../src/Addons.cpp:684
msgid "Remove"
-msgstr ""
+msgstr "Fjern"
#: ../src/Addons.cpp:673
msgid "Update"
-msgstr ""
+msgstr "Oppdater"
#: ../src/Addons.cpp:679
msgid "Install"
-msgstr ""
+msgstr "Installer"
#: ../src/Addons.cpp:774
#, c-format
msgid "This addon can't be removed because it's needed by %s."
-msgstr ""
+msgstr "Dette programtillegget kan ikke fjernes fordi det trengs av %s."
#: ../src/Addons.cpp:774 ../src/Addons.cpp:1051
msgid "Dependency"
-msgstr ""
+msgstr "Avhengighet"
#: ../src/Addons.cpp:803
#, c-format
msgid "WARNING: File '%s' appears to have been removed already."
-msgstr ""
+msgstr "ADVARSEL: Filen \"%s\" later til å ha blitt fjernet allerede."
#: ../src/Addons.cpp:803 ../src/Addons.cpp:810 ../src/Addons.cpp:818
#: ../src/Addons.cpp:825 ../src/Addons.cpp:834 ../src/Addons.cpp:840
#: ../src/Addons.cpp:859 ../src/Addons.cpp:866 ../src/Addons.cpp:893
#: ../src/Addons.cpp:900 ../src/Addons.cpp:907 ../src/Addons.cpp:918
#: ../src/Addons.cpp:947 ../src/Addons.cpp:952 ../src/Addons.cpp:962
#: ../src/Addons.cpp:968 ../src/Addons.cpp:981 ../src/Addons.cpp:986
#: ../src/Addons.cpp:1008 ../src/Addons.cpp:1014 ../src/Addons.cpp:1044
msgid "Addon error"
-msgstr ""
+msgstr "Programtilleggsfeil"
#: ../src/Addons.cpp:810
#, c-format
msgid "ERROR: Unable to remove file '%s'!"
-msgstr ""
+msgstr "FEIL: Kunne ikke fjerne filen \"%s\"."
#: ../src/Addons.cpp:818
#, c-format
msgid "WARNING: Directory '%s' appears to have been removed already."
-msgstr ""
+msgstr "ADVARSEL: Mappen \"%s\" later til å ha blitt fjernet allerede."
#: ../src/Addons.cpp:825
#, c-format
msgid "ERROR: Unable to remove directory '%s'!"
-msgstr ""
+msgstr "FEIL: Kunne ikke fjerne mappen \"%s\"."
#: ../src/Addons.cpp:834
#, c-format
msgid "WARNING: Level '%s' appears to have been removed already."
-msgstr ""
+msgstr "ADVARSEL: Nivået \"%s\" later til å ha blitt fjernet allerede."
#: ../src/Addons.cpp:840
#, c-format
msgid "ERROR: Unable to remove level '%s'!"
-msgstr ""
+msgstr "FEIL: Kunne ikke fjerne nivået \"%s\"!"
#: ../src/Addons.cpp:859
#, c-format
msgid "WARNING: Levelpack directory '%s' appears to have been removed already."
-msgstr ""
+msgstr "ADVARSEL: Nivåpakkemappen \"%s\" later til å ha blitt fjernet allerede."
#: ../src/Addons.cpp:866
#, c-format
msgid "ERROR: Unable to remove levelpack directory '%s'!"
-msgstr ""
+msgstr "FEIL: Kunne ikke fjerne nivåpakkemappen \"%s\"."
#: ../src/Addons.cpp:893
#, c-format
msgid "ERROR: Unable to download addon file %s."
-msgstr ""
+msgstr "FEIL: Kunne ikke laste ned programtilleggsfilen %s."
#: ../src/Addons.cpp:900
#, c-format
msgid "ERROR: Unable to extract addon file %s."
-msgstr ""
+msgstr "FEIL: Kunne ikke pakke ut programtilleggsfilen %s."
#: ../src/Addons.cpp:907
msgid "ERROR: Addon is missing metadata!"
-msgstr ""
+msgstr "FEIL: Programtillegget mangler metadata!"
#: ../src/Addons.cpp:918
msgid "ERROR: Invalid file format for metadata file!"
-msgstr ""
+msgstr "FEIL: Ugyldig filformat for metadatafil!"
#: ../src/Addons.cpp:947
#, c-format
msgid "WARNING: File '%s' already exists, addon may be broken or not working!"
msgstr ""
+"ADVARSEL: Filen \"%s\" finnes allerede, programtillegget kan være skadet "
+"eller ha sluttet å fungere."
#: ../src/Addons.cpp:952
#, c-format
msgid ""
"WARNING: Unable to copy file '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
+"ADVARSEL: Kunne ikke kopiere filen \"%s\" til \"%s\" programtillegget kan "
+"være skadet eller ha sluttet å virke."
#: ../src/Addons.cpp:962
#, c-format
msgid ""
"WARNING: Destination directory '%s' already exists, addon may be broken or "
"not working!"
msgstr ""
+"ADVARSEL: Målmappen \"%s\" finnes allerede, programtillegget kan være "
+"skadet, eller ha sluttet å virke."
#: ../src/Addons.cpp:968 ../src/Addons.cpp:1014
#, c-format
msgid ""
"WARNING: Unable to move directory '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
+"ADVARSEL: Kunne ikke flytte mappen \"%s\" til \"%s\", programtillegget kan "
+"være skadet, eller ha sluttet å virke."
#: ../src/Addons.cpp:981
#, c-format
msgid "WARNING: Level '%s' already exists, addon may be broken or not working!"
msgstr ""
+"ADVARSEL: Nivået \"%s\" finnes allerede, programtillegget kan være skadet, "
+"eller ha sluttet å virke."
#: ../src/Addons.cpp:986
#, c-format
msgid ""
"WARNING: Unable to copy level '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
+"ADVARSEL: Kunne ikke kopiere nivået \"%s\" til \"%s\", programtillegget kan "
+"være skadet, eller ha sluttet å virke."
#: ../src/Addons.cpp:1008
#, c-format
msgid ""
"WARNING: Levelpack directory '%s' already exists, addon may be broken or not "
"working!"
msgstr ""
+"ADVARSEL: Nivåpakkemappen \"%s\" finnes allerede, programtillegget kan være "
+"skadet, eller ha sluttet å virke."
#: ../src/Addons.cpp:1044
#, c-format
msgid "ERROR: Addon requires another addon (%s) which can't be found!"
msgstr ""
+"FEIL: Programtillegget krever et annet programtillegg (%s) som ikke ble "
+"funnet."
#: ../src/Addons.cpp:1051
#, c-format
msgid "The addon %s is needed and will be installed now."
-msgstr ""
+msgstr "Programtillegget %s er nødvendig, og vil bli installert nå."
#: ../src/Block.cpp:822 ../src/LevelEditor.cpp:265
msgid "On"
-msgstr ""
+msgstr "På"
#: ../src/Block.cpp:823 ../src/LevelEditor.cpp:266
msgid "Off"
-msgstr ""
+msgstr "Av"
#: ../src/CommandManager.cpp:41
#, c-format
msgid "Undo %s"
-msgstr ""
+msgstr "Angre %s"
#: ../src/CommandManager.cpp:43
msgid "Can't undo"
-msgstr ""
+msgstr "Kan ikke angre"
#: ../src/CommandManager.cpp:49
#, c-format
msgid "Redo %s"
-msgstr ""
+msgstr "Gjenta %s"
#: ../src/CommandManager.cpp:51
msgid "Can't redo"
-msgstr ""
+msgstr "Kan ikke gjenta"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:190
msgid "Resize level"
-msgstr ""
+msgstr "Endre nivåstørrelse"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:807
msgid "Modify level property"
-msgstr ""
+msgstr "Endre nivåegenskap"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:919
#, c-format
msgid "Add scenery layer %s"
-msgstr ""
+msgstr "Legg til omgivelseslag %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:921
#, c-format
msgid "Delete scenery layer %s"
-msgstr ""
+msgstr "Slett omgivelseslag %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:951
#, c-format
msgid "Modify the property of scenery layer %s"
-msgstr ""
+msgstr "Endre egenskapene på omgivelseslaget %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:1040
#, c-format
msgid "Move %d object from layer %s to layer %s"
msgid_plural "Move %d objects from layer %s to layer %s"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Flytt %d objekter fra lag %s til lag %s"
+msgstr[1] "Flytt %d objekter fra lag %s til lag %s"
#: ../src/CreditsMenu.cpp:35 ../src/TitleMenu.cpp:53
msgid "Credits"
-msgstr ""
+msgstr "Bidragsytere"
# TRANSLATORS: Font used in GUI:
# - Use "knewave" for languages using Latin and Latin-derived alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:569 ../src/Functions.cpp:570 ../src/Functions.cpp:571
#: ../src/Functions.cpp:588
msgid "knewave"
-msgstr ""
+msgstr "knewave"
# TRANSLATORS: Font used for normal text:
# - Use "Blokletters-Viltstift" for languages using Latin and Latin-derived alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:575
msgid "Blokletters-Viltstift"
-msgstr ""
+msgstr "Blokletters-Viltstift"
#: ../src/Functions.cpp:674
msgid "Loading..."
-msgstr ""
+msgstr "Laster…"
#: ../src/Functions.cpp:1243 ../src/Functions.cpp:1270
#: ../src/LevelEditor.cpp:559 ../src/LevelEditor.cpp:693
#: ../src/LevelEditor.cpp:758 ../src/LevelEditor.cpp:821
#: ../src/LevelEditor.cpp:908 ../src/LevelEditor.cpp:1033
#: ../src/LevelEditor.cpp:1083 ../src/LevelEditor.cpp:1180
#: ../src/LevelEditor.cpp:1244 ../src/LevelEditor.cpp:2923
#: ../src/LevelEditSelect.cpp:244 ../src/LevelEditSelect.cpp:277
#: ../src/LevelEditSelect.cpp:317
msgid "OK"
-msgstr ""
+msgstr "OK"
#: ../src/Functions.cpp:1244 ../src/Functions.cpp:1256
#: ../src/Functions.cpp:1266 ../src/LevelEditor.cpp:565
#: ../src/LevelEditor.cpp:699 ../src/LevelEditor.cpp:764
#: ../src/LevelEditor.cpp:827 ../src/LevelEditor.cpp:914
#: ../src/LevelEditor.cpp:1039 ../src/LevelEditor.cpp:1089
#: ../src/LevelEditor.cpp:1186 ../src/LevelEditor.cpp:1250
#: ../src/LevelEditor.cpp:2929 ../src/LevelEditSelect.cpp:248
#: ../src/LevelEditSelect.cpp:281 ../src/LevelEditSelect.cpp:321
#: ../src/OptionsMenu.cpp:289
msgid "Cancel"
-msgstr ""
+msgstr "Avbryt"
#: ../src/Functions.cpp:1248
msgid "Abort"
-msgstr ""
+msgstr "Angre"
#: ../src/Functions.cpp:1249 ../src/Functions.cpp:1265
msgid "Retry"
-msgstr ""
+msgstr "Prøv igjen"
#: ../src/Functions.cpp:1250
msgid "Ignore"
-msgstr ""
+msgstr "Ignorer"
#: ../src/Functions.cpp:1254 ../src/Functions.cpp:1260
msgid "Yes"
-msgstr ""
+msgstr "Ja"
#: ../src/Functions.cpp:1255 ../src/Functions.cpp:1261
msgid "No"
-msgstr ""
+msgstr "Nei"
# TRANSLATORS: Please do not remove %s or %d from your translation:
# - %d means the level number in a levelpack
# - %s means the name of current level
#: ../src/Game.cpp:280 ../src/Game.cpp:1236
#, c-format
msgid "Level %d %s"
-msgstr ""
+msgstr "NIvå %d %s"
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:915
#, c-format
msgid "Press %s key to save the game."
-msgstr ""
+msgstr "Trykk %s-tasten for å lagre spillet."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:920
-#, c-format
+#, c-format, fuzzy
msgid "Press %s key to swap the position of player and shadow."
-msgstr ""
+msgstr "Trykk %s-tasten for å bytte sted mellom karakteren og skyggen."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:925
#, c-format
msgid "Press %s key to activate the switch."
-msgstr ""
+msgstr "Trykk %s-tasten for å aktivere bryteren."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:930
#, c-format
msgid "Press %s key to teleport."
-msgstr ""
+msgstr "Trykk %s-tasten for å teleportere."
# TRANSLATORS: Please do not remove %s from your translation:
# - first %s means currently configured key to restart game
# - Second %s means configured key to load from last save
#: ../src/Game.cpp:972
#, c-format
msgid "Press %s to restart current level or press %s to load the game."
msgstr ""
+"Trykk %s for å starte nåværende nivå igjen, eller trykk %s for å laste inn "
+"spillet."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with currently configured key to restart game
#: ../src/Game.cpp:983
#, c-format
msgid "Press %s to restart current level."
-msgstr ""
+msgstr "Trykk %s for å starte nåværende nivå igjen."
#: ../src/Game.cpp:996
msgid "Your shadow has died."
-msgstr ""
+msgstr "Skyggen din har dødd."
#: ../src/Game.cpp:1052
#, c-format
msgid "%d recording"
msgid_plural "%d recordings"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d opptak"
+msgstr[1] "%d opptak"
#: ../src/Game.cpp:1224
msgid "You've finished:"
-msgstr ""
+msgstr "Du har fullført:"
# TRANSLATORS: Please do not remove %-.2f from your translation:
# - %-.2f means time in seconds
# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1291
#, c-format
msgid "Time: %-.2fs"
-msgstr ""
+msgstr "Tid: %-.2fs"
# TRANSLATORS: Please do not remove %-.2f from your translation:
# - %-.2f means time in seconds
# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1300
#, c-format
msgid "Best time: %-.2fs"
-msgstr ""
+msgstr "Beste tid: %-.2fs"
#: ../src/Game.cpp:1311
#, c-format
msgid "Target time: %-.2fs"
-msgstr ""
+msgstr "Mål: %-.2fs"
# TRANSLATORS: Please do not remove %d from your translation:
# - %d means the number of recordings user has made
#: ../src/Game.cpp:1332
#, c-format
msgid "Recordings: %d"
-msgstr ""
+msgstr "Opptak: %d"
# TRANSLATORS: Please do not remove %d from your translation:
# - %d means the number of recordings user has made
#: ../src/Game.cpp:1340
#, c-format
msgid "Best recordings: %d"
-msgstr ""
+msgstr "Beste opptak: %d"
#: ../src/Game.cpp:1350
#, c-format
msgid "Target recordings: %d"
-msgstr ""
+msgstr "Mål-opptak: %d"
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with name of a prize medal (gold, silver or bronze)
#: ../src/Game.cpp:1363
#, c-format
msgid "You earned the %s medal"
-msgstr ""
+msgstr "Du har gjort deg fortjent til %s-medaljen"
#: ../src/Game.cpp:1363
msgid "GOLD"
-msgstr ""
+msgstr "GULL"
#: ../src/Game.cpp:1363
msgid "SILVER"
-msgstr ""
+msgstr "SØLV"
#: ../src/Game.cpp:1363
msgid "BRONZE"
-msgstr ""
+msgstr "BRONSE"
# TRANSLATORS: used as return to the level selector menu
#: ../src/Game.cpp:1390
msgid "Menu"
-msgstr ""
+msgstr "Meny"
# TRANSLATORS: used as restart level
#: ../src/Game.cpp:1397 ../src/InputManager.cpp:47
msgid "Restart"
-msgstr ""
+msgstr "Start igjen"
# TRANSLATORS: used as next level
#: ../src/Game.cpp:1404
msgid "Next"
-msgstr ""
+msgstr "Neste"
#: ../src/Game.cpp:1430
msgid "Game replay is done."
-msgstr ""
+msgstr "Spillopptaket er ferdigspilt."
#: ../src/Game.cpp:1430
msgid "Game Replay"
-msgstr ""
+msgstr "Spillopptaksavspilling"
#: ../src/Game.cpp:1767 ../src/Game.cpp:1769
msgid "Congratulations"
-msgstr ""
+msgstr "Gratulerer"
#: ../src/Game.cpp:1769
msgid "You have finished the levelpack!"
-msgstr ""
+msgstr "Du har fullført nivåpakken."
#: ../src/InputManager.cpp:46
msgid "Up (in menu)"
-msgstr ""
+msgstr "Opp (i menyen)"
#: ../src/InputManager.cpp:46
msgid "Down (in menu)"
-msgstr ""
+msgstr "Ned (i menyen)"
#: ../src/InputManager.cpp:46
msgid "Left"
-msgstr ""
+msgstr "Venstre"
#: ../src/InputManager.cpp:46
msgid "Right"
-msgstr ""
+msgstr "Høyre"
#: ../src/InputManager.cpp:46
msgid "Jump"
-msgstr ""
+msgstr "Hopp"
#: ../src/InputManager.cpp:46
msgid "Action"
-msgstr ""
+msgstr "Handling"
#: ../src/InputManager.cpp:46
msgid "Space (Record)"
-msgstr ""
+msgstr "Mellomrom (ta opp)"
#: ../src/InputManager.cpp:46
msgid "Cancel recording"
-msgstr ""
+msgstr "Avbryt opptak"
#: ../src/InputManager.cpp:47
msgid "Escape"
-msgstr ""
+msgstr "Esc"
#: ../src/InputManager.cpp:47
msgid "Tab (View shadow/Level prop.)"
-msgstr ""
+msgstr "Tab (vis skygge/nivåegenskap)"
#: ../src/InputManager.cpp:47
msgid "Save game (in editor)"
-msgstr ""
+msgstr "Lagre spill (i redigerer)"
#: ../src/InputManager.cpp:47
msgid "Load game"
-msgstr ""
+msgstr "Last inn spill"
#: ../src/InputManager.cpp:47
msgid "Swap (in editor)"
-msgstr ""
+msgstr "Veksle (i redigerer)"
#: ../src/InputManager.cpp:48
msgid "Teleport (in editor)"
-msgstr ""
+msgstr "Teleporter (i redigerer)"
#: ../src/InputManager.cpp:48
msgid "Suicide (in editor)"
-msgstr ""
+msgstr "Selvmord (i redigerer)"
#: ../src/InputManager.cpp:48
+#, fuzzy
msgid "Shift (in editor)"
-msgstr ""
+msgstr "Bytt (i redigerer)"
#: ../src/InputManager.cpp:48
msgid "Next block type (in Editor)"
-msgstr ""
+msgstr "Neste blokktype (i redigerer)"
#: ../src/InputManager.cpp:49
msgid "Previous block type (in editor)"
-msgstr ""
+msgstr "Forrige blokktype (i redigerer)"
#: ../src/InputManager.cpp:49
msgid "Select (in menu)"
-msgstr ""
+msgstr "Velg (i meny)"
# TRANSLAOTRS: This is used when the name of the key code is not found.
#: ../src/InputManager.cpp:156
#, c-format
msgid "(Key %d)"
-msgstr ""
+msgstr "(Tast %d)"
#: ../src/InputManager.cpp:163
#, c-format
msgid "Joystick axis %d %s"
-msgstr ""
+msgstr "Spillstikkeakse %d %s"
#: ../src/InputManager.cpp:166
#, c-format
msgid "Joystick button %d"
-msgstr ""
+msgstr "Spillstikkeknapp %d"
#: ../src/InputManager.cpp:171
-#, c-format
+#, c-format, fuzzy
msgid "Joystick hat %d left"
-msgstr ""
+msgstr "Spillstikkepinne %d venstre"
#: ../src/InputManager.cpp:174
-#, c-format
+#, c-format, fuzzy
msgid "Joystick hat %d right"
-msgstr ""
+msgstr "Spillstikkepinne %d høyre"
#: ../src/InputManager.cpp:177
-#, c-format
+#, c-format, fuzzy
msgid "Joystick hat %d up"
-msgstr ""
+msgstr "Spillstikkepinne %d oppover"
#: ../src/InputManager.cpp:180
-#, c-format
+#, c-format, fuzzy
msgid "Joystick hat %d down"
-msgstr ""
+msgstr "Spillstikkepinne %d nedover"
# TRANSLAOTRS: This is used when the JOYSTICK_HAT value is invalid.
#: ../src/InputManager.cpp:185
-#, c-format
+#, c-format, fuzzy
msgid "Joystick hat %d %d"
-msgstr ""
+msgstr "Spillstikkepinne %d %d"
#: ../src/InputManager.cpp:202
msgid "OR"
-msgstr ""
+msgstr "ELLER"
#: ../src/InputManager.cpp:416
msgid "Select an item and press a key to change it."
-msgstr ""
+msgstr "Velg et element og trykk en tast for å endre det."
#: ../src/InputManager.cpp:419
+#, fuzzy
msgid "Press backspace to clear the selected item."
-msgstr ""
+msgstr "Trykk rettetast for å tømme valgt element."
#: ../src/LevelEditor.cpp:56
msgid "Block"
-msgstr ""
+msgstr "Blokk"
#: ../src/LevelEditor.cpp:56
msgid "Player Start"
-msgstr ""
+msgstr "Spillerstart"
#: ../src/LevelEditor.cpp:56
msgid "Shadow Start"
-msgstr ""
+msgstr "Skyggestart"
#: ../src/LevelEditor.cpp:57
msgid "Exit"
-msgstr ""
+msgstr "Avslutt"
#: ../src/LevelEditor.cpp:57
msgid "Shadow Block"
-msgstr ""
+msgstr "Skyggeblokk"
#: ../src/LevelEditor.cpp:57
msgid "Spikes"
-msgstr ""
+msgstr "Tagger"
#: ../src/LevelEditor.cpp:58
msgid "Checkpoint"
-msgstr ""
+msgstr "Sjekkpunkt"
#: ../src/LevelEditor.cpp:58 ../src/LevelEditSelect.cpp:312
+#, fuzzy
msgid "Swap"
-msgstr ""
+msgstr "Bytt"
#: ../src/LevelEditor.cpp:58
msgid "Fragile"
-msgstr ""
+msgstr "Skjør"
#: ../src/LevelEditor.cpp:59
msgid "Moving Block"
-msgstr ""
+msgstr "Bevegende blokk"
#: ../src/LevelEditor.cpp:59
msgid "Moving Shadow Block"
-msgstr ""
+msgstr "Bevegende skyggeblokk"
#: ../src/LevelEditor.cpp:59
msgid "Moving Spikes"
-msgstr ""
+msgstr "Bevegende tagger"
#: ../src/LevelEditor.cpp:60
msgid "Teleporter"
-msgstr ""
+msgstr "Teleportør"
#: ../src/LevelEditor.cpp:60
msgid "Button"
-msgstr ""
+msgstr "Knapp"
#: ../src/LevelEditor.cpp:60
msgid "Switch"
-msgstr ""
+msgstr "Bryter"
#: ../src/LevelEditor.cpp:61
msgid "Conveyor Belt"
-msgstr ""
+msgstr "Samlebånd"
#: ../src/LevelEditor.cpp:61
msgid "Shadow Conveyor Belt"
-msgstr ""
+msgstr "Skyggesamlebånd"
#: ../src/LevelEditor.cpp:61
msgid "Notification Block"
-msgstr ""
+msgstr "Merknadsblokk"
#: ../src/LevelEditor.cpp:61
msgid "Collectable"
-msgstr ""
+msgstr "Sankbar"
#: ../src/LevelEditor.cpp:61
msgid "Pushable"
-msgstr ""
+msgstr "Trykkbar"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:310
msgid "Select"
-msgstr ""
+msgstr "Velg"
#: ../src/LevelEditor.cpp:65
msgid "Add"
-msgstr ""
+msgstr "Legg til"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:311
msgid "Delete"
-msgstr ""
+msgstr "Slett"
#: ../src/LevelEditor.cpp:65 ../src/LevelPlaySelect.cpp:66
#: ../src/TitleMenu.cpp:43
msgid "Play"
-msgstr ""
+msgstr "Spill"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:2852
msgid "Level settings"
-msgstr ""
+msgstr "Nivåinnstillinger"
#: ../src/LevelEditor.cpp:65
msgid "Save level"
-msgstr ""
+msgstr "Lagre nivå"
#: ../src/LevelEditor.cpp:65
msgid "Back to menu"
-msgstr ""
+msgstr "Tilbake til nivå"
#: ../src/LevelEditor.cpp:65
msgid "Configure"
-msgstr ""
+msgstr "Sett opp"
#: ../src/LevelEditor.cpp:84
-#, c-format
+#, c-format, fuzzy
msgid "%s (Scenery)"
-msgstr ""
+msgstr "%s (naturutsikt)"
#: ../src/LevelEditor.cpp:267
msgid "Toggle"
-msgstr ""
+msgstr "Veksle"
#: ../src/LevelEditor.cpp:270
msgid "Complete"
-msgstr ""
+msgstr "Fullfør"
#: ../src/LevelEditor.cpp:271
msgid "One step"
-msgstr ""
+msgstr "Ett steg"
#: ../src/LevelEditor.cpp:272
msgid "Two steps"
-msgstr ""
+msgstr "To steg"
#: ../src/LevelEditor.cpp:273
+#, fuzzy
msgid "Gone"
-msgstr ""
+msgstr "Borte"
#: ../src/LevelEditor.cpp:291
msgid "Negative infinity"
-msgstr ""
+msgstr "Negativ uendelighet"
#: ../src/LevelEditor.cpp:293
msgid "Zero"
-msgstr ""
+msgstr "Null"
#: ../src/LevelEditor.cpp:295
msgid "Level size"
-msgstr ""
+msgstr "Nivåstørrelse"
#: ../src/LevelEditor.cpp:297
msgid "Positive infinity"
-msgstr ""
+msgstr "Positiv uendelighet"
#: ../src/LevelEditor.cpp:299
msgid "Default"
-msgstr ""
+msgstr "Forvalg"
#: ../src/LevelEditor.cpp:308
msgid "Deselect"
-msgstr ""
+msgstr "Fravelg"
#: ../src/LevelEditor.cpp:318 ../src/LevelEditor.cpp:1136
#, c-format
msgid "Horizontal repeat start: %s"
-msgstr ""
+msgstr "Vannrett gjentagelsesstart: %s"
#: ../src/LevelEditor.cpp:320 ../src/LevelEditor.cpp:1137
#, c-format
msgid "Horizontal repeat end: %s"
-msgstr ""
+msgstr "Vannrett gjentagelsesslutt: %s"
#: ../src/LevelEditor.cpp:322 ../src/LevelEditor.cpp:1138
#, c-format
msgid "Vertical repeat start: %s"
-msgstr ""
+msgstr "Vannrett gjentagelsesstart: %s"
#: ../src/LevelEditor.cpp:324 ../src/LevelEditor.cpp:1139
#, c-format
msgid "Vertical repeat end: %s"
-msgstr ""
+msgstr "Vannrett gjentagelsesslutt: %s"
#: ../src/LevelEditor.cpp:329 ../src/LevelEditor.cpp:1150
+#, fuzzy
msgid "Custom scenery"
-msgstr ""
+msgstr "Egendefinert omgivelsesmiljø"
#: ../src/LevelEditor.cpp:335 ../src/LevelEditor.cpp:600
#: ../src/LevelEditor.cpp:602
msgid "Visible"
-msgstr ""
+msgstr "Synlig"
#: ../src/LevelEditor.cpp:344
msgid "Link"
-msgstr ""
+msgstr "Lenke"
#: ../src/LevelEditor.cpp:345
msgid "Remove Links"
-msgstr ""
+msgstr "Fjern lenker"
#: ../src/LevelEditor.cpp:349 ../src/LevelEditor.cpp:624
#: ../src/LevelEditor.cpp:626
msgid "Automatic"
-msgstr ""
+msgstr "Automatisk"
#: ../src/LevelEditor.cpp:359 ../src/LevelEditor.cpp:649
#, c-format
msgid "Behavior: %s"
-msgstr ""
+msgstr "Oppførsel: %s"
#: ../src/LevelEditor.cpp:362
msgid "Path"
-msgstr ""
+msgstr "Sti"
#: ../src/LevelEditor.cpp:363
msgid "Remove Path"
-msgstr ""
+msgstr "Fjern sti"
#: ../src/LevelEditor.cpp:365 ../src/LevelEditor.cpp:371
#: ../src/LevelEditor.cpp:587 ../src/LevelEditor.cpp:589
msgid "Activated"
-msgstr ""
+msgstr "Aktivert"
#: ../src/LevelEditor.cpp:366 ../src/LevelEditor.cpp:612
#: ../src/LevelEditor.cpp:614
+#, fuzzy
msgid "Looping"
-msgstr ""
+msgstr "Sløyfe"
#: ../src/LevelEditor.cpp:372 ../src/LevelEditor.cpp:3526
msgid "Speed"
-msgstr ""
+msgstr "Hastighet"
#: ../src/LevelEditor.cpp:378 ../src/LevelEditor.cpp:668
#, c-format
msgid "State: %s"
-msgstr ""
+msgstr "Tilstand: %s"
#: ../src/LevelEditor.cpp:382 ../src/LevelEditor.cpp:3511
msgid "Message"
-msgstr ""
+msgstr "Melding"
#: ../src/LevelEditor.cpp:384 ../src/LevelEditor.cpp:1202
#: ../src/LevelEditor.cpp:3825
msgid "Appearance"
-msgstr ""
+msgstr "Utseende"
#: ../src/LevelEditor.cpp:389 ../src/LevelEditor.cpp:431
#: ../src/LevelEditor.cpp:715
msgid "Scripting"
-msgstr ""
+msgstr "Skripting"
#: ../src/LevelEditor.cpp:402 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Background layer: %s"
-msgstr ""
+msgstr "Bakgrunnslag: %s"
#: ../src/LevelEditor.cpp:409 ../src/LevelEditor.cpp:866
#: ../src/LevelEditor.cpp:884
msgid "Blocks layer"
-msgstr ""
+msgstr "Blokklag"
#: ../src/LevelEditor.cpp:417 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Foreground layer: %s"
-msgstr ""
+msgstr "Forgrunnslag: %s"
#: ../src/LevelEditor.cpp:423
msgid "Add new layer"
-msgstr ""
+msgstr "Legg til nytt lag"
#: ../src/LevelEditor.cpp:424
msgid "Delete selected layer"
-msgstr ""
+msgstr "Slett valgt lag"
#: ../src/LevelEditor.cpp:425
msgid "Configure selected layer"
-msgstr ""
+msgstr "Sett opp valgt lag"
#: ../src/LevelEditor.cpp:426
msgid "Move selected object to layer"
-msgstr ""
+msgstr "Flytt valgt objekt til lag"
#: ../src/LevelEditor.cpp:430 ../src/OptionsMenu.cpp:55
msgid "Settings"
-msgstr ""
+msgstr "Innstillinger"
#: ../src/LevelEditor.cpp:463
+#, fuzzy
msgid ""
"NOTE: the layers are sorted by name alphabetically.\n"
"The layer is background layer if its name is < 'f'\n"
"by dictionary order, otherwise it's foreground layer."
msgstr ""
+"MERK: Lagene sorteres etter navn alfabetisk.\n"
+"Laget er et bakgrunnslag hvis dets navn er < 'f'\n"
+"etter ordboksanordning, ellers er det forgrunnslaget."
#: ../src/LevelEditor.cpp:539
msgid "Notification block"
-msgstr ""
+msgstr "Merknadsblokk"
#: ../src/LevelEditor.cpp:545
msgid "Enter message here:"
-msgstr ""
+msgstr "Skriv inn melding her:"
#: ../src/LevelEditor.cpp:646
msgid "Behavior"
-msgstr ""
+msgstr "Oppførsel"
#: ../src/LevelEditor.cpp:665
msgid "State"
-msgstr ""
+msgstr "Tilstand"
#: ../src/LevelEditor.cpp:673
msgid "Conveyor belt speed"
-msgstr ""
+msgstr "Samlebåndshastighet"
#: ../src/LevelEditor.cpp:679
msgid "Enter speed here:"
-msgstr ""
+msgstr "Skriv inn hastighet her:"
#: ../src/LevelEditor.cpp:690
+#, fuzzy
msgid "NOTE: 1 Speed = 0.08 block/s"
-msgstr ""
+msgstr "Merk: 1 fartsenhet = 0.08 blokk/er"
#: ../src/LevelEditor.cpp:721
+#, fuzzy
msgid "Id:"
-msgstr ""
+msgstr "ID:"
#: ../src/LevelEditor.cpp:787
msgid "Level Scripting"
-msgstr ""
+msgstr "Nivåskript"
#: ../src/LevelEditor.cpp:892
msgid "Add layer"
-msgstr ""
+msgstr "Legg til lag"
#: ../src/LevelEditor.cpp:898
msgid "Enter the layer name:"
-msgstr ""
+msgstr "Skriv inn lagnavn:"
#: ../src/LevelEditor.cpp:943
#, c-format
msgid "Are you sure you want to delete layer '%s'?"
-msgstr ""
+msgstr "Er du sikker på at du vil slette laget \"%s\"?"
#: ../src/LevelEditor.cpp:944
msgid "Delete layer"
-msgstr ""
+msgstr "Slett lag"
#: ../src/LevelEditor.cpp:968
msgid "Layer settings"
-msgstr ""
+msgstr "Laginnstillinger"
#: ../src/LevelEditor.cpp:974
msgid "Layer name:"
-msgstr ""
+msgstr "Lagnavn:"
#: ../src/LevelEditor.cpp:989
msgid "Layer moving speed (1 speed = 0.8 block/s):"
-msgstr ""
+msgstr "Lagbevegelseshastighet (1 hastighet = 0,8 blokker/s):"
#: ../src/LevelEditor.cpp:1010
msgid "Speed of following camera:"
-msgstr ""
+msgstr "Hastighet på følgekamera:"
#: ../src/LevelEditor.cpp:1062
msgid "Move to layer"
-msgstr ""
+msgstr "Flytt til lag"
#: ../src/LevelEditor.cpp:1068
msgid "Enter the layer name (create new layer if necessary):"
-msgstr ""
+msgstr "Skriv innlagnavn (opprett nytt lag hvis nødvendig):"
#: ../src/LevelEditor.cpp:1132
msgid "Repeat mode"
-msgstr ""
+msgstr "Gjentagelsesmodus"
#: ../src/LevelEditor.cpp:1156
msgid "Custom scenery:"
-msgstr ""
+msgstr "Egendefinert omgivelsesmiljø:"
#: ../src/LevelEditor.cpp:1219
msgid "(Use the default appearance for this block)"
-msgstr ""
+msgstr "(Bruk forvalgt utseende for denne blokken)"
# TRANSLATORS: Block name
# TRANSLATORS: Context: Resize/Move ...
# TRANSLATORS: Context: Add/Remove ...
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1465 ../src/LevelEditor.cpp:1707
#: ../src/LevelEditor.cpp:1723 ../src/LevelEditor.cpp:1772
#: ../src/LevelEditor.cpp:4400
msgid "Custom scenery block"
-msgstr ""
+msgstr "Egendefinert omgivelsesmiljøblokk"
#: ../src/LevelEditor.cpp:1673
msgid "Toolbox"
-msgstr ""
+msgstr "Verktøyskasse"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Resize %s"
-msgstr ""
+msgstr "Endre størrelse på %s"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Move %s"
-msgstr ""
+msgstr "Flytt %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1713
#, c-format
msgid "Move %d object"
msgid_plural "Move %d objects"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Flytt %d objekt"
+msgstr[1] "Flytt %d objekter"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Add %s"
-msgstr ""
+msgstr "Legg til %s"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Remove %s"
-msgstr ""
+msgstr "Fjern %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1729
#, c-format
msgid "Add %d object"
msgid_plural "Add %d objects"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Legg til %d objekt"
+msgstr[1] "Legg til %d objekter"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1731
#, c-format
msgid "Remove %d object"
msgid_plural "Remove %d objects"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Fjern %d objekt"
+msgstr[1] "Fjern %d objekter"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1739
#, c-format
msgid "Add path to %s"
-msgstr ""
+msgstr "Legg til sti i %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1741
#, c-format
msgid "Remove a path point from %s"
-msgstr ""
+msgstr "Fjern stipunkt fra %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1747
#, c-format
msgid "Remove all paths from %s"
-msgstr ""
+msgstr "Fjern alle stier fra %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1753
#, c-format
msgid "Add link from %s to %s"
-msgstr ""
+msgstr "Legg til lenke fra %s til %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1759
#, c-format
msgid "Remove all links from %s"
-msgstr ""
+msgstr "Fjern alle lenker fra %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1766
+#, fuzzy
msgid "Modify the %2 property of %1"
-msgstr ""
+msgstr "Endre %s-egenskap for %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1818
#, c-format
msgid "Edit the script of %s"
-msgstr ""
+msgstr "Rediger skriptet for %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1821
msgid "Edit the script of level"
-msgstr ""
+msgstr "Rediger nivåets skript"
#: ../src/LevelEditor.cpp:2146 ../src/LevelEditor.cpp:2226
msgid "The level has unsaved changes."
-msgstr ""
+msgstr "Nivået har ulagrede endringer."
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Are you sure you want to quit?"
-msgstr ""
+msgstr "Er du sikker på at du vil avslutte?"
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Quit prompt"
-msgstr ""
+msgstr "Avslutt spørring"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
#, c-format
msgid "Level \"%s\" saved"
-msgstr ""
+msgstr "Nivået \"%s\" har blitt lagret"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
msgid "Saved"
-msgstr ""
+msgstr "Lagret"
#: ../src/LevelEditor.cpp:2859 ../src/LevelEditSelect.cpp:208
msgid "Name:"
-msgstr ""
+msgstr "Navn:"
#: ../src/LevelEditor.cpp:2866
msgid "Theme:"
-msgstr ""
+msgstr "Drakt:"
#: ../src/LevelEditor.cpp:2873
+#, fuzzy
msgid "Examples: %DATA%/themes/classic"
-msgstr ""
+msgstr "Eksempler: %DATA%/themes/classic"
#: ../src/LevelEditor.cpp:2875
msgid "or %USER%/themes/Orange"
-msgstr ""
+msgstr "eller %USER%/themes/Orange"
#: ../src/LevelEditor.cpp:2878
msgid "Music:"
-msgstr ""
+msgstr "Musikk:"
#: ../src/LevelEditor.cpp:2887
msgid "Target time (s):"
-msgstr ""
+msgstr "Måltid (s):"
#: ../src/LevelEditor.cpp:2903
msgid "Target recordings:"
-msgstr ""
+msgstr "Målopptak:"
#: ../src/LevelEditor.cpp:2919
msgid "Restart level editor is required"
-msgstr ""
+msgstr "Omstart av nivåredigerer kreves"
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3718
#: ../src/LevelEditor.cpp:3739
msgid "Please enter a layer name."
-msgstr ""
+msgstr "Skriv inn et lagnavn."
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3683
#: ../src/LevelEditor.cpp:3718 ../src/LevelEditor.cpp:3722
#: ../src/LevelEditor.cpp:3739 ../src/LevelEditor.cpp:3743
#: ../src/LevelEditSelect.cpp:644 ../src/LevelEditSelect.cpp:683
#: ../src/LevelEditSelect.cpp:688 ../src/LevelEditSelect.cpp:693
#: ../src/LevelEditSelect.cpp:698 ../src/LevelEditSelect.cpp:796
msgid "Error"
-msgstr ""
+msgstr "Feil"
#: ../src/LevelEditor.cpp:3683 ../src/LevelEditor.cpp:3722
#, c-format
msgid "The layer '%s' already exists."
-msgstr ""
+msgstr "Laget \"%s\" finnes allerede."
#: ../src/LevelEditor.cpp:3743
msgid "Source and destination layers are the same."
-msgstr ""
+msgstr "Kilde og mållag er de samme."
#: ../src/LevelEditor.cpp:3760
msgid "Scenery"
-msgstr ""
+msgstr "Omgivelsesmiljø"
#: ../src/LevelEditor.cpp:4190 ../src/LevelEditor.cpp:4218
#, c-format
msgid "Speed: %d = %0.2f block/s"
-msgstr ""
+msgstr "Hastighet: %d = %0.2f blokker/s"
#: ../src/LevelEditor.cpp:4203
msgid "Stop at this point"
-msgstr ""
+msgstr "Stopp på dette punktet"
#: ../src/LevelEditor.cpp:4208
#, c-format
msgid "Pause: %d = %0.3fs"
-msgstr ""
+msgstr "Pause: %d = %0.3fs"
#: ../src/LevelEditSelect.cpp:41 ../src/TitleMenu.cpp:45
msgid "Map Editor"
-msgstr ""
+msgstr "Kartredigerer"
#: ../src/LevelEditSelect.cpp:66
msgid "New Levelpack"
-msgstr ""
+msgstr "Ny nivåpakke"
#: ../src/LevelEditSelect.cpp:71
msgid "Pack Properties"
-msgstr ""
+msgstr "Pakkeegenskaper"
#: ../src/LevelEditSelect.cpp:76
msgid "Remove Pack"
-msgstr ""
+msgstr "Fjern pakke"
#: ../src/LevelEditSelect.cpp:81
msgid "Move Map"
-msgstr ""
+msgstr "Flytt kart"
#: ../src/LevelEditSelect.cpp:89
msgid "Remove Map"
-msgstr ""
+msgstr "FJern kart"
#: ../src/LevelEditSelect.cpp:94
msgid "Edit Map"
-msgstr ""
+msgstr "Rediger kart"
#: ../src/LevelEditSelect.cpp:205
msgid "Properties"
-msgstr ""
+msgstr "Egenskaper"
#: ../src/LevelEditSelect.cpp:217
msgid "Description:"
-msgstr ""
+msgstr "Beskrivelse:"
#: ../src/LevelEditSelect.cpp:226
msgid "Congratulation text:"
-msgstr ""
+msgstr "Gratuleringstekst:"
#: ../src/LevelEditSelect.cpp:235
msgid "Music list:"
-msgstr ""
+msgstr "Musikkliste:"
#: ../src/LevelEditSelect.cpp:265 ../src/LevelEditSelect.cpp:485
msgid "Add level"
-msgstr ""
+msgstr "Legg til nivå"
#: ../src/LevelEditSelect.cpp:268
+#, fuzzy
msgid "File name:"
-msgstr ""
+msgstr "Filnavn:"
#: ../src/LevelEditSelect.cpp:293
msgid "Move level"
-msgstr ""
+msgstr "Flytt nivå"
#: ../src/LevelEditSelect.cpp:296
+#, fuzzy
msgid "Level: "
-msgstr ""
+msgstr "Nivå: "
#: ../src/LevelEditSelect.cpp:310
msgid "Before"
-msgstr ""
+msgstr "Før"
#: ../src/LevelEditSelect.cpp:311
msgid "After"
-msgstr ""
+msgstr "Etter"
#: ../src/LevelEditSelect.cpp:368 ../src/LevelPlaySelect.cpp:124
msgid "Individual levels which are not contained in any level packs"
-msgstr ""
+msgstr "Individuelle nivå som ikke finnes i noen nivåpakker"
#: ../src/LevelEditSelect.cpp:577
#, c-format
msgid "Are you sure remove the level pack '%s'?"
-msgstr ""
+msgstr "Er du sikker på at du vil fjerne nivåpakken \"%s\"?"
#: ../src/LevelEditSelect.cpp:577 ../src/LevelEditSelect.cpp:607
msgid "Remove prompt"
-msgstr ""
+msgstr "Fjern spørring"
#: ../src/LevelEditSelect.cpp:607
#, c-format
msgid "Are you sure remove the map '%s'?"
-msgstr ""
+msgstr "Er du sikker på at du vil fjerne kartet \"%s\"?"
#: ../src/LevelEditSelect.cpp:644
msgid "Levelpack name cannot be empty."
-msgstr ""
+msgstr "Nivåpakkenavn kan ikke være tomt."
#: ../src/LevelEditSelect.cpp:683
#, c-format
msgid "The levelpack directory '%s' already exists!"
-msgstr ""
+msgstr "Nivåpakkemappen \"%s\" finnes allerede."
#: ../src/LevelEditSelect.cpp:688
#, c-format
msgid "Unable to create levelpack directory '%s'!"
-msgstr ""
+msgstr "Kunne ikke opprette nivåpakkemappe \"%s\"."
#: ../src/LevelEditSelect.cpp:693
#, c-format
msgid "The levelpack file '%s' already exists!"
-msgstr ""
+msgstr "Nivåpakkefilen \"%s\" finnes allerede."
#: ../src/LevelEditSelect.cpp:698
#, c-format
msgid "Unable to create levelpack file '%s'!"
-msgstr ""
+msgstr "Kunne ikke opprette nivåpakkefil \"%s\"."
#: ../src/LevelEditSelect.cpp:758
+#, fuzzy
msgid "No file name given for the new level."
-msgstr ""
+msgstr "Inget filnavn gitt for nytt nivå."
#: ../src/LevelEditSelect.cpp:758
+#, fuzzy
msgid "Missing file name"
-msgstr ""
+msgstr "Mangler filnavn"
#: ../src/LevelEditSelect.cpp:796
#, c-format
msgid "The file %s already exists."
-msgstr ""
+msgstr "Filen %s finnes allerede."
#: ../src/LevelEditSelect.cpp:849
msgid "The entered level number isn't valid!"
-msgstr ""
+msgstr "Innskrevet nivånummer er ikke gyldig!"
#: ../src/LevelEditSelect.cpp:849
msgid "Illegal number"
-msgstr ""
+msgstr "Ulovlig nummer"
#: ../src/LevelInfoRender.cpp:19
msgid "Choose a level"
-msgstr ""
+msgstr "Velg et nivå"
#: ../src/LevelInfoRender.cpp:20
msgid "Time:"
-msgstr ""
+msgstr "Tid:"
#: ../src/LevelInfoRender.cpp:21 ../src/StatisticsScreen.cpp:259
msgid "Recordings:"
-msgstr ""
+msgstr "Opptak:"
#: ../src/LevelPackManager.cpp:124
msgid "Custom Levels"
-msgstr ""
+msgstr "Egendefinerte nivå"
#: ../src/LevelPlaySelect.cpp:41
msgid "Select Level"
-msgstr ""
+msgstr "Velg nivå"
# TRANSLATORS: Used for button which clear any level progress like unlocked levels and highscores.
#: ../src/OptionsMenu.cpp:66
msgid "Clear Progress"
-msgstr ""
+msgstr "Opphev framdrift"
#: ../src/OptionsMenu.cpp:109
msgid "General"
-msgstr ""
+msgstr "Generelt"
#: ../src/OptionsMenu.cpp:110
msgid "Controls"
-msgstr ""
+msgstr "Kontroller"
#: ../src/OptionsMenu.cpp:121
msgid "Music"
-msgstr ""
+msgstr "Musikk"
#: ../src/OptionsMenu.cpp:129
msgid "Sound"
-msgstr ""
+msgstr "Lyd"
#: ../src/OptionsMenu.cpp:137
msgid "Resolution"
-msgstr ""
+msgstr "Oppløsning"
#: ../src/OptionsMenu.cpp:177
msgid "Language"
-msgstr ""
+msgstr "Språk"
# TRANSLATORS: as detect user's language automatically
#: ../src/OptionsMenu.cpp:185
msgid "Auto-Detect"
-msgstr ""
+msgstr "Oppdag automatisk"
#: ../src/OptionsMenu.cpp:209
msgid "Theme"
-msgstr ""
+msgstr "Drakt"
#: ../src/OptionsMenu.cpp:247
msgid "Internet proxy"
-msgstr ""
+msgstr "Internettmellomtjener"
#: ../src/OptionsMenu.cpp:256
msgid "Fullscreen"
-msgstr ""
+msgstr "Fullskjermsvisning"
#: ../src/OptionsMenu.cpp:261
msgid "Quick record"
-msgstr ""
+msgstr "Raskt opptak"
#: ../src/OptionsMenu.cpp:266
msgid "Internet"
-msgstr ""
+msgstr "Internett"
#: ../src/OptionsMenu.cpp:271
+#, fuzzy
msgid "Fade transition"
-msgstr ""
+msgstr "Overgangsutmykning"
#: ../src/OptionsMenu.cpp:294
msgid "Save Changes"
-msgstr ""
+msgstr "Lagre endringer"
#: ../src/OptionsMenu.cpp:513
msgid "Do you really want to reset level progress?"
-msgstr ""
+msgstr "Ønsker du virkelig å tilbakestille nivåfremdriften?"
#: ../src/OptionsMenu.cpp:513
msgid "Warning"
-msgstr ""
+msgstr "Advarsel"
#: ../src/StatisticsManager.cpp:386
+#, fuzzy
msgid "New achievement:"
-msgstr ""
+msgstr "Ny oppnåelse:"
#: ../src/StatisticsManager.cpp:394
-#, c-format
+#, c-format, fuzzy
msgid "Achieved on %s"
-msgstr ""
+msgstr "Oppnådd den %s"
#: ../src/StatisticsManager.cpp:400
msgid "Unknown achievement"
-msgstr ""
+msgstr "Ukjent oppnåelse"
#: ../src/StatisticsManager.cpp:406
-#, c-format
+#, c-format, fuzzy
msgid "Achieved %1.0f%%"
-msgstr ""
+msgstr "Oppnådd %1.0f%%"
#: ../src/StatisticsManager.cpp:410
msgid "Not achieved"
-msgstr ""
+msgstr "Ikke oppnådd"
#: ../src/StatisticsScreen.cpp:57 ../src/TitleMenu.cpp:55
+#, fuzzy
msgid "Achievements and Statistics"
-msgstr ""
+msgstr "Oppnåelser og statistikk"
#: ../src/StatisticsScreen.cpp:166
+#, fuzzy
msgid "Achievements"
-msgstr ""
+msgstr "Oppnåelser"
#: ../src/StatisticsScreen.cpp:167
msgid "Statistics"
-msgstr ""
+msgstr "Statistikk"
#: ../src/StatisticsScreen.cpp:234
msgid "Total"
-msgstr ""
+msgstr "Totalt"
#: ../src/StatisticsScreen.cpp:246
msgid "Traveling distance (m)"
-msgstr ""
+msgstr "Reist distanse (m)"
#: ../src/StatisticsScreen.cpp:247
+#, fuzzy
msgid "Jump times"
-msgstr ""
+msgstr "Hopp"
#: ../src/StatisticsScreen.cpp:248
+#, fuzzy
msgid "Die times"
-msgstr ""
+msgstr "Dødsfall"
#: ../src/StatisticsScreen.cpp:249
msgid "Squashed times"
-msgstr ""
+msgstr "Most"
#: ../src/StatisticsScreen.cpp:260
+#, fuzzy
msgid "Switch pulled times:"
-msgstr ""
+msgstr "Bryteraktuasjoner"
#: ../src/StatisticsScreen.cpp:261
+#, fuzzy
msgid "Swap times:"
-msgstr ""
+msgstr "Bytter"
#: ../src/StatisticsScreen.cpp:262
+#, fuzzy
msgid "Save times:"
-msgstr ""
+msgstr "Lagringer"
#: ../src/StatisticsScreen.cpp:263
msgid "Load times:"
-msgstr ""
+msgstr "Innlastinger"
#: ../src/StatisticsScreen.cpp:268
msgid "Completed levels:"
-msgstr ""
+msgstr "Fullførte nivå"
#: ../src/StatisticsScreen.cpp:306
msgid "In-game time:"
-msgstr ""
+msgstr "Spilltid"
#: ../src/StatisticsScreen.cpp:308
msgid "Level editing time:"
-msgstr ""
+msgstr "Redigeringstid"
#: ../src/StatisticsScreen.cpp:310
msgid "Created levels:"
-msgstr ""
+msgstr "Opprettede nivå"
#: ../src/TitleMenu.cpp:44
msgid "Options"
-msgstr ""
+msgstr "Valg"
#: ../src/TitleMenu.cpp:47
msgid "Quit"
-msgstr ""
+msgstr "Avslutt"
#: ../src/TitleMenu.cpp:131
+#, fuzzy
msgid "Enable internet in order to install addons."
-msgstr ""
+msgstr "Tilknytt deg Internett for å installere programtillegg."
#: ../src/TitleMenu.cpp:131
+#, fuzzy
msgid "Internet disabled"
-msgstr ""
+msgstr "Ikke tilknyttet Internett"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Return"
-msgstr ""
+msgstr "Tilbake"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Escape"
-msgstr ""
+msgstr "Esc"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Backspace"
-msgstr ""
+msgstr "Rettetast"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Tab"
-msgstr ""
+msgstr "Tab"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Space"
-msgstr ""
+msgstr "Mellomrom"
# TRANSLATORS: name of a key
+#, fuzzy
msgctxt "keys"
msgid "CapsLock"
-msgstr ""
+msgstr "Caps Lock"
# TRANSLATORS: name of a key
+#, fuzzy
msgctxt "keys"
msgid "PrintScreen"
-msgstr ""
+msgstr "Print Screen"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "ScrollLock"
-msgstr ""
+msgstr "Scroll Lock"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Pause"
-msgstr ""
+msgstr "Pause"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Insert"
-msgstr ""
+msgstr "Insert"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Home"
-msgstr ""
+msgstr "Home"
# TRANSLATORS: name of a key
+#, fuzzy
msgctxt "keys"
msgid "PageUp"
-msgstr ""
+msgstr "Page Up"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Delete"
-msgstr ""
+msgstr "Del"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "End"
-msgstr ""
+msgstr "End"
# TRANSLATORS: name of a key
+#, fuzzy
msgctxt "keys"
msgid "PageDown"
-msgstr ""
+msgstr "Page Down"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right"
-msgstr ""
+msgstr "→"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left"
-msgstr ""
+msgstr "←"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Down"
-msgstr ""
+msgstr "↓"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Up"
-msgstr ""
+msgstr "↑"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Numlock"
-msgstr ""
+msgstr "Num Lock"
# TRANSLATORS: name of a key
+#, fuzzy
msgctxt "keys"
msgid "SysReq"
-msgstr ""
+msgstr "Sys Req"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Ctrl"
-msgstr ""
+msgstr "Venstre Ctrl"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Shift"
-msgstr ""
+msgstr "Venstre Shift"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Alt"
-msgstr ""
+msgstr "Venstre Alt"
# TRANSLATORS: name of a key
+#, fuzzy
msgctxt "keys"
msgid "Left GUI"
-msgstr ""
+msgstr "Venstre visuelle grensesnitt"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Ctrl"
-msgstr ""
+msgstr "Høyre Ctrl"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Shift"
-msgstr ""
+msgstr "Høyre Shift"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Alt"
-msgstr ""
+msgstr "Høyre Alt"
# TRANSLATORS: name of a key
+#, fuzzy
msgctxt "keys"
msgid "Right GUI"
-msgstr ""
+msgstr "Høyre visuelle grensesnitt"
diff --git a/data/locale/ru.po b/data/locale/ru.po
index c38c0d1..3c79444 100644
--- a/data/locale/ru.po
+++ b/data/locale/ru.po
@@ -1,2110 +1,2027 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# Russian translation for Me and My Shadow strings
+# Copyright (C)
# This file is distributed under the same license as the meandmyshadow package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# Jz Pan <acme_pjz@hotmail.com>, 2012.
#
msgid ""
msgstr ""
-"Project-Id-Version: meandmyshadow 0.5svn\n"
+"Project-Id-Version: Russian (Me And My Shadow)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-09 16:18+0800\n"
-"PO-Revision-Date: 2018-09-13 12:13+0000\n"
-"Last-Translator: Jz Pan <acme_pjz@hotmail.com>\n"
+"PO-Revision-Date: 2018-09-29 22:36+0000\n"
+"Last-Translator: mesnevi <shams@airpost.net>\n"
"Language-Team: Russian <https://hosted.weblate.org/projects/me-and-my-shadow/"
"translations/ru/>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
-"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
-"(n%100>=11 && n%100<=14)? 2 : 3);\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<="
+"4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 3.2-dev\n"
#: ../src/AchievementList.cpp:43
msgid "Newbie"
msgstr "Новичок"
#: ../src/AchievementList.cpp:43
msgid "Complete a level."
msgstr "Завершить уровень."
#: ../src/AchievementList.cpp:44
msgid "Experienced player"
-msgstr "Опытный игрок"
+msgstr "Матёрый игрок"
#: ../src/AchievementList.cpp:44
msgid "Complete 50 levels."
msgstr "Завершить 50 уровней."
#: ../src/AchievementList.cpp:45
msgid "Good job!"
-msgstr "Отличная работа!"
+msgstr "Молодец!"
#: ../src/AchievementList.cpp:45
msgid "Receive a gold medal."
msgstr "Получить золотую медаль."
#: ../src/AchievementList.cpp:46
msgid "Expert"
-msgstr "Эксперт"
+msgstr "Специалист"
#: ../src/AchievementList.cpp:46
msgid "Earn 50 gold medal."
msgstr "Получить 50 золотых медалей."
#: ../src/AchievementList.cpp:48
msgid "Graduate"
msgstr "Выпускник"
#: ../src/AchievementList.cpp:48
msgid "Complete the tutorial level pack."
msgstr "Завершить все обучающие уровни."
#: ../src/AchievementList.cpp:49
msgid "Outstanding graduate"
msgstr "Отличник"
#: ../src/AchievementList.cpp:49
msgid "Complete the tutorial level pack with gold for all levels."
msgstr "Завершить все обучающие уровни, собрав все золотые медали."
#: ../src/AchievementList.cpp:51
msgid "Hooked"
-msgstr "Залип"
+msgstr "Зависимость"
#: ../src/AchievementList.cpp:51
msgid "Play Me and My Shadow for more than 2 hours."
-msgstr "Играть в Me and My Shadow более двух часов."
+msgstr "За игрой прошло более двух часов."
#: ../src/AchievementList.cpp:52
msgid "Loyal fan of Me and My Shadow"
-msgstr "Преданный фанат"
+msgstr "Преданный поклонник"
#: ../src/AchievementList.cpp:52
msgid "Play Me and My Shadow for more than 24 hours."
-msgstr "Играть в Me and My Shadow более 24 часов."
+msgstr "За игрой прошло более суток."
#: ../src/AchievementList.cpp:54
msgid "Constructor"
msgstr "Конструктор"
#: ../src/AchievementList.cpp:54
msgid "Use the level editor for more than 2 hours."
msgstr "Использовать редактор уровней более двух часов."
#: ../src/AchievementList.cpp:55
msgid "The creator"
msgstr "Творец"
#: ../src/AchievementList.cpp:55
msgid "Use the level editor for more than 24 hours."
-msgstr "Использовать редактор уровней более 24 часов."
+msgstr "Использовать редактор уровней более суток."
#: ../src/AchievementList.cpp:57
msgid "Look, cute level!"
-msgstr "Какая прелесть!"
+msgstr "Ах, какая прелесть!"
#: ../src/AchievementList.cpp:57
msgid "Create a level for the first time."
msgstr "Создать свой первый уровень."
#: ../src/AchievementList.cpp:58
msgid "The level museum"
msgstr "Музей уровнестроения"
#: ../src/AchievementList.cpp:58
msgid "Create 50 levels."
msgstr "Создать 50 уровней."
#: ../src/AchievementList.cpp:60
msgid "Hello, World!"
msgstr "Hello, World!"
#: ../src/AchievementList.cpp:60
msgid "Write a script for the first time."
msgstr "Написать свой первый скрипт."
#: ../src/AchievementList.cpp:62
msgid "Frog"
-msgstr "Лягушка"
+msgstr "Лягушка-попрыгушка"
#: ../src/AchievementList.cpp:62
msgid "Jump 1000 times."
msgstr "Прыгнуть 1000 раз."
#: ../src/AchievementList.cpp:64
msgid "Wanderer"
-msgstr "Ходилка"
+msgstr "Пешеход"
#: ../src/AchievementList.cpp:64
msgid "Travel 100 meters."
msgstr "Пройти 100 метров."
#: ../src/AchievementList.cpp:65
msgid "Runner"
msgstr "Бродилка"
#: ../src/AchievementList.cpp:65
msgid "Travel 1 kilometer."
msgstr "Пройти 1 километр."
#: ../src/AchievementList.cpp:66
msgid "Long distance runner"
msgstr "Дальнобойщик"
#: ../src/AchievementList.cpp:66
msgid "Travel 10 kilometers."
msgstr "Пройти 10 километров."
#: ../src/AchievementList.cpp:67
msgid "Marathon runner"
msgstr "Марафонец"
#: ../src/AchievementList.cpp:67
msgid "Travel 42,195 meters."
msgstr "Пройти 42195 метров."
#: ../src/AchievementList.cpp:69
msgid "Be careful!"
msgstr "Поберегись!"
#: ../src/AchievementList.cpp:69
msgid "Die for the first time."
msgstr "Умереть впервые."
#: ../src/AchievementList.cpp:70
msgid "It doesn't matter..."
-msgstr "Это привычка..."
+msgstr "Ну и ладно..."
#: ../src/AchievementList.cpp:70
msgid "Die 50 times."
msgstr "Умереть 50 раз."
#: ../src/AchievementList.cpp:71
msgid "Expert of trial and error"
msgstr "Лауреат премии Дарвина"
#: ../src/AchievementList.cpp:71
msgid "Die 1000 times."
msgstr "Умереть 1000 раз."
#: ../src/AchievementList.cpp:73
msgid "Keep an eye for moving blocks!"
msgstr "Осторожно, они двигаются!"
#: ../src/AchievementList.cpp:73
msgid "Get squashed for the first time."
msgstr "Быть раздавленным первый раз."
#: ../src/AchievementList.cpp:74
msgid "Potato masher"
msgstr "Пюре"
#: ../src/AchievementList.cpp:74
msgid "Get squashed 50 times."
msgstr "Быть раздавленным 50 раз."
#: ../src/AchievementList.cpp:76
msgid "Double kill"
msgstr "За двумя зайцами"
#: ../src/AchievementList.cpp:76
msgid "Get both the player and the shadow dead."
msgstr "Добиться смерти игрока и его тени."
#: ../src/AchievementList.cpp:78
msgid "Bad luck"
-msgstr "Неудача"
+msgstr "Тотальное невезение"
#: ../src/AchievementList.cpp:78
msgid "Die 5 times in under 5 seconds."
msgstr "Умереть 5 раз за менее, чем 5 секунд."
#: ../src/AchievementList.cpp:79
msgid "This level is too dangerous"
-msgstr "Здесь слишком опасно"
+msgstr "Да что ж это за жизнь такая?"
#: ../src/AchievementList.cpp:79
msgid "Die 10 times in under 5 seconds."
msgstr "Умереть 10 раз за менее, чем 5 секунд."
#: ../src/AchievementList.cpp:81
msgid "You forgot your friend"
-msgstr "Друзей не бросают"
+msgstr "Друзья? Нет, не слышал"
#: ../src/AchievementList.cpp:81
msgid "Finish the level with the player or the shadow dead."
msgstr "Завершить уровень при том, что игрок или тень мертвы."
#: ../src/AchievementList.cpp:82
msgid "Just in time"
msgstr "Точность - вежливость королей"
#: ../src/AchievementList.cpp:82
msgid "Reach the exit with the player and the shadow simultaneously."
msgstr "Достичь выхода игроком и тенью одновременно."
#: ../src/AchievementList.cpp:84
msgid "Recorder"
msgstr "По-медленнее, я записываю!"
#: ../src/AchievementList.cpp:84
msgid "Record 100 times."
msgstr "Выполнить запись 100 раз."
#: ../src/AchievementList.cpp:85
msgid "Shadowmaster"
-msgstr "У меня все хода записаны!"
+msgstr "У меня все ходы записаны!"
#: ../src/AchievementList.cpp:85
msgid "Record 1000 times."
msgstr "Выполнить запись 1000 раз."
#: ../src/AchievementList.cpp:87
msgid "Switch puller"
msgstr "Выключателевключатель"
#: ../src/AchievementList.cpp:87
msgid "Pull the switch 100 times."
msgstr "Включить выключатель 100 раз."
#: ../src/AchievementList.cpp:88
msgid "The switch is broken!"
msgstr "Выключателеломатель"
#: ../src/AchievementList.cpp:88
msgid "Pull the switch 1000 times."
msgstr "Включить выключатель 1000 раз."
#: ../src/AchievementList.cpp:90
msgid "Swapper"
msgstr "Обменный пункт"
#: ../src/AchievementList.cpp:90
msgid "Swap 100 times."
msgstr "Поменяться 100 раз."
#: ../src/AchievementList.cpp:91
msgid "Player to shadow to player to shadow..."
msgstr "Туда-сюдашка"
#: ../src/AchievementList.cpp:91
msgid "Swap 1000 times."
msgstr "Поменяться 1000 раз."
#: ../src/AchievementList.cpp:93
msgid "Play it save"
msgstr "Хорошо сохранился"
#: ../src/AchievementList.cpp:93
msgid "Save 1000 times."
msgstr "Сохраниться 1000 раз."
#: ../src/AchievementList.cpp:94
msgid "This game is too hard"
-msgstr "Игра слишком сложна"
+msgstr "Сизифов труд"
#: ../src/AchievementList.cpp:94
msgid "Load the game 1000 times."
msgstr "Загрузить игру 1000 раз."
#: ../src/AchievementList.cpp:96
msgid "No, thanks"
-msgstr "Нет, спасибо"
+msgstr "Нет уж, спасибо"
#: ../src/AchievementList.cpp:96
msgid "Complete a level with checkpoint, but without saving."
msgstr "Пройти уровень с чекпоинтом, но без сохранений."
#: ../src/AchievementList.cpp:98
msgid "Panic save"
-msgstr "Паникёр сохранений"
+msgstr "Паническое сохранение"
#: ../src/AchievementList.cpp:98
msgid "Save twice in 1 second."
msgstr "Сохраниться дважды за секунду."
#: ../src/AchievementList.cpp:99
msgid "Panic load"
-msgstr "Паникёр загрузок"
+msgstr "Паническая загрузка"
#: ../src/AchievementList.cpp:99
msgid "Load twice in 1 second."
msgstr "Загрузиться дважды за секунду."
#: ../src/AchievementList.cpp:101
msgid "Bad saving position"
-msgstr "Неудачное сохранение"
+msgstr "Местечко так себе"
#: ../src/AchievementList.cpp:101
msgid "Load the game and die within 1 second."
-msgstr "Загрузить одну и ту же сохранённую игру и умереть в течении секунды."
+msgstr "Загрузить сохранённую игру и умереть в течении секунды."
#: ../src/AchievementList.cpp:102
msgid "This level is too hard"
-msgstr "Слишком сложный уровень"
+msgstr "Да что ж такое"
#: ../src/AchievementList.cpp:102
msgid "Load the same save and die 100 times."
msgstr "Загрузить игру и умереть 100 раз."
#: ../src/AchievementList.cpp:104
msgid "Quick swap"
msgstr "Быстрая смена"
#: ../src/AchievementList.cpp:104
msgid "Swap twice in under a second."
msgstr "Поменяться дважды за секунду."
#: ../src/AchievementList.cpp:107
msgid "Horizontal confusion"
-msgstr "Учимся отличать право и лево"
+msgstr "Сено-солома"
#: ../src/AchievementList.cpp:107
msgid "Press left and right simultaneously."
msgstr "Нажать влево и вправо одновременно."
#: ../src/AchievementList.cpp:109
msgid "Cheater"
-msgstr "Читер"
+msgstr "Жулик"
#: ../src/AchievementList.cpp:109
msgid "Cheat in game."
-msgstr "Сыграть нечестно."
+msgstr "Не надо было жульничать."
#: ../src/AchievementList.cpp:111
msgid "Programmer"
msgstr "Тыжпрограммист"
#: ../src/AchievementList.cpp:111
msgid "Play the development version of Me and My Shadow."
msgstr "Играть в версию в разработке."
#: ../src/Addons.cpp:44 ../src/LevelPackManager.cpp:108
msgid "Levels"
msgstr "Уровни"
#: ../src/Addons.cpp:44
msgid "Single level which usually contain demanding puzzles"
msgstr "Один уровень, который обычно содержит головоломки"
#: ../src/Addons.cpp:45
msgid "Levelpacks"
msgstr "Сборники уровней"
#: ../src/Addons.cpp:45
msgid "Collection of levels with the same author or style"
-msgstr "Коллекция уровней от единого автора или с подобным стилем"
+msgstr "Несколько уровней, созданных единым автором или в подобном стиле"
#: ../src/Addons.cpp:46
msgid "Themes"
msgstr "Темы"
#: ../src/Addons.cpp:46
msgid "Give every block and background a new look and feel"
msgstr "Придать каждому блоку и фону новый вид и стиль"
#: ../src/Addons.cpp:55 ../src/TitleMenu.cpp:46
msgid "Addons"
msgstr "Дополнения"
#: ../src/Addons.cpp:87
msgid "Unable to initialize addon menu:"
-msgstr "Не удалось инициализировать меню дополнений:"
+msgstr "Не удалось запустить меню дополнений:"
#: ../src/Addons.cpp:95 ../src/Addons.cpp:158 ../src/Addons.cpp:662
#: ../src/Addons.cpp:690 ../src/CreditsMenu.cpp:89 ../src/LevelSelect.cpp:168
#: ../src/StatisticsScreen.cpp:159
msgid "Back"
msgstr "Назад"
#: ../src/Addons.cpp:169
msgid "ERROR: unable to download addons file!"
-msgstr "ОШИБКА: невозможно загрузить файл дополнения!"
+msgstr "ОШИБКА: не удалось загрузить файл дополнения!"
-# TRANSLATORS: addon_list is the name of a file and should not be translated.
#: ../src/Addons.cpp:182
msgid "ERROR: unable to load addon_list file!"
-msgstr "ОШИБКА: невозможно загрузить файл addon_list!"
+msgstr "ОШИБКА: не удалось загрузить файл addon_list!"
#: ../src/Addons.cpp:193
msgid "ERROR: Invalid file format of addons file!"
msgstr "ОШИБКА: Неверный формат файла дополнения!"
#: ../src/Addons.cpp:205
msgid "ERROR: Addon list version is unsupported!"
-msgstr "ОШИБКА: Не поддерживается версия списка дополнений!"
+msgstr "ОШИБКА: версия списка дополнений не поддерживается!"
-# TRANSLATORS: installed_addons is the name of a file and should not be
-# translated.
#: ../src/Addons.cpp:226
msgid "ERROR: Unable to create the installed_addons file."
-msgstr "ОШИБКА: Невозможно создать файл installed_addons."
+msgstr "ОШИБКА: не удалось создать список дополнений installed_addons."
#: ../src/Addons.cpp:238
msgid "ERROR: Invalid file format of the installed_addons!"
-msgstr "ОШИБКА: Неверный формат файла installed_addons!"
+msgstr "ОШИБКА: у списка дополнений installed_addons неправильный формат!"
-# TRANSLATORS: indicates the author of an addon.
#: ../src/Addons.cpp:389 ../src/Addons.cpp:621
#, c-format
msgid "by %s"
-msgstr "от автора %s"
+msgstr "автор %"
#: ../src/Addons.cpp:397
msgid "Installed"
msgstr "Установлено"
#: ../src/Addons.cpp:402
msgid "Updatable"
-msgstr "Обновить"
+msgstr "Есть обновления"
#: ../src/Addons.cpp:412
msgid "Not installed"
msgstr "Не установлено"
#: ../src/Addons.cpp:625
#, c-format
msgid "Version: %d\n"
msgstr "Версия: %d\n"
#: ../src/Addons.cpp:627
#, c-format
msgid "Installed version: %d\n"
msgstr "Установленная версия: %d\n"
#: ../src/Addons.cpp:630
#, c-format
msgid "License: %s\n"
msgstr "Лицензия: %s\n"
#: ../src/Addons.cpp:633
#, c-format
msgid "Website: %s\n"
-msgstr "Вебсайт: %s\n"
+msgstr "Веб-сайт: %s\n"
#: ../src/Addons.cpp:637
msgid "(No descriptions provided)"
msgstr "(Нет описания)"
#: ../src/Addons.cpp:657 ../src/Addons.cpp:684
msgid "Remove"
msgstr "Удалить"
#: ../src/Addons.cpp:673
msgid "Update"
msgstr "Обновить"
#: ../src/Addons.cpp:679
msgid "Install"
msgstr "Установить"
#: ../src/Addons.cpp:774
#, c-format
msgid "This addon can't be removed because it's needed by %s."
-msgstr "Это дополнение нельзя удалить, поскольку оно необходимо для %s."
+msgstr "Это дополнение нельзя удалить, оно необходимо для правильной работы %s."
#: ../src/Addons.cpp:774 ../src/Addons.cpp:1051
msgid "Dependency"
-msgstr "Зависит"
+msgstr "Зависимости"
#: ../src/Addons.cpp:803
#, c-format
msgid "WARNING: File '%s' appears to have been removed already."
-msgstr "ВНИМАНИЕ: Файл '%s', похоже, уже удалён."
+msgstr "ВНИМАНИЕ: файл '%s' уже удалён."
#: ../src/Addons.cpp:803 ../src/Addons.cpp:810 ../src/Addons.cpp:818
#: ../src/Addons.cpp:825 ../src/Addons.cpp:834 ../src/Addons.cpp:840
#: ../src/Addons.cpp:859 ../src/Addons.cpp:866 ../src/Addons.cpp:893
#: ../src/Addons.cpp:900 ../src/Addons.cpp:907 ../src/Addons.cpp:918
#: ../src/Addons.cpp:947 ../src/Addons.cpp:952 ../src/Addons.cpp:962
#: ../src/Addons.cpp:968 ../src/Addons.cpp:981 ../src/Addons.cpp:986
#: ../src/Addons.cpp:1008 ../src/Addons.cpp:1014 ../src/Addons.cpp:1044
msgid "Addon error"
msgstr "Ошибка дополнения"
#: ../src/Addons.cpp:810
#, c-format
msgid "ERROR: Unable to remove file '%s'!"
-msgstr "ОШИБКА: Невозможно удалить файл '%s'!"
+msgstr "ОШИБКА: не получается удалить файл '%s'!"
#: ../src/Addons.cpp:818
#, c-format
msgid "WARNING: Directory '%s' appears to have been removed already."
-msgstr "ВНИМАНИЕ: Папка '%s', похоже, уже удалена."
+msgstr "ВНИМАНИЕ: папка '%s' уже удалена."
#: ../src/Addons.cpp:825
#, c-format
msgid "ERROR: Unable to remove directory '%s'!"
-msgstr "ОШИБКА: Невозможно удалить папку '%s'!"
+msgstr "ОШИБКА: не получается удалить папку '%s'!"
#: ../src/Addons.cpp:834
#, c-format
msgid "WARNING: Level '%s' appears to have been removed already."
-msgstr "ВНИМАНИЕ: Уровень '%s', похоже, уже удалён."
+msgstr "ВНИМАНИЕ: уровень '%s' уже удалён."
#: ../src/Addons.cpp:840
#, c-format
msgid "ERROR: Unable to remove level '%s'!"
-msgstr "ОШИБКА: Невозможно удалить уровень '%s'!"
+msgstr "ОШИБКА: не получается удалить уровень '%s'!"
#: ../src/Addons.cpp:859
#, c-format
msgid "WARNING: Levelpack directory '%s' appears to have been removed already."
-msgstr "ВНИМАНИЕ: Папка со сборником уровней '%s', похоже, уже удалена."
+msgstr "ВНИМАНИЕ: папка со сборником уровней '%s' уже удалена."
#: ../src/Addons.cpp:866
#, c-format
msgid "ERROR: Unable to remove levelpack directory '%s'!"
-msgstr "ОШИБКА: Невозможно удалить папку сборника уровней '%s'!"
+msgstr "ОШИБКА: не получается удалить набор уровней '%s'!"
#: ../src/Addons.cpp:893
#, c-format
msgid "ERROR: Unable to download addon file %s."
-msgstr "ОШИБКА: Невозможно загрузить файл дополнения %s."
+msgstr "ОШИБКА: не получается загрузить файл дополнения %s."
#: ../src/Addons.cpp:900
#, c-format
msgid "ERROR: Unable to extract addon file %s."
-msgstr "ОШИБКА: Невозможно распаковать файл дополнения %s."
+msgstr "ОШИБКА: не получается распаковать файл дополнения %s."
#: ../src/Addons.cpp:907
msgid "ERROR: Addon is missing metadata!"
-msgstr "ОШИБКА: В дополнении отсутствуют метаданные!"
+msgstr "ОШИБКА: в дополнении отсутствуют метаданные!"
#: ../src/Addons.cpp:918
msgid "ERROR: Invalid file format for metadata file!"
-msgstr "ОШИБКА: Неверный формат в файле метаданных!"
+msgstr "ОШИБКА: неверный формат файла метаданных!"
#: ../src/Addons.cpp:947
#, c-format
msgid "WARNING: File '%s' already exists, addon may be broken or not working!"
-msgstr "ВНИМАНИЕ: Файл '%s' уже существует, дополнение может быть повреждено или быть неработоспособным!"
+msgstr "ВНИМАНИЕ: файл '%s' уже существует, дополнение может глючить или не работать!"
#: ../src/Addons.cpp:952
#, c-format
msgid ""
"WARNING: Unable to copy file '%s' to '%s', addon may be broken or not "
"working!"
-msgstr "ВНИМАНИЕ: Невозможно скопировать файл '%s' в '%s', дополнение может быть повреждено или быть неработоспособным!"
+msgstr ""
+"ВНИМАНИЕ: не получается скопировать файл '%s' в '%s', "
+"дополнение может глючить или не работать!"
#: ../src/Addons.cpp:962
#, c-format
msgid ""
"WARNING: Destination directory '%s' already exists, addon may be broken or "
"not working!"
-msgstr "ВНИМАНИЕ: Целевая папка '%s' уже существует, дополнение может быть повреждено или быть неработоспособным!"
+msgstr ""
+"ВНИМАНИЕ: папка '%s' уже существует, "
+"дополнение может глючить или не работать!"
#: ../src/Addons.cpp:968 ../src/Addons.cpp:1014
#, c-format
msgid ""
"WARNING: Unable to move directory '%s' to '%s', addon may be broken or not "
"working!"
-msgstr "ВНИМАНИЕ: Невозможно переместить папку '%s' в '%s', дополнение может быть повреждено или быть неработоспособным!"
+msgstr ""
+"ВНИМАНИЕ: не получается переместить папку '%s' в '%s',"
+"дополнение может глючить или не работать!"
#: ../src/Addons.cpp:981
#, c-format
msgid "WARNING: Level '%s' already exists, addon may be broken or not working!"
-msgstr "ВНИМАНИЕ: Уровень '%s' уже существует, дополнение может быть повреждено или быть неработоспособным!"
+msgstr "ВНИМАНИЕ: уровень '%s' уже существует, дополнение может глючить или не работать!"
#: ../src/Addons.cpp:986
#, c-format
msgid ""
"WARNING: Unable to copy level '%s' to '%s', addon may be broken or not "
"working!"
-msgstr "ВНИМАНИЕ: Невозможно скопировать уровень '%s' в '%s', дополнение может быть повреждено или быть неработоспособным!"
+msgstr ""
+"ВНИМАНИЕ: не получается скопировать уровень '%s' в '%s',"
+"дополнение может глючить или не работать!"
#: ../src/Addons.cpp:1008
#, c-format
msgid ""
"WARNING: Levelpack directory '%s' already exists, addon may be broken or not "
"working!"
-msgstr "ВНИМАНИЕ: Папка со сборником уровней '%s' уже существует, дополнение может быть повреждено или быть неработоспособным!"
+msgstr ""
+"ВНИМАНИЕ: папка со сборником уровней '%s' уже существует,"
+"дополнение может глючить или не работать!"
#: ../src/Addons.cpp:1044
#, c-format
msgid "ERROR: Addon requires another addon (%s) which can't be found!"
-msgstr "ОШИБКА: Дополнение требует другое дополнение (%s), которе не найдено!"
+msgstr ""
+"ОШИБКА: для правильной работы дополнения необходимо установить другое "
+"дополнение (%s), однако его не удалось найти!"
#: ../src/Addons.cpp:1051
#, c-format
msgid "The addon %s is needed and will be installed now."
-msgstr "Требуемое дополнение %s сейчас будет установлено."
+msgstr "Необходимо установить дополнение %s. Установка сейчас начнётся."
#: ../src/Block.cpp:822 ../src/LevelEditor.cpp:265
msgid "On"
msgstr "Вкл."
#: ../src/Block.cpp:823 ../src/LevelEditor.cpp:266
msgid "Off"
msgstr "Выкл."
#: ../src/CommandManager.cpp:41
#, c-format
msgid "Undo %s"
msgstr "Отменить %s"
#: ../src/CommandManager.cpp:43
msgid "Can't undo"
msgstr "Невозможно отменить"
#: ../src/CommandManager.cpp:49
#, c-format
msgid "Redo %s"
-msgstr "Вернуть %s"
+msgstr "Повторить %s"
#: ../src/CommandManager.cpp:51
msgid "Can't redo"
-msgstr "Невозможно вернуть"
+msgstr "Невозможно повторить"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:190
msgid "Resize level"
msgstr "Изменить размер уровня"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:807
msgid "Modify level property"
msgstr "Изменить свойства уровня"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:919
#, c-format
msgid "Add scenery layer %s"
msgstr "Добавить фоновый слой %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:921
#, c-format
msgid "Delete scenery layer %s"
msgstr "Удалить фоновый слой %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:951
#, c-format
msgid "Modify the property of scenery layer %s"
msgstr "Изменить свойства фонового слоя %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:1040
#, c-format
msgid "Move %d object from layer %s to layer %s"
msgid_plural "Move %d objects from layer %s to layer %s"
msgstr[0] "Переместить %d объект из слоя %s в слой %s"
msgstr[1] "Переместить %d объекта из слоя %s в слой %s"
msgstr[2] "Переместить %d объектов из слоя %s в слой %s"
msgstr[3] "Переместить %d объектов из слоя %s в слой %s"
#: ../src/CreditsMenu.cpp:35 ../src/TitleMenu.cpp:53
msgid "Credits"
-msgstr "Разработчики"
+msgstr "Благодарности"
# TRANSLATORS: Font used in GUI:
# - Use "knewave" for languages using Latin and Latin-derived alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:569 ../src/Functions.cpp:570 ../src/Functions.cpp:571
#: ../src/Functions.cpp:588
msgid "knewave"
msgstr "DejaVuSansCondensed-Oblique"
-# TRANSLATORS: Font used for normal text:
-# - Use "Blokletters-Viltstift" for languages using Latin and Latin-derived
-# alphabets
-# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:575
msgid "Blokletters-Viltstift"
msgstr "DejaVuSansCondensed-Oblique"
#: ../src/Functions.cpp:674
msgid "Loading..."
msgstr "Загрузка..."
#: ../src/Functions.cpp:1243 ../src/Functions.cpp:1270
#: ../src/LevelEditor.cpp:559 ../src/LevelEditor.cpp:693
#: ../src/LevelEditor.cpp:758 ../src/LevelEditor.cpp:821
#: ../src/LevelEditor.cpp:908 ../src/LevelEditor.cpp:1033
#: ../src/LevelEditor.cpp:1083 ../src/LevelEditor.cpp:1180
#: ../src/LevelEditor.cpp:1244 ../src/LevelEditor.cpp:2923
#: ../src/LevelEditSelect.cpp:244 ../src/LevelEditSelect.cpp:277
#: ../src/LevelEditSelect.cpp:317
msgid "OK"
msgstr "Да"
#: ../src/Functions.cpp:1244 ../src/Functions.cpp:1256
#: ../src/Functions.cpp:1266 ../src/LevelEditor.cpp:565
#: ../src/LevelEditor.cpp:699 ../src/LevelEditor.cpp:764
#: ../src/LevelEditor.cpp:827 ../src/LevelEditor.cpp:914
#: ../src/LevelEditor.cpp:1039 ../src/LevelEditor.cpp:1089
#: ../src/LevelEditor.cpp:1186 ../src/LevelEditor.cpp:1250
#: ../src/LevelEditor.cpp:2929 ../src/LevelEditSelect.cpp:248
#: ../src/LevelEditSelect.cpp:281 ../src/LevelEditSelect.cpp:321
#: ../src/OptionsMenu.cpp:289
msgid "Cancel"
msgstr "Отмена"
#: ../src/Functions.cpp:1248
msgid "Abort"
msgstr "Прервать"
#: ../src/Functions.cpp:1249 ../src/Functions.cpp:1265
msgid "Retry"
msgstr "Попробовать снова"
#: ../src/Functions.cpp:1250
msgid "Ignore"
-msgstr "Игнорировать"
+msgstr "Пропустить"
#: ../src/Functions.cpp:1254 ../src/Functions.cpp:1260
msgid "Yes"
msgstr "Да"
#: ../src/Functions.cpp:1255 ../src/Functions.cpp:1261
msgid "No"
msgstr "Нет"
-# TRANSLATORS: Please do not remove %s or %d from your translation:
-# - %d means the level number in a levelpack
-# - %s means the name of current level
#: ../src/Game.cpp:280 ../src/Game.cpp:1236
#, c-format
msgid "Level %d %s"
msgstr "Уровень %d %s"
-# TRANSLATORS: Please do not remove %s from your translation:
-# - %s will be replaced with current action key
#: ../src/Game.cpp:915
#, c-format
msgid "Press %s key to save the game."
msgstr "Чтобы сохранить игру, нажмите клавишу %s."
-# TRANSLATORS: Please do not remove %s from your translation:
-# - %s will be replaced with current action key
#: ../src/Game.cpp:920
#, c-format
msgid "Press %s key to swap the position of player and shadow."
msgstr "Чтобы поменять позицию игрока и тени, нажмите клавишу %s."
-# TRANSLATORS: Please do not remove %s from your translation:
-# - %s will be replaced with current action key
#: ../src/Game.cpp:925
#, c-format
msgid "Press %s key to activate the switch."
msgstr "Чтобы активировать переключатель, нажмите клавишу %s."
-# TRANSLATORS: Please do not remove %s from your translation:
-# - %s will be replaced with current action key
#: ../src/Game.cpp:930
#, c-format
msgid "Press %s key to teleport."
msgstr "Чтобы телепортироваться, нажмите клавишу %s."
-# TRANSLATORS: Please do not remove %s from your translation:
-# - first %s means currently configured key to restart game
-# - Second %s means configured key to load from last save
#: ../src/Game.cpp:972
#, c-format
msgid "Press %s to restart current level or press %s to load the game."
msgstr "Чтобы перезапустить этот уровень, нажмите %s, или нажмите %s, чтобы загрузить игру."
-# TRANSLATORS: Please do not remove %s from your translation:
-# - %s will be replaced with currently configured key to restart game
#: ../src/Game.cpp:983
#, c-format
msgid "Press %s to restart current level."
msgstr "Чтобы перезапустить этот уровень, нажмите %s."
#: ../src/Game.cpp:996
msgid "Your shadow has died."
msgstr "Ваша тень умерла."
#: ../src/Game.cpp:1052
#, c-format
msgid "%d recording"
msgid_plural "%d recordings"
msgstr[0] "%d запись"
msgstr[1] "%d записи"
msgstr[2] "%d записей"
msgstr[3] "%d записей"
#: ../src/Game.cpp:1224
msgid "You've finished:"
-msgstr "Вы завершили:"
+msgstr "Завершенных уровней:"
-# TRANSLATORS: Please do not remove %-.2f from your translation:
-# - %-.2f means time in seconds
-# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1291
#, c-format
msgid "Time: %-.2fs"
msgstr "Время: %-.2fs"
-# TRANSLATORS: Please do not remove %-.2f from your translation:
-# - %-.2f means time in seconds
-# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1300
#, c-format
msgid "Best time: %-.2fs"
msgstr "Лучшее время: %-.2fs"
#: ../src/Game.cpp:1311
#, c-format
msgid "Target time: %-.2fs"
msgstr "Необходимое время: %-.2fs"
-# TRANSLATORS: Please do not remove %d from your translation:
-# - %d means the number of recordings user has made
#: ../src/Game.cpp:1332
#, c-format
msgid "Recordings: %d"
msgstr "Записей: %d"
-# TRANSLATORS: Please do not remove %d from your translation:
-# - %d means the number of recordings user has made
#: ../src/Game.cpp:1340
#, c-format
msgid "Best recordings: %d"
msgstr "Лучшая запись: %d"
#: ../src/Game.cpp:1350
#, c-format
msgid "Target recordings: %d"
msgstr "Необходимо записей: %d"
-# TRANSLATORS: Please do not remove %s from your translation:
-# - %s will be replaced with name of a prize medal (gold, silver or bronze)
#: ../src/Game.cpp:1363
#, c-format
msgid "You earned the %s medal"
msgstr "Вы получили %s медаль"
#: ../src/Game.cpp:1363
msgid "GOLD"
msgstr "ЗОЛОТУЮ"
#: ../src/Game.cpp:1363
msgid "SILVER"
msgstr "СЕРЕБРЯНУЮ"
#: ../src/Game.cpp:1363
msgid "BRONZE"
msgstr "БРОНЗОВУЮ"
-# TRANSLATORS: used as return to the level selector menu
#: ../src/Game.cpp:1390
msgid "Menu"
msgstr "Меню"
-# TRANSLATORS: used as restart level
#: ../src/Game.cpp:1397 ../src/InputManager.cpp:47
msgid "Restart"
msgstr "Снова"
-# TRANSLATORS: used as next level
#: ../src/Game.cpp:1404
msgid "Next"
msgstr "Далее"
#: ../src/Game.cpp:1430
msgid "Game replay is done."
msgstr "Воспроизведение завершено."
#: ../src/Game.cpp:1430
msgid "Game Replay"
msgstr "Воспроизведение игры"
#: ../src/Game.cpp:1767 ../src/Game.cpp:1769
msgid "Congratulations"
msgstr "Поздравляем"
#: ../src/Game.cpp:1769
msgid "You have finished the levelpack!"
msgstr "Вы закончили этот сборник уровней!"
#: ../src/InputManager.cpp:46
msgid "Up (in menu)"
msgstr "Вверх (в меню)"
#: ../src/InputManager.cpp:46
msgid "Down (in menu)"
msgstr "Вниз (в меню)"
#: ../src/InputManager.cpp:46
msgid "Left"
msgstr "Влево"
#: ../src/InputManager.cpp:46
msgid "Right"
msgstr "Вправо"
#: ../src/InputManager.cpp:46
msgid "Jump"
msgstr "Прыжок"
#: ../src/InputManager.cpp:46
msgid "Action"
msgstr "Действие"
#: ../src/InputManager.cpp:46
msgid "Space (Record)"
msgstr "Пробел (Запись)"
#: ../src/InputManager.cpp:46
msgid "Cancel recording"
msgstr "Отменить запись"
#: ../src/InputManager.cpp:47
msgid "Escape"
msgstr "Выход"
#: ../src/InputManager.cpp:47
msgid "Tab (View shadow/Level prop.)"
msgstr "Tab (См. тень/Настр. уровня)"
#: ../src/InputManager.cpp:47
msgid "Save game (in editor)"
msgstr "Сохранить игру (в редакторе)"
#: ../src/InputManager.cpp:47
msgid "Load game"
msgstr "Загрузить игру"
#: ../src/InputManager.cpp:47
msgid "Swap (in editor)"
msgstr "Перестановка (в редакторе)"
#: ../src/InputManager.cpp:48
msgid "Teleport (in editor)"
msgstr "Телепорт (в редакторе)"
#: ../src/InputManager.cpp:48
msgid "Suicide (in editor)"
msgstr "Смерть (в редакторе)"
#: ../src/InputManager.cpp:48
msgid "Shift (in editor)"
msgstr "Сдвиг (в редакторе)"
#: ../src/InputManager.cpp:48
msgid "Next block type (in Editor)"
msgstr "Следующий вид блока (в редакторе)"
#: ../src/InputManager.cpp:49
msgid "Previous block type (in editor)"
msgstr "Предыдущий вид блока (в редакторе)"
#: ../src/InputManager.cpp:49
msgid "Select (in menu)"
msgstr "Выбрать (в меню)"
-# TRANSLAOTRS: This is used when the name of the key code is not found.
#: ../src/InputManager.cpp:156
#, c-format
msgid "(Key %d)"
msgstr "(Клавиша %d)"
#: ../src/InputManager.cpp:163
#, c-format
msgid "Joystick axis %d %s"
msgstr "Ось джойстика %d %s"
#: ../src/InputManager.cpp:166
#, c-format
msgid "Joystick button %d"
msgstr "Кнопка джойстика %d"
#: ../src/InputManager.cpp:171
#, c-format
msgid "Joystick hat %d left"
msgstr "Джойстик %d влево"
#: ../src/InputManager.cpp:174
#, c-format
msgid "Joystick hat %d right"
msgstr "Джойстик %d вправо"
#: ../src/InputManager.cpp:177
#, c-format
msgid "Joystick hat %d up"
msgstr "Джойстик %d вверх"
#: ../src/InputManager.cpp:180
#, c-format
msgid "Joystick hat %d down"
msgstr "Джойстик %d вниз"
-# TRANSLAOTRS: This is used when the JOYSTICK_HAT value is invalid.
#: ../src/InputManager.cpp:185
#, c-format
msgid "Joystick hat %d %d"
msgstr "Джойстик %d %d"
#: ../src/InputManager.cpp:202
msgid "OR"
msgstr "ИЛИ"
#: ../src/InputManager.cpp:416
msgid "Select an item and press a key to change it."
msgstr "Выберите элемент и нажмите клавишу, чтобы изменить его."
#: ../src/InputManager.cpp:419
msgid "Press backspace to clear the selected item."
msgstr "Нажмите backspace, чтобы очистить выбранный элемент."
#: ../src/LevelEditor.cpp:56
msgid "Block"
msgstr "Блок"
#: ../src/LevelEditor.cpp:56
msgid "Player Start"
msgstr "Здесь начинает игрок"
#: ../src/LevelEditor.cpp:56
msgid "Shadow Start"
msgstr "Здесь начинает тень"
#: ../src/LevelEditor.cpp:57
msgid "Exit"
msgstr "Выход"
#: ../src/LevelEditor.cpp:57
msgid "Shadow Block"
msgstr "Блок для тени"
#: ../src/LevelEditor.cpp:57
msgid "Spikes"
msgstr "Шипы"
#: ../src/LevelEditor.cpp:58
msgid "Checkpoint"
msgstr "Чекпоинт"
#: ../src/LevelEditor.cpp:58 ../src/LevelEditSelect.cpp:312
msgid "Swap"
msgstr "Поменяться"
#: ../src/LevelEditor.cpp:58
msgid "Fragile"
msgstr "Хрупкий"
#: ../src/LevelEditor.cpp:59
msgid "Moving Block"
msgstr "Движущийся блок"
#: ../src/LevelEditor.cpp:59
msgid "Moving Shadow Block"
msgstr "Движущийся блок для тени"
#: ../src/LevelEditor.cpp:59
msgid "Moving Spikes"
msgstr "Движущиеся шипы"
#: ../src/LevelEditor.cpp:60
msgid "Teleporter"
msgstr "Телепортер"
#: ../src/LevelEditor.cpp:60
msgid "Button"
msgstr "Кнопка"
#: ../src/LevelEditor.cpp:60
msgid "Switch"
msgstr "Переключатель"
#: ../src/LevelEditor.cpp:61
msgid "Conveyor Belt"
msgstr "Конвейер"
#: ../src/LevelEditor.cpp:61
msgid "Shadow Conveyor Belt"
msgstr "Конвейер для тени"
#: ../src/LevelEditor.cpp:61
msgid "Notification Block"
msgstr "Блок оповещения"
#: ../src/LevelEditor.cpp:61
msgid "Collectable"
msgstr "Подбираемый предмет"
#: ../src/LevelEditor.cpp:61
msgid "Pushable"
msgstr "Толкаемый предмет"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:310
msgid "Select"
msgstr "Выбрать"
#: ../src/LevelEditor.cpp:65
msgid "Add"
msgstr "Добавить"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:311
msgid "Delete"
msgstr "Удалить"
#: ../src/LevelEditor.cpp:65 ../src/LevelPlaySelect.cpp:66
#: ../src/TitleMenu.cpp:43
msgid "Play"
msgstr "Играть"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:2852
msgid "Level settings"
msgstr "Настройки уровня"
#: ../src/LevelEditor.cpp:65
msgid "Save level"
msgstr "Сохранить уровень"
#: ../src/LevelEditor.cpp:65
msgid "Back to menu"
msgstr "Назад в меню"
#: ../src/LevelEditor.cpp:65
msgid "Configure"
msgstr "Настройки"
#: ../src/LevelEditor.cpp:84
#, c-format
msgid "%s (Scenery)"
msgstr "%s (Фон)"
#: ../src/LevelEditor.cpp:267
msgid "Toggle"
msgstr "Переключать"
#: ../src/LevelEditor.cpp:270
msgid "Complete"
msgstr "Завершённый"
#: ../src/LevelEditor.cpp:271
msgid "One step"
msgstr "Один шаг"
#: ../src/LevelEditor.cpp:272
msgid "Two steps"
msgstr "Два шага"
#: ../src/LevelEditor.cpp:273
msgid "Gone"
msgstr "Разрушен"
#: ../src/LevelEditor.cpp:291
msgid "Negative infinity"
msgstr "Минус бесконечность"
#: ../src/LevelEditor.cpp:293
msgid "Zero"
msgstr "Ноль"
#: ../src/LevelEditor.cpp:295
msgid "Level size"
msgstr "Размер уровня"
#: ../src/LevelEditor.cpp:297
msgid "Positive infinity"
msgstr "Плюс бесконечность"
#: ../src/LevelEditor.cpp:299
msgid "Default"
-msgstr "По умолчанию"
+msgstr "Стандартный"
#: ../src/LevelEditor.cpp:308
msgid "Deselect"
msgstr "Отменить выбор"
#: ../src/LevelEditor.cpp:318 ../src/LevelEditor.cpp:1136
#, c-format
msgid "Horizontal repeat start: %s"
msgstr "Начать горизонтальное повторение: %s"
#: ../src/LevelEditor.cpp:320 ../src/LevelEditor.cpp:1137
#, c-format
msgid "Horizontal repeat end: %s"
msgstr "Завершить горизонтальное повторение: %s"
#: ../src/LevelEditor.cpp:322 ../src/LevelEditor.cpp:1138
#, c-format
msgid "Vertical repeat start: %s"
msgstr "Начать вертикальное повторение: %s"
#: ../src/LevelEditor.cpp:324 ../src/LevelEditor.cpp:1139
#, c-format
msgid "Vertical repeat end: %s"
msgstr "Завершить вертикальное повторение: %s"
#: ../src/LevelEditor.cpp:329 ../src/LevelEditor.cpp:1150
msgid "Custom scenery"
msgstr "Настроить фон"
#: ../src/LevelEditor.cpp:335 ../src/LevelEditor.cpp:600
#: ../src/LevelEditor.cpp:602
msgid "Visible"
msgstr "Видимый"
#: ../src/LevelEditor.cpp:344
msgid "Link"
msgstr "Ссылка"
#: ../src/LevelEditor.cpp:345
msgid "Remove Links"
msgstr "Убрать ссылки"
#: ../src/LevelEditor.cpp:349 ../src/LevelEditor.cpp:624
#: ../src/LevelEditor.cpp:626
msgid "Automatic"
msgstr "Авто"
#: ../src/LevelEditor.cpp:359 ../src/LevelEditor.cpp:649
#, c-format
msgid "Behavior: %s"
msgstr "Поведение: %s"
#: ../src/LevelEditor.cpp:362
msgid "Path"
msgstr "Путь"
#: ../src/LevelEditor.cpp:363
msgid "Remove Path"
msgstr "Удалить путь"
#: ../src/LevelEditor.cpp:365 ../src/LevelEditor.cpp:371
#: ../src/LevelEditor.cpp:587 ../src/LevelEditor.cpp:589
msgid "Activated"
msgstr "Активировано"
#: ../src/LevelEditor.cpp:366 ../src/LevelEditor.cpp:612
#: ../src/LevelEditor.cpp:614
msgid "Looping"
msgstr "Повторять"
#: ../src/LevelEditor.cpp:372 ../src/LevelEditor.cpp:3526
msgid "Speed"
msgstr "Скорость"
#: ../src/LevelEditor.cpp:378 ../src/LevelEditor.cpp:668
#, c-format
msgid "State: %s"
msgstr "Состояние: %s"
#: ../src/LevelEditor.cpp:382 ../src/LevelEditor.cpp:3511
msgid "Message"
msgstr "Сообщение"
#: ../src/LevelEditor.cpp:384 ../src/LevelEditor.cpp:1202
#: ../src/LevelEditor.cpp:3825
msgid "Appearance"
msgstr "Внешний вид"
#: ../src/LevelEditor.cpp:389 ../src/LevelEditor.cpp:431
#: ../src/LevelEditor.cpp:715
msgid "Scripting"
msgstr "Скрипты"
#: ../src/LevelEditor.cpp:402 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Background layer: %s"
msgstr "Слой фона: %s"
#: ../src/LevelEditor.cpp:409 ../src/LevelEditor.cpp:866
#: ../src/LevelEditor.cpp:884
msgid "Blocks layer"
msgstr "Сбой блоков"
#: ../src/LevelEditor.cpp:417 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Foreground layer: %s"
msgstr "Передний план: %s"
#: ../src/LevelEditor.cpp:423
msgid "Add new layer"
msgstr "Добавить слой"
#: ../src/LevelEditor.cpp:424
msgid "Delete selected layer"
msgstr "Удалить выбранный слой"
#: ../src/LevelEditor.cpp:425
msgid "Configure selected layer"
msgstr "Настроить выбранный слой"
#: ../src/LevelEditor.cpp:426
msgid "Move selected object to layer"
msgstr "Переместить выбранный объект в слой"
#: ../src/LevelEditor.cpp:430 ../src/OptionsMenu.cpp:55
msgid "Settings"
msgstr "Настройки"
#: ../src/LevelEditor.cpp:463
msgid ""
"NOTE: the layers are sorted by name alphabetically.\n"
"The layer is background layer if its name is < 'f'\n"
"by dictionary order, otherwise it's foreground layer."
msgstr "ЗАМЕТКА: слои отсортированы по названию в алфавитном порядке.\n"
"Слой является фоновым слоем, если его имя < 'f'\n"
"по словарному порядку, в противоположном случае - это слой переднего плана."
#: ../src/LevelEditor.cpp:539
msgid "Notification block"
msgstr "Блок оповещения"
#: ../src/LevelEditor.cpp:545
msgid "Enter message here:"
msgstr "Введите сообщение:"
#: ../src/LevelEditor.cpp:646
msgid "Behavior"
msgstr "Поведение"
#: ../src/LevelEditor.cpp:665
msgid "State"
msgstr "Состояние"
#: ../src/LevelEditor.cpp:673
msgid "Conveyor belt speed"
msgstr "Скорость конвейера"
#: ../src/LevelEditor.cpp:679
msgid "Enter speed here:"
msgstr "Введите скорость:"
#: ../src/LevelEditor.cpp:690
msgid "NOTE: 1 Speed = 0.08 block/s"
msgstr "ПРИМ.: 1 Скорость = 0,08 блоков/с"
#: ../src/LevelEditor.cpp:721
msgid "Id:"
msgstr "Ид:"
#: ../src/LevelEditor.cpp:787
msgid "Level Scripting"
msgstr "Скрипты уровня"
#: ../src/LevelEditor.cpp:892
msgid "Add layer"
msgstr "Добавить слой"
#: ../src/LevelEditor.cpp:898
msgid "Enter the layer name:"
msgstr "Введите название слоя:"
#: ../src/LevelEditor.cpp:943
#, c-format
msgid "Are you sure you want to delete layer '%s'?"
msgstr "Вы уверены, что Вы хотите удалить слой '%s'?"
#: ../src/LevelEditor.cpp:944
msgid "Delete layer"
msgstr "Удалить слой"
#: ../src/LevelEditor.cpp:968
msgid "Layer settings"
msgstr "Настроить слой"
#: ../src/LevelEditor.cpp:974
msgid "Layer name:"
msgstr "Название слоя:"
#: ../src/LevelEditor.cpp:989
msgid "Layer moving speed (1 speed = 0.8 block/s):"
msgstr "Скорость перемещения слоя (1 скорость = 0,08 блоков/с):"
#: ../src/LevelEditor.cpp:1010
msgid "Speed of following camera:"
msgstr "Скорость камеры:"
#: ../src/LevelEditor.cpp:1062
msgid "Move to layer"
msgstr "Переместить в слой"
#: ../src/LevelEditor.cpp:1068
msgid "Enter the layer name (create new layer if necessary):"
msgstr "Введите название слоя (создать новый слой при необходимости):"
#: ../src/LevelEditor.cpp:1132
msgid "Repeat mode"
msgstr "Режим повторения"
#: ../src/LevelEditor.cpp:1156
msgid "Custom scenery:"
msgstr "Фон:"
#: ../src/LevelEditor.cpp:1219
msgid "(Use the default appearance for this block)"
msgstr "(Использовать обычный вид этого блока)"
-# TRANSLATORS: Block name
-# TRANSLATORS: Context: Resize/Move ...
-# TRANSLATORS: Context: Add/Remove ...
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1465 ../src/LevelEditor.cpp:1707
#: ../src/LevelEditor.cpp:1723 ../src/LevelEditor.cpp:1772
#: ../src/LevelEditor.cpp:4400
msgid "Custom scenery block"
msgstr "Блок фона"
#: ../src/LevelEditor.cpp:1673
msgid "Toolbox"
msgstr "Инструменты"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Resize %s"
msgstr "Изменить размер %s"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Move %s"
msgstr "Переместить %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1713
#, c-format
msgid "Move %d object"
msgid_plural "Move %d objects"
msgstr[0] "Переместить %d объект"
msgstr[1] "Переместить %d объекта"
msgstr[2] "Переместить %d объектов"
msgstr[3] "Переместить %d объектов"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Add %s"
msgstr "Добавить %s"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Remove %s"
msgstr "Удалить %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1729
#, c-format
msgid "Add %d object"
msgid_plural "Add %d objects"
msgstr[0] "Добавить %d объект"
msgstr[1] "Добавить %d объекта"
msgstr[2] "Добавить %d объектов"
msgstr[3] "Добавить %d объектов"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1731
#, c-format
msgid "Remove %d object"
msgid_plural "Remove %d objects"
msgstr[0] "Удалить %d объект"
msgstr[1] "Удалить %d объекта"
msgstr[2] "Удалить %d объектов"
msgstr[3] "Удалить %d объектов"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1739
#, c-format
msgid "Add path to %s"
msgstr "Добавить путь к %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1741
#, c-format
msgid "Remove a path point from %s"
msgstr "Удалить точку пути из %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1747
#, c-format
msgid "Remove all paths from %s"
msgstr "Удалить все пути из %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1753
#, c-format
msgid "Add link from %s to %s"
msgstr "Добавить сслыку между %s и %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1759
#, c-format
msgid "Remove all links from %s"
msgstr "Удалить все ссылки из %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1766
msgid "Modify the %2 property of %1"
msgstr "Изменить у %1 свойство %2"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1818
#, c-format
msgid "Edit the script of %s"
msgstr "Редактировать скрипт %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1821
msgid "Edit the script of level"
msgstr "Редактировать скрипт уровня"
#: ../src/LevelEditor.cpp:2146 ../src/LevelEditor.cpp:2226
msgid "The level has unsaved changes."
msgstr "В уровне есть несохранённые изменения."
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Are you sure you want to quit?"
msgstr "Вы действительно хотите выйти?"
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Quit prompt"
msgstr "Подтвердить выход"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
#, c-format
msgid "Level \"%s\" saved"
msgstr "Уровень \"%s\" сохранён"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
msgid "Saved"
msgstr "Сохранено"
#: ../src/LevelEditor.cpp:2859 ../src/LevelEditSelect.cpp:208
msgid "Name:"
msgstr "Название:"
#: ../src/LevelEditor.cpp:2866
msgid "Theme:"
msgstr "Тема:"
#: ../src/LevelEditor.cpp:2873
msgid "Examples: %DATA%/themes/classic"
msgstr "Примеры: %DATA%/themes/classic"
#: ../src/LevelEditor.cpp:2875
msgid "or %USER%/themes/Orange"
msgstr "или %USER%/themes/Orange"
#: ../src/LevelEditor.cpp:2878
msgid "Music:"
msgstr "Музыка:"
#: ../src/LevelEditor.cpp:2887
msgid "Target time (s):"
msgstr "Необходимое время (с):"
#: ../src/LevelEditor.cpp:2903
msgid "Target recordings:"
msgstr "Необходимо записей:"
#: ../src/LevelEditor.cpp:2919
msgid "Restart level editor is required"
msgstr "Необходимо перезапустить редактор уровней"
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3718
#: ../src/LevelEditor.cpp:3739
msgid "Please enter a layer name."
msgstr "Пожалуйста, введите название слоя."
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3683
#: ../src/LevelEditor.cpp:3718 ../src/LevelEditor.cpp:3722
#: ../src/LevelEditor.cpp:3739 ../src/LevelEditor.cpp:3743
#: ../src/LevelEditSelect.cpp:644 ../src/LevelEditSelect.cpp:683
#: ../src/LevelEditSelect.cpp:688 ../src/LevelEditSelect.cpp:693
#: ../src/LevelEditSelect.cpp:698 ../src/LevelEditSelect.cpp:796
msgid "Error"
msgstr "Ошибка"
#: ../src/LevelEditor.cpp:3683 ../src/LevelEditor.cpp:3722
#, c-format
msgid "The layer '%s' already exists."
msgstr "Слой '%s' уже существует."
#: ../src/LevelEditor.cpp:3743
msgid "Source and destination layers are the same."
msgstr "Исходный и конечный слои совпадают."
#: ../src/LevelEditor.cpp:3760
msgid "Scenery"
msgstr "Фон"
#: ../src/LevelEditor.cpp:4190 ../src/LevelEditor.cpp:4218
#, c-format
msgid "Speed: %d = %0.2f block/s"
msgstr "Скорость: %d = %0.2f блоков/с"
#: ../src/LevelEditor.cpp:4203
msgid "Stop at this point"
msgstr "Остановиться в этой точке"
#: ../src/LevelEditor.cpp:4208
#, c-format
msgid "Pause: %d = %0.3fs"
msgstr "Пауза: %d = %0.3fс"
#: ../src/LevelEditSelect.cpp:41 ../src/TitleMenu.cpp:45
msgid "Map Editor"
msgstr "Редактор карт"
#: ../src/LevelEditSelect.cpp:66
msgid "New Levelpack"
msgstr "Новый сборник уровней"
#: ../src/LevelEditSelect.cpp:71
msgid "Pack Properties"
msgstr "Свойства сборника"
#: ../src/LevelEditSelect.cpp:76
msgid "Remove Pack"
msgstr "Удалить сборник"
#: ../src/LevelEditSelect.cpp:81
msgid "Move Map"
msgstr "Переместить карту"
#: ../src/LevelEditSelect.cpp:89
msgid "Remove Map"
msgstr "Удалить карту"
#: ../src/LevelEditSelect.cpp:94
msgid "Edit Map"
msgstr "Редактировать карту"
#: ../src/LevelEditSelect.cpp:205
msgid "Properties"
msgstr "Свойства"
#: ../src/LevelEditSelect.cpp:217
msgid "Description:"
msgstr "Описание:"
#: ../src/LevelEditSelect.cpp:226
msgid "Congratulation text:"
msgstr "Текст поздравления:"
#: ../src/LevelEditSelect.cpp:235
msgid "Music list:"
msgstr "Список музыки:"
#: ../src/LevelEditSelect.cpp:265 ../src/LevelEditSelect.cpp:485
msgid "Add level"
msgstr "Добавить уровень"
#: ../src/LevelEditSelect.cpp:268
msgid "File name:"
msgstr "Название файла:"
#: ../src/LevelEditSelect.cpp:293
msgid "Move level"
msgstr "Переместить уровень"
#: ../src/LevelEditSelect.cpp:296
msgid "Level: "
msgstr "Уровень: "
#: ../src/LevelEditSelect.cpp:310
msgid "Before"
msgstr "До"
#: ../src/LevelEditSelect.cpp:311
msgid "After"
msgstr "После"
#: ../src/LevelEditSelect.cpp:368 ../src/LevelPlaySelect.cpp:124
msgid "Individual levels which are not contained in any level packs"
msgstr "Отдельные уровни, которые не содержатся ни в одном сборнике уровней"
#: ../src/LevelEditSelect.cpp:577
#, c-format
msgid "Are you sure remove the level pack '%s'?"
msgstr "Вы уверены, что хотите удалить сборник уровней '%s'?"
#: ../src/LevelEditSelect.cpp:577 ../src/LevelEditSelect.cpp:607
msgid "Remove prompt"
msgstr "Подтвердить удаление"
#: ../src/LevelEditSelect.cpp:607
#, c-format
msgid "Are you sure remove the map '%s'?"
msgstr "Вы уверены, что хотите удалить карту '%s'?"
#: ../src/LevelEditSelect.cpp:644
msgid "Levelpack name cannot be empty."
msgstr "Название сборника уровней не может быть пустым."
#: ../src/LevelEditSelect.cpp:683
#, c-format
msgid "The levelpack directory '%s' already exists!"
msgstr "Папка сборника уровней '%s' уже существует!"
#: ../src/LevelEditSelect.cpp:688
#, c-format
msgid "Unable to create levelpack directory '%s'!"
-msgstr "Невозможно создать папку сборника уровней '%s'!"
+msgstr "Не удалось создать папку сборника уровней '%s'!"
#: ../src/LevelEditSelect.cpp:693
#, c-format
msgid "The levelpack file '%s' already exists!"
msgstr "Файл сборника уровней '%s' уже существует!"
#: ../src/LevelEditSelect.cpp:698
#, c-format
msgid "Unable to create levelpack file '%s'!"
-msgstr "Невозможно создать файл сборника уровней '%s'!"
+msgstr "Не удалось создать файл сборника уровней '%s'!"
#: ../src/LevelEditSelect.cpp:758
msgid "No file name given for the new level."
msgstr "Новому уровню не было присвоено имя файла."
#: ../src/LevelEditSelect.cpp:758
msgid "Missing file name"
msgstr "Отсутствует имя файла"
#: ../src/LevelEditSelect.cpp:796
#, c-format
msgid "The file %s already exists."
msgstr "Файл %s уже существует."
#: ../src/LevelEditSelect.cpp:849
msgid "The entered level number isn't valid!"
msgstr "Введённый номер уровня некорректен!"
#: ../src/LevelEditSelect.cpp:849
msgid "Illegal number"
msgstr "Некорректное число"
#: ../src/LevelInfoRender.cpp:19
msgid "Choose a level"
msgstr "Выберите уровень"
#: ../src/LevelInfoRender.cpp:20
msgid "Time:"
msgstr "Время:"
#: ../src/LevelInfoRender.cpp:21 ../src/StatisticsScreen.cpp:259
msgid "Recordings:"
msgstr "Записей:"
#: ../src/LevelPackManager.cpp:124
msgid "Custom Levels"
msgstr "Другие уровни"
#: ../src/LevelPlaySelect.cpp:41
msgid "Select Level"
msgstr "Выберите уровень"
-# TRANSLATORS: Used for button which clear any level progress like unlocked
-# levels and highscores.
#: ../src/OptionsMenu.cpp:66
msgid "Clear Progress"
msgstr "Очистить прогресс"
#: ../src/OptionsMenu.cpp:109
msgid "General"
msgstr "Общие"
#: ../src/OptionsMenu.cpp:110
msgid "Controls"
msgstr "Управление"
#: ../src/OptionsMenu.cpp:121
msgid "Music"
msgstr "Музыка"
#: ../src/OptionsMenu.cpp:129
msgid "Sound"
msgstr "Звуки"
#: ../src/OptionsMenu.cpp:137
msgid "Resolution"
msgstr "Разрешение"
#: ../src/OptionsMenu.cpp:177
msgid "Language"
msgstr "Язык"
-# TRANSLATORS: as detect user's language automatically
#: ../src/OptionsMenu.cpp:185
msgid "Auto-Detect"
msgstr "Автоопределение"
#: ../src/OptionsMenu.cpp:209
msgid "Theme"
msgstr "Тема"
#: ../src/OptionsMenu.cpp:247
msgid "Internet proxy"
msgstr "Интернет-прокси"
#: ../src/OptionsMenu.cpp:256
msgid "Fullscreen"
msgstr "На весь экран"
#: ../src/OptionsMenu.cpp:261
msgid "Quick record"
msgstr "Быстрая запись"
#: ../src/OptionsMenu.cpp:266
msgid "Internet"
msgstr "Интернет"
#: ../src/OptionsMenu.cpp:271
msgid "Fade transition"
msgstr "Плавный переход"
#: ../src/OptionsMenu.cpp:294
msgid "Save Changes"
msgstr "Сохранить изменения"
#: ../src/OptionsMenu.cpp:513
msgid "Do you really want to reset level progress?"
msgstr "Вы действительно хотите очистить прогресс уровня?"
#: ../src/OptionsMenu.cpp:513
msgid "Warning"
-msgstr "Предупреждение"
+msgstr "Внимание"
#: ../src/StatisticsManager.cpp:386
msgid "New achievement:"
msgstr "Новое достижение:"
#: ../src/StatisticsManager.cpp:394
#, c-format
msgid "Achieved on %s"
msgstr "Достижение %s"
#: ../src/StatisticsManager.cpp:400
msgid "Unknown achievement"
msgstr "Неизвестное достижение"
#: ../src/StatisticsManager.cpp:406
#, c-format
msgid "Achieved %1.0f%%"
msgstr "Достижение %1.0f%%"
#: ../src/StatisticsManager.cpp:410
msgid "Not achieved"
msgstr "Не достигнуто"
#: ../src/StatisticsScreen.cpp:57 ../src/TitleMenu.cpp:55
msgid "Achievements and Statistics"
msgstr "Достижения и статистика"
#: ../src/StatisticsScreen.cpp:166
msgid "Achievements"
msgstr "Достижения"
#: ../src/StatisticsScreen.cpp:167
msgid "Statistics"
msgstr "Статистика"
#: ../src/StatisticsScreen.cpp:234
msgid "Total"
msgstr "Всего"
#: ../src/StatisticsScreen.cpp:246
msgid "Traveling distance (m)"
msgstr "Пройденное расстояние (м)"
#: ../src/StatisticsScreen.cpp:247
msgid "Jump times"
msgstr "Прыжков"
#: ../src/StatisticsScreen.cpp:248
msgid "Die times"
msgstr "Смертей"
#: ../src/StatisticsScreen.cpp:249
msgid "Squashed times"
msgstr "Раздавлен"
#: ../src/StatisticsScreen.cpp:260
msgid "Switch pulled times:"
msgstr "Включено выключателей:"
#: ../src/StatisticsScreen.cpp:261
msgid "Swap times:"
msgstr "Поменялся:"
#: ../src/StatisticsScreen.cpp:262
msgid "Save times:"
msgstr "Сохранений:"
#: ../src/StatisticsScreen.cpp:263
msgid "Load times:"
msgstr "Загрузок:"
#: ../src/StatisticsScreen.cpp:268
msgid "Completed levels:"
msgstr "Завершённых уровней:"
#: ../src/StatisticsScreen.cpp:306
msgid "In-game time:"
msgstr "Время в игре:"
#: ../src/StatisticsScreen.cpp:308
msgid "Level editing time:"
msgstr "Время в редакторе:"
#: ../src/StatisticsScreen.cpp:310
msgid "Created levels:"
msgstr "Создано уровней:"
#: ../src/TitleMenu.cpp:44
msgid "Options"
msgstr "Настройки"
#: ../src/TitleMenu.cpp:47
msgid "Quit"
msgstr "Выход"
#: ../src/TitleMenu.cpp:131
msgid "Enable internet in order to install addons."
msgstr "Чтобы устанавливать дополнения, разрешите подключение к Интернету."
#: ../src/TitleMenu.cpp:131
msgid "Internet disabled"
msgstr "Подключение к Интернету запрещено"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Return"
msgstr "Return"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Escape"
msgstr "Escape"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Backspace"
msgstr "Backspace"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Tab"
msgstr "Tab"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Space"
msgstr "Пробел"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "CapsLock"
msgstr "CapsLock"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PrintScreen"
msgstr "PrintScreen"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "ScrollLock"
msgstr "ScrollLock"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Pause"
msgstr "Pause"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Insert"
msgstr "Insert"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Home"
msgstr "Home"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageUp"
msgstr "PageUp"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Delete"
msgstr "Delete"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "End"
msgstr "End"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageDown"
msgstr "PageDown"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right"
msgstr "Вправо"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left"
msgstr "Влево"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Down"
msgstr "Вниз"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Up"
msgstr "Вверх"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Numlock"
msgstr "Numlock"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "SysReq"
msgstr "SysReq"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Ctrl"
msgstr "Левый Ctrl"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Shift"
msgstr "Левый Shift"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Alt"
msgstr "Левый Alt"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left GUI"
msgstr "Левый GUI"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Ctrl"
msgstr "Правый Ctrl"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Shift"
msgstr "Правый Shift"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Alt"
msgstr "Правый Alt"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right GUI"
msgstr "Правый GUI"
diff --git a/data/themes/Cloudscape/theme.mnmstheme b/data/themes/Cloudscape/theme.mnmstheme
index a889325..b570bf7 100644
--- a/data/themes/Cloudscape/theme.mnmstheme
+++ b/data/themes/Cloudscape/theme.mnmstheme
@@ -1,890 +1,934 @@
name="Cloudscape"
background(background.png){
repeat=0,0
}
block(Block){
editorPicture(tiles/tiles.png,0,0,50,50)
state(default){
object{
picture(tiles/tiles.png,0,0,50,50)
optionalPicture(tiles/tiles.png,50,0,50,50,0.15)
optionalPicture(tiles/tiles.png,100,0,50,50,0.15)
}
}
}
block(ShadowBlock){
editorPicture(tiles/tiles.png,0,50,50,50)
state(default){
object{
picture(tiles/tiles.png,0,50,50,50)
}
}
}
character(Player){
state(standleft){
object{
picture(characters/player.png,115,0,23,40)
}
}
state(standright){
object{
picture(characters/player.png,0,0,23,40)
}
}
state(walkleft){
object{
animation=20,0
pictureAnimation(characters/player.png){
point(138,0,23,40,1,5)
point(230,0,23,40,4,5)
}
}
}
state(walkright){
object{
animation=20,0
pictureAnimation(characters/player.png){
point(23,0,23,40,1,5)
point(115,0,23,40,4,5)
}
}
}
state(jumpleft){
object{
picture(characters/player.png,276,0,23,40)
}
}
state(fallleft){
object{
picture(characters/player.png,299,0,23,40)
}
}
state(jumpright){
object{
picture(characters/player.png,230,0,23,40)
}
}
state(fallright){
object{
picture(characters/player.png,253,0,23,40)
}
}
state(holding){
object{
picture(characters/player.png,322,0,23,40)
}
}
state(line){
object{
picture(characters/line.png,0,0,5,5)
}
}
state(dieright){
oneTimeAnimation=8,dead
object{
offset(0,-14)
animation=8,0
pictureAnimation(characters/deathright.png){
point(0,0,23,54,1,2)
point(92,0,23,54,4,2)
}
}
}
state(dieleft){
oneTimeAnimation=8,dead
object{
offset(0,-14)
animation=8,0
pictureAnimation(characters/deathleft.png){
point(0,0,23,54,1,2)
point(92,0,23,54,4,2)
}
}
}
state(dead){
object{
offset(0,-14)
picture(characters/deathright.png,69,0,23,54)
invisibleAtRunTime=1
}
}
}
character(Shadow){
state(standleft){
object{
picture(characters/shadow.png,115,0,23,40)
}
}
state(standright){
object{
picture(characters/shadow.png,0,0,23,40)
}
}
state(walkleft){
object{
animation=20,0
pictureAnimation(characters/shadow.png){
point(138,0,23,40,1,5)
point(230,0,23,40,4,5)
}
}
}
state(walkright){
object{
animation=20,0
pictureAnimation(characters/shadow.png){
point(23,0,23,40,1,5)
point(115,0,23,40,4,5)
}
}
}
state(jumpleft){
object{
picture(characters/shadow.png,276,0,23,40)
}
}
state(fallleft){
object{
picture(characters/shadow.png,299,0,23,40)
}
}
state(jumpright){
object{
picture(characters/shadow.png,230,0,23,40)
}
}
state(fallright){
object{
picture(characters/shadow.png,253,0,23,40)
}
}
state(holding){
object{
picture(characters/shadow.png,322,0,23,40)
}
}
state(line){
object{
picture(characters/line.png,0,0,5,5)
}
}
state(dieright){
oneTimeAnimation=8,dead
object{
offset(0,-14)
animation=8,0
pictureAnimation(characters/shadowdeathright.png){
point(0,0,23,54,1,2)
point(92,0,23,54,4,2)
}
}
}
state(dieleft){
oneTimeAnimation=8,dead
object{
offset(0,-14)
animation=8,0
pictureAnimation(characters/shadowdeathleft.png){
point(0,0,23,54,1,2)
point(92,0,23,54,4,2)
}
}
}
state(dead){
object{
offset(0,-14)
picture(characters/shadowdeathright.png,69,0,23,54)
invisibleAtRunTime=1
}
}
}
block(Fragile){
editorPicture(tiles/tiles.png,150,0,50,50)
state(default){
object{
+ positioning(stretch,stretch)
picture(tiles/tiles.png,150,0,50,50)
}
}
transitionState(default,fragile1){
oneTimeAnimation=20,fragile1
object{
+ positioning(stretch,stretch)
oneTimeAnimation=20,6
pictureAnimation(tiles/tiles.png){
point(200,0,50,50,1,5)
point(250,0,50,50,1,5)
}
}
}
state(fragile1){
object{
+ positioning(stretch,stretch)
picture(tiles/tiles.png,250,0,50,50)
}
}
transitionState(fragile1,fragile2){
oneTimeAnimation=20,fragile2
object{
+ positioning(stretch,stretch)
oneTimeAnimation=20,6
pictureAnimation(tiles/tiles.png){
point(300,0,50,50,1,5)
point(350,0,50,50,1,5)
}
}
}
state(fragile2){
object{
+ positioning(stretch,stretch)
picture(tiles/tiles.png,350,0,50,50)
}
}
state(fragile3){
oneTimeAnimation=6,fragile3_1
object{
+ offset(0,0,0,-50)
+ positioning(stretch,stretch)
animation=20,0
pictureAnimation(tiles/tiles.png){
point(150,50,50,100,1,2)
point(250,50,50,100,2,2)
}
}
}
state(fragile3_1){
object{
+ offset(0,0,0,-50)
+ positioning(stretch,stretch)
picture(tiles/tiles.png,250,50,50,100)
invisibleAtRunTime=1
}
}
}
block(MovingBlock){
editorPicture(tiles/tiles.png,350,200,50,50)
state(default){
object{
picture(tiles/tiles.png,0,0,50,50)
optionalPicture(tiles/tiles.png,50,0,50,50,0.15)
optionalPicture(tiles/tiles.png,100,0,50,50,0.15)
editorPicture(tiles/tiles.png,350,200,50,50)
}
}
}
block(MovingShadowBlock){
editorPicture(tiles/tiles.png,300,200,50,50)
state(default){
object{
picture(tiles/tiles.png,0,50,50,50)
editorPicture(tiles/tiles.png,300,200,50,50)
}
}
}
block(Exit){
editorPicture(tiles/tiles.png,150,200,50,50)
state(default){
object{
picture(tiles/tiles.png,150,200,50,50)
}
}
state(closed){
object{
picture(tiles/tiles.png,0,200,50,50)
}
}
transitionState(closed,default){
oneTimeAnimation=6,default
object{
animation=80,0
pictureAnimation(tiles/tiles.png){
point(50,200,50,50,1,2)
point(150,200,50,50,2,2)
}
}
}
}
block(Spikes){
editorPicture(tiles/tiles.png,450,0,50,50)
state(default){
object{
picture(tiles/tiles.png,450,0,50,50)
optionalPicture(tiles/tiles.png,450,50,50,50,0.5)
}
}
}
block(MovingSpikes){
editorPicture(tiles/tiles.png,400,200,50,50)
state(default){
object{
editorPicture(tiles/tiles.png,400,200,50,50)
picture(tiles/tiles.png,450,0,50,50)
optionalPicture(tiles/tiles.png,450,50,50,50,0.5)
}
}
}
block(Checkpoint){
editorPicture(tiles/tiles.png,50,50,50,50)
state(default){
object{
+ positioning(stretch,stretch)
picture(tiles/tiles.png,50,50,50,50)
}
}
state(activated){
object{
+ positioning(stretch,stretch)
animation=16,0
picture(tiles/tiles.png,100,50,50,50)
offsetAnimation{
point(0,0)
- point(0,-4,4,1)
- point(0,4,8,1)
+ point(0,-4,4,1,0,4)
+ point(0,4,8,1,0,-4)
point(0,0,4,1)
}
}
}
}
block(Swap){
editorPicture(tiles/swap.png,0,0,50,50)
state(default){
object{
+ positioning(stretch,stretch)
picture(tiles/swap.png,0,0,50,50)
}
}
state(activated){
oneTimeAnimation=24,default
object{
+ positioning(stretch,stretch)
animation=12,0
pictureAnimation(tiles/swap.png){
point(0,0,50,50)
point(600,0,50,50,12,1)
}
}
}
}
block(Teleporter){
editorPicture(tiles/tiles.png,300,50,50,50)
state(default){
object{
- picture(tiles/tiles.png,300,50,50,50)
+ positioning(left,stretch)
+ picture(tiles/tiles.png,300,50,16,50)
+ }
+ object{
+ positioning(stretch,stretch)
+ offset(16,0,16,0)
+ picture(tiles/tiles.png,316,50,18,50)
+ }
+ object{
+ positioning(right,stretch)
+ picture(tiles/tiles.png,334,50,16,50)
}
}
state(activated){
oneTimeAnimation=9,default
object{
+ positioning(left,stretch)
+ animation=3,0
+ pictureAnimation(tiles/tiles.png){
+ point(300,50,16,50)
+ point(450,50,16,50,3,1)
+ }
+ }
+ object{
+ positioning(stretch,stretch)
+ offset(16,0,16,0)
+ animation=3,0
+ pictureAnimation(tiles/tiles.png){
+ point(316,50,18,50)
+ point(466,50,18,50,3,1)
+ }
+ }
+ object{
+ positioning(right,stretch)
animation=3,0
pictureAnimation(tiles/tiles.png){
- point(300,50,50,50)
- point(450,50,50,50,3,1)
+ point(334,50,16,50)
+ point(484,50,16,50,3,1)
}
}
}
}
block(Switch){
editorPicture(tiles/tiles.png,0,100,50,50)
state(default){
object{
picture(tiles/tiles.png,0,100,50,50)
}
}
transitionState(default,activated){
oneTimeAnimation=3,activated
object{
animation=3,0
pictureAnimation(tiles/tiles.png){
point(0,100,50,50)
point(150,100,50,50,3,1)
}
}
}
state(activated){
object{
picture(tiles/tiles.png,100,100,50,50)
}
}
transitionState(activated,default){
oneTimeAnimation=3,default
object{
animation=3,0
pictureAnimation(tiles/tiles.png){
point(100,100,50,50)
point(-50,100,50,50,3,1)
}
}
}
}
block(Button){
editorPicture(tiles/tiles.png,450,200,50,50)
state(default){
object{
picture(tiles/tiles.png,0,0,50,50)
optionalPicture(tiles/tiles.png,50,0,50,50,0.15)
optionalPicture(tiles/tiles.png,100,0,50,50,0.15)
}
}
state(button){
object{
# TODO:
positioning(left,top)
picture(tiles/tiles.png,300,100,12,10)
}
object{
positioning(repeat,top)
offset(12,0,17,0)
picture(tiles/tiles.png,312,100,21,10)
}
object{
positioning(right,top)
picture(tiles/tiles.png,333,100,17,10)
}
}
}
block(NotificationBlock){
editorPicture(tiles/tiles.png,350,100,50,50)
state(default){
object{
picture(tiles/tiles.png,350,100,50,50)
}
}
}
block(ConveyorBelt){
editorPicture(tiles/tiles.png,450,100,50,50)
state(default){
object{
picture(tiles/tiles.png,450,100,50,50)
}
}
}
block(ShadowConveyorBelt){
editorPicture(tiles/tiles.png,400,100,50,50)
state(default){
object{
picture(tiles/tiles.png,400,100,50,50)
}
}
}
block(PlayerStart){
editorPicture(tiles/tiles.png,250,200,50,50)
state(default){
object{
+ positioning(centre,top)
picture(tiles/tiles.png,250,200,50,50)
invisibleAtRunTime=1
}
}
}
block(ShadowStart){
editorPicture(tiles/tiles.png,200,200,50,50)
state(default){
object{
+ positioning(centre,top)
picture(tiles/tiles.png,200,200,50,50)
invisibleAtRunTime=1
}
}
}
block(Collectable){
editorPicture(tiles/tiles.png,50,150,50,50)
state(inactive){
}
state(default){
object{
+ positioning(stretch,stretch)
picture(tiles/tiles.png,50,150,50,50)
}
}
}
block(Pushable){
editorPicture(tiles/tiles.png,0,150,50,50)
state(default){
object{
positioning(left,top)
picture(tiles/tiles.png,0,150,8,11)
}
object{
positioning(left,bottom)
picture(tiles/tiles.png,0,175,8,25)
}
object{
positioning(left,bottom)
picture(tiles/tiles.png,0,191,9,9)
}
object{
positioning(right,top)
picture(tiles/tiles.png,41,150,9,9)
}
object{
positioning(right,bottom)
picture(tiles/tiles.png,42,189,8,11)
}
object{
positioning(right,bottom)
picture(tiles/tiles.png,35,191,15,9)
}
object{
positioning(left,repeat)
offset(0,11,0,25)
picture(tiles/tiles.png,0,161,8,14)
}
object{
positioning(repeat,top)
offset(8,0,9,0)
picture(tiles/tiles.png,8,150,33,8)
}
object{
positioning(right,repeat)
offset(0,9,0,11)
picture(tiles/tiles.png,42,159,8,30)
}
object{
positioning(repeat,bottom)
offset(9,0,15,0)
picture(tiles/tiles.png,9,191,26,9)
}
object{
positioning(left,repeat)
offset(8,8,0,9)
picture(tiles/tiles.png,8,158,5,33)
}
object{
positioning(repeat,repeat)
offset(13,8,14,9)
picture(tiles/tiles.png,13,158,23,33)
}
object{
positioning(right,repeat)
offset(-8,8,0,9)
picture(tiles/tiles.png,36,158,6,33)
}
}
}
# Scenery - brick wall
scenery(brick_wall_top_left){
editorPicture(tiles/scenery.png,0,0,50,50)
state(default){
object{
picture(tiles/scenery.png,0,0,50,50)
}
}
}
scenery(brick_wall_top){
editorPicture(tiles/scenery.png,50,0,50,50)
state(default){
object{
picture(tiles/scenery.png,50,0,50,50)
}
}
}
scenery(brick_wall_top_right){
editorPicture(tiles/scenery.png,100,0,50,50)
state(default){
object{
picture(tiles/scenery.png,100,0,50,50)
}
}
}
scenery(brick_wall_left){
editorPicture(tiles/scenery.png,0,50,50,50)
state(default){
object{
picture(tiles/scenery.png,0,50,50,50)
}
}
}
scenery(brick_wall){
editorPicture(tiles/scenery.png,50,50,50,50)
state(default){
object{
picture(tiles/scenery.png,50,50,50,50)
}
}
}
scenery(brick_wall_right){
editorPicture(tiles/scenery.png,100,50,50,50)
state(default){
object{
picture(tiles/scenery.png,100,50,50,50)
}
}
}
scenery(brick_wall_left_2){
editorPicture(tiles/scenery.png,0,100,50,50)
state(default){
object{
picture(tiles/scenery.png,0,100,50,50)
}
}
}
scenery(brick_wall_2){
editorPicture(tiles/scenery.png,50,100,50,50)
state(default){
object{
picture(tiles/scenery.png,50,100,50,50)
}
}
}
scenery(brick_wall_right_2){
editorPicture(tiles/scenery.png,100,100,50,50)
state(default){
object{
picture(tiles/scenery.png,100,100,50,50)
}
}
}
scenery(brick_wall_bottom_left){
editorPicture(tiles/scenery.png,0,150,50,50)
state(default){
object{
picture(tiles/scenery.png,0,150,50,50)
}
}
}
scenery(brick_wall_bottom){
editorPicture(tiles/scenery.png,50,150,50,50)
state(default){
object{
picture(tiles/scenery.png,50,150,50,50)
}
}
}
scenery(brick_wall_bottom_right){
editorPicture(tiles/scenery.png,100,150,50,50)
state(default){
object{
picture(tiles/scenery.png,100,150,50,50)
}
}
}
scenery(brick_wall_bottom_left_2){
editorPicture(tiles/scenery.png,150,0,50,50)
state(default){
object{
picture(tiles/scenery.png,150,0,50,50)
}
}
}
scenery(brick_wall_top_left_2){
editorPicture(tiles/scenery.png,150,50,50,50)
state(default){
object{
picture(tiles/scenery.png,150,50,50,50)
}
}
}
scenery(brick_wall_bottom_right_2){
editorPicture(tiles/scenery.png,150,100,50,50)
state(default){
object{
picture(tiles/scenery.png,150,100,50,50)
}
}
}
scenery(brick_wall_top_right_2){
editorPicture(tiles/scenery.png,150,150,50,50)
state(default){
object{
picture(tiles/scenery.png,150,150,50,50)
}
}
}
# Scenery - stone pillar
scenery(stone_pillar_top){
editorPicture(tiles/scenery.png,200,0,50,50)
state(default){
object{
positioning(repeat,top)
picture(tiles/scenery.png,200,0,50,50)
}
}
}
scenery(stone_pillar){
editorPicture(tiles/scenery.png,200,50,50,50)
state(default){
object{
picture(tiles/scenery.png,200,50,50,50)
}
}
}
scenery(stone_pillar_bottom){
editorPicture(tiles/scenery.png,200,100,50,50)
state(default){
object{
positioning(repeat,bottom)
picture(tiles/scenery.png,200,100,50,50)
}
}
}
scenery(stone_pillar_top_broken_short_L){
editorPicture(tiles/scenery.png,250,0,50,25)
state(default){
object{
positioning(repeat,top)
picture(tiles/scenery.png,250,0,50,25)
}
}
}
scenery(stone_pillar_top_broken_short_L_2){
editorPicture(tiles/scenery.png,250,25,50,25)
state(default){
object{
positioning(repeat,top)
picture(tiles/scenery.png,250,25,50,25)
}
}
}
scenery(stone_pillar_top_broken_short_R){
editorPicture(tiles/scenery.png,250,100,50,25)
state(default){
object{
positioning(repeat,top)
picture(tiles/scenery.png,250,100,50,25)
}
}
}
scenery(stone_pillar_top_broken_short_R_2){
editorPicture(tiles/scenery.png,250,125,50,25)
state(default){
object{
positioning(repeat,top)
picture(tiles/scenery.png,250,125,50,25)
}
}
}
scenery(stone_pillar_bottom_broken_short_L){
editorPicture(tiles/scenery.png,250,50,50,25)
state(default){
object{
positioning(repeat,bottom)
picture(tiles/scenery.png,250,50,50,25)
}
}
}
scenery(stone_pillar_bottom_broken_short_L_2){
editorPicture(tiles/scenery.png,250,75,50,25)
state(default){
object{
positioning(repeat,bottom)
picture(tiles/scenery.png,250,75,50,25)
}
}
}
scenery(stone_pillar_bottom_broken_short_R){
editorPicture(tiles/scenery.png,250,150,50,25)
state(default){
object{
positioning(repeat,bottom)
picture(tiles/scenery.png,250,150,50,25)
}
}
}
scenery(stone_pillar_bottom_broken_short_R_2){
editorPicture(tiles/scenery.png,250,175,50,25)
state(default){
object{
positioning(repeat,bottom)
picture(tiles/scenery.png,250,175,50,25)
}
}
}
scenery(stone_pillar_top_broken_long_L){
editorPicture(tiles/scenery.png,300,0,50,30)
state(default){
object{
positioning(repeat,top)
picture(tiles/scenery.png,300,0,50,30)
}
}
}
scenery(stone_pillar_top_broken_long_L_2){
editorPicture(tiles/scenery.png,300,30,50,30)
state(default){
object{
positioning(repeat,top)
picture(tiles/scenery.png,300,30,50,30)
}
}
}
scenery(stone_pillar_top_broken_long_R){
editorPicture(tiles/scenery.png,350,0,50,30)
state(default){
object{
positioning(repeat,top)
picture(tiles/scenery.png,350,0,50,30)
}
}
}
scenery(stone_pillar_top_broken_long_R_2){
editorPicture(tiles/scenery.png,350,30,50,30)
state(default){
object{
positioning(repeat,top)
picture(tiles/scenery.png,350,30,50,30)
}
}
}
scenery(stone_pillar_bottom_broken_long_L){
editorPicture(tiles/scenery.png,300,60,50,30)
state(default){
object{
positioning(repeat,bottom)
picture(tiles/scenery.png,300,60,50,30)
}
}
}
scenery(stone_pillar_bottom_broken_long_L_2){
editorPicture(tiles/scenery.png,300,90,50,30)
state(default){
object{
positioning(repeat,bottom)
picture(tiles/scenery.png,300,90,50,30)
}
}
}
scenery(stone_pillar_bottom_broken_long_R){
editorPicture(tiles/scenery.png,350,60,50,30)
state(default){
object{
positioning(repeat,bottom)
picture(tiles/scenery.png,350,60,50,30)
}
}
}
scenery(stone_pillar_bottom_broken_long_R_2){
editorPicture(tiles/scenery.png,350,90,50,30)
state(default){
object{
positioning(repeat,bottom)
picture(tiles/scenery.png,350,90,50,30)
}
}
}
# Scenery - snow - from Snowscape
scenery(snow_left){
editorPicture(tiles/snow.png,0,0,50,25)
state(default){
object{
positioning(left,top)
picture(tiles/snow.png,0,0,50,25)
}
}
}
scenery(snow_middle){
editorPicture(tiles/snow.png,50,0,50,25)
state(default){
object{
positioning(repeat,top)
picture(tiles/snow.png,50,0,50,25)
}
}
}
scenery(snow_right){
editorPicture(tiles/snow.png,100,0,50,25)
state(default){
object{
positioning(right,top)
picture(tiles/snow.png,100,0,50,25)
}
}
}
scenery(snow_single){
editorPicture(tiles/snow.png,150,0,50,25)
state(default){
object{
positioning(repeat,top)
picture(tiles/snow.png,150,0,50,25)
}
}
}
diff --git a/data/themes/Cloudscape/tiles/tiles.png b/data/themes/Cloudscape/tiles/tiles.png
index 898ae3a..a7b06e2 100644
Binary files a/data/themes/Cloudscape/tiles/tiles.png and b/data/themes/Cloudscape/tiles/tiles.png differ
diff --git a/docs/ScriptAPI.md b/docs/ScriptAPI.md
index c2e1814..ef2e840 100644
--- a/docs/ScriptAPI.md
+++ b/docs/ScriptAPI.md
@@ -1,522 +1,530 @@
Me and My Shadow Script API Reference
=====================================
(draft)
The script language is Lua 5.2 (later we may bump it to 5.3).
Always check `ScriptAPI.cpp` for the newest API changed unmentioned in this document.
How to edit script
==================
To edit the script of a block, right click the block and select "Scripting".
To edit the script of the level, right click the empty space of the level and select "Scripting".
Currently the scenery block doesn't support scripting.
Available event types of block:
Event type | Description
----------------------|--------------
"playerWalkOn" | Fired once when the player walks on. (For example this is used in fragile block.)
"playerIsOn" | Fired every frame when the player is on.
"playerLeave" | Fired once when the player leaves.
"onCreate" | Fired when object creates.
"onEnterFrame" | Fired every frame.
"onPlayerInteraction" | Fired when the player press DOWN key. Currently this event only fires when the block type is TYPE_SWITCH.
"onToggle" | Fired when the block receives "toggle" from a switch/button. NOTE: The switch/button itself will also receive this event. This is used in an old example found on my old computer.
"onSwitchOn" | Fired when the block receives "switch on" from a switch/button. NOTE: The switch/button itself will also receive this event. This is used in an old example found on my old computer.
"onSwitchOff" | Fired when the block receives "switch off" from a switch/button. NOTE: The switch/button itself will also receive this event. This is used in an old example found on my old computer.
NOTE: During the event execution the global variable `this` temporarily points to current block. (Ad-hoc workaround!)
When the event execution ends the global variable `this` is reset to its previous value.
The block event may return an integer value (default is 0) to alter the game logic:
Return value | Description
-------------|--------------
0 | Skip the default game logic for this event.
1 | Do the default game logic for this event.
Available event types of level:
Event type | Description
-----------|--------------
"onCreate" | Fired when the level is created or the game is reset. This happens after all the blocks are created and their `onCreate` is called.
"onSave" | Fired when the game is saved.
"onLoad" | Fired when the game is loaded.
For the newest lists of event types, see `init()` function in `Functions.cpp`.
NOTE: the following methods to specify scripts can be used:
* Specify scripts for each events in the block script editing dialog.
* Only specify `onCreate` script in the block script editing dialog,
and use `setEventHandler()` function in script to specify scripts for other events dynamically.
* Only specify `onCreate` script in the level script editing dialog,
and use `setEventHandler()` function in script to specify scripts for other events for level/blocks dynamically.
Script API reference
====================
The "block" library
-------------------
### Static functions:
* getBlockById(id)
Returns the first block with specified id. If not found, returns `nil`.
Example:
~~~lua
local b=block.getBlockById("1")
local x,y=b:getLocation()
print(x..","..y)
~~~
* getBlocksById(id)
Returns the list of all blocks with specified id.
Example:
~~~lua
local l=block.getBlocksById("1")
for i,b in ipairs(l) do
local x,y=b:getLocation()
print(x..","..y)
end
~~~
### Member functions:
* isValid() -- check the object is valid (i.e. not deleted, etc.)
* moveTo(x,y)
Move the block to the new position, update the velocity of block according to the position changed.
Example:
~~~lua
local b=block.getBlockById("1")
local x,y=b:getLocation()
b:moveTo(x+1,y)
~~~
* getLocation()
Returns the position of the block.
Example: see the example for moveTo().
+* getBaseLocation()
+
+Returns the base position of the block. Mainly used for moving blocks.
+
* setLocation(x,y)
Move the block to the new position without updating the velocity of block.
Example: omitted since it's almost the same as moveTo().
* growTo(w,h)
Resize the block, update the velocity of block according to the size changed.
NOTE: I don't think the velocity need to be updated when resizing block, so don't use this function.
Example: omitted since it's almost the same as setSize().
* getSize()
Returns the size of the block.
Example:
~~~lua
local b=block.getBlockById("1")
local w,h=b:getSize()
print(w..","..h)
~~~
+* getBaseSize()
+
+Returns the base size of the block. Mainly used for moving blocks.
+
* setSize(w,h)
Resize the block without updating the velocity of block.
Example:
~~~lua
local b=block.getBlockById("1")
local w,h=b:getSize()
b:setSize(w+1,h)
~~~
* getType()
Returns the type of the block (which is a string).
Example:
~~~lua
local b=block.getBlockById("1")
local s=b:getType()
print(s)
~~~
* changeThemeState(new_state)
Change the state of the block to new_state (which is a string).
Example:
~~~lua
local b=block.getBlockById("1")
b:changeThemeState("activated")
~~~
* setVisible(b)
Set the visibility the block.
NOTE: The default value is `true`. If set to `false` the block is hidden completely,
the animation is stopped, can't receive any event, can't execute any scripts (except for `onCreate`),
can't be used as a portal destination,
doesn't participate in collision check and game logic, etc...
NOTE: This is a newly added feature.
If you find any bugs (e.g. if an invisible block still affects the game logic)
please report the bugs to GitHub issue tracker.
Example:
~~~lua
local b=block.getBlockById("1")
if b:isVisible() then
b:setVisible(false)
else
b:setVisible(true)
end
~~~
* isVisible()
Returns whether the block is visible.
Example: see the example for setVisible().
* getEventHandler(event_type)
Returns the event handler of event_type (which is a string).
Example:
~~~lua
local b=block.getBlockById("1")
local f=b:getEventHandler("onSwitchOn")
b:setEventHandler("onSwitchOff",f)
~~~
* setEventHandler(event_type,handler)
Set the handler of event_type (which is a string). The handler should be a function or `nil`.
Returns the previous event handler.
Example:
~~~lua
local b=block.getBlockById("1")
b:setEventHandler("onSwitchOff",function()
print("I am switched off.")
end)
~~~
* onEvent(eventType)
Fire an event to specified block.
NOTE: The event will be processed immediately.
Example:
~~~lua
local b=block.getBlockById("1")
b:onEvent("onToggle")
~~~
NOTE: Be careful not to write infinite recursive code! Bad example:
~~~lua
-- onToggle event of a moving block
this:onEvent("onToggle")
~~~
* isActivated() / setActivated(bool) -- get/set a boolean indicates if the block is activated
-- the block should be one of TYPE_MOVING_BLOCK, TYPE_MOVING_SHADOW_BLOCK, TYPE_MOVING_SPIKES,
TYPE_CONVEYOR_BELT, TYPE_SHADOW_CONVEYOR_BELT.
* isAutomatic() / setAutomatic(bool) -- get/set a boolean indicates if the portal is automatic
-- the block should be TYPE_PORTAL
* getBehavior() / setBehavior(str) -- get/set a string (must be "on", "off" or "toggle")
representing the behavior of the block -- the block should be TYPE_BUTTON, TYPE_SWITCH
* getState() / setState(num) -- get/set a number (must be 0,1,2 or 3)
representing the state of a fragile block -- the block should be TYPE_FRAGILE
* isPlayerOn() -- get a boolean indicates if the player is on -- only works for TYPE_BUTTON
* getPathMaxTime() -- get the total time of the path of a moving block
* getPathTime() / setPathTime(num) -- get/set the current time of the path of a moving block
* isLooping() / setLooping(bool) -- get/set the looping property of a moving block
* getSpeed() / setSpeed(num) -- get/set the speed of a conveyor belt. NOTE: 1 Speed = 0.08 block/s = 0.1 pixel/frame
The "playershadow" library
--------------------------
### Global constants:
* player
The player object.
* shadow
The shadow object.
### Member functions:
* getLocation()
Returns the location of player/shadow.
Example:
~~~lua
local x,y=player:getLocation()
print("player: "..x..","..y)
x,y=shadow:getLocation()
print("shadow: "..x..","..y)
~~~
* setLocation(x,y)
Set the location of player/shadow.
Example:
~~~lua
local x,y=player:getLocation()
player:setLocation(x+1,y)
~~~
* jump([strength=13])
Let the player/shadow jump if it's allowed.
strength: Jump strength.
Example:
~~~lua
player:jump(20)
~~~
* isShadow()
Returns whether the current object is shadow.
Example:
~~~lua
print(player:isShadow())
print(shadow:isShadow())
~~~
* getCurrentStand()
Returns the block on which the player/shadow is standing on. Can be `nil`.
Example:
~~~lua
local b=player:getCurrentStand()
if b then
print(b:getType())
else
print("The player is not standing on any blocks")
end
~~~
* isInAir() -- returns a boolean indicating if the player is in air
* canMove() -- returns a boolean indicating if the player can move (i.e. not standing on shadow)
* isDead() -- returns a boolean indicating if the player is dead
* isHoldingOther() -- returns a boolean indicating if the player is holding other
The "level" library
-------------------
### Static functions:
* getSize() -- get the level size
* getWidth() -- get the level width
* getHeight() -- get the level height
* getName() -- get the level name
* getEventHandler(event_type) -- get the event handler
* setEventHandler(event_type,handler) -- set the event handler, return the old handler
* win() -- win the game
* getTime() -- get the game time (in frames)
* getRecordings() -- get the game recordings
* broadcastObjectEvent(eventType,[objectType=nil],[id=nil],[target=nil])
Broadcast the event to blocks satisfying the specified condition.
NOTE: The event will be processed in next frame.
Argument name | Description
--------------|-------------
eventType | string.
objectType | string or nil. If this is set then the event is only received by the block with specified type.
id | string or nil. If this is set then the event is only received by the block with specified id.
target | block or nil. If this is set then the event is only received by the specified block.
Example:
~~~lua
level.broadcastObjectEvent("onToggle",nil,"1")
~~~
The "delayExecution" library
----------------------------
### Static functions:
* schedule(func,time,[repeatCount=1],[repeatInterval],[enabled=true],[arguments...])
Schedule a delay execution of a given function after the given time.
Argument name | Description
---------------|-------------
func | A function to be executed.
time | Time, given in frames (NOTE: 40 frames = 1 second). NOTE: If <=0 it is the same as =1.
repeatCount | The number of times the function will be executed. After such number of times executed, the delay execution will be removed from the list and get deleted. If =0 the delay execution object will be deleted soon. If <0 the function will be executed indefinitely.
repeatInterval | The repeat interval. If it is `nil` then the `time` argument will be used instead. NOTE: If <=0 the repeat execution will be disabled at all and the repeatCount will be set to 1.
enabled | Enabled.
arguments | Optional arguments passed to the function.
Return value: the delayExecution object.
NOTE: If you want to update time/repeatCount during the function execution,
notice that the time/repeatCount is updated BEFORE the function execution.
NOTE: During the execution the global variable `this`
temporarily points to current delay execution object. (Ad-hoc workaround!)
When the execution ends the global variable `this` is reset to its previous value.
Example:
~~~lua
local f=function()
local a
a=0
return(function(b)
shadow:jump()
print('obj1 '..this:getExecutionTime()..' '..a..' '..tostring(b))
a=a+2
end)
end
local obj1=delayExecution.schedule(f(),40*2,5,nil,nil,100)
local obj2=delayExecution.schedule(
function(o)
print('obj2 '..tostring(o:isValid()))
if not o:isValid() then
this:setFunc(f())
end
end,40*1,-1,nil,nil,obj1)
local obj3=delayExecution.schedule(
function(o)
o:cancel()
end,40*30,1,nil,nil,obj2)
~~~
### Member functions:
* isValid() -- Check if it's valid, i.e. not removed from list.
* cancel() -- Cancels a delay execution. The canceled delay execution will be removed from the list and can not be restored.
* isEnabled()/setEnabled(bool) -- get/set enabled of a delay execution. A disabled one will not count down its timer.
* getTime()/setTime(integer) -- get/set the remaining time until the next execution. NOTE: If <=0 it is the same as =1.
* getRepeatCount()/setRepeatCount(integer) -- get/set the remaining repeat count. If =0 the object will get deleted soon. If <0 the function will be executed indefinitely.
* getRepeatInterval()/setRepeatInterval(integer) -- get/set the repeat interval. NOTE: If <=0 then nothing happens.
* getFunc()/setFunc(func) -- get/set the function to be executed. NOTE: The setFunc will return the original function.
* getArguments()/setArguments(args...) -- get/set the arguments
* getExecutionTime()/setExecutionTime(integer) -- get/set the number of times the function being executed. NOTE: this execution time doesn't affect the default logic.
The "camera" library
--------------------
### Static functions:
* setMode(mode) -- set the camera mode, which is "player" or "shadow"
* lookAt(x,y) -- set the camera mode to "custom" and set the new center of camera
The "audio" library
-------------------
NOTE: the following functions are not going to work if the sound/music volume is 0.
### Static functions:
* playSound(name[,concurrent=-1[,force=false[,fade=-1]]])
Play a sound effect.
Argument name | Description
--------------|-------------
name | The name of the sound effect. Currently available: "jump", "hit", "checkpoint", "swap", "toggle", "error", "collect", "achievement".
concurrent | The number of times the same sfx can be played at once, -1 is unlimited. NOTE: there are 64 channels.
force | If the sound must be played even if all channels are used. In this case the sound effect in the first channel will be stopped.
fade | A factor to temporarily turn the music volume down (0-128). -1 means don't use this feature.
Return value: The channel of the sfx. -1 means failed (channel is full, invalid sfx name, sfx volume is 0, etc.)
* playMusic(name[,fade=true])
Play a music.
Argument name | Description
--------------|-------------
name | The name of the song, e.g. "default/neverending" or "menu".
fade | Boolean if it should fade the current one out or not.
* pickMusic() - pick a song from the current music list.
* getMusicList()/setMusicList(name_of_the_music_list) - get/set the music list. Example: "default".
* currentMusic() - get the current music.
diff --git a/docs/ThemeDescription.md b/docs/ThemeDescription.md
index b253ac7..cb7609c 100644
--- a/docs/ThemeDescription.md
+++ b/docs/ThemeDescription.md
@@ -1,283 +1,285 @@
Me and My Shadow Theme File Description
=======================================
(draft)
The theme file contains:
~~~
name=<theme name> //The theme name
textColor=<r>,<g>,<b> //Optional theme text color. Should be dark or light depending on the theme background is light or dark.
textColorDialog=<r>,<g>,<b> //Optional theme text color in dialog. Should be dark since the dialog background is light.
~~~
and the following subnode.
1 block subnode
---------------
~~~
block(<block name>){...} //subnode specifies the block's appearance
~~~
block name:
"Block", "PlayerStart", "ShadowStart",
"Exit", "ShadowBlock", "Spikes",
"Checkpoint", "Swap", "Fragile",
"MovingBlock", "MovingShadowBlock", "MovingSpikes",
"Teleporter", "Button", "Switch",
"ConveyorBelt", "ShadowConveyorBelt", "NotificationBlock", "Collectable", "Pushable".
For the newest version of this list, see `Game::blockName` in `Game.cpp`.
In this subnode:
### 1.1 state/characterState/blockState/transitionState subnode
NOTE: blockState and characterState are for backwards compatibility, use state instead.
~~~
blockState(<state name>){...} //subnode specifies the appearance of each state of the block
state(<state name>){...}
characterState(<state name>){...}
transitionState(<current state>,<new state>){...}
~~~
the state name:
* for all blocks
- "base": Always draw the base before other states (intended to be used in level editor).
- "default": The default state.
* for "Checkpoint","Swap","Switch"
- "activated": The activated state.
* for "Collectable"
- "inactive": The collected state.
* for "Exit"
- "closed": The closed state.
* for "Button"
- "button": The button (which is a separated, movable part of the button).
* for "Fragile"
- "fragile1": The stepped once state.
- "fragile2": The stepped twice state.
- "fragile3": The broken state.
transition state:
If we found a transition state which matches the current state and the next state,
then we switch to this transition state instead of the new state.
NOTE: the transition state should have the oneTimeAnimation attribute and set the correct next state.
optional attributes:
~~~
oneTimeAnimation=<length>,<next state> //if this state is one-time animation only
~~~
#### 1.1.1 object subnode
~~~
object{...} //subnode specifies (multiple) objects to display in each state
~~~
optional attributes:
~~~
animation=<length>,<loop point> //if object has looped animation
oneTimeAnimation=<length>,<end point>
invisibleAtRunTime=1 //if this object is invisible when playing game
invisibleAtDesignTime=1 //if this object is invisible when editing the map
~~~
optional nodes specifies object to display:
##### 1.1.1.1 picture subnode
~~~
picture(<file name>,<x>,<y>,<w>,<h>)
~~~
The (x,y,w,h) defines the source rectangle.
NOTE: picture and pictureAnimation are mutually exclusive. (?)
##### 1.1.1.2 optionalPicture subnode
~~~
optionalPicture(<file name>,<x>,<y>,<w>,<h>,<probability>)
~~~
If this subnode is set, the picture will be randomly used according to the given probability.
##### 1.1.1.3 editorPicture subnode
~~~
editorPicture(<file name>,<x>,<y>,<w>,<h>)
~~~
If this subnode is set, the picture will be used instead in the level editor.
##### 1.1.1.4 positioning subnode
~~~
positioning(<xalign>,<yalign>)
~~~
-* xalign: 'left', 'centre' (NOTE: not 'center'), 'right' or 'repeat' or 'stretch'
+* xalign: 'left', 'centre' (same as 'center'), 'right' or 'repeat' or 'stretch'
* yalign: 'top', 'middle', 'bottom' or 'repeat' or 'stretch'
##### 1.1.1.5 offset subnode
~~~
offset(<x>,<y>[,<w>[,<h>]])
~~~
-Shift the top,left,right,bottom of the destination rectangle by x,y,-w,-h.
+Shift the left,top,right,bottom of the destination rectangle by x,y,-w,-h.
NOTE: w,h are only used when the corresponding positioning modes are 'repeat' or 'stretch'.
##### 1.1.1.6 pictureAnimation subnode
~~~
pictureAnimation(<file name>){
point(<x>,<y>,<w>,<h>[,<frame count>[,<display time of each frame>]])
point(<x>,<y>,<w>,<h>[,<frame count>[,<display time of each frame>]])
...
}
~~~
NOTE: picture and pictureAnimation are mutually exclusive. (?)
NOTE: The default value of frame_count and the display_time_of_each_frame are 1.
The source rectangle (x,y,w,h) is animated by the following way:
- (x,y,w,h)=point[0] is displayed for time display_time[0]
- for i=1,2,3... the following sequences of pictures are displayed:
- for r=1/frame_count[i],2/frame_count[i],...,1
the (x,y,w,h)=point[i-1]*(1-r)+point[i]*r is displayed for time display_time[i]
##### 1.1.1.7 offsetAnimation subnode
~~~
offsetAnimation{
- point(<x>,<y>[,<frame count>[,<display time of each frame>]])
- point(<x>,<y>[,<frame count>[,<display time of each frame>]])
+ point(<x>,<y>[,<frame count>[,<display time of each frame>[,<w>[,<h>]]]])
+ point(<x>,<y>[,<frame count>[,<display time of each frame>[,<w>[,<h>]]]])
...
}
~~~
This is similar to pictureAnimation subnode, but this time it animates the offset.
+Notice that the w,h are the last two arguments.
+
### 1.2 editorPicture subnode
Syntax:
~~~
editorPicture(<file name>,<x>,<y>,<w>,<h>) //specifies the picture shows in editor
~~~
This subnode is required (?) for block and scenery.
2 background subnode
--------------------
Specifies the background of level.
There can be multiple background subnodes.
Each subnode is a layer of background.
Syntax:
~~~
background(<file name>){
srcSize=<x>,<y>,<w>,<h> //Specifies the source size and offset of picture (optional, default value=image size)
destSize=<x>,<y>,<w>,<h> //Specifies the destination size and offset of picture (optional, default value=size of game window WHEN THE THEME FILE IS LOADED (!!!))
scaleToScreen=<0 or 1> //Specifies the destination should be scale to screen. (optional, default is 1)
repeat=<repeat x>,<repeat y> //Repeat in x,y direction? (0 or 1) (optional, default value=1,1)
speed=<speed x>,<speed y> //Specifies the moving speed (pixel/frame, a real number) (optional, default=0,0)
cameraSpeed=<x>,<y> //The speed of following camera (a real number, typically in 0-1) (optional, default=0,0)
}
~~~
3 character subnode
-------------------
Specifies the appearance of player and shadow.
Syntax:
~~~
character(Player){
...
}
~~~
or
~~~
character(Shadow){
...
}
~~~
The other format is the same as the block subnode.
Here are the states for character which should be implemented.
Unless otherwise specified, all of them should be of size 23x40.
* "standleft", "standright", "walkleft", "walkright", "jumpleft", "jumpright",
"fallleft", "fallright" -- various animations
* "dieleft", "dieright" -- death animation, should be `oneTimeAnimation` to "dead"
* "holding" -- used when the player is holding the shadow, vice versa
* "line" -- used when the player is recording moves for the shadow, should be of size 5x5
* "dead" -- mainly used in level editor, should be visible only in editor
4 menuBackground subnode (optional)
----------------
Specifies the background of main menu.
There can be multiple menuBackground subnodes.
The format is the same as the background subnode.
5 menu block (optional)
------------
Specifies the appearance of blocks used in level selection screen.
Syntax:
~~~
menu(Block){
...
}
~~~
or
~~~
menu(ShadowBlock){
...
}
~~~
The other format is the same as the block subnode.
NOTE: if you defined menu(Block) but not defined menu(ShadowBlock),
then all blocks used in level selection screen will be menu(Block)
regardless of locked or not.
6 scenery block
---------------
Defines new scenery block type.
Syntax:
~~~
scenery(<name>){
...
}
~~~
The other format is the same as the block subnode.
diff --git a/src/AchievementList.cpp b/src/AchievementList.cpp
index d05e802..147aa87 100644
--- a/src/AchievementList.cpp
+++ b/src/AchievementList.cpp
@@ -1,115 +1,116 @@
/*
* Copyright (C) 2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AchievementList.h"
// We redefined it here to prevent including more header files.
#ifndef __
#define __(X) (X)
#endif
// Format: {<id>, <name>, <file>, <pos>, <description>, <type>, nullptr}
// id: Identifier of achievement. Should not change once the new achievement is added,
// otherwise the old statistics will be lost.
// name: Name of achievement. Translatable.
// file: The icon file. NULL for no icon.
// pos: Specifies which part of the icon will be displayed (left, top, width, height).
// description: Description of achievement. Translatable. Can be multi-line text.
// type [optional]: Specifies the display type of achievement.
// ACHIEVEMENT_HIDDEN [default]: Show "Unknown achievement" when unfinished.
// ACHIEVEMENT_TITLE: Only show icon and title when unfinished.
// ACHIEVEMENT_ALL: Always show icon, title and description.
// ACHIEVEMENT_PROGRESS: Show icon, title and description and a progress bar.
// StatisticsManager::getAchievementProgress() function should return the progress (between 0 and 1).
// NOTE: WARNING: All arguments should now be specified, as surface being nullptr is being relied upon.
AchievementInfo achievementList[]={
{"newbie",__("Newbie"),"gfx/medals.png",{0,0,30,30},__("Complete a level."),ACHIEVEMENT_ALL,nullptr},
- {"experienced",__("Experienced player"),"gfx/medals.png",{30,0,30,30},__("Complete 50 levels."),ACHIEVEMENT_PROGRESS,nullptr},
- {"goodjob",__("Good job!"),"gfx/medals.png",{60,0,30,30},__("Receive a gold medal."),ACHIEVEMENT_ALL,nullptr},
- {"expert",__("Expert"),"gfx/medals.png",{60,0,30,30},__("Earn 50 gold medal."),ACHIEVEMENT_PROGRESS,nullptr},
+ {"tutorial",__("Graduate"),"gfx/achievements/tutorial.png",{0,0,51,27},__("Complete the tutorial level pack."),ACHIEVEMENT_PROGRESS,nullptr},
+ {"experienced",__("Experienced player"),"gfx/achievements/experienced.png",{0,0,51,51},__("Complete 50 levels."),ACHIEVEMENT_PROGRESS,nullptr},
- {"tutorial",__("Graduate"),"gfx/medals.png",{30,0,30,30},__("Complete the tutorial level pack."),ACHIEVEMENT_PROGRESS,nullptr},
- {"tutorialGold",__("Outstanding graduate"),"gfx/medals.png",{60,0,30,30},__("Complete the tutorial level pack with gold for all levels."),ACHIEVEMENT_PROGRESS,nullptr},
+ {"goodjob",__("Good job!"),"gfx/medals.png",{60,0,30,30},__("Receive a gold medal."),ACHIEVEMENT_ALL,nullptr},
+ {"tutorialGold",__("Outstanding graduate"),"gfx/achievements/tutorialgold.png",{0,0,51,27},__("Complete the tutorial level pack with gold for all levels."),ACHIEVEMENT_PROGRESS,nullptr},
+ // FIXME: Fix typo
+ {"expert",__("Expert"),"gfx/achievements/expert.png",{0,0,51,51},__("Earn 50 gold medal."),ACHIEVEMENT_PROGRESS,nullptr},
- {"addicted",__("Hooked"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Play Me and My Shadow for more than 2 hours."),ACHIEVEMENT_TITLE,nullptr},
- {"loyalFan",__("Loyal fan of Me and My Shadow"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Play Me and My Shadow for more than 24 hours."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"addicted",__("Hooked"),"gfx/achievements/addicted.png",{0,0,50,50},__("Play Me and My Shadow for more than 2 hours."),ACHIEVEMENT_TITLE,nullptr},
+ {"loyalFan",__("Loyal fan of Me and My Shadow"),"gfx/achievements/loyalfan.png",{0,0,50,50},__("Play Me and My Shadow for more than 24 hours."),ACHIEVEMENT_HIDDEN,nullptr},
- {"constructor",__("Constructor"),"gfx/gui.png",{112,16,16,16},__("Use the level editor for more than 2 hours."),ACHIEVEMENT_HIDDEN,nullptr},
- {"constructor2",__("The creator"),"gfx/gui.png",{112,16,16,16},__("Use the level editor for more than 24 hours."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"constructor",__("Constructor"),"gfx/achievements/constructor.png",{0,0,50,50},__("Use the level editor for more than 2 hours."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"constructor2",__("The creator"),"gfx/achievements/constructor2.png",{0,0,50,50},__("Use the level editor for more than 24 hours."),ACHIEVEMENT_HIDDEN,nullptr},
{"create1",__("Look, cute level!"),"gfx/addon1.png",{0,0,64,64},__("Create a level for the first time."),ACHIEVEMENT_ALL,nullptr},
{"create50",__("The level museum"),"gfx/addon2.png",{0,0,64,64},__("Create 50 levels."),ACHIEVEMENT_PROGRESS,nullptr},
- { "helloworld", __("Hello, World!"), "gfx/gui.png", { 112, 16, 16, 16 }, __("Write a script for the first time."), ACHIEVEMENT_TITLE, nullptr },
+ { "helloworld", __("Hello, World!"), "gfx/achievements/helloworld.png", { 0, 0, 50, 50 }, __("Write a script for the first time."), ACHIEVEMENT_TITLE, nullptr },
- {"frog",__("Frog"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Jump 1000 times."),ACHIEVEMENT_PROGRESS,nullptr},
+ {"frog",__("Frog"),"themes/Cloudscape/characters/player.png",{230,0,23,40},__("Jump 1000 times."),ACHIEVEMENT_PROGRESS,nullptr},
- {"travel100",__("Wanderer"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Travel 100 meters."),ACHIEVEMENT_PROGRESS,nullptr},
- {"travel1k",__("Runner"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Travel 1 kilometer."),ACHIEVEMENT_PROGRESS,nullptr},
- {"travel10k",__("Long distance runner"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Travel 10 kilometers."),ACHIEVEMENT_PROGRESS,nullptr},
- {"travel42k",__("Marathon runner"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Travel 42,195 meters."),ACHIEVEMENT_PROGRESS,nullptr},
+ {"travel100",__("Wanderer"),"themes/Cloudscape/characters/player.png",{69,0,23,40},__("Travel 100 meters."),ACHIEVEMENT_PROGRESS,nullptr},
+ {"travel1k",__("Runner"),"themes/Cloudscape/characters/player.png",{23,0,23,40},__("Travel 1 kilometer."),ACHIEVEMENT_PROGRESS,nullptr},
+ {"travel10k",__("Long distance runner"),"themes/Cloudscape/characters/player.png",{46,0,23,40},__("Travel 10 kilometers."),ACHIEVEMENT_PROGRESS,nullptr},
+ {"travel42k",__("Marathon runner"),"themes/Cloudscape/characters/player.png",{92,0,23,40},__("Travel 42,195 meters."),ACHIEVEMENT_PROGRESS,nullptr},
{"die1",__("Be careful!"),"themes/Cloudscape/characters/deathright.png",{0,14,23,40},__("Die for the first time."),ACHIEVEMENT_ALL,nullptr},
- {"die50",__("It doesn't matter..."),"themes/Cloudscape/characters/deathright.png",{0,14,23,40},__("Die 50 times."),ACHIEVEMENT_HIDDEN,nullptr},
- {"die1000",__("Expert of trial and error"),"themes/Cloudscape/characters/deathright.png",{0,14,23,40},__("Die 1000 times."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"die50",__("It doesn't matter..."),"gfx/achievements/die50.png",{0,0,50,50},__("Die 50 times."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"die1000",__("Expert of trial and error"),"gfx/achievements/die1000.png",{0,0,50,50},__("Die 1000 times."),ACHIEVEMENT_HIDDEN,nullptr},
- {"squash1",__("Keep an eye for moving blocks!"),"themes/Cloudscape/characters/deathright.png",{0,14,23,40},__("Get squashed for the first time."),ACHIEVEMENT_HIDDEN,nullptr},
- {"squash50",__("Potato masher"),"themes/Cloudscape/characters/deathright.png",{0,14,23,40},__("Get squashed 50 times."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"squash1",__("Keep an eye for moving blocks!"),"gfx/achievements/squash1.png",{0,0,50,50},__("Get squashed for the first time."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"squash50",__("Potato masher"),"gfx/achievements/squash50.png",{0,0,50,50},__("Get squashed 50 times."),ACHIEVEMENT_HIDDEN,nullptr},
- {"doubleKill",__("Double kill"),"themes/Cloudscape/characters/deathright.png",{0,14,23,40},__("Get both the player and the shadow dead."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"doubleKill",__("Double kill"),"gfx/achievements/doublekill.png",{0,0,50,50},__("Get both the player and the shadow dead."),ACHIEVEMENT_HIDDEN,nullptr},
- {"die5in5",__("Bad luck"),"themes/Cloudscape/characters/deathright.png",{0,14,23,40},__("Die 5 times in under 5 seconds."),ACHIEVEMENT_TITLE,nullptr},
- {"die10in5",__("This level is too dangerous"),"themes/Cloudscape/characters/deathright.png",{0,14,23,40},__("Die 10 times in under 5 seconds."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"die5in5",__("Bad luck"),"gfx/achievements/die5in5.png",{0,0,50,50},__("Die 5 times in under 5 seconds."),ACHIEVEMENT_TITLE,nullptr},
+ {"die10in5",__("This level is too dangerous"),"gfx/achievements/die10in5.png",{0,0,50,50},__("Die 10 times in under 5 seconds."),ACHIEVEMENT_HIDDEN,nullptr},
- {"forget",__("You forgot your friend"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Finish the level with the player or the shadow dead."),ACHIEVEMENT_HIDDEN,nullptr},
- {"jit",__("Just in time"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Reach the exit with the player and the shadow simultaneously."),ACHIEVEMENT_TITLE,nullptr},
+ {"forget",__("You forgot your friend"),"gfx/achievements/forget.png",{0,0,45,53},__("Finish the level with the player or the shadow dead."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"jit",__("Just in time"),"gfx/achievements/jit.png",{0,0,50,50},__("Reach the exit with the player and the shadow simultaneously."),ACHIEVEMENT_TITLE,nullptr},
- {"record100",__("Recorder"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Record 100 times."),ACHIEVEMENT_PROGRESS,nullptr},
- {"record1k",__("Shadowmaster"),"themes/Cloudscape/characters/shadow.png",{0,0,23,40},__("Record 1000 times."),ACHIEVEMENT_PROGRESS,nullptr},
+ {"record100",__("Recorder"),"gfx/achievements/record100.png",{0,0,50,50},__("Record 100 times."),ACHIEVEMENT_PROGRESS,nullptr},
+ {"record1k",__("Shadowmaster"),"themes/Cloudscape/characters/shadow.png",{23,0,23,40},__("Record 1000 times."),ACHIEVEMENT_PROGRESS,nullptr},
- {"switch100",__("Switch puller"),"themes/Cloudscape/tiles/tiles.png",{0,100,50,50},__("Pull the switch 100 times."),ACHIEVEMENT_PROGRESS,nullptr},
- {"switch1k",__("The switch is broken!"),"themes/Cloudscape/tiles/tiles.png",{0,100,50,50},__("Pull the switch 1000 times."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"switch100",__("Switch puller"),"themes/Cloudscape/tiles/tiles.png",{100,100,50,50},__("Pull the switch 100 times."),ACHIEVEMENT_PROGRESS,nullptr},
+ {"switch1k",__("The switch is broken!"),"gfx/achievements/switch1k.png",{0,0,50,50},__("Pull the switch 1000 times."),ACHIEVEMENT_HIDDEN,nullptr},
{"swap100",__("Swapper"),"themes/Cloudscape/tiles/swap.png",{0,0,50,50},__("Swap 100 times."),ACHIEVEMENT_PROGRESS,nullptr},
- {"swap1k",__("Player to shadow to player to shadow..."),"themes/Cloudscape/tiles/swap.png",{0,0,50,50},__("Swap 1000 times."),ACHIEVEMENT_HIDDEN,nullptr},
+ // {"swap1k",__("Player to shadow to player to shadow..."),"themes/Cloudscape/tiles/swap.png",{0,0,50,50},__("Swap 1000 times."),ACHIEVEMENT_HIDDEN,nullptr},
- {"save1k",__("Play it save"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Save 1000 times."),ACHIEVEMENT_HIDDEN,nullptr},
- {"load1k",__("This game is too hard"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Load the game 1000 times."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"save1k",__("Play it save"),"gfx/achievements/save1k.png",{0,0,50,50},__("Save 1000 times."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"load1k",__("This game is too hard"),"gfx/achievements/load1k.png",{0,0,50,50},__("Load the game 1000 times."),ACHIEVEMENT_HIDDEN,nullptr},
- { "withoutsave", __("No, thanks"), "themes/Cloudscape/characters/player.png", { 0, 0, 23, 40 }, __("Complete a level with checkpoint, but without saving."), ACHIEVEMENT_TITLE, nullptr },
+ { "withoutsave", __("No, thanks"), "gfx/achievements/withoutsave.png", { 0, 0, 50, 50 }, __("Complete a level with checkpoint, but without saving."), ACHIEVEMENT_TITLE, nullptr },
- {"panicSave",__("Panic save"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Save twice in 1 second."),ACHIEVEMENT_HIDDEN,nullptr},
- {"panicLoad",__("Panic load"),"themes/Cloudscape/characters/player.png",{0,0,23,40},__("Load twice in 1 second."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"panicSave",__("Panic save"),"gfx/achievements/panicsave.png",{0,0,50,50},__("Save twice in 1 second."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"panicLoad",__("Panic load"),"gfx/achievements/panicload.png",{0,0,50,50},__("Load twice in 1 second."),ACHIEVEMENT_HIDDEN,nullptr},
- {"loadAndDie",__("Bad saving position"),"themes/Cloudscape/characters/deathright.png",{0,14,23,40},__("Load the game and die within 1 second."),ACHIEVEMENT_TITLE,nullptr},
- {"loadAndDie100",__("This level is too hard"),"themes/Cloudscape/characters/deathright.png",{0,14,23,40},__("Load the same save and die 100 times."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"loadAndDie",__("Bad saving position"),"gfx/achievements/loadanddie.png",{0,0,50,50},__("Load the game and die within 1 second."),ACHIEVEMENT_TITLE,nullptr},
+ {"loadAndDie100",__("This level is too hard"),"gfx/achievements/loadanddie100.png",{0,0,50,50},__("Load the same save and die 100 times."),ACHIEVEMENT_HIDDEN,nullptr},
- {"quickswap",__("Quick swap"),"themes/Cloudscape/tiles/swap.png",{0,0,50,50},__("Swap twice in under a second."),ACHIEVEMENT_HIDDEN,nullptr},
+ {"quickswap",__("Quick swap"),"gfx/achievements/quickswap.png",{0,0,50,50},__("Swap twice in under a second."),ACHIEVEMENT_HIDDEN,nullptr},
//ripped from Achievements Unlocked
{"horizontal",__("Horizontal confusion"),"gfx/emotions.png",{0,0,23,40},__("Press left and right simultaneously."),ACHIEVEMENT_HIDDEN,nullptr},
- { "cheat", __("Cheater"), "themes/Cloudscape/characters/player.png", { 0, 0, 23, 40 }, __("Cheat in game."), ACHIEVEMENT_HIDDEN, nullptr },
+ { "cheat", __("Cheater"), "gfx/achievements/cheat.png", { 0, 0, 50, 50 }, __("Cheat in game."), ACHIEVEMENT_HIDDEN, nullptr },
- {"programmer",__("Programmer"),"gfx/gui.png",{112,16,16,16},__("Play the development version of Me and My Shadow."),ACHIEVEMENT_TITLE,nullptr},
+ {"programmer",__("Programmer"),"gfx/achievements/programmer.png",{0,0,50,50},__("Play the development version of Me and My Shadow."),ACHIEVEMENT_TITLE,nullptr},
//end of achievements
{}
};
diff --git a/src/Addons.cpp b/src/Addons.cpp
index ce08437..8d98c9a 100644
--- a/src/Addons.cpp
+++ b/src/Addons.cpp
@@ -1,1066 +1,1082 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Addons.h"
#include "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "GUIObject.h"
#include "GUIOverlay.h"
#include "GUIScrollBar.h"
#include "GUITextArea.h"
#include "GUIListBox.h"
#include "POASerializer.h"
#include "LevelPackManager.h"
#include "InputManager.h"
#include "ThemeManager.h"
#include <string>
#include <sstream>
#include <iostream>
#include "libs/tinyformat/tinyformat.h"
#include <SDL.h>
#include <SDL_ttf.h>
using namespace std;
static const char* predefinedCategories[] = {
"levels", __("Levels"), __("Single level which usually contain demanding puzzles"),
"levelpacks", __("Levelpacks"), __("Collection of levels with the same author or style"),
"themes", __("Themes"), __("Give every block and background a new look and feel"),
NULL,
};
static std::map<std::string, std::string> categoryNameMap;
static std::map<std::string, std::string> categoryDescriptionMap;
Addons::Addons(SDL_Renderer &renderer, ImageManager &imageManager):selected(NULL){
//Render the title.
title = titleTextureFromText(renderer, _("Addons"), objThemes.getTextColor(false), SCREEN_WIDTH);
//Load placeholder addon icons and screenshot.
addonIcon["levels"] = imageManager.loadImage(getDataPath() + "/gfx/addon1.png");
addonIcon["levelpacks"] = imageManager.loadImage(getDataPath() + "/gfx/addon2.png");
addonIcon["themes"] = imageManager.loadImage(getDataPath() + "/gfx/addon3.png");
addonIcon[std::string()] = imageManager.loadImage(getDataPath() + "/gfx/addon0.png");
//Load predefined categories.
if (categoryNameMap.empty()) {
for (int i = 0; predefinedCategories[i]; i += 3) {
categoryNameMap[predefinedCategories[i]] = predefinedCategories[i + 1];
categoryDescriptionMap[predefinedCategories[i]] = predefinedCategories[i + 2];
}
}
screenshot=imageManager.loadTexture(getDataPath()+"/gfx/screenshot.png", renderer);
//Open the addons file in the user cache path for writing (downloading) to.
FILE* addon=fopen((getUserPath(USER_CACHE)+"addons").c_str(),"wb");
//Clear the GUI if any.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
-
+
+ //Show a loading screen
+ {
+ int w = 0, h = 0;
+ SDL_GetRendererOutputSize(&renderer, &w, &h);
+ SDL_Color fg = { 255, 255, 255, 0 };
+ TexturePtr loadingTexture = titleTextureFromText(renderer, _("Loading..."), fg, w);
+ SDL_Rect loadingRect = rectFromTexture(*loadingTexture);
+ loadingRect.x = (w - loadingRect.w) / 2;
+ loadingRect.y = (h - loadingRect.h) / 2;
+ SDL_SetRenderDrawColor(&renderer, 0, 0, 0, 255);
+ SDL_RenderClear(&renderer);
+ SDL_RenderCopy(&renderer, loadingTexture.get(), NULL, &loadingRect);
+ SDL_RenderPresent(&renderer);
+ SDL_RenderClear(&renderer);
+ }
+
//Try to get(download) the addonsList.
if(getAddonsList(addon, renderer, imageManager)==false){
//It failed so we show the error message.
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
GUIObject* obj=new GUILabel(imageManager,renderer,90,96,200,32,_("Unable to initialize addon menu:"));
obj->name="lbl";
GUIObjectRoot->addChild(obj);
obj=new GUILabel(imageManager,renderer,120,130,200,32,error.c_str());
obj->name="lbl";
GUIObjectRoot->addChild(obj);
obj=new GUIButton(imageManager,renderer,90,550,200,32,_("Back"));
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
return;
}
//Now create the GUI.
createGUI(renderer, imageManager);
}
Addons::~Addons(){
//If the GUIObjectRoot exist delete it.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
void Addons::createGUI(SDL_Renderer& renderer, ImageManager& imageManager){
//Downloaded the addons file now we can create the GUI.
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Create list of categories
categoryList = new GUISingleLineListBox(imageManager, renderer, (SCREEN_WIDTH - 500) / 2, 100, 500, 32);
categoryList->name="lstTabs";
//Loop through the categories and add them to the list.
//FIXME: Hack for easy detecting which categories there are.
{
set<string> categories;
for (const auto& a : addons) {
categories.insert(a.type);
}
for (const auto& c : categories) {
auto it = categoryNameMap.find(c);
categoryList->addItem(c, it == categoryNameMap.end() ? c.c_str() : _(it->second));
}
}
categoryList->value=0;
categoryList->eventCallback=this;
GUIObjectRoot->addChild(categoryList);
//category description
categoryDescription = new GUILabel(imageManager, renderer, 0, 136, SCREEN_WIDTH, 32, "", 0, true, true, GUIGravityCenter);
if (categoryList->value >= 0 && categoryList->value < (int)categoryList->item.size()) {
auto it = categoryDescriptionMap.find(categoryList->item[categoryList->value].first);
if (it != categoryDescriptionMap.end()) categoryDescription->caption = _(it->second);
}
GUIObjectRoot->addChild(categoryDescription);
//Create the list for the addons.
//By default levels will be selected.
list=new GUIListBox(imageManager,renderer,SCREEN_WIDTH*0.1,176,SCREEN_WIDTH*0.8,SCREEN_HEIGHT-228);
addonsToList(categoryList->getName(), renderer, imageManager);
list->name="lstAddons";
list->clickEvents=true;
list->eventCallback=this;
list->value=-1;
GUIObjectRoot->addChild(list);
type="levels";
//The back button.
GUIObject* obj=new GUIButton(imageManager,renderer,20,20,-1,32,_("Back"));
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
}
bool Addons::getAddonsList(FILE* file, SDL_Renderer& renderer, ImageManager& imageManager){
//First we download the file.
if(downloadFile(getSettings()->getValue("addon_url"),file)==false){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: unable to download addons file!"<<endl;
error=_("ERROR: unable to download addons file!");
return false;
}
fclose(file);
//Load the downloaded file.
ifstream addonFile;
addonFile.open((getUserPath(USER_CACHE)+"addons").c_str());
if(!addonFile.good()) {
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: unable to load addon_list file!"<<endl;
/// TRANSLATORS: addon_list is the name of a file and should not be translated.
error=_("ERROR: unable to load addon_list file!");
return false;
}
//Parse the addonsfile.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(addonFile,&obj,true)){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Invalid file format of addons file!"<<endl;
error=_("ERROR: Invalid file format of addons file!");
return false;
}
}
//Check the addon version in the addons list.
int version=0;
if(!obj.attributes["version"].empty())
version=atoi(obj.attributes["version"][0].c_str());
if(version<MIN_VERSION || version>MAX_VERSION){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Addon list version is unsupported! (received: "<<version<<" supported:"<<MIN_VERSION<<"-"<<MAX_VERSION<<")"<<endl;
error=_("ERROR: Addon list version is unsupported!");
return false;
}
//Also load the installed_addons file.
ifstream iaddonFile;
iaddonFile.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddonFile) {
//The installed_addons file doesn't exist, so we create it.
ofstream iaddons;
iaddons.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
iaddons<<" "<<endl;
iaddons.close();
//Also load the installed_addons file.
iaddonFile.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddonFile) {
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Unable to create the installed_addons file."<<endl;
/// TRANSLATORS: installed_addons is the name of a file and should not be translated.
error=_("ERROR: Unable to create the installed_addons file.");
return false;
}
}
//And parse the installed_addons file.
TreeStorageNode obj1;
{
POASerializer objSerializer;
if(!objSerializer.readNode(iaddonFile,&obj1,true)){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Invalid file format of the installed_addons!"<<endl;
error=_("ERROR: Invalid file format of the installed_addons!");
return false;
}
}
//Fill the vector.
fillAddonList(obj,obj1, renderer, imageManager);
//Close the files.
iaddonFile.close();
addonFile.close();
return true;
}
void Addons::fillAddonList(TreeStorageNode &objAddons, TreeStorageNode &objInstalledAddons,
SDL_Renderer& renderer, ImageManager& imageManager){
//Loop through the blocks of the addons file.
//These should contain the types levels, levelpacks, themes.
for(unsigned int i=0;i<objAddons.subNodes.size();i++){
TreeStorageNode* block=objAddons.subNodes[i];
if(block==NULL) continue;
//Check what kind of block it is, only category at the moment.
if(block->name=="category" && block->value.size()>0){
string type=block->value[0];
//Now loop the entries(subNodes) of the block.
for(unsigned int i=0;i<block->subNodes.size();i++){
TreeStorageNode* entry=block->subNodes[i];
if(entry==NULL) continue;
if(entry->name=="entry" && entry->value.size()==1){
//The entry is valid so create a new Addon.
Addon addon;
addon.icon=nullptr;
addon.screenshot=nullptr;
addon.type=type;
addon.name=entry->value[0];
addon.version = 0;
addon.installedVersion = 0;
if(!entry->attributes["file"].empty())
addon.file=entry->attributes["file"][0];
if(!entry->attributes["author"].empty())
addon.author=entry->attributes["author"][0];
if(!entry->attributes["description"].empty())
addon.description=entry->attributes["description"][0];
if(!entry->attributes["license"].empty())
addon.license=entry->attributes["license"][0];
if(!entry->attributes["website"].empty())
addon.website=entry->attributes["website"][0];
if(entry->attributes["icon"].size()>1){
//There are (at least) two values, the url to the icon and its md5sum used for caching.
addon.icon=loadCachedImage(
entry->attributes["icon"][0].c_str(),
entry->attributes["icon"][1].c_str(),
imageManager
);
}
if(entry->attributes["screenshot"].size()>1){
//There are (at least) two values, the url to the screenshot and its md5sum used for caching.
addon.screenshot=loadCachedTexture(
entry->attributes["screenshot"][0].c_str(),
entry->attributes["screenshot"][1].c_str(),
renderer,
imageManager
);
}
if(!entry->attributes["version"].empty())
addon.version=atoi(entry->attributes["version"][0].c_str());
addon.upToDate=false;
addon.installed=false;
//Check if the addon is already installed.
for(unsigned int i=0;i<objInstalledAddons.subNodes.size();i++){
TreeStorageNode* installed=objInstalledAddons.subNodes[i];
if(installed==NULL) continue;
if(installed->name=="entry" && installed->value.size()==3){
if(addon.type.compare(installed->value[0])==0 && addon.name.compare(installed->value[1])==0) {
addon.installed=true;
addon.installedVersion=atoi(installed->value[2].c_str());
if(addon.installedVersion>=addon.version) {
addon.upToDate=true;
}
//Read the dependencies and content from the file.
for(unsigned int j=0;j<installed->subNodes.size();j++){
if(installed->subNodes[j]->name=="content"){
TreeStorageNode* obj=installed->subNodes[j];
for(unsigned int k=0;k<obj->subNodes.size();k++){
if(obj->subNodes[k]->value.size()==1)
addon.content.push_back(pair<string,string>(obj->subNodes[k]->name,obj->subNodes[k]->value[0]));
}
}else if(installed->subNodes[j]->name=="dependencies"){
TreeStorageNode* obj=installed->subNodes[j];
for(unsigned int k=0;k<obj->subNodes.size();k++){
if(obj->subNodes[k]->value.size()==1)
addon.dependencies.push_back(pair<string,string>(obj->subNodes[k]->name,obj->subNodes[k]->value[0]));
}
}
}
}
}
}
//Finally put him in the list.
addons.push_back(addon);
}
}
}
}
}
void Addons::addonsToList(const std::string &type, SDL_Renderer& renderer, ImageManager&){
//Clear the list.
list->clearItems();
//Loop through the addons.
for(unsigned int i=0;i<addons.size();i++) {
//Make sure the addon is of the requested type.
if(addons[i].type!=type)
continue;
const Addon& addon=addons[i];
string entry=addon.name+" by "+addon.author;
if(addon.installed){
if(addon.upToDate){
entry+=" *";
}else{
entry+=" +";
}
}
SurfacePtr surf = createSurface(list->width,74);
//Check if there's an icon for the addon.
if(addon.icon){
applySurface(5, 5, addon.icon, surf.get(), NULL);
}else{
auto it = addonIcon.find(type);
if (it == addonIcon.end()) it = addonIcon.find(std::string());
assert(it != addonIcon.end());
applySurface(5, 5, it->second, surf.get(), NULL);
}
SDL_Surface* nameSurf=TTF_RenderUTF8_Blended(fontGUI,addon.name.c_str(),objThemes.getTextColor(true));
SDL_SetSurfaceAlphaMod(nameSurf,0xFF);
applySurface(74,-1,nameSurf,surf.get(),NULL);
SDL_FreeSurface(nameSurf);
/// TRANSLATORS: indicates the author of an addon.
string authorLine = tfm::format(_("by %s"),addon.author);
SDL_Surface* authorSurf=TTF_RenderUTF8_Blended(fontText,authorLine.c_str(),objThemes.getTextColor(true));
SDL_SetSurfaceAlphaMod(authorSurf,0xFF);
applySurface(74,43,authorSurf,surf.get(),NULL);
SDL_FreeSurface(authorSurf);
if(addon.installed){
if(addon.upToDate){
SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Installed"),objThemes.getTextColor(true));
SDL_SetSurfaceAlphaMod(infoSurf,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf.get(),NULL);
SDL_FreeSurface(infoSurf);
}else{
SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Updatable"),objThemes.getTextColor(true));
SDL_SetSurfaceAlphaMod(infoSurf,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf.get(),NULL);
SDL_FreeSurface(infoSurf);
}
}else{
SDL_Color c = objThemes.getTextColor(true);
c.r = c.r / 2 + 128;
c.g = c.g / 2 + 128;
c.b = c.b / 2 + 128;
SDL_Surface* infoSurf = TTF_RenderUTF8_Blended(fontText, _("Not installed"), c);
SDL_SetSurfaceAlphaMod(infoSurf,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf.get(),NULL);
SDL_FreeSurface(infoSurf);
}
list->addItem(renderer,entry,textureFromSurface(renderer,std::move(surf)));
}
}
bool Addons::saveInstalledAddons(){
//Open the file.
ofstream iaddons;
iaddons.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddons) return false;
TreeStorageNode installed;
//Loop through all the addons.
vector<Addon>::iterator it;
for(it=addons.begin();it!=addons.end();++it){
//Check if the level is installed or not.
if(it->installed) {
TreeStorageNode *entry=new TreeStorageNode;
entry->name="entry";
entry->value.push_back(it->type);
entry->value.push_back(it->name);
char version[64];
sprintf(version,"%d",it->installedVersion);
entry->value.push_back(version);
//Now add a subNode for each content.
TreeStorageNode* content=new TreeStorageNode;
content->name="content";
for(unsigned int i=0;i<it->content.size();i++){
TreeStorageNode* contentEntry=new TreeStorageNode;
contentEntry->name=it->content[i].first;
contentEntry->value.push_back(it->content[i].second);
//Add the content node to the entry node.
content->subNodes.push_back(contentEntry);
}
entry->subNodes.push_back(content);
//Now add a sub node for the dependencies.
TreeStorageNode* deps=new TreeStorageNode;
deps->name="dependencies";
for(unsigned int i=0;i<it->dependencies.size();i++){
TreeStorageNode* depsEntry=new TreeStorageNode;
depsEntry->name=it->dependencies[i].first;
depsEntry->value.push_back(it->dependencies[i].second);
//Add the content node to the entry node.
deps->subNodes.push_back(depsEntry);
}
entry->subNodes.push_back(deps);
//And add the entry to the top node.
installed.subNodes.push_back(entry);
}
}
//And write away the file.
POASerializer objSerializer;
objSerializer.writeNode(&installed,iaddons,true,true);
return true;
}
SharedTexture Addons::loadCachedTexture(const char* url,const char* md5sum,
SDL_Renderer& renderer, ImageManager& imageManager){
//Check if the image is cached.
string imageFile=getUserPath(USER_CACHE)+"images/"+md5sum;
if(fileExists(imageFile.c_str())){
//It is, so load the image.
return imageManager.loadTexture(imageFile, renderer);
}else{
//Download the image.
FILE* file=fopen(imageFile.c_str(),"wb");
//Downloading failed.
if(!downloadFile(url,file)){
cerr<<"ERROR: Unable to download image from "<<url<<endl;
fclose(file);
return NULL;
}
fclose(file);
//Load the image.
return imageManager.loadTexture(imageFile, renderer);
}
}
SDL_Surface* Addons::loadCachedImage(const char* url, const char* md5sum,
ImageManager& imageManager){
//Check if the image is cached.
string imageFile = getUserPath(USER_CACHE) + "images/" + md5sum;
if (fileExists(imageFile.c_str())){
//It is, so load the image.
return imageManager.loadImage(imageFile);
} else{
//Download the image.
FILE* file = fopen(imageFile.c_str(), "wb");
//Downloading failed.
if (!downloadFile(url, file)){
cerr << "ERROR: Unable to download image from " << url << endl;
fclose(file);
return NULL;
}
fclose(file);
//Load the image.
return imageManager.loadImage(imageFile);
}
}
void Addons::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//Check if we should quit.
if(event.type==SDL_QUIT){
//Save the installed addons before exiting.
saveInstalledAddons();
setNextState(STATE_EXIT);
}
//Check if escape is pressed, if so return to the main menu.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
//Check horizontal movement
int value = categoryList->value;
if (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
isKeyboardOnly = true;
value++;
if (value >= (int)categoryList->item.size()) value = 0;
} else if (inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
isKeyboardOnly = true;
value--;
if (value < 0) value = categoryList->item.size() - 1;
}
if (value >= 0 && value < (int)categoryList->item.size()) {
if (categoryList->value != value) {
categoryList->value = value;
GUIEventCallback_OnEvent(imageManager, renderer, categoryList->name, categoryList, GUIEventChange);
return;
}
//Check vertical movement
if (inputMgr.isKeyDownEvent(INPUTMGR_UP)){
isKeyboardOnly = true;
list->value--;
if (list->value < 0) list->value = 0;
//FIXME: ad-hoc stupid code
list->scrollScrollbar(0xC0000000);
list->scrollScrollbar(list->value);
} else if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
isKeyboardOnly = true;
list->value++;
if (list->value >= (int)list->item.size()) list->value = list->item.size() - 1;
//FIXME: ad-hoc stupid code
list->scrollScrollbar(0xC0000000);
list->scrollScrollbar(list->value);
}
if (isKeyboardOnly && inputMgr.isKeyDownEvent(INPUTMGR_SELECT) && list->value >= 0 && list->value<(int)list->item.size()) {
GUIEventCallback_OnEvent(imageManager, renderer, list->name, list, GUIEventChange); // ???
GUIEventCallback_OnEvent(imageManager, renderer, list->name, list, GUIEventClick);
return;
}
}
}
void Addons::logic(ImageManager&, SDL_Renderer&){}
void Addons::render(ImageManager&, SDL_Renderer& renderer){
//Draw background.
objThemes.getBackground(true)->draw(renderer);
//Draw the title.
drawTitleTexture(SCREEN_WIDTH, *title, renderer);
}
void Addons::resize(ImageManager& imageManager, SDL_Renderer& renderer){
//Delete the gui (if any).
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Now create a new one.
createGUI(renderer, imageManager);
}
void Addons::showAddon(ImageManager& imageManager, SDL_Renderer& renderer){
//Make sure an addon is selected.
if(!selected)
return;
//Skip next mouse up event since we're clicking a list box and showing a new window.
GUISkipNextMouseUpEvent = true;
//Create a root object.
GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-400)/2,600,400,selected->name.c_str());
//Create the 'by creator' label.
GUIObject* obj=new GUILabel(imageManager,renderer,0,50,600,50,tfm::format(_("by %s"),selected->author).c_str(),0,true,true,GUIGravityCenter);
root->addChild(obj);
//Create the description text.
std::string s = tfm::format(_("Version: %d\n"), selected->version);
if (selected->installed) {
s += tfm::format(_("Installed version: %d\n"), selected->installedVersion);
}
if (!selected->license.empty()) {
s += tfm::format(_("License: %s\n"), appendURLToLicense(selected->license));
}
if (!selected->website.empty()) {
s += tfm::format(_("Website: %s\n"), selected->website);
}
s += '\n';
if (selected->description.empty()) {
s += _("(No descriptions provided)");
} else {
s += selected->description;
}
GUITextArea* description=new GUITextArea(imageManager,renderer,10,100,370,200);
description->setString(renderer, s, true);
description->editable=false;
description->onResize();
description->extractHyperlinks();
root->addChild(description);
//Create the screenshot image. (If a screenshot is missing, we use the default screenshot.)
GUIImage* img=new GUIImage(imageManager,renderer,390,100,200,150,selected->screenshot?selected->screenshot:screenshot);
root->addChild(img);
GUIButton *cancelButton;
//Add buttons depending on the installed/update status.
if(selected->installed && !selected->upToDate){
GUIObject* bRemove=new GUIButton(imageManager,renderer,root->width*0.97,350,-1,32,_("Remove"),0,true,true,GUIGravityRight);
bRemove->name="cmdRemove";
bRemove->eventCallback=this;
root->addChild(bRemove);
//Create a back button.
cancelButton = new GUIButton(imageManager, renderer, root->width*0.03, 350, -1, 32, _("Back"), 0, true, true, GUIGravityLeft);
cancelButton->name = "cmdCloseOverlay";
cancelButton->eventCallback = this;
root->addChild(cancelButton);
//Update widget sizes.
root->render(renderer, 0,0,false);
//Create a nicely centered button.
obj = new GUIButton(imageManager, renderer,
(int)floor((cancelButton->left + cancelButton->width + bRemove->left - bRemove->width)*0.5), 350,
-1, 32, _("Update"), 0, true, true, GUIGravityCenter);
obj->name="cmdUpdate";
obj->eventCallback=this;
root->addChild(obj);
}else{
if(!selected->installed){
obj=new GUIButton(imageManager,renderer,root->width*0.9,350,-1,32,_("Install"),0,true,true,GUIGravityRight);
obj->name="cmdInstall";
obj->eventCallback=this;
root->addChild(obj);
}else if(selected->upToDate){
obj=new GUIButton(imageManager,renderer,root->width*0.9,350,-1,32,_("Remove"),0,true,true,GUIGravityRight);
obj->name="cmdRemove";
obj->eventCallback=this;
root->addChild(obj);
}
//Create a back button.
cancelButton = new GUIButton(imageManager, renderer, root->width*0.1, 350, -1, 32, _("Back"), 0, true, true, GUIGravityLeft);
cancelButton->name = "cmdCloseOverlay";
cancelButton->eventCallback = this;
root->addChild(cancelButton);
}
new AddonOverlay(renderer, root, cancelButton, description, TabFocus | ReturnControls);
}
void Addons::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
if(name=="lstTabs"){
//Get the category type.
type=categoryList->getName();
//Get the description of current category.
auto it = categoryDescriptionMap.find(type);
if (it != categoryDescriptionMap.end()) categoryDescription->caption = _(it->second);
else categoryDescription->caption.clear();
//Get the list corresponding with the category and select the first entry.
addonsToList(type, renderer, imageManager);
list->value=-1;
//Call an event as if an entry in the addons listbox was clicked.
GUIEventCallback_OnEvent(imageManager, renderer, "lstAddons",list,GUIEventChange);
}else if(name=="lstAddons"){
//Check which type of event.
if(eventType==GUIEventChange){
//Get the addon struct that belongs to it.
Addon* addon=NULL;
//Make sure the addon list on screen isn't empty.
if (list->value >= 0 && list->value < (int)list->item.size()){
//Get the name of the (newly) selected entry.
string entry=list->getItem(list->value);
//Loop through the addons of the selected category.
for(unsigned int i=0;i<addons.size();i++){
//Make sure the addons are of the same type.
if(addons[i].type!=categoryList->getName())
continue;
string prefix=addons[i].name;
if(!entry.compare(0,prefix.size(),prefix)){
addon=&addons[i];
}
}
}
//Set the new addon as selected and unselect the list.
selected=addon;
if (!isKeyboardOnly) list->value = -1;
}else if(eventType==GUIEventClick){
//Make sure an addon is selected.
if(selected){
showAddon(imageManager,renderer);
}
}
}else if(name=="cmdBack"){
saveInstalledAddons();
setNextState(STATE_MENU);
}else if(name=="cmdCloseOverlay"){
//We can safely delete the GUIObjectRoot, since it's handled by the GUIOverlay.
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}else if(name=="cmdUpdate"){
//NOTE: This simply removes the addon and reinstalls it.
//The complete addon is downloaded either way so no need for checking what has been changed/added/removed/etc...
if(selected){
removeAddon(imageManager,renderer,selected);
installAddon(imageManager,renderer,selected);
}
addonsToList(categoryList->getName(), renderer, imageManager);
}else if(name=="cmdInstall"){
if(selected)
installAddon(imageManager,renderer,selected);
addonsToList(categoryList->getName(), renderer, imageManager);
}else if(name=="cmdRemove"){
//TODO: Check for dependencies.
//Loop through the addons to check if this addon is a dependency of another addon.
vector<Addon>::iterator it;
for(it=addons.begin();it!=addons.end();++it){
//Check if the addon has dependencies.
if(!it->dependencies.empty()){
vector<pair<string,string> >::iterator depIt;
for(depIt=it->dependencies.begin();depIt!=it->dependencies.end();++depIt){
if(depIt->first=="addon" && depIt->second==selected->name){
msgBox(imageManager,renderer,tfm::format(_("This addon can't be removed because it's needed by %s."),it->name),MsgBoxOKOnly,_("Dependency"));
return;
}
}
}
}
if(selected)
removeAddon(imageManager,renderer,selected);
addonsToList(categoryList->getName(), renderer, imageManager);
}
//NOTE: In case of install/remove/update we can delete the GUIObjectRoot, since it's managed by the GUIOverlay.
if(name=="cmdUpdate" || name=="cmdInstall" || name=="cmdRemove"){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
void Addons::removeAddon(ImageManager& imageManager,SDL_Renderer& renderer, Addon* addon){
//To remove an addon we loop over the content vector in the structure.
//NOTE: This should contain all INSTALLED content, if something failed during installation it isn't added.
for(unsigned int i=0;i<addon->content.size();i++){
//Check the type of content.
if(addon->content[i].first=="file"){
string file=getUserPath(USER_DATA)+addon->content[i].second;
//Check if the file exists.
if(!fileExists(file.c_str())){
cerr<<"WARNING: File '"<<file<<"' appears to have been removed already."<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: File '%s' appears to have been removed already."),file),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Remove the file.
if(!removeFile(file.c_str())){
cerr<<"ERROR: Unable to remove file '"<<file<<"'!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to remove file '%s'!"),file),MsgBoxOKOnly,_("Addon error"));
continue;
}
}else if(addon->content[i].first=="folder"){
string dir=getUserPath(USER_DATA)+addon->content[i].second;
//Check if the directory exists.
if(!dirExists(dir.c_str())){
cerr<<"WARNING: Directory '"<<dir<<"' appears to have been removed already."<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Directory '%s' appears to have been removed already."),dir),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Remove the directory.
if(!removeDirectory(dir.c_str())){
cerr<<"ERROR: Unable to remove directory '"<<dir<<"'!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to remove directory '%s'!"),dir),MsgBoxOKOnly,_("Addon error"));
continue;
}
}else if(addon->content[i].first=="level"){
string file=getUserPath(USER_DATA)+"levels/"+addon->content[i].second;
//Check if the level file exists.
if(!fileExists(file.c_str())){
cerr<<"WARNING: Level '"<<file<<"' appears to have been removed already."<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Level '%s' appears to have been removed already."),file),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Remove the level file.
if(!removeFile(file.c_str())){
cerr<<"ERROR: Unable to remove level '"<<file<<"'!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to remove level '%s'!"),file),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Also remove the level from the Levels levelpack.
LevelPack* levelsPack=getLevelPackManager()->getLevelPack(LEVELS_PATH);
for(int i=0;i<levelsPack->getLevelCount();i++){
if(levelsPack->getLevelFile(i)==file){
//Remove the level and break out of the loop.
levelsPack->removeLevel(i);
break;
}
}
}else if(addon->content[i].first=="levelpack"){
//FIXME: We assume no trailing slash since there mustn't be one for installing, bad :(
string dir=getUserPath(USER_DATA)+"levelpacks/"+addon->content[i].second+"/";
//Check if the directory exists.
if(!dirExists(dir.c_str())){
cerr<<"WARNING: Levelpack directory '"<<dir<<"' appears to have been removed already."<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Levelpack directory '%s' appears to have been removed already."),dir),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Remove the directory.
if(!removeDirectory(dir.c_str())){
cerr<<"ERROR: Unable to remove levelpack directory '"<<dir<<"'!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to remove levelpack directory '%s'!"),dir),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Also remove the levelpack from the levelpackManager.
getLevelPackManager()->removeLevelPack(dir, true);
}
}
//Now that the content has been removed clear the content list itself.
addon->content.clear();
//And finally set the addon to not installed.
addon->installed=false;
addon->installedVersion=0;
//Also clear the 'offline' information.
addon->content.clear();
addon->dependencies.clear();
}
void Addons::installAddon(ImageManager& imageManager,SDL_Renderer& renderer, Addon* addon){
string tmpDir=getUserPath(USER_CACHE)+"tmp/";
string fileName=fileNameFromPath(addon->file,true);
//Download the selected addon to the tmp folder.
if(!downloadFile(addon->file,tmpDir)){
cerr<<"ERROR: Unable to download addon file "<<addon->file<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to download addon file %s."),addon->file),MsgBoxOKOnly,_("Addon error"));
return;
}
//Now extract the addon.
if(!extractFile(tmpDir+fileName,tmpDir+"/addon/")){
cerr<<"ERROR: Unable to extract addon file "<<addon->file<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to extract addon file %s."),addon->file),MsgBoxOKOnly,_("Addon error"));
return;
}
ifstream metadata((tmpDir+"/addon/metadata").c_str());
if(!metadata){
cerr<<"ERROR: Addon is missing metadata!"<<endl;
msgBox(imageManager,renderer,_("ERROR: Addon is missing metadata!"),MsgBoxOKOnly,_("Addon error"));
return;
}
//Read the metadata from the addon.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(metadata,&obj,true)){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Invalid file format for metadata file!"<<endl;
msgBox(imageManager,renderer,_("ERROR: Invalid file format for metadata file!"),MsgBoxOKOnly,_("Addon error"));
return;
}
}
//Loop through the subNodes.
for(unsigned int i=0;i<obj.subNodes.size();i++){
//Check for the content subNode (there should only be one).
if(obj.subNodes[i]->name=="content"){
TreeStorageNode* obj1=obj.subNodes[i];
//Loop through the subNodes of that.
for(unsigned int j=0;j<obj1->subNodes.size();j++){
TreeStorageNode* obj2=obj1->subNodes[j];
//This code happens for all types of content.
string source=tmpDir+"addon/content/";
if(obj2->value.size()>0)
source+=obj2->value[0];
//The destination MUST be in the user data path.
string dest=getUserPath(USER_DATA);
if(obj2->value.size()>1)
dest+=obj2->value[1];
//Check what the content type is.
if(obj2->name=="file" && obj2->value.size()==2){
//Now copy the file.
if(fileExists(dest.c_str())){
cerr<<"WARNING: File '"<<dest<<"' already exists, addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: File '%s' already exists, addon may be broken or not working!"),dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
if(!copyFile(source.c_str(),dest.c_str())){
cerr<<"WARNING: Unable to copy file '"<<source<<"' to '"<<dest<<"', addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Unable to copy file '%s' to '%s', addon may be broken or not working!"),source,dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Add it to the content vector.
addon->content.push_back(pair<string,string>("file",obj2->value[1]));
}else if(obj2->name=="folder" && obj2->value.size()==2){
//The dest must NOT exist, otherwise it will fail.
if(dirExists(dest.c_str())){
cerr<<"WARNING: Destination directory '"<<dest<<"' already exists, addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Destination directory '%s' already exists, addon may be broken or not working!"),dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//FIXME: Copy the directory instead of renaming it, in case the same folder/parts of the folder are needed in different places.
if(!renameDirectory(source.c_str(),dest.c_str())){
cerr<<"WARNING: Unable to move directory '"<<source<<"' to '"<<dest<<"', addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Unable to move directory '%s' to '%s', addon may be broken or not working!"),source,dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Add it to the content vector.
addon->content.push_back(pair<string,string>("folder",obj2->value[1]));
}else if(obj2->name=="level" && obj2->value.size()==1){
//The destination MUST be in the levels folder in the user data path.
dest+="levels/"+fileNameFromPath(source);
//Now copy the file.
if(fileExists(dest.c_str())){
cerr<<"WARNING: Level '"<<dest<<"' already exists, addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Level '%s' already exists, addon may be broken or not working!"),dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
if(!copyFile(source.c_str(),dest.c_str())){
cerr<<"WARNING: Unable to copy level '"<<source<<"' to '"<<dest<<"', addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Unable to copy level '%s' to '%s', addon may be broken or not working!"),source,dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//It's a level so add it to the Levels levelpack.
LevelPack* levelsPack=getLevelPackManager()->getLevelPack(LEVELS_PATH);
if(levelsPack){
levelsPack->addLevel(dest);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}else{
cerr<<"ERROR: Unable to add level to Levels levelpack"<<endl;
}
addon->content.push_back(pair<string,string>("level",fileNameFromPath(source)));
}else if(obj2->name=="levelpack" && obj2->value.size()==1){
//TODO: Check if the source contains a trailing slash.
//The destination MUST be in the user data path.
dest+="levelpacks/"+fileNameFromPath(source);
//The dest must NOT exist, otherwise it will fail.
if(dirExists(dest.c_str())){
cerr<<"WARNING: Levelpack directory '"<<dest<<"' already exists, addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Levelpack directory '%s' already exists, addon may be broken or not working!"),dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//FIXME: Copy the directory instead of renaming it, in case the same folder/parts of the folder are needed in different places.
if(!renameDirectory(source.c_str(),dest.c_str())){
cerr<<"WARNING: Unable to move directory '"<<source<<"' to '"<<dest<<"', addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Unable to move directory '%s' to '%s', addon may be broken or not working!"),source,dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//It's a levelpack so add it to the levelpack manager.
getLevelPackManager()->loadLevelPack(dest);
addon->content.push_back(pair<string,string>("levelpack",fileNameFromPath(source)));
}
}
}else if(obj.subNodes[i]->name=="dependencies"){
TreeStorageNode* obj1=obj.subNodes[i];
//Loop through the subNodes of that.
for(unsigned int j=0;j<obj1->subNodes.size();j++){
TreeStorageNode* obj2=obj1->subNodes[j];
if(obj2->name=="addon" && obj2->value.size()>0){
Addon* dep=NULL;
//Check if the requested addon can be found.
vector<Addon>::iterator it;
for(it=addons.begin();it!=addons.end();++it){
if(it->name==obj2->value[0]){
dep=&(*it);
break;
}
}
if(!dep){
cerr<<"ERROR: Addon requires another addon ("<<obj2->value[0]<<") which can't be found!"<<endl;
msgBox(imageManager, renderer, tfm::format(_("ERROR: Addon requires another addon (%s) which can't be found!"), obj2->value[0]), MsgBoxOKOnly, _("Addon error"));
continue;
}
//The addon has been found, try to install it.
//FIXME: Somehow prevent recursion, maybe max depth (??)
if(!dep->installed){
msgBox(imageManager, renderer, tfm::format(_("The addon %s is needed and will be installed now."), dep->name), MsgBoxOKOnly, _("Dependency"));
installAddon(imageManager,renderer, dep);
}
//Add the dependency to the addon.
addon->dependencies.push_back(pair<string,string>("addon",dep->name));
}
}
}
}
//The addon is installed and up to date, but not necessarily flawless.
addon->installed=true;
addon->upToDate=true;
addon->installedVersion=addon->version;
}
diff --git a/src/Block.cpp b/src/Block.cpp
index fc808b0..c848694 100644
--- a/src/Block.cpp
+++ b/src/Block.cpp
@@ -1,1284 +1,1286 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me And My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Block.h"
#include "Functions.h"
#include "LevelEditor.h"
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
using namespace std;
Block::Block(Game* parent,int x,int y,int w,int h,int type):
GameObject(parent),
animation(0),
animationSave(0),
flags(0),
flagsSave(0),
temp(0),
tempSave(0),
dx(0),
dy(0),
dxSave(0),
dySave(0),
movingPosTime(-1),
speed(0),
speedSave(0),
editorSpeed(0),
editorFlags(0)
{
//Make sure the type is set, if not init should be called somewhere else with this information.
if(type>=0 && type<TYPE_MAX)
init(x,y,w,h,type);
}
Block::~Block(){}
int Block::getPathMaxTime() {
if (movingPosTime < 0) {
movingPosTime = 0;
for (const SDL_Rect& p : movingPos) {
movingPosTime += p.w;
}
}
return movingPosTime;
}
void Block::init(int x,int y,int w,int h,int type){
//First set the location and size of the box.
//The default size is 50x50.
box.x=boxBase.x=x;
box.y=boxBase.y=y;
box.w=boxBase.w=w;
box.h=boxBase.h=h;
//Set the save values.
boxSave.x=x;
boxSave.y=y;
boxSave.w=w;
boxSave.h=h;
//Set the type.
this->type=type;
//Some types need type specific code.
if(type==TYPE_START_PLAYER){
//This is the player start so set the player here.
//We center the player, the player is 23px wide.
parent->player.setLocation(box.x+(box.w-23)/2,box.y);
parent->player.fx=box.x+(box.w-23)/2;
parent->player.fy=box.y;
}else if(type==TYPE_START_SHADOW){
//This is the shadow start so set the shadow here.
//We center the shadow, the shadow is 23px wide.
parent->shadow.setLocation(box.x+(box.w-23)/2,box.y);
parent->shadow.fx=box.x+(box.w-23)/2;
parent->shadow.fy=box.y;
}
objCurrentStand=objCurrentStandSave=NULL;
inAir=inAirSave=true;
xVel=yVel=xVelBase=yVelBase=0;
xVelSave=yVelSave=xVelBaseSave=yVelBaseSave=0;
//And load the (default) appearance.
objThemes.getBlock(type)->createInstance(&appearance);
}
void Block::show(SDL_Renderer& renderer){
//Make sure we are visible.
if ((flags & 0x80000000) != 0 && (stateID != STATE_LEVEL_EDITOR || dynamic_cast<LevelEditor*>(parent)->isPlayMode()))
return;
//Check if the block is visible.
if(checkCollision(camera,box)==true || (stateID==STATE_LEVEL_EDITOR && checkCollision(camera,boxBase)==true)){
//Some type of block needs additional state check.
switch(type){
case TYPE_CHECKPOINT:
//Check if the checkpoint is last used.
if(parent!=NULL && parent->objLastCheckPoint==this){
if(!temp) appearance.changeState("activated");
temp=1;
}else{
if(temp) appearance.changeState("default");
temp=0;
}
break;
}
//Always draw the base. (This is only supposed to show in editor.)
appearance.drawState("base", renderer, boxBase.x - camera.x, boxBase.y - camera.y, boxBase.w, boxBase.h);
//What we need to draw depends on the type of block.
switch (type) {
default:
//Draw normal.
appearance.draw(renderer, box.x - camera.x, box.y - camera.y, box.w, box.h);
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
//Draw conveyor belt.
if (animation) {
// FIXME: ad-hoc code. Should add a new animation type in theme system.
const int a = animation / 10;
const SDL_Rect r = { box.x - camera.x, box.y - camera.y, box.w, box.h };
appearance.draw(renderer, box.x - camera.x - 50 + a, box.y - camera.y, box.w + 50, box.h, &r);
} else {
appearance.draw(renderer, box.x - camera.x, box.y - camera.y, box.w, box.h);
}
break;
}
//Some types need to draw something on top of the base/default.
switch(type){
case TYPE_BUTTON:
if(flags&4){
if(animation<5) animation++;
}else{
if(animation>0) animation--;
}
appearance.drawState("button", renderer, box.x - camera.x, box.y - camera.y - 5 + animation, box.w, box.h);
break;
}
//Draw some stupid icons during edit mode.
if (stateID == STATE_LEVEL_EDITOR) {
auto bmGUI = static_cast<LevelEditor*>(parent)->getGuiTexture();
if (!bmGUI) {
return;
}
int x = box.x - camera.x + 2;
//Scripted blocks
if (!scripts.empty()){
const SDL_Rect r = { 0, 32, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
//Invisible blocks
if (editorFlags & 0x80000000) {
const SDL_Rect r = { 16, 48, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
//Block with custom appearance
if (!customAppearanceName.empty()) {
const SDL_Rect r = { 48, 16, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
}
}
}
SDL_Rect Block::getBox(int boxType){
SDL_Rect r={0,0,0,0};
switch(boxType){
case BoxType_Base:
return boxBase;
case BoxType_Previous:
r.x=box.x-dx;
r.y=box.y-dy;
r.w=box.w;
r.h=box.h;
return r;
case BoxType_Delta:
r.x=dx;
r.y=dy;
return r;
case BoxType_Velocity:
r.x=xVel;
r.y=yVel;
//NOTE: In case of the pushable block we sometimes need to substract one from the vertical velocity.
//The yVel is set to one when it's resting, but should be handled as zero in collision.
if(type==TYPE_PUSHABLE && !inAir)
r.y=0;
return r;
case BoxType_Current:
return box;
}
return r;
}
void Block::moveTo(int x,int y){
//The block has moved so calculate the delta.
//NOTE: Every delta is summed since they all happened within one frame and for collision/movement we need the resulting delta.
int delta=(x-box.x);
dx+=delta;
xVel+=delta;
delta=(y-box.y);
dy+=delta;
yVel+=delta;
//And set the new location.
box.x=x;
box.y=y;
}
void Block::growTo(int w,int h){
//The block has changed size
//NOTE: Every delta is summed since they all happened within one frame and for collision/movement we need the resulting delta.
int delta=(w-box.w);
dx+=delta;
xVel+=delta;
delta=(h-box.h);
dy+=delta;
yVel+=delta;
//And set the new location.
box.w=w;
box.h=h;
}
void Block::saveState(){
animationSave=animation;
flagsSave=flags;
tempSave=temp;
dxSave=dx;
dySave=dy;
boxSave.x=box.x-boxBase.x;
boxSave.y=box.y-boxBase.y;
boxSave.w=box.w-boxBase.w;
boxSave.h=box.h-boxBase.h;
xVelSave=xVel;
yVelSave=yVel;
appearance.saveAnimation();
//In case of a certain blocks we need to save some more.
switch(type){
case TYPE_PUSHABLE:
objCurrentStandSave=objCurrentStand;
xVelBaseSave=xVelBase;
yVelBaseSave=yVelBase;
inAirSave=inAir;
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
speedSave=speed;
break;
}
}
void Block::loadState(){
//Restore the flags and animation var.
animation=animationSave;
flags=flagsSave;
temp=tempSave;
dx=dxSave;
dy=dySave;
//Restore the location.
box.x=boxBase.x+boxSave.x;
box.y=boxBase.y+boxSave.y;
box.w=boxBase.w+boxSave.w;
box.h=boxBase.h+boxSave.h;
//And the velocity.
xVel=xVelSave;
yVel=yVelSave;
//Invalidates the cache.
movingPosTime = -1;
//Handle block type specific variables.
switch(type){
case TYPE_PUSHABLE:
objCurrentStand=objCurrentStandSave;
xVelBase=xVelBaseSave;
yVelBase=yVelBaseSave;
inAir=inAirSave;
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
speed=speedSave;
break;
}
//And load the animation.
appearance.loadAnimation();
}
void Block::reset(bool save){
//We need to reset so we clear the animation and saves.
if(save){
animation=animationSave=0;
boxSave.x=boxSave.y=boxSave.w=boxSave.h=0;
flags=flagsSave=editorFlags;
temp=tempSave=0;
dx=dxSave=0;
dy=dySave=0;
}else{
animation=0;
flags=editorFlags;
temp=0;
dx=0;
dy=0;
}
//Invalidates the cache.
movingPosTime = -1;
//Reset the block to its original location.
box.x=boxBase.x;
box.y=boxBase.y;
box.w=boxBase.w;
box.h=boxBase.h;
//Reset any velocity.
xVel=yVel=xVelBase=yVelBase=0;
if(save)
xVelSave=yVelSave=xVelBaseSave=yVelBaseSave=0;
//Also reset the appearance.
appearance.resetAnimation(save);
appearance.changeState("default");
//NOTE: We load the animation right after changing it to prevent a transition.
if(save)
appearance.loadAnimation();
//Some types of block requires type specific code.
switch(type){
case TYPE_FRAGILE:
{
const int f = flags & 0x3;
const char* s=(f==0)?"default":((f==1)?"fragile1":((f==2)?"fragile2":"fragile3"));
appearance.changeState(s);
}
break;
case TYPE_PUSHABLE:
objCurrentStand=NULL;
inAir=false;
if(save) {
objCurrentStandSave=NULL;
inAirSave=false;
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
if(save)
speed=speedSave=editorSpeed;
else
speed=editorSpeed;
break;
}
}
void Block::playAnimation(){
switch(type){
case TYPE_SWAP:
appearance.changeState("activated");
break;
case TYPE_SWITCH:
temp^=1;
appearance.changeState(temp?"activated":"default");
break;
}
}
void Block::onEvent(int eventType){
//Make sure we are visible, otherwise no events should be handled except for 'OnCreate'.
if ((flags & 0x80000000) != 0 && eventType != GameObjectEvent_OnCreate)
return;
//Iterator used to check if the map contains certain entries.
map<int,int>::iterator it;
//Check if there's a script for the event.
it=compiledScripts.find(eventType);
if(it!=compiledScripts.end()){
//There is a script so execute it and check return value.
int ret=parent->getScriptExecutor()->executeScript(it->second,this);
//Return value 1 means do default event process.
//Other values are coming soon...
if(ret!=1) return;
}
//Event handling.
switch(eventType){
case GameObjectEvent_PlayerWalkOn:
switch(type){
case TYPE_FRAGILE:
if ((flags & 0x3) < 3) {
flags++;
const int f = flags & 0x3;
const char* s = (f <= 0) ? "default" : ((f == 1) ? "fragile1" : ((f == 2) ? "fragile2" : "fragile3"));
appearance.changeState(s);
}
break;
}
break;
case GameObjectEvent_PlayerIsOn:
switch(type){
case TYPE_BUTTON:
temp=1;
break;
}
break;
case GameObjectEvent_OnPlayerInteraction:
switch (type) {
case TYPE_SWITCH:
//Make sure that the id isn't emtpy.
if (!id.empty()) {
parent->broadcastObjectEvent(0x10000 | (flags & 3),
-1, id.c_str());
} else {
cerr << "WARNING: invalid switch id!" << endl;
}
break;
}
break;
case GameObjectEvent_OnToggle:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
flags^=1;
break;
case TYPE_PORTAL:
appearance.changeState("activated");
break;
case TYPE_COLLECTABLE:
appearance.changeState("inactive");
flags|=1;
break;
}
break;
case GameObjectEvent_OnSwitchOn:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
flags&=~1;
break;
case TYPE_EXIT:
appearance.changeState("default");
break;
}
break;
case GameObjectEvent_OnSwitchOff:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
flags|=1;
break;
case TYPE_EXIT:
appearance.changeState("closed");
break;
}
break;
}
}
int Block::queryProperties(int propertyType,Player* obj){
switch(propertyType){
case GameObjectProperty_PlayerCanWalkOn:
+ if (flags & 0x80000000) break;
switch(type){
case TYPE_BLOCK:
case TYPE_MOVING_BLOCK:
case TYPE_CONVEYOR_BELT:
case TYPE_BUTTON:
case TYPE_PUSHABLE:
return 1;
case TYPE_SHADOW_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_SHADOW_CONVEYOR_BELT:
if(obj!=NULL && obj->isShadow()) return 1;
break;
case TYPE_FRAGILE:
if ((flags & 0x3) < 3) return 1;
break;
}
break;
case GameObjectProperty_IsSpikes:
+ if (flags & 0x80000000) break;
switch(type){
case TYPE_SPIKES:
case TYPE_MOVING_SPIKES:
return 1;
}
break;
case GameObjectProperty_Flags:
return flags;
break;
default:
break;
}
return 0;
}
void Block::getEditorData(std::vector<std::pair<std::string,std::string> >& obj){
//Every block has an id.
obj.push_back(pair<string,string>("id",id));
//And visibility.
obj.push_back(pair<string, string>("visible", (editorFlags & 0x80000000) == 0 ? "1" : "0"));
//And custom appearance.
obj.push_back(pair<string, string>("appearance", customAppearanceName));
//Block specific properties.
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
char s[64],s0[64];
sprintf(s,"%d",(int)movingPos.size());
obj.push_back(pair<string,string>("MovingPosCount",s));
obj.push_back(pair<string,string>("activated",(editorFlags&0x1)?"0":"1"));
obj.push_back(pair<string,string>("loop",(editorFlags&0x2)?"0":"1"));
for(unsigned int i=0;i<movingPos.size();i++){
sprintf(s0+1,"%u",i);
sprintf(s,"%d",movingPos[i].x);
s0[0]='x';
obj.push_back(pair<string,string>(s0,s));
sprintf(s,"%d",movingPos[i].y);
s0[0]='y';
obj.push_back(pair<string,string>(s0,s));
sprintf(s,"%d",movingPos[i].w);
s0[0]='t';
obj.push_back(pair<string,string>(s0,s));
}
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
{
char s[64];
obj.push_back(pair<string,string>("activated",(editorFlags&0x1)?"0":"1"));
sprintf(s,"%d",editorSpeed);
obj.push_back(pair<string,string>("speed10",s));
}
break;
case TYPE_PORTAL:
obj.push_back(pair<string,string>("automatic",(editorFlags&0x1)?"1":"0"));
obj.push_back(pair<string,string>("destination",destination));
break;
case TYPE_BUTTON:
case TYPE_SWITCH:
{
string s;
switch(editorFlags&0x3){
case 1:
s="on";
break;
case 2:
s="off";
break;
default:
s="toggle";
break;
}
obj.push_back(pair<string,string>("behaviour",s));
}
break;
case TYPE_NOTIFICATION_BLOCK:
{
string value=message;
//Change \n with the characters '\n'.
while(value.find('\n',0)!=string::npos){
size_t pos=value.find('\n',0);
value=value.replace(pos,1,"\\n");
}
obj.push_back(pair<string,string>("message",value));
}
break;
case TYPE_FRAGILE:
{
char s[64];
sprintf(s,"%d",editorFlags&0x3);
obj.push_back(pair<string,string>("state",s));
}
break;
}
}
void Block::setEditorData(std::map<std::string,std::string>& obj){
//Iterator used to check if the map contains certain entries.
map<string,string>::iterator it;
//Check if the data contains the appearance.
it = obj.find("appearance");
if (it != obj.end()) {
std::string newAppearanceName;
if (it->second.empty() || it->second == std::string(Game::blockName[type]) + "_Scenery") {
//Use the default appearance. (Do nothing since newAppearanceName is already empty)
} else {
//Use the custom appearance.
newAppearanceName = it->second;
}
if (newAppearanceName != customAppearanceName) {
//Try to find the custom appearance.
ThemeBlock *themeBlock = NULL;
if (!newAppearanceName.empty()) {
themeBlock = objThemes.getScenery(newAppearanceName);
if (themeBlock == NULL) {
std::cerr << "ERROR: failed to load custom appearance '" << newAppearanceName << "' for block " << Game::blockName[type] << std::endl;
}
} else {
themeBlock = objThemes.getBlock(type);
if (themeBlock == NULL) {
std::cerr << "ERROR: failed to load default appearance for block " << Game::blockName[type] << std::endl;
}
}
if (themeBlock) {
//Update the custom appearance name.
customAppearanceName = newAppearanceName;
//Recreate the theme block instance.
themeBlock->createInstance(&appearance);
//Do some block specific stuff,
//e.g. reset the state according to block type,
//or load some missing part of block states from default appearance.
switch (type) {
case TYPE_FRAGILE:
{
const int f = flags & 0x3;
const char* s = (f == 0) ? "default" : ((f == 1) ? "fragile1" : ((f == 2) ? "fragile2" : "fragile3"));
appearance.changeState(s);
}
break;
case TYPE_BUTTON:
if (appearance.blockStates.find("button") == appearance.blockStates.end()) {
//Try to load the "button" state from default appearance
ThemeBlockInstance defaultAppearance;
objThemes.getBlock(type)->createInstance(&defaultAppearance);
auto it = defaultAppearance.blockStates.find("button");
if (it != defaultAppearance.blockStates.end()) {
appearance.blockStates[it->first] = it->second;
}
}
break;
}
}
}
}
//Check if the data contains the id block.
it=obj.find("id");
if(it!=obj.end()){
//Set the id of the block.
id=obj["id"];
}
//Check if the data contains the visibility
it = obj.find("visible");
if (it != obj.end()) {
//Set the visibility.
const string& s = it->second;
flags = flagsSave = editorFlags = (editorFlags & ~0x80000000) | ((s == "true" || atoi(s.c_str())) ? 0 : 0x80000000);
}
//Block specific properties.
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Make sure that the editor data contains MovingPosCount.
it=obj.find("MovingPosCount");
if(it!=obj.end()){
char s0[64];
int m=atoi(obj["MovingPosCount"].c_str());
movingPos.clear();
for(int i=0;i<m;i++){
SDL_Rect r={0,0,0,0};
sprintf(s0+1,"%d",i);
s0[0]='x';
r.x=atoi(obj[s0].c_str());
s0[0]='y';
r.y=atoi(obj[s0].c_str());
s0[0]='t';
r.w=atoi(obj[s0].c_str());
movingPos.push_back(r);
}
}
//Check if the activated or disabled key is in the data.
//NOTE: 'disabled' is obsolete in V0.5.
it=obj.find("activated");
if(it!=obj.end()){
const string& s=it->second;
editorFlags&=~0x1;
if(!(s=="true" || atoi(s.c_str()))) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}else{
it=obj.find("disabled");
if(it!=obj.end()){
const string& s=it->second;
editorFlags&=~0x1;
if(s=="true" || atoi(s.c_str())) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}
}
//Check if the loop key is in the data.
it=obj.find("loop");
if(it!=obj.end()){
const string& s=it->second;
editorFlags |= 0x2;
if (s == "true" || atoi(s.c_str())) editorFlags &= ~0x2;
flags = flagsSave = editorFlags;
}
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
{
//Check if there's a speed key in the editor data.
//NOTE: 'speed' is obsolete in V0.5.
it=obj.find("speed10");
if(it!=obj.end()){
editorSpeed=atoi(it->second.c_str());
speed=speedSave=editorSpeed;
}else{
it = obj.find("speed");
if (it != obj.end()){
editorSpeed = atoi(it->second.c_str()) * 10;
speed = speedSave = editorSpeed;
}
}
//Check if the activated or disabled key is in the data.
//NOTE: 'disabled' is obsolete in V0.5.
it=obj.find("activated");
if(it!=obj.end()){
const string& s=it->second;
editorFlags&=~0x1;
if(!(s=="true" || atoi(s.c_str()))) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}else{
it=obj.find("disabled");
if(it!=obj.end()){
const string& s=it->second;
editorFlags&=~0x1;
if(s=="true" || atoi(s.c_str())) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}
}
}
break;
case TYPE_PORTAL:
{
//Check if the automatic key is in the data.
it=obj.find("automatic");
if(it!=obj.end()){
const string& s=it->second;
editorFlags&=~0x1;
if(s=="true" || atoi(s.c_str())) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}
//Check if the destination key is in the data.
it=obj.find("destination");
if(it!=obj.end()){
destination=it->second;
}
}
break;
case TYPE_BUTTON:
case TYPE_SWITCH:
{
//Check if the behaviour key is in the data.
it=obj.find("behaviour");
if(it!=obj.end()){
const string& s=it->second;
editorFlags&=~0x3;
if(s=="on" || s==_("On")) editorFlags|=1;
else if(s=="off" || s==_("Off")) editorFlags|=2;
flags=flagsSave=editorFlags;
}
}
break;
case TYPE_NOTIFICATION_BLOCK:
{
//Check if the message key is in the data.
it=obj.find("message");
if(it!=obj.end()){
message=it->second;
//Change the characters '\n' to a real \n
while(message.find("\\n")!=string::npos){
message=message.replace(message.find("\\n"),2,"\n");
}
}
}
break;
case TYPE_FRAGILE:
{
//Check if the status is in the data.
it=obj.find("state");
if(it!=obj.end()){
editorFlags=(editorFlags&~0x3)|(atoi(it->second.c_str())&0x3);
flags=flagsSave=editorFlags;
{
const int f = flags & 0x3;
const char* s=(f==0)?"default":((f==1)?"fragile1":((f==2)?"fragile2":"fragile3"));
appearance.changeState(s);
}
}
}
break;
}
}
-std::string Block::getEditorProperty(std::string property){
+std::string Block::getEditorProperty(const std::string& property){
//First get the complete editor data.
vector<pair<string,string> > objMap;
vector<pair<string,string> >::iterator it;
getEditorData(objMap);
//Loop through the entries.
for(it=objMap.begin();it!=objMap.end();++it){
if(it->first==property)
return it->second;
}
//Nothing found.
return "";
}
-void Block::setEditorProperty(std::string property,std::string value){
+void Block::setEditorProperty(const std::string& property, const std::string& value){
//Create a map to hold the property.
std::map<std::string,std::string> editorData;
editorData[property]=value;
//And call the setEditorData method.
setEditorData(editorData);
}
bool Block::loadFromNode(ImageManager&, SDL_Renderer&, TreeStorageNode* objNode){
//Make sure there are enough parameters.
if(objNode->value.size()<3)
return false;
//Load the type and location.
int type = 0;
{
auto it = Game::blockNameMap.find(objNode->value[0]);
if (it != Game::blockNameMap.end()) {
type = it->second;
} else {
cerr << "WARNING: Unknown block type '" << objNode->value[0] << "'!" << endl;
}
}
int x=atoi(objNode->value[1].c_str());
int y=atoi(objNode->value[2].c_str());
int w=50;
int h=50;
if(objNode->value.size()>3)
w=atoi(objNode->value[3].c_str());
if(objNode->value.size()>4)
h=atoi(objNode->value[4].c_str());
//Call the init method.
init(x,y,w,h,type);
//Loop through the attributes as editorProperties.
map<string,string> obj;
for(map<string,vector<string> >::iterator i=objNode->attributes.begin();i!=objNode->attributes.end();++i){
if(i->second.size()>0) obj[i->first]=i->second[0];
}
setEditorData(obj);
//Loop through the subNodes.
for(unsigned int i=0;i<objNode->subNodes.size();i++){
//FIXME: Ugly variable naming.
TreeStorageNode* obj=objNode->subNodes[i];
if(obj==NULL) continue;
//Check for a script block.
if(obj->name=="script" && !obj->value.empty()){
map<string,int>::iterator it=Game::gameObjectEventNameMap.find(obj->value[0]);
if(it!=Game::gameObjectEventNameMap.end()){
int eventType=it->second;
const std::string& script=obj->attributes["script"][0];
if(!script.empty()) scripts[eventType]=script;
}
}
}
return true;
}
void Block::move(){
bool isPlayMode = stateID != STATE_LEVEL_EDITOR || dynamic_cast<LevelEditor*>(parent)->isPlayMode();
//Make sure we are visible, if not return.
if ((flags & 0x80000000) != 0 && isPlayMode)
return;
//First update the animation of the appearance.
appearance.updateAnimation();
//Block specific move code.
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
//Only move block when we are in play mode.
if (isPlayMode) {
//Make sure the block is enabled, if so increase the time.
if(!(flags&0x1)) temp++;
int t=temp;
SDL_Rect r0={0,0,0,0},r1;
dx=0;
dy=0;
//Loop through the moving positions.
for(unsigned int i=0;i<movingPos.size();i++){
r1.x=movingPos[i].x;
r1.y=movingPos[i].y;
r1.w=movingPos[i].w;
if(t==0&&r1.w==0){ // time == 0 means the block deactivates at this point automatically
r1.w=1;
flags|=0x1;
}
if(t>=0 && t<(int)r1.w){
int newX=boxBase.x+(int)(float(r0.x)+(float(r1.x)-float(r0.x))*float(t)/float(r1.w));
int newY=boxBase.y+(int)(float(r0.y)+(float(r1.y)-float(r0.y))*float(t)/float(r1.w));
//Calculate the delta and velocity.
xVel=dx=newX-box.x;
yVel=dy=newY-box.y;
//Set the new location of the moving block.
box.x=newX;
box.y=newY;
return;
} else if (t == (int)r1.w && i == movingPos.size() - 1) {
//If the time is the time of the movingPosition then set it equal to the location.
//We do this to prevent a slight edge between normal blocks and moving blocks.
int newX=boxBase.x+r1.x;
int newY=boxBase.y+r1.y;
xVel=dx=newX-box.x;
yVel=dy=newY-box.y;
box.x=newX;
box.y=newY;
return;
}
t-=r1.w;
r0.x=r1.x;
r0.y=r1.y;
}
//Only reset the stuff when we're looping.
if((flags & 0x2) == 0){
//Set the time back to zero.
temp=0;
//Calculate the delta movement.
if(!movingPos.empty() && movingPos.back().x==0 && movingPos.back().y==0){
dx=boxBase.x-box.x;
dy=boxBase.y-box.y;
}
//Set the movingblock back to it's initial location.
box.x=boxBase.x;
box.y=boxBase.y;
} else {
//Reached the end, but not looping
xVel=yVel=dx=dy=0;
}
}
break;
case TYPE_BUTTON:
//Only move block when we are in play mode.
if (isPlayMode) {
//Check the third bit of flags to see if temp changed.
int new_flags=temp?4:0;
if((flags^new_flags)&4){
//The button has been pressed or unpressed so change the third bit on flags.
flags=(flags&~4)|new_flags;
if(parent && (new_flags || (flags&3)==0)){
//Make sure that id isn't empty.
if(!id.empty()){
parent->broadcastObjectEvent(0x10000|(flags&3),-1,id.c_str());
}else{
cerr<<"WARNING: invalid button id!"<<endl;
}
}
}
temp=0;
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
//NOTE: we update conveyor belt animation even in edit mode.
//Increase the conveyor belt animation.
if((flags&1)==0){
//Since now 1 speed = 0.1 pixel/s we need some more sophisticated calculation.
int a = animation + speed, d = 0;
if (a < 0) {
//Add a delta value to make it positive
d = (((-a) / 500) + 1) * 500;
}
//Set the velocity NOTE This isn't the actual velocity of the block, but the speed of the player/shadow standing on it.
xVel = (a + d) / 10 - (animation + d) / 10;
//Update animation value
animation = (a + d) % 500;
assert(animation >= 0);
} else {
//Clear the velocity NOTE This isn't the actual velocity of the block, but the speed of the player/shadow standing on it.
xVel = 0;
}
break;
case TYPE_PUSHABLE:
//Only move block when we are in play mode.
if (isPlayMode) {
//Update the vertical velocity, horizontal is set by the player.
if(inAir==true){
yVel+=1;
//Cap fall speed to 13.
if(yVel>13)
yVel=13;
}
if(objCurrentStand!=NULL){
//Now get the velocity and delta of the object the player is standing on.
SDL_Rect v=objCurrentStand->getBox(BoxType_Velocity);
SDL_Rect delta=objCurrentStand->getBox(BoxType_Delta);
switch(objCurrentStand->type){
//For conveyor belts the velocity is transfered.
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
{
xVelBase+=v.x;
}
break;
//In other cases, such as, player on shadow, player on crate... the change in x position must be considered.
default:
{
if(delta.x != 0)
xVelBase+=delta.x;
}
break;
}
//NOTE: Only copy the velocity of the block when moving down.
//Upwards is automatically resolved before the player is moved.
if(delta.y>0)
yVelBase=delta.y;
else
yVelBase=0;
}
//Set the object the player is currently standing to NULL.
objCurrentStand=NULL;
//Store the location of the player.
int lastX=box.x;
int lastY=box.y;
//An array that will hold all the GameObjects that are involved in the collision/movement.
vector<Block*> objects;
//All the blocks have moved so if there's collision with the player, the block moved into him.
- for(unsigned int o=0;o<parent->levelObjects.size();o++){
+ for(auto o : parent->levelObjects){
//Make sure to only check visible blocks.
- if(parent->levelObjects[o]->flags & 0x80000000)
+ if(o->flags & 0x80000000)
continue;
//Make sure we aren't the block.
- if(parent->levelObjects[o]==this)
+ if(o==this)
continue;
- //Make sure the object is solid for the player.
- if(!parent->levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,&parent->player))
+ //Make sure the object is spike or solid for the player.
+ if(o->type!=TYPE_SPIKES && o->type!=TYPE_MOVING_SPIKES && !o->queryProperties(GameObjectProperty_PlayerCanWalkOn,&parent->player))
continue;
//Check for collision.
- if(checkCollision(box,parent->levelObjects[o]->getBox()))
- objects.push_back(parent->levelObjects[o]);
+ if(checkCollision(box,o->getBox()))
+ objects.push_back(o);
}
//There was collision so try to resolve it.
if(!objects.empty()){
//FIXME: When multiple moving blocks are overlapping the pushable can be "bounced" off depending on the block order.
- for(unsigned int o=0;o<objects.size();o++){
- SDL_Rect r=objects[o]->getBox();
- SDL_Rect delta=objects[o]->getBox(BoxType_Delta);
+ for(auto o : objects){
+ SDL_Rect r=o->getBox();
+ SDL_Rect delta=o->getBox(BoxType_Delta);
//Check on which side of the box the pushable is.
if(delta.x!=0){
if(delta.x>0){
//Move the pushable right if necessary.
if((r.x+r.w)-box.x<=delta.x && box.x<r.x+r.w)
box.x=r.x+r.w;
}else{
//Move the pushable left if necessary.
if((box.x+box.w)-r.x<=-delta.x && box.x>r.x-box.w)
box.x=r.x-box.w;
}
}
if(delta.y!=0){
if(delta.y>0){
//Move the pushable down if necessary.
if((r.y+r.h)-box.y<=delta.y && box.y<r.y+r.h)
box.y=r.y+r.h;
}else{
//Move the pushable up if necessary.
if((box.y+box.h)-r.y<=-delta.y && box.y>r.y-box.h)
box.y=r.y-box.h;
}
}
}
}
//Reuse the objects array, this time for blocks the block moves into.
objects.clear();
//Determine the collision frame.
SDL_Rect frame={box.x,box.y,box.w,box.h};
//Keep the horizontal movement of the block in mind.
if(xVel+xVelBase>=0){
frame.w+=(xVel+xVelBase);
}else{
frame.x+=(xVel+xVelBase);
frame.w-=(xVel+xVelBase);
}
//And the vertical movement.
if(yVel+yVelBase>=0){
frame.h+=(yVel+yVelBase);
}else{
frame.y+=(yVel+yVelBase);
frame.h-=(yVel+yVelBase);
}
//Loop through the game objects.
- for(unsigned int o=0; o<parent->levelObjects.size(); o++){
+ for(auto o : parent->levelObjects){
//Make sure the object is visible.
- if(parent->levelObjects[o]->flags & 0x80000000)
+ if(o->flags & 0x80000000)
continue;
//Make sure we aren't the block.
- if(parent->levelObjects[o]==this)
+ if(o==this)
continue;
- //Check if the player can collide with this game object.
- if(!parent->levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,&parent->player))
+ //Check if the player can collide with this game object, or this object is spike.
+ if(o->type!=TYPE_SPIKES && o->type!=TYPE_MOVING_SPIKES && !o->queryProperties(GameObjectProperty_PlayerCanWalkOn,&parent->player))
continue;
//Check if the block is inside the frame.
- if(checkCollision(frame,parent->levelObjects[o]->getBox()))
- objects.push_back(parent->levelObjects[o]);
+ if(checkCollision(frame,o->getBox()))
+ objects.push_back(o);
}
//Horizontal pass.
if(xVel+xVelBase!=0){
box.x+=xVel+xVelBase;
- for(unsigned int o=0;o<objects.size();o++){
- SDL_Rect r=objects[o]->getBox();
+ for(auto o : objects){
+ SDL_Rect r=o->getBox();
if(!checkCollision(box,r))
continue;
if(xVel+xVelBase>0){
//We came from the left so the right edge of the player must be less or equal than xVel+xVelBase.
if((box.x+box.w)-r.x<=xVel+xVelBase)
box.x=r.x-box.w;
}else{
//We came from the right so the left edge of the player must be greater or equal than xVel+xVelBase.
if(box.x-(r.x+r.w)>=xVel+xVelBase)
box.x=r.x+r.w;
}
}
}
//Some variables that are used in vertical movement.
Block* lastStand=NULL;
inAir=true;
//Vertical pass.
if(yVel+yVelBase!=0){
box.y+=yVel+yVelBase;
- for(unsigned int o=0;o<objects.size();o++){
- SDL_Rect r=objects[o]->getBox();
+ for(auto o : objects){
+ SDL_Rect r=o->getBox();
if(!checkCollision(box,r))
continue;
//Now check how we entered the block (vertically or horizontally).
if(yVel+yVelBase>0){
//We came from the top so the bottom edge of the player must be less or equal than yVel+yVelBase.
if((box.y+box.h)-r.y<=yVel+yVelBase){
//NOTE: lastStand is handled later since the player can stand on only one block at the time.
//Check if there's already a lastStand.
if(lastStand){
//There is one, so check 'how much' the player is on the blocks.
- SDL_Rect r=objects[o]->getBox();
+ SDL_Rect r=o->getBox();
int w=0;
if(box.x+box.w>r.x+r.w)
w=(r.x+r.w)-box.x;
else
w=(box.x+box.w)-r.x;
//Do the same for the other box.
r=lastStand->getBox();
int w2=0;
if(box.x+box.w>r.x+r.w)
w2=(r.x+r.w)-box.x;
else
w2=(box.x+box.w)-r.x;
//NOTE: It doesn't matter which block the player is on if they are both stationary.
- SDL_Rect v=objects[o]->getBox(BoxType_Velocity);
+ SDL_Rect v=o->getBox(BoxType_Velocity);
SDL_Rect v2=lastStand->getBox(BoxType_Velocity);
if(v.y==v2.y){
if(w>w2)
- lastStand=objects[o];
+ lastStand=o;
}else if(v.y<v2.y){
- lastStand=objects[o];
+ lastStand=o;
}
}else{
- lastStand=objects[o];
+ lastStand=o;
}
}
}else{
//We came from the bottom so the upper edge of the player must be greater or equal than yVel+yVelBase.
if(box.y-(r.y+r.h)>=yVel+yVelBase){
box.y=r.y+r.h;
yVel=0;
}
}
}
}
if(lastStand){
inAir=false;
yVel=1;
SDL_Rect r=lastStand->getBox();
box.y=r.y-box.h;
}
//Block will currently be standing on whatever it was last standing on.
objCurrentStand=lastStand;
dx=box.x-lastX;
dy=box.y-lastY;
xVel=0;
xVelBase=0;
}
break;
}
}
diff --git a/src/Block.h b/src/Block.h
index 03acfc2..04c7143 100644
--- a/src/Block.h
+++ b/src/Block.h
@@ -1,213 +1,213 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef BLOCK_H
#define BLOCK_H
#include "GameObjects.h"
#include "ThemeManager.h"
#include "ScriptUserData.h"
#include "ScriptExecutor.h"
#include <vector>
#include <SDL.h>
class Game; // ad-hoc
class LevelEditor;
class AddRemoveGameObjectCommand;
class AddRemovePathCommand;
class BlockScriptAPI;
class Block: public GameObject, public ScriptUserClass<'B','L','O','K',Block>{
friend class Game; // ad-hoc
friend class LevelEditor;
friend class AddRemoveGameObjectCommand;
friend class AddRemovePathCommand;
friend class BlockScriptAPI;
private:
//Integer that a block can use for all animation or visual related things.
int animation;
//The save for animation when the state of the block is saved.
int animationSave;
//flags:
//all: 0x80000000=invisible (If it's not visible it will not collide with anything or execute any scripts except for 'onCreate'.)
//moving object: 0x1=disabled 0x2=NOT loop
//button: bit0-1=behavior 0x4=pressed
//switch: bit0-1=behavior
//portal: 0x1=automatic
//fragile: bit0-1 state
//collectible: 0x1=collected
int flags;
//The save for flags when the state of the block is saved.
int flagsSave;
//Temp variables used to keep track of time/state.
int temp;
//The save for temp when the state of the block is saved.
int tempSave;
//Save variables for the current location and size of the block.
SDL_Rect boxSave;
//Delta variables, if the block moves these must be set to the delta movement.
int dx,dy;
int dxSave,dySave;
//Vector containing the poisitions of the moving block.
std::vector<SDL_Rect> movingPos;
//Cached variable for total time ot moving positions. -1 means uninitialized
//NOTE: should reset it when editing movingPos (except for edit mode since it will reset when level starts)
int movingPosTime;
//Integer containing the speed for conveyorbelts.
//NOTE: in V0.5 the speed 1 means 0.1 pixel/frame = 0.08 block/s
//which is 1/10 of the old speed, and named "speed10" in the level file to keep compatibility
int speed;
int speedSave;
int editorSpeed;
//Following is for the pushable block.
Block* objCurrentStand;
Block* objCurrentStandSave;
//Flags of the block for the editor.
int editorFlags;
public:
// The custom appearance name, whose meaning is the same as Scenery::sceneryName_. "" means using default one
std::string customAppearanceName;
//The Appearance of the block.
ThemeBlockInstance appearance;
//Velocity variables for the block, if the block moves these must be set for collision/movement of the player.
int xVel,yVel;
//Save variables for the velocity.
int xVelSave,yVelSave;
//Follwing is for pushable block.
bool inAir;
int xVelBase,yVelBase;
//The save variables for each of the above.
bool inAirSave;
int xVelBaseSave,yVelBaseSave;
//The id of the block.
std::string id;
//String containing the id of the destination for portals.
std::string destination;
//String containing the message of the notification block.
std::string message;
//The map that holds a script for every event.
map<int,std::string> scripts;
//Compiled scripts. Use lua_rawgeti(L, LUA_REGISTRYINDEX, r) to get the function.
std::map<int, int> compiledScripts, savedCompiledScripts, initialCompiledScripts;
//Constructor.
//objParent: Pointer to the Game object.
//x: The x location of the block.
//y: The y location of the block.
//w: The width of the block.
//h: The height of the block.
//type: The block type.
Block(Game* objParent,int x=0,int y=0,int w=50,int h=50,int type=-1);
//Desturctor.
~Block();
//Method for initializing the block.
//x: The x location of the block.
//y: The y location of the block.
//w: The width of the block.
//h: The height of the block.
//type: The block type.
void init(int x,int y,int w,int h,int type);
//Method used to draw the block.
void show(SDL_Renderer &renderer) override;
//Returns the box of a given type.
//boxType: The type of box that should be returned.
//See GameObjects.h for the types.
//Returns: The box.
virtual SDL_Rect getBox(int boxType=BoxType_Current) override;
//Method for setting the block to a given location as if it moved there.
//x: The new x location.
//y: The new y location.
void moveTo(int x,int y);
//Method for setting a new size as if the block grew,
//w: The new width of the block.
//h: The new height of the block.
void growTo(int w,int h);
//Save the state of the block so we can load it later on.
virtual void saveState() override;
//Load the saved state of the block so.
virtual void loadState() override;
//Reset the block.
//save: Boolean if the saved state should also be deleted.
virtual void reset(bool save) override;
//Play an animation.
virtual void playAnimation() override;
//Method called when there's an event.
//eventType: The type of event.
//See GameObjects.h for the eventtypes.
virtual void onEvent(int eventType) override;
//Method used to retrieve a property from the block.
//propertyType: The type of property requested.
//See GameObjects.h for the properties.
//obj: Pointer to the player.
//Returns: Integer containing the value of the property.
virtual int queryProperties(int propertyType,Player* obj) override;
//Get the editor data of the block.
//obj: The vector that will be filled with the editorData.
virtual void getEditorData(std::vector<std::pair<std::string,std::string> >& obj) override;
//Set the editor data of the block.
//obj: The new editor data.
virtual void setEditorData(std::map<std::string,std::string>& obj) override;
//Get a single property of the block.
//property: The property to return.
//Returns: The value for the requested property.
- virtual std::string getEditorProperty(std::string property) override;
+ virtual std::string getEditorProperty(const std::string& property) override;
//Set a single property of the block.
//property: The property to set.
//value: The new value for the property.
- virtual void setEditorProperty(std::string property,std::string value) override;
+ virtual void setEditorProperty(const std::string& property, const std::string& value) override;
//Method for loading the Block from a node.
//objNode: Pointer to the storage node to load from.
//Returns: True if it succeeds without errors.
virtual bool loadFromNode(ImageManager&, SDL_Renderer&, TreeStorageNode* objNode) override;
//Method used for updating moving blocks or elements of blocks.
virtual void move() override;
//Get total time ot moving positions.
int getPathMaxTime();
};
#endif
diff --git a/src/CommandManager.cpp b/src/CommandManager.cpp
index c7bbc65..631841d 100644
--- a/src/CommandManager.cpp
+++ b/src/CommandManager.cpp
@@ -1,154 +1,154 @@
/*
* Copyright (C) 2018 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me And My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
// An undo/redo system based on <http://www.codeproject.com/Articles/2500/A-Basic-Undo-Redo-Framework-For-C>.
// Originally written by squarecross <https://forum.freegamedev.net/viewtopic.php?f=48&t=5432>.
#include "CommandManager.h"
#include "Commands.h"
#include "Functions.h"
#include "libs/tinyformat/tinyformat.h"
#include <iostream>
bool CommandManager::canUndo() const {
return currentCommand > 0;
}
bool CommandManager::canRedo() const {
return currentCommand < (int)undoList.size();
}
std::string CommandManager::describeUndo() {
if (canUndo()) {
- return tfm::format(_("Undo %s"), undoList[currentCommand - 1]->describe());
+ return tfm::format(_("Undo %s"), undoList[currentCommand - 1]->describe()) + " (Ctrl+Z)";
} else {
return _("Can't undo");
}
}
std::string CommandManager::describeRedo() {
if (canRedo()) {
- return tfm::format(_("Redo %s"), undoList[currentCommand]->describe());
+ return tfm::format(_("Redo %s"), undoList[currentCommand]->describe()) + " (Ctrl+Y)";
} else {
return _("Can't redo");
}
}
int CommandManager::getUndoLevel() const {
return undoLevel;
}
void CommandManager::setUndoLevel(int newValue) {
undoLevel = newValue;
// check if the current undo list is too big
const int n = undoList.size() - undoLevel;
if (n > 0) {
currentCommand -= n;
pivot = (pivot < n) ? -1 : (pivot - n);
for (int i = 0; i < n; i++) {
delete undoList[i];
}
undoList.erase(undoList.begin(), undoList.begin() + n);
}
}
bool CommandManager::isChanged() const {
return currentCommand != pivot;
}
void CommandManager::doCommand(Command* command) {
// clear the redo list
if (currentCommand < (int)undoList.size()) {
if (pivot > currentCommand) pivot = -1;
for (int i = currentCommand; i < (int)undoList.size(); i++) {
delete undoList[i];
}
undoList.erase(undoList.begin() + currentCommand, undoList.end());
}
// debug
#ifdef _DEBUG
std::cout << "Do command: " << command->describe() << std::endl;
#endif
// Execute the command and add it to undo list
command->execute();
undoList.push_back(command);
currentCommand++;
// check if the current undo list is too big
setUndoLevel(undoLevel);
}
void CommandManager::undo() {
if (canUndo()) {
//Move the pointer
currentCommand--;
//Gets the command in undoList.
Command* command = undoList[currentCommand];
// debug
#ifdef _DEBUG
std::cout << "Undo command: " << command->describe() << std::endl;
#endif
//undoing the command
command->unexecute();
}
}
void CommandManager::redo() {
if (canRedo()) {
//Gets the command in undoList.
Command* command = undoList[currentCommand];
// debug
#ifdef _DEBUG
std::cout << "Redo command: " << command->describe() << std::endl;
#endif
//redoing the command
command->execute();
//Move the pointer
currentCommand++;
}
}
void CommandManager::destroy() {
for (int i = 0; i < (int)undoList.size(); i++) {
delete undoList[i];
}
undoList.clear();
currentCommand = 0;
pivot = -1;
}
void CommandManager::resetChange() {
pivot = currentCommand;
}
diff --git a/src/EasterEggScreen.cpp b/src/EasterEggScreen.cpp
new file mode 100644
index 0000000..c73c67c
--- /dev/null
+++ b/src/EasterEggScreen.cpp
@@ -0,0 +1,431 @@
+/*
+* Copyright (C) 2018 Me and My Shadow
+*
+* This file is part of Me and My Shadow.
+*
+* Me and My Shadow 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 3 of the License, or
+* (at your option) any later version.
+*
+* Me And My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "EasterEggScreen.h"
+#include "SoundManager.h"
+#include "Render.h"
+#include "Globals.h"
+#include "Functions.h"
+#include "InputManager.h"
+#include <SDL_mixer.h>
+#include <SDL_ttf.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <string>
+#include <map>
+
+//Define this to enable fake "ls -la" screen.
+#define SCAM
+
+#ifdef SCAM
+
+const unsigned int randMaxPlusOne = (unsigned int)(RAND_MAX) + 1;
+
+struct FakeSoundEffectState {
+ Sint16 buffer[4096];
+ int state;
+ int randContext;
+
+ int rand() {
+ randContext = (randContext * 1103515245 + 12345) & 0x7FFFFFFF;
+ return ((randContext << 15) | (randContext >> 16)) & RAND_MAX;
+ }
+};
+
+static void SDLCALL fakeSoundEffect(void *udata, Uint8 *stream_, int len) {
+ FakeSoundEffectState *state = (FakeSoundEffectState*)udata;
+ Sint16 *stream = (Sint16*)stream_;
+
+ int len2 = len / 2; // NOTE: len is in bytes, not in samples !!!
+ int len3 = len2;
+ if (len3 > sizeof(state->buffer) / sizeof(state->buffer[0])) {
+ len3 = sizeof(state->buffer) / sizeof(state->buffer[0]);
+ }
+
+ if (state->state & 0xFF) {
+ state->state--;
+ } else {
+ state->state = int(float(state->rand()) / float(randMaxPlusOne) * 9.0f) << 8;
+ if (state->state) state->state |= int(float(state->rand()) / float(randMaxPlusOne) * 10.0f);
+ else state->state |= int(float(state->rand()) / float(randMaxPlusOne) * 80.0f);
+
+ switch (state->state >> 8) {
+ case 1:
+ memcpy(state->buffer, stream, len3 * sizeof(Sint16));
+ break;
+ }
+ }
+
+ int i;
+
+ switch (state->state >> 8) {
+ case 0:
+ break;
+ case 1:
+ memcpy(stream, state->buffer, len3 * sizeof(Sint16));
+ break;
+ case 2:
+ for (i = 0; i < len2; i++) {
+ stream[i] = stream[i] >= 0 ? 0x7FFF : 0x8000;
+ }
+ break;
+ case 3:
+ for (i = 1; i < len2; i++) {
+ stream[i] ^= stream[i - 1];
+ }
+ break;
+ case 4:
+ for (i = 0; i < len2 / 2; i++) {
+ std::swap(stream[i], stream[len2 - 1 - i]);
+ }
+ break;
+ case 5:
+ for (i = 0; i < len2; i++) {
+ bool b = (stream[i] & 0x1000) != 0;
+ if (stream[i] < 0) b = !b;
+ stream[i] <<= 3;
+ if (b) stream[i] = ~stream[i];
+ }
+ break;
+ case 6:
+ memcpy(state->buffer, stream, len3 * sizeof(Sint16));
+ for (i = 0; i < len3; i += 2) {
+ stream[i / 2] = state->buffer[i];
+ }
+ for (i = 1; i < len3; i += 2) {
+ stream[(len3 + i) / 2] = state->buffer[i];
+ }
+ case 7:
+ for (i = 0; i < len2; i++) {
+ float f = float(stream[i]);
+ stream[i] = Sint32(f * f * f / 1073741824.0f);
+ }
+ break;
+ case 8:
+ for (i = 0; i < len2; i++) {
+ stream[i] = 0;
+ }
+ break;
+ }
+}
+
+FakeSoundEffectState fakeSoundEffectState;
+
+static void scamDrawText(ImageManager& imageManager, SDL_Renderer& renderer,
+ const std::map<int, TexturePtr>& cache,
+ int fontWidth, int fontHeight,
+ int x, int y, const char* text)
+{
+ int color = 0, x0 = x;
+
+ for (int i = 0;; i++) {
+ int c = (int)(unsigned char)text[i];
+ if (c == 0) break;
+ if (c == '\n') {
+ x = x0;
+ y += fontHeight;
+ } else if (c == ' ') {
+ x += fontWidth;
+ } else if (c >= 0x10 && c < 0x20) {
+ color = c & 0xF;
+ } else {
+ const int key = c | (color << 8);
+ auto it = cache.find(key);
+ if (it != cache.end()) {
+ applyTexture(x, y, const_cast<TexturePtr&>(it->second), renderer);
+ x += fontWidth;
+ }
+ }
+ }
+}
+
+//Show a fake never-ending "ls -la" screen unless the user press Ctrl+C.
+bool easterEggScreen(ImageManager& imageManager, SDL_Renderer& renderer) {
+ //Some colors.
+ SDL_Color colors[] = {
+ { 0xC0, 0xC0, 0xC0, 0xFF }, //lightgray
+ { 0x00, 0xFF, 0x00, 0xFF }, //green
+ { 0x00, 0x00, 0xFF, 0xFF }, //blue
+ { 0x00, 0xFF, 0xFF, 0xFF }, //cyan
+ { 0xFF, 0x00, 0xFF, 0xFF }, //magenta
+ };
+ const int numberOfColors = sizeof(colors) / sizeof(colors[0]);
+
+ int fontWidth = 0;
+ TTF_GlyphMetrics(fontMono, 'W', NULL, NULL, NULL, NULL, &fontWidth);
+
+ //Initialize some textures.
+ std::map<int, TexturePtr> cache;
+ for (int i = 0; i < numberOfColors; i++) {
+ for (int c = 33; c <= 126; c++) {
+ const char s[2] = { (char)c, 0 };
+ const int key = c | (i << 8);
+ cache[key] = textureFromText(renderer, *fontMono, s, colors[i]);
+ }
+ }
+
+ const char* extensions[] = {
+ "\x11sh", "\x11py",
+ "\x14png", "\x14jpg",
+ "txt", "c", "cpp", "h", "map", "lst", "lua",
+ };
+ const int numberOfExtensions = sizeof(extensions) / sizeof(extensions[0]);
+
+ const char* users[] = {
+ "root", "user", "me", "shadow"
+ };
+ const int numberOfUsers = sizeof(users) / sizeof(users[0]);
+
+ const char* months[12] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ };
+
+ const int numOfDays[12] = {
+ 31, 28, 31, 30, 31, 30,
+ 31, 31, 30, 31, 30, 31,
+ };
+
+ const int fontHeight = TTF_FontHeight(fontMono);
+
+ //Keep the last resize event, this is to only process one.
+ SDL_Event lastResize = {};
+
+ bool ret = false, isRunning = true;
+
+ char s0[72] = {}, s1[72] = {}, s2[256] = {};
+ for (int i = 0; i < 60; i++) {
+ s0[i] = int(float(rand()) / float(randMaxPlusOne) * 15.0f);
+ }
+
+ if (getSettings()->getBoolValue("music")) {
+ fakeSoundEffectState.state = int(float(rand()) / float(randMaxPlusOne) * 80.0f);
+ fakeSoundEffectState.randContext = rand() ^ (rand() << 16);
+ Mix_SetPostMix(fakeSoundEffect, &fakeSoundEffectState);
+ }
+
+ for (int t = 0; isRunning; t++) {
+ while (SDL_PollEvent(&event)) {
+ //Check if we need to quit, if so enter the exit state.
+ if (event.type == SDL_QUIT){
+ setNextState(STATE_EXIT);
+ isRunning = false;
+ }
+
+ //Check for a resize event.
+ if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
+ lastResize = event;
+ continue;
+ }
+
+ //Check Ctrl+C.
+ if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_c
+ && (event.key.keysym.mod & KMOD_CTRL) != 0 && (event.key.keysym.mod & ~KMOD_CTRL) == 0)
+ {
+ ret = true;
+ isRunning = false;
+ }
+
+ //Set the cursor type to the default one, the GUI can change that if needed.
+ currentCursor = CURSOR_POINTER;
+
+ //Let the input manager handle the events.
+ inputMgr.updateState(true);
+ }
+
+ //Process the resize event.
+ if (lastResize.type == SDL_WINDOWEVENT){
+ //TODO - used to be SDL_VIDEORESIZE
+ // so this may trigger on more events than intended
+ event = lastResize;
+ onVideoResize(imageManager, renderer);
+
+ //After resize we erase the event type
+ //TODO - used to be SDL_NOEVENT
+ lastResize.type = SDL_FIRSTEVENT;
+ }
+
+ //update input state (??)
+ inputMgr.updateState(false);
+
+ //Don't update the screen when the sound glitches.
+ if (fakeSoundEffectState.state >> 8) {
+ SDL_Delay(1000 / FPS);
+ continue;
+ }
+
+ //Clear the screen.
+ SDL_SetRenderDrawColor(&renderer, 0, 0, 0, 255);
+ SDL_RenderClear(&renderer);
+
+ const int rows = SCREEN_HEIGHT / fontHeight - 1;
+
+ for (int row = 0; row < rows; row++) {
+ //Generate some random text.
+ int m = int(float(rand()) / float(randMaxPlusOne) * 56.0f);
+ int carry = 0;
+ for (int i = 0; i < 60; i++) {
+ int c = s0[59 - i] + carry;
+ if (i <= m) c += int(float(rand()) / float(randMaxPlusOne) * 15.0f);
+ carry = 0;
+ while (c >= 15) {
+ c -= 15;
+ carry++;
+ }
+ s0[59 - i] = c;
+ }
+
+ int lp;
+ for (lp = 0; s0[lp]; lp++) {
+ char c = s0[lp] - 4;
+ if (c >= 0 && c < 10) {
+ s1[lp] = c + '0';
+ } else if (c == 10) {
+ s1[lp] = '_';
+ } else {
+ break;
+ }
+ }
+ s1[lp] = 0;
+ if (lp < 60) s0[lp] = 4;
+
+ //Choose a random extension.
+ int extension = int(float(rand()) / float(randMaxPlusOne) * 20.0f);
+ const char* ext = NULL;
+ int color = 0;
+ if (extension < numberOfExtensions) {
+ ext = extensions[extension];
+ if (ext[0] >= 0x10 && ext[0] < 0x20) {
+ color = ext[0] & 0xF;
+ ext++;
+ }
+ }
+ if (ext) {
+ s1[lp] = '.';
+ s1[lp + 1] = 0;
+ strcat(s1 + lp, ext);
+ lp = strlen(s1);
+ }
+
+ //Choose a random color.
+ if (extension >= numberOfExtensions) {
+ int r = int(float(rand()) / float(randMaxPlusOne) * 10.0f);
+ if (r <= 2) color = r;
+ }
+
+ bool isLink = int(float(rand()) / float(randMaxPlusOne) * 10.0f) == 0;
+
+ //Choose a random user.
+ const char* user = users[int(float(rand()) / float(randMaxPlusOne) * float(numberOfUsers))];
+
+ //Choose a random permission
+ const char* permission = NULL;
+ switch (int(float(rand()) / float(randMaxPlusOne) * 10.0f)) {
+ case 0:
+ permission = (color == 1 || color == 2) ? "rwxrwxrwx" : "rw-rw-rw-";
+ break;
+ case 1:
+ permission = (color == 1 || color == 2) ? "rwx------" : "rw-------";
+ break;
+ default:
+ permission = (color == 1 || color == 2) ? "rwxr-xr-x" : "rw-r--r--";
+ break;
+ }
+
+ //Choose a random size
+ int size = 0;
+ if (isLink) {
+ size = 3 + lp;
+ } else if (color == 2) {
+ size = 4096;
+ } else {
+ size = int(float(rand()) / float(randMaxPlusOne) * 10000.0f);
+ }
+
+ int num = 1;
+ if (color == 2) {
+ num = int(float(rand()) / float(randMaxPlusOne) * 10.0f);
+ }
+
+ //Choose a random date
+ char date[8];
+ {
+ int d = int(float(rand()) / float(randMaxPlusOne) * 365.0f);
+ int m = 0;
+ while (d >= numOfDays[m]) {
+ d -= numOfDays[m];
+ m++;
+ }
+ sprintf(date, "%s %2d", months[m], d + 1);
+ }
+
+ //Choose a random year or time
+ char year[8];
+ if (int(float(rand()) / float(randMaxPlusOne) * 10.0f) == 0) {
+ sprintf(year, "%d", 1970 + int(float(rand()) / float(randMaxPlusOne) * 100.0f));
+ } else {
+ sprintf(year, "%02d:%02d", int(float(rand()) / float(randMaxPlusOne) * 24.0f), int(float(rand()) / float(randMaxPlusOne) * 60.0f));
+ }
+
+ //Put them together
+ if (isLink) {
+ sprintf(s2, "l%s %d %-6s %-6s %4d %s %5s \x13.%s\x10 -> %c../%s",
+ permission, num, user, user, size, date, year, s1, 0x10 + color, s1
+ );
+ } else {
+ sprintf(s2, "%c%s %d %-6s %-6s %4d %s %5s %c.%s",
+ color == 2 ? 'd' : '-', permission, num, user, user, size, date, year, 0x10 + color, s1
+ );
+ }
+
+ //Show text
+ scamDrawText(imageManager, renderer, cache, fontWidth, fontHeight, 0, row * fontHeight, s2);
+ }
+
+ //Show a caret.
+ if (t & 0x10) {
+ SDL_Rect r = { 0, rows * fontHeight, fontWidth, fontHeight };
+ SDL_SetRenderDrawColor(&renderer, 0x80, 0xFF, 0, 0xFF);
+ SDL_RenderDrawRect(&renderer, &r);
+ }
+
+ //display it
+ flipScreen(renderer);
+ SDL_Delay(1000 / FPS);
+ }
+
+ Mix_SetPostMix(NULL, NULL);
+
+ return ret;
+}
+
+#else
+
+// Only play a sound.
+bool easterEggScreen(ImageManager& imageManager, SDL_Renderer& renderer) {
+ //play a sound effect
+ getSoundManager()->playSound("hit");
+
+ return true;
+}
+
+#endif
diff --git a/src/EasterEggScreen.h b/src/EasterEggScreen.h
new file mode 100644
index 0000000..324a66a
--- /dev/null
+++ b/src/EasterEggScreen.h
@@ -0,0 +1,27 @@
+/*
+* Copyright (C) 2018 Me and My Shadow
+*
+* This file is part of Me and My Shadow.
+*
+* Me and My Shadow 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 3 of the License, or
+* (at your option) any later version.
+*
+* Me And My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef EASTEREGGSCREEN_H
+#define EASTEREGGSCREEN_H
+
+#include "ImageManager.h"
+
+bool easterEggScreen(ImageManager& imageManager, SDL_Renderer& renderer);
+
+#endif
diff --git a/src/GUIListBox.cpp b/src/GUIListBox.cpp
index 0c703d9..ce2d7a5 100644
--- a/src/GUIListBox.cpp
+++ b/src/GUIListBox.cpp
@@ -1,573 +1,578 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Functions.h"
#include "GUIListBox.h"
#include "ThemeManager.h"
using namespace std;
namespace {
inline int tHeight(const SharedTexture& t) {
if(t) {
return rectFromTexture(*t).h;
} else {
return 0;
}
}
inline int tWidth(const SharedTexture& t) {
if(t) {
return rectFromTexture(*t).w;
} else {
return 0;
}
}
}
GUIListBox::GUIListBox(ImageManager& imageManager, SDL_Renderer& renderer,int left,int top,int width,int height,bool enabled,bool visible,int gravity):
GUIObject(imageManager,renderer,left,top,width,height,NULL,-1,enabled,visible,gravity),selectable(true),clickEvents(false){
//Set the state -1.
state=-1;
//Create the scrollbar and add it to the children.
scrollBar=new GUIScrollBar(imageManager,renderer,width-16,0,16,height,1,0,0,0,1,1,true,true);
childControls.push_back(scrollBar);
}
GUIListBox::~GUIListBox(){
//Remove all items and cache.
clearItems();
//We need to delete every child we have.
for(unsigned int i=0;i<childControls.size();i++){
delete childControls[i];
}
//Deleted the childs now empty the childControls vector.
childControls.clear();
}
void GUIListBox::scrollScrollbar(int dy) {
if (scrollBar->enabled && dy) {
scrollBar->value = clamp(scrollBar->value + dy, 0, scrollBar->maxValue);
}
}
void GUIListBox::onResize() {
scrollBar->left = width - 16;
scrollBar->height = height;
updateScrollbar = true;
}
bool GUIListBox::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when he and his parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when he and his parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left;
y+=top;
//Update the scrollbar.
if(scrollBar->visible)
b=b||scrollBar->handleEvents(renderer,x,y,enabled,visible,b);
//Set state negative.
state=-1;
//Check if the GUIListBox is visible, enabled and no event has been processed before.
if(enabled&&visible&&!b){
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j;
SDL_GetMouseState(&i,&j);
//Convert the mouse location to a relative location.
i-=x;
j-=y;
//Check if the mouse is inside the GUIListBox.
if(i>=0&&i<width-4&&j>=0&&j<height-4){
//Calculate selected item.
int idx=-1;
int yPos=-firstItemY;
int i=scrollBar->value;
if(yPos!=0) i--;
for(;i<images.size();++i){
const SharedTexture& tex = images.at(i);
if(tex){
yPos+=tHeight(tex);
}
if(j<yPos){
idx=i;
break;
}
}
//If the entry isn't above the max we have an event.
if (idx >= 0 && idx < (int)item.size() && selectable && itemSelectable[idx]) {
state=idx;
//Check if the left mouse button is pressed.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT){
//Check if the slected item changed.
if(value!=idx){
value=idx;
//If event callback is configured then add an event to the queue.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
//After possibly a change event, there will always be a click event.
if(eventCallback && clickEvents){
GUIEvent e={eventCallback,name,this,GUIEventClick};
GUIEventQueue.push_back(e);
}
}
}
//Check for mouse wheel scrolling.
if (event.type == SDL_MOUSEWHEEL && event.wheel.y && scrollBar->enabled) {
scrollScrollbar(event.wheel.y < 0 ? 1 : -1);
}
}
}
//Process child controls event except for the scrollbar.
//That's why i starts at one.
for(unsigned int i=1;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(renderer,x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
void GUIListBox::render(SDL_Renderer& renderer, int x,int y,bool draw){
if(updateScrollbar){
//Calculate the height of the content.
int maxY=0;
for(const SharedTexture& t: images){
if(t){
maxY+=textureHeight(*t);
} else {
std::cerr << "WARNING: Null texture in GUIListBox!" << std::endl;
}
}
//Check if we need to show the scrollbar for many entries.
if(maxY<height){
scrollBar->maxValue=0;
scrollBar->value=0;
scrollBar->visible=false;
}else{
scrollBar->visible=true;
scrollBar->maxValue=item.size();
int yy=0;
for(int i=images.size()-1;i>0;i--){
yy+=textureHeight(*images.at(i));
if(yy>height)
break;
else
scrollBar->maxValue--;
}
scrollBar->largeChange=item.size()/4;
}
updateScrollbar=false;
}
//Rectangle the size of the GUIObject, used to draw borders.
//SDL_Rect r; //Unused local variable :/
//There's no need drawing the GUIObject when it's invisible.
if(!visible||!draw)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Draw the background box.
const SDL_Rect r={x,y,width,height};
SDL_SetRenderDrawColor(&renderer,255,255,255,220);
SDL_RenderFillRect(&renderer, &r);
firstItemY=0;
//Loop through the entries and draw them.
if(scrollBar->value==scrollBar->maxValue&&scrollBar->visible){
int lowNumber=height;
int currentItem=images.size()-1;
while(lowNumber>=0&&currentItem>=0){
const SharedTexture& currentTexture = images.at(currentItem);
lowNumber-=tHeight(currentTexture);//->h;
if(lowNumber>0){
if(selectable){
//Check if the mouse is hovering on current entry. If so draw borders around it.
if(state==currentItem)
drawGUIBox(x,y+lowNumber-1,width,tHeight(currentTexture)+1,renderer,0x00000000);
//Check if the current entry is selected. If so draw a gray background.
if(value==currentItem)
drawGUIBox(x,y+lowNumber-1,width,tHeight(currentTexture)+1,renderer,0xDDDDDDFF);
}
applyTexture(x,y+lowNumber,*currentTexture,renderer);
}else{
// This is the top item that is partially obscured.
if(selectable){
//Check if the mouse is hovering on current entry. If so draw borders around it.
if(state==currentItem)
drawGUIBox(x,y,width,tHeight(currentTexture)+lowNumber+1,renderer,0x00000000);
//Check if the current entry is selected. If so draw a gray background.
if(value==currentItem)
drawGUIBox(x,y,width,tHeight(currentTexture)+lowNumber+1,renderer,0xDDDDDDFF);
}
firstItemY=-lowNumber;
const SDL_Rect clip{ 0, -lowNumber, textureWidth(*currentTexture), textureHeight(*currentTexture) + lowNumber };
const SDL_Rect dstRect{x, y, clip.w, clip.h};
if (clip.w > 0 && clip.h > 0) SDL_RenderCopy(&renderer, currentTexture.get(), &clip, &dstRect);
break;
}
currentItem--;
}
}else{
for(int i=scrollBar->value,j=y+1;i<(int)item.size();i++){
//Check if the current item is out side of the widget.
int yOver=tHeight(images[i]);
if(j+yOver>y+height)
yOver=y+height-j;
if (yOver > 0){
if (selectable){
//Check if the mouse is hovering on current entry. If so draw borders around it.
if (state == i)
drawGUIBox(x, j - 1, width, yOver + 1, renderer, 0x00000000);
//Check if the current entry is selected. If so draw a gray background.
if (value == i)
drawGUIBox(x, j - 1, width, yOver + 1, renderer, 0xDDDDDDFF);
}
//Draw the image.
const SDL_Rect clip{ 0, 0, tWidth(images[i]), yOver };
const SDL_Rect dstRect{ x, j, clip.w, clip.h };
SDL_RenderCopy(&renderer, images[i].get(), &clip, &dstRect);
} else if (yOver<0) {
break;
}
j+=tHeight(images[i]);
}
}
//Draw borders around the whole thing.
drawGUIBox(x,y,width,height,renderer,0x00000000);
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(renderer,x,y,draw);
}
}
void GUIListBox::clearItems(){
item.clear();
images.clear();
itemSelectable.clear();
}
-void GUIListBox::addItem(SDL_Renderer &renderer, std::string name, SharedTexture texture, bool selectable) {
+void GUIListBox::addItem(SDL_Renderer &renderer, const std::string& name, SharedTexture texture, bool selectable) {
if(texture){
images.push_back(texture);
}else if(!texture&&!name.empty()){
auto tex=SharedTexture(textureFromText(renderer, *fontText, name.c_str(), objThemes.getTextColor(true)));
// Make sure we don't create any empty textures.
if(!tex) {
std::cerr << "WARNING: Failed to create texture from text: \"" << name << "\"" << std::endl;
return;
}
images.push_back(tex);
} else {
// If nothing was added, ignore it.
return;
}
item.push_back(name);
itemSelectable.push_back(selectable);
updateScrollbar=true;
}
-void GUIListBox::updateItem(SDL_Renderer &renderer, int index, string newText, SharedTexture newTexture) {
+void GUIListBox::updateItem(SDL_Renderer &renderer, int index, const string& newText, SharedTexture newTexture) {
+ updateItem(renderer, index, newText, newTexture, itemSelectable[index]);
+}
+
+void GUIListBox::updateItem(SDL_Renderer &renderer, int index, const string& newText, SharedTexture newTexture, bool selectable) {
if(newTexture) {
images.at(index) = newTexture;
} else if (!newTexture&&!newText.empty()) {
auto tex=SharedTexture(textureFromText(renderer, *fontText, newText.c_str(), objThemes.getTextColor(true)));
// Make sure we don't create any empty textures.
if(!tex) {
std::cerr << "WARNING: Failed to update texture at index" << index << " \"" << newText << "\"" << std::endl;
return;
}
images.at(index)=tex;
} else {
return;
}
item.at(index)=newText;
+ itemSelectable.at(index) = selectable;
updateScrollbar=true;
}
std::string GUIListBox::getItem(int index){
return item.at(index);
}
GUISingleLineListBox::GUISingleLineListBox(ImageManager& imageManager, SDL_Renderer& renderer, int left, int top, int width, int height, bool enabled, bool visible, int gravity):
GUIObject(imageManager,renderer,left,top,width,height,NULL,-1,enabled,visible,gravity),animation(0){}
void GUISingleLineListBox::addItem(string name,string label){
//Check if the label is set, if not use the name.
if(label.size()==0)
label=name;
item.push_back(pair<string,string>(name,label));
}
void GUISingleLineListBox::addItems(vector<pair<string,string> > items){
vector<pair<string,string> >::iterator it;
for(it=items.begin();it!=items.end();++it){
addItem(it->first,it->second);
}
}
void GUISingleLineListBox::addItems(vector<string> items){
vector<string>::iterator it;
for(it=items.begin();it!=items.end();++it){
addItem(*it);
}
}
string GUISingleLineListBox::getName(unsigned int index){
if(index==-1)
index=value;
if(index<0||index>item.size())
return "";
return item[index].first;
}
bool GUISingleLineListBox::handleEvents(SDL_Renderer&,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when he and his parent(s) are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when he and his parent(s) are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
state&=~0xF;
if(enabled&&visible){
//Only process mouse event when not in keyboard only mode
if (!isKeyboardOnly) {
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Convert the mouse location to a relative location.
i -= x;
j -= y;
//The selected button.
//0=nothing 1=left 2=right.
int idx = 0;
//Check which button the mouse is above.
if (i >= 0 && i < width&&j >= 0 && j < height){
if (i < 26 && i < width / 2){
//The left arrow.
idx = 1;
} else if (i >= width - 26){
//The right arrow.
idx = 2;
}
}
//If idx is 0 it means the mous doesn't hover any arrow so reset animation.
if (idx == 0)
animation = 0;
//Check if there's a mouse button press or not.
if (k&SDL_BUTTON(1)){
if (((state >> 4) & 0xF) == idx)
state |= idx;
} else{
state |= idx;
}
//Check if there's a mouse press.
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT && idx){
state = idx | (idx << 4);
} else if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT && idx && ((state >> 4) & 0xF) == idx){
int m = (int)item.size();
if (m > 0){
if (idx == 2){
idx = value + 1;
if (idx < 0 || idx >= m) idx = 0;
if (idx != value){
value = idx;
//If there is an event callback then call it.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventClick };
GUIEventQueue.push_back(e);
}
}
} else if (idx == 1){
idx = value - 1;
if (idx < 0 || idx >= m) idx = m - 1;
if (idx != value){
value = idx;
//If there is an event callback then call it.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventClick };
GUIEventQueue.push_back(e);
}
}
}
}
}
if (event.type == SDL_MOUSEBUTTONUP) state &= 0xF;
}
}else{
//Set state zero.
state=0;
}
return b;
}
void GUISingleLineListBox::render(SDL_Renderer& renderer, int x,int y,bool draw){
//Rectangle the size of the GUIObject, used to draw borders.
SDL_Rect r;
//There's no need drawing the GUIObject when it's invisible.
if(!visible)
return;
//NOTE: logic in the render method since it's the only part that gets called every frame.
if (!isKeyboardOnly) {
if ((state & 0xF) == 0x1 || (state & 0xF) == 0x2){
animation++;
if (animation > 20)
animation = -20;
}
}
//Get the absolute x and y location.
x+=left;
y+=top;
gravityX = 0;
if(gravity==GUIGravityCenter)
gravityX=int(width/2);
else if(gravity==GUIGravityRight)
gravityX=width;
x-=gravityX;
if (isKeyboardOnly && state && draw) {
drawGUIBox(x, y, width, height, renderer, 0xFFFFFF40);
}
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
if(enabled!=cachedEnabled || item[value].second.compare(cachedCaption)!=0){
//Free the cache.
cacheTex.reset(nullptr);
//And cache the new values.
cachedEnabled=enabled;
cachedCaption=item[value].second;
}
//Draw the text.
if(value>=0 && value<(int)item.size()){
//Get the text.
const std::string& lp=item[value].second;
//Check if the text is empty or not.
if(!lp.empty()){
if(!cacheTex){
SDL_Color color = objThemes.getTextColor(inDialog);
cacheTex=textureFromText(renderer, *fontGUI, lp.c_str(), color);
//If the text is too wide then we change to smaller font (?)
//NOTE: The width is 32px smaller (2x16px for the arrows).
if(rectFromTexture(*cacheTex).w>width-32){
cacheTex=textureFromText(renderer, *fontGUISmall,lp.c_str(),color);
}
}
if(draw){
//Center the text both vertically as horizontally.
const SDL_Rect textureSize = rectFromTexture(*cacheTex);
r.x=x+(width-textureSize.w)/2;
r.y=y+(height-textureSize.h)/2-GUI_FONT_RAISE;
//Draw the text.
applyTexture(r.x, r.y, cacheTex, renderer);
}
}
}
if(draw){
//Draw the arrows.
r.x=x;
if (!isKeyboardOnly) {
if ((state & 0xF) == 0x1)
r.x += abs(animation / 2);
}
r.y=y+4;
if(inDialog)
applyTexture(r.x,r.y,*arrowLeft2,renderer);
else
applyTexture(r.x,r.y,*arrowLeft1,renderer);
r.x=x+width-16;
if (!isKeyboardOnly) {
if ((state & 0xF) == 0x2)
r.x -= abs(animation / 2);
}
if(inDialog)
applyTexture(r.x,r.y,*arrowRight2,renderer);
else
applyTexture(r.x,r.y,*arrowRight1,renderer);
}
}
diff --git a/src/GUIListBox.h b/src/GUIListBox.h
index 80412bf..0630d7c 100644
--- a/src/GUIListBox.h
+++ b/src/GUIListBox.h
@@ -1,153 +1,159 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GUILISTBOX_H
#define GUILISTBOX_H
#include "GUIObject.h"
#include "GUIScrollBar.h"
//GUIObject that displays a list.
//It extends GUIObject because it's a special GUIObject.
class GUIListBox:public GUIObject{
public:
//Vector containing the entries of the list.
std::vector<std::string> item;
std::vector<SharedTexture> images;
std::vector<bool> itemSelectable;
//Boolean if the whole list is selectable.
//If it's false then all the items are not selectable, otherwise itemSelectable is checked further.
bool selectable;
//Boolean if the listbox should send click events.
bool clickEvents;
private:
//Scrollbar used when there are more entries than fit on the screen.
GUIScrollBar* scrollBar;
int firstItemY;
//Boolean if update for scrollbar is needed.
bool updateScrollbar;
public:
//Constructor.
//left: The relative x location of the GUIListBox.
//top: The relative y location of the GUIListBox.
//witdh: The width of the GUIListBox.
//height: The height of the GUIListBox.
//enabled: Boolean if the GUIListBox is enabled or not.
//visible: Boolean if the GUIListBox is visisble or not.
GUIListBox(ImageManager& imageManager, SDL_Renderer& renderer, int left=0, int top=0, int width=0, int height=0, bool enabled=true, bool visible=true, int gravity=GUIGravityLeft);
//Destructor
~GUIListBox();
//Method to remove all items and clear cache.
void clearItems();
//Method to add an item to the widget.
//name: Text of the item.
//texture: Custom image for the widget, if NULL the image will be generated from name string.
- void addItem(SDL_Renderer& renderer, std::string name, SharedTexture texture=nullptr, bool selectable=true);
+ //selectable: If this item is selectable.
+ void addItem(SDL_Renderer& renderer, const std::string& name, SharedTexture texture=nullptr, bool selectable=true);
//Method to update an item in the widget.
//index: index of the item.
//newName: New text for the item.
//newTexture: New custom image for the widget, if NULL the image will be generated from newName string.
- void updateItem(SDL_Renderer& renderer, int index, std::string newText, SharedTexture newTexture=nullptr);
-
+ //NOTE: This will not update the selectable property.
+ void updateItem(SDL_Renderer& renderer, int index, const std::string& newText, SharedTexture newTexture=nullptr);
+
+ //Method to update an item in the widget.
+ //This will also update the seletable property
+ void updateItem(SDL_Renderer& renderer, int index, const std::string& newText, SharedTexture newTexture, bool selectable);
+
//Method used the get item names from the widget.
//index: index of the item.
std::string getItem(int index);
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(SDL_Renderer&renderer, int x=0, int y=0, bool enabled=true, bool visible=true, bool processed=false);
//Method that will render the GUIListBox.
//x: The x location to draw the GUIListBox. (x+left)
//y: The y location to draw the GUIListBox. (y+top)
virtual void render(SDL_Renderer &renderer, int x=0, int y=0, bool draw=true);
//Scroll the scrollbar.
//dy: vertical scroll (in lines)
void scrollScrollbar(int dy);
//Method used to reposition scrollbars after a resize.
void onResize() override;
};
//GUIObject that displays a list on only one line.
//Instead of clicking the entries of the list you iterate through them.
//It extends GUIObject because it's a special GUIObject.
class GUISingleLineListBox:public GUIObject{
public:
//Vector containing the entries of the list.
std::vector<std::pair<std::string,std::string> > item;
//Integer used for the animation of the arrow.
int animation;
public:
//Constructor.
//left: The relative x location of the GUIListBox.
//top: The relative y location of the GUIListBox.
//witdh: The width of the GUIListBox.
//height: The height of the GUIListBox.
//enabled: Boolean if the GUIListBox is enabled or not.
//visible: Boolean if the GUIListBox is visisble or not.
GUISingleLineListBox(ImageManager& imageManager, SDL_Renderer& renderer,int left=0,int top=0,int width=0,int height=0,bool enabled=true,bool visible=true,int gravity=GUIGravityLeft);
//Method for adding an item to the list.
//name: The name of the item.
//label: The text that is displayed.
void addItem(std::string name,std::string label="");
//Method for adding items from a vector to the list.
//items: Vector containing the items in pairs, first is the name, second the label.
void addItems(std::vector<std::pair<std::string,std::string> > items);
//Method for adding items from a vector to the list.
//items: Vector containing the items, the name will also be used as label.
void addItems(std::vector<std::string> items);
//Method for retrieving the name of an item for a given index.
//index: The index of the item, when -1 is entered the current one will be used.
std::string getName(unsigned int index=-1);
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(SDL_Renderer&,int x=0,int y=0,bool enabled=true,bool visible=true,bool processed=false);
//Method that will render the GUIListBox.
//x: The x location to draw the GUIListBox. (x+left)
//y: The y location to draw the GUIListBox. (y+top)
virtual void render(SDL_Renderer &renderer, int x=0, int y=0, bool draw=true);
};
#endif
diff --git a/src/GUIObject.cpp b/src/GUIObject.cpp
index b565d62..c7ca850 100644
--- a/src/GUIObject.cpp
+++ b/src/GUIObject.cpp
@@ -1,1222 +1,1234 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Functions.h"
#include "GUIObject.h"
#include "ThemeManager.h"
#include "InputManager.h"
#include "GUIListBox.h"
#include "GUISlider.h"
#include <algorithm>
#include <iostream>
#include <list>
#include <SDL_ttf.h>
#include "Render.h"
using namespace std;
//Set the GUIObjectRoot to NULL.
GUIObject* GUIObjectRoot=NULL;
//Initialise the event queue.
list<GUIEvent> GUIEventQueue;
//A boolean variable used to skip next mouse up event for GUI (temporary workaround).
bool GUISkipNextMouseUpEvent = false;
void GUIObjectHandleEvents(ImageManager& imageManager, SDL_Renderer& renderer, bool kill){
//Check if we need to reset the skip variable.
if (event.type == SDL_MOUSEBUTTONDOWN) {
GUISkipNextMouseUpEvent = false;
}
//Check if we need to skip event.
if (event.type == SDL_MOUSEBUTTONUP && GUISkipNextMouseUpEvent) {
GUISkipNextMouseUpEvent = false;
} else {
//Make sure that GUIObjectRoot isn't null.
if (GUIObjectRoot)
GUIObjectRoot->handleEvents(renderer);
}
//Check for SDL_QUIT.
if(event.type==SDL_QUIT && kill){
//We get a quit event so enter the exit state.
setNextState(STATE_EXIT);
delete GUIObjectRoot;
GUIObjectRoot=NULL;
return;
}
//Keep calling events until there are none left.
while(!GUIEventQueue.empty()){
//Get one event and remove it from the queue.
GUIEvent e=GUIEventQueue.front();
GUIEventQueue.pop_front();
//If an eventCallback exist call it.
if(e.eventCallback){
e.eventCallback->GUIEventCallback_OnEvent(imageManager,renderer,e.name,e.obj,e.eventType);
}
}
//We empty the event queue just to be sure.
GUIEventQueue.clear();
}
GUIObject::GUIObject(ImageManager& imageManager, SDL_Renderer& renderer, int left, int top, int width, int height,
const char* caption, int value,
bool enabled, bool visible, int gravity) :
left(left), top(top), width(width), height(height),
gravity(gravity), value(value),
enabled(enabled), visible(visible),
eventCallback(NULL), state(0),
cachedEnabled(enabled), gravityX(0),
gravityLeft(0), gravityTop(0), gravityRight(0), gravityBottom(0)
{
//Make sure that caption isn't NULL before setting it.
if (caption){
GUIObject::caption = caption;
//And set the cached caption.
cachedCaption = caption;
}
if (width <= 0)
autoWidth = true;
else
autoWidth = false;
inDialog = false;
//Load the gui images.
bmGuiTex = imageManager.loadTexture(getDataPath() + "gfx/gui.png", renderer);
}
GUIObject::~GUIObject(){
//We need to delete every child we have.
for(unsigned int i=0;i<childControls.size();i++){
delete childControls[i];
}
//Deleted the childs now empty the childControls vector.
childControls.clear();
}
void GUIObject::addChild(GUIObject* obj){
//Add widget add a child
childControls.push_back(obj);
//Copy inDialog boolean from parent.
obj->inDialog = inDialog;
}
GUIObject* GUIObject::getChild(const std::string& name){
//Look for a child with the name.
for (unsigned int i = 0; i<childControls.size(); i++)
if (childControls[i]->name == name)
return childControls[i];
//Not found so return NULL.
return NULL;
}
bool GUIObject::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//Also let the children handle their events.
for(unsigned int i=0;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(renderer,x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
void GUIObject::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the GUIObject when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(renderer,x,y,draw);
}
}
void GUIObject::onResize() {
}
void GUIObject::refreshCache(bool enabled) {
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
if(enabled!=cachedEnabled || caption.compare(cachedCaption)!=0 || width<=0){
//TODO: Only change alpha if only enabled changes.
//Free the cache.
cacheTex.reset(nullptr);
//And cache the new values.
cachedEnabled=enabled;
cachedCaption=caption;
//Finally resize the widget
if(autoWidth)
width=-1;
}
}
int GUIObject::getSelectedControl() {
for (int i = 0; i < (int)childControls.size(); i++) {
GUIObject *obj = childControls[i];
if (obj && obj->visible && obj->enabled && obj->state) {
if (dynamic_cast<GUITextBox*>(obj) && obj->state == 2) {
return i;
} else if (dynamic_cast<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)
|| dynamic_cast<GUISingleLineListBox*>(obj)
|| dynamic_cast<GUISlider*>(obj)
)
{
return i;
}
}
}
return -1;
}
void GUIObject::setSelectedControl(int index) {
for (int i = 0; i < (int)childControls.size(); i++) {
GUIObject *obj = childControls[i];
if (obj && obj->visible && obj->enabled) {
if (dynamic_cast<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)) {
//It's a button.
obj->state = (i == index) ? 1 : 0;
} else if (dynamic_cast<GUITextBox*>(obj)) {
//It's a text box (or a spin box).
if(i == index) {
obj->state = 2;
} else {
dynamic_cast<GUITextBox*>(obj)->blur();
}
} else if (dynamic_cast<GUISingleLineListBox*>(obj)) {
//It's a single line list box.
obj->state = (i == index) ? 0x100 : 0;
} else if (dynamic_cast<GUISlider*>(obj)) {
//It's a slider.
obj->state = (i == index) ? 0x10000 : 0;
}
}
}
}
int GUIObject::selectNextControl(int direction, int selected) {
//Get the index of currently selected control.
if (selected == 0x80000000) {
selected = getSelectedControl();
}
//Find the next control.
for (int i = 0; i < (int)childControls.size(); i++) {
if (selected < 0) {
selected = 0;
} else {
selected += direction;
if (selected >= (int)childControls.size()) {
selected -= childControls.size();
} else if (selected < 0) {
selected += childControls.size();
}
}
GUIObject *obj = childControls[selected];
if (obj && obj->visible && obj->enabled) {
if (dynamic_cast<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)
|| dynamic_cast<GUITextBox*>(obj)
|| dynamic_cast<GUISingleLineListBox*>(obj)
|| dynamic_cast<GUISlider*>(obj)
)
{
setSelectedControl(selected);
return selected;
}
}
}
return -1;
}
bool GUIObject::handleKeyboardNavigationEvents(ImageManager& imageManager, SDL_Renderer& renderer, int keyboardNavigationMode) {
if (keyboardNavigationMode == 0) return false;
//Check operation on focused control. These have higher priority.
if (isKeyboardOnly) {
//Check enter key.
if ((keyboardNavigationMode & ReturnControls) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_SELECT)) {
int index = getSelectedControl();
if (index >= 0) {
GUIObject *obj = childControls[index];
if (dynamic_cast<GUIButton*>(obj)) {
//It's a button.
if (obj->eventCallback) {
obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventClick);
}
return true;
}
if (dynamic_cast<GUICheckBox*>(obj)) {
//It's a check box.
obj->value = obj->value ? 0 : 1;
if (obj->eventCallback) {
obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventClick);
}
return true;
}
}
}
//Check left/right key.
if ((keyboardNavigationMode & LeftRightControls) != 0 && (inputMgr.isKeyDownEvent(INPUTMGR_LEFT) || inputMgr.isKeyDownEvent(INPUTMGR_RIGHT))) {
int index = getSelectedControl();
if (index >= 0) {
GUIObject *obj = childControls[index];
auto sllb = dynamic_cast<GUISingleLineListBox*>(obj);
if (sllb) {
//It's a single line list box.
int newValue = sllb->value + (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT) ? 1 : -1);
if (newValue >= (int)sllb->item.size()) {
newValue -= sllb->item.size();
} else if (newValue < 0) {
newValue += sllb->item.size();
}
if (sllb->value != newValue) {
sllb->value = newValue;
if (obj->eventCallback) {
obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventClick);
}
}
return true;
}
auto slider = dynamic_cast<GUISlider*>(obj);
if (slider) {
//It's a slider.
int newValue = slider->value + (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT) ? slider->largeChange : -slider->largeChange);
newValue = clamp(newValue, slider->minValue, slider->maxValue);
if (slider->value != newValue) {
slider->value = newValue;
if (obj->eventCallback) {
obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventChange);
}
}
return true;
}
}
}
}
+ //Check if we need to exclude printable characters
+ bool excludePrintable = false;
+ {
+ int index = getSelectedControl();
+ if (index >= 0) {
+ GUIObject *obj = childControls[index];
+ if (dynamic_cast<GUITextBox*>(obj)) {
+ excludePrintable = true;
+ }
+ }
+ }
+
//Check focus movement
int m = SDL_GetModState();
- if (((keyboardNavigationMode & LeftRightFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_RIGHT))
- || ((keyboardNavigationMode & UpDownFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_DOWN))
- || ((keyboardNavigationMode & TabFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_TAB) && (m & KMOD_SHIFT) == 0)
+ if (((keyboardNavigationMode & LeftRightFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_RIGHT, excludePrintable))
+ || ((keyboardNavigationMode & UpDownFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_DOWN, excludePrintable))
+ || ((keyboardNavigationMode & TabFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_TAB, excludePrintable) && (m & KMOD_SHIFT) == 0)
)
{
isKeyboardOnly = true;
selectNextControl(1);
return true;
- } else if (((keyboardNavigationMode & LeftRightFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_LEFT))
- || ((keyboardNavigationMode & UpDownFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_UP))
- || ((keyboardNavigationMode & TabFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_TAB) && (m & KMOD_SHIFT) != 0)
+ } else if (((keyboardNavigationMode & LeftRightFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_LEFT, excludePrintable))
+ || ((keyboardNavigationMode & UpDownFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_UP, excludePrintable))
+ || ((keyboardNavigationMode & TabFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_TAB, excludePrintable) && (m & KMOD_SHIFT) != 0)
)
{
isKeyboardOnly = true;
selectNextControl(-1);
return true;
}
return false;
}
//////////////GUIButton///////////////////////////////////////////////////////////////////
bool GUIButton::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The widget is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The widget is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//We don't update button state under keyboard only mode.
if (!isKeyboardOnly) {
//Set state to 0.
state = 0;
//Only check for events when the object is both enabled and visible.
if (enabled && visible) {
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the widget.
if (i >= x && i < x + width && j >= y && j < y + height) {
//We have hover so set state to one.
state = 1;
//Check for a mouse button press.
if (k&SDL_BUTTON(1))
state = 2;
//Check if there's a mouse press and the event hasn't been already processed.
if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT && !b) {
//If event callback is configured then add an event to the queue.
if (eventCallback) {
GUIEvent e = { eventCallback, name, this, GUIEventClick };
GUIEventQueue.push_back(e);
}
//Event has been processed.
b = true;
}
}
}
}
//Also let the children handle their events.
for(unsigned int i=0;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(renderer,x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
void GUIButton::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
refreshCache(enabled);
//Get the text and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
//Update cache if needed.
if(!cacheTex){
SDL_Color color = objThemes.getTextColor(inDialog);
if(!smallFont) {
cacheTex = textureFromText(renderer, *fontGUI, lp, color);
} else {
cacheTex = textureFromText(renderer, *fontGUISmall, lp, color);
}
//Make the widget transparent if it's disabled.
if(!enabled) {
SDL_SetTextureAlphaMod(cacheTex.get(), 128);
}
//Calculate proper size for the widget.
if(width<=0){
width=textureWidth(*cacheTex)+50;
if(gravity==GUIGravityCenter){
gravityX=int(width/2);
}else if(gravity==GUIGravityRight){
gravityX=width;
}else{
gravityX=0;
}
}
}
if(draw){
//Center the text both vertically as horizontally.
const SDL_Rect size = rectFromTexture(*cacheTex);
const int drawX=x-gravityX+(width-size.w)/2;
const int drawY=y+(height-size.h)/2-GUI_FONT_RAISE;
//Check if the arrows don't fall of.
if(size.w+32<=width){
if(state==1){
if(inDialog){
applyTexture(x-gravityX+(width-size.w)/2+4+size.w+5,y+2,*arrowLeft2,renderer);
applyTexture(x-gravityX+(width-size.w)/2-25,y+2,*arrowRight2,renderer);
}else{
applyTexture(x-gravityX+(width-size.w)/2+4+size.w+5,y+2,*arrowLeft1,renderer);
applyTexture(x-gravityX+(width-size.w)/2-25,y+2,*arrowRight1,renderer);
}
}else if(state==2){
if(inDialog){
applyTexture(x-gravityX+(width-size.w)/2+4+size.w,y+2,*arrowLeft2,renderer);
applyTexture(x-gravityX+(width-size.w)/2-20,y+2,*arrowRight2,renderer);
}else{
applyTexture(x-gravityX+(width-size.w)/2+4+size.w,y+2,*arrowLeft1,renderer);
applyTexture(x-gravityX+(width-size.w)/2-20,y+2,arrowRight1,renderer);
}
}
}
//Draw the text.
applyTexture(drawX, drawY, *cacheTex, renderer);
}
}
}
//////////////GUICheckBox///////////////////////////////////////////////////////////////////
bool GUICheckBox::handleEvents(SDL_Renderer&,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The widget is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The widget is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//We don't update state under keyboard only mode.
if (!isKeyboardOnly) {
//Set state to 0.
state = 0;
//Only check for events when the object is both enabled and visible.
if (enabled&&visible){
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the widget.
if (i >= x && i < x + width && j >= y && j < y + height){
//We have hover so set state to one.
state = 1;
//Check for a mouse button press.
if (k&SDL_BUTTON(1))
state = 2;
//Check if there's a mouse press and the event hasn't been already processed.
if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT && !b){
//It's a checkbox so toggle the value.
value = value ? 0 : 1;
//If event callback is configured then add an event to the queue.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventClick };
GUIEventQueue.push_back(e);
}
//Event has been processed.
b = true;
}
}
}
}
return b;
}
void GUICheckBox::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
refreshCache(enabled);
//Draw the highlight in keyboard only mode.
if (isKeyboardOnly && state && draw) {
drawGUIBox(x, y, width, height, renderer, 0xFFFFFF40);
}
//Get the text.
const char* lp=caption.c_str();
//Make sure it isn't empty.
if(lp!=NULL && lp[0]){
//Update the cache if needed.
if(!cacheTex){
SDL_Color color = objThemes.getTextColor(inDialog);
cacheTex=textureFromText(renderer,*fontText,lp,color);
}
if(draw){
//Calculate the location, center it vertically.
const int drawX=x;
const int drawY=y+(height - textureHeight(*cacheTex))/2;
//Draw the text
applyTexture(drawX, drawY, *cacheTex, renderer);
}
}
if(draw){
//Draw the check (or not).
//value*16 determines where in the gui textures we draw from.
//if(value==1||value==2)
// r1.x=value*16;
const SDL_Rect srcRect={value*16,0,16,16};
const SDL_Rect dstRect={x+width-20, y+(height-16)/2, 16, 16};
//Get the right image depending on the state of the object.
SDL_RenderCopy(&renderer, bmGuiTex.get(), &srcRect, &dstRect);
}
}
//////////////GUILabel///////////////////////////////////////////////////////////////////
bool GUILabel::handleEvents(SDL_Renderer&,int ,int ,bool ,bool ,bool processed){
return processed;
}
void GUILabel::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
if(!visible)
return;
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
refreshCache(enabled);
//Get the absolute x and y location.
x+=left;
y+=top;
//Rectangle the size of the widget.
SDL_Rect r;
r.x=x;
r.y=y;
r.w=width;
r.h=height;
//Get the caption and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
//Update cache if needed.
if(!cacheTex){
SDL_Color color = objThemes.getTextColor(inDialog);
cacheTex=textureFromText(renderer, *fontText, lp, color);
if(width<=0)
width=textureWidth(*cacheTex);
}
//Align the text properly and draw it.
if(draw){
const SDL_Rect size = rectFromTexture(*cacheTex);
if(gravity==GUIGravityCenter)
gravityX=(width-size.w)/2;
else if(gravity==GUIGravityRight)
gravityX=width-size.w;
else
gravityX=0;
r.y=y+(height - size.h)/2;
r.x+=gravityX;
applyTexture(r.x, r.y, cacheTex, renderer);
}
}
}
//////////////GUITextBox///////////////////////////////////////////////////////////////////
void GUITextBox::backspaceChar(){
//We need to remove a character so first make sure that there is text.
if(caption.length()>0){
if(highlightStart==highlightEnd&&highlightStart>0){
int advance = 0;
// this is proper UTF-8 support
int ch = utf8ReadBackward(caption.c_str(), highlightStart); // we obtain new highlightStart from this
if (ch > 0) TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
highlightEndX = highlightStartX = highlightEndX - advance;
caption.erase(highlightStart, highlightEnd - highlightStart);
highlightEnd = highlightStart;
} else if (highlightStart<highlightEnd){
caption.erase(highlightStart,highlightEnd-highlightStart);
highlightEnd=highlightStart;
highlightEndX=highlightStartX;
}else{
caption.erase(highlightEnd,highlightStart-highlightEnd);
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
}
void GUITextBox::deleteChar(){
//We need to remove a character so first make sure that there is text.
if(caption.length()>0){
if(highlightStart==highlightEnd){
// this is proper utf8 support
int i = highlightEnd;
utf8ReadForward(caption.c_str(), i);
if (i > highlightEnd) caption.erase(highlightEnd, i - highlightEnd);
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}else if(highlightStart<highlightEnd){
caption.erase(highlightStart,highlightEnd-highlightStart);
highlightEnd=highlightStart;
highlightEndX=highlightStartX;
}else{
caption.erase(highlightEnd,highlightStart-highlightEnd);
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
}
void GUITextBox::moveCarrotLeft(){
if(highlightEnd>0){
int advance = 0;
// this is proper UTF-8 support
int ch = utf8ReadBackward(caption.c_str(), highlightEnd); // we obtain new highlightEnd from this
if (ch > 0) TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
if(SDL_GetModState() & KMOD_SHIFT){
highlightEndX-=advance;
}else{
highlightStart=highlightEnd;
highlightStartX=highlightEndX=highlightEndX-advance;
}
}else{
if((SDL_GetModState() & KMOD_SHIFT)==0){
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
}
tick=15;
}
void GUITextBox::moveCarrotRight(){
if(highlightEnd<caption.length()){
int advance = 0;
// this is proper UTF-8 support
int ch = utf8ReadForward(caption.c_str(), highlightEnd); // we obtain new highlightEnd from this
if (ch > 0) TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
if(SDL_GetModState() & KMOD_SHIFT){
highlightEndX+=advance;
}else{
highlightStartX=highlightEndX=highlightEndX+advance;
highlightStart=highlightEnd;
}
}else{
if((SDL_GetModState() & KMOD_SHIFT)==0){
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
}
tick=15;
}
void GUITextBox::updateText(const std::string& text) {
caption = text;
updateSelection(0, 0);
}
void GUITextBox::updateSelection(int start, int end) {
start = clamp(start, 0, caption.size());
end = clamp(end, 0, caption.size());
highlightStart = start;
highlightStartX = 0;
highlightEnd = end;
highlightEndX = 0;
if (start > 0) {
TTF_SizeUTF8(fontText, caption.substr(0, start).c_str(), &highlightStartX, NULL);
}
if (end > 0) {
TTF_SizeUTF8(fontText, caption.substr(0, end).c_str(), &highlightEndX, NULL);
}
}
void GUITextBox::inputText(const char* s) {
int m = strlen(s);
if (m > 0){
if (highlightStart == highlightEnd) {
caption.insert((size_t)highlightStart, s);
highlightStart += m;
highlightEnd = highlightStart;
} else if (highlightStart < highlightEnd) {
caption.erase(highlightStart, highlightEnd - highlightStart);
caption.insert((size_t)highlightStart, s);
highlightStart += m;
highlightEnd = highlightStart;
highlightEndX = highlightStartX;
} else {
caption.erase(highlightEnd, highlightStart - highlightEnd);
caption.insert((size_t)highlightEnd, s);
highlightEnd += m;
highlightStart = highlightEnd;
highlightStartX = highlightEndX;
}
int advance = 0;
for (int i = 0;;) {
int a = 0;
int ch = utf8ReadForward(s, i);
if (ch <= 0) break;
TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &a);
advance += a;
}
highlightStartX = highlightEndX = highlightStartX + advance;
//If there is an event callback then call it.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventChange };
GUIEventQueue.push_back(e);
}
}
}
void GUITextBox::blur(){
state = 0;
highlightStart=highlightStartX=0;
highlightEnd=highlightEndX=0;
}
bool GUITextBox::handleEvents(SDL_Renderer&,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The widget is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The widget is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//NOTE: We don't reset the state to have a "focus" effect.
//Only check for events when the object is both enabled and visible.
if(enabled&&visible){
//Check if there's a key press and the event hasn't been already processed.
if(state==2 && event.type==SDL_KEYDOWN && !b){
//Get the keycode.
SDL_Keycode key=event.key.keysym.sym;
if ((event.key.keysym.mod & KMOD_CTRL) == 0) {
//Check if the key is supported.
if (event.key.keysym.sym == SDLK_BACKSPACE){
backspaceChar();
} else if (event.key.keysym.sym == SDLK_DELETE){
deleteChar();
} else if (event.key.keysym.sym == SDLK_RIGHT){
moveCarrotRight();
} else if (event.key.keysym.sym == SDLK_LEFT){
moveCarrotLeft();
}
} else {
//Check hotkey.
if (event.key.keysym.sym == SDLK_a) {
//Select all.
highlightStart = 0;
highlightStartX = 0;
highlightEnd = caption.size();
highlightEndX = 0;
if (highlightEnd > 0) {
TTF_SizeUTF8(fontText, caption.c_str(), &highlightEndX, NULL);
}
} else if (event.key.keysym.sym == SDLK_x || event.key.keysym.sym == SDLK_c) {
//Cut or copy.
int start = highlightStart, end = highlightEnd;
if (start > end) std::swap(start, end);
if (start < end) {
SDL_SetClipboardText(caption.substr(start, end - start).c_str());
if (event.key.keysym.sym == SDLK_x) {
//Cut.
backspaceChar();
}
}
} else if (event.key.keysym.sym == SDLK_v) {
//Paste.
if (SDL_HasClipboardText()) {
char *s = SDL_GetClipboardText();
inputText(s);
SDL_free(s);
}
}
}
//The event has been processed.
b = true;
} else if (state == 2 && event.type == SDL_TEXTINPUT && !b){
inputText(event.text.text);
//The event has been processed.
b = true;
} else if (state == 2 && event.type == SDL_TEXTEDITING && !b){
// TODO: process SDL_TEXTEDITING event
}
//Only process mouse event when not in keyboard only mode
if (!isKeyboardOnly) {
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the widget.
if (i >= x && i < x + width && j >= y && j < y + height){
//We can only increase our state. (nothing->hover->focus).
if (state != 2){
state = 1;
}
//Also update the cursor type.
currentCursor = CURSOR_CARROT;
//Move carrot and highlightning according to mouse input.
int clickX = i - x - 2;
int finalPos = 0;
int finalX = 0;
if (cacheTex&&!caption.empty()){
finalPos = caption.length();
for (int i = 0;;){
int advance = 0;
// this is proper UTF-8 support
int i0 = i;
int ch = utf8ReadForward(caption.c_str(), i);
if (ch <= 0) break;
TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
finalX += advance;
if (clickX < finalX - advance / 2){
finalPos = i0;
finalX -= advance;
break;
}
}
}
if (event.type == SDL_MOUSEBUTTONUP && state == 2){
state = 2;
highlightEnd = finalPos;
highlightEndX = finalX;
} else if (event.type == SDL_MOUSEBUTTONDOWN){
state = 2;
highlightStart = highlightEnd = finalPos;
highlightStartX = highlightEndX = finalX;
} else if (event.type == SDL_MOUSEMOTION && (k&SDL_BUTTON(1)) && state == 2){
state = 2;
highlightEnd = finalPos;
highlightEndX = finalX;
}
} else{
//The mouse is outside the TextBox.
//If we don't have focus but only hover we lose it.
if (state == 1){
state = 0;
}
//If it's a click event outside the textbox then we blur.
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT){
blur();
}
}
}
}
return b;
}
void GUITextBox::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
refreshCache(enabled);
if(draw){
//Default background opacity
int clr=50;
//If hovering or focused make background more visible.
if(state==1)
clr=128;
else if (state==2)
clr=100;
//Draw the box.
Uint32 color=0xFFFFFF00|clr;
drawGUIBox(x,y,width,height,renderer,color);
}
//Rectangle used for drawing.
SDL_Rect r{0,0,0,0};
//Get the text and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
if(!cacheTex) {
//Draw the text.
cacheTex=textureFromText(renderer,*fontText,lp,objThemes.getTextColor(true));
}
if(draw){
//Only draw the carrot and highlight when focus.
if(state==2){
//Place the highlighted area.
r.x=x+4;
r.y=y+3;
r.h=height-6;
if(highlightStart<highlightEnd){
r.x+=highlightStartX;
r.w=highlightEndX-highlightStartX;
}else{
r.x+=highlightEndX;
r.w=highlightStartX-highlightEndX;
}
//Draw the area.
//SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,128,128,128));
SDL_SetRenderDrawColor(&renderer, 128,128,128,255);
SDL_RenderFillRect(&renderer, &r);
//Ticking carrot.
if(tick<16){
//Show carrot: 15->0.
r.x=x+highlightEndX+2;
r.y=y+3;
r.h=height-6;
r.w=2;
//SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,0,0,0));
SDL_SetRenderDrawColor(&renderer,0,0,0,255);
SDL_RenderFillRect(&renderer, &r);
//Reset: 32 or count down.
if(tick<=0)
tick=32;
else
tick--;
}else{
//Hide carrot: 32->16.
tick--;
}
}
//Calculate the location, center it vertically.
SDL_Rect dstRect=rectFromTexture(*cacheTex);
dstRect.x=x+4;
dstRect.y=y+(height-dstRect.h)/2;
dstRect.w=std::min(width-2, dstRect.w);
//Draw the text.
const SDL_Rect srcRect={0,0,width-2,25};
SDL_RenderCopy(&renderer, cacheTex.get(), &srcRect, &dstRect);
}
}else{
//Only draw the carrot when focus.
if(state==2&&draw){
//Ticking carrot.
if (tick<16){
//Show carrot: 15->0.
r.x = x + 4;
r.y = y + 4;
r.w = 2;
r.h = height - 8;
//SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,0,0,0));
SDL_SetRenderDrawColor(&renderer, 0, 0, 0, 255);
SDL_RenderFillRect(&renderer, &r);
//Reset: 32 or count down.
if (tick <= 0)
tick = 32;
else
tick--;
} else{
//Hide carrot: 32->16.
tick--;
}
}
}
}
//////////////GUIFrame///////////////////////////////////////////////////////////////////
bool GUIFrame::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The widget is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The widget is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left;
y+=top;
//Also let the children handle their events.
for(unsigned int i=0;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(renderer,x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
void GUIFrame::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing this widget when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
if(enabled!=cachedEnabled || caption.compare(cachedCaption)!=0 || width<=0){
//Free the cache.
cacheTex.reset(nullptr);
//And cache the new values.
cachedEnabled=enabled;
cachedCaption=caption;
//Finally resize the widget.
if(autoWidth)
width=-1;
}
//Draw fill and borders.
if(draw){
Uint32 color=0xDDDDDDFF;
drawGUIBox(x,y,width,height,renderer,color);
}
//Get the title text and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
//Update cache if needed.
if(!cacheTex) {
cacheTex = textureFromText(renderer, *fontGUI, lp, objThemes.getTextColor(true));
}
//Draw the text.
if(draw) {
applyTexture(x+(width-textureWidth(*cacheTex))/2, y+6-GUI_FONT_RAISE, *cacheTex, renderer);
}
}
//We now need to draw all the children.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(renderer,x,y,draw);
}
}
//////////////GUIImage///////////////////////////////////////////////////////////////////
GUIImage::~GUIImage(){
}
bool GUIImage::handleEvents(SDL_Renderer&,int ,int ,bool ,bool ,bool processed){
return processed;
}
void GUIImage::fitToImage(){
const SDL_Rect imageSize = rectFromTexture(*image);
//Increase or decrease the width and height to fully show the image.
if(clip.w!=0) {
width=clip.w;
} else {
width=imageSize.w;
}
if(clip.h!=0) {
height=clip.h;
} else {
height=imageSize.h;
}
}
void GUIImage::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
//Also make sure the image isn't null.
if(!visible || !image)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Create a clip rectangle.
SDL_Rect r=clip;
//The width and height are capped by the GUIImage itself.
if(r.w>width || r.w==0) {
r.w=width;
}
if(r.h>height || r.h==0) {
r.h=height;
}
const SDL_Rect dstRect={x,y,r.w,r.h};
SDL_RenderCopy(&renderer, image.get(), &r, &dstRect);
}
diff --git a/src/GUISpinBox.cpp b/src/GUISpinBox.cpp
index 6409d6a..664125b 100644
--- a/src/GUISpinBox.cpp
+++ b/src/GUISpinBox.cpp
@@ -1,203 +1,203 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Functions.h"
#include "GUISpinBox.h"
#include "ThemeManager.h"
#include <algorithm>
bool GUISpinBox::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Backup the old state.
int oldState = state;
//First we call the GUITextBox::handleEvents().
bool b0 = GUITextBox::handleEvents(renderer, x, y, enabled, visible, processed);
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when he and his parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when he and his parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//Reset "key" to stop contant update of "number" in render().
//If the mouse is still on the button, the "key" will be reassigned later.
key=-1;
//Only check for events when the object is both enabled and visible.
if (enabled&&visible){
//Check if there's a key press and the event hasn't been already processed.
if (state == 2 && event.type == SDL_KEYDOWN && !b) {
if (event.key.keysym.sym == SDLK_UP) {
updateValue(true);
b = true;
} else if (event.key.keysym.sym == SDLK_DOWN) {
updateValue(false);
b = true;
}
}
//Only process mouse event when not in keyboard only mode
if (!isKeyboardOnly) {
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the text box.
if (i >= x && i < x + width && j >= y && j < y + height){
//Check if the mouse is inside the up/down button.
if (i >= x + width - 16) {
//Reset the cursor back to normal.
currentCursor = CURSOR_POINTER;
//Check for a mouse button press.
if (k&SDL_BUTTON(1)){
if (j < y + 17){
//Set the key values correct.
this->key = SDLK_UP;
keyHoldTime = 0;
keyTime = getKeyboardRepeatDelay();
//Update once to prevent a lag.
updateValue(true);
} else{
//Set the key values correct.
this->key = SDLK_DOWN;
keyHoldTime = 0;
keyTime = getKeyboardRepeatDelay();
//Update once to prevent a lag.
updateValue(false);
}
}
}
//Allow mouse wheel to change value.
if (event.type == SDL_MOUSEWHEEL){
if (event.wheel.y > 0){
updateValue(true);
b = true;
} else if (event.wheel.y < 0){
updateValue(false);
b = true;
}
}
}
}
//Validate the input when we lost focus.
if (oldState == 2 && state == 0){
update();
}
}
return b || b0;
}
void GUISpinBox::render(SDL_Renderer &renderer, int x, int y, bool draw){
//Call the GUITextBox::render().
GUITextBox::render(renderer, x, y, draw);
//FIXME: Logic in the render method since that is update constant.
if(key!=-1){
//Increase the key time.
keyHoldTime++;
//Make sure the deletionTime isn't to short.
if(keyHoldTime>=keyTime){
keyHoldTime=0;
keyTime = getKeyboardRepeatInterval();
//Now check the which key it was.
switch(key){
case SDLK_UP:
{
updateValue(true);
break;
}
case SDLK_DOWN:
{
updateValue(false);
break;
}
}
}
}
//There's no need drawing when it's invisible.
if(!visible || !draw)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Draw arrow buttons.
SDL_Rect srcRect = { 80, 0, 16, 16 };
SDL_Rect dstRect = { x + width - 18, y + 1, srcRect.w, srcRect.h };
SDL_RenderCopy(&renderer, bmGuiTex.get(), &srcRect, &dstRect);
srcRect.x = 96;
dstRect.y += 16;
SDL_RenderCopy(&renderer, bmGuiTex.get(), &srcRect, &dstRect);
}
void GUISpinBox::update(){
//Read number from the caption string.
- float number=(float)atof(caption.c_str());
+ double number=atof(caption.c_str());
//Stay in the limits.
if(number>limitMax){
number=limitMax;
}else if(number<limitMin){
number=limitMin;
}
//Write the number to the caption string.
char str[128];
sprintf(str,format.c_str(),number);
updateText(str);
}
void GUISpinBox::updateValue(bool positive){
//Read number from the caption string.
- float number=(float)atof(caption.c_str());
+ double number=atof(caption.c_str());
//Apply change.
if(positive)
number+=change;
else
number-=change;
//Stay in the limits.
if(number>limitMax){
number=limitMax;
}else if(number<limitMin){
number=limitMin;
}
//Write the number to the caption string.
char str[128];
sprintf(str,format.c_str(),number);
updateText(str);
}
diff --git a/src/GUISpinBox.h b/src/GUISpinBox.h
index 2bbf0e8..8a1f3be 100644
--- a/src/GUISpinBox.h
+++ b/src/GUISpinBox.h
@@ -1,70 +1,70 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GUISPINBOX_H
#define GUISPINBOX_H
#include "GUIObject.h"
class GUISpinBox:public GUITextBox{
public:
GUISpinBox(ImageManager& imageManager,SDL_Renderer& renderer,int left=0,int top=0,int width=0,int height=0,
bool enabled=true,bool visible=true):
GUITextBox(imageManager,renderer,left,top,width,height,NULL,0,enabled,visible),
change(1.0f),limitMax(100),limitMin(-100),format("%g"),
key(-1),keyHoldTime(0),keyTime(0){}
//Amount of single change.
- float change;
+ double change;
//Widget's value stays between these values.
- float limitMax,limitMin;
+ double limitMax,limitMin;
//Standard C printf format used for displaying the number.
std::string format;
//Method to update widget's value.
void update();
//Method to change widget's value.
//positive: Boolean if add or remove change.
void updateValue(bool positive);
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(SDL_Renderer&,int x=0,int y=0,bool enabled=true,bool visible=true,bool processed=false) override;
//Method that will render the GUIScrollBar.
//x: The x location to draw the GUIObject. (x+left)
//y: The y location to draw the GUIObject. (y+top)
virtual void render(SDL_Renderer& renderer, int x=0,int y=0,bool draw=true) override;
private:
//Integer containing the key that is holded.
int key;
//Integer containing the time the key is pressed.
int keyHoldTime;
//The time it takes to invoke the key action again.
int keyTime;
};
#endif
diff --git a/src/Game.cpp b/src/Game.cpp
index 2de16b5..193ca46 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,1807 +1,1840 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Block.h"
#include "GameState.h"
#include "Functions.h"
#include "GameObjects.h"
#include "ThemeManager.h"
#include "Game.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "InputManager.h"
#include "MusicManager.h"
#include "Render.h"
#include "StatisticsManager.h"
#include "ScriptExecutor.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include <map>
#include <algorithm>
#include <stdio.h>
#include <SDL_ttf.h>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
const char* Game::blockName[TYPE_MAX]={"Block","PlayerStart","ShadowStart",
"Exit","ShadowBlock","Spikes",
"Checkpoint","Swap","Fragile",
"MovingBlock","MovingShadowBlock","MovingSpikes",
"Teleporter","Button","Switch",
"ConveyorBelt","ShadowConveyorBelt","NotificationBlock", "Collectable", "Pushable"
};
map<string,int> Game::blockNameMap;
map<int,string> Game::gameObjectEventTypeMap;
map<string,int> Game::gameObjectEventNameMap;
map<int,string> Game::levelEventTypeMap;
map<string,int> Game::levelEventNameMap;
string Game::recordFile;
//An internal function.
static void copyCompiledScripts(lua_State *state, const std::map<int, int>& src, std::map<int, int>& dest) {
//Clear the existing scripts.
for (auto it = dest.begin(); it != dest.end(); ++it) {
luaL_unref(state, LUA_REGISTRYINDEX, it->second);
}
dest.clear();
//Copy the source to the destination.
for (auto it = src.begin(); it != src.end(); ++it) {
lua_rawgeti(state, LUA_REGISTRYINDEX, it->second);
dest[it->first] = luaL_ref(state, LUA_REGISTRYINDEX);
}
}
Game::Game(SDL_Renderer &renderer, ImageManager &imageManager):isReset(false)
, scriptExecutor(new ScriptExecutor())
,currentLevelNode(NULL)
,customTheme(NULL)
,background(NULL)
,won(false)
,interlevel(false)
,gameTipIndex(0)
,time(0),timeSaved(0)
,recordings(0),recordingsSaved(0)
,cameraMode(CAMERA_PLAYER),cameraModeSaved(CAMERA_PLAYER)
,player(this),shadow(this),objLastCheckPoint(NULL)
,currentCollectables(0),totalCollectables(0),currentCollectablesSaved(0){
saveStateNextTime=false;
loadStateNextTime=false;
recentSwap=recentSwapSaved=-10000;
recentLoad=recentSave=0;
action=imageManager.loadTexture(getDataPath()+"gfx/actions.png", renderer);
medals=imageManager.loadTexture(getDataPath()+"gfx/medals.png", renderer);
//Get the collectable image from the theme.
//NOTE: Isn't there a better way to retrieve the image?
objThemes.getBlock(TYPE_COLLECTABLE)->createInstance(&collectable);
//Hide the cursor if not in the leveleditor.
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
}
Game::~Game(){
//Simply call our destroy method.
destroy();
//Before we leave make sure the cursor is visible.
SDL_ShowCursor(SDL_ENABLE);
}
void Game::destroy(){
delete scriptExecutor;
scriptExecutor = NULL;
//Loop through the levelObjects and delete them.
for(unsigned int i=0;i<levelObjects.size();i++)
delete levelObjects[i];
//Done now clear the levelObjects vector.
levelObjects.clear();
//Loop through the sceneryLayers and delete them.
for(auto it=sceneryLayers.begin();it!=sceneryLayers.end();++it){
delete it->second;
}
sceneryLayers.clear();
//Clear the name and the editor data.
levelName.clear();
levelFile.clear();
editorData.clear();
//Remove everything from the themeManager.
background=NULL;
if(customTheme)
objThemes.removeTheme();
customTheme=NULL;
//If there's a (partial) theme bundled with the levelpack remove that as well.
if(levels->customTheme)
objThemes.removeTheme();
//delete current level (if any)
if(currentLevelNode){
delete currentLevelNode;
currentLevelNode=NULL;
}
//Reset the time.
time=timeSaved=0;
recordings=recordingsSaved=0;
recentSwap=recentSwapSaved=-10000;
//Set the music list back to the configured list.
getMusicManager()->setMusicList(getSettings()->getValue("musiclist"));
}
void Game::reloadMusic() {
//NOTE: level music is always enabled.
//Check if the levelpack has a prefered music list.
if (levels && !levels->levelpackMusicList.empty())
getMusicManager()->setMusicList(levels->levelpackMusicList);
//Check for the music to use.
string &s = editorData["music"];
if (!s.empty()) {
getMusicManager()->playMusic(s);
} else {
getMusicManager()->pickMusic();
}
}
void Game::loadLevelFromNode(ImageManager& imageManager,SDL_Renderer& renderer,TreeStorageNode* obj,const string& fileName){
//Make sure there's nothing left from any previous levels.
//Not needed since loadLevelFromNode is only called from the changeState method, meaning it's a new instance of Game.
//destroy();
//set current level to loaded one.
currentLevelNode=obj;
//Set the level dimensions to the default, it will probably be changed by the editorData,
//but 800x600 is a fallback.
LEVEL_WIDTH=800;
LEVEL_HEIGHT=600;
currentCollectables=0;
totalCollectables=0;
currentCollectablesSaved=0;
//Load the additional data.
for(map<string,vector<string> >::iterator i=obj->attributes.begin();i!=obj->attributes.end();++i){
if(i->first=="size"){
//We found the size attribute.
if(i->second.size()>=2){
//Set the dimensions of the level.
LEVEL_WIDTH=atoi(i->second[0].c_str());
LEVEL_HEIGHT=atoi(i->second[1].c_str());
}
}else if(i->second.size()>0){
//Any other data will be put into the editorData.
editorData[i->first]=i->second[0];
}
}
//Get the theme.
{
//NOTE: level themes are always enabled.
//Check for bundled (partial) themes for level pack.
if (levels->customTheme){
if (objThemes.appendThemeFromFile(levels->levelpackPath + "/theme/theme.mnmstheme", imageManager, renderer) == NULL){
//The theme failed to load so set the customTheme boolean to false.
levels->customTheme = false;
}
}
//Check for the theme to use for this level. This has higher priority.
//Examples: %DATA%/themes/classic or %USER%/themes/Orange
string &s = editorData["theme"];
if (!s.empty()){
customTheme = objThemes.appendThemeFromFile(processFileName(s) + "/theme.mnmstheme", imageManager, renderer);
}
//Set the Appearance of the player and the shadow.
objThemes.getCharacter(false)->createInstance(&player.appearance);
objThemes.getCharacter(true)->createInstance(&shadow.appearance);
}
//Get the music.
reloadMusic();
//Load the data from the level node.
for(unsigned int i=0;i<obj->subNodes.size();i++){
TreeStorageNode* obj1=obj->subNodes[i];
if(obj1==NULL) continue;
if(obj1->name=="tile"){
Block* block=new Block(this);
if(!block->loadFromNode(imageManager,renderer,obj1)){
delete block;
continue;
}
//If the type is collectable, increase the number of totalCollectables
if(block->type==TYPE_COLLECTABLE)
totalCollectables++;
//Add the block to the levelObjects vector.
levelObjects.push_back(block);
}else if(obj1->name=="scenerylayer" && obj1->value.size()==1){
//Check if the layer exists.
if (sceneryLayers[obj1->value[0]] == NULL) {
sceneryLayers[obj1->value[0]] = new SceneryLayer();
}
//Load contents from node.
sceneryLayers[obj1->value[0]]->loadFromNode(this, imageManager, renderer, obj1);
}else if(obj1->name=="script" && !obj1->value.empty()){
map<string,int>::iterator it=Game::levelEventNameMap.find(obj1->value[0]);
if(it!=Game::levelEventNameMap.end()){
int eventType=it->second;
const std::string& script=obj1->attributes["script"][0];
if(!script.empty()) scripts[eventType]=script;
}
}
}
//Set the levelName to the name of the current level.
levelName=editorData["name"];
levelFile=fileName;
//Some extra stuff only needed when not in the levelEditor.
if(stateID!=STATE_LEVEL_EDITOR){
//We create a text with the text "Level <levelno> <levelName>".
//It will be shown in the left bottom corner of the screen.
string s;
if(levels->getLevelCount()>1 && levels->type!=COLLECTION){
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_CC(levels->getDictionaryManager(),editorData["name"]));
} else {
s = _CC(levels->getDictionaryManager(), editorData["name"]);
}
bmTips[0]=textureFromText(renderer, *fontText,s.c_str(),objThemes.getTextColor(true));
}
//Get the background
background=objThemes.getBackground(false);
//Now the loading is finished, we reset all objects to their initial states.
reset(true, stateID == STATE_LEVEL_EDITOR);
}
void Game::loadLevel(ImageManager& imageManager,SDL_Renderer& renderer,std::string fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode *obj=new TreeStorageNode();
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),obj,true)){
cerr<<"ERROR: Can't load level file "<<s<<endl;
delete obj;
return;
}
}
//Now call another function.
loadLevelFromNode(imageManager,renderer,obj,fileName);
}
void Game::saveRecord(const char* fileName){
//check if current level is NULL (which should be impossible)
if(currentLevelNode==NULL) return;
TreeStorageNode obj;
POASerializer objSerializer;
//put current level to the node.
currentLevelNode->name="map";
obj.subNodes.push_back(currentLevelNode);
//serialize the game record using RLE compression.
#define PUSH_BACK \
if(j>0){ \
if(j>1){ \
sprintf(c,"%d*%d",last,j); \
}else{ \
sprintf(c,"%d",last); \
} \
v.push_back(c); \
}
vector<string> &v=obj.attributes["record"];
vector<int> *record=player.getRecord();
char c[64];
int i,j=0,last;
for(i=0;i<(int)record->size();i++){
int currentKey=(*record)[i];
if(j==0 || currentKey!=last){
PUSH_BACK;
last=currentKey;
j=1;
}else{
j++;
}
}
PUSH_BACK;
#undef PUSH_BACK
#ifdef RECORD_FILE_DEBUG
//add record file debug data.
{
obj.attributes["recordKeyPressLog"].push_back(player.keyPressLog());
vector<SDL_Rect> &playerPosition=player.playerPosition();
string s;
char c[32];
sprintf(c,"%d\n",int(playerPosition.size()));
s=c;
for(unsigned int i=0;i<playerPosition.size();i++){
SDL_Rect& r=playerPosition[i];
sprintf(c,"%d %d\n",r.x,r.y);
s+=c;
}
obj.attributes["recordPlayerPosition"].push_back(s);
}
#endif
//save it
objSerializer.saveNodeToFile(fileName,&obj,true,true);
//remove current level from node to prevent delete it.
obj.subNodes.clear();
}
void Game::loadRecord(ImageManager& imageManager, SDL_Renderer& renderer, const char* fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode obj;
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),&obj,true)){
cerr<<"ERROR: Can't load record file "<<s<<endl;
return;
}
}
//find the node named 'map'.
bool loaded=false;
for(unsigned int i=0;i<obj.subNodes.size();i++){
if(obj.subNodes[i]->name=="map"){
//load the level. (fileName=???)
loadLevelFromNode(imageManager,renderer,obj.subNodes[i],"???");
//remove this node to prevent delete it.
obj.subNodes[i]=NULL;
//over
loaded=true;
break;
}
}
if(!loaded){
cerr<<"ERROR: Can't find subnode named 'map' from record file"<<endl;
return;
}
//load the record.
{
vector<int> *record=player.getRecord();
record->clear();
vector<string> &v=obj.attributes["record"];
for(unsigned int i=0;i<v.size();i++){
string &s=v[i];
string::size_type pos=s.find_first_of('*');
if(pos==string::npos){
//1 item only.
int i=atoi(s.c_str());
record->push_back(i);
}else{
//contains many items.
int i=atoi(s.substr(0,pos).c_str());
int j=atoi(s.substr(pos+1).c_str());
for(;j>0;j--){
record->push_back(i);
}
}
}
}
#ifdef RECORD_FILE_DEBUG
//load the debug data
{
vector<string> &v=obj.attributes["recordPlayerPosition"];
vector<SDL_Rect> &playerPosition=player.playerPosition();
playerPosition.clear();
if(!v.empty()){
if(!v[0].empty()){
stringstream st(v[0]);
int m;
st>>m;
for(int i=0;i<m;i++){
SDL_Rect r;
st>>r.x>>r.y;
r.w=0;
r.h=0;
playerPosition.push_back(r);
}
}
}
}
#endif
//play the record.
//TODO: tell the level manager don't save the level progress.
player.playRecord();
shadow.playRecord(); //???
}
/////////////EVENT///////////////
void Game::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//First of all let the player handle input.
player.handleInput(&shadow);
//Check for an SDL_QUIT event.
if(event.type==SDL_QUIT){
//We need to quit so enter STATE_EXIT.
setNextState(STATE_EXIT);
}
//Check for the escape key.
if(stateID != STATE_LEVEL_EDITOR && inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
//Escape means we go one level up, to the level select state.
setNextState(STATE_LEVEL_SELECT);
//Save the progress.
levels->saveLevelProgress();
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}
//Check if 'R' is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_RESTART)){
//Restart game only if we are not watching a replay.
if (!player.isPlayFromRecord() || interlevel) {
//Reset the game at next frame.
isReset = true;
//Also delete any gui (most likely the interlevel gui). Only in game mode.
if (GUIObjectRoot && stateID != STATE_LEVEL_EDITOR){
delete GUIObjectRoot;
GUIObjectRoot = NULL;
}
//And set interlevel to false.
interlevel = false;
}
}
//Check for the next level buttons when in the interlevel popup.
if (inputMgr.isKeyDownEvent(INPUTMGR_SPACE) || inputMgr.isKeyDownEvent(INPUTMGR_SELECT)){
if(interlevel){
//The interlevel popup is shown so we need to delete it.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Now goto the next level.
gotoNextLevel(imageManager,renderer);
}
}
//Check if tab is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
//Switch the camera mode.
switch(cameraMode){
case CAMERA_PLAYER:
cameraMode=CAMERA_SHADOW;
break;
case CAMERA_SHADOW:
case CAMERA_CUSTOM:
cameraMode=CAMERA_PLAYER;
break;
}
}
}
/////////////////LOGIC///////////////////
void Game::logic(ImageManager& imageManager, SDL_Renderer& renderer){
//Add one tick to the time.
time++;
//NOTE: This code reverts some changes in commit 5f03ae5.
//This is part of old prepareFrame() code.
//This is needed since otherwise the script function block:setLocation() and block:moveTo() are completely broken.
//Later we should rewrite collision system completely which will remove this piece of code.
//NOTE: In new collision system the effect of dx/dy/xVel/yVel should only be restricted in one frame.
for (auto obj : levelObjects) {
switch (obj->type) {
default:
obj->dx = obj->dy = obj->xVel = obj->yVel = 0;
break;
case TYPE_PUSHABLE:
//NOTE: Currently the dx/dy/etc. of pushable blocks are still carry across frames, in order to make the collision system work correct.
break;
case TYPE_CONVEYOR_BELT: case TYPE_SHADOW_CONVEYOR_BELT:
//NOTE: We let the conveyor belt itself to reset its xVel/yVel.
obj->dx = obj->dy = 0;
break;
}
}
+ //NOTE2: The above code breaks pushable block with moving block in most cases,
+ //more precisely, if the pushable block is processed before the moving block then things may be broken.
+
//Process delay execution scripts.
getScriptExecutor()->processDelayExecution();
//Process any event in the queue.
for(unsigned int idx=0;idx<eventQueue.size();idx++){
//Get the event from the queue.
typeGameObjectEvent &e=eventQueue[idx];
//Check if the it has an id attached to it.
if(e.target){
//NOTE: Should we check if the target still exists???
e.target->onEvent(e.eventType);
}else if(e.flags&1){
//Loop through the levelObjects and give them the event if they have the right id.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
if(levelObjects[i]->id==e.id){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}else{
//Loop through the levelObjects and give them the event.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}
//Done processing the events so clear the queue.
eventQueue.clear();
//Check if we should save/load state.
//NOTE: This happens after event handling so no eventQueue has to be saved/restored.
if(saveStateNextTime){
saveState();
}else if(loadStateNextTime){
loadState();
}
saveStateNextTime=false;
loadStateNextTime=false;
//Loop through the gameobjects to update them.
for(unsigned int i=0;i<levelObjects.size();i++){
//Send GameObjectEvent_OnEnterFrame event to the script
levelObjects[i]->onEvent(GameObjectEvent_OnEnterFrame);
}
- for(unsigned int i=0;i<levelObjects.size();i++){
- //Let the gameobject handle movement.
- levelObjects[i]->move();
+ //Let the gameobject handle movement.
+ {
+ std::vector<Block*> pushableBlocks;
+
+ //First we process blocks which are not pushable blocks.
+ for (auto o : levelObjects) {
+ if (o->type == TYPE_PUSHABLE) {
+ pushableBlocks.push_back(o);
+ } else {
+ o->move();
+ }
+ }
+
+ //Sort pushable blocks by their position, which is an ad-hoc workaround for
+ //<https://forum.freegamedev.net/viewtopic.php?f=48&t=8047#p77692>.
+ std::stable_sort(pushableBlocks.begin(), pushableBlocks.end(),
+ [](const Block* obj1, const Block* obj2)->bool
+ {
+ SDL_Rect r1 = const_cast<Block*>(obj1)->getBox(), r2 = const_cast<Block*>(obj2)->getBox();
+ if (r1.y > r2.y) return true;
+ else if (r1.y < r2.y) return false;
+ else return r1.x < r2.x;
+ });
+
+ //Now we process pushable blocks.
+ for (auto o : pushableBlocks) {
+ o->move();
+ }
}
//Also update the scenery.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it){
it->second->updateAnimation();
}
//Let the player store his move, if recording.
player.shadowSetState();
//Let the player give his recording to the shadow, if configured.
player.shadowGiveState(&shadow);
//NOTE: to fix bugs regarding player/shadow swap, we should first process collision of player/shadow then move them
const SDL_Rect playerLastPosition = player.getBox();
const SDL_Rect shadowLastPosition = shadow.getBox();
//NOTE: The following is ad-hoc code to fix shadow on blocked player on conveyor belt bug
if (shadow.holdingOther) {
//We need to process shadow collision first if shadow is holding player.
//Let the shadow decide his move, if he's playing a recording.
shadow.moveLogic();
//Check collision for shadow.
shadow.collision(levelObjects, NULL);
//Get the new position of it.
const SDL_Rect r = shadow.getBox();
//Check collision for player. Transfer the velocity of shadow to it only if the shadow moves its position.
player.collision(levelObjects, (r.x != shadowLastPosition.x || r.y != shadowLastPosition.y) ? &shadow : NULL);
} else {
//Otherwise we process player first.
//Check collision for player.
player.collision(levelObjects, NULL);
//Get the new position of it.
const SDL_Rect r = player.getBox();
//Now let the shadow decide his move, if he's playing a recording.
shadow.moveLogic();
//Check collision for shadow. Transfer the velocity of player to it only if the player moves its position.
shadow.collision(levelObjects, (r.x != playerLastPosition.x || r.y != playerLastPosition.y) ? &player : NULL);
}
//Let the player move.
player.move(levelObjects, playerLastPosition.x, playerLastPosition.y);
//Let the shadow move.
shadow.move(levelObjects, shadowLastPosition.x, shadowLastPosition.y);
//Check collision and stuff for the shadow and player.
player.otherCheck(&shadow);
//Update the camera.
switch(cameraMode){
case CAMERA_PLAYER:
player.setMyCamera();
break;
case CAMERA_SHADOW:
shadow.setMyCamera();
break;
case CAMERA_CUSTOM:
//NOTE: The target is (should be) screen size independent so calculate the real target x and y here.
int targetX=cameraTarget.x-(SCREEN_WIDTH/2);
int targetY=cameraTarget.y-(SCREEN_HEIGHT/2);
//Move the camera to the cameraTarget.
if(camera.x>targetX){
camera.x-=(camera.x-targetX)>>4;
//Make sure we don't go too far.
if(camera.x<targetX)
camera.x=targetX;
}else if(camera.x<targetX){
camera.x+=(targetX-camera.x)>>4;
//Make sure we don't go too far.
if(camera.x>targetX)
camera.x=targetX;
}
if(camera.y>targetY){
camera.y-=(camera.y-targetY)>>4;
//Make sure we don't go too far.
if(camera.y<targetY)
camera.y=targetY;
}else if(camera.y<targetY){
camera.y+=(targetY-camera.y)>>4;
//Make sure we don't go too far.
if(camera.y>targetY)
camera.y=targetY;
}
break;
}
//Check if we won.
if(won){
//Check if it's playing from record
if(player.isPlayFromRecord() && !interlevel){
recordingEnded(imageManager,renderer);
}else{
//the string to store auto-save record path.
string bestTimeFilePath,bestRecordingFilePath;
//and if we can't get test path.
bool filePathError=false;
//Get current level
LevelPack::Level *level=levels->getLevel();
//Now check if we should update statistics
{
//Get previous and current medal
int oldMedal=level->won?1:0,newMedal=1;
int bestTime=level->time;
int targetTime=level->targetTime;
int bestRecordings=level->recordings;
int targetRecordings=level->targetRecordings;
if(oldMedal){
- if(targetTime<0){
- oldMedal=3;
- }else{
- if(targetTime<0 || bestTime<=targetTime)
- oldMedal++;
- if(targetRecordings<0 || bestRecordings<=targetRecordings)
- oldMedal++;
- }
+ if(bestTime>=0 && (targetTime<0 || bestTime<=targetTime))
+ oldMedal++;
+ if(bestRecordings>=0 && (targetRecordings<0 || bestRecordings<=targetRecordings))
+ oldMedal++;
}else{
bestTime=time;
bestRecordings=recordings;
}
- if(bestTime==-1 || bestTime>time) bestTime=time;
- if(bestRecordings==-1 || bestRecordings>recordings) bestRecordings=recordings;
+ if(bestTime<0 || bestTime>time) bestTime=time;
+ if(bestRecordings<0 || bestRecordings>recordings) bestRecordings=recordings;
- if(targetTime<0){
- newMedal=3;
- }else{
- if(targetTime<0 || bestTime<=targetTime)
- newMedal++;
- if(targetRecordings<0 || bestRecordings<=targetRecordings)
- newMedal++;
- }
+ if(targetTime<0 || bestTime<=targetTime)
+ newMedal++;
+ if(targetRecordings<0 || bestRecordings<=targetRecordings)
+ newMedal++;
//Check if we need to update statistics
if(newMedal>oldMedal){
switch(oldMedal){
case 0:
statsMgr.completedLevels++;
break;
case 2:
statsMgr.silverLevels--;
break;
}
switch(newMedal){
case 2:
statsMgr.silverLevels++;
break;
case 3:
statsMgr.goldLevels++;
break;
}
}
}
//Check the achievement "Complete a level with checkpoint, but without saving"
if (objLastCheckPoint == NULL) {
for (auto obj : levelObjects) {
if (obj->type == TYPE_CHECKPOINT) {
statsMgr.newAchievement("withoutsave");
break;
}
}
}
//Set the current level won.
level->won=true;
if(level->time==-1 || level->time>time){
level->time=time;
//save the best-time game record.
if(bestTimeFilePath.empty()){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestTimeFilePath.empty()){
cerr<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestTimeFilePath.c_str());
}
}
if(level->recordings==-1 || level->recordings>recordings){
level->recordings=recordings;
//save the best-recordings game record.
if(bestRecordingFilePath.empty() && !filePathError){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestRecordingFilePath.empty()){
cerr<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestRecordingFilePath.c_str());
}
}
//Set the next level unlocked if it exists.
if(levels->getCurrentLevel()+1<levels->getLevelCount()){
levels->setLocked(levels->getCurrentLevel()+1);
}
//And save the progress.
levels->saveLevelProgress();
//Now go to the interlevel screen.
replayPlay(imageManager,renderer);
//Update achievements
if(levels->levelpackName=="tutorial") statsMgr.updateTutorialAchievements();
statsMgr.updateLevelAchievements();
//NOTE: We set isReset false to prevent the user from getting a best time of 0.00s and 0 recordings.
isReset = false;
}
}
won=false;
//Check if we should reset.
if (isReset) {
//NOTE: we don't need to reset save ??? it looks like that there are no bugs
reset(false, false);
}
isReset=false;
}
/////////////////RENDER//////////////////
void Game::render(ImageManager&,SDL_Renderer &renderer){
//First of all render the background.
{
//Get a pointer to the background.
ThemeBackground* bg=background;
//Check if the background is null, but there are themes.
if(bg==NULL && objThemes.themeCount()>0){
//Get the background from the first theme in the stack.
bg=objThemes[0]->getBackground(false);
}
//Check if the background isn't null.
if(bg){
//It isn't so draw it.
bg->draw(renderer);
//And if it's the loaded background then also update the animation.
//FIXME: Updating the animation in the render method?
if(bg==background)
bg->updateAnimation();
}else{
//There's no background so fill the screen with white.
SDL_SetRenderDrawColor(&renderer, 255,255,255,255);
SDL_RenderClear(&renderer);
}
}
//Now draw the blackground layers.
auto it = sceneryLayers.begin();
for (; it != sceneryLayers.end(); ++it){
if (it->first >= "f") break; // now we meet a foreground layer
it->second->show(renderer);
}
//Now we draw the levelObjects.
- for(unsigned int o=0; o<levelObjects.size(); o++){
- levelObjects[o]->show(renderer);
+ {
+ //NEW: always render the pushable blocks in front of other blocks
+ std::vector<Block*> pushableBlocks;
+
+ for (auto o : levelObjects) {
+ if (o->type == TYPE_PUSHABLE) {
+ pushableBlocks.push_back(o);
+ } else {
+ o->show(renderer);
+ }
+ }
+
+ for (auto o : pushableBlocks) {
+ o->show(renderer);
+ }
}
//Followed by the player and the shadow.
//NOTE: We draw the shadow first, because he needs to be behind the player.
shadow.show(renderer);
player.show(renderer);
//Now draw the foreground layers.
for (; it != sceneryLayers.end(); ++it){
it->second->show(renderer);
}
//Show the levelName if it isn't the level editor.
if(stateID!=STATE_LEVEL_EDITOR && bmTips[0]!=NULL && !interlevel){
withTexture(*bmTips[0], [&](SDL_Rect r){
drawGUIBox(-2,SCREEN_HEIGHT-r.h-4,r.w+8,r.h+6,renderer,0xFFFFFFFF);
applyTexture(2,SCREEN_HEIGHT-r.h,*bmTips[0],renderer,NULL);
});
}
//Check if there's a tooltip.
//NOTE: gameTipIndex 0 is used for the levelName, 1 for shadow death, 2 for restart text, 3 for restart+checkpoint.
if(gameTipIndex>3 && gameTipIndex<TYPE_MAX){
//Check if there's a tooltip for the type.
if(bmTips[gameTipIndex]==NULL){
//There isn't thus make it.
string s;
string keyCode = InputManagerKeyCode::describeTwo(inputMgr.getKeyCode(INPUTMGR_ACTION, false), inputMgr.getKeyCode(INPUTMGR_ACTION, true));
transform(keyCode.begin(),keyCode.end(),keyCode.begin(),::toupper);
switch(gameTipIndex){
case TYPE_CHECKPOINT:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to save the game."),keyCode);
break;
case TYPE_SWAP:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to swap the position of player and shadow."),keyCode);
break;
case TYPE_SWITCH:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to activate the switch."),keyCode);
break;
case TYPE_PORTAL:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to teleport."),keyCode);
break;
}
//If we have a string then it's a supported GameObject type.
if(!s.empty()){
bmTips[gameTipIndex]=textureFromText(renderer, *fontText, s.c_str(), objThemes.getTextColor(true));
}
}
//We already have a gameTip for this type so draw it.
if(bmTips[gameTipIndex]!=NULL){
withTexture(*bmTips[gameTipIndex], [&](SDL_Rect r){
drawGUIBox(-2,-2,r.w+8,r.h+6,renderer,0xFFFFFFFF);
applyTexture(2,2,*bmTips[gameTipIndex],renderer);
});
}
}
//Set the gameTip to 0.
gameTipIndex=0;
// Limit the scope of bm, as it's a borrowed pointer.
{
//Pointer to the sdl texture that will contain a message, if any.
SDL_Texture* bm=NULL;
//Check if the player is dead, meaning we draw a message.
if(player.dead){
//Get user configured restart key
string keyCodeRestart = InputManagerKeyCode::describeTwo(inputMgr.getKeyCode(INPUTMGR_RESTART, false), inputMgr.getKeyCode(INPUTMGR_RESTART, true));
transform(keyCodeRestart.begin(),keyCodeRestart.end(),keyCodeRestart.begin(),::toupper);
//The player is dead, check if there's a state that can be loaded.
if(player.canLoadState()){
//Now check if the tip is already made, if not make it.
if(bmTips[3]==NULL){
//Get user defined key for loading checkpoint
string keyCodeLoad = InputManagerKeyCode::describeTwo(inputMgr.getKeyCode(INPUTMGR_LOAD, false), inputMgr.getKeyCode(INPUTMGR_LOAD, true));
transform(keyCodeLoad.begin(),keyCodeLoad.end(),keyCodeLoad.begin(),::toupper);
//Draw string
bmTips[3]=textureFromText(renderer, *fontText,//TTF_RenderUTF8_Blended(fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - first %s means currently configured key to restart game
/// - Second %s means configured key to load from last save
tfm::format(_("Press %s to restart current level or press %s to load the game."),
keyCodeRestart,keyCodeLoad).c_str(),
objThemes.getTextColor(true));
}
bm=bmTips[3].get();
}else{
//Now check if the tip is already made, if not make it.
if(bmTips[2]==NULL){
bmTips[2]=textureFromText(renderer, *fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with currently configured key to restart game
tfm::format(_("Press %s to restart current level."),keyCodeRestart).c_str(),
objThemes.getTextColor(true));
}
bm=bmTips[2].get();
}
}
//Check if the shadow has died (and there's no other notification).
//NOTE: We use the shadow's jumptime as countdown, this variable isn't used when the shadow is dead.
if(shadow.dead && bm==NULL && shadow.jumpTime>0){
//Now check if the tip is already made, if not make it.
if(bmTips[1]==NULL){
bmTips[1]=textureFromText(renderer, *fontText,
_("Your shadow has died."),
objThemes.getTextColor(true));
}
bm=bmTips[1].get();
//NOTE: Logic in the render loop, we substract the shadow's jumptime by one.
shadow.jumpTime--;
//return view to player and keep it there
cameraMode=CAMERA_PLAYER;
}
//Draw the tip.
if(bm!=NULL){
const SDL_Rect textureSize = rectFromTexture(*bm);
int x=(SCREEN_WIDTH-textureSize.w)/2;
int y=32;
drawGUIBox(x-8,y-8,textureSize.w+16,textureSize.h+14,renderer,0xFFFFFFFF);
applyTexture(x,y,*bm,renderer);
}
}
//Show the number of collectables the user has collected if there are collectables in the level.
//We hide this when interlevel.
if(currentCollectables<=totalCollectables && totalCollectables!=0 && !interlevel && time>0){
if(collectablesTexture.needsUpdate(currentCollectables)) {
//Temp stringstream just to addup all the text nicely
std::stringstream temp;
temp << currentCollectables << "/" << totalCollectables;
collectablesTexture.update(currentCollectables,
textureFromText(renderer,
*fontText,
temp.str().c_str(),
objThemes.getTextColor(true)));
}
SDL_Rect bmSize = rectFromTexture(*collectablesTexture.get());
//Draw background
drawGUIBox(SCREEN_WIDTH-bmSize.w-34,SCREEN_HEIGHT-bmSize.h-4,bmSize.w+34+2,bmSize.h+4+2,renderer,0xFFFFFFFF);
//Draw the collectable icon
collectable.draw(renderer,SCREEN_WIDTH-50+12,SCREEN_HEIGHT-50+10);
//Draw text
applyTexture(SCREEN_WIDTH-50-bmSize.w+22,SCREEN_HEIGHT-bmSize.h,collectablesTexture.getTexture(),renderer);
}
- //show time and records used in level editor.
- if(stateID==STATE_LEVEL_EDITOR && time>0){
+ //show time and records used in level editor or during replay.
+ if((stateID==STATE_LEVEL_EDITOR || (!interlevel && player.isPlayFromRecord())) && time>0){
const SDL_Color fg=objThemes.getTextColor(true),bg={255,255,255,255};
const int alpha = 160;
if (recordingsTexture.needsUpdate(recordings)) {
recordingsTexture.update(recordings,
textureFromTextShaded(
renderer,
*fontText,
tfm::format(ngettext("%d recording","%d recordings",recordings).c_str(),recordings).c_str(),
fg,
bg
));
SDL_SetTextureAlphaMod(recordingsTexture.get(),alpha);
}
int y=SCREEN_HEIGHT - textureHeight(*recordingsTexture.get());
+ if (stateID != STATE_LEVEL_EDITOR && bmTips[0] != NULL && !interlevel) {
+ y -= textureHeight(bmTips[0]) + 4;
+ }
- applyTexture(0,y,*recordingsTexture.get(), renderer);
+ applyTexture(0,y,*recordingsTexture.get(), renderer);
if(timeTexture.needsUpdate(time)) {
const size_t len = 32;
char c[len];
- SDL_snprintf(c,len,"%-.2fs",time/40.0f);
+ SDL_snprintf(c,len,"%-.2fs",time/40.0);
timeTexture.update(time,
textureFromTextShaded(
renderer,
*fontText,
c,
fg,
bg
));
- y-=textureHeight(*timeTexture.get());
}
+ y -= textureHeight(*timeTexture.get());
+
applyTexture(0,y,*timeTexture.get(), renderer);
}
//Draw the current action in the upper right corner.
if(player.record){
const SDL_Rect r = { 0, 0, 50, 50 };
applyTexture(SCREEN_WIDTH - 50, 0, *action, renderer, &r);
} else if (shadow.state != 0){
const SDL_Rect r={50,0,50,50};
applyTexture(SCREEN_WIDTH-50,0,*action,renderer,&r);
}
//if the game is play from record then draw something indicates it
if(player.isPlayFromRecord()){
//Dim the screen if interlevel is true.
if( interlevel){
dimScreen(renderer,191);
}else if((time & 0x10)==0x10){
// FIXME: replace this ugly ad-hoc animation by a better one
const SDL_Rect r={50,0,50,50};
applyTexture(0,0,*action,renderer,&r);
- applyTexture(0,SCREEN_HEIGHT-50,*action,renderer,&r);
- applyTexture(SCREEN_WIDTH-50,SCREEN_HEIGHT-50,*action,renderer,&r);
+ //applyTexture(0,SCREEN_HEIGHT-50,*action,renderer,&r);
+ //applyTexture(SCREEN_WIDTH-50,SCREEN_HEIGHT-50,*action,renderer,&r);
}
}else if(player.objNotificationBlock){
//If the player is in front of a notification block show the message.
//And it isn't a replay.
//Check if we need to update the notification message texture.
const auto& blockId = player.objNotificationBlock;
int maxWidth = 0;
int y = 20;
//We check against blockId rather than the full message, as blockId is most likely shorter.
if(notificationTexture.needsUpdate(blockId)) {
const std::string &untranslated_message=player.objNotificationBlock->message;
std::string message=_CC(levels->getDictionaryManager(),untranslated_message);
std::vector<std::string> string_data;
//Trim the message.
{
size_t lps = message.find_first_not_of("\n\r \t");
if (lps == string::npos) {
message.clear(); // it's completely empty
} else {
message = message.substr(lps, message.find_last_not_of("\n\r \t") - lps + 1);
}
}
//Split the message into lines.
for (int lps = 0;;) {
// determine the end of line
int lpe = lps;
for (; message[lpe] != '\n' && message[lpe] != '\r' && message[lpe] != '\0'; lpe++);
string_data.push_back(message.substr(lps, lpe - lps));
// break if the string ends
if (message[lpe] == '\0') break;
// skip "\r\n" for Windows line ending
if (message[lpe] == '\r' && message[lpe + 1] == '\n') lpe++;
// point to the start of next line
lps = lpe + 1;
}
vector<SurfacePtr> lines;
//Create the image for each lines
for (int i = 0; i < (int)string_data.size(); i++) {
//Integer used to center the sentence horizontally.
int x = 0;
TTF_SizeUTF8(fontText, string_data[i].c_str(), &x, NULL);
//Find out largest width
if (x>maxWidth)
maxWidth = x;
lines.emplace_back(TTF_RenderUTF8_Blended(fontText, string_data[i].c_str(), objThemes.getTextColor(true)));
//Increase y with 25, about the height of the text.
y += 25;
}
maxWidth+=SCREEN_WIDTH*0.15;
SurfacePtr surf = createSurface(maxWidth, y);
int y1 = y;
for(SurfacePtr &s : lines) {
if(s) {
applySurface((surf->w-s->w)/2,surf->h - y1,s.get(),surf.get(),NULL);
}
y1 -= 25;
}
notificationTexture.update(blockId, textureUniqueFromSurface(renderer,std::move(surf)));
} else {
auto texSize = rectFromTexture(*notificationTexture.get());
maxWidth=texSize.w;
y=texSize.h;
}
drawGUIBox((SCREEN_WIDTH-maxWidth)/2,SCREEN_HEIGHT-y-25,maxWidth,y+20,renderer,0xFFFFFFBF);
applyTexture((SCREEN_WIDTH-maxWidth)/2,SCREEN_HEIGHT-y,notificationTexture.getTexture(),renderer);
}
}
void Game::resize(ImageManager&, SDL_Renderer& /*renderer*/){
//Check if the interlevel popup is shown.
if(interlevel && GUIObjectRoot){
GUIObjectRoot->left=(SCREEN_WIDTH-GUIObjectRoot->width)/2;
}
}
void Game::replayPlay(ImageManager& imageManager,SDL_Renderer& renderer){
//Set interlevel true.
interlevel=true;
//Make a copy of the playerButtons.
vector<int> recordCopy=player.recordButton;
//Reset the game.
//NOTE: We don't reset the saves. I'll see that if it will introduce bugs.
reset(false, false);
//Make the cursor visible when the interlevel popup is up.
SDL_ShowCursor(SDL_ENABLE);
//Set the copy of playerButtons back.
player.recordButton=recordCopy;
//Now play the recording.
player.playRecord();
//Create the gui if it isn't already done.
if(!GUIObjectRoot){
//Create a new GUIObjectRoot the size of the screen.
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Make child widgets change color properly according to theme.
GUIObjectRoot->inDialog=true;
//Create a GUIFrame for the upper frame.
- GUIFrame* upperFrame=new GUIFrame(imageManager,renderer,0,4,0,68);
+ GUIFrame* upperFrame=new GUIFrame(imageManager,renderer,0,4,0,74);
GUIObjectRoot->addChild(upperFrame);
//Render the You've finished: text and add it to a GUIImage.
//NOTE: The texture is managed by the GUIImage so no need to free it ourselfs.
auto bm = SharedTexture(textureFromText(renderer, *fontGUI,_("You've finished:"),objThemes.getTextColor(true)));
const SDL_Rect textureSize = rectFromTexture(*bm);
GUIImage* title=new GUIImage(imageManager,renderer,0,4-GUI_FONT_RAISE,textureSize.w,textureSize.h,bm);
upperFrame->addChild(title);
//Create the sub title.
string s;
if (levels->getLevelCount()>0){
/// TRANSLATORS: Please do not remove %s or %d from your translation:
/// - %d means the level number in a levelpack
/// - %s means the name of current level
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_CC(levels->getDictionaryManager(),levelName));
}
- GUIObject* obj=new GUILabel(imageManager,renderer,0,40,0,28,s.c_str(),0,true,true,GUIGravityCenter);
+ GUIObject* obj=new GUILabel(imageManager,renderer,0,44,0,28,s.c_str(),0,true,true,GUIGravityCenter);
upperFrame->addChild(obj);
obj->render(renderer,0,0,false);
//Determine the width the upper frame should have.
int width;
if(textureSize.w>obj->width)
width=textureSize.w+32;
else
width=obj->width+32;
//Set the left of the title.
title->left=(width-title->width)/2;
//Set the width of the level label to the width of the frame for centering.
obj->width=width;
//Now set the position and width of the frame.
upperFrame->width=width;
upperFrame->left=(SCREEN_WIDTH-width)/2;
//Now create a GUIFrame for the lower frame.
GUIFrame* lowerFrame=new GUIFrame(imageManager,renderer,0,SCREEN_HEIGHT-140,570,135);
GUIObjectRoot->addChild(lowerFrame);
//The different values.
int bestTime=levels->getLevel()->time;
int targetTime=levels->getLevel()->targetTime;
int bestRecordings=levels->getLevel()->recordings;
int targetRecordings=levels->getLevel()->targetRecordings;
int medal=1;
- if(targetTime<0){
- medal=3;
- }else{
- if(targetTime<0 || bestTime<=targetTime)
- medal++;
- if(targetRecordings<0 || bestRecordings<=targetRecordings)
- medal++;
- }
-
+ if(bestTime>=0 && (targetTime<0 || bestTime<=targetTime))
+ medal++;
+ if(bestRecordings>=0 && (targetRecordings<0 || bestRecordings<=targetRecordings))
+ medal++;
+
int maxWidth=0;
int x=20;
//Is there a target time for this level?
int timeY=0;
bool isTargetTime=true;
- if(targetTime<=0){
+ if(targetTime<0){
isTargetTime=false;
timeY=12;
}
//Create the labels with the time and best time.
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
- obj=new GUILabel(imageManager,renderer,x,10+timeY,-1,36,tfm::format(_("Time: %-.2fs"),time/40.0f).c_str());
+ obj=new GUILabel(imageManager,renderer,x,10+timeY,-1,36,tfm::format(_("Time: %-.2fs"),time/40.0).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
- obj=new GUILabel(imageManager,renderer,x,34+timeY,-1,36,tfm::format(_("Best time: %-.2fs"),bestTime/40.0f).c_str());
+ obj=new GUILabel(imageManager,renderer,x,34+timeY,-1,36,tfm::format(_("Best time: %-.2fs"),bestTime/40.0).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
if(isTargetTime){
- obj=new GUILabel(imageManager,renderer,x,58,-1,36,tfm::format(_("Target time: %-.2fs"),targetTime/40.0f).c_str());
+ obj=new GUILabel(imageManager,renderer,x,58,-1,36,tfm::format(_("Target time: %-.2fs"),targetTime/40.0).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth+20;
//Is there target recordings for this level?
int recsY=0;
bool isTargetRecs=true;
if(targetRecordings<0){
isTargetRecs=false;
recsY=12;
}
//Now the ones for the recordings.
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
obj=new GUILabel(imageManager,renderer,x,10+recsY,-1,36,tfm::format(_("Recordings: %d"),recordings).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
obj=new GUILabel(imageManager,renderer,x,34+recsY,-1,36,tfm::format(_("Best recordings: %d"),bestRecordings).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
if(isTargetRecs){
obj=new GUILabel(imageManager,renderer,x,58,-1,36,tfm::format(_("Target recordings: %d"),targetRecordings).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth;
//The medal that is earned.
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with name of a prize medal (gold, silver or bronze)
string s1=tfm::format(_("You earned the %s medal"),(medal>1)?(medal==3)?_("GOLD"):_("SILVER"):_("BRONZE"));
obj=new GUILabel(imageManager,renderer,50,92,-1,36,s1.c_str(),0,true,true,GUIGravityCenter);
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->left+obj->width>x){
x=obj->left+obj->width+30;
}else{
obj->left=20+(x-20-obj->width)/2;
}
//Create the rectangle for the earned medal.
SDL_Rect r;
r.x=(medal-1)*30;
r.y=0;
r.w=30;
r.h=30;
//Create the medal on the left side.
obj=new GUIImage(imageManager,renderer,16,92,30,30,medals,r);
lowerFrame->addChild(obj);
//And the medal on the right side.
obj=new GUIImage(imageManager,renderer,x-24,92,30,30,medals,r);
lowerFrame->addChild(obj);
//Create the three buttons, Menu, Restart, Next.
/// TRANSLATORS: used as return to the level selector menu
GUIObject* b1=new GUIButton(imageManager,renderer,x,10,-1,36,_("Menu"),0,true,true,GUIGravityCenter);
b1->name="cmdMenu";
b1->eventCallback=this;
lowerFrame->addChild(b1);
b1->render(renderer,0,0,true);
/// TRANSLATORS: used as restart level
GUIObject* b2=new GUIButton(imageManager,renderer,x,50,-1,36,_("Restart"),0,true,true,GUIGravityCenter);
b2->name="cmdRestart";
b2->eventCallback=this;
lowerFrame->addChild(b2);
b2->render(renderer,0,0,true);
/// TRANSLATORS: used as next level
GUIObject* b3=new GUIButton(imageManager,renderer,x,90,-1,36,_("Next"),0,true,true,GUIGravityCenter);
b3->name="cmdNext";
b3->eventCallback=this;
lowerFrame->addChild(b3);
b3->render(renderer,0,0,true);
maxWidth=b1->width;
if(b2->width>maxWidth)
maxWidth=b2->width;
if(b3->width>maxWidth)
maxWidth=b3->width;
b1->left=b2->left=b3->left=x+maxWidth/2;
x+=maxWidth;
lowerFrame->width=x;
lowerFrame->left=(SCREEN_WIDTH-lowerFrame->width)/2;
}
}
void Game::recordingEnded(ImageManager& imageManager, SDL_Renderer& renderer){
//Check if it's a normal replay, if so just stop.
if(!interlevel){
//Show the cursor so that the user can press the ok button.
SDL_ShowCursor(SDL_ENABLE);
//Now show the message box.
msgBox(imageManager,renderer,_("Game replay is done."),MsgBoxOKOnly,_("Game Replay"));
//Go to the level select menu.
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else{
//Instead of directly replaying we set won true to let the Game handle the replaying at the end of the update cycle.
won=true;
}
}
bool Game::canSaveState(){
return (player.canSaveState() && shadow.canSaveState());
}
bool Game::saveState(){
//Check if the player and shadow can save the current state.
if(canSaveState()){
//Let the player and the shadow save their state.
player.saveState();
shadow.saveState();
//Save the stats.
timeSaved=time;
recordingsSaved=recordings;
recentSwapSaved=recentSwap;
//Save the camera mode and target.
cameraModeSaved=cameraMode;
cameraTargetSaved=cameraTarget;
//Save the current collectables
currentCollectablesSaved=currentCollectables;
//Save scripts.
copyCompiledScripts(getScriptExecutor()->getLuaState(), compiledScripts, savedCompiledScripts);
//Save other state, for example moving blocks.
for (auto block : levelObjects){
block->saveState();
copyCompiledScripts(getScriptExecutor()->getLuaState(), block->compiledScripts, block->savedCompiledScripts);
}
//Also save states of scenery layers.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
it->second->saveAnimation();
}
//Also save the background animation, if any.
if(background)
background->saveAnimation();
if(!player.isPlayFromRecord() && !interlevel){
//Update achievements
Uint32 t=SDL_GetTicks()+5000; //Add a bias to prevent bugs
if(recentSave+1000>t){
statsMgr.newAchievement("panicSave");
}
recentSave=t;
//Update statistics.
statsMgr.saveTimes++;
//Update achievements
switch(statsMgr.saveTimes){
case 1000:
statsMgr.newAchievement("save1k");
break;
}
}
//Save the state for script executor.
getScriptExecutor()->saveState();
//Execute the onSave event.
executeScript(LevelEvent_OnSave);
//Return true.
return true;
}
//We can't save the state so return false.
return false;
}
bool Game::loadState(){
//Check if there's a state that can be loaded.
if(player.canLoadState() && shadow.canLoadState()){
//Let the player and the shadow load their state.
player.loadState();
shadow.loadState();
//Load the stats.
time=timeSaved;
recordings=recordingsSaved;
recentSwap=recentSwapSaved;
//Load the camera mode and target.
cameraMode=cameraModeSaved;
cameraTarget=cameraTargetSaved;
//Load the current collactbles
currentCollectables=currentCollectablesSaved;
//Load scripts.
copyCompiledScripts(getScriptExecutor()->getLuaState(), savedCompiledScripts, compiledScripts);
//Load other state, for example moving blocks.
for(auto block:levelObjects){
block->loadState();
copyCompiledScripts(getScriptExecutor()->getLuaState(), block->savedCompiledScripts, block->compiledScripts);
}
//Also load states of scenery layers.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
it->second->loadAnimation();
}
//Also load the background animation, if any.
if(background)
background->loadAnimation();
if(!player.isPlayFromRecord() && !interlevel){
//Update achievements.
Uint32 t=SDL_GetTicks()+5000; //Add a bias to prevent bugs
if(recentLoad+1000>t){
statsMgr.newAchievement("panicLoad");
}
recentLoad=t;
//Update statistics.
statsMgr.loadTimes++;
//Update achievements
switch(statsMgr.loadTimes){
case 1000:
statsMgr.newAchievement("load1k");
break;
}
}
//Load the state for script executor.
getScriptExecutor()->loadState();
//Execute the onLoad event, if any.
executeScript(LevelEvent_OnLoad);
//Return true.
return true;
}
//We can't load the state so return false.
return false;
}
void Game::reset(bool save,bool noScript){
//We need to reset the game so we also reset the player and the shadow.
player.reset(save);
shadow.reset(save);
saveStateNextTime=false;
loadStateNextTime=false;
//Reset the stats if interlevel isn't true.
if(!interlevel){
time=0;
recordings=0;
}
recentSwap=-10000;
if(save) recentSwapSaved=-10000;
//Reset the camera.
cameraMode=CAMERA_PLAYER;
if(save) cameraModeSaved=CAMERA_PLAYER;
cameraTarget.x=cameraTarget.y=cameraTarget.w=cameraTarget.h=0;
if(save) cameraTargetSaved.x=cameraTargetSaved.y=cameraTargetSaved.w=cameraTargetSaved.h=0;
//Reset the number of collectables
currentCollectables=0;
if(save)
currentCollectablesSaved=0;
//There is no last checkpoint so set it to NULL.
if(save)
objLastCheckPoint=NULL;
//Clear the event queue, since all the events are from before the reset.
eventQueue.clear();
//Reset other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->reset(save);
}
//Also reset states of scenery layers.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
it->second->resetAnimation(save);
}
//Also reset the background animation, if any.
if(background)
background->resetAnimation(save);
//Reset the cached notification block
notificationTexture.update(NULL, NULL);
//Reset the script environment if necessary.
if (noScript) {
//Destroys the script environment completely.
getScriptExecutor()->destroy();
//Clear the level script.
compiledScripts.clear();
savedCompiledScripts.clear();
initialCompiledScripts.clear();
//Clear the block script.
for (auto block : levelObjects){
block->compiledScripts.clear();
block->savedCompiledScripts.clear();
block->initialCompiledScripts.clear();
}
} else {
if (save || getScriptExecutor()->getLuaState() == NULL) {
//Create a new script environment.
getScriptExecutor()->reset(true);
//Recompile the level script.
compiledScripts.clear();
savedCompiledScripts.clear();
initialCompiledScripts.clear();
for (auto it = scripts.begin(); it != scripts.end(); ++it){
int index = getScriptExecutor()->compileScript(it->second);
compiledScripts[it->first] = index;
lua_rawgeti(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX, index);
initialCompiledScripts[it->first] = luaL_ref(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX);
}
//Recompile the block script.
for (auto block : levelObjects) {
block->compiledScripts.clear();
block->savedCompiledScripts.clear();
block->initialCompiledScripts.clear();
for (auto it = block->scripts.begin(); it != block->scripts.end(); ++it){
int index = getScriptExecutor()->compileScript(it->second);
block->compiledScripts[it->first] = index;
lua_rawgeti(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX, index);
block->initialCompiledScripts[it->first] = luaL_ref(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX);
}
}
} else {
//Do a soft reset.
getScriptExecutor()->reset(false);
//Restore the level script to initial state.
copyCompiledScripts(getScriptExecutor()->getLuaState(), initialCompiledScripts, compiledScripts);
//Restore the block script to initial state.
for (auto block : levelObjects) {
copyCompiledScripts(getScriptExecutor()->getLuaState(), block->initialCompiledScripts, block->compiledScripts);
}
}
}
//Call the level's onCreate event.
executeScript(LevelEvent_OnCreate);
//Send GameObjectEvent_OnCreate event to the script
for (auto block : levelObjects) {
block->onEvent(GameObjectEvent_OnCreate);
}
//Close exit(s) if there are any collectables
if (totalCollectables>0){
for (auto block : levelObjects){
if (block->type == TYPE_EXIT){
block->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
//Hide the cursor (if not the leveleditor).
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
}
void Game::executeScript(int eventType){
map<int,int>::iterator it;
//Check if there's a script for the given event.
it=compiledScripts.find(eventType);
if(it!=compiledScripts.end()){
//There is one so execute it.
getScriptExecutor()->executeScript(it->second);
}
}
void Game::broadcastObjectEvent(int eventType,int objectType,const char* id,GameObject* target){
//Create a typeGameObjectEvent that can be put into the queue.
typeGameObjectEvent e;
//Set the event type.
e.eventType=eventType;
//Set the object type.
e.objectType=objectType;
//By default flags=0.
e.flags=0;
//Unless there's an id.
if(id){
//Set flags to 0x1 and set the id.
e.flags|=1;
e.id=id;
}
//Or there's a target given.
if(target)
e.target=target;
else
e.target=NULL;
//Add the event to the queue.
eventQueue.push_back(e);
}
void Game::getCurrentLevelAutoSaveRecordPath(std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
levels->getLevelAutoSaveRecordPath(-1,bestTimeFilePath,bestRecordingFilePath,createPath);
}
void Game::gotoNextLevel(ImageManager& imageManager, SDL_Renderer& renderer){
//Goto the next level.
levels->nextLevel();
//Check if the level exists.
if(levels->getCurrentLevel()<levels->getLevelCount()){
setNextState(STATE_GAME);
}else{
if(!levels->congratulationText.empty()){
msgBox(imageManager,renderer,_CC(levels->getDictionaryManager(),levels->congratulationText),MsgBoxOKOnly,_("Congratulations"));
}else{
msgBox(imageManager,renderer,_("You have finished the levelpack!"),MsgBoxOKOnly,_("Congratulations"));
}
//Now go back to the levelselect screen.
setNextState(STATE_LEVEL_SELECT);
//And set the music back to menu.
getMusicManager()->playMusic("menu");
}
}
void Game::GUIEventCallback_OnEvent(ImageManager& imageManager,SDL_Renderer& renderer, string name,GUIObject* obj,int eventType){
if(name=="cmdMenu"){
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else if(name=="cmdRestart"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
interlevel=false;
//And reset the game.
//NOTE: We don't need to clear the save game because in level replay the game won't be saved (??)
//TODO: it seems work (??); I'll see if it introduce bugs
reset(false, false);
}else if(name=="cmdNext"){
//No matter what, clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//And goto the next level.
gotoNextLevel(imageManager,renderer);
}
}
diff --git a/src/GameObjects.cpp b/src/GameObjects.cpp
index 186be56..ea69406 100644
--- a/src/GameObjects.cpp
+++ b/src/GameObjects.cpp
@@ -1,82 +1,82 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "GameObjects.h"
#include "Functions.h"
#include "Globals.h"
GameObject::GameObject(Game* parent):type(0),parent(parent){}
GameObject::~GameObject(){}
SDL_Rect GameObject::getBox(int boxType){
//This is the default implementation of getBox(int) method.
switch(boxType){
case BoxType_Current:
case BoxType_Previous:
return box;
case BoxType_Base:
return boxBase;
}
//Return an empty SDL_Rect.
SDL_Rect tmp={0,0,0,0};
return tmp;
}
void GameObject::setLocation(int x,int y){
box.x=x;
box.y=y;
}
void GameObject::setBaseLocation(int x,int y){
box.x=x;
box.y=y;
boxBase.x=x;
boxBase.y=y;
}
void GameObject::setSize(int w,int h){
box.w=w;
box.h=h;
}
void GameObject::setBaseSize(int w,int h){
box.w=w;
box.h=h;
boxBase.w=w;
boxBase.h=h;
}
void GameObject::saveState(){}
void GameObject::loadState(){}
void GameObject::reset(bool save){}
void GameObject::playAnimation(){}
void GameObject::onEvent(int eventType){}
int GameObject::queryProperties(int propertyType,Player* obj){
return 0;
}
void GameObject::getEditorData(std::vector<std::pair<std::string,std::string> >& obj){}
void GameObject::setEditorData(std::map<std::string,std::string>& obj){}
-std::string GameObject::getEditorProperty(std::string property){return "";}
-void GameObject::setEditorProperty(std::string property,std::string value){}
+std::string GameObject::getEditorProperty(const std::string& property){return "";}
+void GameObject::setEditorProperty(const std::string& property, const std::string& value){}
bool GameObject::loadFromNode(ImageManager&, SDL_Renderer&, TreeStorageNode*){return true;}
void GameObject::move(){}
diff --git a/src/GameObjects.h b/src/GameObjects.h
index 79353f0..78d7434 100644
--- a/src/GameObjects.h
+++ b/src/GameObjects.h
@@ -1,174 +1,174 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GAME_OBJECTS_H
#define GAME_OBJECTS_H
#include "Globals.h"
#include "TreeStorageNode.h"
#include "Player.h"
#include <SDL.h>
#include <string>
#include <vector>
#include <utility>
#include <map>
class Game;
class Player;
//The different gameObject events.
enum GameObjectEventType{
//Event called when the player walks on the gameObject.
GameObjectEvent_PlayerWalkOn=1,
//Event called when the player is on the gameObject.
GameObjectEvent_PlayerIsOn,
//Event called when the player leaves the gameObject.
GameObjectEvent_PlayerLeave,
//Event called when the gameObject is created.
//Only used for scripting purpose.
GameObjectEvent_OnCreate,
//Event called every frame.
//Only used for scripting purpose.
GameObjectEvent_OnEnterFrame,
//Event called when the player press DOWN key.
//Currently this event only fires when the block type is TYPE_SWITCH.
GameObjectEvent_OnPlayerInteraction,
//Event called when the block receives "toggle" from a switch/button.
GameObjectEvent_OnToggle=0x10000,
//Event called when the block receives "switch on" from a switch/button.
GameObjectEvent_OnSwitchOn=0x10001,
//Event called when the block receives "switch off" from a switch/button.
GameObjectEvent_OnSwitchOff=0x10002,
};
//The different gameObject properties.
enum GameObjectPropertyType{
//If the player can walk on the gameObject.
GameObjectProperty_PlayerCanWalkOn=1,
//If the object is spiked.
GameObjectProperty_IsSpikes,
//If the gameObject has some flags.
GameObjectProperty_Flags,
};
//The different box types that can be requested using the getBox(int boxType) method.
enum GameObjectBoxType{
//Box of the current position.
BoxType_Current=0,
//Box of the base/start position.
BoxType_Base,
//Box of the previous position.
BoxType_Previous,
//The movement of the block since last position.
BoxType_Delta,
//The velocity for when the player is standing on it.
BoxType_Velocity,
};
//The GameObject class.
class GameObject{
protected:
//The box of the gameObject.
//It's used for the current location of the gameObject and its size.
SDL_Rect box;
//The base location of the game object.
SDL_Rect boxBase;
public:
//The type of the GameObject.
int type;
//Pointer to the Game state.
Game* parent;
//Constructor.
//parent: Pointer to the Game state.
GameObject(Game* parent);
//Destructor.
virtual ~GameObject();
//Method used to retrieve a certain box from the GameObject.
//boxType: The type of box that is requested. (default=0)
//Returns: An SDL_Rect.
virtual SDL_Rect getBox(int boxType=0);
//This method is used to place the location on a given location.
//x: The x location to place the gameObject.
//y: The y location to place the gameObject.
virtual void setLocation(int x,int y);
//This method is used to set the base of an object to a given location.
//x: The x location to place the gameObject.
//y: The y location to place the gameObject.
virtual void setBaseLocation(int x,int y);
//This method sets the size of the object to a given size.
//w: The new width of the gameObject.
//h: The new height the gameObject.
virtual void setSize(int w,int h);
//This method sets the size of the base of the object to a given size.
//w: The new width of the gameObject.
//h: The new height of the gameObject.
virtual void setBaseSize(int w,int h);
//Method used to draw the GameObject.
virtual void show(SDL_Renderer& renderer)=0;
//Save the state of the GameObject, used for moving blocks, etc.
virtual void saveState();
//Load the state of the GameObject, used for moving blocks, etc.
virtual void loadState();
//Reset the state of the GameObject, used for moving blocks, etc.
//save: Boolean if the saved state should also be reset.
virtual void reset(bool save);
//Play an animation.
virtual void playAnimation();
//Invoke an event of the GameObject.
//eventType: The event type.
virtual void onEvent(int eventType);
//Method used to request certain properties of the GameObject.
//propertyType: The property that is requested.
//obj: Pointer to the player.
virtual int queryProperties(int propertyType,Player* obj);
//Method used to retrieve the additional editor data for the GameObject.
//Used for messages, moving positions, etc...
//obj: Vector containing the editorData pairs. (key, value)
virtual void getEditorData(std::vector<std::pair<std::string,std::string> >& obj);
//Set the editorData.
//obj: Map containing the key/value for the editor data.
virtual void setEditorData(std::map<std::string,std::string>& obj);
//Get a single property of the block.
//property: The property to return.
//Returns: The value for the requested property.
- virtual std::string getEditorProperty(std::string property);
+ virtual std::string getEditorProperty(const std::string& property);
//Set a single property of the block.
//property: The property to set.
//value: The new value for the property.
- virtual void setEditorProperty(std::string property,std::string value);
+ virtual void setEditorProperty(const std::string& property, const std::string& value);
//Method for loading the GameObject from a node.
//objNode: Pointer to the storage node to load from.
//Returns: True if it succeeds without errors.
virtual bool loadFromNode(ImageManager&, SDL_Renderer&, TreeStorageNode*);
//Update method for GameObjects, used for moving blocks.
virtual void move();
};
#endif
diff --git a/src/Globals.h b/src/Globals.h
index 7cafa87..4487ca4 100644
--- a/src/Globals.h
+++ b/src/Globals.h
@@ -1,263 +1,263 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GLOBALS_H
#define GLOBALS_H
#include <SDL.h>
#include <string>
#include "libs/tinygettext/tinygettext.hpp"
#include "LevelPack.h"
#include "Render.h"
#if defined (WIN32) || defined (__APPLE__)
//#define DATA_PATH
#else
#include "config.h"
#endif
#define TITLE_FONT_RAISE 19
#define GUI_FONT_RAISE 5
class GameState;
//Global constants
//The width of the screen.
extern int SCREEN_WIDTH;
//The height of the screen.
extern int SCREEN_HEIGHT;
//The depth of the screen.
#if defined(ANDROID)
//TODO: change other surface creating code to make the game runs faster
const int SCREEN_BPP=16; //??? 24?? 32??
//const int SCREEN_FLAGS=SDL_HWSURFACE;
#else
const int SCREEN_BPP=32;
//const int SCREEN_FLAGS=SDL_HWSURFACE;
#endif
const int SCREEN_FLAGS = 0;
//SDL interprets each pixel as a 32-bit number,
// so our masks must depend on the endianness (byte order) of the machine.
//NOTE: We define them here so we only have to do it once.
/*#if SDL_BYTEORDER == SDL_BIG_ENDIAN
const Uint32 BMASK=0xFF000000;
const Uint32 GMASK=0x00FF0000;
const Uint32 RMASK=0x0000FF00;
const Uint32 AMASK=0x000000FF;
#else*/
// NOTE: Changed to ARGB for SDL2.
const Uint32 BMASK=0x000000FF;
const Uint32 GMASK=0x0000FF00;
const Uint32 RMASK=0x00FF0000;
const Uint32 AMASK=0xFF000000;
/*#endif*/
//String containing the version, used in the titelbar.
//NOTE: for development version please write something like "V0.5 Development version"
//which can trigger the corresponding achievement.
-const std::string version = "V0.5 RC";
+const std::string version = "V0.5 RC2";
//The height of the current level.
extern int LEVEL_HEIGHT;
//The width of the current level.
extern int LEVEL_WIDTH;
//The target frames per seconds.
const int FPS=40;
//The language that in which the game should be translated.
extern std::string language;
//The DictionaryManager that is used to translate the game itself.
extern tinygettext::DictionaryManager* dictionaryManager;
//SDL Window and renderer
extern SDL_Window* sdlWindow;
//Font that is used for titles.
//Knewave large.
extern TTF_Font* fontTitle;
//Font that is used for captions of buttons and other GUI elements.
//Knewave small.
extern TTF_Font* fontGUI;
//Font that is used for long captions of buttons and other GUI elements.
//Knewave smaller.
extern TTF_Font* fontGUISmall;
//Font that is used for (long) text.
//Blokletter-Viltstift small.
extern TTF_Font* fontText;
//Font used for scripting editor.
//Monospace.
extern TTF_Font* fontMono;
//Small arrows used for GUI widgets.
//2 directions and 2 different/same colors depending on theme.
extern TexturePtr arrowLeft1;
extern TexturePtr arrowRight1;
extern TexturePtr arrowLeft2;
extern TexturePtr arrowRight2;
//Event, used for event handling.
extern SDL_Event event;
//GUI
class GUIObject;
extern GUIObject *GUIObjectRoot;
//The state id of the current state.
extern int stateID;
//Integer containing what the next state will be.
extern int nextState;
//The currentState.
extern GameState* currentState;
//Pointer to the current levelpack.
extern LevelPack* levels;
//String containing the name of the current level.
extern std::string levelName;
//SDL rectangle used to store the camera.
//x is the x location of the camera.
//y is the y location of the camera.
//w is the width of the camera. (equal to SCREEN_WIDTH)
//h is the height of the camera. (equal to SCREEN_HEIGHT)
extern SDL_Rect camera;
//Themable colors
const SDL_Color BLACK = SDL_Color{0,0,0,255};
//Enumeration containing the different cursor types there are.
enum CursorType{
//The default pointer.
CURSOR_POINTER,
//The vertical ibeam, used to indicate text input.
CURSOR_CARROT,
//A closed hand, used for indicating a drag action.
CURSOR_DRAG,
//The different (window) size cursor icons.
CURSOR_SIZE_HOR,
CURSOR_SIZE_VER,
CURSOR_SIZE_FDIAG,
CURSOR_SIZE_BDIAG,
//Remove cursor used in level editor
CURSOR_REMOVE,
//Pointing hand cursor, for hyperlinks.
CURSOR_POINTING_HAND,
//The number of cursor types there are.
CURSOR_MAX
};
//Currently used cursor type.
extern CursorType currentCursor;
//Array containing the SDL_Cursors.
extern SDL_Cursor* cursors[CURSOR_MAX];
//Enumeration containing the ids of the game states.
enum GameStates{
//State null is a special state used to indicate no state.
//This is used when no next state is defined.
STATE_NULL,
//This state is before the actual leveleditor used to make levelpacks.
STATE_LEVEL_EDIT_SELECT,
//This state is for the level editor.
STATE_LEVEL_EDITOR,
//This state is for the main menu.
STATE_MENU,
//This state is for the actual game.
STATE_GAME,
//Special state used when exiting meandmyshadow.
STATE_EXIT,
//This state is for the help screen.
STATE_LEVEL_SELECT,
//This state is for the options screen.
STATE_OPTIONS,
//This state is for the addon screen.
STATE_ADDONS,
//This state is for credits screen
STATE_CREDITS,
//This state is for statistics screen
STATE_STATISTICS,
};
//Enumeration containing the ids of the different block types.
enum GameTileType{
//The normal solid block.
TYPE_BLOCK=0,
//Block representing the start location of the player.
TYPE_START_PLAYER,
//Block representing the start location of the shadow.
TYPE_START_SHADOW,
//The exit of the level.
TYPE_EXIT,
//The shadow block which is only solid for the shadow.
TYPE_SHADOW_BLOCK,
//Block that can kill both the player and the shadow.
TYPE_SPIKES,
//Special point where the player can save.
TYPE_CHECKPOINT,
//Block that will switch the location of the player and the shadow when invoked.
TYPE_SWAP,
//Block that will crumble to dust when stepped on it for the third time.
TYPE_FRAGILE,
//Normal block that moves along a path.
TYPE_MOVING_BLOCK,
//Shadow block that moves along a path.
TYPE_MOVING_SHADOW_BLOCK,
//A spike block that moves along a path.
TYPE_MOVING_SPIKES,
//Special block which, once entered, moves the player/shadow to a different portal.
TYPE_PORTAL,
//A block with a button which can activate or stop moving blocks, converyor belts
TYPE_BUTTON,
//A switch which can activate or stop moving blocks, converyor belts
TYPE_SWITCH,
//Solid block which works like
TYPE_CONVEYOR_BELT,
TYPE_SHADOW_CONVEYOR_BELT,
//Block that contains a message that can be read.
TYPE_NOTIFICATION_BLOCK,
//A collectable that is able to open locked doors
TYPE_COLLECTABLE,
//Block that can be pushed by the player and the shadow.
//Pushable blocks can push other pushable blocks.
TYPE_PUSHABLE,
//The (max) number of tiles.
TYPE_MAX
};
//Keyboard only mode. This is set to true if the last menu navigation is performed by keyboard.
extern bool isKeyboardOnly;
#endif
diff --git a/src/InputManager.cpp b/src/InputManager.cpp
index 37032f4..337c2a8 100644
--- a/src/InputManager.cpp
+++ b/src/InputManager.cpp
@@ -1,582 +1,593 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "InputManager.h"
#include "Globals.h"
#include "Settings.h"
#include "Functions.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <iostream>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
InputManager inputMgr;
//the order must be the same as InputManagerKeys
static const char* keySettingNames[INPUTMGR_MAX]={
"key_up","key_down","key_left","key_right","key_jump","key_action","key_space","key_cancelRecording",
"key_escape","key_restart","key_tab","key_save","key_load","key_swap",
"key_teleport","key_suicide","key_shift","key_next","key_previous","key_select"
};
//the order must be the same as InputManagerKeys
static const char* keySettingDescription[INPUTMGR_MAX]={
__("Up (in menu)"),__("Down (in menu)"),__("Left"),__("Right"),__("Jump"),__("Action"),__("Space (Record)"),__("Cancel recording"),
__("Escape"),__("Restart"),__("Tab (View shadow/Level prop.)"),__("Save game (in editor)"),__("Load game"),__("Swap (in editor)"),
__("Teleport (in editor)"),__("Suicide (in editor)"),__("Shift (in editor)"),__("Next block type (in Editor)"),
__("Previous block type (in editor)"), __("Select (in menu)")
};
InputManagerKeyCode::InputManagerKeyCode(int sym_, int mod_)
: type(KEYBOARD), sym(sym_), mod(mod_)
{
// normalize a bit
if (sym == 0) {
mod = 0;
} else {
mod = ((mod & KMOD_CTRL) ? KMOD_CTRL : 0)
| ((mod & KMOD_ALT) ? KMOD_ALT : 0)
| ((mod & KMOD_SHIFT) ? KMOD_SHIFT : 0);
if (sym == SDLK_LCTRL || sym == SDLK_RCTRL) mod &= ~KMOD_CTRL;
if (sym == SDLK_LALT || sym == SDLK_RALT) mod &= ~KMOD_ALT;
if (sym == SDLK_LSHIFT || sym == SDLK_RSHIFT) mod &= ~KMOD_SHIFT;
}
}
InputManagerKeyCode::InputManagerKeyCode(InputType type_, int buttonIndex_, int buttonValue_)
: type(type_), buttonIndex(buttonIndex_), buttonValue(buttonValue_)
{
}
InputManagerKeyCode InputManagerKeyCode::createFromString(const std::string& s) {
int i, j, lp;
if (s.find("JoystickAxis;") == 0) {
lp = s.find(';');
if (sscanf(s.c_str() + (lp + 1), "%d;%d", &i, &j) == 2) {
return InputManagerKeyCode(JOYSTICK_AXIS, i, j);
}
} else if (s.find("JoystickButton;") == 0) {
lp = s.find(';');
if (sscanf(s.c_str() + (lp + 1), "%d", &i) == 1) {
return InputManagerKeyCode(JOYSTICK_BUTTON, i, 0);
}
} else if (s.find("JoystickHat;") == 0) {
lp = s.find(';');
if (sscanf(s.c_str() + (lp + 1), "%d;%d", &i, &j) == 2) {
return InputManagerKeyCode(JOYSTICK_HAT, i, j);
}
} else {
i = atoi(s.c_str());
j = 0;
if (i) {
if (s.find(";Ctrl") != std::string::npos) j |= KMOD_CTRL;
if (s.find(";Alt") != std::string::npos) j |= KMOD_ALT;
if (s.find(";Shift") != std::string::npos) j |= KMOD_SHIFT;
}
return InputManagerKeyCode(i, j);
}
fprintf(stderr, "ERROR: Can't parse '%s' as InputManagerKeyCode\n", s.c_str());
return InputManagerKeyCode();
}
std::string InputManagerKeyCode::toString() const {
std::ostringstream str;
switch (type) {
default:
if (sym == 0) {
return std::string();
} else {
str << sym;
if (mod & KMOD_CTRL) str << ";Ctrl";
if (mod & KMOD_ALT) str << ";Alt";
if (mod & KMOD_SHIFT) str << ";Shift";
return str.str();
}
break;
case JOYSTICK_AXIS:
str << "JoystickAxis;" << buttonIndex << ";" << buttonValue;
return str.str();
break;
case JOYSTICK_BUTTON:
str << "JoystickButton;" << buttonIndex;
return str.str();
break;
case JOYSTICK_HAT:
str << "JoystickHat;" << buttonIndex << ";" << buttonValue;
return str.str();
break;
}
}
std::string InputManagerKeyCode::describe() const {
switch (type) {
default:
if (sym == 0) {
return std::string();
} else {
std::ostringstream str;
if (mod & KMOD_CTRL) str << "Ctrl+";
if (mod & KMOD_ALT) str << "Alt+";
if (mod & KMOD_SHIFT) str << "Shift+";
const char* s = SDL_GetKeyName(sym);
if (s != NULL){
str << (dictionaryManager != NULL ? dictionaryManager->get_dictionary().translate_ctxt("keys", s).c_str() : s);
} else{
/// TRANSLAOTRS: This is used when the name of the key code is not found.
str << tfm::format(_("(Key %d)"), sym);
}
return str.str();
}
break;
case JOYSTICK_AXIS:
return tfm::format(_("Joystick axis %d %s"), buttonIndex, buttonValue > 0 ? "+" : "-");
break;
case JOYSTICK_BUTTON:
return tfm::format(_("Joystick button %d"), buttonIndex);
break;
case JOYSTICK_HAT:
switch (buttonValue){
case SDL_HAT_LEFT:
return tfm::format(_("Joystick hat %d left"), buttonIndex);
break;
case SDL_HAT_RIGHT:
return tfm::format(_("Joystick hat %d right"), buttonIndex);
break;
case SDL_HAT_UP:
return tfm::format(_("Joystick hat %d up"), buttonIndex);
break;
case SDL_HAT_DOWN:
return tfm::format(_("Joystick hat %d down"), buttonIndex);
break;
default:
fprintf(stderr, "ERROR: Invalid joystick hat value %d\n", buttonValue);
/// TRANSLAOTRS: This is used when the JOYSTICK_HAT value is invalid.
return tfm::format(_("Joystick hat %d %d"), buttonIndex, buttonValue);
break;
}
break;
}
}
bool InputManagerKeyCode::empty() const {
return type == KEYBOARD && sym == 0;
}
std::string InputManagerKeyCode::describeTwo(const InputManagerKeyCode& keyCode, const InputManagerKeyCode& keyCodeAlt) {
std::string s = keyCode.describe();
if (!keyCodeAlt.empty()) {
if (!keyCode.empty()) {
s += " ";
s += _("OR");
s += " ";
}
s += keyCodeAlt.describe();
}
return s;
}
bool InputManagerKeyCode::operator == (const InputManagerKeyCode& rhs) const {
return type == rhs.type && sym == rhs.sym && ((type == KEYBOARD && sym == 0) || mod == rhs.mod);
}
bool InputManagerKeyCode::contains(const InputManagerKeyCode& rhs) const {
if (type == KEYBOARD && rhs.type == KEYBOARD) {
if ((mod & KMOD_CTRL) == 0 && (rhs.mod & KMOD_CTRL) != 0) return false;
if ((mod & KMOD_ALT) == 0 && (rhs.mod & KMOD_ALT) != 0) return false;
if ((mod & KMOD_SHIFT) == 0 && (rhs.mod & KMOD_SHIFT) != 0) return false;
if ((mod & KMOD_CTRL) != 0 && (rhs.mod & KMOD_CTRL) == 0) {
if (rhs.sym == SDLK_LCTRL || rhs.sym == SDLK_RCTRL) return true;
}
if ((mod & KMOD_ALT) != 0 && (rhs.mod & KMOD_ALT) == 0) {
if (rhs.sym == SDLK_LALT || rhs.sym == SDLK_RALT) return true;
}
if ((mod & KMOD_SHIFT) != 0 && (rhs.mod & KMOD_SHIFT) == 0) {
if (rhs.sym == SDLK_LSHIFT || rhs.sym == SDLK_RSHIFT) return true;
}
}
return false;
}
//A class that handles the gui events of the inputDialog.
class InputDialogHandler:public GUIEventCallback{
private:
//the list box which contains keys.
GUIListBox* listBox;
//the parent object.
InputManager* parent;
//update specified key config item
void updateConfigItem(SDL_Renderer& renderer,int index){
//Get the description of the key.
std::string s=_(keySettingDescription[index]);
s+=": ";
//Get the key code name.
s += InputManagerKeyCode::describeTwo(
parent->getKeyCode((InputManagerKeys)index, false),
parent->getKeyCode((InputManagerKeys)index, true)
);
//Update item.
listBox->updateItem(renderer, index, s);
}
public:
//Constructor.
InputDialogHandler(SDL_Renderer& renderer,GUIListBox* listBox,InputManager* parent):listBox(listBox),parent(parent){
//load the available keys to the list box.
for(int i=0;i<INPUTMGR_MAX;i++){
//Get the description of the key.
std::string s=_(keySettingDescription[i]);
s+=": ";
//Get key code name.
s += InputManagerKeyCode::describeTwo(
parent->getKeyCode((InputManagerKeys)i, false),
parent->getKeyCode((InputManagerKeys)i, true)
);
//Add item.
listBox->addItem(renderer, s);
}
}
virtual ~InputDialogHandler(){}
//When a key is pressed call this to set the key to currently-selected item.
void onKeyDown(SDL_Renderer& renderer,const InputManagerKeyCode& keyCode){
//Check if an item is selected.
int index=listBox->value;
if(index<0 || index>=INPUTMGR_MAX) return;
//Get the existing keys.
auto key = parent->getKeyCode((InputManagerKeys)index, false);
auto altKey = parent->getKeyCode((InputManagerKeys)index, true);
//SDLK_BACKSPACE will erase the last key if there are keys.
if (keyCode == InputManagerKeyCode(SDLK_BACKSPACE) && (!key.empty() || !altKey.empty())) {
if (!altKey.empty()) {
parent->setKeyCode((InputManagerKeys)index, InputManagerKeyCode(), true);
} else {
parent->setKeyCode((InputManagerKeys)index, InputManagerKeyCode(), false);
}
updateConfigItem(renderer, index);
} else if (keyCode == key || keyCode == altKey) {
//Do nothing if keyCode is equal to one of the existing keys.
} else if (key.empty() || (altKey.empty() && keyCode.contains(key))) {
//Update the main key if there isn't one.
parent->setKeyCode((InputManagerKeys)index, keyCode, false);
updateConfigItem(renderer, index);
} else if (altKey.empty() || keyCode.contains(altKey)) {
//Otherwise update the alternative key if there isn't one.
parent->setKeyCode((InputManagerKeys)index, keyCode, true);
updateConfigItem(renderer, index);
}
}
void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Do nothing...
}
};
//Event handler.
static InputDialogHandler* handler;
//A GUIObject that is used to create events for key presses.
class GUIKeyListener:public GUIObject{
public:
GUIKeyListener(ImageManager& imageManager, SDL_Renderer& renderer)
:GUIObject(imageManager,renderer){}
private:
//Leave empty.
~GUIKeyListener(){}
bool handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
if(enabled && handler){
if(event.type==SDL_KEYDOWN){
handler->onKeyDown(renderer, InputManagerKeyCode(event.key.keysym.sym, event.key.keysym.mod));
}
//Joystick
else if(event.type==SDL_JOYAXISMOTION){
if(event.jaxis.value>3200){
handler->onKeyDown(renderer, InputManagerKeyCode(InputManagerKeyCode::JOYSTICK_AXIS, event.jaxis.axis, 1));
}else if(event.jaxis.value<-3200){
handler->onKeyDown(renderer, InputManagerKeyCode(InputManagerKeyCode::JOYSTICK_AXIS, event.jaxis.axis, -1));
}
}
else if(event.type==SDL_JOYBUTTONDOWN){
handler->onKeyDown(renderer, InputManagerKeyCode(InputManagerKeyCode::JOYSTICK_BUTTON, event.jbutton.button, 0));
}
else if(event.type==SDL_JOYHATMOTION){
if (event.jhat.value & SDL_HAT_LEFT){
handler->onKeyDown(renderer, InputManagerKeyCode(InputManagerKeyCode::JOYSTICK_HAT, event.jhat.hat, SDL_HAT_LEFT));
} else if (event.jhat.value & SDL_HAT_RIGHT){
handler->onKeyDown(renderer, InputManagerKeyCode(InputManagerKeyCode::JOYSTICK_HAT, event.jhat.hat, SDL_HAT_RIGHT));
} else if (event.jhat.value & SDL_HAT_UP){
handler->onKeyDown(renderer, InputManagerKeyCode(InputManagerKeyCode::JOYSTICK_HAT, event.jhat.hat, SDL_HAT_UP));
} else if (event.jhat.value & SDL_HAT_DOWN){
handler->onKeyDown(renderer, InputManagerKeyCode(InputManagerKeyCode::JOYSTICK_HAT, event.jhat.hat, SDL_HAT_DOWN));
}
}
}
return false;
}
//Nothing to do.
void render(){}
};
InputManagerKeyCode InputManager::getKeyCode(InputManagerKeys key, bool isAlternativeKey){
if(isAlternativeKey) return alternativeKeys[key];
else return keys[key];
}
void InputManager::setKeyCode(InputManagerKeys key, const InputManagerKeyCode& keyCode, bool isAlternativeKey){
if(isAlternativeKey) alternativeKeys[key]=keyCode;
else keys[key]=keyCode;
}
void InputManager::loadConfig(){
for(int i=0;i<INPUTMGR_MAX;i++){
string s=keySettingNames[i];
keys[i] = InputManagerKeyCode::createFromString(getSettings()->getValue(s));
s+="2";
alternativeKeys[i] = InputManagerKeyCode::createFromString(getSettings()->getValue(s));
//Move the alternative key to main key if the main key is empty.
if(keys[i].empty() && !alternativeKeys[i].empty()){
keys[i]=alternativeKeys[i];
alternativeKeys[i] = InputManagerKeyCode();
}
//Remove duplicate.
if(keys[i]==alternativeKeys[i])
alternativeKeys[i] = InputManagerKeyCode();
}
}
void InputManager::saveConfig(){
int i;
for(i=0;i<INPUTMGR_MAX;i++){
string s=keySettingNames[i];
//Remove duplicate.
if(keys[i]==alternativeKeys[i])
alternativeKeys[i] = InputManagerKeyCode();
getSettings()->setValue(s, keys[i].toString());
s+="2";
getSettings()->setValue(s, alternativeKeys[i].toString());
}
}
GUIObject* InputManager::showConfig(ImageManager& imageManager, SDL_Renderer& renderer,int height){
//Create the new GUI.
GUIObject* root=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,height);
//Instruction label.
GUIObject* obj=new GUILabel(imageManager,renderer,0,6,root->width,36,_("Select an item and press a key to change it."),0,true,true,GUIGravityCenter);
root->addChild(obj);
obj=new GUILabel(imageManager,renderer,0,30,root->width,36,_("Press backspace to clear the selected item."),0,true,true,GUIGravityCenter);
root->addChild(obj);
//The listbox for keys.
GUIListBox *listBox=new GUIListBox(imageManager,renderer,SCREEN_WIDTH*0.15,72,SCREEN_WIDTH*0.7,height-72-8);
root->addChild(listBox);
//Create the event handler.
if(handler)
delete handler;
handler=new InputDialogHandler(renderer,listBox,this);
obj=new GUIKeyListener(imageManager,renderer);
root->addChild(obj);
//Return final widget.
return root;
}
InputManager::InputManager(){
//Clear the arrays.
for(int i=0;i<INPUTMGR_MAX;i++){
keyFlags[i]=0;
}
}
InputManager::~InputManager(){
closeAllJoysticks();
}
int InputManagerKeyCode::getKeyState(int oldState, bool hasEvent, std::vector<SDL_Joystick*>& joysticks, int deadZone) const {
int state = 0;
switch (type) {
- default:
+ default: {
//Keyboard.
if (sym == 0) return 0;
+ const bool isPrintable = (mod & ~KMOD_SHIFT) == 0 && sym >= 32 && sym <= 126;
if (hasEvent && (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP)) {
const bool isModCorrect = (mod == 0) ? true :
(mod == InputManagerKeyCode(event.key.keysym.sym, event.key.keysym.mod).mod);
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == sym && isModCorrect) {
state |= 0x3;
+ if (!isPrintable) state |= 0x30;
} else if (event.type == SDL_KEYUP && event.key.keysym.sym == sym && isModCorrect) {
state |= 0x4;
+ if (!isPrintable) state |= 0x40;
}
}
{
//Get keyboard state for key code
//SDL_GetKeyboardState needs a scankey rather than keycode, so we convert
int numKeys = 0;
const Uint8* keyStates = SDL_GetKeyboardState(&numKeys);
SDL_Scancode scanCode = SDL_GetScancodeFromKey(sym);
if (scanCode < numKeys && keyStates[scanCode]) {
const int m = SDL_GetModState();
const bool isModCorrect = (mod == 0) ? true :
(mod == InputManagerKeyCode(sym, m).mod);
- if (isModCorrect) state |= 0x1;
+ if (isModCorrect) {
+ state |= 0x1;
+ if (!isPrintable) state |= 0x10;
+ }
}
}
break;
+ }
case JOYSTICK_AXIS:
//Axis.
if (hasEvent && event.type == SDL_JOYAXISMOTION && event.jaxis.axis == buttonIndex) {
if ((buttonValue > 0 && event.jaxis.value > deadZone) || (buttonValue < 0 && event.jaxis.value < -deadZone)) {
if ((oldState & 0x1) == 0) state |= 0x3;
+ if ((oldState & 0x10) == 0) state |= 0x30;
} else {
if (oldState & 0x1) state |= 0x4;
+ if (oldState & 0x10) state |= 0x40;
}
}
for (auto j : joysticks) {
Sint16 v = SDL_JoystickGetAxis(j, buttonIndex);
if ((buttonValue > 0 && v > deadZone) || (buttonValue < 0 && v < -deadZone)){
- state |= 0x1;
+ state |= 0x11;
break;
}
}
break;
case JOYSTICK_BUTTON:
//Button.
if (hasEvent) {
if (event.type == SDL_JOYBUTTONDOWN && event.jbutton.button == buttonIndex){
- state |= 0x3;
+ state |= 0x33;
} else if (event.type == SDL_JOYBUTTONUP && event.jbutton.button == buttonIndex){
- state |= 0x4;
+ state |= 0x44;
}
}
for (auto j : joysticks) {
Uint8 v = SDL_JoystickGetButton(j, buttonIndex);
if (v) {
- state |= 0x1;
+ state |= 0x11;
break;
}
}
break;
case JOYSTICK_HAT:
//Hat.
if (hasEvent && event.type == SDL_JOYHATMOTION && event.jhat.hat == buttonIndex) {
if (event.jhat.value & buttonValue){
if ((oldState & 0x1) == 0) state |= 0x3;
+ if ((oldState & 0x10) == 0) state |= 0x30;
} else{
if (oldState & 0x1) state |= 0x4;
+ if (oldState & 0x10) state |= 0x40;
}
}
for (auto j : joysticks) {
Uint8 v = SDL_JoystickGetHat(j, buttonIndex);
if (v & buttonValue) {
- state |= 0x1;
+ state |= 0x11;
break;
}
}
break;
}
return state;
}
//Update the key state, according to current SDL event, etc.
void InputManager::updateState(bool hasEvent){
for(int i=0;i<INPUTMGR_MAX;i++){
keyFlags[i] = keys[i].getKeyState(keyFlags[i], hasEvent, joysticks) | alternativeKeys[i].getKeyState(keyFlags[i], hasEvent, joysticks);
}
}
//Check if there is KeyDown event.
-bool InputManager::isKeyDownEvent(InputManagerKeys key){
- return keyFlags[key]&0x2;
+bool InputManager::isKeyDownEvent(InputManagerKeys key, bool excludePrintable) {
+ return (keyFlags[key] & (excludePrintable ? 0x20 : 0x2)) != 0;
}
//Check if there is KeyUp event.
-bool InputManager::isKeyUpEvent(InputManagerKeys key){
- return keyFlags[key]&0x4;
+bool InputManager::isKeyUpEvent(InputManagerKeys key, bool excludePrintable) {
+ return (keyFlags[key] & (excludePrintable ? 0x40 : 0x4)) != 0;
}
//Check if specified key is down.
-bool InputManager::isKeyDown(InputManagerKeys key){
- return keyFlags[key]&0x1;
+bool InputManager::isKeyDown(InputManagerKeys key, bool excludePrintable) {
+ return (keyFlags[key] & (excludePrintable ? 0x10 : 0x1)) != 0;
}
//Open all joysticks.
void InputManager::openAllJoysitcks(){
int i,m;
//First close previous joysticks.
closeAllJoysticks();
//Open all joysticks.
m=SDL_NumJoysticks();
for(i=0;i<m;i++){
SDL_Joystick *j=SDL_JoystickOpen(i);
if(j==NULL){
printf("ERROR: Couldn't open Joystick %d\n",i);
}else{
joysticks.push_back(j);
}
}
}
//Close all joysticks.
void InputManager::closeAllJoysticks(){
for(int i=0;i<(int)joysticks.size();i++){
SDL_JoystickClose(joysticks[i]);
}
joysticks.clear();
}
diff --git a/src/InputManager.h b/src/InputManager.h
index b377715..049d7fb 100644
--- a/src/InputManager.h
+++ b/src/InputManager.h
@@ -1,171 +1,171 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INPUTMANAGER_H
#define INPUTMANAGER_H
#include <SDL.h>
#include <vector>
#include <string>
#include "GUIObject.h"
enum InputManagerKeys{
INPUTMGR_UP,
INPUTMGR_DOWN,
INPUTMGR_LEFT,
INPUTMGR_RIGHT,
INPUTMGR_JUMP,
INPUTMGR_ACTION,
INPUTMGR_SPACE,
INPUTMGR_CANCELRECORDING,
INPUTMGR_ESCAPE,
INPUTMGR_RESTART,
INPUTMGR_TAB,
INPUTMGR_SAVE,
INPUTMGR_LOAD,
INPUTMGR_SWAP,
INPUTMGR_TELEPORT,
INPUTMGR_SUICIDE,
INPUTMGR_SHIFT,
INPUTMGR_NEXT,
INPUTMGR_PREVIOUS,
INPUTMGR_SELECT,
INPUTMGR_MAX
};
// Represents a key code or a joystick input code.
struct InputManagerKeyCode {
// The input type. Currently joystick ball is unsupported.
enum InputType {
KEYBOARD,
JOYSTICK_AXIS,
JOYSTICK_BUTTON,
JOYSTICK_HAT,
};
// The input type.
InputType type;
union {
struct {
// The keycode if the input type is KEYBOARD.
// NOTE: if type == KEYBOARD and sym == 0 then this key is disabled at all.
int sym;
// The modifier if the input type is KEYBOARD.
// NOTE: if modifier == 0 then we don't check the modifier at all.
int mod;
};
struct {
// Joystick button index.
int buttonIndex;
// Joystick button value.
// If type == JOYSTICK_AXIS then value should be 1 or -1.
// If type == JOYSTICK_BUTTON then it's unused.
// If type == JOYSTICK_HAT then it's SDL_HAT_LEFT, SDL_HAT_RIGHT, SDL_HAT_UP or SDL_HAT_DOWN.
int buttonValue;
};
};
// Constructor for a keyboard input.
explicit InputManagerKeyCode(int sym_ = 0, int mod_ = 0);
// Constructor for a joystick input.
explicit InputManagerKeyCode(InputType type_, int buttonIndex_, int buttonValue_);
// Create a key code from string.
static InputManagerKeyCode createFromString(const std::string& s);
// Convert a key code to a machine-readable string.
std::string toString() const;
// Convert a key code to a human-readable string.
std::string describe() const;
// Convert two key codes to a human-readable string.
static std::string describeTwo(const InputManagerKeyCode& keyCode, const InputManagerKeyCode& keyCodeAlt);
// Check if the key code is empty.
bool empty() const;
// Internal function to check if the key corresponding to this key code is pressed.
// oldState: The bit-field flag saves the key states. 0x1=key is down, 0x2=KeyDown event,0x4=KeyUp event.
// hasEvent: current SDL event is present.
// joysticks: List of joystick to detect.
// deadZone: Joystick dead zone for JOYSTICK_AXIS detection.
// Return value: The bit-field flag with the same meaning as oldState.
int getKeyState(int oldState, bool hasEvent, std::vector<SDL_Joystick*>& joysticks, int deadZone = 3200) const;
bool operator==(const InputManagerKeyCode& rhs) const;
bool operator!=(const InputManagerKeyCode& rhs) const { return !(*this == rhs); }
// Check if a keyboard input contains another keyboard input (for example, Ctrl+A contains Ctrl but doesn't contain A).
bool contains(const InputManagerKeyCode& rhs) const;
};
class InputManager{
public:
InputManager();
~InputManager();
//Get and set key code of each key.
InputManagerKeyCode getKeyCode(InputManagerKeys key, bool isAlternativeKey);
void setKeyCode(InputManagerKeys key, const InputManagerKeyCode& keyCode, bool isAlternativeKey);
//Load and save key settings from config file.
void loadConfig();
void saveConfig();
//Show the config screen.
GUIObject* showConfig(ImageManager& imageManager, SDL_Renderer& renderer, int height);
//Update the key state, according to current SDL event, etc.
void updateState(bool hasEvent);
//Check if there is KeyDown event.
- bool isKeyDownEvent(InputManagerKeys key);
+ bool isKeyDownEvent(InputManagerKeys key, bool excludePrintable = false);
//Check if there is KeyUp event.
- bool isKeyUpEvent(InputManagerKeys key);
+ bool isKeyUpEvent(InputManagerKeys key, bool excludePrintable = false);
//Check if specified key is down.
- bool isKeyDown(InputManagerKeys key);
+ bool isKeyDown(InputManagerKeys key, bool excludePrintable = false);
//Open all joysticks.
void openAllJoysitcks();
//Close all joysticks.
void closeAllJoysticks();
private:
//The key code of each key.
InputManagerKeyCode keys[INPUTMGR_MAX], alternativeKeys[INPUTMGR_MAX];
//The bit-field flag array saves the key states.
// 0x1 means the key is down.
// 0x2 means KeyDown event.
// 0x4 means KeyUp event.
int keyFlags[INPUTMGR_MAX];
//Contains all joysticks.
std::vector<SDL_Joystick*> joysticks;
};
extern InputManager inputMgr;
#endif //INPUTMANAGER_H
diff --git a/src/LevelEditor.cpp b/src/LevelEditor.cpp
index 1d6558d..977dc19 100644
--- a/src/LevelEditor.cpp
+++ b/src/LevelEditor.cpp
@@ -1,4816 +1,4922 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Block.h"
#include "Commands.h"
#include "CommandManager.h"
#include "GameState.h"
#include "Functions.h"
#include "GameObjects.h"
#include "ThemeManager.h"
#include "LevelPack.h"
+#include "LevelPackManager.h"
#include "LevelEditor.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "GUIListBox.h"
#include "GUITextArea.h"
#include "GUIWindow.h"
#include "GUISpinBox.h"
#include "MusicManager.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include <fstream>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string.h>
#include <stdio.h>
#include <sstream>
#include <SDL_ttf.h>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
static int levelTime,levelRecordings;
//Array containing translateble block names
static const char* blockNames[TYPE_MAX]={
__("Block"),__("Player Start"),__("Shadow Start"),
__("Exit"),__("Shadow Block"),__("Spikes"),
__("Checkpoint"),__("Swap"),__("Fragile"),
__("Moving Block"),__("Moving Shadow Block"),__("Moving Spikes"),
__("Teleporter"),__("Button"),__("Switch"),
__("Conveyor Belt"),__("Shadow Conveyor Belt"),__("Notification Block"),__("Collectable"),__("Pushable")
};
static const std::array<const char*, static_cast<size_t>(ToolTips::TooltipMax)> tooltipNames = {
- __("Select"), __("Add"), __("Delete"), __("Play"), "", "", __("Level settings"), __("Save level"), __("Back to menu"), __("Configure")
+ __("Select"), __("Add"), __("Delete"), __("Play"), "", "", __("Level settings"), __("Save level"), __("Back to menu"),
+ __("Select"), __("Delete"), __("Configure")
};
+static const std::array<const char*, static_cast<size_t>(ToolTips::TooltipMax)> tooltipHotkey = {
+ "F2", "F3", "F4", "F5", "", "", "", "Ctrl+S", "",
+ "", "", ""
+};
+
+static const std::array<int, static_cast<size_t>(ToolTips::TooltipMax)> tooltipHotkey2 = {
+ -1, -1, -1, -1, -1, -1, INPUTMGR_TAB, -1, INPUTMGR_ESCAPE,
+ -1, -1, -1
+};
//Array indicates if block is linkable
static const bool isLinkable[TYPE_MAX]={
false,false,false,
false,false,false,
false,false,false,
true,true,true,
true,true,true,
false,false,false,false
};
static std::string describeSceneryName(const std::string& name) {
//Check if the input is a valid block name.
if (name.size() > 8 && name.substr(name.size() - 8) == "_Scenery") {
auto it = Game::blockNameMap.find(name.substr(0, name.size() - 8));
if (it != Game::blockNameMap.end()){
return tfm::format(_("%s (Scenery)"), _(blockNames[it->second]));
}
}
return name;
}
/////////////////LevelEditorActionsPopup/////////////////
class LevelEditorActionsPopup:private GUIEventCallback{
private:
//The parent object.
LevelEditor* parent;
//The position and size of window.
SDL_Rect rect;
//Array containing the actions in this popup.
GUIListBox* actions;
//GUI image.
SDL_Surface* bmGUI;
//Pointer to the object the actions apply to.
GameObject* target;
//The behaviour names.
vector<string> behaviour;
//The fragile block states.
vector<string> states;
//The separator texture.
//NOTE: It's created only when it's used. So don't access it directly!
SharedTexture separatorTexture;
//Get or create a new separator texture.
SharedTexture getOrCreateSeparatorTexture(SDL_Renderer& renderer) {
if (!separatorTexture) {
//NOTE: we use an arbitrart width since it will be updated soon.
createSeparatorTexture(renderer, 16);
}
return separatorTexture;
}
void createSeparatorTexture(SDL_Renderer& renderer, int width) {
//create surface
SurfacePtr surface = createSurface(width, 5);
//draw horizontal separator
const SDL_Rect r0 = { 4, 1, width - 8, 1 };
const SDL_Rect r1 = { 4, 3, width - 8, 1 };
const SDL_Rect r2 = { 4, 2, width - 8, 1 };
Uint32 c0 = SDL_MapRGB(surface->format, 192, 192, 192);
Uint32 c2 = SDL_MapRGB(surface->format, 64, 64, 64);
SDL_FillRect(surface.get(), &r0, c0);
SDL_FillRect(surface.get(), &r1, c0);
SDL_FillRect(surface.get(), &r2, c2);
//over
separatorTexture = textureFromSurface(renderer, std::move(surface));
}
void updateSeparators(SDL_Renderer& renderer) {
createSeparatorTexture(renderer, rect.w);
for (unsigned int i = 0; i < actions->item.size(); i++) {
if (actions->item[i] == "-") {
actions->updateItem(renderer, i, "-", separatorTexture);
}
}
}
public:
SDL_Rect getRect(){
return rect;
}
void dismiss(){
//Remove the actionsPopup from the parent.
if(parent!=NULL && parent->actionsPopup==this){
parent->actionsPopup=NULL;
}
//And delete ourself.
delete this;
}
- SharedTexture createItem(SDL_Renderer& renderer,const char* caption,int icon){
+ SharedTexture createItem(SDL_Renderer& renderer,const char* caption,int icon,bool grayed=false){
//FIXME: Add some sort of caching?
//We draw using surfaces and convert to a texture in the end for now.
- SurfacePtr tip(TTF_RenderUTF8_Blended(fontText,caption,objThemes.getTextColor(true)));
+ SDL_Color color = objThemes.getTextColor(true);
+ if (grayed) {
+ color.r = 128 + color.r / 2;
+ color.g = 128 + color.g / 2;
+ color.b = 128 + color.b / 2;
+ }
+ SurfacePtr tip(TTF_RenderUTF8_Blended(fontText,caption,color));
SDL_SetSurfaceBlendMode(tip.get(), SDL_BLENDMODE_NONE);
//Create the surface, we add 16px to the width for an icon,
//plus 8px for the border to make it looks better.
SurfacePtr item = createSurface(tip->w + 24 + (icon >= 0x100 ? 24 : 0), 24);
//Draw the text on the item surface.
applySurface(24 + (icon >= 0x100 ? 24 : 0), 0, tip.get(), item.get(), NULL);
//Temporarily set the blend mode of bmGUI to none, which simply copies RGBA channels to destination.
SDL_BlendMode oldBlendMode = SDL_BLENDMODE_BLEND;
SDL_GetSurfaceBlendMode(bmGUI, &oldBlendMode);
SDL_SetSurfaceBlendMode(bmGUI, SDL_BLENDMODE_NONE);
//Check if we should draw an icon.
if(icon>0){
//Draw the check (or not).
SDL_Rect r={0,0,16,16};
r.x=((icon-1)%8)*16;
r.y=(((icon-1)/8)%8)*16;
applySurface(4,3,bmGUI,item.get(),&r);
}
//Check if we should draw a secondary icon.
if (icon >= 0x100) {
SDL_Rect r = { 0, 0, 16, 16 };
r.x = ((icon / 0x100 - 1) % 8) * 16;
r.y = (((icon / 0x100 - 1) / 8) % 8) * 16;
applySurface(28, 3, bmGUI, item.get(), &r);
}
//Reset the blend mode of bmGUI.
SDL_SetSurfaceBlendMode(bmGUI, oldBlendMode);
//Check if we should update the width., 8px extra on the width is for four pixels spacing on either side.
if(item->w+8>rect.w) {
rect.w=item->w+8;
}
return textureFromSurface(renderer, std::move(item));
}
void updateListBoxSize() {
//Update the size of the GUIListBox.
actions->width = rect.w;
actions->height = rect.h;
int x = rect.x, y = rect.y;
if (x>SCREEN_WIDTH - rect.w) x = SCREEN_WIDTH - rect.w;
else if (x<0) x = 0;
if (y>SCREEN_HEIGHT - rect.h) y = SCREEN_HEIGHT - rect.h;
else if (y<0) y = 0;
rect.x = x;
rect.y = y;
}
- void updateItem(SDL_Renderer& renderer,int index,const char* action,const char* caption,int icon=0){
- auto item=createItem(renderer,caption,icon);
- actions->updateItem(renderer, index,action,item);
+ void updateItem(SDL_Renderer& renderer,int index,const char* action,const char* caption,int icon=0,bool grayed=false){
+ auto item=createItem(renderer,caption,icon,grayed);
+ actions->updateItem(renderer, index,action,item,!grayed);
//Update the size of the GUIListBox.
updateListBoxSize();
}
- void addItem(SDL_Renderer& renderer,const char* action,const char* caption,int icon=0){
- auto item=createItem(renderer,caption,icon);
- actions->addItem(renderer,action,item);
+ void addItem(SDL_Renderer& renderer,const char* action,const char* caption,int icon=0,bool grayed=false){
+ auto item=createItem(renderer,caption,icon,grayed);
+ actions->addItem(renderer,action,item,!grayed);
//Update the height.
rect.h += 24;
//Update the size of the GUIListBox.
updateListBoxSize();
}
void addSeparator(SDL_Renderer& renderer) {
actions->addItem(renderer, "-", getOrCreateSeparatorTexture(renderer), false);
//Update the height.
rect.h += 5;
}
LevelEditorActionsPopup(ImageManager& imageManager,SDL_Renderer& renderer,LevelEditor* parent, GameObject* target, int x=0, int y=0){
this->parent=parent;
this->target=target;
rect.x = x;
rect.y = y;
//NOTE: The size gets set in the addItem method, height is already four to prevent a scrollbar.
rect.w=0;
rect.h=4;
//Load the gui images.
bmGUI=imageManager.loadImage(getDataPath()+"gfx/gui.png");
//Create the behaviour vector.
behaviour.push_back(_("On"));
behaviour.push_back(_("Off"));
behaviour.push_back(_("Toggle"));
//Create the states list.
states.push_back(_("Complete"));
states.push_back(_("One step"));
states.push_back(_("Two steps"));
states.push_back(_("Gone"));
//TODO: The width should be based on the longest option.
//Create default actions.
//NOTE: Width and height are determined later on when the options are rendered.
actions=new GUIListBox(imageManager,renderer,0,0,0,0);
actions->eventCallback=this;
//Check if it's a block or not.
if(target!=NULL)
addBlockItems(renderer);
else
addLevelItems(renderer);
}
static std::string getRepeatModeName(int mode) {
switch (mode) {
case Scenery::NEGATIVE_INFINITY:
return _("Negative infinity");
case Scenery::ZERO:
return _("Zero");
case Scenery::LEVEL_SIZE:
return _("Level size");
case Scenery::POSITIVE_INFINITY:
return _("Positive infinity");
default:
return _("Default");
}
}
void addBlockItems(SDL_Renderer& renderer){
//Check if the block is selected or not.
std::vector<GameObject*>::iterator it;
it=find(parent->selection.begin(),parent->selection.end(),target);
if(it!=parent->selection.end())
addItem(renderer,"Deselect",_("Deselect"));
else
addItem(renderer,"Select",_("Select"));
addItem(renderer,"Delete",_("Delete"),8);
addSeparator(renderer);
Scenery *scenery = dynamic_cast<Scenery*>(target);
if (scenery) {
// it is scenery block
addItem(renderer, "RepeatMode0", tfm::format(_("Horizontal repeat start: %s"),
getRepeatModeName(scenery->repeatMode & 0xFF)).c_str(), 8 * 2 + 3);
addItem(renderer, "RepeatMode1", tfm::format(_("Horizontal repeat end: %s"),
getRepeatModeName((scenery->repeatMode >> 8) & 0xFF)).c_str(), 8 * 2 + 4);
addItem(renderer, "RepeatMode2", tfm::format(_("Vertical repeat start: %s"),
getRepeatModeName((scenery->repeatMode >> 16) & 0xFF)).c_str(), 8 * 3 + 3);
addItem(renderer, "RepeatMode3", tfm::format(_("Vertical repeat end: %s"),
getRepeatModeName((scenery->repeatMode >> 24) & 0xFF)).c_str(), 8 * 3 + 4);
if (scenery->sceneryName_.empty()) {
addSeparator(renderer);
addItem(renderer, "CustomScenery", _("Custom scenery"), 8 + 4);
}
return;
}
addItem(renderer, "Visible", _("Visible"), (target->getEditorProperty("visible") == "1") ? 2 : 1);
//Get the type of the target.
int type = target->type;
//Determine what to do depending on the type.
if(isLinkable[type]){
//Check if it's a moving block type or trigger.
if(type==TYPE_BUTTON || type==TYPE_SWITCH || type==TYPE_PORTAL){
addItem(renderer, "Link", _("Link"), 8 * 2 + 8);
addItem(renderer, "Remove Links", _("Remove Links"), 8 * 2 + 7);
//Check if it's a portal, which contains a automatic option, and triggers a behaviour one.
if(type==TYPE_PORTAL){
addItem(renderer,"Automatic",_("Automatic"),(target->getEditorProperty("automatic")=="1")?2:1);
}else{
//Get the current behaviour.
int currentBehaviour=2;
if(target->getEditorProperty("behaviour")=="on"){
currentBehaviour=0;
}else if(target->getEditorProperty("behaviour")=="off"){
currentBehaviour=1;
}
addItem(renderer, "Behaviour", tfm::format(_("Behavior: %s"), behaviour[currentBehaviour]).c_str());
}
}else{
addItem(renderer, "Path", _("Path"), 8 + 5);
addItem(renderer, "Remove Path", _("Remove Path"), 8 * 4 + 2);
addItem(renderer,"Activated",_("Activated"),(target->getEditorProperty("activated")=="1")?2:1);
addItem(renderer,"Looping",_("Looping"),(target->getEditorProperty("loop")=="1")?2:1);
}
}
//Check for a conveyor belt.
if(type==TYPE_CONVEYOR_BELT || type==TYPE_SHADOW_CONVEYOR_BELT){
addItem(renderer,"Activated",_("Activated"),(target->getEditorProperty("activated")=="1")?2:1);
addItem(renderer,"Speed",_("Speed"));
}
//Check if it's a fragile block.
if(type==TYPE_FRAGILE){
//Get the current state.
int currentState=atoi(target->getEditorProperty("state").c_str());
addItem(renderer, "State", tfm::format(_("State: %s"), states[currentState]).c_str());
}
//Check if it's a notification block.
if(type==TYPE_NOTIFICATION_BLOCK)
addItem(renderer,"Message",_("Message"));
//Add the custom appearance menu item.
addItem(renderer, "Appearance", _("Appearance"), 8 + 4);
addSeparator(renderer);
//Finally add scripting to the bottom.
addItem(renderer,"Scripting",_("Scripting"),8*2+1);
}
void addLevelItems(SDL_Renderer& renderer){
// add the layers
{
// background layers.
auto it = parent->sceneryLayers.begin();
for (; it != parent->sceneryLayers.end(); ++it){
if (it->first >= "f") break; // now we meet a foreground layer
int icon = parent->layerVisibility[it->first] ? (8 * 3 + 1) : (8 * 3 + 2);
- icon |= (parent->selectedLayer == it->first ? 2 : 1) << 8;
+ icon |= (parent->selectedLayer == it->first ? 3 : 36) << 8;
std::string s = "_layer:" + it->first;
addItem(renderer, s.c_str(), tfm::format(_("Background layer: %s"), it->first).c_str(), icon);
}
// the Blocks layer.
{
int icon = parent->layerVisibility[std::string()] ? (8 * 3 + 1) : (8 * 3 + 2);
- icon |= (parent->selectedLayer.empty() ? 2 : 1) << 8;
+ icon |= (parent->selectedLayer.empty() ? 3 : 36) << 8;
addItem(renderer, "_layer:", _("Blocks layer"), icon);
}
// foreground layers.
for (; it != parent->sceneryLayers.end(); ++it){
int icon = parent->layerVisibility[it->first] ? (8 * 3 + 1) : (8 * 3 + 2);
- icon |= (parent->selectedLayer == it->first ? 2 : 1) << 8;
+ icon |= (parent->selectedLayer == it->first ? 3 : 36) << 8;
std::string s = "_layer:" + it->first;
addItem(renderer, s.c_str(), tfm::format(_("Foreground layer: %s"), it->first).c_str(), icon);
}
}
addSeparator(renderer);
addItem(renderer, "AddLayer", _("Add new layer"), 8 * 3 + 6);
- addItem(renderer, "DeleteLayer", _("Delete selected layer"), 8 * 3 + 7);
- addItem(renderer, "LayerSettings", _("Configure selected layer"), 8 * 3 + 8);
- addItem(renderer, "MoveToLayer", _("Move selected object to layer"));
+ addItem(renderer, "DeleteLayer", _("Delete selected layer"), 8 * 3 + 7, parent->selectedLayer.empty());
+ addItem(renderer, "LayerSettings", _("Configure selected layer"), 8 * 3 + 8, parent->selectedLayer.empty());
+ addItem(renderer, "MoveToLayer", _("Move selected object to layer"), 0, parent->selectedLayer.empty() || parent->selection.empty());
addSeparator(renderer);
addItem(renderer,"LevelSettings",_("Settings"),8*2);
addItem(renderer,"LevelScripting",_("Scripting"),8*2+1);
}
virtual ~LevelEditorActionsPopup(){
//bmGui is freed by imageManager.
if(actions)
delete actions;
}
void render(SDL_Renderer& renderer){
//Check if we need to resize the separator.
//NOTE: if separatorTexture is NULL then it means that we didn't use the separator at all, so we don't need to update it.
if (separatorTexture && textureWidth(*separatorTexture) < rect.w) {
updateSeparators(renderer);
}
//Draw the actions.
actions->render(renderer,rect.x,rect.y);
}
void handleEvents(SDL_Renderer& renderer){
//Check if a mouse is pressed outside the popup.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
if(event.type==SDL_MOUSEBUTTONDOWN && !pointOnRect(mouse,rect)){
dismiss();
return;
}
//Let the listbox handle its events.
actions->handleEvents(renderer,rect.x,rect.y);
}
static void addLayerNameNote(ImageManager& imageManager, SDL_Renderer& renderer, GUIWindow *root, int yy = 148) {
std::string s = _("NOTE: the layers are sorted by name alphabetically.\nThe layer is background layer if its name is < 'f'\nby dictionary order, otherwise it's foreground layer.");
for (int lps = 0;;) {
size_t lpe = s.find_first_of('\n', lps);
GUIObject *obj = new GUILabel(imageManager, renderer, 40, yy, 520, 36,
lpe == string::npos ? (s.c_str() + lps) : s.substr(lps, lpe - lps).c_str());
root->addChild(obj);
if (lpe == string::npos) break;
lps = lpe + 1;
yy += 24;
}
}
void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Skip next mouse up event since we're clicking a list box and possibly showing a new window.
GUISkipNextMouseUpEvent = true;
//NOTE: There should only be one GUIObject, so we know what event is fired.
//Get the selected entry.
std::string action=actions->item[actions->value];
if(action=="Select"){
//Add the target to the selection.
parent->selection.push_back(target);
dismiss();
return;
}else if(action=="Deselect"){
//Check if the block is in the selection.
std::vector<GameObject*>::iterator it;
it=find(parent->selection.begin(),parent->selection.end(),target);
if(it!=parent->selection.end()){
//Remove the object from the selection.
parent->selection.erase(it);
}
dismiss();
return;
}else if(action=="Delete"){
parent->commandManager->doCommand(new AddRemoveGameObjectCommand(parent, target, false));
dismiss();
return;
}else if(action=="Link"){
parent->linking=true;
parent->linkingTrigger=dynamic_cast<Block*>(target);
parent->tool=LevelEditor::SELECT;
dismiss();
return;
}else if(action=="Remove Links"){
//Remove all the links
Block *block = dynamic_cast<Block*>(target);
if (block) {
RemoveLinkCommand* pCommand = new RemoveLinkCommand(parent, block);
parent->commandManager->doCommand(pCommand);
}
dismiss();
return;
}else if(action=="Path"){
parent->moving=true;
parent->pauseMode = false;
parent->pauseTime = 0;
parent->movingBlock=dynamic_cast<Block*>(target);
parent->tool=LevelEditor::SELECT;
dismiss();
return;
}else if(action=="Remove Path"){
//Remove all the paths
Block *block = dynamic_cast<Block*>(target);
if (block) {
RemovePathCommand* pCommand = new RemovePathCommand(parent, block);
parent->commandManager->doCommand(pCommand);
}
dismiss();
return;
}else if(action=="Message"){
//Create the GUI.
GUIWindow* root=new GUIWindow(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-250)/2,600,250,true,true,_("Notification block"));
root->minWidth = root->width; root->minHeight = root->height;
root->name="notificationBlockWindow";
root->eventCallback=parent;
GUIObject* obj;
obj=new GUILabel(imageManager,renderer,40,50,240,36,_("Enter message here:"));
root->addChild(obj);
GUITextArea* textarea=new GUITextArea(imageManager,renderer,50,90,500,100);
textarea->gravityRight = textarea->gravityBottom = GUIGravityRight;
//Set the name of the text area, which is used to identify the object later on.
textarea->name="message";
string tmp=target->getEditorProperty("message");
//Change \n with the characters '\n'.
while(tmp.find("\\n")!=string::npos){
tmp=tmp.replace(tmp.find("\\n"),2,"\n");
}
textarea->setString(renderer, tmp);
root->addChild(textarea);
obj=new GUIButton(imageManager,renderer,root->width*0.3,250-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgNotificationBlockOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.7,250-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}else if(action=="Activated"){
//Get the previous state.
bool enabled=(target->getEditorProperty("activated")=="1");
//Switch the state.
enabled=!enabled;
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "activated", enabled ? "1" : "0", _("Activated")));
updateItem(renderer,actions->value,"Activated",_("Activated"),enabled?2:1);
actions->value=-1;
return;
} else if (action == "Visible"){
//Get the previous state.
bool visible = (target->getEditorProperty("visible") == "1");
//Switch the state.
visible = !visible;
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "visible", visible ? "1" : "0", _("Visible")));
updateItem(renderer, actions->value, "Visible", _("Visible"), visible ? 2 : 1);
actions->value = -1;
return;
} else if (action == "Looping"){
//Get the previous state.
bool loop=(target->getEditorProperty("loop")=="1");
//Switch the state.
loop=!loop;
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "loop", loop ? "1" : "0", _("Looping")));
updateItem(renderer,actions->value,"Looping",_("Looping"),loop?2:1);
actions->value=-1;
return;
}else if(action=="Automatic"){
//Get the previous state.
bool automatic=(target->getEditorProperty("automatic")=="1");
//Switch the state.
automatic=!automatic;
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "automatic", automatic ? "1" : "0", _("Automatic")));
updateItem(renderer,actions->value,"Automatic",_("Automatic"),automatic?2:1);
actions->value=-1;
return;
}else if(action=="Behaviour"){
//Get the current behaviour.
int currentBehaviour=2;
string behave=target->getEditorProperty("behaviour");
if(behave=="on"){
currentBehaviour=0;
}else if(behave=="off"){
currentBehaviour=1;
}
//Increase the behaviour.
currentBehaviour++;
if(currentBehaviour>2)
currentBehaviour=0;
//Update the data of the block.
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "behaviour", behaviour[currentBehaviour], _("Behavior")));
//And update the item.
updateItem(renderer, actions->value, "Behaviour", tfm::format(_("Behavior: %s"), behaviour[currentBehaviour]).c_str());
actions->value=-1;
return;
}else if(action=="State"){
//Get the current state.
int currentState=atoi(target->getEditorProperty("state").c_str());
//Increase the state.
currentState++;
if(currentState>3)
currentState=0;
//Update the data of the block.
char s[64];
sprintf(s,"%d",currentState);
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "state", s, _("State")));
//And update the item.
updateItem(renderer, actions->value, "State", tfm::format(_("State: %s"), states[currentState]).c_str());
actions->value=-1;
return;
}else if(action=="Speed"){
//Create the GUI.
GUIWindow* root=new GUIWindow(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-250)/2,600,250,true,true,_("Conveyor belt speed"));
root->minWidth = root->width; root->minHeight = root->height;
root->name="conveyorBlockWindow";
root->eventCallback=parent;
GUIObject* obj;
obj=new GUILabel(imageManager,renderer,40,100,240,36,_("Enter speed here:"));
root->addChild(obj);
GUISpinBox* obj2=new GUISpinBox(imageManager,renderer,240,100,320,36);
obj2->gravityRight = GUIGravityRight;
//Set the name of the text area, which is used to identify the object later on.
obj2->name="speed";
obj2->caption=target->getEditorProperty("speed10");
obj2->format = "%1.0f";
obj2->update();
root->addChild(obj2);
obj = new GUILabel(imageManager, renderer, 40, 160, 520, 36, _("NOTE: 1 Speed = 0.08 block/s"));
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.3,250-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgConveyorBlockOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.7,250-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}else if(action=="Scripting"){
//Create the GUI.
GUIWindow* root=new GUIWindow(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-500)/2,600,500,true,true,_("Scripting"));
root->minWidth = root->width; root->minHeight = root->height;
root->name="scriptingWindow";
root->eventCallback=parent;
GUIObject* obj;
obj=new GUILabel(imageManager,renderer,50,60,240,36,_("Id:"));
root->addChild(obj);
obj=new GUITextBox(imageManager,renderer,100,60,240,36,dynamic_cast<Block*>(target)->id.c_str());
obj->gravityRight = GUIGravityRight;
obj->name="id";
root->addChild(obj);
GUISingleLineListBox* list=new GUISingleLineListBox(imageManager,renderer,50,100,500,36);
list->gravityRight = GUIGravityRight;
std::map<std::string,int>::iterator it;
for(it=Game::gameObjectEventNameMap.begin();it!=Game::gameObjectEventNameMap.end();++it)
list->addItem(it->first);
list->name="cfgScriptingEventType";
list->value=0;
list->eventCallback=root;
root->addChild(list);
//Add a text area for each event type.
Block* block=dynamic_cast<Block*>(target);
for(unsigned int i=0;i<list->item.size();i++){
GUITextArea* text=new GUITextArea(imageManager,renderer,50,140,500,300);
text->gravityRight = text->gravityBottom = GUIGravityRight;
text->name=list->item[i].first;
text->setFont(fontMono);
//Only set the first one visible and enabled.
text->visible=(i==0);
text->enabled=(i==0);
map<int,string>::iterator it=block->scripts.find(Game::gameObjectEventNameMap[list->item[i].first]);
if(it!=block->scripts.end())
text->setString(renderer, it->second);
root->addChild(text);
}
obj=new GUIButton(imageManager,renderer,root->width*0.3,500-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgScriptingOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.7,500-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}else if(action=="LevelSettings"){
//Open the levelSettings window.
parent->levelSettings(imageManager,renderer);
//And dismiss this popup.
dismiss();
return;
}else if(action=="LevelScripting"){
//Create the GUI.
GUIWindow* root=new GUIWindow(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-500)/2,600,500,true,true,_("Level Scripting"));
root->minWidth = root->width; root->minHeight = root->height;
root->name="levelScriptingWindow";
root->eventCallback=parent;
GUIObject* obj;
GUISingleLineListBox* list=new GUISingleLineListBox(imageManager,renderer,50,60,500,36);
list->gravityRight = GUIGravityRight;
std::map<std::string,int>::iterator it;
for(it=Game::levelEventNameMap.begin();it!=Game::levelEventNameMap.end();++it)
list->addItem(it->first);
list->name="cfgLevelScriptingEventType";
list->value=0;
list->eventCallback=root;
root->addChild(list);
//Add a text area for each event type.
for(unsigned int i=0;i<list->item.size();i++){
GUITextArea* text=new GUITextArea(imageManager,renderer,50,100,500,340);
text->gravityRight = text->gravityBottom = GUIGravityRight;
text->name=list->item[i].first;
text->setFont(fontMono);
//Only set the first one visible and enabled.
text->visible=(i==0);
text->enabled=(i==0);
map<int,string>::iterator it=parent->scripts.find(Game::levelEventNameMap[list->item[i].first]);
if(it!=parent->scripts.end())
text->setString(renderer, it->second);
root->addChild(text);
}
obj=new GUIButton(imageManager,renderer,root->width*0.3,500-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgLevelScriptingOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.7,500-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
} else if (action.size() >= 7 && action.substr(0, 7) == "_layer:") {
std::string s0 = action.substr(7);
auto it = parent->layerVisibility.find(s0);
if (it != parent->layerVisibility.end()) {
int x;
SDL_GetMouseState(&x, NULL);
if (x < rect.x + 24) {
// toggle the visibility
it->second = !it->second;
if (parent->selectedLayer == it->first) {
// deselect all blocks if the visibility of current layer is changed
parent->deselectAll();
}
} else if (parent->selectedLayer != it->first) {
// deselect all blocks if the selected layer is changed
parent->deselectAll();
// uncheck the previously selected layer
std::string oldSelected = "_layer:" + parent->selectedLayer;
for (unsigned int idx = 0; idx < actions->item.size(); idx++) {
if (actions->item[idx] == oldSelected) {
int icon = parent->layerVisibility[parent->selectedLayer] ? (8 * 3 + 1) : (8 * 3 + 2);
- icon |= 1 << 8;
+ icon |= 36 << 8;
updateItem(renderer, idx, oldSelected.c_str(),
parent->selectedLayer.empty() ? _("Blocks layer") :
tfm::format((parent->selectedLayer < "f") ? _("Background layer: %s") : _("Foreground layer: %s"), parent->selectedLayer).c_str(),
icon);
break;
}
}
// change the selected layer
parent->selectedLayer = it->first;
} else {
actions->value = -1;
return;
}
int icon = it->second ? (8 * 3 + 1) : (8 * 3 + 2);
- icon |= (parent->selectedLayer == it->first ? 2 : 1) << 8;
+ icon |= (parent->selectedLayer == it->first ? 3 : 36) << 8;
std::string s = "_layer:" + it->first;
updateItem(renderer, actions->value, s.c_str(),
it->first.empty() ? _("Blocks layer") :
tfm::format((it->first < "f") ? _("Background layer: %s") : _("Foreground layer: %s"), it->first).c_str(),
icon);
+
+ // update some other menu items according to selection/visibility changes
+ for (unsigned int i = 0; i < actions->item.size(); i++) {
+ if (actions->item[i] == "DeleteLayer") {
+ updateItem(renderer, i, "DeleteLayer", _("Delete selected layer"), 8 * 3 + 7, parent->selectedLayer.empty());
+ } else if (actions->item[i] == "LayerSettings") {
+ updateItem(renderer, i, "LayerSettings", _("Configure selected layer"), 8 * 3 + 8, parent->selectedLayer.empty());
+ } else if (actions->item[i] == "MoveToLayer") {
+ updateItem(renderer, i, "MoveToLayer", _("Move selected object to layer"), 0, parent->selectedLayer.empty() || parent->selection.empty());
+ }
+ }
}
actions->value = -1;
return;
} else if (action == "AddLayer") {
//Create the add layer GUI.
GUIWindow* root = new GUIWindow(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 400) / 2, 600, 400, true, true, _("Add layer"));
root->minWidth = root->width; root->minHeight = root->height;
root->name = "addLayerWindow";
root->eventCallback = parent;
GUIObject* obj;
obj = new GUILabel(imageManager, renderer, 40, 64, 520, 36, _("Enter the layer name:"));
root->addChild(obj);
GUITextBox* obj2 = new GUITextBox(imageManager, renderer, 40, 100, 520, 36);
obj2->gravityRight = GUIGravityRight;
//Set the name of the text area, which is used to identify the object later on.
obj2->name = "layerName";
root->addChild(obj2);
addLayerNameNote(imageManager, renderer, root);
obj = new GUIButton(imageManager, renderer, root->width*0.3, 400 - 44, -1, 36, _("OK"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgAddLayerOK";
obj->eventCallback = root;
root->addChild(obj);
obj = new GUIButton(imageManager, renderer, root->width*0.7, 400 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgCancel";
obj->eventCallback = root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root] = target;
//And dismiss this popup.
dismiss();
return;
} else if (action == "DeleteLayer") {
// delete selected layer
if (parent->selectedLayer.empty()) {
// can't delete Blocks layer
actions->value = -1;
return;
}
if (parent->sceneryLayers.find(parent->selectedLayer) == parent->sceneryLayers.end()) {
// can't find the layer with given name
actions->value = -1;
return;
}
if (msgBox(imageManager, renderer,
tfm::format(_("Are you sure you want to delete layer '%s'?"), parent->selectedLayer).c_str(),
MsgBoxYesNo, _("Delete layer")) == MsgBoxYes)
{
// do the actual operation
parent->commandManager->doCommand(new AddRemoveLayerCommand(parent, parent->selectedLayer, false));
}
dismiss();
return;
} else if (action == "LayerSettings") {
// rename selected layer
if (parent->selectedLayer.empty()) {
// can't rename Blocks layer
actions->value = -1;
return;
}
auto it = parent->sceneryLayers.find(parent->selectedLayer);
if (it == parent->sceneryLayers.end()) {
// can't find the layer with given name
actions->value = -1;
return;
}
//Create the rename layer GUI.
GUIWindow* root = new GUIWindow(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 500) / 2, 600, 500, true, true, _("Layer settings"));
root->minWidth = root->width; root->minHeight = root->height;
root->name = "layerSettingsWindow";
root->eventCallback = parent;
GUIObject* obj;
obj = new GUILabel(imageManager, renderer, 40, 64, 520, 36, _("Layer name:"));
root->addChild(obj);
GUITextBox* textBox = new GUITextBox(imageManager, renderer, 40, 100, 520, 36, it->first.c_str());
textBox->gravityRight = GUIGravityRight;
//Set the name of the text area, which is used to identify the object later on.
textBox->name = "layerName";
root->addChild(textBox);
// A stupid code to save the old name
obj = new GUIObject(imageManager, renderer, 0, 0, 0, 0, it->first.c_str(), 0, false, false);
obj->name = "oldName";
root->addChild(obj);
addLayerNameNote(imageManager, renderer, root);
obj = new GUILabel(imageManager, renderer, 40, 284, 520, 36, _("Layer moving speed (1 speed = 0.8 block/s):"));
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 40, 320, 40, 36, "X");
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 320, 320, 40, 36, "Y");
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
root->addChild(obj);
GUISpinBox *spinBox = new GUISpinBox(imageManager, renderer, 80, 320, 200, 36);
spinBox->gravityRight = GUIGravityCenter;
spinBox->name = "speedX";
spinBox->caption = tfm::format("%g", it->second->speedX);
spinBox->format = "%g";
root->addChild(spinBox);
spinBox = new GUISpinBox(imageManager, renderer, 360, 320, 200, 36);
spinBox->gravityLeft = GUIGravityCenter; spinBox->gravityRight = GUIGravityRight;
spinBox->name = "speedY";
spinBox->caption = tfm::format("%g", it->second->speedY);
spinBox->format = "%g";
root->addChild(spinBox);
obj = new GUILabel(imageManager, renderer, 40, 364, 520, 36, _("Speed of following camera:"));
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 40, 400, 40, 36, "X");
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 320, 400, 40, 36, "Y");
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
root->addChild(obj);
spinBox = new GUISpinBox(imageManager, renderer, 80, 400, 200, 36);
spinBox->gravityRight = GUIGravityCenter;
spinBox->name = "cameraX";
spinBox->caption = tfm::format("%g", it->second->cameraX);
spinBox->format = "%g";
spinBox->change = 0.1f;
root->addChild(spinBox);
spinBox = new GUISpinBox(imageManager, renderer, 360, 400, 200, 36);
spinBox->gravityLeft = GUIGravityCenter; spinBox->gravityRight = GUIGravityRight;
spinBox->name = "cameraY";
spinBox->caption = tfm::format("%g", it->second->cameraY);
spinBox->format = "%g";
spinBox->change = 0.1f;
root->addChild(spinBox);
obj = new GUIButton(imageManager, renderer, root->width*0.3, 500 - 44, -1, 36, _("OK"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgLayerSettingsOK";
obj->eventCallback = root;
root->addChild(obj);
obj = new GUIButton(imageManager, renderer, root->width*0.7, 500 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgCancel";
obj->eventCallback = root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root] = target;
//And dismiss this popup.
dismiss();
return;
} else if (action == "MoveToLayer") {
// move the selected object to another layer
if (parent->selection.empty() || parent->selectedLayer.empty()) {
// either nothing selected, or can't move objects in Blocks layer
actions->value = -1;
return;
}
//Create the rename layer GUI.
GUIWindow* root = new GUIWindow(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 300) / 2, 600, 400, true, true, _("Move to layer"));
root->minWidth = root->width; root->minHeight = root->height;
root->name = "moveToLayerWindow";
root->eventCallback = parent;
GUIObject* obj;
obj = new GUILabel(imageManager, renderer, 40, 64, 520, 36, _("Enter the layer name (create new layer if necessary):"));
root->addChild(obj);
GUITextBox* obj2 = new GUITextBox(imageManager, renderer, 40, 100, 520, 36, parent->selectedLayer.c_str());
obj2->gravityRight = GUIGravityRight;
//Set the name of the text area, which is used to identify the object later on.
obj2->name = "layerName";
root->addChild(obj2);
// A stupid code to save the old name
obj = new GUIObject(imageManager, renderer, 0, 0, 0, 0, parent->selectedLayer.c_str(), 0, false, false);
obj->name = "oldName";
root->addChild(obj);
addLayerNameNote(imageManager, renderer, root);
obj = new GUIButton(imageManager, renderer, root->width*0.3, 400 - 44, -1, 36, _("OK"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgMoveToLayerOK";
obj->eventCallback = root;
root->addChild(obj);
obj = new GUIButton(imageManager, renderer, root->width*0.7, 400 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgCancel";
obj->eventCallback = root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root] = target;
//And dismiss this popup.
dismiss();
return;
} else if (action.size() > 10 && action.substr(0, 10) == "RepeatMode") {
Scenery *scenery = dynamic_cast<Scenery*>(target);
if (scenery) {
int index = atoi(action.c_str() + 10);
assert(index >= 0 && index < 4);
//Get the current repeat mode.
unsigned int repeatMode = scenery->repeatMode;
//Extract the value we want to modify.
unsigned int i = (repeatMode >> (index * 8)) & 0xFF;
repeatMode &= ~(0xFFu << (index * 8));
//Increase the value.
for (;;) {
i++;
if (i >= Scenery::REPEAT_MODE_MAX) i = 0;
// skip invalid values (POSITIVE_INFINITY for start, NEGATIVE_INFINITY for end)
if ((index & 1) == 0 && i == Scenery::POSITIVE_INFINITY) continue;
if ((index & 1) == 1 && i == Scenery::NEGATIVE_INFINITY) continue;
break;
}
//Update the repeat mode of the block.
char s[64];
sprintf(s, "%u", repeatMode | (i << (index * 8)));
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "repeatMode", s, _("Repeat mode")));
//And update the item.
const char* ss[4] = {
__("Horizontal repeat start: %s"),
__("Horizontal repeat end: %s"),
__("Vertical repeat start: %s"),
__("Vertical repeat end: %s"),
};
const int icons[4] = {
8 * 2 + 3, 8 * 2 + 4, 8 * 3 + 3, 8 * 3 + 4,
};
updateItem(renderer, actions->value, action.c_str(), tfm::format(_(ss[index]), getRepeatModeName(i)).c_str(), icons[index]);
}
actions->value = -1;
return;
} else if (action == "CustomScenery") {
//Create the GUI.
GUIWindow* root = new GUIWindow(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 400) / 2, 600, 400, true, true, _("Custom scenery"));
root->minWidth = root->width; root->minHeight = root->height;
root->name = "customSceneryWindow";
root->eventCallback = parent;
GUIObject* obj;
obj = new GUILabel(imageManager, renderer, 50, 60, 240, 36, _("Custom scenery:"));
root->addChild(obj);
//Add a text area.
Scenery* scenery = dynamic_cast<Scenery*>(target);
GUITextArea* text = new GUITextArea(imageManager, renderer, 50, 100, 500, 240);
text->gravityRight = text->gravityBottom = GUIGravityRight;
text->name = "cfgCustomScenery";
text->setFont(fontMono);
//Only set the first one visible and enabled.
text->visible = true;
text->enabled = true;
// FIXME: an ad-hoc code
std::string s;
for (char c : scenery->customScenery_) {
if (c == '\t')
s.append(" ");
else
s.push_back(c);
}
text->setString(renderer, s);
root->addChild(text);
obj = new GUIButton(imageManager, renderer, root->width*0.3, 400 - 44, -1, 36, _("OK"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgCustomSceneryOK";
obj->eventCallback = root;
root->addChild(obj);
obj = new GUIButton(imageManager, renderer, root->width*0.7, 400 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgCancel";
obj->eventCallback = root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root] = target;
//And dismiss this popup.
dismiss();
return;
} else if (action == "Appearance"){
//Create the GUI.
GUIWindow* root = new GUIWindow(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 400) / 2, 600, 400, true, true, _("Appearance"));
root->minWidth = root->width; root->minHeight = root->height;
root->name = "appearanceWindow";
root->eventCallback = parent;
GUIObject* obj;
Block* block = dynamic_cast<Block*>(target);
//Create a list box showing all available scenery blocks.
GUIListBox *list = new GUIListBox(imageManager, renderer, 50, 60, 440, 280);
list->gravityRight = list->gravityBottom = GUIGravityRight;
list->name = "lstAppearance";
list->eventCallback = root;
root->addChild(list);
//TODO: Show the image along with the text in the list box.
//Currently I don't like to do that because this requires a lot of video memory since there are a lot of available scenery blocks.
list->addItem(renderer, _("(Use the default appearance for this block)"));
if (block->customAppearanceName.empty()) list->value = 0;
for (const std::string& s : parent->sceneryBlockNames) {
list->addItem(renderer, describeSceneryName(s));
if (block->customAppearanceName == s) list->value = list->item.size() - 1;
}
if (list->value < 0) {
std::cerr << "WARNING: The custom appearance '" << block->customAppearanceName << "' is unrecognized, added it to the list box anyway" << std::endl;
list->addItem(renderer, block->customAppearanceName);
list->value = list->item.size() - 1;
}
//Ask the list box to update scrollbar and we scroll the scrollbar to the correct position (FIXME: ad-hoc code)
list->render(renderer, 0, 0, false);
list->scrollScrollbar(list->value);
//Create an image widget showing the appearance of selected scenery block.
GUIImage *image = new GUIImage(imageManager, renderer, 500, 60, 50, 50);
image->gravityLeft = image->gravityRight = GUIGravityRight;
image->name = "imgAppearance";
root->addChild(image);
//Add a Change event to the list box which will be processed next frame, which is used to update the image widget.
GUIEventQueue.push_back(GUIEvent{ list->eventCallback, list->name, list, GUIEventChange });
obj = new GUIButton(imageManager, renderer, root->width*0.3, 400 - 44, -1, 36, _("OK"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgAppearanceOK";
obj->eventCallback = root;
root->addChild(obj);
obj = new GUIButton(imageManager, renderer, root->width*0.7, 400 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgCancel";
obj->eventCallback = root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root] = target;
//And dismiss this popup.
dismiss();
return;
}
}
};
/////////////////LevelEditorSelectionPopup/////////////////
class LevelEditorSelectionPopup{
private:
//The parent object.
LevelEditor* parent;
//The position of window.
SDL_Rect rect;
//GUI image.
SharedTexture bmGUI;
//The selection
std::vector<GameObject*> selection;
//The scrollbar.
GUIScrollBar* scrollBar;
//Highlighted object.
GameObject* highlightedObj;
//Highlighted button index. 0=none 1=select/deselect 2=delete 3=configure
int highlightedBtn;
public:
int startRow,showedRow;
//If selection is dirty
bool dirty;
public:
SDL_Rect getRect(){
return rect;
}
int width(){
return rect.w;
}
int height(){
return rect.h;
}
void updateScrollBar(ImageManager& imageManager, SDL_Renderer& renderer){
int m=selection.size()-showedRow;
if(m>0){
if(startRow<0) startRow=0;
else if(startRow>m) startRow=m;
if(scrollBar==NULL){
scrollBar=new GUIScrollBar(imageManager,renderer,0,0,16,rect.h-16,ScrollBarVertical,startRow,0,m,1,showedRow);
}
scrollBar->visible=true;
scrollBar->maxValue=m;
scrollBar->value=startRow;
}else{
startRow=0;
if(scrollBar){
scrollBar->visible=false;
scrollBar->value=0;
}
}
}
void updateSelection(ImageManager& imageManager, SDL_Renderer& renderer){
if(parent!=NULL){
std::vector<Block*>& v=parent->levelObjects;
for(int i=selection.size()-1;i>=0;i--){
if(find(v.begin(),v.end(),selection[i])==v.end()){
selection.erase(selection.begin()+i);
}
}
updateScrollBar(imageManager,renderer);
}
}
void dismiss(){
if(parent!=NULL && parent->selectionPopup==this){
parent->selectionPopup=NULL;
}
delete this;
}
LevelEditorSelectionPopup(LevelEditor* parent, ImageManager& imageManager, SDL_Renderer& renderer, std::vector<GameObject*>& selection, int x=0, int y=0){
this->parent=parent;
this->selection=selection;
dirty=false;
scrollBar=NULL;
highlightedObj=NULL;
highlightedBtn=0;
//calc window size
startRow=0;
showedRow=selection.size();
int m=SCREEN_HEIGHT/64-1;
if(showedRow>m) showedRow=m;
rect.w=320;
rect.h=showedRow*64+16;
if(x>SCREEN_WIDTH-rect.w) x=SCREEN_WIDTH-rect.w;
else if(x<0) x=0;
if(y>SCREEN_HEIGHT-rect.h) y=SCREEN_HEIGHT-rect.h;
else if(y<0) y=0;
rect.x=x;
rect.y=y;
updateScrollBar(imageManager,renderer);
//Load the gui images.
bmGUI=imageManager.loadTexture(getDataPath()+"gfx/gui.png",renderer);
}
virtual ~LevelEditorSelectionPopup(){
if(scrollBar)
delete scrollBar;
}
void move(int x,int y){
if(x>SCREEN_WIDTH-rect.w) x=SCREEN_WIDTH-rect.w;
else if(x<0) x=0;
if(y>SCREEN_HEIGHT-rect.h) y=SCREEN_HEIGHT-rect.h;
else if(y<0) y=0;
rect.x=x;
rect.y=y;
}
void render(ImageManager& imageManager, SDL_Renderer& renderer){
//Check dirty
if(dirty){
updateSelection(imageManager,renderer);
if(selection.empty()){
dismiss();
return;
}
dirty=false;
}
//background
drawGUIBox(rect.x,rect.y,rect.w,rect.h,renderer,0xFFFFFFFFU);
//get mouse position
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
//the tool tip of item
SDL_Rect tooltipRect;
//string tooltip;
if(scrollBar && scrollBar->visible){
startRow=scrollBar->value;
}
highlightedObj=NULL;
highlightedBtn=0;
ToolTips toolTip = ToolTips::TooltipMax;
+ int maxWidth = 0;
+
//draw avaliable item
for(int i=0;i<showedRow;i++){
int j=startRow+i;
if(j>=(int)selection.size()) break;
SDL_Rect r={rect.x+8,rect.y+i*64+8,rect.w-16,64};
if(scrollBar && scrollBar->visible) r.w-=24;
//check highlight
if(pointOnRect(mouse,r)){
highlightedObj=selection[j];
//0xCCCCCC
SDL_SetRenderDrawColor(&renderer,0xCC,0xCC,0xCC,0xFF);
SDL_RenderFillRect(&renderer,&r);
}
const int type = selection[j]->type;
Scenery *scenery = dynamic_cast<Scenery*>(selection[j]);
if (scenery) {
if (scenery->themeBlock == &(scenery->internalThemeBlock)) {
// custom scenery, draw an ad-hoc stupid icon
if (parent) {
const SDL_Rect srcRect = { 48, 16, 16, 16 };
const SDL_Rect dstRect = { r.x + 7, r.y + 7, 16, 16 };
SDL_RenderCopy(&renderer, parent->bmGUI.get(), &srcRect, &dstRect);
}
} else {
scenery->themeBlock->editorPicture.draw(renderer, r.x + 7, r.y + 7);
}
} else {
//draw tile picture
ThemeBlock* obj = objThemes.getBlock(type);
if (obj){
//obj->editorPicture.draw(screen,r.x+7,r.y+7);
obj->editorPicture.draw(renderer, r.x + 7, r.y + 7);
}
}
if(parent!=NULL){
//draw name
TexturePtr& tex = scenery ? (parent->getCachedTextTexture(renderer, scenery->sceneryName_.empty()
/// TRANSLATORS: Block name
? std::string(_("Custom scenery block")) : describeSceneryName(scenery->sceneryName_)))
: parent->typeTextTextures.at(type);
if (tex) {
+ const int w = textureWidth(tex) + 160;
+ if (w > maxWidth) maxWidth = w;
applyTexture(r.x + 64, r.y + (64 - textureHeight(tex)) / 2, tex, renderer);
}
//draw selected
{
std::vector<GameObject*> &v=parent->selection;
bool isSelected=find(v.begin(),v.end(),selection[j])!=v.end();
SDL_Rect r1={isSelected?16:0,0,16,16};
SDL_Rect r2={r.x+r.w-72,r.y+20,24,24};
if(pointOnRect(mouse,r2)){
drawGUIBox(r2.x,r2.y,r2.w,r2.h,renderer,0x999999FFU);
tooltipRect=r2;
//tooltip=_("Select");
highlightedBtn=1;
- toolTip=ToolTips::Select;
+ toolTip=ToolTips::Select_UsedInSelectionPopup;
}
r2.x+=4;
r2.y+=4;
r2.w=r1.w;
r2.h=r1.h;
SDL_RenderCopy(&renderer, bmGUI.get(),&r1,&r2);
}
//draw delete
{
SDL_Rect r1={112,0,16,16};
SDL_Rect r2={r.x+r.w-48,r.y+20,24,24};
if(pointOnRect(mouse,r2)){
drawGUIBox(r2.x,r2.y,r2.w,r2.h,renderer,0x999999FFU);
tooltipRect=r2;
//tooltip=_("Delete");
highlightedBtn=2;
- toolTip=ToolTips::Delete;
+ toolTip=ToolTips::Delete_UsedInSelectionPopup;
}
r2.x+=4;
r2.y+=4;
r2.w=r1.w;
r2.h=r1.h;
SDL_RenderCopy(&renderer, bmGUI.get(),&r1,&r2);
}
//draw configure
{
SDL_Rect r1={112,16,16,16};
SDL_Rect r2={r.x+r.w-24,r.y+20,24,24};
if(pointOnRect(mouse,r2)){
drawGUIBox(r2.x,r2.y,r2.w,r2.h,renderer,0x999999FFU);
tooltipRect=r2;
//tooltip=_("Configure");
- toolTip=ToolTips::Configure;
+ toolTip=ToolTips::Configure_UsedInSelectionPopup;
highlightedBtn=3;
}
r2.x+=4;
r2.y+=4;
r2.w=r1.w;
r2.h=r1.h;
SDL_RenderCopy(&renderer, bmGUI.get(),&r1,&r2);
}
}
}
//draw scrollbar
if(scrollBar && scrollBar->visible){
scrollBar->render(renderer,rect.x+rect.w-24,rect.y+8);
}
//draw tooltip
if(parent && int(toolTip) < parent->tooltipTextures.size()){
//Tool specific text.
TexturePtr& tip=parent->tooltipTextures.at(size_t(toolTip));
//Draw only if there's a tooltip available
if(tip){
const auto tipSize = rectFromTexture(tip);
tooltipRect.y-=4;
tooltipRect.h+=8;
if(tooltipRect.y+tooltipRect.h+tipSize.h>SCREEN_HEIGHT-20)
tooltipRect.y-=tipSize.h;
else
tooltipRect.y+=tooltipRect.h;
if(tooltipRect.x+tipSize.w>SCREEN_WIDTH-20)
tooltipRect.x=SCREEN_WIDTH-20-tipSize.w;
//Draw borders around text
Uint32 color=0xFFFFFF00|230;
drawGUIBox(tooltipRect.x-2,tooltipRect.y-2,tipSize.w+4,tipSize.h+4,renderer,color);
//Draw tooltip's text
applyTexture(tooltipRect.x,tooltipRect.y,tip,renderer);
}
}
+
+ //Resize the selection popup if necessary
+ if (maxWidth > rect.w) {
+ rect.w = maxWidth;
+ move(rect.x, rect.y);
+ }
}
void handleEvents(ImageManager& imageManager,SDL_Renderer& renderer){
//Check dirty
if(dirty){
updateSelection(imageManager,renderer);
if(selection.empty()){
dismiss();
return;
}
dirty=false;
}
//Check scrollbar event
if(scrollBar && scrollBar->visible){
if(scrollBar->handleEvents(renderer,rect.x+rect.w-24,rect.y+8)) return;
}
if(event.type==SDL_MOUSEBUTTONDOWN){
if(event.button.button==SDL_BUTTON_LEFT){
SDL_Rect mouse={event.button.x,event.button.y,0,0};
//Check if close it
if(!pointOnRect(mouse,rect)){
dismiss();
return;
}
//Check if item is clicked
if(highlightedObj!=NULL && highlightedBtn>0 && parent!=NULL){
//std::vector<Block*>& v=parent->levelObjects;
if(/*find(v.begin(),v.end(),highlightedObj)!=v.end()*/true/*???*/){
switch(highlightedBtn){
case 1:
{
std::vector<GameObject*>& v2=parent->selection;
std::vector<GameObject*>::iterator it=find(v2.begin(),v2.end(),highlightedObj);
if(it==v2.end()){
v2.push_back(highlightedObj);
}else{
v2.erase(it);
}
}
break;
case 2:
parent->commandManager->doCommand(new AddRemoveGameObjectCommand(parent, highlightedObj, false));
break;
case 3:
if(parent->actionsPopup)
delete parent->actionsPopup;
parent->actionsPopup=new LevelEditorActionsPopup(imageManager,renderer,parent,highlightedObj,mouse.x,mouse.y);
break;
}
}
}
}
}
else if(event.type == SDL_MOUSEWHEEL) {
//check mousewheel
if(event.wheel.y < 0){
startRow-=2;
updateScrollBar(imageManager,renderer);
return;
} else {
startRow+=2;
updateScrollBar(imageManager,renderer);
return;
}
}
}
};
/////////////////MovingPosition////////////////////////////
MovingPosition::MovingPosition(int x,int y,int time){
this->x=x;
this->y=y;
this->time=time;
}
MovingPosition::~MovingPosition(){}
void MovingPosition::updatePosition(int x,int y){
this->x=x;
this->y=y;
}
/////////////////LEVEL EDITOR//////////////////////////////
LevelEditor::LevelEditor(SDL_Renderer& renderer, ImageManager& imageManager):Game(renderer, imageManager){
//This will set some default settings.
reset();
//Create the GUI root.
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Load the toolbar.
toolbar=imageManager.loadTexture(getDataPath()+"gfx/menu/toolbar.png",renderer);
toolbarRect={(SCREEN_WIDTH-460)/2,SCREEN_HEIGHT-50,460,50};
selectionPopup=NULL;
actionsPopup=NULL;
movingSpeedWidth=-1;
//Load the selectionMark.
selectionMark=imageManager.loadTexture(getDataPath()+"gfx/menu/selection.png",renderer);
//Load the movingMark.
movingMark=imageManager.loadTexture(getDataPath()+"gfx/menu/moving.png",renderer);
//Load the gui images.
bmGUI=imageManager.loadTexture(getDataPath()+"gfx/gui.png",renderer);
toolboxText=textureFromText(renderer,*fontText,_("Toolbox"),objThemes.getTextColor(true));
for(size_t i = 0;i < typeTextTextures.size();++i) {
typeTextTextures[i] =
textureFromText(renderer,
*fontText,
_(blockNames[i]),
objThemes.getTextColor(true));
}
for(size_t i = 0;i < tooltipTextures.size();++i) {
if (tooltipNames[i][0]) {
- tooltipTextures[i] =
- textureFromText(renderer,
- *fontText,
- _(tooltipNames[i]),
- objThemes.getTextColor(true));
+ std::string s = _(tooltipNames[i]);
+ if (tooltipHotkey[i][0]) {
+ s += " (" + std::string(tooltipHotkey[i]) + ")";
+ } else if (tooltipHotkey2[i] >= 0) {
+ std::string s2 = InputManagerKeyCode::describeTwo(
+ inputMgr.getKeyCode((InputManagerKeys)tooltipHotkey2[i], false),
+ inputMgr.getKeyCode((InputManagerKeys)tooltipHotkey2[i], true));
+ if (!s2.empty()) s += " (" + s2 + ")";
+ }
+ tooltipTextures[i] = textureFromText(renderer, *fontText, s.c_str(), objThemes.getTextColor(true));
}
}
//Count the level editing time.
statsMgr.startLevelEdit();
//Create the command manager with a maximum of 100 commands.
commandManager = new CommandManager(100);
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string MoveGameObjectCommand::describe() {
if (objects.size() == 1) {
const bool isResize = oldPosition[0].w != newPosition[0].w || oldPosition[0].h != newPosition[0].h;
Scenery *scenery = dynamic_cast<Scenery*>(objects[0]);
return tfm::format(isResize ? _("Resize %s") : _("Move %s"), scenery ?
/// TRANSLATORS: Context: Resize/Move ...
(scenery->sceneryName_.empty() ? _("Custom scenery block")
: describeSceneryName(scenery->sceneryName_).c_str())
: _(blockNames[objects[0]->type]));
} else {
const size_t number_of_objects = objects.size();
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(ngettext("Move %d object", "Move %d objects", number_of_objects).c_str(), number_of_objects);
}
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string AddRemoveGameObjectCommand::describe() {
if (objects.size() == 1) {
Scenery *scenery = dynamic_cast<Scenery*>(objects[0]);
return tfm::format(isAdd ? _("Add %s") : _("Remove %s"), scenery ? (scenery->sceneryName_.empty() ?
/// TRANSLATORS: Context: Add/Remove ...
_("Custom scenery block")
: describeSceneryName(scenery->sceneryName_).c_str())
: _(blockNames[objects[0]->type]));
} else {
const size_t number_of_objects = objects.size();
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(isAdd ? ngettext("Add %d object", "Add %d objects", number_of_objects).c_str() :
/// TRANSLATORS: Context: Undo/Redo ...
ngettext("Remove %d object", "Remove %d objects", number_of_objects).c_str(), number_of_objects);
}
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string AddRemovePathCommand::describe() {
return tfm::format(isAdd ?
/// TRANSLATORS: Context: Undo/Redo ...
_("Add path to %s") :
/// TRANSLATORS: Context: Undo/Redo ...
_("Remove a path point from %s"), _(blockNames[target->type]));
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string RemovePathCommand::describe() {
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(_("Remove all paths from %s"), _(blockNames[target->type]));
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string AddLinkCommand::describe() {
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(_("Add link from %s to %s"), _(blockNames[target->type]), _(blockNames[clickedObj->type]));
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string RemoveLinkCommand::describe() {
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(_("Remove all links from %s"), _(blockNames[target->type]));
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string SetEditorPropertyCommand::describe() {
Scenery *scenery = dynamic_cast<Scenery*>(target);
/// TRANSLATORS: Context: Undo/Redo ...
std::string s = _("Modify the %2 property of %1");
size_t lp = s.find("%1");
if (lp != string::npos) {
std::string s1 = scenery ?
(scenery->sceneryName_.empty() ?
/// TRANSLATORS: Context: Undo/Redo ...
_("Custom scenery block")
: describeSceneryName(scenery->sceneryName_).c_str())
: _(blockNames[target->type]);
s = s.substr(0, lp) + s1 + s.substr(lp + 2);
}
lp = s.find("%2");
if (lp != string::npos) {
s = s.substr(0, lp) + desc + s.substr(lp + 2);
}
return s;
}
// FIXME: I have to write this function here since we need to access the static variable levelTime and levelRecordings
SetLevelPropertyCommand::SetLevelPropertyCommand(LevelEditor* levelEditor, const LevelProperty& levelProperty)
: editor(levelEditor), newProperty(levelProperty)
{
oldProperty.levelName = editor->levelName;
oldProperty.levelTheme = editor->levelTheme;
oldProperty.levelMusic = editor->levelMusic;
oldProperty.levelTime = levelTime;
oldProperty.levelRecordings = levelRecordings;
}
// FIXME: I have to write this function here since we need to access the static variable levelTime and levelRecordings
void SetLevelPropertyCommand::setLevelProperty(const LevelProperty& levelProperty) {
bool musicChanged = editor->levelMusic != levelProperty.levelMusic;
editor->levelName = levelProperty.levelName;
editor->levelTheme = levelProperty.levelTheme;
editor->levelMusic = levelProperty.levelMusic;
levelTime = levelProperty.levelTime;
levelRecordings = levelProperty.levelRecordings;
if (musicChanged) {
#ifdef _DEBUG
printf("DEBUG: Level music is changed dynamically in level editor\n");
#endif
editor->editorData["music"] = editor->levelMusic;
editor->reloadMusic();
}
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string SetScriptCommand::describe() {
if (target) {
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(_("Edit the script of %s"), _(blockNames[target->type]));
} else {
/// TRANSLATORS: Context: Undo/Redo ...
return _("Edit the script of level");
}
}
LevelEditor::~LevelEditor(){
//Delete the command manager.
delete commandManager;
// NOTE: We don't need to delete levelObjects, etc. since they are deleted in Game::~Game().
// Clear selection
selection.clear();
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Delete the popup
if(selectionPopup){
delete selectionPopup;
selectionPopup=NULL;
}
//Delete the popup
if(actionsPopup){
delete actionsPopup;
actionsPopup=NULL;
}
//Reset the camera.
camera.x=0;
camera.y=0;
//Count the level editing time.
statsMgr.endLevelEdit();
}
TexturePtr& LevelEditor::getCachedTextTexture(SDL_Renderer& renderer, const std::string& text) {
auto it = cachedTextTextures.find(text);
if (it != cachedTextTextures.end()) return it->second;
return (cachedTextTextures[text] = textureFromText(renderer,
*fontText,
text.c_str(),
objThemes.getTextColor(true)));
}
void LevelEditor::reset(){
//Set some default values.
playMode=false;
tool=ADD;
currentType=0;
toolboxVisible=false;
toolboxRect.x=-1;
toolboxRect.y=-1;
toolboxRect.w=0;
toolboxRect.h=0;
toolboxIndex=0;
pressedShift=false;
pressedLeftMouse=false;
dragging=false;
selectionDrag=-1;
dragCenter=NULL;
cameraXvel=0;
cameraYvel=0;
linking=false;
linkingTrigger=NULL;
currentId=0;
movingBlock=NULL;
moving=false;
movingSpeed=10;
pauseTime = 0;
tooltip=-1;
//Set the player and shadow to their starting position.
player.setLocation(player.fx,player.fy);
shadow.setLocation(shadow.fx,shadow.fy);
selection.clear();
clipboard.clear();
triggers.clear();
movingBlocks.clear();
//Delete any gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Clear the GUIWindow object map.
objectWindows.clear();
}
void LevelEditor::loadLevelFromNode(ImageManager& imageManager, SDL_Renderer& renderer,TreeStorageNode* obj, const std::string& fileName){
//call the method of base class.
Game::loadLevelFromNode(imageManager,renderer,obj,fileName);
//now do our own stuff.
string s=editorData["time"];
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelTime=-1;
}else{
levelTime=atoi(s.c_str());
}
s=editorData["recordings"];
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelRecordings=-1;
}else{
levelRecordings=atoi(s.c_str());
}
levelTheme = editorData["theme"];
levelMusic = editorData["music"];
//NOTE: We set the camera here since we know the dimensions of the level.
if(LEVEL_WIDTH<SCREEN_WIDTH)
camera.x=-(SCREEN_WIDTH-LEVEL_WIDTH)/2;
else
camera.x=0;
if(LEVEL_HEIGHT<SCREEN_HEIGHT)
camera.y=-(SCREEN_HEIGHT-LEVEL_HEIGHT)/2;
else
camera.y=0;
//The level is loaded, so call postLoad.
postLoad();
}
void LevelEditor::saveLevel(string fileName){
//Create the output stream and check if it starts.
std::ofstream save(fileName.c_str());
if(!save) return;
- //The dimensions of the level.
- int maxX=0;
- int maxY=0;
+ //The current level.
+ LevelPack::Level *currentLevel = levels->getLevel(), *currentLevel2 = NULL;
+
+ //Check if the current level is individual level,
+ //in this case the level are both in "Levels" and "Custom Levels" level packs.
+ if (levels->type == COLLECTION) {
+ assert(levels->levelpackPath == CUSTOM_LEVELS_PATH);
+ if (auto levels2 = getLevelPackManager()->getLevelPack(LEVELS_PATH)) {
+ for (int i = 0, m = levels2->getLevelCount(); i < m; i++) {
+ if (levels2->getLevel(i)->file == currentLevel->file) {
+ currentLevel2 = levels2->getLevel(i);
+ break;
+ }
+ }
+ if (currentLevel2 == NULL) {
+ fprintf(stderr, "BUG: The custom level '%s' is not conatined in 'Levels' pack!\n", currentLevel->file.c_str());
+ }
+ }
+ }
//The storageNode to put the level data in before writing it away.
TreeStorageNode node;
char s[64];
//The name of the level.
if(!levelName.empty()){
node.attributes["name"].push_back(levelName);
//Update the level name in the levelpack.
- levels->getLevel()->name=levelName;
+ currentLevel->name = levelName;
+ if (currentLevel2) currentLevel2->name = levelName;
}
//The level theme.
if(!levelTheme.empty())
node.attributes["theme"].push_back(levelTheme);
//The level music.
if (!levelMusic.empty())
node.attributes["music"].push_back(levelMusic);
//target time and recordings.
{
char c[32];
- if(levelTime>=0){
- sprintf(c,"%d",levelTime);
- node.attributes["time"].push_back(c);
- //Update the target time the levelpack.
- levels->getLevel()->targetTime=levelTime;
- }
- if(levelRecordings>=0){
- sprintf(c,"%d",levelRecordings);
- node.attributes["recordings"].push_back(c);
+ sprintf(c, "%d", std::max(levelTime, -1));
+ node.attributes["time"].push_back(c);
- //Update the target recordings the levelpack.
- levels->getLevel()->targetRecordings=levelRecordings;
- }
+ //Update the target time the levelpack.
+ currentLevel->targetTime = std::max(levelTime, -1);
+ if (currentLevel2) currentLevel2->targetTime = std::max(levelTime, -1);
+
+ sprintf(c, "%d", std::max(levelRecordings, -1));
+ node.attributes["recordings"].push_back(c);
+
+ //Update the target recordings the levelpack.
+ currentLevel->targetRecordings = std::max(levelRecordings, -1);
+ if (currentLevel2) currentLevel2->targetRecordings = std::max(levelRecordings, -1);
}
//The width of the level.
- maxX=LEVEL_WIDTH;
- sprintf(s,"%d",maxX);
+ sprintf(s, "%d", LEVEL_WIDTH);
node.attributes["size"].push_back(s);
//The height of the level.
- maxY=LEVEL_HEIGHT;
- sprintf(s,"%d",maxY);
+ sprintf(s, "%d", LEVEL_HEIGHT);
node.attributes["size"].push_back(s);
//Loop through the gameObjects and save them.
for(int o=0;o<(signed)levelObjects.size();o++){
int objectType=levelObjects[o]->type;
//Check if it's a legal gameObject type.
if(objectType>=0 && objectType<TYPE_MAX){
TreeStorageNode* obj1=new TreeStorageNode;
node.subNodes.push_back(obj1);
//It's a tile so name the node tile.
obj1->name="tile";
//Write away the type of the gameObject.
obj1->value.push_back(blockName[objectType]);
//Get the box for the location of the gameObject.
SDL_Rect box=levelObjects[o]->getBox(BoxType_Base);
//Put the location and size in the storageNode.
sprintf(s,"%d",box.x);
obj1->value.push_back(s);
sprintf(s,"%d",box.y);
obj1->value.push_back(s);
sprintf(s,"%d",box.w);
obj1->value.push_back(s);
sprintf(s,"%d",box.h);
obj1->value.push_back(s);
//Loop through the editor data and save it also.
vector<pair<string,string> > obj;
levelObjects[o]->getEditorData(obj);
for (const auto& o : obj) {
//Skip the data whose key or value is empty.
if (o.first.empty()) continue;
if (o.second.empty()) continue;
//Skip SOME data whose value is the default value. Currently only some boolean values are skipped.
//WARNING: When the default values are changed, these codes MUST be modified accordingly!!!
//NOTE: Currently we skip the "visible" property since it is used for every block and usually it's the default value.
if (o.first == "visible" && o.second == "1") continue;
#if 0
//NOTE: The following codes are more aggressive!!!
//if (o.first == "activated" && o.second == "1") continue; //moving blocks and conveyor belt // Don't use this because there was a "disabled" property
if (o.first == "loop" && o.second == "1") continue; //moving blocks and conveyor belt
if (o.first == "automatic" && o.second == "0") continue; //portal
#endif
//Save the data.
obj1->attributes[o.first].push_back(o.second);
}
//Loop through the scripts and add them to the storage node of the game object.
map<int,string>::iterator it;
Block* object=(dynamic_cast<Block*>(levelObjects[o]));
for(it=object->scripts.begin();it!=object->scripts.end();++it){
//Make sure the script isn't an empty string.
if(it->second.empty())
continue;
TreeStorageNode* script=new TreeStorageNode;
obj1->subNodes.push_back(script);
script->name="script";
script->value.push_back(gameObjectEventTypeMap[it->first]);
script->attributes["script"].push_back(it->second);
}
}
}
//Loop through the level scripts and save them.
for(auto it=scripts.begin();it!=scripts.end();++it){
//Make sure the script isn't an empty string.
if(it->second.empty())
continue;
TreeStorageNode* script=new TreeStorageNode;
node.subNodes.push_back(script);
script->name="script";
script->value.push_back(levelEventTypeMap[it->first]);
script->attributes["script"].push_back(it->second);
}
//Loop through the scenery layers and save them.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
TreeStorageNode* layer = new TreeStorageNode;
node.subNodes.push_back(layer);
layer->name = "scenerylayer";
layer->value.push_back(it->first);
it->second->saveToNode(layer);
}
//Create a POASerializer and write away the level node.
POASerializer objSerializer;
objSerializer.writeNode(&node,save,true,true);
}
void LevelEditor::deselectAll() {
selection.clear();
dragCenter = NULL;
selectionDrag = -1;
selectionDirty();
}
///////////////EVENT///////////////////
void LevelEditor::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//Check if we need to quit, if so we enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//If playing/testing we should the game handle the events.
if(playMode){
Game::handleEvents(imageManager,renderer);
//Also check if we should exit the playMode.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
//Reset the game and disable playMode, disable script.
Game::reset(true, true);
playMode=false;
GUIObjectRoot->visible=true;
camera.x=cameraSave.x;
camera.y=cameraSave.y;
//NOTE: To prevent the mouse to still "be pressed" we set it to false.
pressedLeftMouse=false;
}
}else{
//Also check if we should exit the editor.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
std::string s;
//Check if the file is changed
if (commandManager->isChanged()) {
s = _("The level has unsaved changes.");
s.push_back('\n');
}
//Before we quit ask a make sure question.
if(msgBox(imageManager,renderer,s+_("Are you sure you want to quit?"),MsgBoxYesNo,_("Quit prompt"))==MsgBoxYes){
//We exit the level editor.
setNextState(STATE_LEVEL_EDIT_SELECT);
//Play the menu music again.
getMusicManager()->playMusic("menu");
//No need for handling other events, so return.
return;
}
}
//Check if we should redirect the event to the actions popup
if(actionsPopup!=NULL){
actionsPopup->handleEvents(renderer);
return;
}
//Check if we should redirect the event to selection popup
if(selectionPopup!=NULL){
if(event.type==SDL_MOUSEBUTTONDOWN
|| event.type==SDL_MOUSEBUTTONUP
|| event.type==SDL_MOUSEMOTION)
{
selectionPopup->handleEvents(imageManager,renderer);
return;
}
}
//TODO: Don't handle any Events when GUIWindows process them.
{
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x,y,0,0};
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
SDL_Rect box={0,0,0,0};
box.x=GUIObjectRoot->childControls[i]->left;
box.y=GUIObjectRoot->childControls[i]->top;
box.w=GUIObjectRoot->childControls[i]->width;
box.h=GUIObjectRoot->childControls[i]->height;
if(pointOnRect(mouse,box))
return;
}
}
//Check if toolbar is clicked.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT && tooltip>=0){
int t=tooltip;
if(t<NUMBER_TOOLS){
+ //Show/hide toolbox if the current mode is ADD and the user clicked ADD again.
+ if (tool == ADD && t == ADD) {
+ toolboxVisible = !toolboxVisible;
+ }
+
tool=(Tools)t;
//Stop linking or moving if the mode is not SELECT.
if (tool != SELECT) {
if (linking) {
linking = false;
linkingTrigger = NULL;
}
if (moving) {
moving = false;
movingBlock = NULL;
}
}
}else{
//The selected button isn't a tool.
//Now check which button it is.
if (t == (int)ToolTips::Play){
enterPlayMode();
}
if (t == (int)ToolTips::LevelSettings){
//Open up level settings dialog
levelSettings(imageManager,renderer);
}
if (t == (int)ToolTips::BackToMenu){
//If the file is changed we show a confirmation dialog
if (commandManager->isChanged()) {
std::string s = _("The level has unsaved changes.");
s.push_back('\n');
if (msgBox(imageManager, renderer, s + _("Are you sure you want to quit?"), MsgBoxYesNo, _("Quit prompt")) != MsgBoxYes) return;
}
//Go back to the level selection screen of Level Editor
setNextState(STATE_LEVEL_EDIT_SELECT);
//Change the music back to menu music.
getMusicManager()->playMusic("menu");
}
if (t == (int)ToolTips::SaveLevel){
//Save current level
saveCurrentLevel(imageManager, renderer);
}
if (t == (int)ToolTips::UndoNoTooltip) {
commandManager->undo();
}
if (t == (int)ToolTips::RedoNoTooltip) {
commandManager->redo();
}
}
return;
}
//Check if tool box is clicked.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT && toolboxRect.w>0){
if(toolboxVisible){
if(event.button.y<64){
//Check if we need to hide it
if(event.button.x>=SCREEN_WIDTH-24 && event.button.x<SCREEN_WIDTH && event.button.y<20){
toolboxVisible=false;
return;
}
const int m = (SCREEN_WIDTH - 48) / 64;
//Check if a block is clicked.
if(event.button.x>=24 && event.button.x<SCREEN_WIDTH-24){
int i=(event.button.x-24)/64;
if(i<m && i+toolboxIndex<getEditorOrderMax()){
currentType=i+toolboxIndex;
}
}
//Check if move left button is clicked
if (event.button.x >= 0 && event.button.x < 24 && event.button.y >= 20 && event.button.y < 44) {
toolboxIndex -= m;
if (toolboxIndex < 0) toolboxIndex = 0;
}
//Check if move right button is clicked
if (event.button.x >= SCREEN_WIDTH - 24 && event.button.x < SCREEN_WIDTH && event.button.y >= 20 && event.button.y < 44) {
toolboxIndex += m;
if (toolboxIndex > getEditorOrderMax() - m) toolboxIndex = getEditorOrderMax() - m;
if (toolboxIndex < 0) toolboxIndex = 0;
}
return;
}
}else if(event.button.x>=toolboxRect.x && event.button.x<toolboxRect.x+toolboxRect.w
&& event.button.y>=toolboxRect.y && event.button.y<toolboxRect.y+toolboxRect.h)
{
toolboxVisible=true;
return;
}
}
//Check if shift is pressed.
pressedShift=inputMgr.isKeyDown(INPUTMGR_SHIFT);
//Check if delete is pressed.
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_DELETE){
if (!selection.empty()){
AddRemoveGameObjectCommand *command = new AddRemoveGameObjectCommand(this, selection, false);
//clear the selection vector first.
deselectAll();
//perform the actual deletion.
commandManager->doCommand(command);
}
}
//Check for copy (Ctrl+c) or cut (Ctrl+x).
if(event.type==SDL_KEYDOWN && (event.key.keysym.sym==SDLK_c || event.key.keysym.sym==SDLK_x) && (event.key.keysym.mod & KMOD_CTRL)){
//Check if the selection isn't empty.
if(!selection.empty()){
//Clear the current clipboard.
clipboard.clear();
//Loop through the selection to find the left-top block.
int x=selection[0]->getBox().x;
int y=selection[0]->getBox().y;
for(unsigned int o=1; o<selection.size(); o++){
if(selection[o]->getBox().x<x || selection[o]->getBox().y<y){
x=selection[o]->getBox().x;
y=selection[o]->getBox().y;
}
}
//Loop through the selection for the actual copying.
for(unsigned int o=0; o<selection.size(); o++){
//Get the editor data of the object.
vector<pair<string,string> > obj;
selection[o]->getEditorData(obj);
//Loop through the editor data and convert it.
map<string,string> objMap;
for(unsigned int i=0;i<obj.size();i++){
objMap[obj[i].first]=obj[i].second;
}
//Add some entries to the map.
char s[64];
SDL_Rect r = selection[o]->getBox();
sprintf(s, "%d", r.x - x);
objMap["x"]=s;
sprintf(s, "%d", r.y - y);
objMap["y"]=s;
sprintf(s, "%d", selection[o]->getBox().w);
objMap["w"] = s;
sprintf(s, "%d", selection[o]->getBox().h);
objMap["h"] = s;
- Scenery *scenery = dynamic_cast<Scenery*>(selection[o]);
- if (scenery) {
+ if (Scenery *scenery = dynamic_cast<Scenery*>(selection[o])) {
objMap["sceneryName"] = scenery->sceneryName_;
objMap["customScenery"] = scenery->customScenery_;
} else {
sprintf(s, "%d", selection[o]->type);
objMap["type"] = s;
+
+ //Save scripts for block.
+ if (Block *block = dynamic_cast<Block*>(selection[o])) {
+ for (auto it = block->scripts.begin(); it != block->scripts.end(); ++it) {
+ sprintf(s, "_script.%d", it->first);
+ objMap[s] = it->second;
+ }
+ }
}
//Overwrite the id to prevent triggers, portals, buttons, movingblocks, etc. from malfunctioning.
//We give an empty string as id, which is invalid and thus suitable.
objMap["id"]="";
//Do the same for destination if the type is portal.
if(selection[o]->type==TYPE_PORTAL){
objMap["destination"]="";
}
//And add the map to the clipboard vector.
clipboard.push_back(objMap);
}
//Cutting means deleting the game object.
if (event.key.keysym.sym == SDLK_x && !selection.empty()){
AddRemoveGameObjectCommand *command = new AddRemoveGameObjectCommand(this, selection, false);
//clear the selection vector first.
deselectAll();
//perform the actual deletion.
commandManager->doCommand(command);
}
}
}
//Check for paste (Ctrl+v).
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_v && (event.key.keysym.mod & KMOD_CTRL)){
//First make sure that the clipboard isn't empty.
if(!clipboard.empty()){
//Clear the current selection.
deselectAll();
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
std::vector<GameObject*> newObjects;
//Loop through the clipboard.
for(unsigned int o=0;o<clipboard.size();o++){
GameObject *obj = NULL;
if (clipboard[o].find("sceneryName") == clipboard[o].end()) {
// a normal block
if (!selectedLayer.empty()) continue;
- obj = new Block(this, 0, 0, 50, 50, atoi(clipboard[o]["type"].c_str()));
+ Block *block = new Block(this, 0, 0, 50, 50, atoi(clipboard[o]["type"].c_str()));
+ obj = block;
+
+ // load the script for the block
+ for (auto it = clipboard[o].begin(); it != clipboard[o].end(); ++it) {
+ if (it->first.find("_script.") == 0) {
+ int eventType = atoi(it->first.c_str() + 8);
+ block->scripts[eventType] = it->second;
+ }
+ }
} else {
// a scenery block
if (selectedLayer.empty()) continue;
Scenery *scenery = new Scenery(this, 0, 0, 50, 50, clipboard[o]["sceneryName"]);
if (clipboard[o]["sceneryName"].empty()) {
scenery->customScenery_ = clipboard[o]["customScenery"];
scenery->updateCustomScenery(imageManager, renderer);
}
obj = scenery;
}
obj->setBaseLocation(atoi(clipboard[o]["x"].c_str())+x,atoi(clipboard[o]["y"].c_str())+y);
obj->setBaseSize(atoi(clipboard[o]["w"].c_str()), atoi(clipboard[o]["h"].c_str()));
obj->setEditorData(clipboard[o]);
//add the object.
newObjects.push_back(obj);
//Also add the block to the selection.
selection.push_back(obj);
}
// Do the actual object insertion
if (!newObjects.empty()) {
commandManager->doCommand(new AddRemoveGameObjectCommand(this, newObjects, true));
}
}
}
//Check for the arrow keys, used for moving the camera when playMode=false.
if (inputMgr.isKeyDown(INPUTMGR_RIGHT)) {
if (cameraXvel < 5) cameraXvel = 5;
} else if (inputMgr.isKeyDown(INPUTMGR_LEFT)) {
if (cameraXvel > -5) cameraXvel = -5;
} else {
cameraXvel = 0;
}
if(inputMgr.isKeyDown(INPUTMGR_DOWN)){
if (cameraYvel < 5) cameraYvel = 5;
} else if (inputMgr.isKeyDown(INPUTMGR_UP)){
if (cameraYvel > -5) cameraYvel = -5;
} else {
cameraYvel = 0;
}
//Check if the left mouse button is pressed/holded.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT){
pressedLeftMouse=true;
}
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
pressedLeftMouse=false;
//We also need to check if dragging is true.
if(dragging){
//Set dragging false and call the onDrop event.
dragging=false;
int x,y;
SDL_GetMouseState(&x,&y);
//We call the drop event.
onDrop(x+camera.x,y+camera.y);
}
}
//Check if the mouse is dragging.
if(pressedLeftMouse && event.type==SDL_MOUSEMOTION) {
//Check if this is the start of the dragging.
if(!dragging){
//The mouse is moved enough so let's set dragging true.
dragging=true;
// NOTE: We start drag from previous mouse position to prevent resize area hit test bug
onDragStart(event.motion.x - event.motion.xrel + camera.x, event.motion.y - event.motion.yrel + camera.y);
onDrag(event.motion.xrel, event.motion.yrel);
} else {
//Dragging was already true meaning we call onDrag() instead of onDragStart().
onDrag(event.motion.xrel,event.motion.yrel);
}
}
//Update cursor.
if(dragging){
if (tool == REMOVE) {
currentCursor = CURSOR_REMOVE;
} else {
switch (selectionDrag) {
case 0: case 8:
currentCursor = CURSOR_SIZE_FDIAG;
break;
case 1: case 7:
currentCursor = CURSOR_SIZE_VER;
break;
case 2: case 6:
currentCursor = CURSOR_SIZE_BDIAG;
break;
case 3: case 5:
currentCursor = CURSOR_SIZE_HOR;
break;
case 4:
currentCursor = CURSOR_DRAG;
break;
default:
currentCursor = CURSOR_POINTER;
break;
}
}
}
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x,y,0,0};
//Check if we scroll up, meaning the currentType++;
if((event.type==SDL_MOUSEWHEEL && event.wheel.y > 0) || inputMgr.isKeyDownEvent(INPUTMGR_NEXT)){
switch(tool){
case ADD:
//Check if mouse is in tool box.
if(toolboxVisible && toolboxRect.w>0){
int x,y;
SDL_GetMouseState(&x,&y);
if(y<64){
const int m = getEditorOrderMax() - (SCREEN_WIDTH - 48) / 64;
toolboxIndex -= 2;
if (toolboxIndex>m) toolboxIndex = m;
if (toolboxIndex<0) toolboxIndex = 0;
break;
}
}
//Only change the current type when using the add tool.
currentType++;
if (currentType >= getEditorOrderMax()){
currentType=0;
}
break;
case SELECT:
//When configuring moving blocks.
if(moving){
if (pauseMode) {
//Here we have to use Ctrl because Shift means snap
pauseTime += (SDL_GetModState() & KMOD_CTRL) ? 10 : 1;
} else {
//Here we have to use Ctrl because Shift means snap
movingSpeed += (SDL_GetModState() & KMOD_CTRL) ? 10 : 1;
//The movingspeed is capped at 125 (10 block/s).
if (movingSpeed > 125){
movingSpeed = 125;
}
}
break;
}
//Fall through.
default:
//When in other mode, just scrolling the map
if (pressedShift) camera.x = clamp(camera.x - 200, -1000 - SCREEN_WIDTH, LEVEL_WIDTH + 1000);
else camera.y = clamp(camera.y - 200, -1000 - SCREEN_HEIGHT, LEVEL_HEIGHT + 1000);
break;
}
}
//Check if we scroll down, meaning the currentType--;
if((event.type==SDL_MOUSEWHEEL && event.wheel.y < 0) || inputMgr.isKeyDownEvent(INPUTMGR_PREVIOUS)){
switch(tool){
case ADD:
//Check if mouse is in tool box.
if(toolboxVisible && toolboxRect.w>0){
int x,y;
SDL_GetMouseState(&x,&y);
if(y<64){
const int m = getEditorOrderMax() - (SCREEN_WIDTH - 48) / 64;
toolboxIndex+=2;
if(toolboxIndex>m) toolboxIndex=m;
if(toolboxIndex<0) toolboxIndex=0;
break;
}
}
//Only change the current type when using the add tool.
currentType--;
if(currentType<0){
currentType = getEditorOrderMax() - 1;
}
break;
case SELECT:
//When configuring moving blocks.
if(moving){
if (pauseMode) {
//Here we have to use Ctrl because Shift means snap
pauseTime -= (SDL_GetModState() & KMOD_CTRL) ? 10 : 1;
if (pauseTime < -1){
pauseTime = -1;
}
} else {
//Here we have to use Ctrl because Shift means snap
movingSpeed -= (SDL_GetModState() & KMOD_CTRL) ? 10 : 1;
if (movingSpeed <= 0){
movingSpeed = 1;
}
}
break;
}
//Fall through.
default:
//When in other mode, just scrolling the map
if (pressedShift) camera.x = clamp(camera.x + 200, -1000 - SCREEN_WIDTH, LEVEL_WIDTH + 1000);
else camera.y = clamp(camera.y + 200, -1000 - SCREEN_HEIGHT, LEVEL_HEIGHT + 1000);
break;
}
}
if (event.type == SDL_KEYDOWN) {
bool unlink = false;
//Check if we should enter playMode.
if (event.key.keysym.sym == SDLK_F5){
enterPlayMode();
}
//Check for tool shortcuts.
if (event.key.keysym.sym == SDLK_F2){
tool = SELECT;
}
if (event.key.keysym.sym == SDLK_F3){
+ //Show/hide toolbox if the current mode is ADD and the user clicked ADD again.
+ if (tool == ADD) {
+ toolboxVisible = !toolboxVisible;
+ }
tool = ADD;
unlink = true;
}
if (event.key.keysym.sym == SDLK_F4){
tool = REMOVE;
unlink = true;
}
//Stop linking or moving if the mode is not SELECT.
if (unlink) {
if (linking) {
linking = false;
linkingTrigger = NULL;
}
if (moving) {
moving = false;
movingBlock = NULL;
}
}
}
//Check for certain events.
//First make sure the mouse isn't above the toolbar.
if(!pointOnRect(mouse,toolbarRect) && !pointOnRect(mouse,toolboxRect)){
mouse.x+=camera.x;
mouse.y+=camera.y;
//Boolean if there's a click event fired.
bool clickEvent=false;
//Check if a mouse button is pressed.
if(event.type==SDL_MOUSEBUTTONDOWN){
//Right click in path or link mode means return to normal mode.
if (event.button.button == SDL_BUTTON_RIGHT && (linking || moving)) {
//Stop linking.
linking = false;
linkingTrigger = NULL;
//Stop moving.
moving = false;
movingBlock = NULL;
//Stop processing further.
return;
}
std::vector<GameObject*> clickObjects;
//Loop through the objects to check collision.
if (selectedLayer.empty()) {
if (layerVisibility[selectedLayer]) {
for (unsigned int o = 0; o<levelObjects.size(); o++){
if (pointOnRect(mouse, levelObjects[o]->getBox()) == true){
clickObjects.push_back(levelObjects[o]);
}
}
}
} else {
auto it = sceneryLayers.find(selectedLayer);
if (it != sceneryLayers.end() && layerVisibility[selectedLayer]) {
for (auto o : it->second->objects){
if (pointOnRect(mouse, o->getBox()) == true){
clickObjects.push_back(o);
}
}
}
}
//Check if there are multiple objects above eachother or just one.
if(clickObjects.size()==1){
//We have collision meaning that the mouse is above an object.
std::vector<GameObject*>::iterator it;
it=find(selection.begin(),selection.end(),clickObjects[0]);
//Set event true since there's a click event.
clickEvent=true;
//Check if the clicked object is in the selection or not.
bool isSelected=(it!=selection.end());
if(event.button.button==SDL_BUTTON_LEFT){
onClickObject(clickObjects[0],isSelected);
}else if(event.button.button==SDL_BUTTON_RIGHT){
onRightClickObject(imageManager,renderer,clickObjects[0],isSelected);
}
}else if(clickObjects.size()>=1){
//There are more than one object under the mouse
//SDL2 port (never managed to trigger this without changing the parameters.
std::vector<GameObject*>::iterator it;
it=find(selection.begin(),selection.end(),clickObjects[0]);
//Set event true since there's a click event.
clickEvent=true;
//Check if the clicked object is in the selection or not.
bool isSelected=(it!=selection.end());
//Only show the selection popup when right clicking.
if(event.button.button==SDL_BUTTON_LEFT){
onClickObject(clickObjects[0],isSelected);
}else if(event.button.button==SDL_BUTTON_RIGHT){
//Remove the selection popup if there's one.
if(selectionPopup!=NULL)
delete selectionPopup;
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
selectionPopup=new LevelEditorSelectionPopup(this,imageManager,renderer,clickObjects,x,y);
}
}
}
//If event is false then we clicked on void.
if(!clickEvent){
if(event.type==SDL_MOUSEBUTTONDOWN){
if(event.button.button==SDL_BUTTON_LEFT){
//Left mouse button on void.
onClickVoid(mouse.x,mouse.y);
}else if(event.button.button==SDL_BUTTON_RIGHT){
onRightClickVoid(imageManager,renderer,mouse.x,mouse.y);
}
}
}
}
//Check for backspace when moving to remove a movingposition.
if(moving && event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_BACKSPACE){
if(movingBlocks[movingBlock].size()>0){
commandManager->doCommand(new AddRemovePathCommand(this, movingBlock, MovingPosition(0, 0, 0), false));
}
}
//Check for the tab key, level settings.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
//Show the levelSettings.
levelSettings(imageManager,renderer);
}
//Check if we should save the level (Ctrl+s).
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_s && (event.key.keysym.mod & KMOD_CTRL)){
saveCurrentLevel(imageManager, renderer);
}
//Undo ctrl+z
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_z && (event.key.keysym.mod & KMOD_CTRL)){
undo();
}
//Redo ctrl+y
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_y && (event.key.keysym.mod & KMOD_CTRL)){
redo();
}
}
}
void LevelEditor::saveCurrentLevel(ImageManager& imageManager, SDL_Renderer& renderer) {
saveLevel(levelFile);
//Clear the dirty flag
commandManager->resetChange();
//And give feedback to the user.
if (levelName.empty())
msgBox(imageManager, renderer, tfm::format(_("Level \"%s\" saved"), fileNameFromPath(levelFile)), MsgBoxOKOnly, _("Saved"));
else
msgBox(imageManager, renderer, tfm::format(_("Level \"%s\" saved"), levelName), MsgBoxOKOnly, _("Saved"));
}
void LevelEditor::enterPlayMode(){
//Check if we are already in play mode.
if(playMode) return;
//Stop linking or moving.
if(linking){
linking=false;
linkingTrigger=NULL;
}
if(moving){
moving=false;
movingBlock=NULL;
}
//We need to reposition player and shadow here, since the related code is removed from object placement.
for (auto obj : levelObjects) {
//If the object is a player or shadow start then change the start position of the player or shadow.
if (obj->type == TYPE_START_PLAYER){
//Center the player horizontally.
player.fx = obj->getBox().x + (obj->getBox().w - player.getBox().w) / 2;
player.fy = obj->getBox().y;
}
if (obj->type == TYPE_START_SHADOW){
//Center the shadow horizontally.
shadow.fx = obj->getBox().x + (obj->getBox().w - shadow.getBox().w) / 2;
shadow.fy = obj->getBox().y;
}
}
//Change mode.
playMode=true;
GUIObjectRoot->visible=false;
cameraSave.x=camera.x;
cameraSave.y=camera.y;
//Restart the game with scripting enabled.
Game::reset(true, false);
}
void LevelEditor::undo(){
commandManager->undo();
}
void LevelEditor::redo(){
commandManager->redo();
}
void LevelEditor::levelSettings(ImageManager& imageManager,SDL_Renderer& renderer){
//It isn't so open a popup asking for a name.
GUIWindow* root=new GUIWindow(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-450)/2,600,450,true,true,_("Level settings"));
root->minWidth = root->width; root->minHeight = root->height;
root->name="lvlSettingsWindow";
root->eventCallback=this;
GUIObject* obj;
//Create the two textboxes with a label.
obj=new GUILabel(imageManager,renderer,40,60,240,36,_("Name:"));
root->addChild(obj);
obj=new GUITextBox(imageManager,renderer,140,60,410,36,levelName.c_str());
obj->gravityRight = GUIGravityRight;
obj->name="name";
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 40, 110, 240, 36, (std::string("* ") + _("Theme:")).c_str());
root->addChild(obj);
obj=new GUITextBox(imageManager,renderer,140,110,410,36,levelTheme.c_str());
obj->gravityRight = GUIGravityRight;
obj->name="theme";
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 40, 150, 510, 36, _("Examples: %DATA%/themes/classic"));
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 40, 174, 510, 36, _("or %USER%/themes/Orange"));
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 40, 210, 240, 36, _("Music:"));
root->addChild(obj);
obj = new GUITextBox(imageManager, renderer, 140, 210, 410, 36, levelMusic.c_str());
obj->gravityRight = GUIGravityRight;
obj->name = "music";
root->addChild(obj);
//target time and recordings.
{
- obj=new GUILabel(imageManager,renderer,40,260,240,36,_("Target time (s):"));
+ obj=new GUICheckBox(imageManager,renderer,40,260,240,36,_("Target time (s):"));
+ obj->name = "chkTime";
+ obj->value = levelTime >= 0 ? 1 : 0;
+ obj->eventCallback = root;
root->addChild(obj);
GUISpinBox* obj2=new GUISpinBox(imageManager,renderer,290,260,260,36);
obj2->gravityRight = GUIGravityRight;
obj2->name="time";
- ostringstream ss;
- ss << levelTime/40.0f;
- obj2->caption=ss.str();
+ char ss[128];
+ sprintf(ss, "%0.2f", double(levelTime >= 0 ? levelTime : ~levelTime) / 40.0);
+ obj2->caption=ss;
+ obj2->visible = levelTime >= 0;
obj2->limitMin=0.0f;
- obj2->format = "%g";
+ obj2->limitMax = 1E+6f;
+ obj2->format = "%0.2f";
obj2->change=0.1f;
obj2->update();
root->addChild(obj2);
- obj=new GUILabel(imageManager,renderer,40,310,240,36,_("Target recordings:"));
+ obj=new GUICheckBox(imageManager,renderer,40,310,240,36,_("Target recordings:"));
+ obj->name = "chkRecordings";
+ obj->value = levelRecordings >= 0 ? 1 : 0;
+ obj->eventCallback = root;
root->addChild(obj);
obj2=new GUISpinBox(imageManager,renderer,290,310,260,36);
obj2->gravityRight = GUIGravityRight;
- ostringstream ss2;
- ss2 << levelRecordings;
- obj2->caption=ss2.str();
+ sprintf(ss, "%d", levelRecordings >= 0 ? levelRecordings : ~levelRecordings);
+ obj2->caption=ss;
+ obj2->visible = levelRecordings >= 0;
obj2->limitMin=0.0f;
+ obj2->limitMax = 1E+6f;
obj2->format="%1.0f";
obj2->name="recordings";
obj2->update();
root->addChild(obj2);
}
obj = new GUILabel(imageManager, renderer, 40, 350, 510, 36, (std::string("* ") + _("Restart level editor is required")).c_str());
root->addChild(obj);
//Ok and cancel buttons.
obj=new GUIButton(imageManager,renderer,root->width*0.3,450-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="lvlSettingsOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.7,450-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="lvlSettingsCancel";
obj->eventCallback=root;
root->addChild(obj);
GUIObjectRoot->addChild(root);
}
void LevelEditor::postLoad(){
//We need to find the triggers.
for(unsigned int o=0;o<levelObjects.size();o++){
//Check for the highest id.
unsigned int id=atoi(levelObjects[o]->getEditorProperty("id").c_str());
if(id>=currentId)
currentId=id+1;
switch(levelObjects[o]->type){
case TYPE_BUTTON:
case TYPE_SWITCH:
{
//Add the object to the triggers vector.
vector<GameObject*> linked;
triggers[levelObjects[o]]=linked;
//Now loop through the levelObjects in search for objects with the same id.
for(unsigned int oo=0;oo<levelObjects.size();oo++){
//Check if it isn't the same object but has the same id.
if(o!=oo && (dynamic_cast<Block*>(levelObjects[o]))->id==(dynamic_cast<Block*>(levelObjects[oo]))->id){
//Add the object to the link vector of the trigger.
triggers[levelObjects[o]].push_back(levelObjects[oo]);
}
}
break;
}
case TYPE_PORTAL:
{
//Add the object to the triggers vector.
vector<GameObject*> linked;
triggers[levelObjects[o]]=linked;
//If the destination is empty we return.
if((dynamic_cast<Block*>(levelObjects[o]))->destination.empty()){
break;
}
//Now loop through the levelObjects in search for objects with the same id as destination.
for(unsigned int oo=0;oo<levelObjects.size();oo++){
//Check if it isn't the same object but has the same id.
if(o!=oo && (dynamic_cast<Block*>(levelObjects[o]))->destination==(dynamic_cast<Block*>(levelObjects[oo]))->id){
//Add the object to the link vector of the trigger.
triggers[levelObjects[o]].push_back(levelObjects[oo]);
}
}
break;
}
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Get the moving position.
const vector<SDL_Rect> &movingPos = levelObjects[o]->movingPos;
//Add the object to the movingBlocks vector.
movingBlocks[levelObjects[o]].clear();
for (int i = 0, m = movingPos.size(); i < m; i++) {
MovingPosition position(movingPos[i].x, movingPos[i].y, movingPos[i].w);
movingBlocks[levelObjects[o]].push_back(position);
}
break;
}
default:
break;
}
}
// Set the visibility of all layers to true
layerVisibility.clear();
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
layerVisibility[it->first] = true;
}
layerVisibility[std::string()] = true;
// Also set the current layer to the Block layer
selectedLayer.clear();
// Get all available scenery blocks
std::set<std::string> tmpset;
objThemes.getSceneryBlockNames(tmpset);
sceneryBlockNames.clear();
sceneryBlockNames.insert(sceneryBlockNames.end(), tmpset.begin(), tmpset.end());
}
void LevelEditor::snapToGrid(int* x,int* y){
//Check if the x location is negative.
if(*x<0){
*x=-((abs(*x-50)/50)*50);
}else{
*x=(*x/50)*50;
}
//Now the y location.
if(*y<0){
*y=-((abs(*y-50)/50)*50);
}else{
*y=(*y/50)*50;
}
}
void LevelEditor::setCamera(const SDL_Rect* r,int count){
//SetCamera only works in the Level editor and when mouse is inside window.
if(stateID==STATE_LEVEL_EDITOR&&(SDL_GetMouseFocus() == sdlWindow)){
//Get the mouse coordinates.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
//Don't continue here if mouse is inside one of the boxes given as parameter.
for(int i=0;i<count;i++){
if(pointOnRect(mouse,r[i]))
return;
}
//FIXME: two ad-hoc camera speed variables similar to cameraXvel and cameraYvel
static int cameraXvelB = 0, cameraYvelB = 0;
//Check if the mouse is near the left edge of the screen.
//Else check if the mouse is near the right edge.
if (x < 50) {
//We're near the left edge so move the camera.
if (cameraXvelB > -5) cameraXvelB = -5;
if (pressedShift) cameraXvelB--;
} else if (x > SCREEN_WIDTH - 50) {
//We're near the right edge so move the camera.
if (cameraXvelB < 5) cameraXvelB = 5;
if (pressedShift) cameraXvelB++;
} else {
cameraXvelB = 0;
}
//Check if the tool box is visible and we need to calc screen size correctly.
int y0=50;
if (toolboxVisible && toolboxRect.w > 0) y0 += toolbarRect.h;
//Check if the mouse is near the top edge of the screen.
//Else check if the mouse is near the bottom edge.
if (y < y0) {
//We're near the top edge so move the camera.
if (cameraYvelB > -5) cameraYvelB = -5;
if (pressedShift) cameraYvelB--;
} else if (y > SCREEN_HEIGHT - 50) {
//We're near the bottom edge so move the camera.
if (cameraYvelB < 5) cameraYvelB = 5;
if (pressedShift) cameraYvelB++;
} else {
cameraYvelB = 0;
}
camera.x = clamp(camera.x + cameraXvelB, -1000 - SCREEN_WIDTH, LEVEL_WIDTH + 1000);
camera.y = clamp(camera.y + cameraYvelB, -1000 - SCREEN_HEIGHT, LEVEL_HEIGHT + 1000);
}
}
void LevelEditor::onClickObject(GameObject* obj,bool selected){
switch(tool){
case SELECT:
{
//Check if we are linking.
if(linking){
//Check if the obj is valid to link to.
switch(obj->type){
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//It's only valid when not linking a portal.
if(linkingTrigger->type==TYPE_PORTAL){
//You can't link a portal to moving blocks, etc.
//Stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
}
break;
}
case TYPE_PORTAL:
{
//Make sure that the linkingTrigger is also a portal.
if(linkingTrigger->type!=TYPE_PORTAL){
//The linkingTrigger isn't a portal so stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
}
break;
}
default:
//It isn't valid so stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
break;
}
AddLinkCommand* pCommand = new AddLinkCommand(this, linkingTrigger, obj);
commandManager->doCommand(pCommand);
//We return to prevent configuring stuff like conveyor belts, etc...
linking=false;
linkingTrigger=NULL;
return;
}
//If we're moving add a movingposition.
if(moving){
//Get the current mouse location.
int x, y;
SDL_GetMouseState(&x, &y);
x += camera.x;
y += camera.y;
addMovingPosition(x, y);
return;
}
}
case ADD:
{
//Check if object is already selected.
if(!selected){
//First check if shift is pressed or not.
if(!pressedShift){
//Clear the selection.
deselectAll();
}
//Add the object to the selection.
selection.push_back(obj);
}
break;
}
case REMOVE:
{
//Remove the object.
commandManager->doCommand(new AddRemoveGameObjectCommand(this, obj, false));
break;
}
default:
break;
}
}
void LevelEditor::addMovingPosition(int x,int y) {
//Apply snap to grid.
if (!pressedShift){
snapToGrid(&x, &y);
} else{
x -= 25;
y -= 25;
}
x -= movingBlock->getBox().x;
y -= movingBlock->getBox().y;
//Calculate the length.
//First get the delta x and y.
int dx, dy;
if (movingBlocks[movingBlock].empty()){
dx = x;
dy = y;
} else{
dx = x - movingBlocks[movingBlock].back().x;
dy = y - movingBlocks[movingBlock].back().y;
}
AddRemovePathCommand* pCommand = NULL;
if (dx == 0 && dy == 0) {
// pause mode
if (pauseTime != 0) pCommand = new AddRemovePathCommand(this, movingBlock, MovingPosition(x, y, std::max(pauseTime, 0)), true);
pauseTime = 0;
} else {
// add new point mode
const double length = sqrt(double(dx*dx + dy*dy));
pCommand = new AddRemovePathCommand(this, movingBlock, MovingPosition(x, y, (int)(length*(10 / (double)movingSpeed))), true);
}
if (pCommand) commandManager->doCommand(pCommand);
}
void LevelEditor::onRightClickObject(ImageManager& imageManager,SDL_Renderer& renderer,GameObject* obj,bool){
//Create an actions popup for the game object.
if(actionsPopup==NULL){
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
actionsPopup=new LevelEditorActionsPopup(imageManager,renderer,this,obj,x,y);
return;
}
}
void LevelEditor::onClickVoid(int x,int y){
switch(tool){
case ADD:
{
//We need to clear the selection.
deselectAll();
//Now place an object.
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
if (currentType >= 0 && currentType < getEditorOrderMax()) {
GameObject *obj;
if (selectedLayer.empty()) {
obj = new Block(this, x, y, 50, 50, editorTileOrder[currentType]);
} else {
obj = new Scenery(this, x, y, 50, 50,
currentType < (int)sceneryBlockNames.size() ? sceneryBlockNames[currentType] : std::string());
}
commandManager->doCommand(new AddRemoveGameObjectCommand(this, obj, true));
}
break;
}
case SELECT:
{
//We need to clear the selection.
deselectAll();
//If we're linking we should stop, user abort.
if(linking){
linking=false;
linkingTrigger=NULL;
//And return.
return;
}
//If we're moving we should add a point.
if(moving){
addMovingPosition(x, y);
//And return.
return;
}
break;
}
default:
break;
}
}
void LevelEditor::onRightClickVoid(ImageManager& imageManager,SDL_Renderer& renderer,int,int){
//Create an actions popup for the game object.
if(actionsPopup==NULL){
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
actionsPopup=new LevelEditorActionsPopup(imageManager,renderer,this,NULL,x,y);
return;
}
}
void LevelEditor::onDragStart(int x,int y){
switch(tool){
case SELECT:
case ADD:
{
//We can drag the selection so check if the selection isn't empty.
if(!selection.empty()){
//The selection isn't empty so search the dragCenter.
//Create a mouse rectangle.
SDL_Rect mouse={x,y,0,0};
// record the drag start position
dragSrartPosition.x = x;
dragSrartPosition.y = y;
//Loop through the objects to check collision.
for(unsigned int o=0; o<selection.size(); o++){
SDL_Rect r = selection[o]->getBox();
if(pointOnRect(mouse, r)){
//We have collision so set the dragCenter.
dragCenter=selection[o];
// determine which part is dragged
selectionDrag = 4;
int midx = r.x + r.w / 2 - 2;
int midy = r.y + r.h / 2 - 2;
if (mouse.x >= r.x && mouse.x < r.x + 5) {
if (mouse.y >= r.y && mouse.y < r.y + 5) {
selectionDrag = 0;
} else if (mouse.y >= midy && mouse.y < midy + 5) {
selectionDrag = 3;
} else if (mouse.y >= r.y + r.h - 5 && mouse.y < r.y + r.h) {
selectionDrag = 6;
}
} else if (mouse.x >= midx && mouse.x < midx + 5) {
if (mouse.y >= r.y && mouse.y < r.y + 5) {
selectionDrag = 1;
} else if (mouse.y >= r.y + r.h - 5 && mouse.y < r.y + r.h) {
selectionDrag = 7;
}
} else if (mouse.x >= r.x + r.w - 5 && mouse.x < r.x + r.w) {
if (mouse.y >= r.y && mouse.y < r.y + 5) {
selectionDrag = 2;
} else if (mouse.y >= midy && mouse.y < midy + 5) {
selectionDrag = 5;
} else if (mouse.y >= r.y + r.h - 5 && mouse.y < r.y + r.h) {
selectionDrag = 8;
}
}
break;
}
}
}
break;
}
default:
break;
}
}
void LevelEditor::onDrag(int dx,int dy){
switch(tool){
case REMOVE:
{
//No matter what we delete the item the mouse is above.
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
currentCursor=CURSOR_REMOVE;
std::vector<GameObject*> objects;
//Loop through the objects to check collision.
if (selectedLayer.empty()) {
if (layerVisibility[selectedLayer]) {
for (unsigned int o = 0; o<levelObjects.size(); o++){
if (pointOnRect(mouse, levelObjects[o]->getBox()) == true){
objects.push_back(levelObjects[o]);
}
}
}
} else {
auto it = sceneryLayers.find(selectedLayer);
if (it != sceneryLayers.end() && layerVisibility[selectedLayer]) {
for (auto o : it->second->objects){
if (pointOnRect(mouse, o->getBox()) == true){
objects.push_back(o);
}
}
}
}
// Do the actual object deletion.
if (!objects.empty()) {
commandManager->doCommand(new AddRemoveGameObjectCommand(this, objects, false));
}
break;
}
default:
break;
}
}
void LevelEditor::onDrop(int x,int y){
switch(tool){
case SELECT:
case ADD:
{
//Check if the drag center isn't null.
if(dragCenter==NULL)
return;
//The location of the dragCenter.
SDL_Rect r=dragCenter->getBox();
if (selectionDrag == 4) { // dragging
//Apply snap to grid.
determineNewPosition(x, y);
commandManager->doCommand(new MoveGameObjectCommand(this, selection, x - r.x, y - r.y));
} else if (selectionDrag >= 0) { // resizing
determineNewSize(x, y, r);
commandManager->doCommand(new MoveGameObjectCommand(this, dragCenter, r.x, r.y, r.w, r.h));
}
//Make sure the dragCenter is null and set selectionDrag false.
dragCenter=NULL;
selectionDrag=-1;
break;
}
default:
break;
}
}
void LevelEditor::onCameraMove(int dx,int dy){
switch(tool){
case REMOVE:
{
//Only delete when the left mouse button is pressed.
if(pressedLeftMouse){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
std::vector<GameObject*> objects;
//Loop through the objects to check collision.
if (selectedLayer.empty()) {
if (layerVisibility[selectedLayer]) {
for (unsigned int o = 0; o<levelObjects.size(); o++){
if (pointOnRect(mouse, levelObjects[o]->getBox()) == true){
objects.push_back(levelObjects[o]);
}
}
}
} else {
auto it = sceneryLayers.find(selectedLayer);
if (it != sceneryLayers.end() && layerVisibility[selectedLayer]) {
for (auto o : it->second->objects){
if (pointOnRect(mouse, o->getBox()) == true){
objects.push_back(o);
}
}
}
}
// Do the actual object deletion.
if (!objects.empty()) {
commandManager->doCommand(new AddRemoveGameObjectCommand(this, objects, false));
}
}
break;
}
default:
break;
}
}
void LevelEditor::selectionDirty() {
if (selectionPopup != NULL) selectionPopup->dirty = true;
}
void LevelEditor::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Check if one of the windows is closed.
if (eventType == GUIEventClick && name.size() >= 6 && name.substr(name.size() - 6) == "Window") {
destroyWindow(obj);
return;
}
//Resize code for each GUIWindow.
if (name.size() >= 6 && name.substr(name.size() - 6) == "Window") {
//Currently we don't need to process custom resize code since they are already processed in GUIWindow::resize().
return;
}
//Check for GUI events.
//Notification block configure events.
if(name=="cfgNotificationBlockOK"){
//Get the configuredObject.
GameObject* configuredObject=objectWindows[obj];
if(configuredObject){
//Get the message textbox from the GUIWindow.
GUITextArea* message=(GUITextArea*)obj->getChild("message");
if(message){
//Set the message of the notification block.
commandManager->doCommand(new SetEditorPropertyCommand(this, imageManager, renderer,
configuredObject, "message", message->getString(), _("Message")));
}
}
}
//Conveyor belt block configure events.
else if(name=="cfgConveyorBlockOK"){
//Get the configuredObject.
GameObject* configuredObject=objectWindows[obj];
if(configuredObject){
//Get the speed textbox from the GUIWindow.
GUISpinBox* speed=(GUISpinBox*)obj->getChild("speed");
if(speed){
//Set the speed of the conveyor belt.
commandManager->doCommand(new SetEditorPropertyCommand(this, imageManager, renderer,
configuredObject, "speed10", speed->caption, _("Speed")));
}
}
}
+ else if (name == "chkTime") {
+ obj->getChild("time")->visible = obj->getChild("chkTime")->value ? 1 : 0;
+ return;
+ }
+ else if (name == "chkRecordings") {
+ obj->getChild("recordings")->visible = obj->getChild("chkRecordings")->value ? 1 : 0;
+ return;
+ }
//LevelSetting events.
else if(name=="lvlSettingsOK"){
SetLevelPropertyCommand::LevelProperty prop;
prop.levelTime = -1;
prop.levelRecordings = -1;
GUIObject* object=obj->getChild("name");
if(object)
prop.levelName=object->caption;
object=obj->getChild("theme");
if(object)
prop.levelTheme=object->caption;
object = obj->getChild("music");
if (object)
prop.levelMusic = object->caption;
//target time and recordings.
- GUISpinBox* object2=(GUISpinBox*)obj->getChild("time");
- if(object2){
- float number=atof(object2->caption.c_str());
- if(number<=0){
- prop.levelTime=-1;
- }else{
- prop.levelTime=int(floor(number*40.0f+0.5f));
- }
- }
+ object = obj->getChild("chkTime");
+ GUISpinBox* object2 = dynamic_cast<GUISpinBox*>(obj->getChild("time"));
+ assert(object && object2);
- object2=(GUISpinBox*)obj->getChild("recordings");
- if(object){
- int number=atoi(object2->caption.c_str());
- if(number<=0){
- prop.levelRecordings=-1;
- }else{
- prop.levelRecordings=number;
- }
- }
+ double number = std::max(atof(object2->caption.c_str()), 0.0);
+ prop.levelTime = int(floor(number*40.0 + 0.5));
+ if (object->value == 0) prop.levelTime = ~prop.levelTime;
+
+ object = obj->getChild("chkRecordings");
+ object2 = dynamic_cast<GUISpinBox*>(obj->getChild("recordings"));
+ assert(object && object2);
+
+ prop.levelRecordings = std::max(atoi(object2->caption.c_str()), 0);
+ if (object->value == 0) prop.levelRecordings = ~prop.levelRecordings;
// Perform the level setting modification
commandManager->doCommand(new SetLevelPropertyCommand(this, prop));
}
//Level scripting window events.
else if(name=="cfgLevelScriptingEventType"){
//Get the script textbox from the GUIWindow.
GUISingleLineListBox* list=(GUISingleLineListBox*)obj->getChild("cfgLevelScriptingEventType");
if(list){
//Loop through the scripts.
for(unsigned int i=0;i<list->item.size();i++){
GUIObject* script=obj->getChild(list->item[i].first);
if(script){
script->visible=(script->name==list->item[list->value].first);
script->enabled=(script->name==list->item[list->value].first);
}
}
}
return;
}
else if(name=="cfgLevelScriptingOK"){
//Get the script textbox from the GUIWindow.
GUISingleLineListBox* list=(GUISingleLineListBox*)obj->getChild("cfgLevelScriptingEventType");
if(list){
std::map<int, std::string> newScript;
//Loop through the scripts.
for(unsigned int i=0;i<list->item.size();i++){
//Get the GUITextArea.
GUITextArea* script=dynamic_cast<GUITextArea*>(obj->getChild(list->item[i].first));
if(script){
//Set the script for the target block.
string str=script->getString();
if(!str.empty())
newScript[levelEventNameMap[script->name]]=str;
}
}
// Check achievement
if (!newScript.empty()) {
statsMgr.newAchievement("helloworld");
}
// Do the actual changes
commandManager->doCommand(new SetScriptCommand(this, NULL, newScript));
}
}
//Scripting window events.
else if (name == "cfgScriptingEventType"){
//TODO: Save any unsaved scripts? (Or keep track of all scripts and save upon cfgScriptingOK?)
//Get the configuredObject.
Block* configuredObject=dynamic_cast<Block*>(objectWindows[obj]);
if(configuredObject){
//Get the script textbox from the GUIWindow.
GUISingleLineListBox* list=dynamic_cast<GUISingleLineListBox*>(obj->getChild("cfgScriptingEventType"));
if(list){
//Loop through the scripts.
for(unsigned int i=0;i<list->item.size();i++){
GUIObject* script=obj->getChild(list->item[i].first);
if(script){
script->visible=script->enabled=(script->name==list->item[list->value].first);
}
}
}
}
return;
}
else if(name=="cfgScriptingOK"){
//Get the configuredObject.
Block* block = dynamic_cast<Block*>(objectWindows[obj]);
if (block){
std::map<int, std::string> newScript;
std::string newId;
//Get the script textbox from the GUIWindow.
GUISingleLineListBox* list=(GUISingleLineListBox*)obj->getChild("cfgScriptingEventType");
GUIObject* id=obj->getChild("id");
if (list){
//Loop through the scripts.
for (unsigned int i = 0; i < list->item.size(); i++){
//Get the GUITextArea.
GUITextArea* script = dynamic_cast<GUITextArea*>(obj->getChild(list->item[i].first));
if (script){
//Set the script for the target block.
string str = script->getString();
if (!str.empty())
newScript[gameObjectEventNameMap[script->name]] = str;
}
}
}
newId = block->id;
if (id){
newId = id->caption;
}
// Check achievement
if (!newScript.empty()) {
statsMgr.newAchievement("helloworld");
}
// now do the actual changes
commandManager->doCommand(new SetScriptCommand(this, block, newScript, newId));
}
}
else if (name == "cfgAddLayerOK") {
GUIObject* object = obj->getChild("layerName");
if (!object) return;
if (object->caption.empty()) {
msgBox(imageManager, renderer, _("Please enter a layer name."), MsgBoxOKOnly, _("Error"));
return;
}
if (sceneryLayers.find(object->caption) != sceneryLayers.end()) {
msgBox(imageManager, renderer, tfm::format(_("The layer '%s' already exists."), object->caption), MsgBoxOKOnly, _("Error"));
return;
}
// do the actual operation
commandManager->doCommand(new AddRemoveLayerCommand(this, object->caption, true));
}
else if (name == "cfgLayerSettingsOK") {
SetLayerPropertyCommand::LayerProperty prop;
GUIObject* object = obj->getChild("layerName");
if (!object) return;
prop.name = object->caption;
object = obj->getChild("speedX");
if (!object) return;
prop.speedX = atof(object->caption.c_str());
object = obj->getChild("speedY");
if (!object) return;
prop.speedY = atof(object->caption.c_str());
object = obj->getChild("cameraX");
if (!object) return;
prop.cameraX = atof(object->caption.c_str());
object = obj->getChild("cameraY");
if (!object) return;
prop.cameraY = atof(object->caption.c_str());
object = obj->getChild("oldName");
if (!object) return;
const std::string& oldName = object->caption;
if (prop.name.empty()) {
msgBox(imageManager, renderer, _("Please enter a layer name."), MsgBoxOKOnly, _("Error"));
return;
}
if (prop.name != oldName && sceneryLayers.find(prop.name) != sceneryLayers.end()) {
msgBox(imageManager, renderer, tfm::format(_("The layer '%s' already exists."), prop.name), MsgBoxOKOnly, _("Error"));
return;
}
// do the actual operation
commandManager->doCommand(new SetLayerPropertyCommand(this, oldName, prop));
}
else if (name == "cfgMoveToLayerOK") {
GUIObject* object = obj->getChild("layerName");
if (!object) return;
const std::string& layerName = object->caption;
object = obj->getChild("oldName");
if (!object) return;
const std::string& oldName = object->caption;
if (layerName.empty()) {
msgBox(imageManager, renderer, _("Please enter a layer name."), MsgBoxOKOnly, _("Error"));
return;
}
if (oldName == layerName) {
msgBox(imageManager, renderer, _("Source and destination layers are the same."), MsgBoxOKOnly, _("Error"));
return;
}
// do the actual operation
commandManager->doCommand(new MoveToLayerCommand(this, selection, oldName, layerName));
}
else if (name == "cfgCustomSceneryOK") {
//Get the configuredObject.
Scenery* configuredObject = dynamic_cast<Scenery*>(objectWindows[obj]);
if (configuredObject){
//Get the custom scenery from the GUIWindow.
GUITextArea* txt = (GUITextArea*)obj->getChild("cfgCustomScenery");
if (txt){
//Set the custom scenery.
commandManager->doCommand(new SetEditorPropertyCommand(this, imageManager, renderer,
configuredObject, "customScenery", txt->getString(), _("Scenery")));
}
}
}
else if (name == "lstAppearance") {
//Get the configuredObject.
Block *block = dynamic_cast<Block*>(objectWindows[obj]);
GUIListBox *list = dynamic_cast<GUIListBox*>(obj->getChild("lstAppearance"));
GUIImage *image = dynamic_cast<GUIImage*>(obj->getChild("imgAppearance"));
if (block && list && image) {
//Reset the image first.
image->setImage(NULL);
image->setClipRect(SDL_Rect{ 0, 0, 0, 0 });
//Get the appearance name.
std::string appearanceName;
if (list->value <= 0) {
//Do nothing since the selected is the default appearance.
} else if (list->value <= (int)sceneryBlockNames.size()) {
//A custom appearance is selected.
appearanceName = sceneryBlockNames[list->value - 1];
} else {
//The configured object has an invalid custom appearance name.
appearanceName = block->customAppearanceName;
}
//Try to find the theme block.
ThemeBlock *themeBlock = NULL;
if (!appearanceName.empty()) {
themeBlock = objThemes.getScenery(appearanceName);
}
if (themeBlock == NULL) {
themeBlock = objThemes.getBlock(block->type);
}
if (themeBlock) {
image->setImage(themeBlock->editorPicture.texture);
const auto& offsetData = themeBlock->editorPicture.offset.offsetData;
if (offsetData.size() > 0) {
const auto& r = offsetData[0];
image->setClipRect(SDL_Rect{ r.x, r.y, r.w, r.h });
}
}
}
return;
}
else if (name == "cfgAppearanceOK") {
//Get the configuredObject.
Block *block = dynamic_cast<Block*>(objectWindows[obj]);
GUIListBox *list = dynamic_cast<GUIListBox*>(obj->getChild("lstAppearance"));
if (block && list) {
//Get the appearance name.
std::string appearanceName;
if (list->value <= 0) {
//Do nothing since the selected is the default appearance.
} else if (list->value <= (int)sceneryBlockNames.size()) {
//A custom appearance is selected.
appearanceName = sceneryBlockNames[list->value - 1];
} else {
//The configured object has an invalid custom appearance name.
appearanceName = block->customAppearanceName;
}
//Update the block property if it's changed.
if (appearanceName != block->customAppearanceName) {
commandManager->doCommand(new SetEditorPropertyCommand(this, imageManager, renderer,
block, "appearance", appearanceName, _("Appearance")));
}
}
}
//NOTE: We assume every event came from a window
//and the event is either window closed event or OK/Cancel button click event, so we remove it.
destroyWindow(obj);
}
void LevelEditor::destroyWindow(GUIObject* window){
//Make sure the given pointer isn't null.
if(!window)
return;
//Remove the window from the GUIObject root.
if(GUIObjectRoot){
vector<GUIObject*>::iterator it;
it=find(GUIObjectRoot->childControls.begin(),GUIObjectRoot->childControls.end(),window);
if(it!=GUIObjectRoot->childControls.end()){
GUIObjectRoot->childControls.erase(it);
}
}
//Also remove the window from the objectWindows map.
map<GUIObject*,GameObject*>::iterator it;
it=objectWindows.find(window);
if(it!=objectWindows.end()){
objectWindows.erase(it);
}
//And delete the GUIWindow.
delete window;
}
////////////////LOGIC////////////////////
void LevelEditor::logic(ImageManager& imageManager, SDL_Renderer& renderer){
if(playMode){
//PlayMode so let the game do it's logic.
Game::logic(imageManager,renderer);
}else{
//Update animation even under edit mode. (There are checks in Block::move() which don't do game logic in edit mode.)
for (unsigned int i = 0; i<levelObjects.size(); i++){
//Let the gameobject handle movement.
levelObjects[i]->move();
}
//Also update the scenery.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it){
it->second->updateAnimation();
}
//In case of a selection or actions popup prevent the camera from moving.
if(selectionPopup || actionsPopup)
return;
//Move the camera.
if (cameraXvel != 0 || cameraYvel != 0) {
if (pressedShift) {
if (cameraXvel > 0) cameraXvel++;
else if (cameraXvel < 0) cameraXvel--;
if (cameraYvel > 0) cameraYvel++;
else if (cameraYvel < 0) cameraYvel--;
}
camera.x = clamp(camera.x + cameraXvel, -1000 - SCREEN_WIDTH, LEVEL_WIDTH + 1000);
camera.y = clamp(camera.y + cameraYvel, -1000 - SCREEN_HEIGHT, LEVEL_HEIGHT + 1000);
//Call the onCameraMove event.
onCameraMove(cameraXvel, cameraYvel);
}
//Move the camera with the mouse.
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
{
//Check if the mouse isn't above a GUIObject (window).
bool inside=false;
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
SDL_Rect box={0,0,0,0};
box.x=GUIObjectRoot->childControls[i]->left;
box.y=GUIObjectRoot->childControls[i]->top;
box.w=GUIObjectRoot->childControls[i]->width;
box.h=GUIObjectRoot->childControls[i]->height;
if(pointOnRect(mouse,box))
inside=true;
}
if(!inside){
SDL_Rect r[3]={toolbarRect,toolboxRect};
int m=2;
//TODO: Also call onCameraMove when moving using the mouse.
setCamera(r,m);
}
}
//It isn't playMode so the mouse should be checked.
tooltip=-1;
//We loop through the number of tools + the number of buttons.
for (int t = 0; t <= (int)ToolTips::BackToMenu; t++){
SDL_Rect toolRect={(SCREEN_WIDTH-460)/2+(t*40)+((t+1)*10),SCREEN_HEIGHT-45,40,40};
//Check for collision.
if(pointOnRect(mouse,toolRect)==true){
//Set the tooltip tool.
tooltip=t;
}
}
}
}
/////////////////RENDER//////////////////////
void LevelEditor::render(ImageManager& imageManager,SDL_Renderer& renderer){
//Let the game render the game when it is the play mode.
if (playMode) {
Game::render(imageManager, renderer);
} else {
// The following code are partially copied from Game::render()
//First of all render the background.
{
//Get a pointer to the background.
ThemeBackground* bg = background;
//Check if the background is null, but there are themes.
if (bg == NULL && objThemes.themeCount()>0){
//Get the background from the first theme in the stack.
bg = objThemes[0]->getBackground(false);
}
//Check if the background isn't null.
if (bg){
//It isn't so draw it.
bg->draw(renderer);
//And if it's the loaded background then also update the animation.
//FIXME: Updating the animation in the render method?
if (bg == background)
bg->updateAnimation();
} else{
//There's no background so fill the screen with white.
SDL_SetRenderDrawColor(&renderer, 255, 255, 255, 255);
SDL_RenderClear(&renderer);
}
}
//Now draw the background layers.
auto it = sceneryLayers.begin();
for (; it != sceneryLayers.end(); ++it){
if (it->first >= "f") break; // now we meet a foreground layer
if (layerVisibility[it->first]) {
it->second->show(renderer);
}
}
//Now we draw the levelObjects.
if (layerVisibility[std::string()]) {
- for (unsigned int o = 0; o < levelObjects.size(); o++){
- levelObjects[o]->show(renderer);
+ //NEW: always render the pushable blocks in front of other blocks
+ std::vector<Block*> pushableBlocks;
+
+ for (auto o : levelObjects) {
+ if (o->type == TYPE_PUSHABLE) {
+ pushableBlocks.push_back(o);
+ } else {
+ o->show(renderer);
+ }
+ }
+
+ for (auto o : pushableBlocks) {
+ o->show(renderer);
}
}
//We don't draw the player and the shadow at all.
//Now draw the foreground layers.
for (; it != sceneryLayers.end(); ++it){
if (layerVisibility[it->first]) {
it->second->show(renderer);
}
}
}
//Only render extra stuff like the toolbar, selection, etc.. when not in playMode.
if(!playMode){
//Get the current mouse location.
int x, y;
SDL_GetMouseState(&x, &y);
//Create the rectangle.
SDL_Rect mouse = { x + camera.x, y + camera.y, 0, 0 };
//Render the selectionmarks.
//TODO: Check if block is in sight.
for(unsigned int o=0; o<selection.size(); o++){
//Get the location to draw.
SDL_Rect r=selection[o]->getBox();
// Change the mouse cursor if necessary
if (selectionDrag < 0 && tool != REMOVE) {
int midx = r.x + r.w / 2 - 2;
int midy = r.y + r.h / 2 - 2;
if (mouse.x >= r.x && mouse.x < r.x + 5) {
if (mouse.y >= r.y && mouse.y < r.y + 5) {
currentCursor = CURSOR_SIZE_FDIAG;
} else if (mouse.y >= midy && mouse.y < midy + 5) {
currentCursor = CURSOR_SIZE_HOR;
} else if (mouse.y >= r.y + r.h - 5 && mouse.y < r.y + r.h) {
currentCursor = CURSOR_SIZE_BDIAG;
}
} else if (mouse.x >= midx && mouse.x < midx + 5) {
if (mouse.y >= r.y && mouse.y < r.y + 5) {
currentCursor = CURSOR_SIZE_VER;
} else if (mouse.y >= r.y + r.h - 5 && mouse.y < r.y + r.h) {
currentCursor = CURSOR_SIZE_VER;
}
} else if (mouse.x >= r.x + r.w - 5 && mouse.x < r.x + r.w) {
if (mouse.y >= r.y && mouse.y < r.y + 5) {
currentCursor = CURSOR_SIZE_BDIAG;
} else if (mouse.y >= midy && mouse.y < midy + 5) {
currentCursor = CURSOR_SIZE_HOR;
} else if (mouse.y >= r.y + r.h - 5 && mouse.y < r.y + r.h) {
currentCursor = CURSOR_SIZE_FDIAG;
}
}
}
bool mouseIn = pointOnRect(mouse, r);
r.x-=camera.x;
r.y-=camera.y;
drawGUIBox(r.x,r.y,r.w,r.h,renderer,0xFFFFFF33);
//Draw the selectionMarks.
applyTexture(r.x,r.y,selectionMark,renderer);
applyTexture(r.x+r.w-5,r.y,selectionMark,renderer);
applyTexture(r.x,r.y+r.h-5,selectionMark,renderer);
applyTexture(r.x+r.w-5,r.y+r.h-5,selectionMark,renderer);
// draw additional selection marks
if (mouseIn && selectionDrag < 0 && tool != REMOVE) {
applyTexture(r.x + r.w / 2 - 2, r.y, selectionMark, renderer);
applyTexture(r.x + r.w / 2 - 2, r.y + r.h - 5, selectionMark, renderer);
applyTexture(r.x, r.y + r.h / 2 - 2, selectionMark, renderer);
applyTexture(r.x + r.w - 5, r.y + r.h / 2 - 2, selectionMark, renderer);
}
}
//Set the color for the borders.
{
SDL_Color c = objThemes.getTextColor(false);
SDL_SetRenderDrawColor(&renderer, c.r, c.g, c.b, 115);
}
int leftWidth=0;
int rightWidth=0;
//Draw the dark areas marking the outside of the level.
SDL_Rect r{0,0,0,0};
if(camera.x<0){
//Draw left side.
r.x=0;
r.y=0;
r.w=0-camera.x;
leftWidth=r.w;
r.h=SCREEN_HEIGHT;
SDL_RenderFillRect(&renderer, &r);
}
if(camera.y<0){
//Draw the top.
r.x=leftWidth;
r.y=0;
r.w=SCREEN_WIDTH;
r.h=0-camera.y;
SDL_RenderFillRect(&renderer, &r);
} else {
r.h=0;
}
if(camera.x>LEVEL_WIDTH-SCREEN_WIDTH){
//Draw right side.
r.x=LEVEL_WIDTH-camera.x;
r.y=std::max(r.y+r.h,0);
r.w=SCREEN_WIDTH-(LEVEL_WIDTH-camera.x);
rightWidth=r.w;
r.h=SCREEN_HEIGHT;
SDL_RenderFillRect(&renderer, &r);
}
if(camera.y>LEVEL_HEIGHT-SCREEN_HEIGHT){
//Draw the bottom.
r.x=leftWidth;
r.y=LEVEL_HEIGHT-camera.y;
r.w=SCREEN_WIDTH-rightWidth-leftWidth;
r.h=SCREEN_HEIGHT-(LEVEL_HEIGHT-camera.y);
SDL_RenderFillRect(&renderer, &r);
}
//Check if we should draw on stuff.
showConfigure(renderer);
if (selectionDrag >= 0 && tool != REMOVE) {
showSelectionDrag(renderer);
}
//Find a block where the mouse is hovering on.
bool isMouseOnSomething = false;
if (selectedLayer.empty()){
if (layerVisibility[selectedLayer]) {
// Current layer is Blocks layer
for (unsigned int o = 0; o<levelObjects.size(); o++){
SDL_Rect rect = levelObjects[o]->getBox();
if (pointOnRect(mouse, rect) == true){
isMouseOnSomething = true;
if (tool == REMOVE){
drawGUIBox(rect.x - camera.x, rect.y - camera.y, rect.w, rect.h, renderer, 0xFF000055);
currentCursor = CURSOR_REMOVE;
} else{
drawGUIBox(rect.x - camera.x, rect.y - camera.y, rect.w, rect.h, renderer, 0xFFFFFF33);
}
}
}
}
} else {
auto it = sceneryLayers.find(selectedLayer);
if (it != sceneryLayers.end() && layerVisibility[selectedLayer]) {
// Current layer is scenery layer
for (auto o : it->second->objects){
SDL_Rect rect = o->getBox();
if (pointOnRect(mouse, rect) == true){
isMouseOnSomething = true;
if (tool == REMOVE){
drawGUIBox(rect.x - camera.x, rect.y - camera.y, rect.w, rect.h, renderer, 0xFF000055);
currentCursor = CURSOR_REMOVE;
} else{
drawGUIBox(rect.x - camera.x, rect.y - camera.y, rect.w, rect.h, renderer, 0xFFFFFF33);
}
}
}
}
}
// show current object only when mouse is not hover on any blocks
if (!isMouseOnSomething && tool == ADD && selectionDrag < 0) {
showCurrentObject(renderer);
}
//Draw the level borders.
drawRect(-camera.x,-camera.y,LEVEL_WIDTH,LEVEL_HEIGHT,renderer);
//Render the hud layer.
renderHUD(renderer);
//Render selection popup (if any).
if(selectionPopup!=NULL){
if(linking || moving){
//If we switch to linking mode then delete it
//FIXME: Logic in the render method.
delete selectionPopup;
selectionPopup=NULL;
}else{
selectionPopup->render(imageManager,renderer);
}
}
//Render actions popup (if any).
if(actionsPopup!=NULL){
actionsPopup->render(renderer);
}
}
}
void LevelEditor::renderHUD(SDL_Renderer& renderer){
//If moving show the moving speed in the top right corner.
if(moving){
//Calculate width of text "Movespeed: 125" to keep the same position with every value
if (movingSpeedWidth == -1){
int w;
TTF_SizeUTF8(fontText, tfm::format(_("Speed: %d = %0.2f block/s"), 125, 10.0f).c_str(), &w, NULL);
movingSpeedWidth = w + 4;
}
SDL_Texture *tex = NULL;
//Check which text should we use.
if (pauseMode) {
//Update the text if necessary.
if (pauseTimeTexture.needsUpdate(pauseTime)) {
if (pauseTime < 0) {
pauseTimeTexture.update(pauseTime,
textureFromText(renderer, *fontText,
_("Stop at this point"),
objThemes.getTextColor(true)));
} else {
pauseTimeTexture.update(pauseTime,
textureFromText(renderer, *fontText,
tfm::format(_("Pause: %d = %0.3fs"), pauseTime, float(pauseTime)*0.025f).c_str(),
objThemes.getTextColor(true)));
}
}
tex = pauseTimeTexture.get();
} else {
//Update the text if necessary.
if (movementSpeedTexture.needsUpdate(movingSpeed)) {
movementSpeedTexture.update(movingSpeed,
textureFromText(renderer, *fontText,
tfm::format(_("Speed: %d = %0.2f block/s"), movingSpeed, float(movingSpeed)*0.08f).c_str(),
objThemes.getTextColor(true)));
}
tex = movementSpeedTexture.get();
}
//Draw the text in the box.
drawGUIBox(SCREEN_WIDTH-movingSpeedWidth-2,-2,movingSpeedWidth+8,
textureHeight(*tex)+6,renderer,0xFFFFFFFF);
applyTexture(SCREEN_WIDTH-movingSpeedWidth,2,*tex,renderer,NULL);
}
//On top of all render the toolbar.
drawGUIBox(toolbarRect.x,toolbarRect.y,9*50+10,52,renderer,0xEDEDEDFF);
//Draw the first four options.
SDL_Rect srcRect={0,0,200,50};
SDL_Rect dstRect={toolbarRect.x+5, toolbarRect.y, srcRect.w, srcRect.h};
SDL_RenderCopy(&renderer, toolbar.get(), &srcRect, &dstRect);
//Draw the undo/redo button.
SDL_SetTextureAlphaMod(toolbar.get(), commandManager->canUndo() ? 255 : 128);
srcRect.x = 200;
srcRect.w = 50;
dstRect.x = toolbarRect.x + 205;
dstRect.w = srcRect.w;
SDL_RenderCopy(&renderer, toolbar.get(), &srcRect, &dstRect);
SDL_SetTextureAlphaMod(toolbar.get(), commandManager->canRedo() ? 255 : 128);
srcRect.x = 250;
srcRect.w = 50;
dstRect.x = toolbarRect.x + 255;
dstRect.w = srcRect.w;
SDL_RenderCopy(&renderer, toolbar.get(), &srcRect, &dstRect);
SDL_SetTextureAlphaMod(toolbar.get(), 255);
//And the last three.
srcRect.x=300;
srcRect.w=150;
dstRect.x=toolbarRect.x+305;
dstRect.w=srcRect.w;
SDL_RenderCopy(&renderer, toolbar.get(), &srcRect, &dstRect);
//Now render a tooltip.
if(tooltip>=0 && static_cast<std::size_t>(tooltip)<tooltipTextures.size()) {
SDL_Texture *tex = tooltipTextures.at(tooltip).get();
if (tooltip == (int)ToolTips::UndoNoTooltip) {
std::string s = commandManager->describeUndo();
if (undoTooltipTexture.needsUpdate(s)) {
undoTooltipTexture.update(s, textureFromText(renderer, *fontText, s.c_str(), objThemes.getTextColor(true)));
}
tex = undoTooltipTexture.get();
} else if (tooltip == (int)ToolTips::RedoNoTooltip) {
std::string s = commandManager->describeRedo();
if (redoTooltipTexture.needsUpdate(s)) {
redoTooltipTexture.update(s, textureFromText(renderer, *fontText, s.c_str(), objThemes.getTextColor(true)));
}
tex = redoTooltipTexture.get();
}
if(tex) {
const SDL_Rect texSize = rectFromTexture(*tex);
SDL_Rect r={(SCREEN_WIDTH-440)/2+(tooltip*40)+(tooltip*10),SCREEN_HEIGHT-45,40,40};
r.y=SCREEN_HEIGHT-50-texSize.h;
if(r.x+texSize.w>SCREEN_WIDTH-50)
r.x=SCREEN_WIDTH-50-texSize.w;
//Draw borders around text
Uint32 color=0xFFFFFF00|230;
drawGUIBox(r.x-2,r.y-2,texSize.w+4,texSize.h+4,renderer,color);
applyTexture(r.x, r.y, *tex, renderer);
}
}
// for toolbox button animation (0-31)
static int tick = 8;
const int mmm = getEditorOrderMax();
if (currentType >= mmm)currentType = mmm - 1;
if (currentType < 0) currentType = 0;
//Render the tool box.
if(!playMode && !moving && tool==ADD && selectionPopup==NULL && actionsPopup==NULL && objectWindows.empty()){
// get mouse position
int x, y;
SDL_GetMouseState(&x, &y);
if (toolboxVisible){
toolboxRect.x=0;
toolboxRect.y=0;
toolboxRect.w=SCREEN_WIDTH;
toolboxRect.h=64;
drawGUIBox(-2,-2,SCREEN_WIDTH+4,66,renderer,0xFFFFFF00|230);
bool isMouseOnSomething = false;
//Draw the hide icon.
SDL_Rect r={SCREEN_WIDTH-20,2,16,16};
SDL_Rect r2={80,0,r.w,r.h};
if (x >= SCREEN_WIDTH - 24 && x < SCREEN_WIDTH && y < 20) {
isMouseOnSomething = true;
tick = (tick + 1) & 31;
r.y -= (tick < 16) ? (tick / 4 - 2) : (6 - tick / 4);
}
SDL_RenderCopy(&renderer, bmGUI.get(), &r2, &r);
//Calculate the maximal number of blocks can be displayed.
const int m=(SCREEN_WIDTH-48)/64;
if(toolboxIndex>=mmm-m){
toolboxIndex = mmm - m;
}else{
//Draw an icon.
r.x=SCREEN_WIDTH-20;
r.y=24;
r2.x=96;
r2.y=16;
if (x >= SCREEN_WIDTH - 24 && x < SCREEN_WIDTH && y >= 20 && y < 44) {
isMouseOnSomething = true;
tick = (tick + 1) & 31;
r.x += (tick < 16) ? (tick / 4 - 2) : (6 - tick / 4);
}
SDL_RenderCopy(&renderer, bmGUI.get(),&r2,&r);
}
if(toolboxIndex<=0){
toolboxIndex=0;
}else{
//Draw an icon.
r.x=4;
r.y=24;
r2.x=80;
r2.y=16;
if (x >= 0 && x < 24 && y >= 20 && y < 44) {
isMouseOnSomething = true;
tick = (tick + 1) & 31;
r.x -= (tick < 16) ? (tick / 4 - 2) : (6 - tick / 4);
}
SDL_RenderCopy(&renderer, bmGUI.get(), &r2, &r);
}
// reset animation timer if there is no animation
if (!isMouseOnSomething) {
tick = 8;
}
//Draw available blocks.
for(int i=0;i<m;i++){
if (i + toolboxIndex >= mmm) break;
//Draw a rectangle around the current tool.
if(i+toolboxIndex==currentType){
drawGUIBox(i*64+24,3,64,58,renderer,0xDDDDDDFF);
}
if (selectedLayer.empty()) {
// show normal blocks
ThemeBlock* obj = objThemes.getBlock(editorTileOrder[i + toolboxIndex]);
if (obj){
obj->editorPicture.draw(renderer, i * 64 + 24 + 7, 7);
}
} else {
// show scenery blocks
if (i + toolboxIndex < (int)sceneryBlockNames.size()) {
ThemeBlock* obj = objThemes.getScenery(sceneryBlockNames[i + toolboxIndex]);
if (obj){
obj->editorPicture.draw(renderer, i * 64 + 24 + 7, 7);
}
} else {
// it's custom scenery block
// just draw a stupid icon
const SDL_Rect r = { 48, 16, 16, 16 };
const SDL_Rect dstRect = { i * 64 + 24 + 7, 7, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
}
}
}
//Draw a tool tip.
if(y<64 && x>=24 && x<24+m*64){
int i=(x-24)/64;
if (i + toolboxIndex < getEditorOrderMax()){
TexturePtr& tip = (!selectedLayer.empty())
? getCachedTextTexture(renderer, (i + toolboxIndex < (int)sceneryBlockNames.size())
? describeSceneryName(sceneryBlockNames[i + toolboxIndex]).c_str() : _("Custom scenery block"))
: typeTextTextures.at(editorTileOrder[i + toolboxIndex]);
const SDL_Rect tipSize = rectFromTexture(*tip);
SDL_Rect r = { 24 + i * 64, 64, 40, 40 };
if (r.x + tipSize.w>SCREEN_WIDTH - 50)
r.x = SCREEN_WIDTH - 50 - tipSize.w;
//Draw borders around text
Uint32 color = 0xFFFFFF00 | 230;
drawGUIBox(r.x - 2, r.y - 2, tipSize.w + 4, tipSize.h + 4, renderer, color);
//Draw tooltip's text
applyTexture(r.x, r.y, tip, renderer);
}
}
}else{
const SDL_Rect tbtSize = rectFromTexture(*toolboxText);
toolboxRect.x=SCREEN_WIDTH-tbtSize.w-28;
toolboxRect.y=0;
toolboxRect.w=tbtSize.w+28;
toolboxRect.h=tbtSize.h+4;
SDL_Rect r={SCREEN_WIDTH-tbtSize.w-24,2,16,16};
drawGUIBox(r.x-4,-2,tbtSize.w+32,tbtSize.h+6,renderer,0xFFFFFFFF);
//Draw "Toolbox" text.
applyTexture(r.x, r.y, toolboxText, renderer);
const SDL_Rect r2={96,0,16,16};
r.x=SCREEN_WIDTH-20;
r.w=r2.w;
r.h=r2.h;
// check if mouse is hovering on
if (x >= toolboxRect.x && x < toolboxRect.x + toolboxRect.w && y >= toolboxRect.y && y < toolboxRect.y + toolboxRect.h) {
tick = (tick + 1) & 31;
r.y += (tick < 16) ? (tick / 4 - 2) : (6 - tick / 4);
} else {
tick = 8;
}
//Draw arrow.
SDL_RenderCopy(&renderer, bmGUI.get(),&r2,&r);
}
}else{
toolboxRect.x=-1;
toolboxRect.y=-1;
toolboxRect.w=0;
toolboxRect.h=0;
}
//Draw a rectangle around the current tool.
Uint32 color=0xFFFFFF00;
drawGUIBox((SCREEN_WIDTH-440)/2+(tool*40)+(tool*10),SCREEN_HEIGHT-46,42,42,renderer,color);
}
void LevelEditor::showCurrentObject(SDL_Renderer& renderer){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Check if we should snap the block to grid or not.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Check if the currentType is a legal type.
if(currentType>=0 && currentType<getEditorOrderMax()){
if (selectedLayer.empty()) {
// show normal blocks
ThemeBlock* obj = objThemes.getBlock(editorTileOrder[currentType]);
if (obj){
obj->editorPicture.draw(renderer, x - camera.x, y - camera.y);
}
} else {
// show scenery blocks
if (currentType < (int)sceneryBlockNames.size()) {
ThemeBlock* obj = objThemes.getScenery(sceneryBlockNames[currentType]);
if (obj){
obj->editorPicture.draw(renderer, x - camera.x, y - camera.y);
}
} else {
// it's custom scenery block
// just draw a stupid icon
const SDL_Rect r = { 48, 16, 16, 16 };
const SDL_Rect dstRect = { x - camera.x, y - camera.y, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
}
}
}
}
void LevelEditor::determineNewPosition(int& x, int& y) {
if (dragCenter) {
SDL_Rect r = dragCenter->getBox();
x -= dragSrartPosition.x - r.x;
y -= dragSrartPosition.y - r.y;
} else {
x -= 25;
y -= 25;
}
// Check if we should snap the block to grid or not.
if (!pressedShift) {
x = int(floor(x/50.0f + 0.5f)) * 50;
y = int(floor(y/50.0f + 0.5f)) * 50;
}
}
void LevelEditor::determineNewSize(int x, int y, SDL_Rect& r) {
switch (selectionDrag % 3) {
case 0:
if (x > r.x + r.w - 15) x = r.x + r.w - 15;
if (!pressedShift) {
x = int(floor(x/50.0f + 0.5f)) * 50;
while (x > r.x + r.w - 15) x -= 50;
}
r.w += r.x - x;
r.x = x;
break;
case 2:
if (x < r.x + 15) x = r.x + 15;
if (!pressedShift) {
x = int(floor(x/50.0f + 0.5f)) * 50;
while (x < r.x + 15) x += 50;
}
r.w = x - r.x;
break;
}
switch (selectionDrag / 3) {
case 0:
if (y > r.y + r.h - 15) y = r.y + r.h - 15;
if (!pressedShift) {
y = int(floor(y/50.0f + 0.5f)) * 50;
while (y > r.y + r.h - 15) y -= 50;
}
r.h += r.y - y;
r.y = y;
break;
case 2:
if (y < r.y + 15) y = r.y + 15;
if (!pressedShift) {
y = int(floor(y/50.0f + 0.5f)) * 50;
while (y < r.y + 15) y += 50;
}
r.h = y - r.y;
break;
}
}
void LevelEditor::showSelectionDrag(SDL_Renderer& renderer){
//Check if the drag center isn't null.
if (dragCenter == NULL) return;
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
x+=camera.x;
y+=camera.y;
//The location of the dragCenter.
SDL_Rect r = dragCenter->getBox();
if (selectionDrag == 4) { // dragging
// Check if we should snap the block to grid or not.
determineNewPosition(x, y);
//Loop through the selection.
//TODO: Check if block is in sight.
for (unsigned int o = 0; o < selection.size(); o++){
// FIXME: ad-hoc code which moves blocks temporarily, draw, and moves them back
const SDL_Rect r1 = selection[o]->getBox();
selection[o]->setBaseLocation((r1.x - r.x) + x, (r1.y - r.y) + y);
selection[o]->show(renderer);
selection[o]->setBaseLocation(r1.x, r1.y);
}
} else if (selectionDrag >= 0) { // resizing
// Check if we should snap the block to grid or not.
determineNewSize(x, y, r);
drawGUIBox(r.x - camera.x, r.y - camera.y, r.w, r.h, renderer, 0xFFFFFF33);
}
}
void LevelEditor::showConfigure(SDL_Renderer& renderer){
//arrow animation value. go through 0-65535 and loops.
static unsigned short arrowAnimation=0;
arrowAnimation++;
// skip if the Blocks layer is invisinble
if (!layerVisibility[std::string()]) return;
//Use theme color for arrows.
Uint32 color;
{
SDL_Color c = objThemes.getTextColor(false);
color = (Uint32(c.r) << 24) | (Uint32(c.g) << 16) | (Uint32(c.b) << 8) | 0xff;
}
//Draw the trigger lines.
{
map<Block*,vector<GameObject*> >::iterator it;
for(it=triggers.begin();it!=triggers.end();++it){
//Check if the trigger has linked targets.
if(!(*it).second.empty()){
//The location of the trigger.
SDL_Rect r=(*it).first->getBox();
//Loop through the targets.
for(unsigned int o=0;o<(*it).second.size();o++){
//Get the location of the target.
SDL_Rect r1=(*it).second[o]->getBox();
//Draw the line from the center of the trigger to the center of the target.
drawLineWithArrow(r.x-camera.x+25,r.y-camera.y+25,r1.x-camera.x+25,r1.y-camera.y+25,renderer,color,32,arrowAnimation%32);
//Also draw two selection marks.
applyTexture(r.x-camera.x+25-2,r.y-camera.y+25-2,selectionMark,renderer);
applyTexture(r1.x-camera.x+25-2,r1.y-camera.y+25-2,selectionMark,renderer);
}
}
}
//Draw a line to the mouse from the linkingTrigger when linking.
if(linking){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Draw the line from the center of the trigger to mouse.
drawLineWithArrow(linkingTrigger->getBox().x-camera.x+25,linkingTrigger->getBox().y-camera.y+25,x,y,renderer,color,32,arrowAnimation%32);
}
}
//This saves the stacked pause marks on each position.
std::map<std::pair<int, int>, int> stackedMarks;
//Draw the moving positions.
map<Block*,vector<MovingPosition> >::iterator it;
for(it=movingBlocks.begin();it!=movingBlocks.end();++it){
//Check if the block has positions.
if(!(*it).second.empty()){
//The location of the moving block.
SDL_Rect block=(*it).first->getBox();
block.x+=25-camera.x;
block.y+=25-camera.y;
//The location of the previous position.
//The first time it's the moving block's position self.
SDL_Rect r=block;
//Loop through the positions.
for(unsigned int o=0;o<(*it).second.size();o++){
//Draw the line from the center of the previous position to the center of the position.
//x and y are the coordinates for the current moving position.
int x=block.x+(*it).second[o].x;
int y=block.y+(*it).second[o].y;
//Check if we need to draw line
double dx=r.x-x;
double dy=r.y-y;
double d=sqrt(dx*dx+dy*dy);
if(d>0.001f){
if(it->second[o].time>0){
//Calculate offset to contain the moving speed.
int offset=int(d*arrowAnimation/it->second[o].time)%32;
drawLineWithArrow(r.x,r.y,x,y,renderer,color,32,offset);
}else{
//time==0 ???? so don't draw arrow at all
drawLine(r.x,r.y,x,y,renderer);
}
} else {
// distance==0 which means pause mode
// FIXME: it's ugly
SDL_Rect r1 = { 0, 0, 16, 16 }, r2 = { x - 25, y - 25 + 15 * (stackedMarks[std::pair<int, int>(x, y)]++), 16, 16 };
if (it->second[o].time) {
char s[64];
sprintf(s, "%gs", float(it->second[o].time) * 0.025f);
r1.x = 0; r1.y = 80;
SDL_RenderCopy(&renderer, bmGUI.get(), &r1, &r2);
r2.x += 16;
for (int i = 0; s[i]; i++) {
if (s[i] >= '0' && s[i] <= '9') {
r1.x = 16 + (s[i] - '0') * 8;
r1.w = 8;
} else if (s[i] == '.') {
r1.x = 96;
r1.w = 8;
} else if (s[i] == 's') {
r1.x = 104;
r1.w = 8;
} else {
// show some garbage
r1.x = 0;
r1.w = 1;
}
r2.w = 8;
SDL_RenderCopy(&renderer, bmGUI.get(), &r1, &r2);
r2.x += 8;
}
} else {
r1.x = 32; r1.y = 64;
SDL_RenderCopy(&renderer, bmGUI.get(), &r1, &r2);
}
}
//And draw a marker at the end.
applyTexture(x-13,y-13,movingMark,renderer);
//Get the box of the previous position.
SDL_Rect tmp={x,y,0,0};
r=tmp;
}
}
}
//Draw a line to the mouse from the previous moving pos.
if(moving){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Check if we should snap the block to grid or not.
if(!pressedShift){
x+=camera.x;
y+=camera.y;
snapToGrid(&x,&y);
x-=camera.x;
y-=camera.y;
}else{
x-=25;
y-=25;
}
int posX,posY;
//Check if there are moving positions for the moving block.
if(!movingBlocks[movingBlock].empty()){
//Draw the line from the center of the previouse moving positions to mouse.
posX=movingBlocks[movingBlock].back().x;
posY=movingBlocks[movingBlock].back().y;
posX-=camera.x;
posY-=camera.y;
posX+=movingBlock->getBox().x;
posY+=movingBlock->getBox().y;
}else{
//Draw the line from the center of the movingblock to mouse.
posX=movingBlock->getBox().x-camera.x;
posY=movingBlock->getBox().y-camera.y;
}
//Check if the current point is the same as the previous point
if (posX == x && posY == y) {
pauseMode = true;
} else {
pauseMode = false;
//Calculate offset to contain the moving speed.
int offset = int(double(arrowAnimation)*movingSpeed / 10.0) % 32;
//Draw the line.
drawLineWithArrow(posX + 25, posY + 25, x + 25, y + 25, renderer, color, 32, offset);
}
//Draw a marker.
applyTexture(x+12,y+12,movingMark,renderer);
}
}
void LevelEditor::resize(ImageManager &imageManager, SDL_Renderer &renderer){
//Call the resize method of the Game.
Game::resize(imageManager, renderer);
//Move the toolbar's position rect used for collision.
toolbarRect.x=(SCREEN_WIDTH-460)/2;
toolbarRect.y=SCREEN_HEIGHT-50;
}
//Filling the order array
const int LevelEditor::editorTileOrder[EDITOR_ORDER_MAX]={
TYPE_BLOCK,
TYPE_SHADOW_BLOCK,
TYPE_SPIKES,
TYPE_FRAGILE,
TYPE_MOVING_BLOCK,
TYPE_MOVING_SHADOW_BLOCK,
TYPE_MOVING_SPIKES,
TYPE_CONVEYOR_BELT,
TYPE_SHADOW_CONVEYOR_BELT,
TYPE_BUTTON,
TYPE_SWITCH,
TYPE_PORTAL,
TYPE_SWAP,
TYPE_CHECKPOINT,
TYPE_NOTIFICATION_BLOCK,
TYPE_START_PLAYER,
TYPE_START_SHADOW,
TYPE_EXIT,
TYPE_COLLECTABLE,
TYPE_PUSHABLE
};
diff --git a/src/LevelEditor.h b/src/LevelEditor.h
index f274445..d3837aa 100644
--- a/src/LevelEditor.h
+++ b/src/LevelEditor.h
@@ -1,406 +1,408 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LEVELEDITOR_H
#define LEVELEDITOR_H
#include "CachedTexture.h"
#include "GameState.h"
#include "GameObjects.h"
#include "Player.h"
#include "Game.h"
#include "GUIObject.h"
#include <vector>
#include <map>
#include <string>
enum class ToolTips {
Select = 0,
Add,
Delete,
Play,
UndoNoTooltip, // dynamically generated
RedoNoTooltip, // dynamically generated
LevelSettings,
SaveLevel,
BackToMenu,
- Configure,
+ Select_UsedInSelectionPopup,
+ Delete_UsedInSelectionPopup,
+ Configure_UsedInSelectionPopup,
TooltipMax,
};
//Class that represents a moving position for moving blocks.
class MovingPosition{
public:
//Integer containing the relative time used to store in the level.
int time;
//The x location.
int x;
//The x location.
int y;
//Constructor.
//x: The x position relative to the moving block's position.
//y: The y position relative to the moving block's position.
//time: The time it takes from the previous position to here.
MovingPosition(int x,int y,int time);
//Destructor.
~MovingPosition();
//This will update the moving position.
//x: The x position relative to the moving block's position.
//y: The y position relative to the moving block's position.
void updatePosition(int x,int y);
};
//Internal selection popup class.
class LevelEditorSelectionPopup;
//Internal actions popup class.
class LevelEditorActionsPopup;
class CommandManager;
class AddRemoveGameObjectCommand;
class AddLinkCommand;
class RemoveLinkCommand;
class AddRemovePathCommand;
class RemovePathCommand;
class SetLevelPropertyCommand;
class SetScriptCommand;
class AddRemoveLayerCommand;
class SetLayerPropertyCommand;
class MoveToLayerCommand;
//The LevelEditor state, it's based on the Game state.
class LevelEditor: public Game{
friend class LevelEditorSelectionPopup;
friend class LevelEditorActionsPopup;
friend class AddRemoveGameObjectCommand;
friend class AddLinkCommand;
friend class RemoveLinkCommand;
friend class AddRemovePathCommand;
friend class RemovePathCommand;
friend class SetLevelPropertyCommand;
friend class SetScriptCommand;
friend class AddRemoveLayerCommand;
friend class SetLayerPropertyCommand;
friend class MoveToLayerCommand;
private:
//Boolean if the user isplaying/testing the level.
bool playMode;
//Enumaration containing the tools.
//SELECT: The select tool, for selecting/dragging blocks.
//ADD: For adding blocks.
//REMOVE: For removing blocks.
enum Tools{
SELECT,
ADD,
REMOVE,
NUMBER_TOOLS
};
//The tool the user has selected.
Tools tool;
//The toolbar surface.
SharedTexture toolbar;
//Rectangle the size and location of the toolbar on screen.
SDL_Rect toolbarRect;
//The selection popup (if any)
LevelEditorSelectionPopup* selectionPopup;
//The actions popup (if any)
LevelEditorActionsPopup* actionsPopup;
//Map used to get the GameObject that belongs to a certain GUIWindow.
map<GUIObject*,GameObject*> objectWindows;
//Map which store the visibility of each scenery layers, "" (empty) means the Block layer
map<string, bool> layerVisibility;
//The selected layer, "" (empty) means the Block layer
string selectedLayer;
//Vector containing pointers to the selected GameObjects.
vector<GameObject*> selection;
//The selection square.
SharedTexture selectionMark;
//A circle at the location of moving positions in configure mode.
SharedTexture movingMark;
//Texture showing the movement speed.
CachedTexture<int> movementSpeedTexture;
//Texture showing the pause time
CachedTexture<int> pauseTimeTexture;
//GUI image.
SharedTexture bmGUI;
//Texture containing the text "Toolbox"
TexturePtr toolboxText;
//Keeps track of commands for undo and redo.
CommandManager* commandManager;
//The current type of block to place in Add mode.
int currentType;
std::array<TexturePtr,TYPE_MAX> typeTextTextures;
std::array<TexturePtr,static_cast<size_t>(ToolTips::TooltipMax)> tooltipTextures;
CachedTexture<std::string> undoTooltipTexture;
CachedTexture<std::string> redoTooltipTexture;
std::map<std::string, TexturePtr> cachedTextTextures;
TexturePtr& getCachedTextTexture(SDL_Renderer& renderer, const std::string& text);
//Boolean if the tool box is displayed.
bool toolboxVisible;
//The rect of tool box tip.
SDL_Rect toolboxRect;
//The first item in tool box.
int toolboxIndex;
//Boolean if the shift button is pressed.
bool pressedShift;
//Boolean if the left mouse button is pressed.
bool pressedLeftMouse;
//Boolean if the mouse is dragged. (Left button pressed and moved)
bool dragging;
//The camera x velocity.
int cameraXvel;
int cameraYvel;
//SDL_Rect used to store the camera's location when entering playMode.
SDL_Rect cameraSave;
//Integer indicating if the selection is dragged and the drag mode.
// -1: not dragged
// 4: dragged
// 0,1,2,3,5,6,7,8: resizing the dragCenter
// 012
// 3x5
// 678
int selectionDrag;
//Pointer to the gameobject that's the center of the drag.
GameObject* dragCenter;
// The drag start position which is used when dragging blocks
SDL_Point dragSrartPosition;
//Integer containing a unique id.
//Everytime a new id is needed it will increase by one.
unsigned int currentId;
typedef map<Block*, vector<GameObject*> > Triggers;
//Vector containing the trigger GameObjects.
Triggers triggers;
//Boolean used in configure mode when linking triggers with their targets.
bool linking;
//Pointer to the trigger that's is being linked.
Block* linkingTrigger;
//Vector containing the moving GameObjects.
map<Block*,vector<MovingPosition> > movingBlocks;
//Integer containing the speed the block is moving for newly added blocks. 1 movingSpeed = 0.1 pixel/frame = 0.08 block/s
//The movingSpeed is capped at 125 (10 block/s).
int movingSpeed;
//The pause time for path edit if the current point is equal to the previous time. 1 pauseTime = 1 frame = 0.04s
int pauseTime;
//Boolean used in configure mode when configuring moving blocks.
bool moving;
//Another boolean used in configure mode when configuring moving blocks.
bool pauseMode;
//Pointer to the moving block that's is being configured.
Block* movingBlock;
//Value used for placing the Movespeed label
int movingSpeedWidth;
//The clipboard.
vector<map<string,string> > clipboard;
//String containing the levelTheme.
std::string levelTheme;
//String containing the levelMusic.
std::string levelMusic;
//Integer containing the button of which a tool tip should be shown.
int tooltip;
//GUI event handling is done here.
void GUIEventCallback_OnEvent(ImageManager&, SDL_Renderer&, std::string name,GUIObject* obj,int eventType);
//Method for deleting a GUIWindow.
//NOTE: This function checks for the presence of the window in the GUIObjectRoot and objectWindows map.
//window: Pointer to the GUIWindow.
void destroyWindow(GUIObject* window);
//Method that will let you configure the levelSettings.
void levelSettings(ImageManager& imageManager,SDL_Renderer &renderer);
//Method used to save the level.
//fileName: Thge filename to write the level to.
void saveLevel(string fileName);
//Method used to convert a given x and y to snap to grid.
//x: Pointer to the x location.
//y: Pointer to the y location.
void snapToGrid(int* x,int* y);
//Method used to check if the cursor is near the border of screen and we should move the camera.
//This method will check if the mouse is near a screen edge.
//r: An array of SDL_Rect, does nothing if mouse inside these rectange(s).
//count: Number of rectangles.
//If so it will move the camera.
void setCamera(const SDL_Rect* r,int count);
public:
//Array containing the ids of different block types in a wanted order
//Maybe also useful to disable deprecated block types in the editor
//PLEASE NOTE: Must be updated for new block types
//Ordered for Edward Liis proposal:
//Normal->Shadow->Spikes->Fragile
//Normal moving->Shadow moving->Moving spikes
//Conveyor belt->Shadow conveyor belt
//Button->Switch->Portal->Swap->Checkpoint->Notification block
//Player start->Shadow start->Exit
//Collectable->Pushable
static const int EDITOR_ORDER_MAX=20;
static const int editorTileOrder[EDITOR_ORDER_MAX];
//Array containing the names of available scenery blocks
std::vector<std::string> sceneryBlockNames;
// get the number of available blocks depending on the selected layer
int getEditorOrderMax() const {
if (selectedLayer.empty()) return EDITOR_ORDER_MAX;
return sceneryBlockNames.size() + 1; // the added one is for custom scenery block
}
protected:
//Inherits the function loadLevelFromNode from Game class.
virtual void loadLevelFromNode(ImageManager& imageManager, SDL_Renderer& renderer, TreeStorageNode* obj, const std::string& fileName) override;
public:
//Constructor.
LevelEditor(SDL_Renderer &renderer, ImageManager &imageManager);
//Destructor.
~LevelEditor();
//Method that will reset some default values.
void reset();
//Inherited from Game(State).
void handleEvents(ImageManager& imageManager, SDL_Renderer& renderer) override;
void logic(ImageManager& imageManager, SDL_Renderer& renderer) override;
void render(ImageManager& imageManager, SDL_Renderer& renderer) override;
void resize(ImageManager& imageManager, SDL_Renderer& renderer) override;
//Method used to draw the currentType on the placement surface.
//This will only be called when the tool is ADD.
void showCurrentObject(SDL_Renderer &renderer);
//Method used to draw the selection that's being dragged.
void showSelectionDrag(SDL_Renderer &renderer);
//Method used to draw configure tool specific things like moving positions, teleport lines.
void showConfigure(SDL_Renderer &renderer);
//Method that will render the HUD.
//It will be rendered after the placement suface but before the toolbar.
void renderHUD(SDL_Renderer &renderer);
//Method called after loading a level.
//It will fill the triggers vector.
void postLoad();
//Event that is invoked when there's a mouse click on an object.
//obj: Pointer to the GameObject clicked on.
//selected: Boolean if the GameObject that has been clicked on was selected.
void onClickObject(GameObject* obj,bool selected);
//Event that is invoked when there's a right mouse button click on an object.
//obj: Pointer to the GameObject clicked on.
//selected: Boolean if the GameObject that has been clicked on was selected.
void onRightClickObject(ImageManager& imageManager, SDL_Renderer& renderer, GameObject* obj, bool);
//Event that is invoked when there's a mouse click but not on any object.
//x: The x location of the click on the game field (+= camera.x).
//y: The y location of the click on the game field (+= camera.y).
void onClickVoid(int x,int y);
//Event that is invoked when there's a mouse right click but not on any object.
//x: The x location of the click on the game field (+= camera.x).
//y: The y location of the click on the game field (+= camera.y).
void onRightClickVoid(ImageManager& imageManager, SDL_Renderer& renderer, int x,int y);
//Event that is invoked when the dragging starts.
//x: The x location the drag started. (mouse.x+camera.x)
//y: The y location the drag started. (mouse.y+camera.y)
void onDragStart(int x,int y);
//Event that is invoked when the mouse is dragged.
//dx: The relative x distance the mouse dragged.
//dy: The relative y distance the mouse dragged.
void onDrag(int dx,int dy);
//Event that is invoked when the mouse stopped dragging.
//x: The x location the drag stopped. (mouse.x+camera.x)
//y: The y location the drag stopped. (mouse.y+camera.y)
void onDrop(int x,int y);
//Event that is invoked when the camera is moved.
//dx: The relative x distance the camera moved.
//dy: The relative y distance the camera moved.
void onCameraMove(int dx,int dy);
//internal function called by onClickObject() and onClickVoid().
void addMovingPosition(int x,int y);
//Set dirty of selection popup
void selectionDirty();
//Deselect all blocks
void deselectAll();
//Save current level and show a notification dialog
void saveCurrentLevel(ImageManager& imageManager, SDL_Renderer& renderer);
// An internal function to determine new position in drag mode.
// Make sure selectionDrag=4 when calling this function.
void determineNewPosition(int& x, int& y);
// An internal function to determine new size in resize mode.
// Make sure selectionDrag=0,1,2,3,5,6,7,8 when calling this function.
void determineNewSize(int x, int y, SDL_Rect& r);
//Call this function to start test play.
void enterPlayMode();
void undo();
void redo();
//Get the GUI texture.
inline SharedTexture& getGuiTexture() {
return bmGUI;
}
//Get the play mode.
bool isPlayMode() const {
return playMode;
}
};
#endif
diff --git a/src/LevelPack.cpp b/src/LevelPack.cpp
index 7754557..2a14c79 100644
--- a/src/LevelPack.cpp
+++ b/src/LevelPack.cpp
@@ -1,637 +1,637 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LevelPack.h"
#include "Functions.h"
#include "FileManager.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "MD5.h"
#include <string.h>
#include <string>
#include <vector>
#include <fstream>
#include <algorithm>
#include <iostream>
using namespace std;
//This is a special TreeStorageNode which only load node name/value and attributes, early exists when meeting any subnodes.
//This is used for fast loading of levels during game startup.
class LoadAttributesOnlyTreeStorageNode : public TreeStorageNode {
public:
virtual ITreeStorageBuilder* newNode() override {
//Early exit.
return NULL;
}
};
LevelPack::LevelPack():currentLevel(0),loaded(false),levels(),customTheme(false){
//We need to set the pointer to the dictionaryManager to NULL.
dictionaryManager=NULL;
//The type of levelpack is determined in the loadLevels method, but 'fallback' is CUSTOM.
type=CUSTOM;
}
LevelPack::~LevelPack(){
//We call clear, since that already takes care of the deletion, including the dictionaryManager.
clear();
}
void LevelPack::clear(){
currentLevel=0;
loaded=false;
levels.clear();
levelpackDescription.clear();
levelpackPath.clear();
levelProgressFile.clear();
congratulationText.clear();
levelpackMusicList.clear();
//Also delete the dictionaryManager if it isn't null.
if(dictionaryManager){
delete dictionaryManager;
dictionaryManager=NULL;
}
}
bool LevelPack::loadLevels(const std::string& levelListFile){
//We're going to load a new levellist so first clean any existing levels.
clear();
//If the levelListFile is empty we have nothing to load so we return false.
if(levelListFile.empty()){
cerr<<"ERROR: No levellist file given."<<endl;
return false;
}
//Determine the levelpack type.
if(levelListFile.find(getDataPath())==0){
type=MAIN;
}else if(levelListFile.find(getUserPath(USER_DATA)+"levelpacks/")==0){
type=ADDON;
}else{
type=CUSTOM;
}
levelpackPath=pathFromFileName(levelListFile);
//Create input streams for the levellist file.
ifstream level(levelListFile.c_str());
if(!level){
cerr<<"ERROR: Can't load level list "<<levelListFile<<endl;
return false;
}
//Load the level list file.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(level,&obj,true)){
cerr<<"ERROR: Invalid file format of level list "<<levelListFile<<endl;
return false;
}
}
//Check for folders inside the levelpack folder.
{
//Get all the sub directories.
vector<string> v;
v=enumAllDirs(pathFromFileName(levelListFile),false);
//Check if there's a locale folder containing translations.
if(std::find(v.begin(),v.end(),"locale")!=v.end()){
//Folder is present so configure the levelDictionaryManager.
dictionaryManager=new tinygettext::DictionaryManager();
dictionaryManager->set_use_fuzzy(false);
dictionaryManager->add_directory(pathFromFileName(levelListFile)+"locale/");
dictionaryManager->set_charset("UTF-8");
dictionaryManager->set_language(tinygettext::Language::from_name(language));
}else{
dictionaryManager=NULL;
}
//Check for a theme folder.
if(std::find(v.begin(),v.end(),"theme")!=v.end()){
customTheme=true;
}
}
//Look for the name.
{
vector<string> &v=obj.attributes["name"];
if(!v.empty()){
levelpackName=v[0];
}else{
//Name is not defined so take the folder name.
levelpackName=pathFromFileName(levelListFile);
//Remove the last character '/'
levelpackName=levelpackName.substr(0,levelpackName.size()-1);
levelpackName=fileNameFromPath(levelpackName);
}
}
//Look for the description.
{
vector<string> &v=obj.attributes["description"];
if(!v.empty())
levelpackDescription=v[0];
}
//Look for the congratulation text.
{
vector<string> &v=obj.attributes["congratulations"];
if(!v.empty())
congratulationText=v[0];
}
//Look for the music list.
{
vector<string> &v=obj.attributes["musiclist"];
if(!v.empty())
levelpackMusicList=v[0];
}
//Loop through the level list entries.
for(unsigned int i=0;i<obj.subNodes.size();i++){
TreeStorageNode* obj1=obj.subNodes[i];
if(obj1==NULL)
continue;
if(!obj1->value.empty() && obj1->name=="levelfile"){
Level level;
level.file=obj1->value[0];
level.targetTime=0;
level.targetRecordings=0;
memset(level.md5Digest, 0, sizeof(level.md5Digest));
//The path to the file to open.
//NOTE: In this function we are always loading levels from a level pack, so levelpackPath is always used.
string levelFile=levelpackPath+level.file;
//Open the level file to retrieve the name and target time/recordings.
LoadAttributesOnlyTreeStorageNode obj;
POASerializer objSerializer;
if(objSerializer.loadNodeFromFile(levelFile.c_str(),&obj,true)){
//Get the name of the level.
vector<string>& v=obj.attributes["name"];
if(!v.empty())
level.name=v[0];
//If the name is empty then we set it to the file name.
if(level.name.empty())
level.name=fileNameFromPath(level.file);
//Get the target time of the level.
v=obj.attributes["time"];
if(!v.empty())
level.targetTime=atoi(v[0].c_str());
else
level.targetTime=-1;
//Get the target recordings of the level.
v=obj.attributes["recordings"];
if(!v.empty())
level.targetRecordings=atoi(v[0].c_str());
else
level.targetRecordings=-1;
}
//The default for locked is true, unless it's the first one.
level.locked=!levels.empty();
level.won=false;
level.time=-1;
level.recordings=-1;
//Add the level to the levels.
levels.push_back(level);
}
}
loaded=true;
return true;
}
void LevelPack::loadProgress(){
//Make sure that a levelProgressFile is set.
if(levelProgressFile.empty()){
levelProgressFile=getLevelProgressPath();
}
//Open the file.
ifstream levelProgress;
levelProgress.open(processFileName(this->levelProgressFile).c_str());
//Check if the file exists.
if(levelProgress){
//Now load the progress/statistics.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(levelProgress,&obj,true)){
cerr<<"ERROR: Invalid file format of level progress file."<<endl;
}
}
//Loop through the entries.
for(unsigned int i=0;i<obj.subNodes.size();i++){
TreeStorageNode* obj1=obj.subNodes[i];
if(obj1==NULL)
continue;
if(!obj1->value.empty() && obj1->name=="level"){
//We've found an entry for a level, now search the correct level.
Level* level=NULL;
for(unsigned int o=0;o<levels.size();o++){
if(obj1->value[0]==levels[o].file){
level=&levels[o];
break;
}
}
//Check if we found the level.
if(!level)
continue;
//Get the progress/statistics.
for(map<string,vector<string> >::iterator i=obj1->attributes.begin();i!=obj1->attributes.end();++i){
if(i->first=="locked"){
level->locked=(i->second[0]=="1");
}
if(i->first=="won"){
level->won=(i->second[0]=="1");
}
if(i->first=="time"){
level->time=(atoi(i->second[0].c_str()));
}
if(i->first=="recordings"){
level->recordings=(atoi(i->second[0].c_str()));
}
}
}
}
}
}
void LevelPack::saveLevels(const std::string& levelListFile){
//Get the fileName.
string levelListNew=levelListFile;
//Open an output stream.
ofstream level(levelListNew.c_str());
//Check if we can use the file.
if(!level){
cerr<<"ERROR: Can't save level list "<<levelListNew<<endl;
return;
}
//Storage node that will contain the data that should be written.
TreeStorageNode obj;
//Also store the name of the levelpack.
if(!levelpackName.empty())
obj.attributes["name"].push_back(levelpackName);
//Make sure that there's a description.
if(!levelpackDescription.empty())
obj.attributes["description"].push_back(levelpackDescription);
//Make sure that there's a congratulation text.
if(!congratulationText.empty())
obj.attributes["congratulations"].push_back(congratulationText);
//Make sure that there's a music list.
if (!levelpackMusicList.empty())
obj.attributes["musiclist"].push_back(levelpackMusicList);
//Add the levels to the file.
for(unsigned int i=0;i<levels.size();i++){
TreeStorageNode* obj1=new TreeStorageNode;
obj1->name="levelfile";
obj1->value.push_back(fileNameFromPath(levels[i].file));
obj1->value.push_back(levels[i].name);
obj.subNodes.push_back(obj1);
}
//Write the it away.
POASerializer objSerializer;
objSerializer.writeNode(&obj,level,false,true);
}
void LevelPack::updateLanguage(){
if(dictionaryManager!=NULL)
dictionaryManager->set_language(tinygettext::Language::from_name(language));
}
void LevelPack::addLevel(const string& levelFileName,int levelno){
//Fill in the details.
Level level;
if(type!=COLLECTION && !levelpackPath.empty() && levelFileName.compare(0,levelpackPath.length(),levelpackPath)==0){
level.file=fileNameFromPath(levelFileName);
}else{
level.file=levelFileName;
}
level.targetTime=0;
level.targetRecordings=0;
memset(level.md5Digest, 0, sizeof(level.md5Digest));
//Get the name of the level.
LoadAttributesOnlyTreeStorageNode obj;
POASerializer objSerializer;
if(objSerializer.loadNodeFromFile(levelFileName.c_str(),&obj,true)){
//Get the name of the level.
vector<string>& v=obj.attributes["name"];
if(!v.empty())
level.name=v[0];
//If the name is empty then we set it to the file name.
if(level.name.empty())
level.name=fileNameFromPath(levelFileName);
//Get the target time of the level.
v=obj.attributes["time"];
if(!v.empty())
level.targetTime=atoi(v[0].c_str());
else
level.targetTime=-1;
//Get the target recordings of the level.
v=obj.attributes["recordings"];
if(!v.empty())
level.targetRecordings=atoi(v[0].c_str());
else
level.targetRecordings=-1;
}
//Set if it should be locked or not.
level.won=false;
level.time=-1;
level.recordings=-1;
level.locked=levels.empty()?false:true;
//Check if the level should be at the end or somewhere in the middle.
if(levelno<0 || levelno>=int(levels.size())){
levels.push_back(level);
}else{
levels.insert(levels.begin()+levelno,level);
}
//NOTE: We set loaded to true.
loaded=true;
}
void LevelPack::moveLevel(unsigned int level1,unsigned int level2){
if(level1>=levels.size())
return;
if(level2>=levels.size())
return;
if(level1==level2)
return;
levels.insert(levels.begin()+level2,levels[level1]);
if(level2<=level1)
levels.erase(levels.begin()+level1+1);
else
levels.erase(levels.begin()+level1);
}
void LevelPack::saveLevelProgress(){
//Check if the levels are loaded and a progress file is given.
if(!loaded || levelProgressFile.empty())
return;
//Open the progress file.
ofstream levelProgress(processFileName(levelProgressFile).c_str());
if(!levelProgress)
return;
//Open an output stream.
TreeStorageNode node;
//Loop through the levels.
for(unsigned int o=0;o<levels.size();o++){
TreeStorageNode* obj=new TreeStorageNode;
node.subNodes.push_back(obj);
char s[64];
//Set the name of the node.
obj->name="level";
obj->value.push_back(levels[o].file);
//Set the values.
obj->attributes["locked"].push_back(levels[o].locked?"1":"0");
obj->attributes["won"].push_back(levels[o].won?"1":"0");
sprintf(s,"%d",levels[o].time);
obj->attributes["time"].push_back(s);
sprintf(s,"%d",levels[o].recordings);
obj->attributes["recordings"].push_back(s);
}
//Create a POASerializer and write away the leve node.
POASerializer objSerializer;
objSerializer.writeNode(&node,levelProgress,true,true);
}
const string& LevelPack::getLevelName(int level){
if(level<0)
level=currentLevel;
return levels[level].name;
}
const unsigned char* LevelPack::getLevelMD5(int level){
if(level<0)
level=currentLevel;
//Check if the md5Digest is not initialized.
bool notInitialized = true;
for (int i = 0; i < 16; i++) {
if (levels[level].md5Digest[i]) {
notInitialized = false;
break;
}
}
//Calculate md5Digest if needed.
if (notInitialized) {
string levelFile = getLevelFile(level);
TreeStorageNode obj;
POASerializer objSerializer;
if (objSerializer.loadNodeFromFile(levelFile.c_str(), &obj, true)) {
obj.name.clear();
obj.calcMD5(levels[level].md5Digest);
} else {
cerr << "ERROR: Failed to load file '" << levelFile << "' for calculating MD5" << endl;
//Fill in a fake MD5
for (int i = 0; i < 16; i++) {
levels[level].md5Digest[i] = 0xCC;
}
}
}
return levels[level].md5Digest;
}
void LevelPack::getLevelAutoSaveRecordPath(int level,std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
if(level<0)
level=currentLevel;
bestTimeFilePath.clear();
bestRecordingFilePath.clear();
//get level pack path.
- string levelpackPath=LevelPack::levelpackPath;
+ string levelpackPath = (type == COLLECTION ? std::string() : LevelPack::levelpackPath);
string s=levels[level].file;
//process level pack name
for(;;){
string::size_type lps=levelpackPath.find_last_of("/\\");
if(lps==string::npos){
break;
}else if(lps==levelpackPath.size()-1){
levelpackPath.resize(lps);
}else{
levelpackPath=levelpackPath.substr(lps+1);
break;
}
}
//profess file name
{
string::size_type lps=s.find_last_of("/\\");
if(lps!=string::npos) s=s.substr(lps+1);
}
//check if it's custom level
{
string path="%USER%/records/autosave/";
if(!levelpackPath.empty()){
path+=levelpackPath;
path+='/';
}
path=processFileName(path);
if(createPath) createDirectory(path.c_str());
s=path+s;
}
//calculate MD5
s+='-';
s += Md5::toString(getLevelMD5(level));
//over
bestTimeFilePath=s+"-best-time.mnmsrec";
bestRecordingFilePath=s+"-best-recordings.mnmsrec";
}
string LevelPack::getLevelProgressPath(){
if(levelProgressFile.empty()){
levelProgressFile="%USER%/progress/";
//Use the levelpack folder name instead of the levelpack name.
//NOTE: Remove the trailing slash.
string folderName=levelpackPath.substr(0,levelpackPath.size()-1);
folderName=fileNameFromPath(folderName);
//Depending on the levelpack type add a folder.
switch(type){
case MAIN:
levelProgressFile+="main/";
levelProgressFile+=folderName+".progress";
break;
case ADDON:
levelProgressFile+="addon/";
levelProgressFile+=folderName+".progress";
break;
case CUSTOM:
levelProgressFile+="custom/";
levelProgressFile+=folderName+".progress";
break;
case COLLECTION:
//NOTE: For collections we use their name since they don't have a folder.
//FIXME: Make sure the name contains legal characters.
levelProgressFile+=levelpackName+".progress";
break;
}
}
return levelProgressFile;
}
void LevelPack::setLevelName(unsigned int level,const std::string& name){
if(level<levels.size())
levels[level].name=name;
}
const string LevelPack::getLevelFile(int level){
if(level<0)
level=currentLevel;
string levelFile;
if(type!=COLLECTION)
levelFile=levelpackPath+levels[level].file;
else
levelFile=levels[level].file;
return levelFile;
}
const string& LevelPack::getLevelpackPath(){
return levelpackPath;
}
struct LevelPack::Level* LevelPack::getLevel(int level){
if(level<0)
return &levels[currentLevel];
return &levels[level];
}
void LevelPack::resetLevel(int level){
if(level<0)
level=currentLevel;
//Set back to default.
levels[level].locked=(level!=0);
levels[level].won=false;
levels[level].time=-1;
levels[level].recordings=-1;
}
void LevelPack::nextLevel(){
currentLevel++;
}
bool LevelPack::getLocked(unsigned int level){
return levels[level].locked;
}
void LevelPack::setCurrentLevel(unsigned int level){
currentLevel=level;
}
void LevelPack::setLocked(unsigned int level,bool locked){
levels[level].locked=locked;
}
void LevelPack::swapLevel(unsigned int level1,unsigned int level2){
if(level1<levels.size()&&level2<levels.size()){
swap(levels[level1],levels[level2]);
}
}
void LevelPack::removeLevel(unsigned int level){
if(level<levels.size()){
levels.erase(levels.begin()+level);
}
}
diff --git a/src/LevelPlaySelect.cpp b/src/LevelPlaySelect.cpp
index 251c0b7..cfae54a 100644
--- a/src/LevelPlaySelect.cpp
+++ b/src/LevelPlaySelect.cpp
@@ -1,503 +1,503 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LevelPlaySelect.h"
#include "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "LevelSelect.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
#include "InputManager.h"
#include "ThemeManager.h"
#include "SoundManager.h"
#include "StatisticsManager.h"
#include "Game.h"
#include <stdio.h>
#include <string>
#include <sstream>
#include <iostream>
/////////////////////LEVEL SELECT/////////////////////
LevelPlaySelect::LevelPlaySelect(ImageManager& imageManager, SDL_Renderer& renderer)
:LevelSelect(imageManager,renderer,_("Select Level")),
levelInfoRender(imageManager,renderer,getDataPath(),*fontText,objThemes.getTextColor(false)){
//Load the play button if needed.
playButtonImage=imageManager.loadTexture(getDataPath()+"gfx/playbutton.png", renderer);
//Create the gui.
createGUI(imageManager,renderer, true);
//Show level list
refresh(imageManager,renderer);
}
LevelPlaySelect::~LevelPlaySelect(){
play=NULL;
//Clear the selected level.
if(selectedNumber!=NULL){
delete selectedNumber;
selectedNumber=NULL;
}
}
void LevelPlaySelect::createGUI(ImageManager& imageManager,SDL_Renderer &renderer, bool initial){
//Create the play button.
if(initial){
play=new GUIButton(imageManager,renderer,SCREEN_WIDTH-240,SCREEN_HEIGHT-60,240,32,_("Play"));
}else{
play->left=SCREEN_WIDTH-240;
play->top=SCREEN_HEIGHT-60;
}
play->name="cmdPlay";
play->eventCallback=this;
play->enabled=false;
if(initial)
GUIObjectRoot->addChild(play);
}
void LevelPlaySelect::refresh(ImageManager& imageManager, SDL_Renderer& renderer, bool /*change*/){
const int m=levels->getLevelCount();
numbers.clear();
levelInfoRender.resetText(renderer, *fontText, objThemes.getTextColor(false));
//Create the non selected number.
if (selectedNumber == NULL){
selectedNumber = new Number(imageManager, renderer);
}
SDL_Rect box={40,SCREEN_HEIGHT-130,50,50};
selectedNumber->init(renderer," ",box);
selectedNumber->setLocked(true);
selectedNumber->setMedal(0);
bestTimeFilePath.clear();
bestRecordingFilePath.clear();
//Disable the play button.
play->enabled=false;
for(int n=0; n<m; n++){
numbers.emplace_back(imageManager, renderer);
}
for(int n=0; n<m; n++){
SDL_Rect box={(n%LEVELS_PER_ROW)*64+static_cast<int>(SCREEN_WIDTH*0.2)/2,(n/LEVELS_PER_ROW)*64+184,0,0};
numbers[n].init(renderer,n,box);
- numbers[n].setLocked(levels->getLocked(n));
- int medal=levels->getLevel(n)->won;
+ numbers[n].setLocked(n>0 && levels->getLocked(n));
+ int medal=levels->getLevel(n)->won?1:0;
if(medal){
- if(levels->getLevel(n)->targetTime<0 || levels->getLevel(n)->time<=levels->getLevel(n)->targetTime)
+ const int targetTime = levels->getLevel(n)->targetTime;
+ const int targetRecordings = levels->getLevel(n)->targetRecordings;
+ const int time = levels->getLevel(n)->time;
+ const int recordings = levels->getLevel(n)->recordings;
+ if (time >= 0 && (targetTime < 0 || time <= targetTime))
medal++;
- if(levels->getLevel(n)->targetRecordings<0 || levels->getLevel(n)->recordings<=levels->getLevel(n)->targetRecordings)
+ if (recordings >= 0 && (targetRecordings < 0 || recordings <= targetRecordings))
medal++;
}
numbers[n].setMedal(medal);
}
if(m>LEVELS_DISPLAYED_IN_SCREEN){
levelScrollBar->maxValue=(m-LEVELS_DISPLAYED_IN_SCREEN+(LEVELS_PER_ROW-1))/LEVELS_PER_ROW;
levelScrollBar->visible=true;
}else{
levelScrollBar->maxValue=0;
levelScrollBar->visible=false;
}
if (levels->levelpackPath == LEVELS_PATH || levels->levelpackPath == CUSTOM_LEVELS_PATH)
levelpackDescription->caption = _("Individual levels which are not contained in any level packs");
else if (!levels->levelpackDescription.empty())
levelpackDescription->caption = _CC(levels->getDictionaryManager(), levels->levelpackDescription);
else
levelpackDescription->caption = "";
}
void LevelPlaySelect::selectNumber(ImageManager& imageManager, SDL_Renderer& renderer, unsigned int number,bool selected){
if (selected) {
if (number >= 0 && number < levels->getLevelCount()) {
levels->setCurrentLevel(number);
setNextState(STATE_GAME);
}
}else{
displayLevelInfo(imageManager, renderer,number);
}
}
void LevelPlaySelect::checkMouse(ImageManager &imageManager, SDL_Renderer &renderer){
int x,y;
//Get the current mouse location.
SDL_GetMouseState(&x,&y);
//Check if we should replay the record.
if(selectedNumber!=NULL){
SDL_Rect mouse={x,y,0,0};
if(!bestTimeFilePath.empty()){
SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-130,372,32};
if(pointOnRect(mouse, box)){
Game::recordFile=bestTimeFilePath;
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_GAME);
return;
}
}
if(!bestRecordingFilePath.empty()){
SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-98,372,32};
if(pointOnRect(mouse, box)){
Game::recordFile=bestRecordingFilePath;
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_GAME);
return;
}
}
}
//Call the base method from the super class.
LevelSelect::checkMouse(imageManager, renderer);
}
void LevelPlaySelect::displayLevelInfo(ImageManager& imageManager, SDL_Renderer& renderer, int number){
//Update currently selected level
if(selectedNumber==NULL){
selectedNumber=new Number(imageManager, renderer);
}
SDL_Rect box={40,SCREEN_HEIGHT-130,50,50};
if (number >= 0 && number < levels->getLevelCount()) {
selectedNumber->init(renderer, number, box);
selectedNumber->setLocked(false);
//Show level medal
- int medal = levels->getLevel(number)->won;
+ int medal = levels->getLevel(number)->won ? 1 : 0;
int time = levels->getLevel(number)->time;
int targetTime = levels->getLevel(number)->targetTime;
int recordings = levels->getLevel(number)->recordings;
int targetRecordings = levels->getLevel(number)->targetRecordings;
if (medal){
- if (targetTime < 0){
- medal = -1;
- } else{
- if (targetTime < 0 || time <= targetTime)
- medal++;
- if (targetRecordings < 0 || recordings <= targetRecordings)
- medal++;
- }
+ if (time >= 0 && (targetTime < 0 || time <= targetTime))
+ medal++;
+ if (recordings >= 0 && (targetRecordings < 0 || recordings <= targetRecordings))
+ medal++;
}
selectedNumber->setMedal(medal);
std::string levelTime;
std::string levelRecs;
//Show best time and recordings
if (medal){
char s[64];
- if (time > 0)
- if (targetTime>0)
- sprintf(s, "%-.2fs / %-.2fs", time / 40.0f, targetTime / 40.0f);
+ if (time >= 0)
+ if (targetTime>=0)
+ sprintf(s, "%-.2fs / %-.2fs", time / 40.0, targetTime / 40.0);
else
- sprintf(s, "%-.2fs / -", time / 40.0f);
+ sprintf(s, "%-.2fs / -", time / 40.0);
else
s[0] = '\0';
levelTime = s;
if (recordings >= 0)
if (targetRecordings >= 0)
sprintf(s, "%5d / %d", recordings, targetRecordings);
else
sprintf(s, "%5d / -", recordings);
else
s[0] = '\0';
levelRecs = s;
} else{
levelTime = "- / -";
levelRecs = "- / -";
}
//Show the play button.
play->enabled = true;
//Check if there is auto record file
levels->getLevelAutoSaveRecordPath(number, bestTimeFilePath, bestRecordingFilePath, false);
if (!bestTimeFilePath.empty()){
FILE *f;
f = fopen(bestTimeFilePath.c_str(), "rb");
if (f == NULL){
bestTimeFilePath.clear();
} else{
fclose(f);
}
}
if (!bestRecordingFilePath.empty()){
FILE *f;
f = fopen(bestRecordingFilePath.c_str(), "rb");
if (f == NULL){
bestRecordingFilePath.clear();
} else{
fclose(f);
}
}
//Show level description
levelInfoRender.update(renderer, *fontText, objThemes.getTextColor(false),
_CC(levels->getDictionaryManager(), levels->getLevelName(number)), levelTime, levelRecs);
} else {
levelInfoRender.resetText(renderer, *fontText, objThemes.getTextColor(false));
selectedNumber->init(renderer, " ", box);
selectedNumber->setLocked(true);
selectedNumber->setMedal(0);
bestTimeFilePath.clear();
bestRecordingFilePath.clear();
//Disable the play button.
play->enabled = false;
}
}
void LevelPlaySelect::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//Call handleEvents() of base class.
LevelSelect::handleEvents(imageManager, renderer);
//Check if the cheat code is input which is used to skip locked level.
//NOTE: The cheat code is NOT in plain text, since we don't want you to find it out immediately.
//NOTE: If you type it wrong, please press a key which is NOT a-z before retype it (as the code suggests).
if (event.type == SDL_KEYDOWN) {
static Uint32 hash = 0;
if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z) {
Uint32 c = event.key.keysym.sym - SDLK_a + 1;
hash = hash * 1296096U + c;
if (hash == 498506457U) {
if (selectedNumber) {
int n = selectedNumber->getNumber();
if (n >= 0 && n < (int)numbers.size() - 1 && numbers[n + 1].getLocked()) {
//unlock the level temporarily
numbers[n + 1].setLocked(false);
//play a sound effect
getSoundManager()->playSound("hit");
//new achievement
statsMgr.newAchievement("cheat");
}
}
hash = 0;
}
} else {
hash = 0;
}
}
if (section == 3) {
//Check focus movement
if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN) || inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
isKeyboardOnly = true;
section2++;
} else if (inputMgr.isKeyDownEvent(INPUTMGR_UP) || inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
isKeyboardOnly = true;
section2--;
}
if (section2 > 3) section2 = 1;
else if (section2 < 1) section2 = 3;
//Check if enter is pressed
if (isKeyboardOnly && inputMgr.isKeyDownEvent(INPUTMGR_SELECT) && selectedNumber) {
int n = selectedNumber->getNumber();
if (n >= 0) {
switch (section2) {
case 1:
if (!bestTimeFilePath.empty()) {
Game::recordFile = bestTimeFilePath;
levels->setCurrentLevel(n);
setNextState(STATE_GAME);
}
break;
case 2:
if (!bestRecordingFilePath.empty()) {
Game::recordFile = bestRecordingFilePath;
levels->setCurrentLevel(n);
setNextState(STATE_GAME);
}
break;
case 3:
selectNumber(imageManager, renderer, n, true);
break;
}
}
}
}
}
void LevelPlaySelect::render(ImageManager& imageManager, SDL_Renderer &renderer){
//First let the levelselect render.
LevelSelect::render(imageManager,renderer);
int x,y,dy=0;
//Get the current mouse location.
SDL_GetMouseState(&x,&y);
if(levelScrollBar)
dy=levelScrollBar->value;
//Upper bound of levels we'd like to display.
y+=dy*64;
SDL_Rect mouse={x,y,0,0};
//Show currently selected level (if any)
if(selectedNumber!=NULL){
selectedNumber->show(renderer, 0);
//Only show the replay button if the level is completed (won).
if(selectedNumber->getNumber()>=0 && selectedNumber->getNumber()<levels->getLevelCount()) {
if(levels->getLevel(selectedNumber->getNumber())->won){
if(!bestTimeFilePath.empty()){
SDL_Rect r={0,0,32,32};
const SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-130,372,32};
if (isKeyboardOnly ? (section == 3 && section2 == 1) : pointOnRect(mouse, box)){
r.x = 32;
drawGUIBox(box.x, box.y, box.w, box.h, renderer, 0xFFFFFF40);
}
const SDL_Rect dstRect = {SCREEN_WIDTH-80,SCREEN_HEIGHT-130,r.w,r.h};
SDL_RenderCopy(&renderer,playButtonImage.get(),&r, &dstRect);
}
if(!bestRecordingFilePath.empty()){
SDL_Rect r={0,0,32,32};
const SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-98,372,32};
if (isKeyboardOnly ? (section == 3 && section2 == 2) : pointOnRect(mouse, box)){
r.x = 32;
drawGUIBox(box.x, box.y, box.w, box.h, renderer, 0xFFFFFF40);
}
const SDL_Rect dstRect = {SCREEN_WIDTH-80,SCREEN_HEIGHT-98,r.w,r.h};
SDL_RenderCopy(&renderer,playButtonImage.get(),&r, &dstRect);
}
}
}
levelInfoRender.render(renderer);
}
//Draw highlight for play button.
if (isKeyboardOnly && play && play->enabled) {
play->state = (section == 3 && section2 == 3) ? 1 : 0;
}
}
void LevelPlaySelect::renderTooltip(SDL_Renderer &renderer, unsigned int number, int dy){
if (!toolTip.name || toolTip.number != number) {
const int SLEN = 64;
char s[SLEN];
//Render the name of the level.
toolTip.name = textureFromText(renderer, *fontText, _CC(levels->getDictionaryManager(), levels->getLevelName(number)), objThemes.getTextColor(true));
toolTip.time=nullptr;
toolTip.recordings=nullptr;
toolTip.number=number;
//The time it took.
if(levels->getLevel(number)->time>0){
- SDL_snprintf(s,SLEN,"%-.2fs",levels->getLevel(number)->time/40.0f);
+ SDL_snprintf(s,SLEN,"%-.2fs",levels->getLevel(number)->time/40.0);
toolTip.time = textureFromText(renderer, *fontText, s, objThemes.getTextColor(true));
}
//The number of recordings it took.
if(levels->getLevel(number)->recordings>=0){
SDL_snprintf(s,SLEN,"%d",levels->getLevel(number)->recordings);
toolTip.recordings = textureFromText(renderer, *fontText, s, objThemes.getTextColor(true));
}
}
const SDL_Rect nameSize = rectFromTexture(*toolTip.name);
//Now draw a square the size of the three texts combined.
SDL_Rect r=numbers[number].box;
r.y-=dy*64;
if(toolTip.time && toolTip.recordings){
const int recW = textureWidth(*toolTip.recordings);
const int timeW = textureWidth(*toolTip.time);
r.w=(nameSize.w)>(25+timeW+40+recW)?(nameSize.w):(25+timeW+40+recW);
r.h=nameSize.h+5+20;
}else{
r.w=nameSize.w;
r.h=nameSize.h;
}
//Make sure the tooltip doesn't go outside the window.
if(r.y>SCREEN_HEIGHT-200){
r.y-=nameSize.h+4;
}else{
r.y+=numbers[number].box.h+2;
}
if(r.x+r.w>SCREEN_WIDTH-50)
r.x=SCREEN_WIDTH-50-r.w;
//Draw a rectange
Uint32 color=0xFFFFFFFF;
drawGUIBox(r.x-5,r.y-5,r.w+10,r.h+10,renderer,color);
//Calc the position to draw.
SDL_Rect r2=r;
//Now we render the name if the surface isn't null.
if(toolTip.name){
//Draw the name.
applyTexture(r2.x, r2.y, toolTip.name, renderer);
}
//Increase the height to leave a gap between name and stats.
r2.y+=30;
if(toolTip.time){
//Now draw the time.
applyTexture(r2.x,r2.y,levelInfoRender.timeIcon,renderer);
r2.x+=25;
applyTexture(r2.x, r2.y, toolTip.time, renderer);
r2.x+=textureWidth(*toolTip.time)+15;
}
if(toolTip.recordings){
//Now draw the recordings.
applyTexture(r2.x,r2.y,levelInfoRender.recordingsIcon,renderer);
r2.x+=25;
applyTexture(r2.x, r2.y, toolTip.recordings, renderer);
}
}
void LevelPlaySelect::resize(ImageManager &imageManager, SDL_Renderer &renderer){
//Let the LevelSelect do his stuff.
LevelSelect::resize(imageManager, renderer);
//Now create our gui again.
createGUI(imageManager,renderer, false);
}
void LevelPlaySelect::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Let the level select handle his GUI events.
LevelSelect::GUIEventCallback_OnEvent(imageManager,renderer,name,obj,eventType);
//Check for the play button.
if(name=="cmdPlay"){
if(selectedNumber!=NULL){
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_GAME);
}
}
}
diff --git a/src/Player.cpp b/src/Player.cpp
index 38fd151..3103e9f 100644
--- a/src/Player.cpp
+++ b/src/Player.cpp
@@ -1,1676 +1,1697 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Block.h"
#include "Player.h"
#include "Game.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "InputManager.h"
#include "SoundManager.h"
#include "StatisticsManager.h"
#include "MD5.h"
#include <iostream>
#include <SDL.h>
using namespace std;
#ifdef RECORD_FILE_DEBUG
string recordKeyPressLog,recordKeyPressLog_saved;
vector<SDL_Rect> recordPlayerPosition,recordPlayerPosition_saved;
#endif
//static internal array to store time of recent deaths for achievements
static Uint32 recentDeaths[10]={0};
static int loadAndDieTimes=0;
//static internal function to add recent deaths and update achievements
static inline void addRecentDeaths(Uint32 recentLoad){
//Get current time in ms.
//We added it by 5 seconds to avoid bug if you choose a level to play
//and die in 5 seconds after the game has startup.
Uint32 t=SDL_GetTicks()+5000;
for(int i=9;i>0;i--){
recentDeaths[i]=recentDeaths[i-1];
}
recentDeaths[0]=t;
//Update achievements
if(recentDeaths[4]+5000>t){
statsMgr.newAchievement("die5in5");
}
if(recentDeaths[9]+5000>t){
statsMgr.newAchievement("die10in5");
}
if(recentLoad+1000>t){
statsMgr.newAchievement("loadAndDie");
}
}
Player::Player(Game* objParent):xVelBase(0),yVelBase(0),objParent(objParent),recordSaved(false),
inAirSaved(false),isJumpSaved(false),canMoveSaved(false),holdingOtherSaved(false){
//Set the dimensions of the player.
//The size of the player is 21x40.
box.x=0;
box.y=0;
box.w=23;
box.h=40;
//Set his velocity to zero.
xVel=0;
yVel=0;
//Set the start position.
fx=0;
fy=0;
//Set some default values.
inAir=true;
isJump=false;
shadowCall=false;
shadow=false;
canMove=true;
holdingOther=false;
dead=false;
record=false;
downKeyPressed=false;
spaceKeyPressed=false;
recordIndex=-1;
#ifdef RECORD_FILE_DEBUG
recordKeyPressLog.clear();
recordKeyPressLog_saved.clear();
recordPlayerPosition.clear();
recordPlayerPosition_saved.clear();
#endif
objNotificationBlock=objCurrentStandSave=objLastStandSave=NULL;
//Some default values for animation variables.
direction=0;
jumpTime=0;
state=stateSaved=0;
//xVelSaved is used to store if there's a state saved or not.
xVelSaved=yVelSaved=0x80000000;
objCurrentStand=objLastStand=objLastTeleport=objShadowBlock=NULL;
}
Player::~Player(){
//Do nothing here
}
bool Player::isPlayFromRecord(){
return recordIndex>=0; // && recordIndex<(int)recordButton.size();
}
//get the game record object.
std::vector<int>* Player::getRecord(){
return &recordButton;
}
#ifdef RECORD_FILE_DEBUG
string& Player::keyPressLog(){
return recordKeyPressLog;
}
vector<SDL_Rect>& Player::playerPosition(){
return recordPlayerPosition;
}
#endif
//play the record.
void Player::playRecord(){
recordIndex=0;
}
void Player::spaceKeyDown(class Shadow* shadow){
//Start recording or stop, depending on the recording state.
if(record==false){
//We start recording.
if(shadow->called==true){
//The shadow is still busy so first stop him before we can start recording.
shadowCall=false;
shadow->called=false;
shadow->playerButton.clear();
}else if(!dead){
//Check if shadow is dead.
if(shadow->dead){
//Show tooltip.
//Just reset the countdown (the shadow's jumptime).
shadow->jumpTime=80;
//Play the error sound.
getSoundManager()->playSound("error");
}else{
//The shadow isn't moving and both player and shadow aren't dead so start recording.
record=true;
//We start a recording meaning we need to increase recordings by one.
objParent->recordings++;
//Update statistics.
if(!dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
statsMgr.recordTimes++;
if(statsMgr.recordTimes==100) statsMgr.newAchievement("record100");
if(statsMgr.recordTimes==1000) statsMgr.newAchievement("record1k");
}
}
}
}else{
//The player is recording so stop recording and call the shadow.
record=false;
shadowCall=true;
}
}
void Player::handleInput(class Shadow* shadow){
//Check if we should read the input from record file.
//Actually, we read input from record file in
//another function shadowSetState.
bool readFromRecord=false;
if(recordIndex>=0 && recordIndex<(int)recordButton.size()) readFromRecord=true;
if(!readFromRecord){
//Reset horizontal velocity.
xVel=0;
if(inputMgr.isKeyDown(INPUTMGR_RIGHT)){
//Walking to the right.
xVel+=7;
}
if(inputMgr.isKeyDown(INPUTMGR_LEFT)){
//Walking to the left.
if(xVel!=0 && !dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
//Horizontal confusion achievement :)
statsMgr.newAchievement("horizontal");
}
xVel-=7;
}
//Check if the action key has been released.
if(!inputMgr.isKeyDown(INPUTMGR_ACTION)){
//It has so downKeyPressed can't be true.
downKeyPressed=false;
}
/*
//Don't reset spaceKeyPressed or when you press the space key
//and release another key then the bug occurs. (ticket #44)
if(event.type==SDL_KEYUP || !inputMgr.isKeyDown(INPUTMGR_SPACE)){
spaceKeyPressed=false;
}*/
}
//Check if a key is pressed (down).
if(inputMgr.isKeyDownEvent(INPUTMGR_JUMP) && !readFromRecord){
//The up key, if we aren't in the air we start jumping.
//Fixed a potential bug
if(!inAir && !isJump){
#ifdef RECORD_FILE_DEBUG
char c[64];
sprintf(c,"[%05d] Jump key pressed\n",objParent->time);
cout<<c;
recordKeyPressLog+=c;
#endif
isJump=true;
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_SPACE) && !readFromRecord){
//Fixed a potential bug
if(!spaceKeyPressed){
#ifdef RECORD_FILE_DEBUG
char c[64];
sprintf(c,"[%05d] Space key pressed\n",objParent->time);
cout<<c;
recordKeyPressLog+=c;
#endif
spaceKeyDown(shadow);
spaceKeyPressed=true;
}
}else if(inputMgr.isKeyUpEvent(INPUTMGR_SPACE) && !readFromRecord){
if(record && getSettings()->getBoolValue("quickrecord")){
spaceKeyDown(shadow);
spaceKeyPressed=true;
}
}else if(record && !readFromRecord && inputMgr.isKeyDownEvent(INPUTMGR_CANCELRECORDING)){
//Cancel current recording
//Search the recorded button and clear the last space key press
int i=recordButton.size()-1;
for(;i>=0;i--){
if(recordButton[i] & PlayerButtonSpace){
recordButton[i] &= ~PlayerButtonSpace;
break;
}
}
if(i>=0){
//Clear the recording at the player's side.
playerButton.clear();
line.clear();
//reset the record flag
record=false;
//decrese the record count
objParent->recordings--;
}else{
cout<<"Failed to find last recording"<<endl;
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_ACTION)){
//Downkey is pressed.
//Fixed a potential bug
if(!downKeyPressed){
#ifdef RECORD_FILE_DEBUG
char c[64];
sprintf(c,"[%05d] Action key pressed\n",objParent->time);
cout<<c;
recordKeyPressLog+=c;
#endif
downKeyPressed=true;
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_SAVE)){
//F2 only works in the level editor.
if(!(dead || shadow->dead) && stateID==STATE_LEVEL_EDITOR){
//Save the state. (delayed)
if (objParent && !objParent->player.isPlayFromRecord() && !objParent->interlevel)
objParent->saveStateNextTime=true;
}
- }else if(inputMgr.isKeyDownEvent(INPUTMGR_LOAD) && !readFromRecord){
+ }else if(inputMgr.isKeyDownEvent(INPUTMGR_LOAD) && (!readFromRecord || objParent->interlevel)){
//F3 is used to load the last state.
- if(objParent)
- objParent->loadStateNextTime=true;
+ if (objParent && canLoadState()) {
+ recordIndex = -1;
+ objParent->loadStateNextTime = true;
+
+ //Also delete any gui (most likely the interlevel gui). Only in game mode.
+ if (GUIObjectRoot && stateID != STATE_LEVEL_EDITOR){
+ delete GUIObjectRoot;
+ GUIObjectRoot = NULL;
+ }
+
+ //And set interlevel to false.
+ objParent->interlevel = false;
+ }
}else if(inputMgr.isKeyDownEvent(INPUTMGR_SWAP)){
//F4 will swap the player and the shadow, but only in the level editor.
if(!(dead || shadow->dead) && stateID==STATE_LEVEL_EDITOR){
swapState(shadow);
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_TELEPORT)){
//F5 will revive and teleoprt the player to the cursor. Only works in the level editor.
//Shift+F5 teleports the shadow.
if(stateID==STATE_LEVEL_EDITOR){
//get the position of the cursor.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
if(inputMgr.isKeyDown(INPUTMGR_SHIFT)){
//teleports the shadow.
shadow->dead=false;
shadow->box.x=x;
shadow->box.y=y;
}else{
//teleports the player.
dead=false;
box.x=x;
box.y=y;
}
//play sound?
getSoundManager()->playSound("swap");
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_SUICIDE)){
//F12 is suicide and only works in the leveleditor.
if(stateID==STATE_LEVEL_EDITOR){
die();
shadow->die();
}
}
}
void Player::setLocation(int x,int y){
box.x=x;
box.y=y;
}
void Player::move(vector<Block*> &levelObjects,int lastX,int lastY){
//Only move when the player isn't dead.
//Fixed the bug that player/shadow can teleport or pull the switch even if died.
//FIXME: Don't know if there will be any side-effects.
if(dead) return;
//Pointer to a checkpoint.
Block* objCheckPoint=NULL;
//Pointer to a swap.
Block* objSwap=NULL;
//Set the objShadowBlock to NULL.
//Only for swapping to prevent the shadow from swapping in a shadow block.
objShadowBlock=NULL;
//Set the objNotificationBlock to NULL.
objNotificationBlock=NULL;
//NOTE: to fix bugs regarding player/shadow swap, we should first process collision of player/shadow
//then move them. The code is moved to Game::logic().
/*//Store the location.
int lastX=box.x;
int lastY=box.y;
collision(levelObjects);*/
bool canTeleport=true;
bool isTraveling=true;
// for checking the achievenemt that player and shadow come to exit simultaneously.
bool weWon = false;
//Now check the functional blocks.
for(unsigned int o=0;o<levelObjects.size();o++){
//Skip block which is not visible.
if (levelObjects[o]->queryProperties(GameObjectProperty_Flags, this) & 0x80000000) continue;
//Check for collision.
if(checkCollision(box,levelObjects[o]->getBox())){
//Now switch the type.
switch(levelObjects[o]->type){
case TYPE_CHECKPOINT:
{
//If we're not the shadow set the gameTip to Checkpoint.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_CHECKPOINT;
//And let objCheckPoint point to this object.
objCheckPoint=levelObjects[o];
break;
}
case TYPE_SWAP:
{
//If we're not the shadow set the gameTip to swap.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_SWAP;
//And let objSwap point to this object.
objSwap=levelObjects[o];
break;
}
case TYPE_EXIT:
{
//Make sure we're not in the leveleditor.
if(stateID==STATE_LEVEL_EDITOR)
break;
//Check to see if we have enough keys to finish the level
if(objParent->currentCollectables>=objParent->totalCollectables){
//Update achievements
if(!objParent->player.isPlayFromRecord() && !objParent->interlevel){
if(objParent->player.dead || objParent->shadow.dead){
//Finish the level with player or shadow died.
statsMgr.newAchievement("forget");
}
if(objParent->won && !weWon){ // This checks if somebody already hit the exit but we haven't hit the exit yet.
//Player and shadow come to exit simultaneously.
statsMgr.newAchievement("jit");
}
}
//We can't just handle the winning here (in the middle of the update cycle)/
//So set won in Game true.
objParent->won=true;
//We hit the exit.
weWon = true;
}
break;
}
case TYPE_PORTAL:
{
//Check if the teleport id isn't empty.
if(levelObjects[o]->id.empty()){
std::cerr<<"WARNING: Invalid teleport id!"<<std::endl;
canTeleport=false;
}
//If we're not the shadow set the gameTip to portal.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_PORTAL;
//Check if we can teleport and should (downkey -or- auto).
if(canTeleport && (downKeyPressed || (levelObjects[o]->queryProperties(GameObjectProperty_Flags,this)&1))){
canTeleport=false;
if(downKeyPressed || levelObjects[o]!=objLastTeleport){
//Loop the levelobjects again to find the destination.
for(unsigned int oo=o+1;;){
//We started at our index+1.
//Meaning that if we reach the end of the vector then we need to start at the beginning.
if(oo>=levelObjects.size())
oo-=(int)levelObjects.size();
//It also means that if we reach the same index we need to stop.
//If the for loop breaks this way then we have no succes.
if(oo==o){
//Couldn't teleport. We play the error sound only when the down key pressed.
if (downKeyPressed) {
getSoundManager()->playSound("error");
}
break;
}
//Check if the second (oo) object is a portal and is visible.
if (levelObjects[oo]->type == TYPE_PORTAL && (levelObjects[oo]->queryProperties(GameObjectProperty_Flags, this) & 0x80000000) == 0){
//Check the id against the destination of the first portal.
if(levelObjects[o]->destination==levelObjects[oo]->id){
//Get the destination location.
SDL_Rect r = levelObjects[oo]->getBox();
r.x += 5;
r.y += 2;
r.w = box.w;
r.h = box.h;
//Check if the destination location is blocked.
bool blocked = false;
for (auto ooo : levelObjects){
//Make sure to only check visible blocks.
if (ooo->queryProperties(GameObjectProperty_Flags, this) & 0x80000000)
continue;
//Make sure the object is solid for the player.
if (!ooo->queryProperties(GameObjectProperty_PlayerCanWalkOn, this))
continue;
//Check for collision.
if (checkCollision(r, ooo->getBox())) {
blocked = true;
break;
}
}
//Teleport only if the destination is not blocked.
if (!blocked) {
//Call the event.
objParent->broadcastObjectEvent(GameObjectEvent_OnToggle, -1, NULL, levelObjects[o]);
objLastTeleport = levelObjects[oo];
//Teleport the player.
box.x = r.x;
box.y = r.y;
//We don't count it to traveling distance.
isTraveling = false;
//Play the swap sound.
getSoundManager()->playSound("swap");
break;
}
}
}
//Increase oo.
oo++;
}
//Reset the down key pressed.
downKeyPressed = false;
}
}
break;
}
case TYPE_SWITCH:
{
//If we're not the shadow set the gameTip to switch.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_SWITCH;
//If the down key is pressed then invoke an event.
if(downKeyPressed){
//Play the animation.
levelObjects[o]->playAnimation();
//Play the toggle sound.
getSoundManager()->playSound("toggle");
//Update statistics.
if(!dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
statsMgr.switchTimes++;
//Update achievements
switch(statsMgr.switchTimes){
case 100:
statsMgr.newAchievement("switch100");
break;
case 1000:
statsMgr.newAchievement("switch1k");
break;
}
}
levelObjects[o]->onEvent(GameObjectEvent_OnPlayerInteraction);
}
break;
}
case TYPE_SHADOW_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
{
//This only applies to the player.
if(!shadow)
objShadowBlock=levelObjects[o];
break;
}
case TYPE_NOTIFICATION_BLOCK:
{
//This only applies to the player.
if(!shadow)
objNotificationBlock=levelObjects[o];
break;
}
case TYPE_COLLECTABLE:
{
//Check if collectable is active (if it's not it's equal to 1(inactive))
if((levelObjects[o]->queryProperties(GameObjectProperty_Flags, this) & 0x1) == 0) {
//Toggle an event
objParent->broadcastObjectEvent(GameObjectEvent_OnToggle,-1,NULL,levelObjects[o]);
//Increase the current number of collectables
objParent->currentCollectables++;
getSoundManager()->playSound("collect");
//Open exit(s)
if(objParent->currentCollectables>=objParent->totalCollectables){
for(unsigned int i=0;i<levelObjects.size();i++){
if(levelObjects[i]->type==TYPE_EXIT){
objParent->broadcastObjectEvent(GameObjectEvent_OnSwitchOn,-1,NULL,levelObjects[i]);
}
}
}
}
break;
}
}
//Now check for the spike property.
if(levelObjects[o]->queryProperties(GameObjectProperty_IsSpikes,this)){
//It is so get the collision box.
SDL_Rect r=levelObjects[o]->getBox();
//TODO: pixel-accuracy hit test.
//For now we shrink the box.
r.x+=2;
r.y+=2;
r.w-=4;
r.h-=4;
//Check collision, if the player collides then let him die.
if(checkCollision(box,r)){
die();
}
}
}
}
//Check if the player can teleport.
if(canTeleport)
objLastTeleport=NULL;
//Check the checkpoint pointer only if the downkey is pressed.
//new: don't save the game if playing game record
if (objParent != NULL && downKeyPressed && objCheckPoint != NULL && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
//Checkpoint thus save the state.
if(objParent->canSaveState()){
objParent->saveStateNextTime=true;
objParent->objLastCheckPoint=objCheckPoint;
}
}
//Check the swap pointer only if the down key is pressed.
if(objSwap!=NULL && downKeyPressed && objParent!=NULL){
//Now check if the shadow we're the shadow or not.
if(shadow){
if(!(dead || objParent->player.dead)){
//Check if the player isn't in front of a shadow block.
if(!objParent->player.objShadowBlock){
objParent->player.swapState(this);
objSwap->playAnimation();
//We don't count it to traveling distance.
isTraveling=false;
//NOTE: Statistics updated in swapState() function.
}else{
//We can't swap so play the error sound.
getSoundManager()->playSound("error");
}
}
}else{
if(!(dead || objParent->shadow.dead)){
//Check if the player isn't in front of a shadow block.
if(!objShadowBlock){
swapState(&objParent->shadow);
objSwap->playAnimation();
//We don't count it to traveling distance.
isTraveling=false;
//NOTE: Statistics updated in swapState() function.
}else{
//We can't swap so play the error sound.
getSoundManager()->playSound("error");
}
}
}
}
//Determine the correct theme state.
if(!dead){
//Set the direction depending on the velocity.
if(xVel>0)
direction=0;
else if(xVel<0)
direction=1;
//Check if the player is in the air.
if(!inAir){
//On the ground so check the direction and movement.
if(xVel>0){
if(appearance.currentStateName!="walkright"){
appearance.changeState("walkright");
}
}else if(xVel<0){
if(appearance.currentStateName!="walkleft"){
appearance.changeState("walkleft");
}
}else if(xVel==0){
if(direction==1){
appearance.changeState("standleft");
}else{
appearance.changeState("standright");
}
}
}else{
//Check for jump appearance (inAir).
if(direction==1){
if(yVel>0){
if(appearance.currentStateName!="fallleft")
appearance.changeState("fallleft");
}else{
if(appearance.currentStateName!="jumpleft")
appearance.changeState("jumpleft");
}
}else{
if(yVel>0){
if(appearance.currentStateName!="fallright")
appearance.changeState("fallright");
}else{
if(appearance.currentStateName!="jumpright")
appearance.changeState("jumpright");
}
}
}
}
//Update traveling distance statistics.
if(isTraveling && (lastX!=box.x || lastY!=box.y) && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
float dx=float(lastX-box.x),dy=float(lastY-box.y);
float d0=statsMgr.playerTravelingDistance+statsMgr.shadowTravelingDistance,
d=sqrtf(dx*dx+dy*dy)/50.0f;
if(shadow) statsMgr.shadowTravelingDistance+=d;
else statsMgr.playerTravelingDistance+=d;
//Update achievement
d+=d0;
if(d0<=100.0f && d>=100.0f) statsMgr.newAchievement("travel100");
if(d0<=1000.0f && d>=1000.0f) statsMgr.newAchievement("travel1k");
if(d0<=10000.0f && d>=10000.0f) statsMgr.newAchievement("travel10k");
if(d0<=42195.0f && d>=42195.0f) statsMgr.newAchievement("travel42k");
}
//Reset the downKeyPressed flag.
downKeyPressed=false;
}
void Player::collision(vector<Block*> &levelObjects, Player* other){
//Only move when the player isn't dead.
if(dead)
return;
//First sort out the velocity.
//NOTE: This is the temporary xVel which takes canMove into consideration.
//This shadows Player::xVel.
const int xVel = canMove ? this->xVel : 0;
//Add gravity acceleration to the vertical velocity.
if(isJump)
jump();
if(inAir==true){
yVel+=1;
//Cap fall speed to 13.
if(yVel>13)
yVel=13;
}
Block* baseBlock=NULL;
if(objCurrentStand != NULL) {
baseBlock=objCurrentStand;
} else if(other && other->holdingOther) {
//NOTE: this actually CAN happen, e.g. when player is holding shadow and the player is going to jump
//assert(other->objCurrentStand != NULL);
baseBlock=other->objCurrentStand;
}
if(baseBlock!=NULL){
//Now get the velocity and delta of the object the player is standing on.
SDL_Rect v=baseBlock->getBox(BoxType_Velocity);
SDL_Rect delta=baseBlock->getBox(BoxType_Delta);
switch(baseBlock->type){
//For conveyor belts the velocity is transfered.
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
xVelBase=v.x;
break;
//In other cases, such as player on shadow, player on crate. the change in x position must be considered.
default:
{
if(delta.x != 0)
xVelBase+=delta.x;
}
break;
}
//NOTE: Only copy the velocity of the block when moving down.
//Upwards is automatically resolved before the player is moved.
if(delta.y>0){
//Fixes the jitters when the player is on a pushable block on a downward moving box.
//NEW FIX: the squash bug. The following line of code is commented and change 'v' to 'delta'.
//box.y+=delta.y;
yVelBase=delta.y;
}
else
yVelBase=0;
}
//Set the object the player is currently standing to NULL.
objCurrentStand=NULL;
//Store the location of the player.
int lastX=box.x;
int lastY=box.y;
//An array that will hold all the GameObjects that are involved in the collision/movement.
vector<Block*> objects;
//All the blocks have moved so if there's collision with the player, the block moved into him.
for(unsigned int o=0;o<levelObjects.size();o++){
//Make sure to only check visible blocks.
if (levelObjects[o]->queryProperties(GameObjectProperty_Flags, this) & 0x80000000)
continue;
//Make sure the object is solid for the player.
if(!levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,this))
continue;
//Check for collision.
if(checkCollision(box,levelObjects[o]->getBox()))
objects.push_back(levelObjects[o]);
}
//There was collision so try to resolve it.
if(!objects.empty()){
//FIXME: When multiple moving blocks are overlapping the player can be "bounced" off depending on the block order.
for(unsigned int o=0;o<objects.size();o++){
SDL_Rect r=objects[o]->getBox();
SDL_Rect delta=objects[o]->getBox(BoxType_Delta);
//Check on which side of the box the player is.
if(delta.x!=0){
if(delta.x>0){
//Move the player right if necessary.
if((r.x+r.w)-box.x<=delta.x && box.x<r.x+r.w)
box.x=r.x+r.w;
}else{
//Move the player left if necessary.
if((box.x+box.w)-r.x<=-delta.x && box.x>r.x-box.w)
box.x=r.x-box.w;
}
}
if(delta.y!=0){
if(delta.y>0){
//Move the player down if necessary.
if((r.y+r.h)-box.y<=delta.y && box.y<r.y+r.h)
box.y=r.y+r.h;
}else{
//Move the player up if necessary.
if((box.y+box.h)-r.y<=-delta.y && box.y>r.y-box.h)
box.y=r.y-box.h;
}
}
}
//Check if the player is squashed.
for(unsigned int o=0;o<levelObjects.size();o++){
//Make sure the object is visible.
if (levelObjects[o]->queryProperties(GameObjectProperty_Flags, this) & 0x80000000)
continue;
//Make sure the object is solid for the player.
if(!levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,this))
continue;
if(checkCollision(box,levelObjects[o]->getBox())){
//The player is squashed so first move him back.
box.x=lastX;
box.y=lastY;
//Update statistics.
if(!dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
if(shadow) statsMgr.shadowSquashed++;
else statsMgr.playerSquashed++;
switch(statsMgr.playerSquashed+statsMgr.shadowSquashed){
case 1:
statsMgr.newAchievement("squash1");
break;
case 50:
statsMgr.newAchievement("squash50");
break;
}
}
//Now call the die method.
die();
return;
}
}
}
//Reuse the objects array, this time for blocks the player walks into.
objects.clear();
//Determine the collision frame.
SDL_Rect frame={box.x,box.y,box.w,box.h};
//Keep the horizontal movement of the player in mind.
if(xVel+xVelBase>=0){
frame.w+=(xVel+xVelBase);
}else{
frame.x+=(xVel+xVelBase);
frame.w-=(xVel+xVelBase);
}
//And the vertical movement.
if(yVel+yVelBase>=0){
frame.h+=(yVel+yVelBase);
}else{
frame.y+=(yVel+yVelBase);
frame.h-=(yVel+yVelBase);
}
//Loop through the game objects.
for(unsigned int o=0; o<levelObjects.size(); o++){
//Make sure the block is visible.
if (levelObjects[o]->queryProperties(GameObjectProperty_Flags, this) & 0x80000000)
continue;
//Check if the player can collide with this game object.
if(!levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,this))
continue;
//Check if the block is inside the frame.
if(checkCollision(frame,levelObjects[o]->getBox()))
objects.push_back(levelObjects[o]);
}
//Horizontal pass.
if(xVel+xVelBase!=0){
box.x+=xVel+xVelBase;
for(unsigned int o=0;o<objects.size();o++){
SDL_Rect r=objects[o]->getBox();
if(!checkCollision(box,r))
continue;
//In case of a pushable block we give it velocity.
if(objects[o]->type==TYPE_PUSHABLE){
objects[o]->xVel+=(xVel+xVelBase)/2;
}
if(xVel+xVelBase>0){
//We came from the left so the right edge of the player must be less or equal than xVel+xVelBase.
if((box.x+box.w)-r.x<=xVel+xVelBase)
box.x=r.x-box.w;
}else{
//We came from the right so the left edge of the player must be greater or equal than xVel+xVelBase.
if(box.x-(r.x+r.w)>=xVel+xVelBase)
box.x=r.x+r.w;
}
}
}
//Some variables that are used in vertical movement.
Block* lastStand=NULL;
inAir=true;
//Vertical pass.
if(yVel+yVelBase!=0){
box.y+=yVel+yVelBase;
//Value containing the previous 'depth' of the collision.
int prevDepth=0;
for(unsigned int o=0;o<objects.size();o++){
SDL_Rect r=objects[o]->getBox();
if(!checkCollision(box,r))
continue;
//Now check how we entered the block (vertically or horizontally).
if(yVel+yVelBase>0){
//Calculate the number of pixels the player is in the block (vertically).
int depth=(box.y+box.h)-r.y;
//We came from the top so the bottom edge of the player must be less or equal than yVel+yVelBase.
if(depth<=yVel+yVelBase){
//NOTE: lastStand is handled later since the player can stand on only one block at the time.
//Check if there's already a lastStand.
if(lastStand){
//Since the player fell he will stand on the highest block, meaning the highest 'depth'.
if(depth>prevDepth){
lastStand=objects[o];
prevDepth=depth;
}else if(depth==prevDepth){
//Both blocks are at the same height so determine the block by the amount the player is standing on them.
SDL_Rect r=objects[o]->getBox();
int w=0;
if(box.x+box.w>r.x+r.w)
w=(r.x+r.w)-box.x;
else
w=(box.x+box.w)-r.x;
//Do the same for the other box.
r=lastStand->getBox();
int w2=0;
if(box.x+box.w>r.x+r.w)
w2=(r.x+r.w)-box.x;
else
w2=(box.x+box.w)-r.x;
//NOTE: It doesn't matter which block the player is on if they are both stationary.
SDL_Rect v=objects[o]->getBox(BoxType_Velocity);
SDL_Rect v2=lastStand->getBox(BoxType_Velocity);
//Either the have the same (vertical) velocity so most pixel standing on is the lastStand...
// ... OR one is moving slower down/faster up and that's the one the player is standing on.
if((v.y==v2.y && w>w2) || v.y<v2.y){
lastStand=objects[o];
prevDepth=depth;
}
}
}else{
//There isn't one so assume the current block for now.
lastStand=objects[o];
prevDepth=depth;
}
}
}else{
//We came from the bottom so the upper edge of the player must be greater or equal than yVel+yVelBase.
if(box.y-(r.y+r.h)>=yVel+yVelBase){
box.y=r.y+r.h;
yVel=0;
}
}
}
}
if(lastStand){
inAir=false;
yVel=1;
SDL_Rect r=lastStand->getBox();
box.y=r.y-box.h;
}
//Check if the player fell of the level, if so let him die but without animation.
if(box.y>LEVEL_HEIGHT)
die(false);
//Check if the player changed blocks, meaning stepped onto a block.
objCurrentStand=lastStand;
if(lastStand!=objLastStand){
//The player has changed block so call the playerleave event.
if(objLastStand)
objParent->broadcastObjectEvent(GameObjectEvent_PlayerLeave,-1,NULL,objLastStand);
//Set the new lastStand.
objLastStand=lastStand;
if(lastStand){
- //Call the walk on event of the laststand.
- objParent->broadcastObjectEvent(GameObjectEvent_PlayerWalkOn,-1,NULL,lastStand);
-
- //Bugfix for Fragile blocks.
- if(lastStand->type==TYPE_FRAGILE && !lastStand->queryProperties(GameObjectProperty_PlayerCanWalkOn,this)){
- inAir=true;
- isJump=false;
+ //NOTE: We partially revert this piece of code to that in commit 0072762,
+ //i.e. change the event GameObjectEvent_PlayerWalkOn from asynchronous back to synchronous,
+ //to fix the fragile block hit test bug when it is breaking.
+ //Hopefully it will not introduce bugs (e.g. bugs regarding dynamic add/delete of objects).
+
+ if (lastStand->type == TYPE_FRAGILE) {
+ //Call the walk on event of the laststand in a synchronous way.
+ lastStand->onEvent(GameObjectEvent_PlayerWalkOn);
+
+ //Bugfix for Fragile blocks.
+ if (!lastStand->queryProperties(GameObjectProperty_PlayerCanWalkOn, this)) {
+ inAir = true;
+ isJump = false;
+ }
+ } else {
+ //Call the walk on event of the laststand in an asynchronous way.
+ objParent->broadcastObjectEvent(GameObjectEvent_PlayerWalkOn, -1, NULL, lastStand);
}
}
}
//NOTE: The PlayerIsOn event must be handled here so that the script can change the location of a block without interfering with the collision detection.
//Handlingin it here also guarantees that this event will only be called once for one block per update.
if(objCurrentStand)
objParent->broadcastObjectEvent(GameObjectEvent_PlayerIsOn,-1,NULL,objCurrentStand);
//Reset the base velocity.
xVelBase=yVelBase=0;
canMove=true;
}
void Player::jump(int strength){
//Check if the player can jump.
if(inAir==false){
//Set the jump velocity.
yVel=-strength;
inAir=true;
isJump=false;
jumpTime++;
//Update statistics
if(!objParent->player.isPlayFromRecord() && !objParent->interlevel){
if(shadow) statsMgr.shadowJumps++;
else statsMgr.playerJumps++;
if(statsMgr.playerJumps+statsMgr.shadowJumps==1000) statsMgr.newAchievement("frog");
}
//Check if sound is enabled, if so play the jump sound.
getSoundManager()->playSound("jump");
}
}
void Player::show(SDL_Renderer& renderer){
//Check if we should render the recorded line.
//Only do this when we're recording and we're not the shadow.
if(shadow==false && record==true){
//FIXME: Adding an entry not in update but in render?
line.push_back(SDL_Rect());
line[line.size()-1].x=box.x+11;
line[line.size()-1].y=box.y+20;
//Loop through the line dots and draw them.
for(int l=0; l<(signed)line.size(); l++){
appearance.drawState("line",renderer,line[l].x-camera.x,line[l].y-camera.y);
}
}
//NOTE: We do logic here, because it's only needed by the appearance.
appearance.updateAnimation();
appearance.draw(renderer, box.x-camera.x, box.y-camera.y);
}
void Player::shadowSetState(){
int currentKey=0;
/*//debug
extern int block_test_count;
extern bool block_test_only;
if(SDL_GetKeyState(NULL)[SDLK_p]){
block_test_count=recordButton.size();
}
if(block_test_count==(int)recordButton.size()){
block_test_only=true;
}*/
//Check if we should read the input from record file.
if(recordIndex>=0){ // && recordIndex<(int)recordButton.size()){
//read the input from record file
if(recordIndex<(int)recordButton.size()){
currentKey=recordButton[recordIndex];
recordIndex++;
}
//Reset horizontal velocity.
xVel=0;
if(currentKey&PlayerButtonRight){
//Walking to the right.
xVel+=7;
}
if(currentKey&PlayerButtonLeft){
//Walking to the left.
xVel-=7;
}
if(currentKey&PlayerButtonJump){
//The up key, if we aren't in the air we start jumping.
if(inAir==false){
isJump=true;
}else{
//Shouldn't go here
cout<<"Replay BUG"<<endl;
}
}
//check the down key
downKeyPressed=(currentKey&PlayerButtonDown)!=0;
//check the space key
if(currentKey&PlayerButtonSpace){
spaceKeyDown(&objParent->shadow);
}
}else{
//read the input from keyboard.
recordIndex=-1;
//Check for xvelocity.
if(xVel>0)
currentKey|=PlayerButtonRight;
if(xVel<0)
currentKey|=PlayerButtonLeft;
//Check for jumping.
if(isJump){
#ifdef RECORD_FILE_DEBUG
char c[64];
sprintf(c,"[%05d] Jump key recorded\n",objParent->time-1);
cout<<c;
recordKeyPressLog+=c;
#endif
currentKey|=PlayerButtonJump;
}
//Check if the downbutton is pressed.
if(downKeyPressed){
#ifdef RECORD_FILE_DEBUG
char c[64];
sprintf(c,"[%05d] Action key recorded\n",objParent->time-1);
cout<<c;
recordKeyPressLog+=c;
#endif
currentKey|=PlayerButtonDown;
}
if(spaceKeyPressed){
#ifdef RECORD_FILE_DEBUG
char c[64];
sprintf(c,"[%05d] Space key recorded\n",objParent->time-1);
cout<<c;
recordKeyPressLog+=c;
#endif
currentKey|=PlayerButtonSpace;
}
//Record it.
recordButton.push_back(currentKey);
}
#ifdef RECORD_FILE_DEBUG
if(recordIndex>=0){
if(recordIndex>0 && recordIndex<=int(recordPlayerPosition.size())/2){
SDL_Rect &r1=recordPlayerPosition[recordIndex*2-2];
SDL_Rect &r2=recordPlayerPosition[recordIndex*2-1];
if(r1.x!=box.x || r1.y!=box.y || r2.x!=objParent->shadow.box.x || r2.y!=objParent->shadow.box.y){
char c[192];
sprintf(c,"Replay ERROR [%05d] %d %d %d %d Expected: %d %d %d %d\n",
objParent->time-1,box.x,box.y,objParent->shadow.box.x,objParent->shadow.box.y,r1.x,r1.y,r2.x,r2.y);
cout<<c;
}
}
}else{
recordPlayerPosition.push_back(box);
recordPlayerPosition.push_back(objParent->shadow.box);
}
#endif
//reset spaceKeyPressed.
spaceKeyPressed=false;
//Only add an entry if the player is recording.
if(record){
//Add the action.
if(!dead && !objParent->shadow.dead){
playerButton.push_back(currentKey);
//Change the state.
state++;
}else{
//Either player or shadow is dead, stop recording.
playerButton.clear();
state=0;
record=false;
}
}
}
void Player::shadowGiveState(Shadow* shadow){
//Check if the player calls the shadow.
if(shadowCall==true){
//Clear any recording still with the shadow.
shadow->playerButton.clear();
//Loop the recorded moves and add them to the one of the shadow.
for(unsigned int s=0;s<playerButton.size();s++){
shadow->playerButton.push_back(playerButton[s]);
}
//Reset the state of both the player and the shadow.
stateReset();
shadow->stateReset();
//Clear the recording at the player's side.
playerButton.clear();
line.clear();
//Set shadowCall false
shadowCall=false;
//And let the shadow know that the player called him.
shadow->meCall();
}
}
void Player::stateReset(){
//Reset the state by setting it to 0.
state=0;
}
void Player::otherCheck(class Player* other){
//Now check if the player is on the shadow.
//First make sure they are both alive.
if(!dead && !other->dead){
//Get the box of the shadow.
SDL_Rect boxShadow=other->getBox();
//Check if the player is on top of the shadow.
if(checkCollision(box,boxShadow)==true){
//We have collision now check if the other is standing on top of you.
if(box.y+box.h<=boxShadow.y+13 && !other->inAir){
//Player is on shadow.
int yVelocity=yVel-1;
if(yVelocity>0){
//If the player is going to stand on the shadow for the first time, check if there are enough spaces for it.
if (!other->holdingOther) {
const SDL_Rect r = { box.x, boxShadow.y - box.h, box.w, box.h };
for (auto ooo : objParent->levelObjects){
//Make sure to only check visible blocks.
if (ooo->queryProperties(GameObjectProperty_Flags, this) & 0x80000000)
continue;
//Make sure the object is solid for the player.
if (!ooo->queryProperties(GameObjectProperty_PlayerCanWalkOn, this))
continue;
//Check for collision.
if (checkCollision(r, ooo->getBox())) {
//We are blocked hence we can't stand on it.
return;
}
}
}
box.y=boxShadow.y-box.h;
inAir=false;
canMove=false;
//Reset the vertical velocity.
yVel=2;
other->holdingOther=true;
other->appearance.changeState("holding");
//Change our own appearance to standing.
if(direction==1){
appearance.changeState("standleft");
}else{
appearance.changeState("standright");
}
//Set the velocity things.
objCurrentStand=NULL;
}
}else if(boxShadow.y+boxShadow.h<=box.y+13 && !inAir){
//Shadow is on player.
int yVelocity=other->yVel-1;
if(yVelocity>0){
//If the shadow is going to stand on the player for the first time, check if there are enough spaces for it.
if (!holdingOther) {
const SDL_Rect r = { boxShadow.x, box.y - boxShadow.h, boxShadow.w, boxShadow.h };
for (auto ooo : objParent->levelObjects){
//Make sure to only check visible blocks.
if (ooo->queryProperties(GameObjectProperty_Flags, other) & 0x80000000)
continue;
//Make sure the object is solid for the shadow.
if (!ooo->queryProperties(GameObjectProperty_PlayerCanWalkOn, other))
continue;
//Check for collision.
if (checkCollision(r, ooo->getBox())) {
//We are blocked hence we can't stand on it.
return;
}
}
}
other->box.y=box.y-boxShadow.h;
other->inAir=false;
other->canMove=false;
//Reset the vertical velocity of the other.
other->yVel=2;
holdingOther=true;
appearance.changeState("holding");
//Change our own appearance to standing.
if(other->direction==1){
other->appearance.changeState("standleft");
}else{
other->appearance.changeState("standright");
}
//Set the velocity things.
other->objCurrentStand=NULL;
}
}
}else{
holdingOther=false;
other->holdingOther=false;
}
}
}
SDL_Rect Player::getBox(){
return box;
}
void Player::setMyCamera(){
//Only change the camera when the player isn't dead.
if(dead)
return;
//Check if the level fit's horizontally inside the camera.
if(camera.w>LEVEL_WIDTH){
camera.x=-(camera.w-LEVEL_WIDTH)/2;
}else{
//Check if the player is halfway pass the halfright of the screen.
if(box.x>camera.x+(SCREEN_WIDTH/2+50)){
//It is so ease the camera to the right.
camera.x+=(box.x-camera.x-(SCREEN_WIDTH/2))>>4;
//Check if the camera isn't going too far.
if(box.x<camera.x+(SCREEN_WIDTH/2+50)){
camera.x=box.x-(SCREEN_WIDTH/2+50);
}
}
//Check if the player is halfway pass the halfleft of the screen.
if(box.x<camera.x+(SCREEN_WIDTH/2-50)){
//It is so ease the camera to the left.
camera.x+=(box.x-camera.x-(SCREEN_WIDTH/2))>>4;
//Check if the camera isn't going too far.
if(box.x>camera.x+(SCREEN_WIDTH/2-50)){
camera.x=box.x-(SCREEN_WIDTH/2-50);
}
}
//If the camera is too far to the left we set it to 0.
if(camera.x<0){
camera.x=0;
}
//If the camera is too far to the right we set it to the max right.
if(camera.x+camera.w>LEVEL_WIDTH){
camera.x=LEVEL_WIDTH-camera.w;
}
}
//Check if the level fit's vertically inside the camera.
if(camera.h>LEVEL_HEIGHT){
//We don't centre vertical because the bottom line of the level (deadly) will be mid air.
camera.y=-(camera.h-LEVEL_HEIGHT);
}else{
//Check if the player is halfway pass the lower half of the screen.
if(box.y>camera.y+(SCREEN_HEIGHT/2+50)){
//If is so ease the camera down.
camera.y+=(box.y-camera.y-(SCREEN_HEIGHT/2))>>4;
//Check if the camera isn't going too far.
if(box.y<camera.y+(SCREEN_HEIGHT/2+50)){
camera.y=box.y-(SCREEN_HEIGHT/2+50);
}
}
//Check if the player is halfway pass the upper half of the screen.
if(box.y<camera.y+(SCREEN_HEIGHT/2-50)){
//It is so ease the camera up.
camera.y+=(box.y-camera.y-(SCREEN_HEIGHT/2))>>4;
//Check if the camera isn't going too far.
if(box.y>camera.y+(SCREEN_HEIGHT/2-50)){
camera.y=box.y-(SCREEN_HEIGHT/2-50);
}
}
//If the camera is too far up we set it to 0.
if(camera.y<0){
camera.y=0;
}
//If the camera is too far down we set it to the max down.
if(camera.y+camera.h>LEVEL_HEIGHT){
camera.y=LEVEL_HEIGHT-camera.h;
}
}
}
void Player::reset(bool save){
//Set the location of the player to it's initial state.
box.x=fx;
box.y=fy;
//Reset back to default value.
inAir=true;
isJump=false;
shadowCall=false;
canMove=true;
holdingOther=false;
dead=false;
record=false;
downKeyPressed=false;
spaceKeyPressed=false;
//Some animation variables.
appearance.resetAnimation(save);
appearance.changeState("standright");
direction=0;
state=0;
xVel=0; //??? fixed a strange bug in game replay
yVel=0;
//Reset the gameObject pointers.
objCurrentStand=objLastStand=objLastTeleport=objNotificationBlock=objShadowBlock=NULL;
if(save)
objCurrentStandSave=objLastStandSave=NULL;
//Clear the recording.
line.clear();
playerButton.clear();
recordButton.clear();
recordIndex=-1;
#ifdef RECORD_FILE_DEBUG
recordKeyPressLog.clear();
recordPlayerPosition.clear();
#endif
if(save){
//xVelSaved is used to indicate if there's a state saved or not.
xVelSaved=0x80000000;
loadAndDieTimes=0;
}
}
void Player::saveState(){
//We can only save the state when the player isn't dead.
if(!dead){
boxSaved.x=box.x;
boxSaved.y=box.y;
xVelSaved=xVel;
yVelSaved=yVel;
inAirSaved=inAir;
isJumpSaved=isJump;
canMoveSaved=canMove;
holdingOtherSaved=holdingOther;
stateSaved=state;
//Let the appearance save.
appearance.saveAnimation();
//Save the lastStand and currentStand pointers.
objCurrentStandSave=objCurrentStand;
objLastStandSave=objLastStand;
//Save any recording stuff.
recordSaved=record;
playerButtonSaved=playerButton;
lineSaved=line;
//Save the record
savedRecordButton=recordButton;
#ifdef RECORD_FILE_DEBUG
recordKeyPressLog_saved=recordKeyPressLog;
recordPlayerPosition_saved=recordPlayerPosition;
#endif
//To prevent playing the sound twice, only the player can cause the sound.
if(!shadow)
getSoundManager()->playSound("checkpoint");
//We saved a new state so reset the counter
loadAndDieTimes=0;
}
}
void Player::loadState(){
//Check with xVelSaved if there's a saved state.
if(xVelSaved==int(0x80000000)){
//There isn't so reset the game to load the first initial state.
//NOTE: There's no need in removing the saved state since there is none.
reset(false);
return;
}
//Restore the saved values.
box.x=boxSaved.x;
box.y=boxSaved.y;
//xVel is set to 0 since it's saved counterpart is used to indicate a saved state.
xVel=0;
yVel=yVelSaved;
//Restore the saved values.
inAir=inAirSaved;
isJump=isJumpSaved;
canMove=canMoveSaved;
holdingOther=holdingOtherSaved;
dead=false;
record=false;
shadowCall=false;
state=stateSaved;
objCurrentStand=objCurrentStandSave;
objLastStand=objLastStandSave;
//Restore the appearance.
appearance.loadAnimation();
//Restore any recorded stuff.
record=recordSaved;
playerButton=playerButtonSaved;
line=lineSaved;
//Load the previously saved record
recordButton=savedRecordButton;
#ifdef RECORD_FILE_DEBUG
recordKeyPressLog=recordKeyPressLog_saved;
recordPlayerPosition=recordPlayerPosition_saved;
#endif
}
void Player::swapState(Player* other){
//We need to swap the values of the player with the ones of the given player.
swap(box.x,other->box.x);
swap(box.y,other->box.y);
swap(xVelBase, other->yVelBase);
swap(yVelBase, other->yVelBase);
swap(objCurrentStand, other->objCurrentStand);
//NOTE: xVel isn't there since it's used for something else.
swap(yVel,other->yVel);
swap(inAir,other->inAir);
swap(isJump,other->isJump);
swap(canMove,other->canMove);
swap(holdingOther,other->holdingOther);
swap(dead, other->dead);
//Also reset the state of the other.
other->stateReset();
//Play the swap sound.
getSoundManager()->playSound("swap");
//Update statistics.
if(!dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
if(objParent->time < objParent->recentSwap + FPS){
//Swap player and shadow twice in 1 senond.
statsMgr.newAchievement("quickswap");
}
objParent->recentSwap=objParent->time;
statsMgr.swapTimes++;
//Update achievements
switch(statsMgr.swapTimes){
case 100:
statsMgr.newAchievement("swap100");
break;
case 1000:
statsMgr.newAchievement("swap1k");
break;
}
}
}
bool Player::canSaveState(){
//We can only save the state if the player isn't dead.
return !dead;
}
bool Player::canLoadState(){
//We use xVelSaved to indicate if a state is saved or not.
return xVelSaved != int(0x80000000);
}
void Player::die(bool animation){
//Make sure the player isn't already dead.
if(!dead){
dead=true;
//If sound is enabled run the hit sound.
getSoundManager()->playSound("hit");
//Change the apearance to die (if animation is true).
if(animation){
if(direction==1){
appearance.changeState("dieleft");
}else{
appearance.changeState("dieright");
}
}
//Update statistics
if(!objParent->player.isPlayFromRecord() && !objParent->interlevel){
addRecentDeaths(objParent->recentLoad);
if(shadow) statsMgr.shadowDies++;
else statsMgr.playerDies++;
switch(statsMgr.playerDies+statsMgr.shadowDies){
case 1:
statsMgr.newAchievement("die1");
break;
case 50:
statsMgr.newAchievement("die50");
break;
case 1000:
statsMgr.newAchievement("die1000");
break;
}
if(canLoadState() && (++loadAndDieTimes)==100){
statsMgr.newAchievement("loadAndDie100");
}
if(objParent->player.dead && objParent->shadow.dead) statsMgr.newAchievement("doubleKill");
}
}
//We set the jumpTime to 80 when this is the shadow.
//That's the countdown for the "Your shadow has died." message.
if(shadow){
jumpTime=80;
}
}
diff --git a/src/Scenery.cpp b/src/Scenery.cpp
index 164758b..dc74470 100644
--- a/src/Scenery.cpp
+++ b/src/Scenery.cpp
@@ -1,396 +1,396 @@
/*
* Copyright (C) 2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me And My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "GameObjects.h"
#include "Scenery.h"
#include "Functions.h"
#include "LevelEditor.h"
#include "POASerializer.h"
#include <iostream>
#include <sstream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
#include "libs/tinyformat/tinyformat.h"
Scenery::Scenery(Game* objParent) :
GameObject(objParent),
xSave(0),
ySave(0),
dx(0),
dy(0),
themeBlock(NULL),
repeatMode(0)
{}
Scenery::Scenery(Game* objParent, int x, int y, int w, int h, const std::string& sceneryName) :
GameObject(objParent),
xSave(0),
ySave(0),
dx(0),
dy(0),
themeBlock(NULL),
repeatMode(0)
{
box.x = boxBase.x = x;
box.y = boxBase.y = y;
box.w = boxBase.w = w;
box.h = boxBase.h = h;
if (sceneryName.empty()) {
themeBlock = &internalThemeBlock;
} else {
// Load the appearance.
themeBlock = objThemes.getScenery(sceneryName);
if (themeBlock) {
sceneryName_ = sceneryName;
} else {
fprintf(stderr, "ERROR: Can't find scenery with name '%s'.\n", sceneryName.c_str());
themeBlock = &internalThemeBlock;
}
}
themeBlock->createInstance(&appearance);
}
Scenery::~Scenery(){
//Destroy the themeBlock since it isn't needed anymore.
internalThemeBlock.destroy();
}
static inline int getNewCoord(unsigned char rm, int default_, int cameraX, int cameraW, int levelW, int offset) {
switch (rm) {
case Scenery::NEGATIVE_INFINITY:
return cameraX;
case Scenery::ZERO:
return std::max(cameraX, offset);
case Scenery::LEVEL_SIZE:
return std::min(cameraX + cameraW, levelW + offset);
case Scenery::POSITIVE_INFINITY:
return cameraX + cameraW;
default:
return default_;
}
}
void Scenery::show(SDL_Renderer& renderer) {
showScenery(renderer, 0, 0);
}
void Scenery::showScenery(SDL_Renderer& renderer, int offsetX, int offsetY) {
//The box which is offset by the input.
const SDL_Rect box = {
this->box.x + offsetX,
this->box.y + offsetY,
this->box.w,
this->box.h,
};
//The real box according to repeat mode.
SDL_Rect theBox = {
getNewCoord(repeatMode, box.x, camera.x, camera.w, LEVEL_WIDTH, offsetX),
getNewCoord(repeatMode >> 16, box.y, camera.y, camera.h, LEVEL_HEIGHT, offsetX),
getNewCoord(repeatMode >> 8, box.x + box.w, camera.x, camera.w, LEVEL_WIDTH, offsetY),
getNewCoord(repeatMode >> 24, box.y + box.h, camera.y, camera.h, LEVEL_HEIGHT, offsetY),
};
theBox.w -= theBox.x;
theBox.h -= theBox.y;
//Check if the scenery is visible.
if (theBox.w > 0 && theBox.h > 0 && checkCollision(camera, theBox)) {
//Snap the size to integral multiple of box.w and box.h
if (box.w > 1) {
theBox.w += theBox.x;
if (repeatMode & 0xFFu) {
theBox.x = box.x + int(floor(float(theBox.x - box.x) / float(box.w))) * box.w;
}
if (repeatMode & 0xFF00u) {
theBox.w = box.x + int(ceil(float(theBox.w - box.x) / float(box.w))) * box.w;
}
theBox.w -= theBox.x;
}
if (box.h > 1) {
theBox.h += theBox.y;
if (repeatMode & 0xFF0000u) {
theBox.y = box.y + int(floor(float(theBox.y - box.y) / float(box.h))) * box.h;
}
if (repeatMode & 0xFF000000u) {
theBox.h = box.y + int(ceil(float(theBox.h - box.y) / float(box.h))) * box.h;
}
theBox.h -= theBox.y;
}
//Now draw normal.
if (theBox.w > 0 && theBox.h > 0) {
appearance.draw(renderer, theBox.x - camera.x, theBox.y - camera.y, theBox.w, theBox.h);
}
}
//Draw some stupid icons in edit mode.
if (stateID == STATE_LEVEL_EDITOR && checkCollision(camera, box)) {
auto bmGUI = static_cast<LevelEditor*>(parent)->getGuiTexture();
if (!bmGUI) {
return;
}
int x = box.x - camera.x + 2;
//Draw a stupid icon for custom scenery.
if (themeBlock == &internalThemeBlock) {
const SDL_Rect r = { 48, 16, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
//Draw a stupid icon for horizonal repeat.
if (repeatMode & 0x0000FFFFu) {
const SDL_Rect r = { 64, 32, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
//Draw a stupid icon for vertical repeat.
if (repeatMode & 0xFFFF0000u) {
const SDL_Rect r = { 64, 48, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
}
}
SDL_Rect Scenery::getBox(int boxType){
SDL_Rect r={0,0,0,0};
switch(boxType){
case BoxType_Base:
return boxBase;
case BoxType_Previous:
r.x=box.x-dx;
r.y=box.y-dy;
r.w=box.w;
r.h=box.h;
return r;
case BoxType_Delta:
r.x=dx;
r.y=dy;
return r;
case BoxType_Velocity:
return r;
case BoxType_Current:
return box;
}
return r;
}
void Scenery::setLocation(int x,int y){
//The scenery has moved so calculate the delta.
dx=x-box.x;
dy=y-box.y;
//And set its new location.
box.x=x;
box.y=y;
}
void Scenery::saveState(){
//Store the location.
xSave=box.x-boxBase.x;
ySave=box.y-boxBase.y;
//And any animations.
appearance.saveAnimation();
}
void Scenery::loadState(){
//Restore the location.
box.x=boxBase.x+xSave;
box.y=boxBase.y+ySave;
//And load the animation.
appearance.loadAnimation();
}
void Scenery::reset(bool save){
//Reset the scenery to its original location.
box.x=boxBase.x;
box.y=boxBase.y;
if(save)
xSave=ySave=0;
//Also reset the appearance.
appearance.resetAnimation(save);
appearance.changeState("default");
//NOTE: We load the animation right after changing it to prevent a transition.
if(save)
appearance.loadAnimation();
}
void Scenery::playAnimation(){}
void Scenery::onEvent(int eventType){
//NOTE: Scenery should not interact with the player or vice versa.
}
int Scenery::queryProperties(int propertyType,Player* obj){
//NOTE: Scenery doesn't have any properties.
return 0;
}
void Scenery::getEditorData(std::vector<std::pair<std::string,std::string> >& obj){
obj.push_back(pair<string, string>("sceneryName", sceneryName_));
obj.push_back(pair<string, string>("customScenery", customScenery_));
obj.push_back(pair<string, string>("repeatMode", tfm::format("%d", repeatMode)));
}
void Scenery::setEditorData(std::map<std::string,std::string>& obj){
// NOTE: currently the sceneryName cannot be changed by this method.
auto it = obj.find("customScenery");
if (it != obj.end()) {
customScenery_ = it->second;
}
it = obj.find("repeatMode");
if (it != obj.end()) {
repeatMode = atoi(it->second.c_str());
}
}
-std::string Scenery::getEditorProperty(std::string property){
+std::string Scenery::getEditorProperty(const std::string& property){
//First get the complete editor data.
vector<pair<string,string> > objMap;
vector<pair<string,string> >::iterator it;
getEditorData(objMap);
//Loop through the entries.
for(it=objMap.begin();it!=objMap.end();++it){
if(it->first==property)
return it->second;
}
//Nothing found.
return "";
}
-void Scenery::setEditorProperty(std::string property,std::string value){
+void Scenery::setEditorProperty(const std::string& property, const std::string& value){
//Create a map to hold the property.
std::map<std::string,std::string> editorData;
editorData[property]=value;
//And call the setEditorData method.
setEditorData(editorData);
}
bool Scenery::loadFromNode(ImageManager& imageManager, SDL_Renderer& renderer, TreeStorageNode* objNode){
sceneryName_.clear();
customScenery_.clear();
repeatMode = 0;
if (objNode->name == "object") {
//Make sure there are enough arguments.
if (objNode->value.size() < 2)
return false;
//Load position and size.
box.x = boxBase.x = atoi(objNode->value[0].c_str());
box.y = boxBase.y = atoi(objNode->value[1].c_str());
box.w = boxBase.w = (objNode->value.size() >= 3) ? atoi(objNode->value[2].c_str()) : 50;
box.h = boxBase.h = (objNode->value.size() >= 4) ? atoi(objNode->value[3].c_str()) : 50;
//Dump the current TreeStorageNode.
//NOTE: we temporarily remove all attributes since they are not related to theme.
std::map<std::string, std::vector<std::string> > tmpAttributes;
std::swap(objNode->attributes, tmpAttributes);
std::ostringstream o;
POASerializer().writeNode(objNode, o, false, true);
customScenery_ = o.str();
//restore old attributes
std::swap(objNode->attributes, tmpAttributes);
//Load the appearance.
if (!internalThemeBlock.loadFromNode(objNode, levels->levelpackPath, imageManager, renderer)) return false;
themeBlock = &internalThemeBlock;
themeBlock->createInstance(&appearance);
} else if (objNode->name == "scenery") {
//Make sure there are enough arguments.
if (objNode->value.size() < 3)
return false;
//Load position and size.
box.x = boxBase.x = atoi(objNode->value[1].c_str());
box.y = boxBase.y = atoi(objNode->value[2].c_str());
box.w = boxBase.w = (objNode->value.size() >= 4) ? atoi(objNode->value[3].c_str()) : 50;
box.h = boxBase.h = (objNode->value.size() >= 5) ? atoi(objNode->value[4].c_str()) : 50;
//Load the appearance.
themeBlock = objThemes.getScenery(objNode->value[0]);
if (!themeBlock) {
fprintf(stderr, "ERROR: Can't find scenery with name '%s'.\n", objNode->value[0].c_str());
return false;
}
themeBlock->createInstance(&appearance);
//Save the scenery name.
sceneryName_ = objNode->value[0];
} else {
//Unsupported node name for scenery block
fprintf(stderr, "ERROR: Unsupported node name '%s' for scenery block.\n", objNode->name.c_str());
return false;
}
auto it = objNode->attributes.find("repeatMode");
if (it != objNode->attributes.end() && it->second.size() >= 4) {
repeatMode = atoi(it->second[0].c_str())
| (atoi(it->second[1].c_str()) << 8)
| (atoi(it->second[2].c_str()) << 16)
| (atoi(it->second[3].c_str()) << 24);
}
return true;
}
bool Scenery::updateCustomScenery(ImageManager& imageManager, SDL_Renderer& renderer) {
POASerializer serializer;
std::istringstream i(customScenery_);
TreeStorageNode objNode;
//Load the node from text dump
if (!serializer.readNode(i, &objNode, true)) return false;
//Load the appearance.
if (!internalThemeBlock.loadFromNode(&objNode, levels->levelpackPath, imageManager, renderer)) return false;
themeBlock = &internalThemeBlock;
themeBlock->createInstance(&appearance);
// Clear the scenery name since we are using custom scenery
sceneryName_.clear();
return true;
}
void Scenery::move(){
//Update our appearance.
appearance.updateAnimation();
}
diff --git a/src/Scenery.h b/src/Scenery.h
index 6c2d1db..c0d6161 100644
--- a/src/Scenery.h
+++ b/src/Scenery.h
@@ -1,152 +1,152 @@
/*
* Copyright (C) 2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SCENERY_H
#define SCENERY_H
#include "GameObjects.h"
#include "ThemeManager.h"
#include <vector>
#include <SDL.h>
class Scenery: public GameObject{
private:
//Save variables for the current location of the scenery.
int xSave,ySave;
//Delta variables, if the scenery moves these must be set to the delta movement.
int dx,dy;
public:
//The ThemeBlock, kept so it can be deleted later on.
ThemeBlock internalThemeBlock;
// The pointer points to the real ThemeBlock, either point to internalThemeBlock, or a ThemeBlock in ThemeManager, or NULL.
ThemeBlock* themeBlock;
//The Appearance of the scenery.
//NOTE: We use a ThemeBlockInstance since it allows for all sorts of things like animations.
ThemeBlockInstance appearance;
// The scenery name. "" means custom scenery, in this case themeBlock is pointing to internalThemeBlock
std::string sceneryName_;
// The custom scenery description, which is the text dump of the TreeStorageNode.
std::string customScenery_;
// The repeat mode.
enum RepeatMode {
DEFAULT, // Starts or ends at the position of this block (default)
NEGATIVE_INFINITY, // Starts at negative infinity
ZERO, // Starts or ends at 0
LEVEL_SIZE, // Starts or ends at level size
POSITIVE_INFINITY, // Ends at positive infinity
REPEAT_MODE_MAX,
};
// The repeat mode of this block. The value is Scenery::RepeatMode left shifted by appropriate value
// bit 0-7: x start
// bit 8-15: x end
// bit 16-23: y start
// bit 24-31: y end
unsigned int repeatMode;
//Constructor.
//objParent: Pointer to the Game object.
Scenery(Game* objParent);
//Constructor.
//objParent: Pointer to the Game object.
//x: the x coordinate
//y: the y coordinate
//w: the width
//h: the height
//sceneryName: the scenery name, "" means custom scenery block
Scenery(Game* objParent, int x, int y, int w, int h, const std::string& sceneryName);
//Desturctor.
~Scenery();
//Method to load custom scenery from customScenery_ member variable.
bool updateCustomScenery(ImageManager& imageManager, SDL_Renderer& renderer);
//Method used to draw the scenery.
//NOTE: To enable parallax scrolling, etc. use showScenery() instead.
void show(SDL_Renderer& renderer) override;
//Method used to draw the scenery.
//offsetX/Y: the offset apply to the scenery block before considering camera position.
void showScenery(SDL_Renderer& renderer, int offsetX, int offsetY);
//Returns the box of a given type.
//boxType: The type of box that should be returned.
//See GameObjects.h for the types.
//Returns: The box.
virtual SDL_Rect getBox(int boxType=BoxType_Current) override;
//Method used to set the location of the scenery.
//NOTE: The new location isn't stored as base location.
//x: The new x location.
//y: The new y location.
virtual void setLocation(int x,int y) override;
//Save the state of the scenery so we can load it later on.
virtual void saveState() override;
//Load the saved state of the scenery.
virtual void loadState() override;
//Reset the scenery.
//save: Boolean if the saved state should also be deleted.
virtual void reset(bool save) override;
//Play an animation.
virtual void playAnimation() override;
//Method called when there's an event.
//eventType: The type of event.
//See GameObjects.h for the eventtypes.
virtual void onEvent(int eventType) override;
//Method used to retrieve a property from the scenery.
//propertyType: The type of property requested.
//See GameObjects.h for the properties.
//obj: Pointer to the player.
//Returns: Integer containing the value of the property.
virtual int queryProperties(int propertyType,Player* obj) override;
//Get the editor data of the scenery.
//obj: The vector that will be filled with the editorData.
virtual void getEditorData(std::vector<std::pair<std::string,std::string> >& obj) override;
//Set the editor data of the scenery.
//obj: The new editor data.
virtual void setEditorData(std::map<std::string,std::string>& obj) override;
//Get a single property of the scenery.
//property: The property to return.
//Returns: The value for the requested property.
- virtual std::string getEditorProperty(std::string property) override;
+ virtual std::string getEditorProperty(const std::string& property) override;
//Set a single property of the scenery.
//property: The property to set.
//value: The new value for the property.
- virtual void setEditorProperty(std::string property,std::string value) override;
+ virtual void setEditorProperty(const std::string& property, const std::string& value) override;
//Method for loading the Scenery object from a node.
//objNode: Pointer to the storage node to load from.
//Returns: True if it succeeds without errors.
virtual bool loadFromNode(ImageManager& imageManager,SDL_Renderer& renderer,TreeStorageNode* objNode) override;
//Method used for updating any animations.
virtual void move() override;
};
#endif
diff --git a/src/ScriptAPI.cpp b/src/ScriptAPI.cpp
index cf30d9d..d220a33 100644
--- a/src/ScriptAPI.cpp
+++ b/src/ScriptAPI.cpp
@@ -1,1876 +1,1911 @@
/*
* Copyright (C) 2012-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ScriptAPI.h"
#include "ScriptExecutor.h"
#include "SoundManager.h"
#include "Functions.h"
#include "Block.h"
#include "Game.h"
#include "MusicManager.h"
#include "ScriptDelayExecution.h"
#include <iostream>
#include <algorithm>
using namespace std;
/////////////////////////// HELPER MACRO ///////////////////////////
#define HELPER_GET_AND_CHECK_ARGS(ARGS) \
int args = lua_gettop(state); \
if(args != ARGS) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected %d.", __FUNCTION__, ARGS); \
}
#define HELPER_GET_AND_CHECK_ARGS_RANGE(ARGS1, ARGS2) \
int args = lua_gettop(state); \
if(args < ARGS1 || args > ARGS2) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected %d-%d.", __FUNCTION__, ARGS1, ARGS2); \
}
#define HELPER_GET_AND_CHECK_ARGS_2(ARGS1, ARGS2) \
int args = lua_gettop(state); \
if(args != ARGS1 && args != ARGS2) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected %d or %d.", __FUNCTION__, ARGS1, ARGS2); \
}
#define HELPER_GET_AND_CHECK_ARGS_AT_LEAST(ARGS) \
int args = lua_gettop(state); \
if(args < ARGS) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected at least %d.", __FUNCTION__, ARGS); \
}
#define HELPER_GET_AND_CHECK_ARGS_AT_MOST(ARGS) \
int args = lua_gettop(state); \
if(args > ARGS) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected at most %d.", __FUNCTION__, ARGS); \
}
//================================================================
#define HELPER_CHECK_ARGS_TYPE(INDEX, TYPE) \
if(!lua_is##TYPE(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s, should be %s.", INDEX, __FUNCTION__, #TYPE); \
}
#define HELPER_CHECK_ARGS_TYPE_NO_HINT(INDEX, TYPE) \
if(!lua_is##TYPE(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s.", INDEX, __FUNCTION__); \
}
#define HELPER_CHECK_ARGS_TYPE_2(INDEX, TYPE1, TYPE2) \
if(!lua_is##TYPE1(state,INDEX) && !lua_is##TYPE2(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s, should be %s or %s.", INDEX, __FUNCTION__, #TYPE1, #TYPE2); \
}
#define HELPER_CHECK_ARGS_TYPE_2_NO_HINT(INDEX, TYPE1, TYPE2) \
if(!lua_is##TYPE1(state,INDEX) && !lua_is##TYPE2(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s.", INDEX, __FUNCTION__); \
}
#define HELPER_CHECK_ARGS_TYPE_OR_NIL(INDEX, TYPE) \
HELPER_CHECK_ARGS_TYPE_2(INDEX, TYPE, nil)
#define HELPER_CHECK_ARGS_TYPE_OR_NIL_NO_HINT(INDEX, TYPE) \
HELPER_CHECK_ARGS_TYPE_2_NO_HINT(INDEX, TYPE, nil)
//================================================================
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE(INDEX, TYPE) \
if(args>=INDEX && !lua_is##TYPE(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s, should be %s.", INDEX, __FUNCTION__, #TYPE); \
}
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_NO_HINT(INDEX, TYPE) \
if(args>=INDEX && !lua_is##TYPE(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s.", INDEX, __FUNCTION__); \
}
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_2(INDEX, TYPE1, TYPE2) \
if(args>=INDEX && !lua_is##TYPE1(state,INDEX) && !lua_is##TYPE2(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s, should be %s or %s.", INDEX, __FUNCTION__, #TYPE1, #TYPE2); \
}
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_2_NO_HINT(INDEX, TYPE1, TYPE2) \
if(args>=INDEX && !lua_is##TYPE1(state,INDEX) && !lua_is##TYPE2(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s.", INDEX, __FUNCTION__); \
}
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(INDEX, TYPE) \
HELPER_CHECK_OPTIONAL_ARGS_TYPE_2(INDEX, TYPE, nil)
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL_NO_HINT(INDEX, TYPE) \
HELPER_CHECK_OPTIONAL_ARGS_TYPE_2_NO_HINT(INDEX, TYPE, nil)
//================================================================
#define HELPER_REGISTER_IS_VALID_FUNCTION(CLASS) \
int isValid(lua_State* state){ \
HELPER_GET_AND_CHECK_ARGS(1); \
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata); \
CLASS* object = CLASS::getObjectFromUserData(state, 1); \
lua_pushboolean(state, object ? 1 : 0); \
return 1; \
}
//================================================================
#define _F(FUNC) { #FUNC, _L::FUNC }
#define _FG(FUNC) _F(get##FUNC)
#define _FI(FUNC) _F(is##FUNC)
#define _FS(FUNC) _F(set##FUNC)
#define _FGS(FUNC) _F(get##FUNC), _F(set##FUNC)
#define _FIS(FUNC) _F(is##FUNC), _F(set##FUNC)
///////////////////////////BLOCK SPECIFIC///////////////////////////
class BlockScriptAPI {
public:
static int getFlags(const Block* block) {
return block->flags;
}
static void setFlags(Block* block, int flags) {
block->flags = flags;
}
static void fragileUpdateState(Block* block, int state) {
state &= 0x3;
block->flags = (block->flags & ~0x3) | state;
const char* s = (state == 0) ? "default" : ((state == 1) ? "fragile1" : ((state == 2) ? "fragile2" : "fragile3"));
block->appearance.changeState(s);
}
static int getTemp(const Block* block) {
return block->temp;
}
static void setTemp(Block* block, int value) {
block->temp = value;
}
static int getSpeed(const Block* block) {
return block->speed;
}
static void setSpeed(Block* block, int value) {
block->speed = value;
}
};
namespace block {
HELPER_REGISTER_IS_VALID_FUNCTION(Block);
int getBlockById(lua_State* state){
//Get the number of args, this MUST be one.
HELPER_GET_AND_CHECK_ARGS(1);
//Make sure the given argument is an id (string).
HELPER_CHECK_ARGS_TYPE(1, string);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Get the actual game object.
string id = lua_tostring(state, 1);
std::vector<Block*>& levelObjects = game->levelObjects;
Block* object = NULL;
for (unsigned int i = 0; i < levelObjects.size(); i++){
if (levelObjects[i]->getEditorProperty("id") == id){
object = levelObjects[i];
break;
}
}
if (object == NULL){
//Unable to find the requested object.
//Return nothing, will result in a nil in the script.
return 0;
}
//Create the userdatum.
object->createUserData(state, "block");
//We return one object, the userdatum.
return 1;
}
int getBlocksById(lua_State* state){
//Get the number of args, this MUST be one.
HELPER_GET_AND_CHECK_ARGS(1);
//Make sure the given argument is an id (string).
HELPER_CHECK_ARGS_TYPE(1, string);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Get the actual game object.
string id = lua_tostring(state, 1);
std::vector<Block*>& levelObjects = game->levelObjects;
std::vector<Block*> result;
for (unsigned int i = 0; i < levelObjects.size(); i++){
if (levelObjects[i]->getEditorProperty("id") == id){
result.push_back(levelObjects[i]);
}
}
//Create the table that will hold the result.
lua_createtable(state, result.size(), 0);
//Loop through the results.
for (unsigned int i = 0; i < result.size(); i++){
//Create the userdatum.
result[i]->createUserData(state, "block");
//And set the table.
lua_rawseti(state, -2, i + 1);
}
//We return one object, the userdatum.
return 1;
}
int moveTo(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
HELPER_CHECK_ARGS_TYPE(3, number); // integer
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int x = lua_tonumber(state, 2);
int y = lua_tonumber(state, 3);
object->moveTo(x, y);
return 0;
}
int getLocation(lua_State* state){
//Make sure there's only one argument and that argument is an userdatum.
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Get the object.
- lua_pushnumber(state, object->getBox().x);
- lua_pushnumber(state, object->getBox().y);
+ SDL_Rect r = object->getBox();
+ lua_pushnumber(state, r.x);
+ lua_pushnumber(state, r.y);
+ return 2;
+ }
+
+ int getBaseLocation(lua_State* state){
+ //Make sure there's only one argument and that argument is an userdatum.
+ HELPER_GET_AND_CHECK_ARGS(1);
+
+ HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
+
+ Block* object = Block::getObjectFromUserData(state, 1);
+ if (object == NULL) return 0;
+
+ //Get the object.
+ SDL_Rect r = object->getBox(BoxType_Base);
+ lua_pushnumber(state, r.x);
+ lua_pushnumber(state, r.y);
return 2;
}
int setLocation(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
HELPER_CHECK_ARGS_TYPE(3, number); // integer
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int x = lua_tonumber(state, 2);
int y = lua_tonumber(state, 3);
object->setLocation(x, y);
return 0;
}
int growTo(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
HELPER_CHECK_ARGS_TYPE(3, number); // integer
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int w = lua_tonumber(state, 2);
int h = lua_tonumber(state, 3);
object->growTo(w, h);
return 0;
}
int getSize(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Get the object.
lua_pushnumber(state, object->getBox().w);
lua_pushnumber(state, object->getBox().h);
return 2;
}
+ int getBaseSize(lua_State* state){
+ //Make sure there's only one argument and that argument is an userdatum.
+ HELPER_GET_AND_CHECK_ARGS(1);
+
+ HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
+
+ Block* object = Block::getObjectFromUserData(state, 1);
+ if (object == NULL) return 0;
+
+ //Get the object.
+ SDL_Rect r = object->getBox(BoxType_Base);
+ lua_pushnumber(state, r.w);
+ lua_pushnumber(state, r.h);
+ return 2;
+ }
+
int setSize(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
HELPER_CHECK_ARGS_TYPE(3, number); // integer
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int w = lua_tonumber(state, 2);
int h = lua_tonumber(state, 3);
object->setSize(w, h);
return 0;
}
int getType(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL || object->type < 0 || object->type >= TYPE_MAX) return 0;
lua_pushstring(state, Game::blockName[object->type]);
return 1;
}
int changeThemeState(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
object->appearance.changeState(lua_tostring(state, 2));
return 0;
}
int setVisible(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL)
return 0;
BlockScriptAPI::setFlags(object,
(BlockScriptAPI::getFlags(object) & ~0x80000000) | (lua_toboolean(state, 2) ? 0 : 0x80000000)
);
return 0;
}
int isVisible(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL)
return 0;
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x80000000) ? 0 : 1);
return 1;
}
int getEventHandler(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 2);
map<string, int>::iterator it = Game::gameObjectEventNameMap.find(eventType);
if (it == Game::gameObjectEventNameMap.end()) return 0;
//Check compiled script
map<int, int>::iterator script = object->compiledScripts.find(it->second);
if (script == object->compiledScripts.end()) return 0;
//Get event handler
lua_rawgeti(state, LUA_REGISTRYINDEX, script->second);
return 1;
}
//It will return old event handler.
int setEventHandler(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
HELPER_CHECK_ARGS_TYPE_OR_NIL(3, function);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 2);
map<string, int>::const_iterator it = Game::gameObjectEventNameMap.find(eventType);
if (it == Game::gameObjectEventNameMap.end()){
lua_pushfstring(state, "Unknown block event type: '%s'.", eventType.c_str());
return lua_error(state);
}
//Check compiled script
int scriptIndex = LUA_REFNIL;
{
map<int, int>::iterator script = object->compiledScripts.find(it->second);
if (script != object->compiledScripts.end()) scriptIndex = script->second;
}
//Set new event handler
object->compiledScripts[it->second] = luaL_ref(state, LUA_REGISTRYINDEX);
if (scriptIndex == LUA_REFNIL) return 0;
//Get old event handler and unreference it
lua_rawgeti(state, LUA_REGISTRYINDEX, scriptIndex);
luaL_unref(state, LUA_REGISTRYINDEX, scriptIndex);
return 1;
}
int onEvent(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 2);
map<string, int>::const_iterator it = Game::gameObjectEventNameMap.find(eventType);
if (it == Game::gameObjectEventNameMap.end()){
lua_pushfstring(state, "Unknown block event type: '%s'.", eventType.c_str());
return lua_error(state);
}
object->onEvent(it->second);
return 0;
}
int isActivated(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x1) ? 0 : 1);
return 1;
default:
return 0;
}
}
int setActivated(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
BlockScriptAPI::setFlags(object,
(BlockScriptAPI::getFlags(object) & ~1) | (lua_toboolean(state, 2) ? 0 : 1)
);
break;
}
return 0;
}
int isAutomatic(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_PORTAL:
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x1) ? 1 : 0);
return 1;
default:
return 0;
}
}
int setAutomatic(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_PORTAL:
BlockScriptAPI::setFlags(object,
(BlockScriptAPI::getFlags(object) & ~1) | (lua_toboolean(state, 2) ? 1 : 0)
);
break;
}
return 0;
}
int getBehavior(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_BUTTON:
case TYPE_SWITCH:
switch (BlockScriptAPI::getFlags(object) & 0x3) {
default:
lua_pushstring(state, "toggle");
break;
case 1:
lua_pushstring(state, "on");
break;
case 2:
lua_pushstring(state, "off");
break;
}
return 1;
default:
return 0;
}
}
int setBehavior(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_BUTTON:
case TYPE_SWITCH:
{
int newFlags = BlockScriptAPI::getFlags(object) & ~3;
std::string s = lua_tostring(state, 2);
if (s == "on") newFlags |= 1;
else if (s == "off") newFlags |= 2;
BlockScriptAPI::setFlags(object, newFlags);
}
break;
}
return 0;
}
int getState(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_FRAGILE:
lua_pushnumber(state, BlockScriptAPI::getFlags(object) & 0x3);
return 1;
default:
return 0;
}
}
int setState(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_FRAGILE:
{
int oldState = BlockScriptAPI::getFlags(object) & 0x3;
int newState = (int)lua_tonumber(state, 2);
if (newState < 0) newState = 0;
else if (newState > 3) newState = 3;
if (newState != oldState) {
BlockScriptAPI::fragileUpdateState(object, newState);
}
}
break;
}
return 0;
}
int isPlayerOn(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_BUTTON:
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x4) ? 1 : 0);
return 1;
default:
return 0;
}
}
int getPathMaxTime(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
lua_pushnumber(state, object->getPathMaxTime());
return 1;
default:
return 0;
}
}
int getPathTime(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
lua_pushnumber(state, BlockScriptAPI::getTemp(object));
return 1;
default:
return 0;
}
}
int setPathTime(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
BlockScriptAPI::setTemp(object, (int)lua_tonumber(state, 2));
break;
}
return 0;
}
int isLooping(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x2) ? 0 : 1);
return 1;
default:
return 0;
}
}
int setLooping(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
BlockScriptAPI::setFlags(object,
(BlockScriptAPI::getFlags(object) & ~2) | (lua_toboolean(state, 2) ? 0 : 2)
);
break;
}
return 0;
}
int getSpeed(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
lua_pushnumber(state, BlockScriptAPI::getSpeed(object));
return 1;
default:
return 0;
}
}
int setSpeed(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
BlockScriptAPI::setSpeed(object, (int)lua_tonumber(state, 2));
break;
}
return 0;
}
}
#define _L block
//Array with the methods for the block library.
static const struct luaL_Reg blocklib_m[]={
_FI(Valid),
_FG(BlockById),
_FG(BlocksById),
_F(moveTo),
_FGS(Location),
+ _FG(BaseLocation),
_F(growTo),
_FGS(Size),
+ _FG(BaseSize),
_FG(Type),
_F(changeThemeState),
_FIS(Visible),
_FGS(EventHandler),
_F(onEvent),
_FIS(Activated),
_FIS(Automatic),
_FGS(Behavior),
_FGS(State),
_FI(PlayerOn),
_FG(PathMaxTime),
_FGS(PathTime),
_FIS(Looping),
_FGS(Speed),
{ NULL, NULL }
};
#undef _L
int luaopen_block(lua_State* state){
luaL_newlib(state,blocklib_m);
//Create the metatable for the block userdata.
luaL_newmetatable(state,"block");
lua_pushstring(state,"__index");
lua_pushvalue(state,-2);
lua_settable(state,-3);
Block::registerMetatableFunctions(state,-3);
//Register the functions and methods.
luaL_setfuncs(state,blocklib_m,0);
return 1;
}
//////////////////////////PLAYER SPECIFIC///////////////////////////
class PlayerScriptAPI {
public:
static bool isInAir(Player* player) {
return player->inAir;
}
static bool canMode(Player* player) {
return player->canMove;
}
static bool isDead(Player* player) {
return player->dead;
}
static bool isHoldingOther(Player* player) {
return player->holdingOther;
}
};
struct PlayerUserDatum{
char sig1,sig2,sig3,sig4;
};
Player* getPlayerFromUserData(lua_State* state,int idx){
PlayerUserDatum* ud=(PlayerUserDatum*)lua_touserdata(state,1);
//Make sure the user datum isn't null.
if(!ud) return NULL;
//Get the game state.
Game* game=dynamic_cast<Game*>(currentState);
if(game==NULL) return NULL;
Player* player=NULL;
//Check the signature to see if it's the player or the shadow.
if(ud->sig1=='P' && ud->sig2=='L' && ud->sig3=='Y' && ud->sig4=='R')
player=&game->player;
else if(ud->sig1=='S' && ud->sig2=='H' && ud->sig3=='D' && ud->sig4=='W')
player=&game->shadow;
return player;
}
namespace playershadow {
int getLocation(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
//Get the object.
lua_pushnumber(state, player->getBox().x);
lua_pushnumber(state, player->getBox().y);
return 2;
}
int setLocation(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
HELPER_CHECK_ARGS_TYPE(3, number); // integer
//Get the player.
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
//Get the new location.
int x = lua_tonumber(state, 2);
int y = lua_tonumber(state, 3);
player->setLocation(x, y);
return 0;
}
int jump(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_2(1, 2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(2, number); // integer
//Get the player.
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
//Get the new location.
if (args == 2){
int yVel = lua_tonumber(state, 2);
player->jump(yVel);
} else{
//Use default jump strength.
player->jump();
}
return 0;
}
int isShadow(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
lua_pushboolean(state, player->isShadow());
return 1;
}
int getCurrentStand(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
//Get the actual game object.
Block* object = player->getObjCurrentStand();
if (object == NULL){
return 0;
}
//Create the userdatum.
object->createUserData(state, "block");
//We return one object, the userdatum.
return 1;
}
int isInAir(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
lua_pushboolean(state, PlayerScriptAPI::isInAir(player));
return 1;
}
int canMove(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
lua_pushboolean(state, PlayerScriptAPI::canMode(player));
return 1;
}
int isDead(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
lua_pushboolean(state, PlayerScriptAPI::isDead(player));
return 1;
}
int isHoldingOther(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
lua_pushboolean(state, PlayerScriptAPI::isHoldingOther(player));
return 1;
}
}
#define _L playershadow
//Array with the methods for the player and shadow library.
static const struct luaL_Reg playerlib_m[]={
_FGS(Location),
_F(jump),
_FI(Shadow),
_FG(CurrentStand),
_FI(InAir),
_F(canMove),
_FI(Dead),
_FI(HoldingOther),
{ NULL, NULL }
};
#undef _L
int luaopen_player(lua_State* state){
luaL_newlib(state,playerlib_m);
//Create the metatable for the player userdata.
luaL_newmetatable(state,"player");
lua_pushstring(state,"__index");
lua_pushvalue(state,-2);
lua_settable(state,-3);
//Now create two default player user data, one for the player and one for the shadow.
PlayerUserDatum* ud=(PlayerUserDatum*)lua_newuserdata(state,sizeof(PlayerUserDatum));
ud->sig1='P';ud->sig2='L';ud->sig3='Y';ud->sig4='R';
luaL_getmetatable(state,"player");
lua_setmetatable(state,-2);
lua_setglobal(state,"player");
ud=(PlayerUserDatum*)lua_newuserdata(state,sizeof(PlayerUserDatum));
ud->sig1='S';ud->sig2='H';ud->sig3='D';ud->sig4='W';
luaL_getmetatable(state,"player");
lua_setmetatable(state,-2);
lua_setglobal(state,"shadow");
//Register the functions and methods.
luaL_setfuncs(state,playerlib_m,0);
return 1;
}
//////////////////////////LEVEL SPECIFIC///////////////////////////
namespace level {
int getSize(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Returns level size.
lua_pushinteger(state, LEVEL_WIDTH);
lua_pushinteger(state, LEVEL_HEIGHT);
return 2;
}
int getWidth(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Returns level size.
lua_pushinteger(state, LEVEL_WIDTH);
return 1;
}
int getHeight(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Returns level size.
lua_pushinteger(state, LEVEL_HEIGHT);
return 1;
}
int getName(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level name.
lua_pushstring(state, game->getLevelName().c_str());
return 1;
}
int getEventHandler(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 1);
map<string, int>::iterator it = Game::levelEventNameMap.find(eventType);
if (it == Game::levelEventNameMap.end()) return 0;
//Check compiled script
map<int, int>::iterator script = game->compiledScripts.find(it->second);
if (script == game->compiledScripts.end()) return 0;
//Get event handler
lua_rawgeti(state, LUA_REGISTRYINDEX, script->second);
return 1;
}
//It will return old event handler.
int setEventHandler(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
HELPER_CHECK_ARGS_TYPE_OR_NIL(2, function);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 1);
map<string, int>::const_iterator it = Game::levelEventNameMap.find(eventType);
if (it == Game::levelEventNameMap.end()){
lua_pushfstring(state, "Unknown level event type: '%s'.", eventType.c_str());
return lua_error(state);
}
//Check compiled script
int scriptIndex = LUA_REFNIL;
{
map<int, int>::iterator script = game->compiledScripts.find(it->second);
if (script != game->compiledScripts.end()) scriptIndex = script->second;
}
//Set new event handler
game->compiledScripts[it->second] = luaL_ref(state, LUA_REGISTRYINDEX);
if (scriptIndex == LUA_REFNIL) return 0;
//Get old event handler and unreference it
lua_rawgeti(state, LUA_REGISTRYINDEX, scriptIndex);
luaL_unref(state, LUA_REGISTRYINDEX, scriptIndex);
return 1;
}
int win(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
if (stateID == STATE_LEVEL_EDITOR)
return 0;
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
game->won = true;
return 0;
}
int getTime(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level size.
lua_pushinteger(state, game->time);
return 1;
}
int getRecordings(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level size.
lua_pushinteger(state, game->recordings);
return 1;
}
int broadcastObjectEvent(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_RANGE(1, 4);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(2, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(3, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL_NO_HINT(4, userdata);
//Check event type
int eventType = 0;
{
string s = lua_tostring(state, 1);
auto it = Game::gameObjectEventNameMap.find(s);
if (it == Game::gameObjectEventNameMap.end()){
lua_pushfstring(state, "Unknown block event type: '%s'.", s.c_str());
return lua_error(state);
} else {
eventType = it->second;
}
}
//Check object type
int objType = -1;
if (args >= 2 && lua_isstring(state, 2)) {
string s = lua_tostring(state, 2);
auto it = Game::blockNameMap.find(s);
if (it == Game::blockNameMap.end()){
lua_pushfstring(state, "Unknown object type: '%s'.", s.c_str());
return lua_error(state);
} else {
objType = it->second;
}
}
//Check id
const char* id = NULL;
if (args >= 3 && lua_isstring(state, 3)) {
id = lua_tostring(state, 3);
}
//Check target
Block *target = NULL;
if (args >= 4) {
target = Block::getObjectFromUserData(state, 4);
}
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
game->broadcastObjectEvent(eventType, objType, id, target);
return 0;
}
}
#define _L level
//Array with the methods for the level library.
static const struct luaL_Reg levellib_m[]={
_FG(Size),
_FG(Width),
_FG(Height),
_FG(Name),
_FGS(EventHandler),
_F(win),
_FG(Time),
_FG(Recordings),
_F(broadcastObjectEvent),
{NULL,NULL}
};
#undef _L
int luaopen_level(lua_State* state){
luaL_newlib(state,levellib_m);
//Register the functions and methods.
luaL_setfuncs(state,levellib_m,0);
return 1;
}
/////////////////////////CAMERA SPECIFIC///////////////////////////
//FIXME: I can't define namespace camera since there is already a global variable named camera.
//Therefore I use struct camera for a workaround.
struct camera {
static int setMode(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
string mode = lua_tostring(state, 1);
//Get the game for setting the camera.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Check which mode.
if (mode == "player"){
game->cameraMode = Game::CAMERA_PLAYER;
} else if (mode == "shadow"){
game->cameraMode = Game::CAMERA_SHADOW;
} else{
//Unknown OR invalid camera mode.
return luaL_error(state, "Unknown or invalid camera mode for %s: '%s'.", __FUNCTION__, mode.c_str());
}
//Returns nothing.
return 0;
}
static int lookAt(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, number); // integer
HELPER_CHECK_ARGS_TYPE(2, number); // integer
//Get the point.
int x = lua_tonumber(state, 1);
int y = lua_tonumber(state, 2);
//Get the game for setting the camera.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
game->cameraMode = Game::CAMERA_CUSTOM;
game->cameraTarget.x = x;
game->cameraTarget.y = y;
return 0;
}
};
#define _L camera
//Array with the methods for the camera library.
static const struct luaL_Reg cameralib_m[]={
_FS(Mode),
_F(lookAt),
{NULL,NULL}
};
#undef _L
int luaopen_camera(lua_State* state){
luaL_newlib(state,cameralib_m);
//Register the functions and methods.
luaL_setfuncs(state,cameralib_m,0);
return 1;
}
/////////////////////////AUDIO SPECIFIC///////////////////////////
namespace audio {
int playSound(lua_State* state){
//Get the number of args, this can be anything from one to three.
HELPER_GET_AND_CHECK_ARGS_RANGE(1, 4);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(2, number); // integer
HELPER_CHECK_OPTIONAL_ARGS_TYPE(3, boolean);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(4, number); // integer
//Default values for concurrent and force.
//See SoundManager.h
int concurrent = -1;
bool force = false;
int fadeMusic = -1;
//If there's a second one it should be an integer.
if (args > 1){
concurrent = lua_tonumber(state, 2);
}
//If there's a third one it should be a boolean.
if (args > 2){
force = lua_toboolean(state, 3);
}
if (args > 3){
fadeMusic = lua_tonumber(state, 4);
}
//Get the name of the sound.
string sound = lua_tostring(state, 1);
//Try to play the sound.
int channel = getSoundManager()->playSound(sound, concurrent, force, fadeMusic);
//Returns whether the operation is successful.
lua_pushboolean(state, channel >= 0 ? 1 : 0);
return 1;
}
int playMusic(lua_State* state){
//Get the number of args, this can be either one or two.
HELPER_GET_AND_CHECK_ARGS_2(1, 2);
//Make sure the first argument is a string.
HELPER_CHECK_ARGS_TYPE(1, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(2, boolean);
//Default value of fade for playMusic.
//See MusicManager.h.
bool fade = true;
//If there's a second one it should be a boolean.
if (args > 1){
fade = lua_toboolean(state, 2);
}
//Get the name of the music.
string music = lua_tostring(state, 1);
//Try to switch to the new music.
getMusicManager()->playMusic(music, fade);
//Returns nothing.
return 0;
}
int pickMusic(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Let the music manager pick a song from the current music list.
getMusicManager()->pickMusic();
return 0;
}
int setMusicList(lua_State* state){
//Get the number of args, this MUST be one.
HELPER_GET_AND_CHECK_ARGS(1);
//Make sure the given argument is a string.
HELPER_CHECK_ARGS_TYPE(1, string);
//And set the music list in the music manager.
string list = lua_tostring(state, 1);
getMusicManager()->setMusicList(list);
return 0;
}
int getMusicList(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Return the name of the song (contains list prefix).
lua_pushstring(state, getMusicManager()->getCurrentMusicList().c_str());
return 1;
}
int currentMusic(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Return the name of the song (contains list prefix).
lua_pushstring(state, getMusicManager()->getCurrentMusic().c_str());
return 1;
}
}
#define _L audio
//Array with the methods for the audio library.
static const struct luaL_Reg audiolib_m[]={
_F(playSound),
_F(playMusic),
_F(pickMusic),
_FGS(MusicList),
_F(currentMusic),
{NULL,NULL}
};
#undef _L
int luaopen_audio(lua_State* state){
luaL_newlib(state,audiolib_m);
//Register the functions and methods.
luaL_setfuncs(state,audiolib_m,0);
return 1;
}
/////////////////////////DELAY EXECUTION SPECIFIC///////////////////////////
namespace delayExecution {
HELPER_REGISTER_IS_VALID_FUNCTION(ScriptDelayExecution);
int schedule(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_AT_LEAST(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_OR_NIL(1, function);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(3, number); // integer
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(4, number); // integer
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(5, boolean);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Create the delay execution object.
ScriptDelayExecution *obj = new ScriptDelayExecution(game->getScriptExecutor()->getDelayExecutionList());
obj->setActive();
obj->time = (int)lua_tonumber(state, 2);
obj->repeatCount = (args >= 3 && lua_isnumber(state, 3)) ? (int)lua_tonumber(state, 3) : 1;
obj->repeatInterval = (args >= 4 && lua_isnumber(state, 4)) ? (int)lua_tonumber(state, 4) : obj->time;
obj->enabled = ((args >= 5 && lua_isboolean(state, 5)) ? lua_toboolean(state, 5) : 1) != 0;
//Get arguments.
for (int i = 6; i <= args; i++) {
obj->arguments.push_back(luaL_ref(state, LUA_REGISTRYINDEX));
}
std::reverse(obj->arguments.begin(), obj->arguments.end());
//Get the function.
lua_settop(state, 1);
obj->func = luaL_ref(state, LUA_REGISTRYINDEX);
//Create the userdatum.
obj->createUserData(state, "delayExecution");
//We return one object, the userdatum.
return 1;
}
int cancel(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Delete the object.
delete object;
return 0;
}
int isEnabled(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
lua_pushboolean(state, object->enabled ? 1 : 0);
return 1;
}
int setEnabled(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS(2);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
object->enabled = lua_toboolean(state, 2) != 0;
return 0;
}
int getTime(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
lua_pushnumber(state, object->time);
return 1;
}
int setTime(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS(2);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
object->time = (int)lua_tonumber(state, 2);
return 0;
}
int getRepeatCount(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
lua_pushnumber(state, object->repeatCount);
return 1;
}
int setRepeatCount(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS(2);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
object->repeatCount = (int)lua_tonumber(state, 2);
return 0;
}
int getRepeatInterval(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
lua_pushnumber(state, object->repeatInterval);
return 1;
}
int setRepeatInterval(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS(2);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Set the repeat interval (should >=1).
int i = (int)lua_tonumber(state, 2);
if (i > 0) object->repeatInterval = i;
return 0;
}
int getFunc(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
if (object->func == LUA_REFNIL) return 0;
lua_rawgeti(state, LUA_REGISTRYINDEX, object->func);
return 1;
}
int setFunc(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS(2);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE_OR_NIL(2, function);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int oldFunc = object->func;
object->func = luaL_ref(state, LUA_REGISTRYINDEX);
if (oldFunc == LUA_REFNIL) return 0;
lua_rawgeti(state, LUA_REGISTRYINDEX, oldFunc);
luaL_unref(state, LUA_REGISTRYINDEX, oldFunc);
return 1;
}
int getArguments(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
for (int a : object->arguments) {
lua_rawgeti(state, LUA_REGISTRYINDEX, a);
}
return object->arguments.size();
}
int setArguments(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS_AT_LEAST(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Remove old arguments.
for (int a : object->arguments) {
luaL_unref(state, LUA_REGISTRYINDEX, a);
}
object->arguments.clear();
//Get arguments.
for (int i = 2; i <= args; i++) {
object->arguments.push_back(luaL_ref(state, LUA_REGISTRYINDEX));
}
std::reverse(object->arguments.begin(), object->arguments.end());
return 0;
}
int getExecutionTime(lua_State* state){
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
lua_pushnumber(state, object->executionTime);
return 1;
}
int setExecutionTime(lua_State* state) {
HELPER_GET_AND_CHECK_ARGS(2);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
auto object = ScriptDelayExecution::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
object->executionTime = (int)lua_tonumber(state, 2);
return 0;
}
}
#define _L delayExecution
//Array with the methods for the block library.
static const struct luaL_Reg delayExecutionLib_m[] = {
_FI(Valid),
_F(schedule),
_F(cancel),
_FIS(Enabled),
_FGS(Time),
_FGS(RepeatCount),
_FGS(RepeatInterval),
_FGS(Func),
_FGS(Arguments),
_FGS(ExecutionTime),
{ NULL, NULL }
};
#undef _L
int luaopen_delayExecution(lua_State* state){
luaL_newlib(state, delayExecutionLib_m);
//Create the metatable for the delay execution userdata.
luaL_newmetatable(state, "delayExecution");
lua_pushstring(state, "__index");
lua_pushvalue(state, -2);
lua_settable(state, -3);
ScriptDelayExecution::registerMetatableFunctions(state, -3);
//Register the functions and methods.
luaL_setfuncs(state, delayExecutionLib_m, 0);
return 1;
}
diff --git a/src/StatisticsManager.cpp b/src/StatisticsManager.cpp
index 0effaf3..f63fb2d 100644
--- a/src/StatisticsManager.cpp
+++ b/src/StatisticsManager.cpp
@@ -1,803 +1,810 @@
/*
* Copyright (C) 2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "StatisticsManager.h"
#include "FileManager.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "Functions.h"
#include "LevelPackManager.h"
#include "MusicManager.h"
#include "SoundManager.h"
#include "ThemeManager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include "libs/tinyformat/tinyformat.h"
#include <SDL_ttf.h>
#if defined(WIN32)
#define PRINTF_LONGLONG "%I64d"
#else
#define PRINTF_LONGLONG "%lld"
#endif
using namespace std;
StatisticsManager statsMgr;
static const int achievementDisplayTime=(FPS*4500)/1000;
static const int achievementIntervalTime=achievementDisplayTime+(FPS*500)/1000;
static map<string,AchievementInfo*> avaliableAchievements;
//================================================================
StatisticsManager::StatisticsManager(){
bmDropShadow=NULL;
bmQuestionMark=NULL;
bmAchievement=NULL;
startTime=time(NULL);
tutorialLevels=0;
clear();
}
void StatisticsManager::clear(){
playerTravelingDistance=shadowTravelingDistance=0.0f;
playerJumps=shadowJumps
=playerDies=shadowDies
=playerSquashed=shadowSquashed
=completedLevels=silverLevels=goldLevels
=recordTimes=switchTimes=swapTimes=saveTimes=loadTimes
=playTime=levelEditTime
=createdLevels=tutorialCompleted=tutorialGold=0;
achievements.clear();
queuedAchievements.clear();
achievementTime=0;
currentAchievement=0;
if(bmAchievement){
bmAchievement.reset();
}
}
#define LOAD_STATS(var,func) { \
vector<string> &v=node.attributes[ #var ]; \
if(!v.empty() && !v[0].empty()) \
var=func(v[0].c_str()); \
}
void StatisticsManager::loadFile(const std::string& fileName){
clear();
ifstream file(fileName.c_str());
if(!file) return;
TreeStorageNode node;
POASerializer serializer;
if(!serializer.readNode(file,&node,true)) return;
//load statistics
LOAD_STATS(playerTravelingDistance,atof);
LOAD_STATS(shadowTravelingDistance,atof);
LOAD_STATS(playerJumps,atoi);
LOAD_STATS(shadowJumps,atoi);
LOAD_STATS(playerDies,atoi);
LOAD_STATS(shadowDies,atoi);
LOAD_STATS(playerSquashed,atoi);
LOAD_STATS(shadowSquashed,atoi);
LOAD_STATS(recordTimes,atoi);
LOAD_STATS(switchTimes,atoi);
LOAD_STATS(swapTimes,atoi);
LOAD_STATS(saveTimes,atoi);
LOAD_STATS(loadTimes,atoi);
LOAD_STATS(playTime,atoi);
LOAD_STATS(levelEditTime,atoi);
LOAD_STATS(createdLevels,atoi);
//load achievements.
//format is: name;time,name;time,...
{
vector<string> &v=node.attributes["achievements"];
for(unsigned int i=0;i<v.size();i++){
string s=v[i];
time_t t=0;
string::size_type lps=s.find(';');
if(lps!=string::npos){
string s1=s.substr(lps+1);
s=s.substr(0,lps);
long long n;
sscanf(s1.c_str(),PRINTF_LONGLONG,&n);
t=(time_t)n;
}
map<string,AchievementInfo*>::iterator it=avaliableAchievements.find(s);
if(it!=avaliableAchievements.end()){
OwnedAchievement ach={t,it->second};
achievements[it->first]=ach;
}
}
}
}
//Call when level edit is start
void StatisticsManager::startLevelEdit(){
levelEditStartTime=time(NULL);
}
//Call when level edit is end
void StatisticsManager::endLevelEdit(){
levelEditTime+=time(NULL)-levelEditStartTime;
}
//update in-game time
void StatisticsManager::updatePlayTime(){
time_t endTime=time(NULL);
playTime+=endTime-startTime;
startTime=endTime;
}
#define SAVE_STATS(var,pattern) { \
sprintf(s,pattern,var); \
node.attributes[ #var ].push_back(s); \
}
void StatisticsManager::saveFile(const std::string& fileName){
char s[64];
//update in-game time
updatePlayTime();
ofstream file(fileName.c_str());
if(!file) return;
TreeStorageNode node;
//save statistics
SAVE_STATS(playerTravelingDistance,"%.2f");
SAVE_STATS(shadowTravelingDistance,"%.2f");
SAVE_STATS(playerJumps,"%d");
SAVE_STATS(shadowJumps,"%d");
SAVE_STATS(playerDies,"%d");
SAVE_STATS(shadowDies,"%d");
SAVE_STATS(playerSquashed,"%d");
SAVE_STATS(shadowSquashed,"%d");
SAVE_STATS(recordTimes,"%d");
SAVE_STATS(switchTimes,"%d");
SAVE_STATS(swapTimes,"%d");
SAVE_STATS(saveTimes,"%d");
SAVE_STATS(loadTimes,"%d");
SAVE_STATS(playTime,"%d");
SAVE_STATS(levelEditTime,"%d");
SAVE_STATS(createdLevels,"%d");
//save achievements.
//format is: name;time,name;time,...
{
vector<string>& v=node.attributes["achievements"];
for(map<string,OwnedAchievement>::iterator it=achievements.begin();it!=achievements.end();++it){
stringstream strm;
char s[32];
long long n=it->second.achievedTime;
sprintf(s,PRINTF_LONGLONG,n);
strm<<it->first<<";"<<s;
v.push_back(strm.str());
}
}
POASerializer serializer;
serializer.writeNode(&node,file,true,true);
}
void StatisticsManager::loadPicture(SDL_Renderer& renderer, ImageManager& imageManager){
//Load drop shadow picture
bmDropShadow=imageManager.loadTexture(getDataPath()+"gfx/dropshadow.png", renderer);
bmQuestionMark=imageManager.loadImage(getDataPath()+"gfx/menu/questionmark.png");
}
void StatisticsManager::registerAchievements(ImageManager& imageManager){
if(!avaliableAchievements.empty()) return;
for(int i=0;achievementList[i].id!=NULL;i++){
avaliableAchievements[achievementList[i].id]=&achievementList[i];
if(achievementList[i].imageFile!=NULL){
achievementList[i].imageSurface = imageManager.loadImage(getDataPath()+achievementList[i].imageFile);
}
}
}
void StatisticsManager::render(ImageManager&,SDL_Renderer &renderer){
if(achievementTime==0 && !bmAchievement && currentAchievement<(int)queuedAchievements.size()){
//create surface
bmAchievement=createAchievementSurface(renderer, queuedAchievements[currentAchievement++]);
//FIXME: Draw the box.
//drawGUIBox(0,0,bmAchievement->w,bmAchievement->h,bmAchievement,0xFFFFFF00);
//check if queue is empty
if(currentAchievement>=(int)queuedAchievements.size()){
queuedAchievements.clear();
currentAchievement=0;
}
//play a sound
getSoundManager()->playSound("achievement", 1, false, 32);
}
//check if we need to display achievements
if(bmAchievement){
achievementTime++;
if(achievementTime<=0){
return;
}else if(achievementTime<=5){
drawAchievement(renderer,achievementTime);
}else if(achievementTime<=achievementDisplayTime-5){
drawAchievement(renderer,5);
}else if(achievementTime<achievementDisplayTime){
drawAchievement(renderer,achievementDisplayTime-achievementTime);
}else if(achievementTime>=achievementIntervalTime){
if(bmAchievement){
bmAchievement.reset();
}
achievementTime=0;
}
}
}
void StatisticsManager::newAchievement(const std::string& id,bool save){
//check avaliable achievements
map<string,AchievementInfo*>::iterator it=avaliableAchievements.find(id);
if(it==avaliableAchievements.end()) return;
//check if already have this achievement
if(save){
map<string,OwnedAchievement>::iterator it2=achievements.find(id);
if(it2!=achievements.end()) return;
OwnedAchievement ach={time(NULL),it->second};
achievements[id]=ach;
}
//add it to queue
queuedAchievements.push_back(it->second);
}
time_t StatisticsManager::achievedTime(const std::string& id) {
auto it = achievements.find(id);
if (it == achievements.end()) return 0;
else return it->second.achievedTime;
}
float StatisticsManager::getAchievementProgress(AchievementInfo* info){
if(!strcmp(info->id,"experienced")){
return float(completedLevels)/50.0f*100.0f;
}
if(!strcmp(info->id,"expert")){
return float(goldLevels)/50.0f*100.0f;
}
if(!strcmp(info->id,"tutorial")){
if(tutorialLevels>0)
return float(tutorialCompleted)/float(tutorialLevels)*100.0f;
else
return 0.0f;
}
if(!strcmp(info->id,"tutorialGold")){
if(tutorialLevels>0)
return float(tutorialGold)/float(tutorialLevels)*100.0f;
else
return 0.0f;
}
if(!strcmp(info->id,"create50")){
return float(createdLevels)/50.0f*100.0f;
}
if(!strcmp(info->id,"frog")){
return float(playerJumps+shadowJumps)/1000.0f*100.0f;
}
if(!strcmp(info->id,"die50")){
return float(playerDies+shadowDies)/50.0f*100.0f;
}
if(!strcmp(info->id,"die1000")){
return float(playerDies+shadowDies)/1000.0f*100.0f;
}
if(!strcmp(info->id,"suqash50")){
return float(playerSquashed+shadowSquashed)/50.0f*100.0f;
}
if(!strcmp(info->id,"travel100")){
return (playerTravelingDistance+shadowTravelingDistance)/100.0f*100.0f;
}
if(!strcmp(info->id,"travel1k")){
return (playerTravelingDistance+shadowTravelingDistance)/1000.0f*100.0f;
}
if(!strcmp(info->id,"travel10k")){
return (playerTravelingDistance+shadowTravelingDistance)/10000.0f*100.0f;
}
if(!strcmp(info->id,"travel42k")){
return (playerTravelingDistance+shadowTravelingDistance)/42195.0f*100.0f;
}
if(!strcmp(info->id,"record100")){
return float(recordTimes)/100.0f*100.0f;
}
if(!strcmp(info->id,"record1k")){
return float(recordTimes)/1000.0f*100.0f;
}
if(!strcmp(info->id,"switch100")){
return float(switchTimes)/100.0f*100.0f;
}
if(!strcmp(info->id,"switch1k")){
return float(switchTimes)/1000.0f*100.0f;
}
if(!strcmp(info->id,"swap100")){
return float(swapTimes)/100.0f*100.0f;
}
if(!strcmp(info->id,"swap1k")){
return float(swapTimes)/1000.0f*100.0f;
}
//not found
return 0.0f;
}
SharedTexture StatisticsManager::createAchievementSurface(SDL_Renderer& renderer, AchievementInfo* info,SDL_Rect* rect,bool showTip,const time_t *achievedTime){
if(info==NULL || info->id==NULL) return NULL;
//prepare text
SurfacePtr title0(nullptr);
SurfacePtr title1(nullptr);
vector<SDL_Surface*> descSurfaces;
SDL_Color fg = objThemes.getTextColor(true);
int fontHeight=TTF_FontLineSkip(fontText);
bool showDescription=false;
bool showImage=false;
float achievementProgress=0.0f;
if(showTip){
title0.reset(TTF_RenderUTF8_Blended(fontText,_("New achievement:"),fg));
title1.reset(TTF_RenderUTF8_Blended(fontGUISmall,_(info->name),fg));
showDescription=showImage=true;
}else if(achievedTime){
char s[128];
strftime(s,sizeof(s),"%c",localtime(achievedTime));
stringstream strm;
tinyformat::format(strm,_("Achieved on %s"),s);
title0.reset(TTF_RenderUTF8_Blended(fontGUISmall,_(info->name),fg));
title1.reset(TTF_RenderUTF8_Blended(fontText,strm.str().c_str(),fg));
showDescription=showImage=true;
}else if(info->displayStyle==ACHIEVEMENT_HIDDEN){
title0.reset(TTF_RenderUTF8_Blended(fontGUISmall,_("Unknown achievement"),fg));
}else{
if(info->displayStyle==ACHIEVEMENT_PROGRESS){
achievementProgress=getAchievementProgress(info);
stringstream strm;
tinyformat::format(strm,_("Achieved %1.0f%%"),achievementProgress);
title1.reset(TTF_RenderUTF8_Blended(fontText,strm.str().c_str(),fg));
}else{
title1.reset(TTF_RenderUTF8_Blended(fontText,_("Not achieved"),fg));
}
title0.reset(TTF_RenderUTF8_Blended(fontGUISmall,_(info->name),fg));
showDescription= info->displayStyle==ACHIEVEMENT_ALL || info->displayStyle==ACHIEVEMENT_PROGRESS;
showImage=true;
}
if(info->description!=NULL && showDescription){
string description=_(info->description);
string::size_type lps=0,lpe;
for(;;){
lpe=description.find('\n',lps);
if(lpe==string::npos){
descSurfaces.push_back(TTF_RenderUTF8_Blended(fontText,(description.substr(lps)+' ').c_str(),fg));
break;
}else{
descSurfaces.push_back(TTF_RenderUTF8_Blended(fontText,(description.substr(lps,lpe-lps)+' ').c_str(),fg));
lps=lpe+1;
}
}
}
//calculate the size
int w=0,h=0,w1=8,h1=0;
if(title0!=NULL){
if(title0->w>w) w=title0->w;
h1+=title0->h;
}
if(title1!=NULL){
if(title1->w>w) w=title1->w;
h1+=title1->h;
/*//calc progress bar size
if(!showTip && !achievedTime && info->displayStyle==ACHIEVEMENT_PROGRESS){
h1+=4;
}*/
}
const int preferredImageWidth = 50;
const int preferredImageHeight = 50;
if(showImage){
if(info->imageSurface!=NULL){
// NEW: we have the preferred image size
const int width = std::max(info->r.w, preferredImageWidth);
const int height = std::max(info->r.h, preferredImageHeight);
w1+=width+8;
w+=width+8;
if(height>h1) h1=height;
}
}else{
w1+=bmQuestionMark->w+8;
w+=bmQuestionMark->w+8;
if(bmQuestionMark->h>h1) h1=bmQuestionMark->h;
}
h=h1+8;
for(unsigned int i=0;i<descSurfaces.size();i++){
if(descSurfaces[i]!=NULL){
if(descSurfaces[i]->w>w) w=descSurfaces[i]->w;
}
}
h+=descSurfaces.size()*fontHeight;
w+=16;
h+=16;
//check if size is specified
int left=0,top=0;
if(rect!=NULL){
//NOTE: SDL2 port. This was never used.
/* if(surface!=NULL){
left=rect->x;
top=rect->y;
}*/
if(rect->w>0) w=rect->w;
else rect->w=w;
rect->h=h;
}
//create surface if necessary
SurfacePtr surface = createSurface(w, h);
std::unique_ptr<SDL_Renderer,decltype(&SDL_DestroyRenderer)> surfaceRenderer(
SDL_CreateSoftwareRenderer(surface.get()), &SDL_DestroyRenderer);
//draw background
const SDL_Rect r={left,top,w,h};
if(showTip || achievedTime){
SDL_FillRect(surface.get(),&r,SDL_MapRGB(surface->format,255,255,255));
}else{
SDL_FillRect(surface.get(),&r,SDL_MapRGB(surface->format,192,192,192));
}
//draw horizontal separator
//FIXME: this is moved from StatisticsScreen::createGUI
if (!showTip) {
const SDL_Rect r0 = { left, top, w, 1 };
const SDL_Rect r1 = { left, top + h - 2, w, 1 };
const SDL_Rect r2 = { left, top + h - 1, w, 1 };
Uint32 c0 = achievedTime ? SDL_MapRGB(surface->format, 224, 224, 224) : SDL_MapRGB(surface->format, 168, 168, 168);
Uint32 c2 = achievedTime ? SDL_MapRGB(surface->format, 128, 128, 128) : SDL_MapRGB(surface->format, 96, 96, 96);
SDL_FillRect(surface.get(), &r0, c0);
SDL_FillRect(surface.get(), &r1, c0);
SDL_FillRect(surface.get(), &r2, c2);
}
//draw picture
if(showImage){
if(info->imageSurface){
// NEW: we have the preferred image size
SDL_Rect r={left+8,top+8+(h1-info->r.h)/2,0,0};
if (info->r.w < preferredImageWidth) r.x += (preferredImageWidth - info->r.w) / 2;
SDL_BlitSurface(info->imageSurface,&info->r,surface.get(),&r);
}
}else{
SDL_Rect r={left+8,top+8+(h1-bmQuestionMark->h)/2,0,0};
SDL_BlitSurface(bmQuestionMark,NULL,surface.get(),&r);
}
//draw text
h=8;
if(title0){
SDL_Rect r={left+w1,top+h,0,0};
SDL_BlitSurface(title0.get(),NULL,surface.get(),&r);
h+=title0->h;
}
if(title1){
SDL_Rect r={left+w1,top+h,0,0};
//Draw progress bar.
if(!showTip && !achievedTime && info->displayStyle==ACHIEVEMENT_PROGRESS){
//Draw borders.
SDL_Rect r1={r.x,r.y,w-8-r.x,title1->h};
drawGUIBox(r1.x,r1.y,r1.w,r1.h,*surfaceRenderer,0x1D);
//Draw progress.
r1.x++;
r1.y++;
r1.w=int(achievementProgress/100.0f*float(r1.w-2)+0.5f);
r1.h-=2;
SDL_SetRenderDrawColor(surfaceRenderer.get(),0,0,0,100);
SDL_RenderFillRect(surfaceRenderer.get(),&r1);
//shift the text a little bit (???)
r.x+=2;
r.y+=2;
}
//Draw text.
SDL_BlitSurface(title1.get(),NULL,surface.get(),&r);
}
h=h1+16;
for(unsigned int i=0;i<descSurfaces.size();i++){
if(descSurfaces[i]!=NULL){
SDL_Rect r={left+8,top+h+static_cast<int>(i)*fontHeight,0,0};
SDL_BlitSurface(descSurfaces[i],NULL,surface.get(),&r);
}
}
//clean up
for(unsigned int i=0;i<descSurfaces.size();i++){
if(descSurfaces[i]!=NULL){
SDL_FreeSurface(descSurfaces[i]);
}
}
//FIXME: Should we clear the vector here?
//over
return textureFromSurface(renderer, std::move(surface));
}
void StatisticsManager::drawAchievement(SDL_Renderer& renderer,int alpha){
if(!bmAchievement || alpha<=0) {
return;
}
if(alpha>5) alpha=5;
SDL_Rect r = rectFromTexture(*bmAchievement);
int w=0,h=0;
SDL_GetRendererOutputSize(&renderer, &w, &h);
r.x = w-32-r.w;
r.y = 32;
SDL_SetTextureAlphaMod(bmAchievement.get(), alpha*40);
applyTexture(r.x, r.y,bmAchievement, renderer);
if(!bmDropShadow) {
return;
}
//draw drop shadow - corner
{
int w1=r.w/2,w2=r.w-w1,h1=r.h/2,h2=r.h-h1;
if(w1>16) w1=16;
if(w2>16) w2=16;
if(h1>16) h1=16;
if(h2>16) h2=16;
const int x=(5-alpha)*64;
//top-left
SDL_Rect r1={x,0,w1+16,h1+16};//),r2={r.x-16,r.y-16,0,0};
SDL_Rect r2 ={r.x-16, r.y-16, r1.w, r1.h};
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//top-right
r1.x=x+48-w2;r2.w=r1.w =w2+16;r2.x=r.x+r.w-w2;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//bottom-right
r1.y=48-h2;r2.h=r1.h=h2+16;r2.y=r.y+r.h-h2;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//bottom-left
r1.x=x;r2.w=r1.w=w1+16;r2.x=r.x-16;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
}
//draw drop shadow - border
int i=r.w-32;
while(i>0){
const int ii=i>128?128:i;
//top
SDL_Rect r1={0,256-alpha*16,ii,16};
SDL_Rect r2={r.x+r.w-16-i,r.y-16,r1.w,r1.h};
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//bottom
r1.x=128;r2.y=r.y+r.h;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
i-=ii;
}
i=r.h-32;
while(i>0){
const int ii=i>128?128:i;
//top
SDL_Rect r1={512-alpha*16,0,16,ii};
SDL_Rect r2={r.x-16,r.y+r.h-16-i, r1.w, r1.h};
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//bottom
r1.y=128;r2.x=r.x+r.w;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
i-=ii;
}
}
void StatisticsManager::reloadCompletedLevelsAndAchievements(){
completedLevels=silverLevels=goldLevels=0;
LevelPackManager *lpm=getLevelPackManager();
vector<pair<string,string> > v=lpm->enumLevelPacks();
bool tutorial=false,tutorialIsGold=false;
for(unsigned int i=0;i<v.size();i++){
string& s=v[i].first;
LevelPack *levels=lpm->getLevelPack(s);
levels->loadProgress();
bool b=false;
if(s==lpm->tutorialLevelPackPath){
tutorialLevels=levels->getLevelCount();
tutorialCompleted=tutorialGold=0;
b=tutorial=tutorialIsGold=true;
}
for(int n=0,m=levels->getLevelCount();n<m;n++){
LevelPack::Level *lv=levels->getLevel(n);
int medal=lv->won;
if(medal){
if(lv->targetTime<0 || lv->time<=lv->targetTime)
medal++;
if(lv->targetRecordings<0 || lv->recordings<=lv->targetRecordings)
medal++;
completedLevels++;
if(b) tutorialCompleted++;
if(medal==2) silverLevels++;
if(medal==3){
goldLevels++;
if(b) tutorialGold++;
}
if(medal!=3 && b) tutorialIsGold=false;
}else if(b){
tutorial=tutorialIsGold=false;
}
}
}
//upadte achievements
updateLevelAchievements();
updateTutorialAchievementsInternal((tutorial?1:0)|(tutorialIsGold?2:0));
}
void StatisticsManager::reloadOtherAchievements(){
int i;
if(playTime>=7200) newAchievement("addicted");
if(playTime>=86400) newAchievement("loyalFan");
if(levelEditTime>=7200) newAchievement("constructor");
if(levelEditTime>=86400) newAchievement("constructor2");
if(createdLevels>=1) newAchievement("create1");
if(createdLevels>=50) newAchievement("create50");
i=playerJumps+shadowJumps;
if(i>=1000) newAchievement("frog");
i=playerDies+shadowDies;
if(i>=1) newAchievement("die1");
if(i>=50) newAchievement("die50");
if(i>=1000) newAchievement("die1000");
i=playerSquashed+shadowSquashed;
if(i>=1) newAchievement("squash1");
if(i>=50) newAchievement("squash50");
float d=playerTravelingDistance+shadowTravelingDistance;
if(d>=100.0f) newAchievement("travel100");
if(d>=1000.0f) newAchievement("travel1k");
if(d>=10000.0f) newAchievement("travel10k");
if(d>=42195.0f) newAchievement("travel42k");
if(recordTimes>=100) newAchievement("record100");
if(recordTimes>=1000) newAchievement("record1k");
if(switchTimes>=100) newAchievement("switch100");
if(switchTimes>=1000) newAchievement("switch1k");
if(swapTimes>=100) newAchievement("swap100");
if(swapTimes>=1000) newAchievement("swap1k");
if(saveTimes>=1000) newAchievement("save1k");
if(loadTimes>=1000) newAchievement("load1k");
- if(version.find("Development")!=string::npos) newAchievement("programmer");
+ if (version.find("Development") != string::npos
+ || version.find("Alpha") != string::npos
+ || version.find("Beta") != string::npos
+ || version.find("RC") != string::npos
+ || version.find("Candidate") != string::npos)
+ {
+ newAchievement("programmer");
+ }
}
//Update level specified achievements.
//Make sure the completed level count is correct.
void StatisticsManager::updateLevelAchievements(){
if(completedLevels>=1) newAchievement("newbie");
if(goldLevels>=1) newAchievement("goodjob");
if(completedLevels>=50) newAchievement("experienced");
if(goldLevels>=50) newAchievement("expert");
}
//Update tutorial specified achievements.
//Make sure the level progress of tutorial is correct.
void StatisticsManager::updateTutorialAchievements(){
//find tutorial level pack
LevelPackManager *lpm=getLevelPackManager();
LevelPack *levels=lpm->getTutorialLevelPack();
if(levels==NULL) return;
bool tutorial=true,tutorialIsGold=true;
tutorialLevels=levels->getLevelCount();
tutorialCompleted=tutorialGold=0;
for(int n=0,m=levels->getLevelCount();n<m;n++){
LevelPack::Level *lv=levels->getLevel(n);
int medal=lv->won;
if(medal){
if(lv->targetTime<0 || lv->time<=lv->targetTime)
medal++;
if(lv->targetRecordings<0 || lv->recordings<=lv->targetRecordings)
medal++;
tutorialCompleted++;
if(medal!=3) tutorialIsGold=false;
else tutorialGold++;
}else{
tutorial=tutorialIsGold=false;
break;
}
}
//upadte achievements
updateTutorialAchievementsInternal((tutorial?1:0)|(tutorialIsGold?2:0));
}
//internal function
//flags: a bit-field value indicates which achievements we have.
void StatisticsManager::updateTutorialAchievementsInternal(int flags){
if(flags&1) newAchievement("tutorial");
if(flags&2) newAchievement("tutorialGold");
}
diff --git a/src/StatisticsScreen.cpp b/src/StatisticsScreen.cpp
index 41ee1ac..f4307de 100644
--- a/src/StatisticsScreen.cpp
+++ b/src/StatisticsScreen.cpp
@@ -1,375 +1,411 @@
/*
* Copyright (C) 2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string>
#include <vector>
#include <map>
#include "StatisticsManager.h"
#include "StatisticsScreen.h"
#include "Globals.h"
#include "Functions.h"
#include "ThemeManager.h"
#include "InputManager.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
+#include "EasterEggScreen.h"
#include <SDL_ttf.h>
#include <array>
using namespace std;
//GUI events are handled here.
//name: The name of the element that invoked the event.
//obj: Pointer to the object that invoked the event.
//eventType: Integer containing the type of event.
void StatisticsScreen::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Check what type of event it was.
if(eventType==GUIEventClick){
if(name=="cmdBack"){
//Goto the main menu.
setNextState(STATE_MENU);
}
}
}
//Constructor.
StatisticsScreen::StatisticsScreen(ImageManager& imageManager, SDL_Renderer& renderer){
//Update in-game time.
statsMgr.updatePlayTime();
//Render the title.
title = titleTextureFromText(renderer, _("Achievements and Statistics"), objThemes.getTextColor(false), SCREEN_WIDTH);
//Create GUI.
createGUI(imageManager, renderer);
}
//Destructor.
StatisticsScreen::~StatisticsScreen(){
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//we are so lazy that we just use height of the first text, ignore the others
#define DRAW_PLAYER_STATISTICS(name,var,fmt) { \
SurfacePtr surface(TTF_RenderUTF8_Blended(fontGUISmall,name,objThemes.getTextColor(true))); \
SurfacePtr stats = createSurface(w,surface->h); \
SDL_FillRect(stats.get(),NULL,-1); \
applySurface(4,0,surface.get(),stats.get(),NULL); \
y=surface->h; \
SDL_snprintf(formatString.data(),formatString.size(),fmt,statsMgr.player##var+statsMgr.shadow##var); \
surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true))); \
applySurface(w-260-surface->w,(y-surface->h)/2,surface.get(),stats.get(),NULL); \
SDL_snprintf(formatString.data(),formatString.size(),fmt,statsMgr.player##var); \
surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true))); \
applySurface(w-140-surface->w,(y-surface->h)/2,surface.get(),stats.get(),NULL); \
SDL_snprintf(formatString.data(),formatString.size(),fmt,statsMgr.shadow##var); \
surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true))); \
applySurface(w-20-surface->w,(y-surface->h)/2,surface.get(),stats.get(),NULL); \
list->addItem(renderer,"",textureFromSurface(renderer, std::move(stats))); /* add it to list box */ \
}
//Add an item to the listbox, that displays "name1", and "var1" formatted with "format"
//we are so lazy that we just use height of the first text, ignore the others
template <class T1>
static void drawMiscStatistics1(SDL_Renderer& renderer, int w,GUIListBox *list,const char* name1,const T1 var1,const char* format1){
//create new surface
SurfacePtr nameSurface(TTF_RenderUTF8_Blended(fontGUISmall,name1,objThemes.getTextColor(true)));
SurfacePtr stats=createSurface(w, nameSurface->h);
SDL_FillRect(stats.get(),NULL,-1);
applySurface(4,0,nameSurface.get(),stats.get(),NULL);
const int x=nameSurface->w+8;
const int y=nameSurface->h;
//draw value
//char s[1024];
std::array<char, 1024> s;
SDL_snprintf(s.data(),s.size(),format1,var1);
SurfacePtr formatSurface(TTF_RenderUTF8_Blended(fontText,s.data(),objThemes.getTextColor(true)));
//NOTE: SDL2 port. Not halving the y value here as this ends up looking better.
applySurface(x,y-formatSurface->h,formatSurface.get(),stats.get(),NULL);
//add it to list box
list->addItem(renderer, "",textureFromSurface(renderer, std::move(stats)));
//over
//return stats;
}
//NOTE: Disabled this for the SDL2 port for now. It looks a bit off anyhow.
//Might want to make a more general method that draws as many "cells" as there is space.
//Draws two stats on one line if there is space.
//we are so lazy that we just use height of the first text, ignore the others
/*template <class T1,class T2>
static void drawMiscStatistics2(int w,GUIListBox *list,const char* name1,const T1 var1,const char* format1,const char* name2,const T2 var2,const char* format2){
SDL_Surface* stats=drawMiscStatistics1(w,list,name1,var1,format1);
//Check if the width is enough
if(w>=800){
//draw name
SDL_Surface* surface=TTF_RenderUTF8_Blended(fontGUISmall,name2,objThemes.getTextColor(true));
applySurface(w/2-8,stats->h-surface->h,surface,stats,NULL);
int x=surface->w+w/2;
SDL_FreeSurface(surface);
//draw value
char s[1024];
sprintf(s,format2,var2);
surface=TTF_RenderUTF8_Blended(fontText,s,objThemes.getTextColor(true));
applySurface(x,(stats->h-surface->h)/2,surface,stats,NULL);
SDL_FreeSurface(surface);
}else{
//Split into two rows
drawMiscStatistics1(w,list,name2,var2,format2);
}
}*/
+void StatisticsScreen::addAchievements(ImageManager& imageManager, SDL_Renderer &renderer, GUIListBox *list, bool revealUnknownAchievements) {
+ for (int idx = 0; achievementList[idx].id != NULL; ++idx) {
+ time_t *lpt = NULL;
+
+ map<string, OwnedAchievement>::iterator it = statsMgr.achievements.find(achievementList[idx].id);
+ if (it != statsMgr.achievements.end()) {
+ lpt = &it->second.achievedTime;
+ }
+
+ AchievementInfo info = achievementList[idx];
+ if (revealUnknownAchievements) {
+ if (info.displayStyle == ACHIEVEMENT_HIDDEN || info.displayStyle == ACHIEVEMENT_TITLE) {
+ info.displayStyle = ACHIEVEMENT_ALL;
+ }
+ }
+
+ SDL_Rect r;
+ r.x = r.y = 0;
+ r.w = list->width - 16;
+ auto surface = statsMgr.createAchievementSurface(renderer, &info, &r, false, lpt);
+
+ if (surface){
+ list->addItem(renderer, "", surface);
+ }
+ }
+}
+
//Method that will create the GUI.
void StatisticsScreen::createGUI(ImageManager& imageManager, SDL_Renderer &renderer){
//Create the root element of the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Create back button.
GUIObject* obj=new GUIButton(imageManager,renderer,SCREEN_WIDTH*0.5,SCREEN_HEIGHT-60,-1,36,_("Back"),0,true,true,GUIGravityCenter);
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
//Create list box.
listBox=new GUISingleLineListBox(imageManager,renderer,(SCREEN_WIDTH-500)/2,104,500,32);
listBox->addItem(_("Achievements"));
listBox->addItem(_("Statistics"));
listBox->value=0;
GUIObjectRoot->addChild(listBox);
//Create list box for achievements.
GUIListBox *list=new GUIListBox(imageManager,renderer,64,150,SCREEN_WIDTH-128,SCREEN_HEIGHT-150-72);
list->selectable=false;
GUIObjectRoot->addChild(list);
lists.clear();
lists.push_back(list);
- for(int idx=0;achievementList[idx].id!=NULL;++idx){
- time_t *lpt=NULL;
-
- map<string,OwnedAchievement>::iterator it=statsMgr.achievements.find(achievementList[idx].id);
- if(it!=statsMgr.achievements.end()){
- lpt=&it->second.achievedTime;
- }
-
- SDL_Rect r;
- r.x=r.y=0;
- r.w=list->width-16;
- auto surface= statsMgr.createAchievementSurface(renderer, &achievementList[idx],&r,false,lpt);
-
- if(surface){
- //FIXME - this is broken with SDL2 as the function now operates on a renderer, not sure how best to fix it
-/* hlineRGBA(surface,0,surface->w,0,0,0,0,32);
- hlineRGBA(surface,0,surface->w,surface->h-1,0,0,0,128);
- hlineRGBA(surface,0,surface->w,surface->h-2,0,0,0,32);*/
- list->addItem(renderer, "",surface);
- }
- }
+ addAchievements(imageManager, renderer, list);
//Now create list box for statistics.
list=new GUIListBox(imageManager,renderer,64,150,SCREEN_WIDTH-128,SCREEN_HEIGHT-150-72,true,false);
list->selectable=false;
GUIObjectRoot->addChild(list);
lists.push_back(list);
//Load needed pictures.
//FIXME: hard-coded image path
//TODO: Might want to consider not caching these as most other stuff use textures now.
SDL_Surface* bmPlayer=imageManager.loadImage(getDataPath()+"themes/Cloudscape/characters/player.png");
SDL_Surface* bmShadow=imageManager.loadImage(getDataPath()+"themes/Cloudscape/characters/shadow.png");
SDL_Surface* bmMedal=imageManager.loadImage(getDataPath()+"gfx/medals.png");
//Render stats.
//char s[64],s2[64];
std::array<char, 64> formatString;
SDL_Rect r;
int x,y,w=SCREEN_WIDTH-128;
SharedTexture h_bar = [&](){
//The horizontal bar.
SurfacePtr h_bar(createSurface(w,2));
SDL_Color c = objThemes.getTextColor(true);
Uint32 clr=SDL_MapRGB(h_bar->format,c.r,c.g,c.b);
SDL_FillRect(h_bar.get(),NULL,clr);
return textureFromSurface(renderer, std::move(h_bar));
}();
//Player and shadow specific statistics
//The header.
{
SurfacePtr stats = createSurface(w, 44);
SDL_FillRect(stats.get(),NULL,-1);
SurfacePtr surface(TTF_RenderUTF8_Blended(fontGUISmall,_("Total"),objThemes.getTextColor(true)));
applySurface(w-260-surface->w,stats->h-surface->h,surface.get(),stats.get(),NULL);
//FIXME: hard-coded player and shadow images
r.x=0;r.y=0;r.w=23;r.h=40;
applySurface(w-140-r.w,stats.get()->h-40,bmPlayer,stats.get(),&r);
applySurface(w-20-r.w,stats.get()->h-40,bmShadow,stats.get(),&r);
list->addItem(renderer, "",textureFromSurface(renderer, std::move(stats)));
}
//Each items.
{
DRAW_PLAYER_STATISTICS(_("Traveling distance (m)"),TravelingDistance,"%0.1f");
DRAW_PLAYER_STATISTICS(_("Jump times"),Jumps,"%d");
DRAW_PLAYER_STATISTICS(_("Die times"),Dies,"%d");
DRAW_PLAYER_STATISTICS(_("Squashed times"),Squashed,"%d");
}
//Game specific statistics.
list->addItem(renderer, "",h_bar);
auto drawMiscStats = [&](const char* name1,const int var1,const char* format1) {
drawMiscStatistics1(renderer, w, list, name1, var1, format1);
};
drawMiscStats(_("Recordings:"),statsMgr.recordTimes,"%d");
drawMiscStats(_("Switch pulled times:"),statsMgr.switchTimes,"%d");
drawMiscStats(_("Swap times:"),statsMgr.swapTimes,"%d");
drawMiscStats(_("Save times:"),statsMgr.saveTimes,"%d");
drawMiscStats(_("Load times:"),statsMgr.loadTimes,"%d");
//Level specific statistics
list->addItem(renderer, "",h_bar);
{
SurfacePtr surface(TTF_RenderUTF8_Blended(fontGUISmall,_("Completed levels:"),objThemes.getTextColor(true)));
SurfacePtr stats = createSurface(w, surface->h);
SDL_FillRect(stats.get(),NULL,-1);
applySurface(4,0,surface.get(),stats.get(),NULL);
x=surface->w+8;
y=surface->h;
SDL_snprintf(formatString.data(), formatString.size(),"%d",statsMgr.completedLevels);
surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true)));
applySurface(x,(y-surface->h),surface.get(),stats.get(),NULL);
SDL_snprintf(formatString.data(), formatString.size(),"%d",statsMgr.completedLevels-statsMgr.goldLevels-statsMgr.silverLevels);
surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true)));
applySurface(w-260-surface->w,(y-surface->h)/2,surface.get(),stats.get(),NULL);
r.x=0;r.y=0;r.w=30;r.h=30;
applySurface(w-260-surface->w-30,(y-30)/2,bmMedal,stats.get(),&r);
SDL_snprintf(formatString.data(), formatString.size(),"%d",statsMgr.silverLevels);
surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true)));
applySurface(w-140-surface->w,(y-surface->h)/2,surface.get(),stats.get(),NULL);
r.x+=30;
applySurface(w-140-surface->w-30,(y-30)/2,bmMedal,stats.get(),&r);
SDL_snprintf(formatString.data(), formatString.size(),"%d",statsMgr.goldLevels);
surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true)));
applySurface(w-20-surface->w,(y-surface->h)/2,surface.get(),stats.get(),NULL);
r.x+=30;
applySurface(w-20-surface->w-30,(y-30)/2,bmMedal,stats.get(),&r);
list->addItem(renderer,"",textureFromSurface(renderer, std::move(stats)));
}
//Other statistics.
list->addItem(renderer, "",h_bar);
SDL_snprintf(formatString.data(), formatString.size(),"%02d:%02d:%02d",statsMgr.playTime/3600,(statsMgr.playTime/60)%60,statsMgr.playTime%60);
drawMiscStatistics1(renderer,w,list,_("In-game time:"),formatString.data(),"%s");
SDL_snprintf(formatString.data(), formatString.size(),"%02d:%02d:%02d",statsMgr.levelEditTime/3600,(statsMgr.levelEditTime/60)%60,statsMgr.levelEditTime%60);
drawMiscStatistics1(renderer,w,list,_("Level editing time:"),formatString.data(),"%s");
drawMiscStats(_("Created levels:"),statsMgr.createdLevels,"%d");
}
//In this method all the key and mouse events should be handled.
//NOTE: The GUIEvents won't be handled here.
-void StatisticsScreen::handleEvents(ImageManager&, SDL_Renderer&){
+void StatisticsScreen::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//Check if we need to quit, if so enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//Check horizontal movement
int value = listBox->value;
if (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
isKeyboardOnly = true;
value++;
if (value >= (int)listBox->item.size()) value = 0;
} else if (inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
isKeyboardOnly = true;
value--;
if (value < 0) value = listBox->item.size() - 1;
}
listBox->value = value;
//Check vertical movement
if (value >= 0 && value < (int)lists.size()) {
if (inputMgr.isKeyDownEvent(INPUTMGR_UP)){
isKeyboardOnly = true;
lists[value]->scrollScrollbar(-1);
} else if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
isKeyboardOnly = true;
lists[value]->scrollScrollbar(1);
}
}
+ //Yet another cheat "ls -la" which reveals all unknown achievements
+ static char input[6];
+ static int inputLen = 0;
+ if (value == 0) {
+ if (event.type == SDL_KEYDOWN) {
+ if (event.key.keysym.sym >= 32 && event.key.keysym.sym <= 126) {
+ if (inputLen < sizeof(input)) input[inputLen] = event.key.keysym.sym;
+ inputLen++;
+ } else {
+ if (event.key.keysym.sym == SDLK_RETURN && inputLen == 6 &&
+ input[0] == 'l' && input[1] == 's' && input[2] == ' ' && input[3] == '-' && input[4] == 'l' && input[5] == 'a')
+ {
+ if (easterEggScreen(imageManager, renderer)) {
+ //new achievement
+ statsMgr.newAchievement("cheat");
+
+ //reload achievement list with hidden achievements revealed
+ lists[0]->clearItems();
+ addAchievements(imageManager, renderer, lists[0], true);
+ }
+ }
+ inputLen = 0;
+ }
+ }
+ } else {
+ inputLen = 0;
+ }
+
//Check if the escape button is pressed, if so go back to the main menu.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
}
//All the logic that needs to be done should go in this method.
void StatisticsScreen::logic(ImageManager&, SDL_Renderer&){
}
//This method handles all the rendering.
void StatisticsScreen::render(ImageManager&, SDL_Renderer& renderer){
//Draw background.
objThemes.getBackground(true)->draw(renderer);
objThemes.getBackground(true)->updateAnimation();
//Draw title.
drawTitleTexture(SCREEN_WIDTH, *title, renderer);
//Draw statistics.
int value=listBox->value;
for(unsigned int i=0;i<lists.size();i++){
lists[i]->visible=(i==value);
}
}
//Method that will be called when the screen size has been changed in runtime.
void StatisticsScreen::resize(ImageManager &imageManager, SDL_Renderer &renderer){
//Recreate the gui to fit the new resolution.
createGUI(imageManager, renderer);
}
diff --git a/src/StatisticsScreen.h b/src/StatisticsScreen.h
index d2f448a..5157777 100644
--- a/src/StatisticsScreen.h
+++ b/src/StatisticsScreen.h
@@ -1,68 +1,74 @@
/*
* Copyright (C) 2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef STATISTICSSCREEN_H
#define STATISTICSSCREEN_H
#include <SDL.h>
#include "GameState.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include "Render.h"
class StatisticsScreen:public GameState, private GUIEventCallback{
private:
//Contains title.
TexturePtr title;
//The list box used to switch between statistics and achievements.
GUISingleLineListBox* listBox;
//The list widgets used for achievements and statistics.
std::vector<GUIListBox*> lists;
//GUI events are handled here.
//name: The name of the element that invoked the event.
//obj: Pointer to the object that invoked the event.
//eventType: Integer containing the type of event.
void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType);
+
+ //Add the list of achievements to the GUIListBox.
+ //list: The list box.
+ //revealUnknownAchievements: Reveal the name and description of unknown achievements. Considered as cheating.
+ void addAchievements(ImageManager& imageManager, SDL_Renderer &renderer, GUIListBox *list, bool revealUnknownAchievements = false);
+
public:
//Constructor.
StatisticsScreen(ImageManager &imageManager, SDL_Renderer& renderer);
//Destructor.
virtual ~StatisticsScreen();
//Method that will create the GUI for the options menu.
void createGUI(ImageManager &imageManager, SDL_Renderer& renderer);
//In this method all the key and mouse events should be handled.
//NOTE: The GUIEvents won't be handled here.
virtual void handleEvents(ImageManager&, SDL_Renderer&) override;
//All the logic that needs to be done should go in this method.
virtual void logic(ImageManager&, SDL_Renderer&) override;
//This method handles all the rendering.
virtual void render(ImageManager&, SDL_Renderer& renderer) override;
//Method that will be called when the screen size has been changed in runtime.
virtual void resize(ImageManager& imageManager, SDL_Renderer& renderer) override;
};
#endif
diff --git a/src/ThemeManager.cpp b/src/ThemeManager.cpp
index 6117e99..d0f0006 100644
--- a/src/ThemeManager.cpp
+++ b/src/ThemeManager.cpp
@@ -1,1618 +1,1620 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ThemeManager.h"
#include "POASerializer.h"
#include "Functions.h"
#include "FileManager.h"
#include "Game.h"
#include "ImageManager.h"
#include <iostream>
using namespace std;
//The ThemeStack that is be used by the GameState.
ThemeStack objThemes;
ThemeObjectInstance::ThemeObjectInstance()
: picture(NULL), parent(NULL), animation(0), savedAnimation(0)
{
}
void ThemeObjectInstance::resetAnimation(bool save){
animation = 0;
if (save){
savedAnimation = 0;
}
}
void ThemeObjectInstance::saveAnimation(){
savedAnimation = animation;
}
void ThemeObjectInstance::loadAnimation(){
animation = savedAnimation;
}
ThemeBlockStateInstance::ThemeBlockStateInstance()
: parent(NULL), animation(0), savedAnimation(0)
{
}
void ThemeBlockStateInstance::draw(SDL_Renderer& renderer, int x, int y, int w, int h, const SDL_Rect *clipRect){
for (unsigned int i = 0; i<objects.size(); i++){
objects[i].draw(renderer, x, y, w, h, clipRect);
}
}
void ThemeBlockStateInstance::updateAnimation(){
for (unsigned int i = 0; i<objects.size(); i++){
objects[i].updateAnimation();
}
animation++;
}
void ThemeBlockStateInstance::resetAnimation(bool save){
for (unsigned int i = 0; i<objects.size(); i++){
objects[i].resetAnimation(save);
}
animation = 0;
if (save){
savedAnimation = 0;
}
}
void ThemeBlockStateInstance::saveAnimation(){
for (unsigned int i = 0; i<objects.size(); i++){
objects[i].saveAnimation();
}
savedAnimation = animation;
}
void ThemeBlockStateInstance::loadAnimation(){
for (unsigned int i = 0; i<objects.size(); i++){
objects[i].loadAnimation();
}
animation = savedAnimation;
}
ThemeBlockInstance::ThemeBlockInstance()
: currentState(NULL)
{
}
bool ThemeBlockInstance::draw(SDL_Renderer& renderer, int x, int y, int w, int h, const SDL_Rect *clipRect){
if (currentState != NULL){
currentState->draw(renderer, x, y, w, h, clipRect);
return true;
}
return false;
}
bool ThemeBlockInstance::drawState(const string& s, SDL_Renderer& renderer, int x, int y, int w, int h, SDL_Rect *clipRect){
map<string, ThemeBlockStateInstance>::iterator it = blockStates.find(s);
if (it != blockStates.end()){
it->second.draw(renderer, x, y, w, h, clipRect);
return true;
}
return false;
}
bool ThemeBlockInstance::changeState(const string& s, bool reset){
bool newState = false;
//First check if there's a transition.
{
pair<string, string> s1 = pair<string, string>(currentStateName, s);
map<pair<string, string>, ThemeBlockStateInstance>::iterator it = transitions.find(s1);
if (it != transitions.end()){
currentState = &it->second;
//NOTE: We set the currentState name to target state name.
//Worst case senario is that the animation is skipped when saving/loading at a checkpoint.
currentStateName = s;
newState = true;
}
}
//If there isn't a transition go directly to the state.
if (!newState){
//Get the new state.
map<string, ThemeBlockStateInstance>::iterator it = blockStates.find(s);
//Check if it exists.
if (it != blockStates.end()){
currentState = &it->second;
currentStateName = it->first;
newState = true;
}
}
//Check if a state has been found.
if (newState){
//FIXME: Is it needed to set the savedStateName here?
if (savedStateName.empty())
savedStateName = currentStateName;
//If reset then reset the animation.
if (reset)
currentState->resetAnimation(true);
return true;
}
//It doesn't so return false.
return false;
}
void ThemeBlockInstance::resetAnimation(bool save){
for (map<string, ThemeBlockStateInstance>::iterator it = blockStates.begin(); it != blockStates.end(); ++it){
it->second.resetAnimation(save);
}
if (save){
savedStateName.clear();
}
}
void ThemeBlockInstance::saveAnimation(){
for (map<string, ThemeBlockStateInstance>::iterator it = blockStates.begin(); it != blockStates.end(); ++it){
it->second.saveAnimation();
}
savedStateName = currentStateName;
}
void ThemeBlockInstance::loadAnimation(){
for (map<string, ThemeBlockStateInstance>::iterator it = blockStates.begin(); it != blockStates.end(); ++it){
it->second.loadAnimation();
}
changeState(savedStateName, false);
}
ThemeOffsetData::ThemeOffsetData()
: length(0)
{
}
void ThemeOffsetData::destroy(){
//Set length to zero.
length = 0;
//And clear the offsetData vector.
offsetData.clear();
}
//Constructor.
ThemePositioningData::ThemePositioningData()
: horizontalAlign(REPEAT), verticalAlign(REPEAT)
{
}
//Method used to destroy the positioningData.
void ThemePositioningData::destroy(){
horizontalAlign = REPEAT;
verticalAlign = REPEAT;
}
ThemePicture::ThemePicture()
:texture(NULL)//, x(0), y(0)
{
}
void ThemePicture::destroy(){
//Freeing handled by ImageManager.
//TODO: Unload unused images
texture = NULL;
//Destroy the offset data.
offset.destroy();
}
ThemeObject::ThemeObject()
:animationLength(0), animationLoopPoint(0), invisibleAtRunTime(false), invisibleAtDesignTime(false)
{
}
ThemeObject::~ThemeObject(){
//Loop through the optionalPicture and delete them.
for (unsigned int i = 0; i<optionalPicture.size(); i++){
delete optionalPicture[i].second;
}
}
void ThemeObject::destroy(){
//Loop through the optionalPicture and delete them.
for (unsigned int i = 0; i<optionalPicture.size(); i++){
delete optionalPicture[i].second;
}
optionalPicture.clear();
animationLength = 0;
animationLoopPoint = 0;
invisibleAtRunTime = false;
invisibleAtDesignTime = false;
picture.destroy();
editorPicture.destroy();
offset.destroy();
positioning.destroy();
}
ThemeBlockState::ThemeBlockState()
:oneTimeAnimationLength(0)
{
}
ThemeBlockState::~ThemeBlockState(){
//Loop through the ThemeObjects and delete them.
for (unsigned int i = 0; i<themeObjects.size(); i++){
delete themeObjects[i];
}
}
void ThemeBlockState::destroy(){
//Loop through the ThemeObjects and delete them.
for (unsigned int i = 0; i<themeObjects.size(); i++){
delete themeObjects[i];
}
//Clear the themeObjects vector.
themeObjects.clear();
//Set the length to 0.
oneTimeAnimationLength = 0;
//Clear the nextState string.
nextState.clear();
}
ThemeBlock::ThemeBlock()
{
}
ThemeBlock::~ThemeBlock(){
//Loop through the ThemeBlockStates and delete them,
for (map<string, ThemeBlockState*>::iterator i = blockStates.begin(); i != blockStates.end(); ++i){
delete i->second;
}
//Loop through the ThemeBlockStates and delete them,
for (map<pair<string, string>, ThemeBlockState*>::iterator i = transitions.begin(); i != transitions.end(); ++i){
delete i->second;
}
}
void ThemeBlock::destroy(){
//Loop through the ThemeBlockStates and delete them,
for (map<string, ThemeBlockState*>::iterator i = blockStates.begin(); i != blockStates.end(); ++i){
delete i->second;
}
//Loop through the ThemeBlockStates transitions and delete them,
for (map<pair<string, string>, ThemeBlockState*>::iterator i = transitions.begin(); i != transitions.end(); ++i){
delete i->second;
}
//Clear the blockStates map.
blockStates.clear();
transitions.clear();
editorPicture.destroy();
}
ThemeBackgroundPicture::ThemeBackgroundPicture(){
//Set some default values.
texture = NULL;
memset(&srcSize, 0, sizeof(srcSize));
memset(&destSize, 0, sizeof(destSize));
memset(&cachedSrcSize, 0, sizeof(cachedSrcSize));
memset(&cachedDestSize, 0, sizeof(cachedDestSize));
scale = true;
repeatX = true;
repeatY = true;
speedX = 0.0f;
speedY = 0.0f;
cameraX = 0.0f;
cameraY = 0.0f;
currentX = 0.0f;
currentY = 0.0f;
savedX = 0.0f;
savedY = 0.0f;
}
void ThemeBackgroundPicture::updateAnimation(){
//Move the picture along the x-axis.
currentX += speedX;
if (repeatX && destSize.w>0){
float f = (float)destSize.w;
if (currentX>f || currentX<-f) currentX -= f*floor(currentX / f);
}
//Move the picture along the y-axis.
currentY += speedY;
if (repeatY && destSize.h>0){
float f = (float)destSize.h;
if (currentY>f || currentY<-f) currentY -= f*floor(currentY / f);
}
}
void ThemeBackgroundPicture::resetAnimation(bool save){
currentX = 0.0f;
currentY = 0.0f;
if (save){
savedX = 0.0f;
savedY = 0.0f;
}
}
void ThemeBackgroundPicture::saveAnimation(){
savedX = currentX;
savedY = currentY;
}
void ThemeBackgroundPicture::loadAnimation(){
currentX = savedX;
currentY = savedY;
}
void ThemeBackground::updateAnimation(){
for (unsigned int i = 0; i<picture.size(); i++){
picture[i].updateAnimation();
}
}
void ThemeBackground::resetAnimation(bool save){
for (unsigned int i = 0; i<picture.size(); i++){
picture[i].resetAnimation(save);
}
}
void ThemeBackground::saveAnimation(){
for (unsigned int i = 0; i<picture.size(); i++){
picture[i].saveAnimation();
}
}
void ThemeBackground::loadAnimation(){
for (unsigned int i = 0; i<picture.size(); i++){
picture[i].loadAnimation();
}
}
void ThemeBackground::scaleToScreen(){
for (unsigned int i = 0; i<picture.size(); i++){
picture[i].scaleToScreen();
}
}
void ThemeBackground::draw(SDL_Renderer& renderer){
for (unsigned int i = 0; i<picture.size(); i++){
picture[i].draw(renderer);
}
}
bool ThemeBackground::addPictureFromNode(TreeStorageNode* objNode, string themePath, ImageManager& imageManager, SDL_Renderer& renderer){
picture.push_back(ThemeBackgroundPicture());
return picture.back().loadFromNode(objNode, themePath, imageManager, renderer);
}
ThemeManager::ThemeManager(){
//Make sure the pointers are set to NULL.
objBackground = NULL;
//Reserve enough memory for the ThemeBlocks.
memset(objBlocks, 0, sizeof(objBlocks));
shadow = NULL;
player = NULL;
menuBackground = NULL;
menuBlock = NULL;
menuShadowBlock = NULL;
hasThemeTextColor = hasThemeTextColorDialog = false;
}
ThemeManager::~ThemeManager(){
//Just call destroy().
destroy();
}
void ThemeManager::destroy(){
//Delete the ThemeBlock of the shadow.
if (shadow) {
delete shadow;
shadow = NULL;
}
//Delete the ThemeBlock of the player.
if (player) {
delete player;
player = NULL;
}
//Loop through the ThemeBlocks and delete them.
for (int i = 0; i<TYPE_MAX; i++){
if (objBlocks[i]) {
delete objBlocks[i];
objBlocks[i] = NULL;
}
}
//Delete all scenery blocks
for (auto it = objScenery.begin(); it != objScenery.end(); ++it) {
delete it->second;
}
objScenery.clear();
//Delete the ThemeBackgrounds, etc.
if (objBackground) {
delete objBackground;
objBackground = NULL;
}
if (menuBackground) {
delete menuBackground;
menuBackground = NULL;
}
if (menuBlock) {
delete menuBlock;
menuBlock = NULL;
}
if (menuShadowBlock) {
delete menuShadowBlock;
menuShadowBlock = NULL;
}
//And clear the themeName, etc.
themeName.clear();
themePath.clear();
}
bool ThemeManager::loadFile(const string& fileName, ImageManager &imageManager, SDL_Renderer &renderer){
POASerializer objSerializer;
TreeStorageNode objNode;
//First we destroy the current ThemeManager.
destroy();
//Now we try to load the file, if it fails we return false.
if(!objSerializer.loadNodeFromFile(fileName.c_str(),&objNode,true)){
cerr<<"ERROR: Unable to open theme file: "<<fileName<<endl;
return false;
}
//Set the themePath.
themePath=pathFromFileName(fileName);
//Retrieve the name of the theme from the file.
{
vector<string> &v=objNode.attributes["name"];
if(!v.empty()) themeName=v[0];
}
//Reset themeable colors to default
hasThemeTextColor = hasThemeTextColorDialog = false;
themeTextColor.r=themeTextColor.g=themeTextColor.b=0;
themeTextColorDialog.r=themeTextColorDialog.g=themeTextColorDialog.b=0;
//Read themeable colors if any
vector<string> &ct=objNode.attributes["textColor"];
if(!ct.empty()){
hasThemeTextColor = true;
themeTextColor.r=atoi(ct[0].c_str());
themeTextColor.g=atoi(ct[1].c_str());
themeTextColor.b=atoi(ct[2].c_str());
}
vector<string> &ct2=objNode.attributes["textColorDialog"];
if(!ct2.empty()){
hasThemeTextColorDialog = true;
themeTextColorDialog.r=atoi(ct2[0].c_str());
themeTextColorDialog.g=atoi(ct2[1].c_str());
themeTextColorDialog.b=atoi(ct2[2].c_str());
}
//Loop the subnodes of the theme.
for(unsigned int i=0;i<objNode.subNodes.size();i++){
TreeStorageNode *obj=objNode.subNodes[i];
//Check if it's a block or a background.
if (obj->name == "block" && !obj->value.empty()){
map<string, int>::iterator it = Game::blockNameMap.find(obj->value[0]);
if (it != Game::blockNameMap.end()){
int idx = it->second;
if (!objBlocks[idx]) objBlocks[idx] = new ThemeBlock;
if (!objBlocks[idx]->loadFromNode(obj, themePath, imageManager, renderer)){
cerr << "ERROR: Unable to load " << Game::blockName[idx] << " for theme " << fileName << endl;
delete objBlocks[idx];
objBlocks[idx] = NULL;
return false;
}
}
} else if (obj->name == "scenery" && !obj->value.empty()){
std::string& name = obj->value[0];
if (!objScenery[name]) objScenery[name] = new ThemeBlock;
if (!objScenery[name]->loadFromNode(obj, themePath, imageManager, renderer)){
cerr << "ERROR: Unable to load scenery '" << name << "' for theme " << fileName << endl;
delete objScenery[name];
objScenery[name] = NULL;
return false;
}
}else if(obj->name=="background" && !obj->value.empty()){
if(!objBackground) objBackground=new ThemeBackground();
if(!objBackground->addPictureFromNode(obj,themePath, imageManager, renderer)){
cerr<<"ERROR: Unable to load background for theme "<<fileName<<endl;
delete objBackground;
objBackground=NULL;
return false;
}
}else if(obj->name=="character" && !obj->value.empty()){
if(obj->value[0]=="Shadow"){
if(!shadow) shadow=new ThemeBlock();
if(!shadow->loadFromNode(obj,themePath, imageManager, renderer)){
cerr<<"ERROR: Unable to load shadow for theme "<<fileName<<endl;
delete shadow;
shadow=NULL;
return false;
}
}else if(obj->value[0]=="Player"){
if(!player) player=new ThemeBlock();
if(!player->loadFromNode(obj,themePath, imageManager, renderer)){
cerr<<"ERROR: Unable to load player for theme "<<fileName<<endl;
delete player;
player=NULL;
return false;
}
}
}else if(obj->name=="menuBackground" && !obj->value.empty()){
if(!menuBackground) menuBackground=new ThemeBackground();
if(!menuBackground->addPictureFromNode(obj,themePath, imageManager, renderer)){
cerr<<"ERROR: Unable to load background for theme "<<fileName<<endl;
delete menuBackground;
menuBackground=NULL;
return false;
}
}else if(obj->name=="menu" && obj->value[0]=="Block"){
if(!menuBlock) menuBlock=new ThemeBlock;
if(!menuBlock->loadFromNode(obj,themePath, imageManager, renderer)){
cerr<<"ERROR: Unable to load menu block for theme "<<fileName<<endl;
delete menuBlock;
menuBlock=NULL;
return false;
}
} else if (obj->name == "menu" && obj->value[0] == "ShadowBlock"){
if (!menuShadowBlock) menuShadowBlock = new ThemeBlock;
if (!menuShadowBlock->loadFromNode(obj, themePath, imageManager, renderer)){
cerr << "ERROR: Unable to load menu shadow block for theme " << fileName << endl;
delete menuShadowBlock;
menuShadowBlock = NULL;
return false;
}
}
}
//Done and nothing went wrong so return true.
return true;
}
void ThemeManager::scaleToScreen(){
//We only need to scale the background.
if (objBackground)
objBackground->scaleToScreen();
}
ThemeBlock* ThemeManager::getBlock(int index, bool menu){
if (!menu)
return objBlocks[index];
else
if (index == TYPE_BLOCK)
if (menuBlock)
return menuBlock;
else
return objBlocks[TYPE_BLOCK];
else if (index == TYPE_SHADOW_BLOCK)
if (menuShadowBlock)
return menuShadowBlock;
else if (menuBlock)
return menuBlock;
else
return objBlocks[TYPE_SHADOW_BLOCK];
else
return objBlocks[index];
}
ThemeBlock* ThemeManager::getScenery(const std::string& name){
auto it = objScenery.find(name);
if (it == objScenery.end())
return NULL;
else
return it->second;
}
void ThemeManager::getSceneryBlockNames(std::set<std::string> &s) {
for (auto it = objScenery.begin(); it != objScenery.end(); ++it) {
s.insert(it->first);
}
}
ThemeBlock* ThemeManager::getCharacter(bool isShadow){
if (isShadow)
return shadow;
return player;
}
ThemeBackground* ThemeManager::getBackground(bool menu){
if (menu&&menuBackground)
return menuBackground;
else
return objBackground;
}
bool ThemeManager::getTextColor(bool isDialog, SDL_Color& color) {
if (isDialog) {
if (hasThemeTextColorDialog) color = themeTextColorDialog;
return hasThemeTextColorDialog;
} else {
if (hasThemeTextColor) color = themeTextColor;
return hasThemeTextColor;
}
}
bool ThemeBlock::loadFromNode(TreeStorageNode* objNode, string themePath, ImageManager &imageManager, SDL_Renderer &renderer){
destroy();
//Loop the subNodes.
for(unsigned int i=0;i<objNode->subNodes.size();i++){
TreeStorageNode *obj=objNode->subNodes[i];
//Check if the subnode is an editorPicture or a blockState.
if(obj->name=="editorPicture"){
if(!editorPicture.loadFromNode(obj,themePath, imageManager, renderer)) return false;
//NOTE: blockState and characterState are for backwards compatability, use state instead.
}else if((obj->name=="blockState" || obj->name=="characterState" || obj->name=="state") && !obj->value.empty()){
string& s=obj->value[0];
map<string,ThemeBlockState*>::iterator it=blockStates.find(s);
if(it==blockStates.end()) blockStates[s]=new ThemeBlockState;
if(!blockStates[s]->loadFromNode(obj,themePath, imageManager, renderer)) return false;
}else if(obj->name=="transitionState" && obj->value.size()==2){
pair<string,string> s=pair<string,string>(obj->value[0],obj->value[1]);
map<pair<string,string>,ThemeBlockState*>::iterator it=transitions.find(s);
if(it==transitions.end()) transitions[s]=new ThemeBlockState;
if(!transitions[s]->loadFromNode(obj,themePath, imageManager, renderer)) return false;
}
}
//Done and nothing went wrong so return true.
return true;
}
bool ThemeBlockState::loadFromNode(TreeStorageNode* objNode, string themePath, ImageManager& imageManager, SDL_Renderer& renderer){
destroy();
//Retrieve the oneTimeAnimation attribute.
{
vector<string> &v=objNode->attributes["oneTimeAnimation"];
//Check if there are enough values for the oneTimeAnimation attribute.
if(v.size()>=2 && !v[0].empty()){
oneTimeAnimationLength=atoi(v[0].c_str());
nextState=v[1];
}
}
//Loop the subNodes.
for(unsigned int i=0;i<objNode->subNodes.size();i++){
TreeStorageNode *obj=objNode->subNodes[i];
if(obj->name=="object"){
ThemeObject *obj1=new ThemeObject();
if(!obj1->loadFromNode(obj,themePath, imageManager, renderer)){
delete obj1;
return false;
}
themeObjects.push_back(obj1);
}
}
//Done and nothing went wrong so return true.
return true;
}
bool ThemeObject::loadFromNode(TreeStorageNode* objNode,string themePath, ImageManager& imageManager, SDL_Renderer& renderer){
destroy();
//Retrieve the animation attribute.
{
vector<string> &v=objNode->attributes["animation"];
if(v.size()>=2){
animationLength=atoi(v[0].c_str());
animationLoopPoint=atoi(v[1].c_str());
}
}
//Retrieve the oneTimeAnimation attribute.
{
vector<string> &v=objNode->attributes["oneTimeAnimation"];
if(v.size()>=2){
animationLength=atoi(v[0].c_str());
animationLoopPoint=atoi(v[1].c_str())|0x80000000;
}
}
//Retrieve the invisibleAtRunTime attribute.
{
vector<string> &v=objNode->attributes["invisibleAtRunTime"];
if(!v.empty() && !v[0].empty()){
invisibleAtRunTime=atoi(v[0].c_str())?true:false;
}
}
//Retrieve the invisibleAtDesignTime attribute.
{
vector<string> &v=objNode->attributes["invisibleAtDesignTime"];
if(!v.empty() && !v[0].empty()){
invisibleAtDesignTime=atoi(v[0].c_str())?true:false;
}
}
//Loop the subnodes.
for(unsigned int i=0;i<objNode->subNodes.size();i++){
TreeStorageNode *obj=objNode->subNodes[i];
if(obj->name=="picture" || obj->name=="pictureAnimation"){
if(!picture.loadFromNode(obj,themePath, imageManager, renderer)){
return false;
}
}else if(obj->name=="editorPicture"){
if(!editorPicture.loadFromNode(obj,themePath, imageManager, renderer)){
return false;
}
}else if(obj->name=="optionalPicture" && obj->value.size()>=6){
ThemePicture *objPic=new ThemePicture();
double f=atof(obj->value[5].c_str());
if(!objPic->loadFromNode(obj,themePath, imageManager, renderer)){
delete objPic;
return false;
}
optionalPicture.push_back(pair<double,ThemePicture*>(f,objPic));
}else if(obj->name=="offset" || obj->name=="offsetAnimation"){
if(!offset.loadFromNode(obj)) return false;
}else if(obj->name=="positioning"){
if(!positioning.loadFromNode(obj)) return false;
}
}
//Done and nothing went wrong so return true.
return true;
}
bool ThemePicture::loadFromNode(TreeStorageNode* objNode, string themePath, ImageManager &imageManager, SDL_Renderer &renderer){
destroy();
//Check if the node has enough values.
if(!objNode->value.empty()){
//Load the texture.
texture=imageManager.loadTexture(themePath+objNode->value[0], renderer);
if(!texture) {
return false;
}
//Check if it's an animation.
if(objNode->name=="pictureAnimation"){
if(!offset.loadFromNode(objNode)) return false;
return true;
}else if(objNode->value.size()>=5){
ThemeOffsetPoint r={atoi(objNode->value[1].c_str()),
atoi(objNode->value[2].c_str()),
atoi(objNode->value[3].c_str()),
atoi(objNode->value[4].c_str()),0,0};
offset.offsetData.push_back(r);
offset.length=0;
return true;
}
}
cerr << "ERROR: The structure of theme picture node '" << objNode->name << "' is incorrect" << endl;
return false;
}
bool ThemeOffsetData::loadFromNode(TreeStorageNode* objNode){
destroy();
//Check what kind of offset it is.
if(objNode->name=="pictureAnimation"){
for(unsigned int i=0;i<objNode->subNodes.size();i++){
TreeStorageNode* obj=objNode->subNodes[i];
if(obj->name=="point" && obj->value.size()>=4){
ThemeOffsetPoint r={atoi(obj->value[0].c_str()),
atoi(obj->value[1].c_str()),
atoi(obj->value[2].c_str()),
atoi(obj->value[3].c_str()),1,1};
if(obj->value.size()>=5) r.frameCount=atoi(obj->value[4].c_str());
if(obj->value.size()>=6) r.frameDisplayTime=atoi(obj->value[5].c_str());
offsetData.push_back(r);
length+=r.frameCount*r.frameDisplayTime;
}
}
return true;
}else if(objNode->name=="offsetAnimation"){
for(unsigned int i=0;i<objNode->subNodes.size();i++){
TreeStorageNode* obj=objNode->subNodes[i];
if(obj->name=="point" && obj->value.size()>=2){
ThemeOffsetPoint r={atoi(obj->value[0].c_str()),
atoi(obj->value[1].c_str()),0,0,1,1};
if(obj->value.size()>=3) r.frameCount=atoi(obj->value[2].c_str());
if(obj->value.size()>=4) r.frameDisplayTime=atoi(obj->value[3].c_str());
+ if(obj->value.size()>=5) r.w=atoi(obj->value[4].c_str());
+ if(obj->value.size()>=6) r.h=atoi(obj->value[5].c_str());
offsetData.push_back(r);
length+=r.frameCount*r.frameDisplayTime;
}
}
return true;
}else if(objNode->name=="offset" && objNode->value.size()>=2){
ThemeOffsetPoint r={atoi(objNode->value[0].c_str()),
atoi(objNode->value[1].c_str()),0,0,0,0};
if(objNode->value.size()>2)
r.w=atoi(objNode->value[2].c_str());
if(objNode->value.size()>3)
r.h=atoi(objNode->value[3].c_str());
offsetData.push_back(r);
length=0;
return true;
}
cerr << "ERROR: The structure of theme offset data node '" << objNode->name << "' is incorrect" << endl;
return false;
}
bool ThemePositioningData::loadFromNode(TreeStorageNode* objNode){
destroy();
//Check if enough values are set.
if(objNode->value.size()>=2){
//Check horizontal alignment.
if(objNode->value[0]=="left"){
horizontalAlign=LEFT;
- }else if(objNode->value[0]=="centre"){
+ }else if(objNode->value[0]=="centre" || objNode->value[0]=="center"){
horizontalAlign=CENTRE;
}else if(objNode->value[0]=="right"){
horizontalAlign=RIGHT;
}else if(objNode->value[0]=="repeat"){
horizontalAlign=REPEAT;
} else if (objNode->value[0] == "stretch") {
horizontalAlign = STRETCH;
} else {
cerr << "ERROR: Unknown horizontal align mode: " << objNode->value[0] << endl;
return false;
}
//Check vertical alignment.
if(objNode->value[1]=="top"){
verticalAlign=TOP;
}else if(objNode->value[1]=="middle"){
verticalAlign=MIDDLE;
}else if(objNode->value[1]=="bottom"){
verticalAlign=BOTTOM;
}else if(objNode->value[1]=="repeat"){
verticalAlign=REPEAT;
} else if (objNode->value[1] == "stretch") {
verticalAlign = STRETCH;
} else {
cerr << "ERROR: Unknown vertical align mode: " << objNode->value[1] << endl;
return false;
}
//Done and nothing went wrong so return true.
return true;
}
cerr << "ERROR: The structure of theme positioning data node '" << objNode->name << "' is incorrect" << endl;
return false;
}
void ThemeObjectInstance::draw(SDL_Renderer& renderer,int x,int y,int w,int h,const SDL_Rect *clipRect){
//Get the picture.
//SDL_Surface *src=picture->picture;
SDL_Texture* src = picture->texture.get();
if(src==NULL) return;
//The offset to the left and top of the destination rectangle.
int ex = 0, ey = 0;
//The offset to the right and bottom of the destination rectangle. Only used when the position mode is REPEAT or STRETCH.
int ew = 0, eh = 0;
//The x,y,width,height of the source rectangle.
int xx=0,yy=0,ww=0,hh=0;
int animationNew=animation&0x7FFFFFFF;
//Get the source rectangle.
{
const vector<ThemeOffsetPoint> &v=picture->offset.offsetData;
if(picture->offset.length==0 || animationNew<v[0].frameDisplayTime){
xx=v[0].x;
yy=v[0].y;
ww=v[0].w;
hh=v[0].h;
}else if(animationNew>=picture->offset.length){
int i=v.size()-1;
xx=v[i].x;
yy=v[i].y;
ww=v[i].w;
hh=v[i].h;
}else{
int t=animationNew-v[0].frameDisplayTime;
for(unsigned int i=1;i<v.size();i++){
int tt=t/v[i].frameDisplayTime;
if(tt>=0 && tt<v[i].frameCount){
xx=(int)((float)v[i-1].x+(float)(v[i].x-v[i-1].x)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
yy=(int)((float)v[i-1].y+(float)(v[i].y-v[i-1].y)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
ww=(int)((float)v[i-1].w+(float)(v[i].w-v[i-1].w)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
hh=(int)((float)v[i-1].h+(float)(v[i].h-v[i-1].h)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
break;
}else{
t-=v[i].frameCount*v[i].frameDisplayTime;
}
}
}
}
//Get the offset.
{
vector<ThemeOffsetPoint> &v=parent->offset.offsetData;
if(v.empty()){
ex=0;
ey=0;
}else if(parent->offset.length==0 || animationNew<v[0].frameDisplayTime){
ex=v[0].x;
ey=v[0].y;
ew=v[0].w;
eh=v[0].h;
}else if(animationNew>=parent->offset.length){
int i=v.size()-1;
ex=v[i].x;
ey=v[i].y;
ew=v[i].w;
eh=v[i].h;
}else{
int t=animationNew-v[0].frameDisplayTime;
for(unsigned int i=1;i<v.size();i++){
int tt=t/v[i].frameDisplayTime;
if(tt>=0 && tt<v[i].frameCount){
ex=(int)((float)v[i-1].x+(float)(v[i].x-v[i-1].x)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
ey=(int)((float)v[i-1].y+(float)(v[i].y-v[i-1].y)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
ew=(int)((float)v[i-1].w+(float)(v[i].w-v[i-1].w)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
eh=(int)((float)v[i-1].h+(float)(v[i].h-v[i-1].h)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
break;
}else{
t-=v[i].frameCount*v[i].frameDisplayTime;
}
}
}
}
//And finally draw the ThemeObjectInstance.
if(ww>0&&hh>0){
Alignment hAlign = parent->positioning.horizontalAlign;
Alignment vAlign = parent->positioning.verticalAlign;
//If the destination size is not set then assume it's the same as the source size.
//In this case we also disable the align.
if (w <= 0) {
w = ww;
hAlign = LEFT;
}
if (h <= 0) {
h = hh;
vAlign = TOP;
}
//The destination rectangle (NOTE: the w,h are actually the right and bottom)
SDL_Rect r2={x+ex,y+ey,0,0};
//Align horizontally.
switch (hAlign){
case CENTRE:
r2.x += (w - ww) / 2;
break;
case RIGHT:
r2.x += w - ww;
break;
}
//Align vertically.
switch (vAlign){
case MIDDLE:
r2.y += (h - hh) / 2;
break;
case BOTTOM:
r2.y += h - hh;
break;
}
//Calculate the correct right and bottom of the destination rectangle (esp. in REPEAT and STRETCH mode)
r2.w = (hAlign == REPEAT || hAlign == STRETCH) ? (x + w - ew) : r2.x + ww;
r2.h = (vAlign == REPEAT || vAlign == STRETCH) ? (y + h - eh) : r2.y + hh;
//For STRETCH mode we have to use SDL builtin clip rect function
//otherwise the texture coordinate is hard to calculate
bool useSDLClipRect = false;
if (clipRect) {
//Clip the right and bottom
if (r2.w > clipRect->x + clipRect->w) {
if (hAlign == STRETCH) useSDLClipRect = true;
else r2.w = clipRect->x + clipRect->w;
}
if (r2.h > clipRect->y + clipRect->h) {
if (vAlign == STRETCH) useSDLClipRect = true;
else r2.h = clipRect->y + clipRect->h;
}
//Clip the left and top (ad-hoc code)
if (r2.x < clipRect->x) {
if (hAlign == STRETCH) useSDLClipRect = true;
else r2.x += ((clipRect->x - r2.x) / ww) * ww;
}
if (r2.y < clipRect->y) {
if (vAlign == STRETCH) useSDLClipRect = true;
else r2.y += ((clipRect->y - r2.y) / hh) * hh;
}
}
//Set the SDL clip rect if necessary
if (useSDLClipRect) {
SDL_RenderSetClipRect(&renderer, clipRect);
}
//As long as we haven't exceeded the horizontal target keep drawing.
while (r2.x < r2.w){
//Store the y position for when more than one column has to be drawn.
const int y2 = r2.y;
//As long as we haven't exceeded the vertical target keep drawing.
while (r2.y < r2.h){
//The source rectangle which will be modified by clipping.
SDL_Rect srcrect = { xx, yy, ww, hh };
//Check if we should clip the right and bottom.
if (r2.x + ww > r2.w && hAlign != STRETCH) srcrect.w = r2.w - r2.x;
if (r2.y + hh > r2.h && vAlign != STRETCH) srcrect.h = r2.h - r2.y;
//The destination rectangle which will be modified by clipping.
SDL_Rect dstrect = { r2.x, r2.y, 0, 0 };
//Clip the left and top
if (clipRect) {
int d = clipRect->x - dstrect.x;
if (d > 0 && hAlign != STRETCH) {
srcrect.x += d; srcrect.w -= d; dstrect.x += d;
}
d = clipRect->y - dstrect.y;
if (d > 0 && vAlign != STRETCH) {
srcrect.y += d; srcrect.h -= d; dstrect.y += d;
}
}
if (srcrect.w > 0 && srcrect.h > 0) {
dstrect.w = (hAlign == STRETCH) ? (r2.w - r2.x) : srcrect.w;
dstrect.h = (vAlign == STRETCH) ? (r2.h - r2.y) : srcrect.h;
SDL_RenderCopy(&renderer, src, &srcrect, &dstrect);
}
if (vAlign == STRETCH) break; //For STRETCH mode draw once is enough
r2.y += hh;
}
if (hAlign == STRETCH) break; //For STRETCH mode draw once is enough
r2.x += ww;
//Reset the y position before drawing a new column.
r2.y = y2;
}
//Reset the SDL clip rect if necessary
if (useSDLClipRect) {
SDL_RenderSetClipRect(&renderer, NULL);
}
}
}
void ThemeObjectInstance::updateAnimation(){
//First get the animation length.
int m;
m=parent->animationLength;
//If it's higher than 0 then we have an animation.
if(m>0 && animation>=0){
//Increase the animation frame.
animation++;
//Check if the animation is beyond the length, if so set it to the looppoint.
if(animation>=m)
animation=parent->animationLoopPoint;
}
}
void ThemeBlockInstance::updateAnimation(){
//Make sure the currentState isn't null.
if(currentState!=NULL){
//Call the updateAnimation method of the currentState.
currentState->updateAnimation();
//Get the length of the animation.
int m=currentState->parent->oneTimeAnimationLength;
//If it's higher than 0 then we have an animation.
//Also check if it's past the lenght, meaning done.
if(m>0 && currentState->animation>=m){
//Now we can change the state to the nextState.
changeState(currentState->parent->nextState);
}
}
}
void ThemeBlock::createInstance(ThemeBlockInstance* obj){
//Make sure the given ThemeBlockInstance is ready.
obj->blockStates.clear();
obj->transitions.clear();
obj->currentState=NULL;
//Loop through the blockstates.
for(map<string,ThemeBlockState*>::iterator it=blockStates.begin();it!=blockStates.end();++it){
//Get the themeBlockStateInstance of the given ThemeBlockInstance.
ThemeBlockStateInstance &obj1=obj->blockStates[it->first];
//Set the parent of the state instance.
obj1.parent=it->second;
//Create the state instance.
createStateInstance(&obj1);
}
//Loop through the transitions.
for(map<pair<string,string>,ThemeBlockState*>::iterator it=transitions.begin();it!=transitions.end();++it){
//Get the themeBlockStateInstance of the given ThemeBlockInstance.
ThemeBlockStateInstance &obj1=obj->transitions[it->first];
//Set the parent of the state instance.
obj1.parent=it->second;
//Create the state instance.
createStateInstance(&obj1);
}
//Change the state to the default one.
//FIXME: Is that needed?
obj->changeState("default");
}
void ThemeBlock::createStateInstance(ThemeBlockStateInstance* obj){
//Get the vector with themeObjects.
vector<ThemeObject*> &v=obj->parent->themeObjects;
//Loop through them.
for(unsigned int i=0;i<v.size();i++){
//Create an instance for every one.
ThemeObjectInstance p;
//Set the parent.
p.parent=v[i];
//Choose the picture.
if(stateID==STATE_LEVEL_EDITOR){
if(p.parent->invisibleAtDesignTime)
continue;
if(p.parent->editorPicture.texture!=NULL)
p.picture=&p.parent->editorPicture;
}else{
if(p.parent->invisibleAtRunTime)
continue;
}
//Get the number of optional Pictures.
int m=p.parent->optionalPicture.size();
//If p.picture is null, not an editor picture, and there are optional pictures then give one random.
if(p.picture==NULL && m>0){
double f=0.0,f1=1.0/256.0;
for(int j=0;j<8;j++){
f+=f1*(double)(rand()&0xff);
f1*=(1.0/256.0);
}
for(int j=0;j<m;j++){
f-=p.parent->optionalPicture[j].first;
if(f<0.0){
p.picture=p.parent->optionalPicture[j].second;
break;
}
}
}
//If random turned out to give nothing then give the non optional picture.
if(p.picture==NULL && p.parent->picture.texture!=NULL)
p.picture=&p.parent->picture;
//If the picture isn't null then can we give it to the ThemeBlockStateInstance.
if(p.picture!=NULL)
obj->objects.push_back(p);
}
}
void ThemePicture::draw(SDL_Renderer& renderer,int x,int y,int animation,SDL_Rect *clipRect){
//Get the Picture.
if(texture==NULL) return;
int ex=0,ey=0,xx,yy,ww,hh;
{
const vector<ThemeOffsetPoint> &v=offset.offsetData;
if(offset.length==0 || animation<v[0].frameDisplayTime){
xx=v[0].x;
yy=v[0].y;
ww=v[0].w;
hh=v[0].h;
}else if(animation>=offset.length){
int i=v.size()-1;
xx=v[i].x;
yy=v[i].y;
ww=v[i].w;
hh=v[i].h;
}else{
int t=animation-v[0].frameDisplayTime;
for(unsigned int i=1;i<v.size();i++){
int tt=t/v[i].frameDisplayTime;
if(tt>=0 && tt<v[i].frameCount){
xx=(int)((float)v[i-1].x+(float)(v[i].x-v[i-1].x)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
yy=(int)((float)v[i-1].y+(float)(v[i].y-v[i-1].y)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
ww=(int)((float)v[i-1].w+(float)(v[i].w-v[i-1].w)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
hh=(int)((float)v[i-1].h+(float)(v[i].h-v[i-1].h)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
break;
}else{
t-=v[i].frameCount*v[i].frameDisplayTime;
}
}
}
}
//Draw the Picture.
if(clipRect){
int d;
d=clipRect->x-ex;
if(d>0){
ex+=d;
xx+=d;
ww-=d;
}
d=clipRect->y-ey;
if(d>0){
ey+=d;
yy+=d;
hh-=d;
}
if(ww>clipRect->w) ww=clipRect->w;
if(hh>clipRect->h) hh=clipRect->h;
}
if(ww>0&&hh>0){
SDL_Rect r1={xx,yy,ww,hh};
SDL_Rect r2={x+ex,y+ey,ww,hh};
SDL_RenderCopy(&renderer, texture.get(), &r1, &r2);
}
}
//This method will scale the background picture (if needed and configured) to the current SCREEN_WIDTH and SCREEN_HEIGHT.
void ThemeBackgroundPicture::scaleToScreen(){
//Only scale if needed.
if(scale){
// SDL2 allows us to scale the texture when rendering, so
// we only need to adjust the size of the destination rect.
destSize.w = SCREEN_WIDTH;
destSize.h = SCREEN_HEIGHT;
}
}
void ThemeBackgroundPicture::draw(SDL_Renderer &dest){
//Check if the picture is visible.
if(!(texture&&srcSize.w>0&&srcSize.h>0&&destSize.w>0&&destSize.h>0))
return;
//Calculate the draw area.
int sx=(int)((float)destSize.x+currentX-cameraX*(float)camera.x+0.5f);
int sy=(int)((float)destSize.y+currentY-cameraY*(float)camera.y+0.5f);
int ex,ey;
//Include repeating.
if(repeatX){
sx%=destSize.w;
if(sx>0) sx-=destSize.w;
ex=SCREEN_WIDTH;
}else{
if(sx<=-(int)destSize.w || sx>=SCREEN_WIDTH) return;
ex=sx+1;
}
if(repeatY){
sy%=destSize.h;
if(sy>0) sy-=destSize.h;
ey=SCREEN_HEIGHT;
}else{
if(sy<=-(int)destSize.h || sy>=SCREEN_HEIGHT) return;
ey=sy+1;
}
//And finally draw the ThemeBackgroundPicture.
for(int x=sx;x<ex;x+=destSize.w){
for(int y=sy;y<ey;y+=destSize.h){
// NOTE: Rendercopy cares about w/h here
// so had to add it for SDL2 port.
SDL_Rect r={x,y,destSize.w,destSize.h};
//SDL_BlitSurface(picture,&srcSize,dest,&r);
SDL_RenderCopy(&dest, texture.get(), &srcSize, &r);
}
}
}
bool ThemeBackgroundPicture::loadFromNode(TreeStorageNode* objNode, string themePath, ImageManager &imageManager, SDL_Renderer& renderer){
//Load the picture directly into a texture.
texture = imageManager.loadTexture(themePath+objNode->value[0], renderer);
if (!texture) {
return false;
}
//Retrieve the source size.
{
vector<string> &v=objNode->attributes["srcSize"];
if(v.size()>=4){
srcSize.x=atoi(v[0].c_str());
srcSize.y=atoi(v[1].c_str());
srcSize.w=atoi(v[2].c_str());
srcSize.h=atoi(v[3].c_str());
}else{
srcSize.x=0;
srcSize.y=0;
// This gets the width and height of the texture.
SDL_QueryTexture(texture.get(), NULL, NULL, &srcSize.w, &srcSize.h);
}
//Cache the sourcesize.
cachedSrcSize=srcSize;
}
//Retrieve the destination size.
{
vector<string> &v=objNode->attributes["destSize"];
if(v.size()>=4){
destSize.x=atoi(v[0].c_str());
destSize.y=atoi(v[1].c_str());
destSize.w=atoi(v[2].c_str());
destSize.h=atoi(v[3].c_str());
}else{
destSize.x=0;
destSize.y=0;
destSize.w=SCREEN_WIDTH;
destSize.h=SCREEN_HEIGHT;
}
//Cache the destsize.
cachedDestSize=destSize;
}
//Retrieve if we should scale to screen.
{
//Get scaleToScreen.
vector<string> &v=objNode->attributes["scaleToScreen"];
//Boolean if the image should be scaled, default is true.
scale=true;
if(!v.empty()){
scale=atoi(v[0].c_str())?true:false;
}
//Now scaleToScreen.
//NOTE: We don't check if scaleToScreen is true or false since that is done in scaleToScreen();
scaleToScreen();
}
//Retrieve if it should be repeated.
{
vector<string> &v=objNode->attributes["repeat"];
if(v.size()>=2){
repeatX=atoi(v[0].c_str())?true:false;
repeatY=atoi(v[1].c_str())?true:false;
}else{
repeatX=true;
repeatY=true;
}
}
//Retrieve the speed.
{
vector<string> &v=objNode->attributes["speed"];
if(v.size()>=2){
speedX=atof(v[0].c_str());
speedY=atof(v[1].c_str());
}else{
speedX=0.0f;
speedY=0.0f;
}
}
//Retrieve the camera speed.
{
vector<string> &v=objNode->attributes["cameraSpeed"];
if(v.size()>=2){
cameraX=atof(v[0].c_str());
cameraY=atof(v[1].c_str());
}else{
cameraX=0.0f;
cameraY=0.0f;
}
}
//Done and nothing went wrong so return true.
return true;
}
//Constructor.
ThemeStack::ThemeStack(){
hasThemeTextColor = hasThemeTextColorDialog = false;
}
//Destructor.
ThemeStack::~ThemeStack(){
//Loop through the themes and delete them.
for(unsigned int i=0;i<objThemes.size();i++)
delete objThemes[i];
}
//Method that will destroy the ThemeStack.
void ThemeStack::destroy(){
//Loop through the themes and delete them.
for(unsigned int i=0;i<objThemes.size();i++)
delete objThemes[i];
//Clear the vector to prevent dangling pointers.
objThemes.clear();
//Invalidates the cache.
hasThemeTextColor = hasThemeTextColorDialog = false;
}
//Method that will append a theme to the stack.
//obj: The ThemeManager to add.
void ThemeStack::appendTheme(ThemeManager* obj){
objThemes.push_back(obj);
//debug
#if defined(DEBUG) || defined(_DEBUG)
cout<<"ThemeStack::appendTheme(): theme count="<<objThemes.size()<<endl;
#endif
//Invalidates the cache.
hasThemeTextColor = hasThemeTextColorDialog = false;
}
//Method that will remove the last theme added to the stack.
void ThemeStack::removeTheme(){
//Make sure that the stack isn't empty.
if(!objThemes.empty()){
delete objThemes.back();
objThemes.pop_back();
}
//Invalidates the cache.
hasThemeTextColor = hasThemeTextColorDialog = false;
}
//Method that will append a theme that will be loaded from file.
//fileName: The file to load the theme from.
//Returns: Pointer to the newly added theme, NULL if failed.
ThemeManager* ThemeStack::appendThemeFromFile(const string& fileName, ImageManager &imageManager, SDL_Renderer &renderer){
//Invalidates the cache.
hasThemeTextColor = hasThemeTextColorDialog = false;
//Create a new themeManager.
ThemeManager* obj=new ThemeManager();
//Let it load from the given file.
if(!obj->loadFile(fileName, imageManager, renderer)){
//Failed thus delete the theme and return null.
cerr<<"ERROR: Failed loading theme "<<fileName<<endl;
delete obj;
return NULL;
}else{
//Succeeded, add it to the stack and return it.
objThemes.push_back(obj);
return obj;
}
}
//Method that is used to let the themes scale.
void ThemeStack::scaleToScreen(){
//Loop through the themes and call their scaleToScreen method.
for(unsigned int i=0;i<objThemes.size();i++)
objThemes[i]->scaleToScreen();
}
//Get a pointer to the ThemeBlock of a given block type.
//index: The type of block.
//Returns: Pointer to the ThemeBlock.
ThemeBlock* ThemeStack::getBlock(int index,bool menu){
//Loop through the themes from top to bottom.
for(int i=objThemes.size()-1;i>=0;i--){
//Get the block from the theme.
ThemeBlock* obj=objThemes[i]->getBlock(index,menu);
//Check if it isn't null.
if(obj)
return obj;
}
//Nothing found.
return NULL;
}
ThemeBlock* ThemeStack::getScenery(const std::string& name) {
//Loop through the themes from top to bottom.
for (int i = objThemes.size() - 1; i >= 0; i--){
//Get the block from the theme.
ThemeBlock* obj = objThemes[i]->getScenery(name);
//Check if it isn't null.
if (obj)
return obj;
}
//Check if the input is a valid block name.
if (name.size() > 8 && name.substr(name.size() - 8) == "_Scenery") {
auto it = Game::blockNameMap.find(name.substr(0, name.size() - 8));
if (it != Game::blockNameMap.end()){
return getBlock(it->second);
}
}
//Nothing found.
return NULL;
}
void ThemeStack::getSceneryBlockNames(std::set<std::string> &s) {
//Loop through the themes from top to bottom.
for (int i = objThemes.size() - 1; i >= 0; i--){
objThemes[i]->getSceneryBlockNames(s);
}
//Also add all block names to it.
for (auto it = Game::blockNameMap.begin(); it != Game::blockNameMap.end(); ++it) {
s.insert(it->first + "_Scenery");
}
}
//Get a pointer to the ThemeBlock of the shadow or the player.
//isShadow: Boolean if it's the shadow
//Returns: Pointer to the ThemeBlock.
ThemeBlock* ThemeStack::getCharacter(bool isShadow){
//Loop through the themes from top to bottom.
for(int i=objThemes.size()-1;i>=0;i--){
//Get the ThemeBlock from the theme.
ThemeBlock* obj=objThemes[i]->getCharacter(isShadow);
//Check if it isn't null.
if(obj)
return obj;
}
//Nothing found.
return NULL;
}
//Get a pointer to the ThemeBackground of the theme.
//Returns: Pointer to the ThemeBackground.
ThemeBackground* ThemeStack::getBackground(bool menu){
//Loop through the themes from top to bottom.
for(int i=objThemes.size()-1;i>=0;i--){
//Get the ThemeBackground from the theme.
ThemeBackground* obj=objThemes[i]->getBackground(menu);
//Check if it isn't null.
if(obj)
return obj;
}
//Nothing found.
return NULL;
}
SDL_Color ThemeStack::getTextColor(bool isDialog) {
if (isDialog) {
if (hasThemeTextColorDialog) return themeTextColorDialog;
//Loop through the themes from top to bottom.
for (int i = objThemes.size() - 1; i >= 0; i--) {
if (objThemes[i]->getTextColor(isDialog, themeTextColorDialog)) {
hasThemeTextColorDialog = true;
return themeTextColorDialog;
}
}
hasThemeTextColorDialog = true;
themeTextColorDialog = BLACK;
return themeTextColorDialog;
} else {
if (hasThemeTextColor) return themeTextColor;
//Loop through the themes from top to bottom.
for (int i = objThemes.size() - 1; i >= 0; i--) {
if (objThemes[i]->getTextColor(isDialog, themeTextColor)) {
hasThemeTextColor = true;
return themeTextColor;
}
}
hasThemeTextColor = true;
themeTextColor = BLACK;
return themeTextColor;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 16, 7:12 PM (1 d, 1 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
22/70/0f122cc841a5298784d1c8b8c543
Default Alt Text
(1 MB)

Event Timeline