Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
651 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
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/de.po b/data/levelpacks/classic/locale/nb.po
similarity index 68%
copy from data/levelpacks/classic/locale/de.po
copy to data/levelpacks/classic/locale/nb.po
index f589a29..145443b 100644
--- a/data/levelpacks/classic/locale/de.po
+++ b/data/levelpacks/classic/locale/nb.po
@@ -1,124 +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: 2018-09-09 08:23+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"
+"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 "Turm"
+msgstr "Tårn"
#: classic\Credits.map:1
msgid "Credits"
-msgstr "Abspann"
+msgstr "Bidragsytere"
#: classic\Lab.map:1
msgid "Lab"
-msgstr "Labor"
+msgstr "Lab"
#: classic\Shadow.map:1
msgid "Shadow"
-msgstr "Schatten"
+msgstr "Skygge"
#: classic\ShadowBlocks.map:1
msgid "ShadowBlocks"
-msgstr "SchattenBlöcke"
+msgstr "Skyggeblokk"
#: classic\levels.lst:1
msgid "classic"
-msgstr "Klassisch"
+msgstr "klassisk"
#: classic\Timing.map:1
+#, fuzzy
msgid "Timing"
-msgstr "Zeitgefühl"
+msgstr "Tidsfølelse"
#: classic\FreeFall.map:1
msgid "FreeFall"
-msgstr "FreierFall"
+msgstr "Frittfall"
#: classic\SomeSpikes.map:1
msgid "SomeSpikes"
-msgstr "EinigeStacheln"
+msgstr "NoenTagger"
#: classic\BabySteps.map:1
msgid "BabySteps"
-msgstr "BabySchritte"
+msgstr "BarneSteg"
#: classic\Tricky.map:1
msgid "Tricky"
-msgstr "Knifflig"
+msgstr "Vanskelig"
#: classic\Road.map:1
msgid "Road"
-msgstr "Straße"
+msgstr "Vei"
#: classic\Jumper.map:1
msgid "Jumper"
-msgstr "Springer"
+msgstr "Hopper"
#: classic\FirstSpikes.map:1
msgid "FirstSpikes"
-msgstr "ErsteStacheln"
+msgstr "FørsteStaker"
#: classic\Jumping.map:1
msgid "Jumping"
-msgstr "Springen"
+msgstr "Hopper"
#: classic\levels.lst:2
msgid "Default level pack"
-msgstr "Standard-Levelsammlung"
+msgstr "Forvalgt brettsamling"
#: classic\LittleHelp.map:1
msgid "LittleHelp"
-msgstr "EtwasHilfe"
+msgstr "LitenHjelp"
#: classic\End.map:1
msgid "End"
-msgstr "Ende"
+msgstr "Slutt"
#: classic\FreeFall2.map:1
msgid "FreeFall2"
-msgstr "FeierFall2"
+msgstr "Frittfall2"
#: classic\Control.map:1
msgid "Control"
-msgstr "Steuerung"
+msgstr "Kontroll"
#: classic\UpDown.map:1
msgid "UpDown"
-msgstr "AufAb"
+msgstr "OppNed"
#: classic\Spiky.map:1
msgid "Spiky"
-msgstr "Stachelig"
+msgstr "Taggete"
#: classic\Here.map:1
msgid "Here"
-msgstr "Hier"
+msgstr "Her"
#: classic\Carry.map:1
msgid "Carry"
-msgstr "Tragen"
+msgstr "Bær"
#: classic\LeftRight.map:1
msgid "LeftRight"
-msgstr "LinksRechts"
+msgstr "VenstreHøyre"
#: classic\Headache.map:1
msgid "Headache"
-msgstr "Kopfschmerzen"
+msgstr "Hodepine"
diff --git a/data/levelpacks/default/locale/nb.po b/data/levelpacks/default/locale/nb.po
new file mode 100644
index 0000000..143f6aa
--- /dev/null
+++ b/data/levelpacks/default/locale/nb.po
@@ -0,0 +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: 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 "Omgrupper"
+
+#: default\3.map:1
+msgid "Running in the Sky"
+msgstr "Løping i himmelen"
+
+#: default\4.map:1
+msgid "Both Up and Down"
+msgstr "Både opp og ned"
+
+#: default\map02.map:1
+msgid "Snail"
+msgstr "Snegle"
+
+#: default\Towers.map:1
+msgid "Towers"
+msgstr "Tårn"
+
+#: default\map01.map:1
+msgid "Simple"
+msgstr "Enkel"
+
+#: default\map04.map:1
+msgid "Double trouble"
+msgstr "Dobbel trøbbel"
+
+#: default\levels.lst:1
+#, fuzzy
+msgid "default"
+msgstr "forvalg"
+
+#: default\Skyscrapers.map:1
+msgid "Skyscrapers"
+msgstr "Skyskrapere"
+
+#: default\QuickSwap.map:1
+msgid "Quick swap"
+msgstr "Raskt bytte"
+
+#: default\5.map:1
+#, fuzzy
+msgid "stopping the spikes"
+msgstr "stopping av taggene"
+
+#: default\levels.lst:2
+msgid "Default"
+msgstr "Forvalg"
+
+#: default\Remote.map:1
+msgid "Remote control"
+msgstr "Fjernkontroll"
+
+#: default\map03.map:1
+msgid "Spiky travel"
+msgstr "Taggete reise"
+
+#: default\2.map:1
+msgid "Tricky Jumping"
+msgstr "Vanskelige hopp"
+
+#: default\1.map:1
+msgid "Building Teamwork"
+msgstr "Lagåndsbygging"
+
+#: default\Timing.map:1
+#, fuzzy
+msgid "Timing"
+msgstr "Tidsfølelse"
+
+#: default\Volcano.map:1
+msgid "Volcano"
+msgstr "Vulkan"
+
+#: default\Sweeper.map:1
+#, fuzzy
+msgid "Sweeper"
+msgstr "Koster"
+
+#: default\Switches.map:1
+msgid "Switches"
+msgstr "Brytere"
+
+#: default\map05.map:1
+msgid "Wall breaking"
+msgstr "Veggknusing"
diff --git a/data/levelpacks/tutorial/locale/nb.po b/data/levelpacks/tutorial/locale/nb.po
new file mode 100644
index 0000000..07acf3a
--- /dev/null
+++ b/data/levelpacks/tutorial/locale/nb.po
@@ -0,0 +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: 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 "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 "veiledning"
+
+#: tutorial\tut23.map:1
+msgid "Collecting Keys"
+msgstr "Nøkkelinnsamling"
+
+#: tutorial\tut10.map:46
+msgid "You've chosen the right way."
+msgstr "Du har valgt rett vei"
+
+#: tutorial\tut22.map:1
+msgid "Shadow swap"
+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 "Skyggeutfordring"
+
+#: tutorial\tut20.map:1
+msgid "Portal mayhem"
+msgstr "Portalkaos"
+
+#: tutorial\tut02.map:1
+msgid "First jumps"
+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 "Lagre din fremdrift her."
+
+#: tutorial\tut13.map:1
+msgid "Moving spikes"
+msgstr "Bevegelige tagger"
+
+#: tutorial\tut13.map:18
+msgid "Watch out for the moving spikes."
+msgstr "Se opp for de bevegelige taggene."
+
+#: tutorial\tut16.map:1
+msgid "The switch"
+msgstr "Bryteren"
+
+#: tutorial\tut25.map:13
+msgid "The very best of luck!"
+msgstr "Lykke til!"
+
+#: tutorial\tut20.map:156
+msgid "Now choose one of the two."
+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 "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 "Du har klart det!"
+
+#: tutorial\tut17.map:1
+msgid "Toggle trigger"
+msgstr "Veksle utløser"
+
+#: tutorial\tut24.map:1
+msgid "Warning"
+msgstr "Advarsel"
+
+#: tutorial\tut05.map:1
+msgid "Shadow"
+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 "Stopper"
+
+#: tutorial\tut01.map:1
+msgid "Walk in the park"
+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 "Første portaler"
+
+#: tutorial\tut25.map:1
+msgid "Final"
+msgstr "Finale"
+
+#: tutorial\tut04.map:1
+msgid "First spikes"
+msgstr "Første tagger"
+
+#: tutorial\tut12.map:1
+msgid "Moving shadow blocks"
+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 "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 "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 "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 "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 "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 "Utløsing"
+
+#: tutorial\tut03.map:1
+msgid "Jumping around"
+msgstr "Hopping rund omkring"
+
+#: tutorial\tut10.map:1
+msgid "Checkpoints"
+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 "Stegvis instroduksjon"
+
+#: tutorial\tut11.map:1
+msgid "Moving blocks"
+msgstr "Bevegelige blokker"
+
+#: tutorial\tut14.map:1
+msgid "Conveyor madness"
+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 "Du har fullført veiledningen!"
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..0eba39b 100644
--- a/data/locale/de.po
+++ b/data/locale/de.po
@@ -1,2123 +1,2123 @@
# 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-09-22 13:26+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."
# 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."
# 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."
# 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."
# 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 "
"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)"
#: ../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"
#: ../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!"
#: ../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"
#: ../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/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/hu.po b/data/locale/nb.po
similarity index 71%
copy from data/locale/hu.po
copy to data/locale/nb.po
index bbebfcb..5ea58fc 100644
--- a/data/locale/hu.po
+++ b/data/locale/nb.po
@@ -1,2106 +1,2157 @@
# 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"
-"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"
+"PO-Revision-Date: 2018-09-19 06:05+0000\n"
+"Last-Translator: Jz Pan <acme_pjz@hotmail.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 "Újonc"
+msgstr "Nybegynner"
#: ../src/AchievementList.cpp:43
msgid "Complete a level."
-msgstr "Szint teljesítése."
+msgstr "Fullføre nivå."
#: ../src/AchievementList.cpp:44
msgid "Experienced player"
-msgstr "Tapasztalt játékos"
+msgstr "Erfaren spiller"
#: ../src/AchievementList.cpp:44
msgid "Complete 50 levels."
-msgstr "50 szint teljesítése."
+msgstr "Fullføre 50 nivåer."
#: ../src/AchievementList.cpp:45
msgid "Good job!"
-msgstr "Szép volt!"
+msgstr "Godt gjort!"
#: ../src/AchievementList.cpp:45
msgid "Receive a gold medal."
-msgstr "Nyerj aranyérmet."
+msgstr "Motta en gullmedalje."
#: ../src/AchievementList.cpp:46
msgid "Expert"
-msgstr "Szakértő"
+msgstr "Ekspert"
#: ../src/AchievementList.cpp:46
msgid "Earn 50 gold medal."
-msgstr "Nyerj 50 aranyérmet."
+msgstr "Motta 50 gullmedaljer."
#: ../src/AchievementList.cpp:48
msgid "Graduate"
-msgstr "Végzős"
+msgstr "Avanser"
#: ../src/AchievementList.cpp:48
msgid "Complete the tutorial level pack."
-msgstr "Teljesítsd a oktató szintcsomagot."
+msgstr "Fullfør veiledningsnivåpakken."
#: ../src/AchievementList.cpp:49
msgid "Outstanding graduate"
-msgstr "Kiváló végzős"
+msgstr "Fremragende disippel"
#: ../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."
+msgstr "Fullfør veiledningsnivåpakken med gull på alle nivåer."
#: ../src/AchievementList.cpp:51
msgid "Hooked"
-msgstr "Ráharaptál"
+msgstr "Hektet"
#: ../src/AchievementList.cpp:51
+#, fuzzy
msgid "Play Me and My Shadow for more than 2 hours."
-msgstr "Több mint 2 óra Me and My Shadow játék."
+msgstr "Spill Jeg og skyggen min i mer enn to timer."
#: ../src/AchievementList.cpp:52
msgid "Loyal fan of Me and My Shadow"
-msgstr "A Me and My Shadow hű rajongója"
+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 "Több mint 24 óra Me and My Shadow játék."
+msgstr "Spill Jeg og skyggen min i mer enn 24 timer."
#: ../src/AchievementList.cpp:54
msgid "Constructor"
-msgstr "Építész"
+msgstr "Byggmester"
#: ../src/AchievementList.cpp:54
msgid "Use the level editor for more than 2 hours."
-msgstr "Több mint 2 óra a szintszerkeszőben."
+msgstr "Bruk nivåredigereren i mer enn to timer."
#: ../src/AchievementList.cpp:55
msgid "The creator"
-msgstr "Az akotó"
+msgstr "Skaperen"
#: ../src/AchievementList.cpp:55
msgid "Use the level editor for more than 24 hours."
-msgstr "Több mint 24 óra a szintszerkeszőben."
+msgstr "Bruk nivåredigereren i mer enn 24 timer."
#: ../src/AchievementList.cpp:57
msgid "Look, cute level!"
-msgstr "Nocsak, cuki szint!"
+msgstr "Se, søtt nivå!"
#: ../src/AchievementList.cpp:57
msgid "Create a level for the first time."
-msgstr "Új szint léthehozása első alkalommal."
+msgstr "Lag et nivå for første gang."
#: ../src/AchievementList.cpp:58
msgid "The level museum"
-msgstr "A szintmúzeum"
+msgstr "Nivåmuseet"
#: ../src/AchievementList.cpp:58
msgid "Create 50 levels."
-msgstr "50 szint létrehozása."
+msgstr "Lag 50 nivåer."
#: ../src/AchievementList.cpp:60
msgid "Hello, World!"
-msgstr "Szia világ!"
+msgstr "Hei, verden!"
#: ../src/AchievementList.cpp:60
msgid "Write a script for the first time."
-msgstr "Szkript írása első alkalommal."
+msgstr "Skriv et skript for første gang."
#: ../src/AchievementList.cpp:62
msgid "Frog"
-msgstr "Béka"
+msgstr "Frosk"
#: ../src/AchievementList.cpp:62
msgid "Jump 1000 times."
-msgstr "1000 ugrás."
+msgstr "Hopp 1000 ganger."
#: ../src/AchievementList.cpp:64
msgid "Wanderer"
-msgstr "Vándor"
+msgstr "Vandrer"
#: ../src/AchievementList.cpp:64
msgid "Travel 100 meters."
-msgstr "100 méternyi utazás."
+msgstr "Reis 100 meter."
#: ../src/AchievementList.cpp:65
msgid "Runner"
-msgstr "Futó"
+msgstr "Løper"
#: ../src/AchievementList.cpp:65
msgid "Travel 1 kilometer."
-msgstr "1 kilóméter utazás."
+msgstr "Reis én kilometer."
#: ../src/AchievementList.cpp:66
msgid "Long distance runner"
-msgstr "Hosszútávfutó"
+msgstr "Langdistanseløper"
#: ../src/AchievementList.cpp:66
msgid "Travel 10 kilometers."
-msgstr "10 kilóméter utazás."
+msgstr "Reis ti kilometer."
#: ../src/AchievementList.cpp:67
msgid "Marathon runner"
-msgstr "Maratonfutó"
+msgstr "Maratonløper"
#: ../src/AchievementList.cpp:67
msgid "Travel 42,195 meters."
-msgstr "42 195 méternyi utazás."
+msgstr "Reis 42195 meter"
#: ../src/AchievementList.cpp:69
msgid "Be careful!"
-msgstr "Óvatosan!"
+msgstr "Vær forsiktig."
#: ../src/AchievementList.cpp:69
msgid "Die for the first time."
-msgstr "Az első halál."
+msgstr "Dø for første gang."
#: ../src/AchievementList.cpp:70
+#, fuzzy
msgid "It doesn't matter..."
-msgstr "Nem számít..."
+msgstr "Det gjør ikke noe…"
#: ../src/AchievementList.cpp:70
msgid "Die 50 times."
-msgstr "50 halál."
+msgstr "Dø 50 ganger."
#: ../src/AchievementList.cpp:71
msgid "Expert of trial and error"
-msgstr "A próbálgatás szakértője"
+msgstr "Ekspert på prøving og feiling"
#: ../src/AchievementList.cpp:71
msgid "Die 1000 times."
-msgstr "1000 halál."
+msgstr "Dø 1000 ganger."
#: ../src/AchievementList.cpp:73
msgid "Keep an eye for moving blocks!"
-msgstr "Figyelj a mozgó tömbökre!"
+msgstr "Hold øye med bevegelige blokker!"
#: ../src/AchievementList.cpp:73
msgid "Get squashed for the first time."
-msgstr "Szétlapítva első alkalommal."
+msgstr "Bli most for første gang."
#: ../src/AchievementList.cpp:74
msgid "Potato masher"
-msgstr "Krumplipüréző"
+msgstr "Potetstamper"
#: ../src/AchievementList.cpp:74
msgid "Get squashed 50 times."
-msgstr "Szétlapítva 50 alkalommal."
+msgstr "Bli most 50 ganger."
#: ../src/AchievementList.cpp:76
msgid "Double kill"
-msgstr "Kettős halál"
+msgstr "Dobbeltdrap"
#: ../src/AchievementList.cpp:76
+#, fuzzy
msgid "Get both the player and the shadow dead."
-msgstr "Haljon meg a játékos és az árnyéka is."
+msgstr "Få både karakteren din og skyggen dens drept."
#: ../src/AchievementList.cpp:78
msgid "Bad luck"
-msgstr "Pech"
+msgstr "Uflaks"
#: ../src/AchievementList.cpp:78
msgid "Die 5 times in under 5 seconds."
-msgstr "Halj meg 5-ször 5 másodpercen belül."
+msgstr "Dø fem ganger på under fem sekunder."
#: ../src/AchievementList.cpp:79
msgid "This level is too dangerous"
-msgstr "Ez a szint túl veszélyes"
+msgstr "Dette nivået er for farlig"
#: ../src/AchievementList.cpp:79
msgid "Die 10 times in under 5 seconds."
-msgstr "Halj meg 10-szer 5 másodpercen belül."
+msgstr "Dø ti ganger på under fem sekunder."
#: ../src/AchievementList.cpp:81
msgid "You forgot your friend"
-msgstr "Megfeledkeztél a barátodról"
+msgstr "Du glemte vennen din"
#: ../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."
+msgstr "Fullfør nivået med spilleren eller skyggen dø."
#: ../src/AchievementList.cpp:82
msgid "Just in time"
-msgstr "Épp időben"
+msgstr "Akkurat tidsnok"
#: ../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."
+msgstr "Nå utgangen med spilleren og skyggen samtidig."
#: ../src/AchievementList.cpp:84
msgid "Recorder"
-msgstr "Felvevő"
+msgstr "Opptaker"
#: ../src/AchievementList.cpp:84
msgid "Record 100 times."
-msgstr "100 felvétel készítése."
+msgstr "Ta opp 100 ganger."
#: ../src/AchievementList.cpp:85
msgid "Shadowmaster"
-msgstr "Árnyékmester"
+msgstr "Skyggemester"
#: ../src/AchievementList.cpp:85
msgid "Record 1000 times."
-msgstr "1000 felvétel készítése."
+msgstr "Ta opp 1000 ganger."
#: ../src/AchievementList.cpp:87
msgid "Switch puller"
-msgstr "Váltókezelő"
+msgstr "Brytervender"
#: ../src/AchievementList.cpp:87
msgid "Pull the switch 100 times."
-msgstr "Váltó meghúzása 100 alkalommal."
+msgstr "Vend bryteren 100 ganger."
#: ../src/AchievementList.cpp:88
msgid "The switch is broken!"
-msgstr "Elromlott a váltó!"
+msgstr "Bryteren er kaputt!"
#: ../src/AchievementList.cpp:88
msgid "Pull the switch 1000 times."
-msgstr "Váltó meghúzása 1000 alkalommal."
+msgstr "Vend bryteren 1000 ganger."
#: ../src/AchievementList.cpp:90
+#, fuzzy
msgid "Swapper"
-msgstr "Cserélő"
+msgstr "Stedveksler"
#: ../src/AchievementList.cpp:90
msgid "Swap 100 times."
-msgstr "Csere 100 alkalommal."
+msgstr "Bytt sted 100 ganger."
#: ../src/AchievementList.cpp:91
+#, fuzzy
msgid "Player to shadow to player to shadow..."
-msgstr "Játékosból árnyék, árnyékból játékos..."
+msgstr "Spiller til skygge til spiller til skygge…"
#: ../src/AchievementList.cpp:91
+#, fuzzy
msgid "Swap 1000 times."
-msgstr "Csere 1000 alkalommal."
+msgstr "Bytt sted 1000 ganger."
#: ../src/AchievementList.cpp:93
msgid "Play it save"
-msgstr "Biztos ami biztos"
+msgstr "Ro det i land"
#: ../src/AchievementList.cpp:93
msgid "Save 1000 times."
-msgstr "Mentés 1000 alkalommal."
+msgstr "Lagre 1000 ganger."
#: ../src/AchievementList.cpp:94
msgid "This game is too hard"
-msgstr "Ez a játék túl nehéz"
+msgstr "Dette spilet er for vanskelig"
#: ../src/AchievementList.cpp:94
msgid "Load the game 1000 times."
-msgstr "Játék betöltése 1000 alkalommal."
+msgstr "Last spillet 1000 ganger."
#: ../src/AchievementList.cpp:96
msgid "No, thanks"
-msgstr "Kösz, nem"
+msgstr "Nei takk"
#: ../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."
+msgstr "Fullfør et nivå med sjekkpunkt, men uten å lagre."
#: ../src/AchievementList.cpp:98
msgid "Panic save"
-msgstr "Pánik mentés"
+msgstr "Panikklagring"
#: ../src/AchievementList.cpp:98
msgid "Save twice in 1 second."
-msgstr "2 mentés 1 másodpercen belül."
+msgstr "Lagre to ganger på ett sekund"
#: ../src/AchievementList.cpp:99
msgid "Panic load"
-msgstr "Pánik betöltés"
+msgstr "Panikklasting"
#: ../src/AchievementList.cpp:99
msgid "Load twice in 1 second."
-msgstr "2 betöltés 1 másodpercen belül."
+msgstr "Last inn to ganger på ett sekund."
#: ../src/AchievementList.cpp:101
msgid "Bad saving position"
-msgstr "Rossz mentési pozíció"
+msgstr "Dårlig lagringsposisjon"
#: ../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."
+msgstr "Last spillet og dø på mindre enn ett sekund."
#: ../src/AchievementList.cpp:102
msgid "This level is too hard"
-msgstr "Ez a szint túl nehéz"
+msgstr "Dette nivået er for vanskelig"
#: ../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."
+msgstr "Last samme lagring og dø 100 ganger."
#: ../src/AchievementList.cpp:104
msgid "Quick swap"
-msgstr "Gyorsváltás"
+msgstr "Raskt bytte"
#: ../src/AchievementList.cpp:104
msgid "Swap twice in under a second."
-msgstr "Váltás kétszer egy másodpercen belül."
+msgstr "Bytt sted to ganger på under ett sekund."
#: ../src/AchievementList.cpp:107
msgid "Horizontal confusion"
-msgstr "Vízszintes összezavarodás"
+msgstr "Vannrett forvirring"
#: ../src/AchievementList.cpp:107
+#, fuzzy
msgid "Press left and right simultaneously."
-msgstr "Nyomd meg egyszerre a balra és a jobbra gombot."
+msgstr "Trykk ← og → samtidig."
#: ../src/AchievementList.cpp:109
msgid "Cheater"
-msgstr "Csaló"
+msgstr "Jukser"
#: ../src/AchievementList.cpp:109
msgid "Cheat in game."
-msgstr "Csalás a játékban."
+msgstr "Juks i spillet."
#: ../src/AchievementList.cpp:111
msgid "Programmer"
-msgstr "Programozó"
+msgstr "Programmerer"
#: ../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."
+msgstr "Spill utviklingsversjonen av Jeg og skyggen min."
#: ../src/Addons.cpp:44 ../src/LevelPackManager.cpp:108
msgid "Levels"
-msgstr "Szintek"
+msgstr "Nivå"
#: ../src/Addons.cpp:44
+#, fuzzy
msgid "Single level which usually contain demanding puzzles"
-msgstr "Egyetlen szint, mely általában igényes rejtvényeket tartalmaz"
+msgstr "Enkeltnivå som vanligvis inneholder utfordrende gåter"
#: ../src/Addons.cpp:45
msgid "Levelpacks"
-msgstr "Szintcsomagok"
+msgstr "Nivåpakker"
#: ../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"
+msgstr "Samling av nivåer laget av samme utvikler eller i samme stil"
#: ../src/Addons.cpp:46
msgid "Themes"
-msgstr "Témák"
+msgstr "Drakter"
#: ../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"
+msgstr "Gi hver blokk og bakgrunn nytt utseende og oppførsel"
#: ../src/Addons.cpp:55 ../src/TitleMenu.cpp:46
msgid "Addons"
-msgstr "Bővítmények"
+msgstr "Programtillegg"
#: ../src/Addons.cpp:87
msgid "Unable to initialize addon menu:"
-msgstr "Nem tudtam az alábbi bővítmény-menüt elindítani:"
+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 "Vissza"
+msgstr "Tilbake"
#: ../src/Addons.cpp:169
+#, fuzzy
msgid "ERROR: unable to download addons file!"
-msgstr "HIBA: a bővítmények letöltése sikertelen!"
+msgstr "FEIL: Kunne ikke laste 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 "HIBA: az addon_list fájl letöltése sikertelen!"
+msgstr "FEIL: Kunne ikke laste inn addon_list-fil!"
#: ../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ó!"
+msgstr "FEIL: Ugyldig filformat for programtilleggsfil!"
#: ../src/Addons.cpp:205
+#, fuzzy
msgid "ERROR: Addon list version is unsupported!"
-msgstr "HIBA: nem támogatott addon_list verzió!"
+msgstr "FEIL: Programtilleggsversjonen støttes ikke!"
# TRANSLATORS: installed_addons is the name of a file and should not be translated.
#: ../src/Addons.cpp:226
+#, fuzzy
msgid "ERROR: Unable to create the installed_addons file."
-msgstr "HIBA: az installed_addons fájl létrehozása sikertelen."
+msgstr "FEIL: Kunne ikke opprette programtilleggsfil for installasjoner."
#: ../src/Addons.cpp:238
msgid "ERROR: Invalid file format of the installed_addons!"
-msgstr "HIBA: az installed_addons fájl formátuma nem jó!"
+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 "írta: %s"
+msgstr "av %s"
#: ../src/Addons.cpp:397
msgid "Installed"
-msgstr "Telepítve"
+msgstr "Installert"
#: ../src/Addons.cpp:402
+#, fuzzy
msgid "Updatable"
-msgstr "Frissíthető"
+msgstr "Oppdaterbar"
#: ../src/Addons.cpp:412
msgid "Not installed"
-msgstr "Nincs telepítve"
+msgstr "Ikke installert"
#: ../src/Addons.cpp:625
#, c-format
msgid "Version: %d\n"
-msgstr "Verzió: %d\n"
+msgstr "Versjon: %d\n"
#: ../src/Addons.cpp:627
#, c-format
msgid "Installed version: %d\n"
-msgstr "Telepített verzió: %d\n"
+msgstr "Installert versjon: %d\n"
#: ../src/Addons.cpp:630
#, c-format
msgid "License: %s\n"
-msgstr "Licensz: %s\n"
+msgstr "Lisens: %s\n"
#: ../src/Addons.cpp:633
#, c-format
msgid "Website: %s\n"
-msgstr "Weboldal: %s\n"
+msgstr "Nettside: %s\n"
#: ../src/Addons.cpp:637
msgid "(No descriptions provided)"
-msgstr "(Nincs hozzá leírás)"
+msgstr "(Ingen beskrivelser medfølger)"
#: ../src/Addons.cpp:657 ../src/Addons.cpp:684
msgid "Remove"
-msgstr "Eltávolítás"
+msgstr "Fjern"
#: ../src/Addons.cpp:673
+#, fuzzy
msgid "Update"
-msgstr "Frissítés"
+msgstr "Oppdater"
#: ../src/Addons.cpp:679
msgid "Install"
-msgstr "Telepítés"
+msgstr "Installer"
#: ../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."
+msgstr "Dette programtillegget kan ikke fjernes fordi det trengs av %s."
#: ../src/Addons.cpp:774 ../src/Addons.cpp:1051
msgid "Dependency"
-msgstr "Függőség"
+msgstr "Avhengighet"
#: ../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."
+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 "Bővítmény hiba"
+msgstr "Programtilleggsfeil"
#: ../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!"
+msgstr "FEIL: Kunne ikke fjerne filen \"%s\"."
#: ../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."
+msgstr "ADVARSEL: Mappen \"%s\" later til å ha blitt fjernet allerede."
#: ../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!"
+msgstr "FEIL: Kunne ikke fjerne mappen \"%s\"."
#: ../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."
+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 "HIBA: Nem sikerült törölni a '%s' szintet!"
+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 "FIGYELMEZTETÉS: A szintcsomag '%s' könyvtára már törölve van."
+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 "HIBA: Nem sikerült törölni a szintcsomag '%s' könyvtárat!"
+msgstr "FEIL: Kunne ikke fjerne nivåpakkemappen \"%s\"."
#: ../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."
+msgstr "FEIL: Kunne ikke laste ned programtilleggsfilen %s."
#: ../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."
+msgstr "FEIL: Kunne ikke pakke ut programtilleggsfilen %s."
#: ../src/Addons.cpp:907
msgid "ERROR: Addon is missing metadata!"
-msgstr "HIBA: A bővítmény nem tartalmaz meta-adatokat!"
+msgstr "FEIL: Programtillegget mangler metadata!"
#: ../src/Addons.cpp:918
msgid "ERROR: Invalid file format for metadata file!"
-msgstr "HIBA: Érvénytelen meta-adat fáljformátum!"
+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 ""
-"FIGYELMEZTETÉS: A '%s' fájl már létezik, a bővitmény hibás, vagy nem működik!"
+"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 ""
-"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!"
+"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 ""
-"FIGYELMEZTETÉS: A '%s' célkönyvtár már létezik, a bővitmény hibás, vagy nem "
-"működik!"
+"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 ""
-"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!"
+"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 ""
-"FIGYELMEZTETÉS: A '%s' szint már létezik, a bővitmény hibás, vagy nem "
-"működik!"
+"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 ""
-"FIGYELMEZTETÉS: A '%s' szintet nem sikerült ide másolni: '%s', a bővítmény "
-"hibás vagy nem működik!"
+"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 ""
-"FIGYELMEZTETÉS: A '%s' szintcsomag könyvtára már létezik, a bővitmény hibás, "
-"vagy nem működik!"
+"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 "HIBA: A bővítmény másik bővítményt (%s) igényel, de az nem található!"
+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 "A %s bővítményre szükség van, és telepítve lesz most."
+msgstr "Programtillegget %s er nødvendig, og vil bli installert nå."
#: ../src/Block.cpp:822 ../src/LevelEditor.cpp:265
msgid "On"
-msgstr "Be"
+msgstr "På"
#: ../src/Block.cpp:823 ../src/LevelEditor.cpp:266
msgid "Off"
-msgstr "Ki"
+msgstr "Av"
#: ../src/CommandManager.cpp:41
#, c-format
msgid "Undo %s"
-msgstr "%s visszavonása"
+msgstr "Angre %s"
#: ../src/CommandManager.cpp:43
msgid "Can't undo"
-msgstr "Nem tudom visszavonni"
+msgstr "Kan ikke angre"
#: ../src/CommandManager.cpp:49
#, c-format
msgid "Redo %s"
-msgstr "%s újra"
+msgstr "Gjenta %s"
#: ../src/CommandManager.cpp:51
msgid "Can't redo"
-msgstr "Nem lehet újra csinálni"
+msgstr "Kan ikke gjenta"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:190
msgid "Resize level"
-msgstr "Szint átméretezése"
+msgstr "Endre nivåstørrelse"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:807
msgid "Modify level property"
-msgstr "Szint tulajdonság módosítása"
+msgstr "Endre nivåegenskap"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:919
#, c-format
msgid "Add scenery layer %s"
-msgstr "Táj réteg %s hozzáadása"
+msgstr "Legg til omgivelseslag %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:921
#, c-format
msgid "Delete scenery layer %s"
-msgstr "Táj réteg %s törlése"
+msgstr "Slett omgivelseslag %s"
# 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"
+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 "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
+#, fuzzy
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
msgid "%s (Scenery)"
msgstr ""
#: ../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
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 ""
+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
msgid "NOTE: 1 Speed = 0.08 block/s"
msgstr ""
#: ../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/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/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..c49db93 100644
--- a/src/AchievementList.cpp
+++ b/src/AchievementList.cpp
@@ -1,115 +1,115 @@
/*
* 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/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},
{"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},
{"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},
{"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 },
{"frog",__("Frog"),"themes/Cloudscape/characters/player.png",{0,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},
{"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},
{"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},
{"doubleKill",__("Double kill"),"themes/Cloudscape/characters/deathright.png",{0,14,23,40},__("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},
{"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},
{"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},
{"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},
{"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},
{ "withoutsave", __("No, thanks"), "themes/Cloudscape/characters/player.png", { 0, 0, 23, 40 }, __("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},
{"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},
{"quickswap",__("Quick swap"),"themes/Cloudscape/tiles/swap.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 },
{"programmer",__("Programmer"),"gfx/gui.png",{112,16,16,16},__("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..f17d0c1 100644
--- a/src/Addons.cpp
+++ b/src/Addons.cpp
@@ -1,1066 +1,1080 @@
/*
* 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_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/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/Game.cpp b/src/Game.cpp
index 618e285..6bc72c4 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,1832 +1,1832 @@
/*
* 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 <assert.h>
#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);
}
}
//An internal function.
static void copyLevelObjects(const std::vector<Block*>& src, std::vector<Block*>& dest, bool setActive) {
//Clear the existing objects.
for (auto o : dest) delete o;
dest.clear();
//Copy the source to the destination.
for (auto o : src) {
if (o == NULL || o->isDelete) continue;
Block* o2 = new Block(*o);
dest.push_back(o2);
if (setActive) o2->setActive();
}
}
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, etc. and delete them.
for (auto o : levelObjects) delete o;
levelObjects.clear();
for (auto o : levelObjectsSave) delete o;
levelObjectsSave.clear();
for (auto o : levelObjectsInitial) delete o;
levelObjectsInitial.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.
assert(levelObjects.empty() && levelObjectsSave.empty() && levelObjectsInitial.empty());
//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, "standright");
player.appearanceInitial = player.appearanceSave = player.appearance;
objThemes.getCharacter(true)->createInstance(&shadow.appearance, "standright");
shadow.appearanceInitial = shadow.appearanceSave = 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.
//Before doing that we swap the levelObjects to levelObjectsInitial.
std::swap(levelObjects, levelObjectsInitial);
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;
}
}
//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();
}
//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)
+ if(bestTime>=0 && (targetTime<=0 || bestTime<=targetTime))
oldMedal++;
- if(targetRecordings<0 || bestRecordings<=targetRecordings)
+ 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)
+ 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.get() == 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);
}
//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){
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());
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);
timeTexture.update(time,
textureFromTextShaded(
renderer,
*fontText,
c,
fg,
bg
));
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);
}
}else if(auto blockId = player.objNotificationBlock.get()){
//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.
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=blockId->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)
+ if(bestTime>=0 && (targetTime<=0 || bestTime<=targetTime))
medal++;
- if(targetRecordings<0 || bestRecordings<=targetRecordings)
+ 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){
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());
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());
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());
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){
+ if(targetTime<0 || 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.
copyLevelObjects(levelObjects, levelObjectsSave, false);
//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.
copyLevelObjects(levelObjectsSave, levelObjects, true);
//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){
//Some sanity check, i.e. if we switch from no-script mode to script mode, we should always reset the save
assert(noScript || getScriptExecutor() || save);
//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;
//Clear the event queue, since all the events are from before the reset.
eventQueue.clear();
//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.
scriptExecutor->destroy();
//Clear the level script.
compiledScripts.clear();
savedCompiledScripts.clear();
initialCompiledScripts.clear();
//Clear the block script.
for (auto block : levelObjects){
block->compiledScripts.clear();
}
for (auto block : levelObjectsSave){
block->compiledScripts.clear();
}
for (auto block : levelObjectsInitial){
block->compiledScripts.clear();
}
} else if (save) {
//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);
savedCompiledScripts[it->first] = luaL_ref(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX);
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();
}
for (auto block : levelObjectsSave){
block->compiledScripts.clear();
}
for (auto block : levelObjectsInitial) {
block->compiledScripts.clear();
for (auto it = block->scripts.begin(); it != block->scripts.end(); ++it){
int index = getScriptExecutor()->compileScript(it->second);
block->compiledScripts[it->first] = index;
}
}
} else {
assert(getScriptExecutor());
//Do a soft reset.
getScriptExecutor()->reset(false);
//Restore the level script to initial state.
copyCompiledScripts(getScriptExecutor()->getLuaState(), initialCompiledScripts, compiledScripts);
//NOTE: We don't need to restore the block script since it will be restored automatically when the block array is copied.
}
//We reset levelObjects here since we need to wait the compiledScripts being initialized.
copyLevelObjects(levelObjectsInitial, levelObjects, true);
if (save) {
copyLevelObjects(levelObjectsInitial, levelObjectsSave, false);
}
//Also reset the last checkpoint so set it to NULL.
if (save)
objLastCheckPoint = NULL;
//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/LevelEditor.cpp b/src/LevelEditor.cpp
index 27b7cca..a5d12f3 100644
--- a/src/LevelEditor.cpp
+++ b/src/LevelEditor.cpp
@@ -1,4826 +1,4844 @@
/*
* 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 "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")
};
//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;
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;
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;
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;
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;
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;
}
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;
}
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;
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));
}
}
//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);
//We swap the levelObjects and levelObjectsInitial again.
std::swap(levelObjects, levelObjectsInitial);
//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 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;
}
//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);
//Update the target recordings the levelpack.
levels->getLevel()->targetRecordings=levelRecordings;
}
}
//The width of the level.
maxX=LEVEL_WIDTH;
sprintf(s,"%d",maxX);
node.attributes["size"].push_back(s);
//The height of the level.
maxY=LEVEL_HEIGHT;
sprintf(s,"%d",maxY);
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);
//We swap the levelObjects and levelObjectsInitial again.
std::swap(levelObjects, levelObjectsInitial);
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){
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) {
objMap["sceneryName"] = scenery->sceneryName_;
objMap["customScenery"] = scenery->customScenery_;
} else {
sprintf(s, "%d", selection[o]->type);
objMap["type"] = s;
}
//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()));
} 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){
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;
//We swap the levelObjects and levelObjectsInitial again.
std::swap(levelObjects, levelObjectsInitial);
//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):"));
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();
- obj2->limitMin=0.0f;
+ obj2->limitMin=-1.0f;
obj2->format = "%g";
obj2->change=0.1f;
obj2->update();
root->addChild(obj2);
obj=new GUILabel(imageManager,renderer,40,310,240,36,_("Target recordings:"));
root->addChild(obj);
obj2=new GUISpinBox(imageManager,renderer,290,310,260,36);
obj2->gravityRight = GUIGravityRight;
ostringstream ss2;
ss2 << levelRecordings;
obj2->caption=ss2.str();
- obj2->limitMin=0.0f;
+ obj2->limitMin=-1.0f;
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")));
}
}
}
//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");
+ GUISpinBox* object2 = dynamic_cast<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));
- }
+ float number = atof(object2->caption.c_str());
+ prop.levelTime = int(floor(number*40.0f + 0.5f));
}
- object2=(GUISpinBox*)obj->getChild("recordings");
- if(object){
- int number=atoi(object2->caption.c_str());
- if(number<=0){
- prop.levelRecordings=-1;
- }else{
- prop.levelRecordings=number;
- }
+ object2 = dynamic_cast<GUISpinBox*>(obj->getChild("recordings"));
+ if(object2){
+ prop.levelRecordings = atoi(object2->caption.c_str());
}
// 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);
}
}
//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/LevelPlaySelect.cpp b/src/LevelPlaySelect.cpp
index 251c0b7..098e6b8 100644
--- a/src/LevelPlaySelect.cpp
+++ b/src/LevelPlaySelect.cpp
@@ -1,503 +1,511 @@
/*
* 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;
+ int medal=levels->getLevel(n)->won?1:0;
if(medal){
- if(levels->getLevel(n)->targetTime<0 || levels->getLevel(n)->time<=levels->getLevel(n)->targetTime)
- medal++;
- if(levels->getLevel(n)->targetRecordings<0 || levels->getLevel(n)->recordings<=levels->getLevel(n)->targetRecordings)
- medal++;
+ const int targetTime = levels->getLevel(n)->targetTime;
+ const int targetRecordings = levels->getLevel(n)->targetRecordings;
+ if (targetTime < 0) {
+ medal = 3;
+ } else {
+ const int time = levels->getLevel(n)->time;
+ const int recordings = levels->getLevel(n)->recordings;
+ if (time >= 0 && (targetTime <= 0 || time <= targetTime))
+ medal++;
+ 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;
+ medal = 3;
} else{
- if (targetTime < 0 || time <= targetTime)
+ if (time >= 0 && (targetTime <= 0 || time <= targetTime))
medal++;
- if (targetRecordings < 0 || recordings <= targetRecordings)
+ 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);
else
sprintf(s, "%-.2fs / -", time / 40.0f);
else
s[0] = '\0';
levelTime = s;
if (recordings >= 0)
- if (targetRecordings >= 0)
+ if (targetTime >= 0 && 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);
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 89571a0..72f2681 100644
--- a/src/Player.cpp
+++ b/src/Player.cpp
@@ -1,1676 +1,1687 @@
/*
* 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 <stdio.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
//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 = objNotificationBlock = objShadowBlock = NULL;
objCurrentStandSave = objLastStandSave = objLastTeleportSave = objNotificationBlockSave = objShadowBlockSave = 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.get()){
//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.get()){
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.get()){
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){
appearance.changeState("walkright",true,true);
}else if(xVel<0){
appearance.changeState("walkleft",true,true);
}else if(xVel==0){
if(direction==1){
appearance.changeState("standleft",true,true);
}else{
appearance.changeState("standright",true,true);
}
}
}else{
//Check for jump appearance (inAir).
if(direction==1){
if(yVel>0){
appearance.changeState("fallleft",true,true);
}else{
appearance.changeState("jumpleft",true,true);
}
}else{
if(yVel>0){
appearance.changeState("fallright",true,true);
}else{
appearance.changeState("jumpright",true,true);
}
}
}
}
//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(auto tmp = objCurrentStand.get()) {
baseBlock=tmp;
} 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.get();
}
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;
auto ols = objLastStand.get();
if(lastStand!=ols){
//The player has changed block so call the playerleave event.
if(ols)
objParent->broadcastObjectEvent(GameObjectEvent_PlayerLeave,-1,NULL,ols);
//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: 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(lastStand)
objParent->broadcastObjectEvent(GameObjectEvent_PlayerIsOn,-1,NULL,lastStand);
//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 = appearanceInitial;
if (save) appearanceSave = appearanceInitial;
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=objLastTeleportSave=objNotificationBlockSave=objShadowBlockSave=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.
appearanceSave = appearance;
//Save the lastStand and currentStand pointers.
objCurrentStandSave=objCurrentStand;
objLastStandSave=objLastStand;
objLastTeleportSave = objLastTeleport;
objNotificationBlockSave = objNotificationBlock;
objShadowBlockSave = objShadowBlock;
//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;
objLastTeleport = objLastTeleportSave;
objNotificationBlock = objNotificationBlockSave;
objShadowBlock = objShadowBlockSave;
//Restore the appearance.
appearance = appearanceSave;
//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);
objCurrentStand.swap(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/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/ThemeManager.cpp b/src/ThemeManager.cpp
index 64feab0..dcdcc91 100644
--- a/src/ThemeManager.cpp
+++ b/src/ThemeManager.cpp
@@ -1,1579 +1,1581 @@
/*
* 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)
{
}
ThemeBlockStateInstance::ThemeBlockStateInstance()
: parent(NULL), animation(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++;
}
ThemeBlockInstance::ThemeBlockInstance()
: currentState(-1)
{
}
bool ThemeBlockInstance::draw(SDL_Renderer& renderer, int x, int y, int w, int h, const SDL_Rect *clipRect){
if (currentState >= 0 && currentState < (int)states.size()) {
states[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){
auto it = blockStates.find(s);
if (it != blockStates.end() && it->second >= 0 && it->second < (int)states.size()) {
states[it->second].draw(renderer, x, y, w, h, clipRect);
return true;
}
return false;
}
bool ThemeBlockInstance::changeState(const string& s, bool reset, bool onlyIfStateChanged) {
//Check if we don't need to change state at all.
if (onlyIfStateChanged && s == currentStateName) {
return true;
}
bool newState = false;
//First check if there's a transition.
{
auto it = transitions.find(pair<string, string>(currentStateName, s));
if (it != transitions.end() && it->second >= 0 && it->second < (int)states.size()) {
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.
auto it = blockStates.find(s);
//Check if it exists.
if (it != blockStates.end() && it->second >= 0 && it->second < (int)states.size()) {
currentState = it->second;
currentStateName = it->first;
newState = true;
}
}
//Check if a state has been found.
if (newState){
//If reset then reset the animation.
if (reset) {
ThemeBlockStateInstance &current = states[currentState];
current.animation = 0;
for (auto& obj : current.objects) {
obj.animation = 0;
}
}
return true;
}
//It doesn't so return false.
return 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 >= 0 && currentState < (int)states.size()) {
ThemeBlockStateInstance &current = states[currentState];
//Call the updateAnimation method of the currentState.
current.updateAnimation();
//Get the length of the animation.
int m = current.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 && current.animation >= m){
//Now we can change the state to the nextState.
changeState(current.parent->nextState);
}
}
}
void ThemeBlockInstance::clear() {
currentState = -1;
currentStateName.clear();
blockStates.clear();
transitions.clear();
states.clear();
}
void ThemeBlock::createInstance(ThemeBlockInstance* obj, const std::string& initialState) {
//Make sure the given ThemeBlockInstance is ready.
obj->clear();
//Loop through the blockstates.
for (auto it = blockStates.begin(); it != blockStates.end(); ++it) {
//Create a new themeBlockStateInstance.
obj->states.emplace_back();
ThemeBlockStateInstance &obj1 = obj->states.back();
//Register it with given name.
obj->blockStates[it->first] = obj->states.size() - 1;
//Set the parent of the state instance.
obj1.parent = it->second;
//Create the state instance.
createStateInstance(&obj1);
}
//Loop through the transitions.
for (auto it = transitions.begin(); it != transitions.end(); ++it) {
//Create a new themeBlockStateInstance.
obj->states.emplace_back();
ThemeBlockStateInstance &obj1 = obj->states.back();
//Register it with given name.
obj->transitions[it->first] = obj->states.size() - 1;
//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(initialState);
}
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:15 PM (1 d, 8 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63200
Default Alt Text
(651 KB)

Event Timeline