diff --git a/CMakeLists.txt b/CMakeLists.txt index 63f1ace6..90843488 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,12 +189,12 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") set(NORMAL_CXX_FLAGS "/wd\"4244\" /wd\"4309\" /wd\"4800\" /wd\"4996\" /wd\"4351\" /EHsc") # disable some useless warnings if(MSVC_STATIC) set(RELEASE_CXX_FLAGS "/MT /Ox") - set(DEBUG_CXX_FLAGS "/MTd /ZI") + set(DEBUG_CXX_FLAGS "/MTd /Od /ZI") else(MSVC_STATIC) set(RELEASE_CXX_FLAGS "/MD /Ox") - set(DEBUG_CXX_FLAGS "/MDd /ZI") + set(DEBUG_CXX_FLAGS "/MDd /Od /ZI") endif() - set(TEST_CXX_FLAGS "") + set(TEST_CXX_FLAGS "${DEBUG_CXX_FLAGS}") add_definitions(-DNOEXCEPT= -DHAS_MSVC_EXCEPTION_BUG) # Needed for Debug information (it's set to "No" by default for some reason) diff --git a/data b/data index 611cbfdd..069fc5bd 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 611cbfdd079e97a71f97810636f2ab2358cb4eeb +Subproject commit 069fc5bd15b87aaf5b1b301a787f05a99dfc3856 diff --git a/po/colobot.pot b/po/colobot.pot index a331ffba..49ecac68 100644 --- a/po/colobot.pot +++ b/po/colobot.pot @@ -84,6 +84,9 @@ msgstr "" msgid "Load a saved mission" msgstr "" +msgid "Mods" +msgstr "" + msgid "Chapters:" msgstr "" @@ -166,6 +169,23 @@ msgstr "" msgid "This menu is for userlevels from mods, but you didn't install any" msgstr "" +msgid "Could not open the file explorer!" +msgstr "" + +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "" + +msgid "Could not open the web browser!" +msgstr "" + +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "" + +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "" + msgid "Keyword help(\\key cbot;)" msgstr "" @@ -280,6 +300,42 @@ msgstr "" msgid "%s: %d pts" msgstr "" +msgid "Mods:" +msgstr "" + +msgid "Information:" +msgstr "" + +msgid "Description:" +msgstr "" + +msgid "Enable\\Enable the selected mod" +msgstr "" + +msgid "Disable\\Disable the selected mod" +msgstr "" + +msgid "Unknown author" +msgstr "" + +msgid "by" +msgstr "" + +msgid "Version" +msgstr "" + +msgid "Website" +msgstr "" + +msgid "Changes" +msgstr "" + +msgid "No description." +msgstr "" + +msgid "No changes." +msgstr "" + msgid "Code battle" msgstr "" @@ -316,6 +372,9 @@ msgstr "" msgid "SatCom" msgstr "" +msgid "Mods\\Mod manager" +msgstr "" + msgid "Change player\\Change player" msgstr "" @@ -343,6 +402,24 @@ msgstr "" msgid "Play\\Start mission!" msgstr "" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "" + +msgid "Open Directory\\Open the mods directory" +msgstr "" + +msgid "Apply\\Apply the current mod configuration" +msgstr "" + +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "" + +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "" + +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "" + msgid "Device\\Driver and resolution settings" msgstr "" diff --git a/po/cs.po b/po/cs.po index 175ab412..c7755b3b 100644 --- a/po/cs.po +++ b/po/cs.po @@ -125,6 +125,9 @@ msgstr "Vzhled\\Upravte svůj vzhled" msgid "Apply changes\\Activates the changed settings" msgstr "Uložit změny\\Aktivovat změny nastavení" +msgid "Apply\\Apply the current mod configuration" +msgstr "" + msgid "Appropriate constructor missing" msgstr "Chybí vhodný konstruktor" @@ -371,6 +374,9 @@ msgstr "Změnit kameru\\Přepíná mezi kamerou na robotu a za robotem" msgid "Change player\\Change player" msgstr "Změnit hráče\\Změnit hráče" +msgid "Changes" +msgstr "" + msgid "Chapters:" msgstr "Kapitoly:" @@ -440,6 +446,12 @@ msgstr "Kopírovat" msgid "Copy (Ctrl+C)" msgstr "Kopírovat (Ctrl+C)" +msgid "Could not open the file explorer!" +msgstr "" + +msgid "Could not open the web browser!" +msgstr "" + msgid "Current mission saved" msgstr "Současná mise uložena" @@ -473,6 +485,9 @@ msgstr "Vrtná věž" msgid "Descend\\Reduces the power of the jet" msgstr "Klesat\\Snížit tah tryskového motoru" +msgid "Description:" +msgstr "" + msgid "Destroy" msgstr "Zbourat" @@ -485,6 +500,9 @@ msgstr "Drtič" msgid "Device\\Driver and resolution settings" msgstr "Obrazovka\\Nastavení grafické karty a rozlišení" +msgid "Disable\\Disable the selected mod" +msgstr "" + msgid "Dividing by zero" msgstr "Dělení nulou" @@ -501,6 +519,9 @@ msgstr "Dveře blokuje robot nebo jiný objekt" msgid "Down (\\key gdown;)" msgstr "Dolů (\\key gdown;)" +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Drawer bot" msgstr "Tužkobot" @@ -528,6 +549,9 @@ msgstr "Vejce" msgid "Empty character constant" msgstr "" +msgid "Enable\\Enable the selected mod" +msgstr "" + msgid "End of block missing" msgstr "Chybí konec bloku" @@ -754,6 +778,9 @@ msgstr "Infikováno virem; dočasně mimo provoz" msgid "Information exchange post" msgstr "Komunikační stanice" +msgid "Information:" +msgstr "" + msgid "Instruction \"break\" outside a loop" msgstr "Příkaz \"break\" mimo cyklus" @@ -904,6 +931,15 @@ msgstr "Mise na této planetě:" msgid "Missions\\Select mission" msgstr "Mise\\Vyberte misi" +msgid "Mods" +msgstr "" + +msgid "Mods:" +msgstr "" + +msgid "Mods\\Mod manager" +msgstr "" + msgid "Mouse inversion X\\Inversion of the scrolling direction on the X axis" msgstr "Vodorovné převrácení posunu\\Při vodorovném posunu kamery myší pousouvat opačným směrem" @@ -952,6 +988,12 @@ msgstr "Další objekt\\Vybere následující objekt" msgid "No" msgstr "Ne" +msgid "No changes." +msgstr "" + +msgid "No description." +msgstr "" + msgid "No energy in the subsoil" msgstr "Pod povrchem není zdroj energie" @@ -1072,6 +1114,9 @@ msgstr "Otevřít" msgid "Open (Ctrl+O)" msgstr "Otevřít (Ctrl+O)" +msgid "Open Directory\\Open the mods directory" +msgstr "" + msgid "Opening brace missing" msgstr "Chybí levá složená závorka" @@ -1282,6 +1327,9 @@ msgstr "Červená vlajka" msgid "Reflections on the buttons \\Shiny buttons" msgstr "Odlesky na tlačítkách\\Blyštivá tlačítka" +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "" + msgid "Remains of Apollo mission" msgstr "Pozůstatky mise Apollo" @@ -1543,6 +1591,10 @@ msgstr "Filtrování textur\\Filtrování textur" msgid "Textures" msgstr "Textury" +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "" + msgid "The battle has ended" msgstr "Souboj skončil" @@ -1555,9 +1607,16 @@ msgstr "Funkce nevrátila žádnou hodnotu" msgid "The mission is not accomplished yet (press \\key help; for more details)" msgstr "Mise ještě nebyla splněna (pro podrobnosti stiskněte \\key help;)" +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "" + msgid "The types of the two operands are incompatible" msgstr "Operaci nelze provést s operandy těchto dvou typů" +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "" + msgid "This class already exists" msgstr "Tato třída již existuje" @@ -1682,6 +1741,9 @@ msgstr "Jednotka" msgid "Unknown Object" msgstr "Neznámý objekt" +msgid "Unknown author" +msgstr "" + msgid "Unknown command" msgstr "Neznámý příkaz" @@ -1694,6 +1756,9 @@ msgstr "Neznámá funkce" msgid "Up (\\key gup;)" msgstr "Vzhůru (\\key gup;)" +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Uranium deposit (site for derrick)" msgstr "Uranové ložisko (místo pro vrtnou věž)" @@ -1715,6 +1780,9 @@ msgstr "Proměnná nebyla nastavena" msgid "Vault" msgstr "Trezor" +msgid "Version" +msgstr "" + msgid "Vertical Synchronization\\Limits the number of frames per second to display frequency" msgstr "" @@ -1733,6 +1801,9 @@ msgstr "Vosa byla smrtelně raněna" msgid "Waste" msgstr "Odpad" +msgid "Website" +msgstr "" + msgid "Wheeled builder" msgstr "" @@ -1766,6 +1837,9 @@ msgstr "Létající detektor" msgid "Withdraw shield (\\key action;)" msgstr "Vypnout štít (\\key action;)" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "" + msgid "Worm" msgstr "Červ" @@ -1916,6 +1990,9 @@ msgstr "\\Fialové vlajky" msgid "\\Yellow flags" msgstr "\\Žluté vlajky" +msgid "by" +msgstr "" + msgid "colobot.info" msgstr "colobot.info" diff --git a/po/de.po b/po/de.po index 8afe0c8b..059898a0 100644 --- a/po/de.po +++ b/po/de.po @@ -126,6 +126,9 @@ msgstr "Aussehen\\Erscheinungsbild des Astronauten einstellen" msgid "Apply changes\\Activates the changed settings" msgstr "Änderungen anwenden\\Getätigte Einstellungen anwenden" +msgid "Apply\\Apply the current mod configuration" +msgstr "" + msgid "Appropriate constructor missing" msgstr "Es gibt keinen geeigneten Konstruktor" @@ -372,6 +375,9 @@ msgstr "Andere Kamera\\Sichtpunkt einstellen" msgid "Change player\\Change player" msgstr "Anderer Spieler\\Spielername ändern" +msgid "Changes" +msgstr "" + msgid "Chapters:" msgstr "Liste der Kapitel:" @@ -441,6 +447,12 @@ msgstr "Kopieren" msgid "Copy (Ctrl+C)" msgstr "Kopieren (Ctrl+C)" +msgid "Could not open the file explorer!" +msgstr "" + +msgid "Could not open the web browser!" +msgstr "" + msgid "Current mission saved" msgstr "Mission gespeichert" @@ -474,6 +486,9 @@ msgstr "Bohrturm" msgid "Descend\\Reduces the power of the jet" msgstr "Sinken\\Leistung des Triebwerks drosseln" +msgid "Description:" +msgstr "" + msgid "Destroy" msgstr "Zerstören" @@ -486,6 +501,9 @@ msgstr "Einstampfer" msgid "Device\\Driver and resolution settings" msgstr "Bildschirm\\Driver und Bildschirmauflösung" +msgid "Disable\\Disable the selected mod" +msgstr "" + msgid "Dividing by zero" msgstr "Division durch Null" @@ -502,6 +520,9 @@ msgstr "Die Türen werden von einem Gegenstand blockiert" msgid "Down (\\key gdown;)" msgstr "Sinkt (\\key gdown;)" +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Drawer bot" msgstr "Zeichner" @@ -529,6 +550,9 @@ msgstr "Ei" msgid "Empty character constant" msgstr "" +msgid "Enable\\Enable the selected mod" +msgstr "" + msgid "End of block missing" msgstr "Es fehlt eine geschlossene geschweifte Klammer \"}\" (Ende des Blocks)" @@ -756,6 +780,9 @@ msgstr "Von Virus infiziert, zeitweise außer Betrieb" msgid "Information exchange post" msgstr "Infoserver" +msgid "Information:" +msgstr "" + msgid "Instruction \"break\" outside a loop" msgstr "Anweisung \"break\" außerhalb einer Schleife" @@ -920,6 +947,15 @@ msgstr "Liste der Missionen des Planeten:" msgid "Missions\\Select mission" msgstr "Missionen\\Aufbruch ins Weltall" +msgid "Mods" +msgstr "" + +msgid "Mods:" +msgstr "" + +msgid "Mods\\Mod manager" +msgstr "" + msgid "Mouse inversion X\\Inversion of the scrolling direction on the X axis" msgstr "Umkehr X\\Umkehr der Kameradrehung X-Achse" @@ -968,6 +1004,12 @@ msgstr "Nächstes auswählen\\Nächstes Objekt auswählen" msgid "No" msgstr "Nein" +msgid "No changes." +msgstr "" + +msgid "No description." +msgstr "" + msgid "No energy in the subsoil" msgstr "Kein unterirdisches Energievorkommen" @@ -1088,6 +1130,9 @@ msgstr "Öffnen" msgid "Open (Ctrl+O)" msgstr "Öffnen (Ctrl+O)" +msgid "Open Directory\\Open the mods directory" +msgstr "" + msgid "Opening brace missing" msgstr "Es fehlt eine offene geschweifte Klammer\"{\"" @@ -1299,6 +1344,9 @@ msgstr "Rote Fahne" msgid "Reflections on the buttons \\Shiny buttons" msgstr "Glänzende Tasten\\Glänzende Tasten in den Menüs" +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "" + msgid "Remains of Apollo mission" msgstr "Überreste einer Apollo-Mission" @@ -1560,6 +1608,10 @@ msgstr "Texturfilterung\\Texturfilterung" msgid "Textures" msgstr "Texturen" +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "" + msgid "The battle has ended" msgstr "" @@ -1572,9 +1624,16 @@ msgstr "Die Funktion hat kein Ergebnis zurückgegeben" msgid "The mission is not accomplished yet (press \\key help; for more details)" msgstr "Mission noch nicht beendet (Drücken Sie auf \\key help; für weitere Informationen)" +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "" + msgid "The types of the two operands are incompatible" msgstr "Die zwei Operanden sind nicht kompatibel" +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "" + msgid "This class already exists" msgstr "Diese Klasse gibt es schon" @@ -1699,6 +1758,9 @@ msgstr "Einheit" msgid "Unknown Object" msgstr "Das Objekt existiert nicht" +msgid "Unknown author" +msgstr "" + msgid "Unknown command" msgstr "Befehl unbekannt" @@ -1711,6 +1773,9 @@ msgstr "Unbekannte Funktion" msgid "Up (\\key gup;)" msgstr "Steigt (\\key gup;)" +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Uranium deposit (site for derrick)" msgstr "Markierung für unterirdisches Platinvorkommen" @@ -1732,6 +1797,9 @@ msgstr "Der Wert dieser Variable wurde nicht definiert" msgid "Vault" msgstr "Bunker" +msgid "Version" +msgstr "" + msgid "Vertical Synchronization\\Limits the number of frames per second to display frequency" msgstr "" @@ -1750,6 +1818,9 @@ msgstr "Wespe tödlich verwundet" msgid "Waste" msgstr "Abfall" +msgid "Website" +msgstr "" + msgid "Wheeled builder" msgstr "" @@ -1783,6 +1854,9 @@ msgstr "Schnüffler" msgid "Withdraw shield (\\key action;)" msgstr "Schutzschild einholen (\\key action;)" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "" + msgid "Worm" msgstr "Wurm" @@ -1931,6 +2005,9 @@ msgstr "\\Violette Fahne" msgid "\\Yellow flags" msgstr "\\Gelbe Fahne" +msgid "by" +msgstr "" + msgid "colobot.info" msgstr "colobot.info" diff --git a/po/fr.po b/po/fr.po index 2bfb0c04..551267e5 100644 --- a/po/fr.po +++ b/po/fr.po @@ -125,6 +125,9 @@ msgstr "Aspect\\Choisir votre aspect" msgid "Apply changes\\Activates the changed settings" msgstr "Appliquer les changements\\Active les changements effectués" +msgid "Apply\\Apply the current mod configuration" +msgstr "" + msgid "Appropriate constructor missing" msgstr "Constructeur approprié manquant" @@ -374,6 +377,9 @@ msgstr "Changement de caméra\\Autre de point de vue" msgid "Change player\\Change player" msgstr "Autre joueur\\Choix du nom du joueur" +msgid "Changes" +msgstr "" + msgid "Chapters:" msgstr "Liste des chapitres :" @@ -443,6 +449,12 @@ msgstr "Copier" msgid "Copy (Ctrl+C)" msgstr "Copier (Ctrl+C)" +msgid "Could not open the file explorer!" +msgstr "" + +msgid "Could not open the web browser!" +msgstr "" + msgid "Current mission saved" msgstr "Enregistrement effectué" @@ -476,6 +488,9 @@ msgstr "Derrick" msgid "Descend\\Reduces the power of the jet" msgstr "Descendre\\Diminuer la puissance du réacteur" +msgid "Description:" +msgstr "" + msgid "Destroy" msgstr "Détruire" @@ -488,6 +503,9 @@ msgstr "Destructeur" msgid "Device\\Driver and resolution settings" msgstr "Affichage\\Pilote et résolution d'affichage" +msgid "Disable\\Disable the selected mod" +msgstr "" + msgid "Dividing by zero" msgstr "Division par zéro" @@ -504,6 +522,9 @@ msgstr "Portes bloquées par un robot ou un objet" msgid "Down (\\key gdown;)" msgstr "Descend (\\key gdown;)" +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Drawer bot" msgstr "Robot dessinateur" @@ -531,6 +552,9 @@ msgstr "Oeuf" msgid "Empty character constant" msgstr "" +msgid "Enable\\Enable the selected mod" +msgstr "" + msgid "End of block missing" msgstr "Il manque la fin du bloc" @@ -758,6 +782,9 @@ msgstr "Infecté par un virus; ne fonctionne plus temporairement" msgid "Information exchange post" msgstr "Station relais" +msgid "Information:" +msgstr "" + msgid "Instruction \"break\" outside a loop" msgstr "Instruction \"break\" en dehors d'une boucle" @@ -922,6 +949,15 @@ msgstr "Liste des missions du chapitre :" msgid "Missions\\Select mission" msgstr "Missions\\La grande aventure" +msgid "Mods" +msgstr "" + +msgid "Mods:" +msgstr "" + +msgid "Mods\\Mod manager" +msgstr "" + msgid "Mouse inversion X\\Inversion of the scrolling direction on the X axis" msgstr "Inversion souris X\\Inversion de la rotation lorsque la souris touche un bord" @@ -970,6 +1006,12 @@ msgstr "Sélectionner l'objet suivant\\Sélectionner l'objet suivant" msgid "No" msgstr "Non" +msgid "No changes." +msgstr "" + +msgid "No description." +msgstr "" + msgid "No energy in the subsoil" msgstr "Pas d'énergie en sous-sol" @@ -1090,6 +1132,9 @@ msgstr "Ouvrir" msgid "Open (Ctrl+O)" msgstr "Ouvrir (Ctrl+O)" +msgid "Open Directory\\Open the mods directory" +msgstr "" + msgid "Opening brace missing" msgstr "Début d'un bloc attendu" @@ -1301,6 +1346,9 @@ msgstr "Drapeau rouge" msgid "Reflections on the buttons \\Shiny buttons" msgstr "Reflets sur les boutons\\Boutons brillants" +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "" + msgid "Remains of Apollo mission" msgstr "Vestige d'une mission Apollo" @@ -1562,6 +1610,10 @@ msgstr "Filtrage de textures\\Filtrage de textures" msgid "Textures" msgstr "Textures" +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "" + msgid "The battle has ended" msgstr "La bataille est terminée" @@ -1574,9 +1626,16 @@ msgstr "La fonction n'a pas retourné de résultat" msgid "The mission is not accomplished yet (press \\key help; for more details)" msgstr "La mission n'est pas terminée (appuyez sur \\key help; pour plus de détails)" +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "" + msgid "The types of the two operands are incompatible" msgstr "Les deux opérandes ne sont pas de types compatibles" +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "" + msgid "This class already exists" msgstr "Cette classe existe déjà" @@ -1701,6 +1760,9 @@ msgstr "Unité" msgid "Unknown Object" msgstr "Objet inconnu" +msgid "Unknown author" +msgstr "" + msgid "Unknown command" msgstr "Commande inconnue" @@ -1713,6 +1775,9 @@ msgstr "Routine inconnue" msgid "Up (\\key gup;)" msgstr "Monte (\\key gup;)" +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Uranium deposit (site for derrick)" msgstr "Emplacement pour un derrick (minerai d'uranium)" @@ -1734,6 +1799,9 @@ msgstr "Variable non initialisée" msgid "Vault" msgstr "Coffre-fort" +msgid "Version" +msgstr "" + msgid "Vertical Synchronization\\Limits the number of frames per second to display frequency" msgstr "Synchronisation verticale :\\Réduit la fréquence d'images par seconde à afficher." @@ -1752,6 +1820,9 @@ msgstr "Guêpe mortellement touchée" msgid "Waste" msgstr "Déchet" +msgid "Website" +msgstr "" + msgid "Wheeled builder" msgstr "" @@ -1785,6 +1856,9 @@ msgstr "Robot renifleur volant" msgid "Withdraw shield (\\key action;)" msgstr "Refermer le bouclier (\\key action;)" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "" + msgid "Worm" msgstr "Ver" @@ -1933,6 +2007,9 @@ msgstr "\\Drapeaux violets" msgid "\\Yellow flags" msgstr "\\Drapeaux jaunes" +msgid "by" +msgstr "" + msgid "colobot.info" msgstr "colobot.info" diff --git a/po/pl.po b/po/pl.po index 3cef57ef..0db014ed 100644 --- a/po/pl.po +++ b/po/pl.po @@ -124,6 +124,9 @@ msgstr "Wygląd\\Wybierz swoją postać" msgid "Apply changes\\Activates the changed settings" msgstr "Zastosuj zmiany\\Aktywuje zmienione ustawienia" +msgid "Apply\\Apply the current mod configuration" +msgstr "Zastosuj\\Zastosuj obecną konfigurację modów" + msgid "Appropriate constructor missing" msgstr "Brak odpowiedniego konstruktora" @@ -370,6 +373,9 @@ msgstr "Zmień kamerę\\Przełącza pomiędzy kamerą pokładową i śledzącą" msgid "Change player\\Change player" msgstr "Zmień gracza\\Zmień gracza" +msgid "Changes" +msgstr "Zmiany" + msgid "Chapters:" msgstr "Rozdziały:" @@ -439,6 +445,12 @@ msgstr "Kopiuj" msgid "Copy (Ctrl+C)" msgstr "Kopiuj (Ctrl+C)" +msgid "Could not open the file explorer!" +msgstr "Nie udało się otworzyć przeglądarki plików!" + +msgid "Could not open the web browser!" +msgstr "Nie udało się otworzyć przeglądarki internetowej!" + msgid "Current mission saved" msgstr "Bieżąca misja zapisana" @@ -472,6 +484,9 @@ msgstr "Kopalnia" msgid "Descend\\Reduces the power of the jet" msgstr "W dół\\Zmniejsza moc silnika" +msgid "Description:" +msgstr "Opis:" + msgid "Destroy" msgstr "Zniszcz" @@ -484,6 +499,9 @@ msgstr "Destroyer" msgid "Device\\Driver and resolution settings" msgstr "Urządzenie\\Ustawienia sterownika i rozdzielczości" +msgid "Disable\\Disable the selected mod" +msgstr "Wyłącz\\Wyłącza zaznaczonego moda" + msgid "Dividing by zero" msgstr "Dzielenie przez zero" @@ -500,6 +518,9 @@ msgstr "Drzwi zablokowane przez robota lub inny obiekt" msgid "Down (\\key gdown;)" msgstr "Dół (\\key gdown;)" +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "W dół\\Przenieś zaznaczonego moda w dół, aby był załadowany później (mody mogą nadpisywać pliki modów wyżej)" + msgid "Drawer bot" msgstr "Robot rysownik" @@ -527,6 +548,9 @@ msgstr "Jajo" msgid "Empty character constant" msgstr "" +msgid "Enable\\Enable the selected mod" +msgstr "Włącz\\Włącza zaznaczonego moda" + msgid "End of block missing" msgstr "Brak końca bloku" @@ -753,6 +777,9 @@ msgstr "Zainfekowane wirusem, chwilowo niesprawne" msgid "Information exchange post" msgstr "Stacja przekaźnikowa informacji" +msgid "Information:" +msgstr "Informacje:" + msgid "Instruction \"break\" outside a loop" msgstr "Polecenie \"break\" na zewnątrz pętli" @@ -903,6 +930,15 @@ msgstr "Misje na tej planecie:" msgid "Missions\\Select mission" msgstr "Misje\\Wybierz misję" +msgid "Mods" +msgstr "Mody" + +msgid "Mods:" +msgstr "Mody:" + +msgid "Mods\\Mod manager" +msgstr "Mody\\Zarządzanie modami" + msgid "Mouse inversion X\\Inversion of the scrolling direction on the X axis" msgstr "Odwrócenie myszy X\\Odwrócenie kierunków przewijania w poziomie" @@ -951,6 +987,12 @@ msgstr "Następny obiekt\\Zaznacza następny obiekt" msgid "No" msgstr "Nie" +msgid "No changes." +msgstr "Brak zmian." + +msgid "No description." +msgstr "Brak opisu." + msgid "No energy in the subsoil" msgstr "Brak energii w ziemi" @@ -1071,6 +1113,9 @@ msgstr "Otwórz" msgid "Open (Ctrl+O)" msgstr "Otwórz (Ctrl+O)" +msgid "Open Directory\\Open the mods directory" +msgstr "Otwórz katalog\\Otwórz katalog z modami" + msgid "Opening brace missing" msgstr "Brak klamry otwierającej" @@ -1281,6 +1326,9 @@ msgstr "Czerwona flaga" msgid "Reflections on the buttons \\Shiny buttons" msgstr "Odbicia na przyciskach \\Świecące przyciski" +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "Odśwież\\Odśwież listę obecnie zainstalowanych modów" + msgid "Remains of Apollo mission" msgstr "Pozostałości z misji Apollo" @@ -1542,6 +1590,10 @@ msgstr "Filtrowanie tekstur\\Filtrowanie tekstur" msgid "Textures" msgstr "Tekstury" +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "Nie udało się otworzyć adresu %s w przeglądarce internetowej." + msgid "The battle has ended" msgstr "Bitwa zakończyła się" @@ -1554,9 +1606,16 @@ msgstr "Funkcja nie zwróciła żadnej wartości" msgid "The mission is not accomplished yet (press \\key help; for more details)" msgstr "Misja nie jest wypełniona (naciśnij \\key help; aby uzyskać szczegóły)" +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "Nie udało się otworzyć ścieżki %s w przeglądarce plików." + msgid "The types of the two operands are incompatible" msgstr "Niezgodne typy operatorów" +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "Są niezapisane zmiany. Czy chcesz je zapisać przed wyjściem?" + msgid "This class already exists" msgstr "Taka klasa już istnieje" @@ -1681,6 +1740,9 @@ msgstr "Jednostka" msgid "Unknown Object" msgstr "Obiekt nieznany" +msgid "Unknown author" +msgstr "Nieznany autor" + msgid "Unknown command" msgstr "Nieznane polecenie" @@ -1693,6 +1755,9 @@ msgstr "Funkcja nieznana" msgid "Up (\\key gup;)" msgstr "Góra (\\key gup;)" +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "W górę\\Przenieś zaznaczonego moda w górę, aby był załadowany wcześniej (mody mogą nadpisywać pliki modów wyżej)" + msgid "Uranium deposit (site for derrick)" msgstr "Złoże uranu (miejsce na kopalnię)" @@ -1714,6 +1779,9 @@ msgstr "Zmienna nie została zainicjalizowana" msgid "Vault" msgstr "Skrytka" +msgid "Version" +msgstr "Wersja" + msgid "Vertical Synchronization\\Limits the number of frames per second to display frequency" msgstr "Synchronizacja pionowa\\Ogranicza ilość klatek na sekundę do wartości odświeżania ekranu" @@ -1732,6 +1800,9 @@ msgstr "Osa śmiertelnie raniona" msgid "Waste" msgstr "Odpady" +msgid "Website" +msgstr "Strona internetowa" + msgid "Wheeled builder" msgstr "" @@ -1765,6 +1836,9 @@ msgstr "Szperacz latający" msgid "Withdraw shield (\\key action;)" msgstr "Wyłącz osłonę (\\key action;)" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "Warsztat\\Otwórz warsztat, aby poszukać modów" + msgid "Worm" msgstr "Robal" @@ -1915,6 +1989,9 @@ msgstr "\\Fioletowe flagi" msgid "\\Yellow flags" msgstr "\\Żółte flagi" +msgid "by" +msgstr "autorstwa" + msgid "colobot.info" msgstr "colobot.info" diff --git a/po/pt.po b/po/pt.po index 9baba2a9..e1f8dc99 100644 --- a/po/pt.po +++ b/po/pt.po @@ -122,6 +122,9 @@ msgstr "Aparência\\Escolha sua aparência" msgid "Apply changes\\Activates the changed settings" msgstr "Aplicar mudanças\\Ativa as configurações alteradas" +msgid "Apply\\Apply the current mod configuration" +msgstr "" + msgid "Appropriate constructor missing" msgstr "Construtor apropriado faltando" @@ -368,6 +371,9 @@ msgstr "Mudar câmera\\Alterna entre câmera incorporada e câmera seguidora" msgid "Change player\\Change player" msgstr "Mudar jogador\\Mudar jogador" +msgid "Changes" +msgstr "" + msgid "Chapters:" msgstr "Capítulos:" @@ -438,6 +444,12 @@ msgstr "Copiar" msgid "Copy (Ctrl+C)" msgstr "Copiar (Ctrl+C)" +msgid "Could not open the file explorer!" +msgstr "" + +msgid "Could not open the web browser!" +msgstr "" + msgid "Current mission saved" msgstr "Missão atual salva" @@ -471,6 +483,9 @@ msgstr "Extrator" msgid "Descend\\Reduces the power of the jet" msgstr "Descer\\Diminui o poder do jato" +msgid "Description:" +msgstr "" + msgid "Destroy" msgstr "Destruir" @@ -483,6 +498,9 @@ msgstr "Destruidor" msgid "Device\\Driver and resolution settings" msgstr "Dispositivo\\Configurações de driver e resolução" +msgid "Disable\\Disable the selected mod" +msgstr "" + msgid "Dividing by zero" msgstr "Dividindo por zero" @@ -499,6 +517,9 @@ msgstr "Portas bloqueadas por um robô ou outro objeto" msgid "Down (\\key gdown;)" msgstr "Baixo (\\key gdown;)" +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Drawer bot" msgstr "Robô cartoonista" @@ -526,6 +547,9 @@ msgstr "Ovo" msgid "Empty character constant" msgstr "" +msgid "Enable\\Enable the selected mod" +msgstr "" + msgid "End of block missing" msgstr "Fim do bloco ausente" @@ -753,6 +777,9 @@ msgstr "Infectado por vírus; temporariamento fora de serviço" msgid "Information exchange post" msgstr "Posto de troca de informação" +msgid "Information:" +msgstr "" + msgid "Instruction \"break\" outside a loop" msgstr "Intrução \"break\" fora de um laço" @@ -917,6 +944,15 @@ msgstr "Lista de missões neste planeta:" msgid "Missions\\Select mission" msgstr "Missões\\Selecione uma missão" +msgid "Mods" +msgstr "" + +msgid "Mods:" +msgstr "" + +msgid "Mods\\Mod manager" +msgstr "" + msgid "Mouse inversion X\\Inversion of the scrolling direction on the X axis" msgstr "Inversão de mouse X\\Inverte a direção da rolagem no eixo X" @@ -965,6 +1001,12 @@ msgstr "Próximo objeto\\Selecionar o próximo objeto" msgid "No" msgstr "Não" +msgid "No changes." +msgstr "" + +msgid "No description." +msgstr "" + msgid "No energy in the subsoil" msgstr "Nenhuma energia no subsolo" @@ -1085,6 +1127,9 @@ msgstr "Abrir" msgid "Open (Ctrl+O)" msgstr "Abrir (Ctrl+O)" +msgid "Open Directory\\Open the mods directory" +msgstr "" + msgid "Opening brace missing" msgstr "Chave de abertura ausente" @@ -1296,6 +1341,9 @@ msgstr "Bandeira vermelha" msgid "Reflections on the buttons \\Shiny buttons" msgstr "Reflexões nos botões\\Botões brilhantes" +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "" + msgid "Remains of Apollo mission" msgstr "Restos da missão Apollo" @@ -1557,6 +1605,10 @@ msgstr "Filtragem de textura\\Filtragem de textura" msgid "Textures" msgstr "Texturas" +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "" + msgid "The battle has ended" msgstr "A batalha acabou" @@ -1569,9 +1621,16 @@ msgstr "A função não retornou nenhum valor" msgid "The mission is not accomplished yet (press \\key help; for more details)" msgstr "A missão não foi completada ainda (pressione \\key help; para mais detalhes)" +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "" + msgid "The types of the two operands are incompatible" msgstr "Os tipos dos dois operandos são incompativeis" +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "" + msgid "This class already exists" msgstr "Esta classe já existe" @@ -1696,6 +1755,9 @@ msgstr "Unidade" msgid "Unknown Object" msgstr "Objeto desconhecido" +msgid "Unknown author" +msgstr "" + msgid "Unknown command" msgstr "Comando desconhecido" @@ -1708,6 +1770,9 @@ msgstr "Função desconhecida" msgid "Up (\\key gup;)" msgstr "Cima (\\key gup;)" +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Uranium deposit (site for derrick)" msgstr "Depósito de urânio (local para extrator)" @@ -1729,6 +1794,9 @@ msgstr "Variável não inicializada" msgid "Vault" msgstr "Cofre" +msgid "Version" +msgstr "" + msgid "Vertical Synchronization\\Limits the number of frames per second to display frequency" msgstr "" @@ -1747,6 +1815,9 @@ msgstr "Vespa fatalmente ferida" msgid "Waste" msgstr "Desperdício" +msgid "Website" +msgstr "" + msgid "Wheeled builder" msgstr "" @@ -1780,6 +1851,9 @@ msgstr "Farejador alado" msgid "Withdraw shield (\\key action;)" msgstr "Retirar escudo (\\key action;)" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "" + msgid "Worm" msgstr "Verme" @@ -1928,6 +2002,9 @@ msgstr "\\Bandeiras violetas" msgid "\\Yellow flags" msgstr "\\Bandeiras amarelas" +msgid "by" +msgstr "" + msgid "colobot.info" msgstr "colobot.info" diff --git a/po/ru.po b/po/ru.po index 4e3f371b..974d6ab1 100644 --- a/po/ru.po +++ b/po/ru.po @@ -124,6 +124,9 @@ msgstr "Внешность\\Настройка внешности" msgid "Apply changes\\Activates the changed settings" msgstr "Принять\\Принять изменения настроек" +msgid "Apply\\Apply the current mod configuration" +msgstr "" + msgid "Appropriate constructor missing" msgstr "Соответствующий конструктор отсутствует" @@ -375,6 +378,9 @@ msgstr "Изменить вид\\Переключение между борто msgid "Change player\\Change player" msgstr "Новый игрок\\Выберите имя для игрока" +msgid "Changes" +msgstr "" + msgid "Chapters:" msgstr "Разделы:" @@ -446,6 +452,12 @@ msgstr "Копировать" msgid "Copy (Ctrl+C)" msgstr "Копировать (Ctrl+C)" +msgid "Could not open the file explorer!" +msgstr "" + +msgid "Could not open the web browser!" +msgstr "" + msgid "Current mission saved" msgstr "Текущая миссия сохранена" @@ -480,6 +492,9 @@ msgstr "Космический корабль" msgid "Descend\\Reduces the power of the jet" msgstr "Снижение и посадка\\Понижение мощности реактивного двигателя" +msgid "Description:" +msgstr "" + msgid "Destroy" msgstr "Уничтожить" @@ -492,6 +507,9 @@ msgstr "Уничтожитель" msgid "Device\\Driver and resolution settings" msgstr "Устройство\\Драйвер и настройки разрешения" +msgid "Disable\\Disable the selected mod" +msgstr "" + msgid "Dividing by zero" msgstr "Деление на ноль (запрещено!)" @@ -508,6 +526,9 @@ msgstr "Двери заблокированы роботом или другим msgid "Down (\\key gdown;)" msgstr "Вниз (\\key gdown;)" +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Drawer bot" msgstr "Рисовальщик" @@ -535,6 +556,9 @@ msgstr "Яйцо" msgid "Empty character constant" msgstr "" +msgid "Enable\\Enable the selected mod" +msgstr "" + msgid "End of block missing" msgstr "Отсутствует конец блока" @@ -762,6 +786,9 @@ msgstr "Заражено вирусом. Временно вышел из стр msgid "Information exchange post" msgstr "Пост обмена информацией" +msgid "Information:" +msgstr "" + msgid "Instruction \"break\" outside a loop" msgstr "Инструкция \"break\" вне цикла" @@ -926,6 +953,15 @@ msgstr "Миссии на этой планете:" msgid "Missions\\Select mission" msgstr "Миссии\\Выбор миссии" +msgid "Mods" +msgstr "" + +msgid "Mods:" +msgstr "" + +msgid "Mods\\Mod manager" +msgstr "" + msgid "Mouse inversion X\\Inversion of the scrolling direction on the X axis" msgstr "Инверсия мыши по оси X\\Инверсия прокрутки по оси Х" @@ -976,6 +1012,12 @@ msgstr "Следующий объект\\Выбор следующего объ msgid "No" msgstr "Нет" +msgid "No changes." +msgstr "" + +msgid "No description." +msgstr "" + msgid "No energy in the subsoil" msgstr "Под землей нет запасов энергии" @@ -1096,6 +1138,9 @@ msgstr "Открыть" msgid "Open (Ctrl+O)" msgstr "Открыть (Ctrl+O)" +msgid "Open Directory\\Open the mods directory" +msgstr "" + msgid "Opening brace missing" msgstr "Открывающая скобка отсутствует" @@ -1308,6 +1353,9 @@ msgstr "Красный флаг" msgid "Reflections on the buttons \\Shiny buttons" msgstr "Отражения на кнопках \\Блестящие кнопки" +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "" + msgid "Remains of Apollo mission" msgstr "Остатки миссии Аполлон" @@ -1573,6 +1621,10 @@ msgstr "Фильтрация текстур\\Фильтрация текстур msgid "Textures" msgstr "Текстуры" +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "" + msgid "The battle has ended" msgstr "" @@ -1585,9 +1637,16 @@ msgstr "Функция не возвратила значения" msgid "The mission is not accomplished yet (press \\key help; for more details)" msgstr "Миссия еще не выполнена (нажмите \\key help; для более подробной информации)" +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "" + msgid "The types of the two operands are incompatible" msgstr "Типы операндов несовместимы" +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "" + msgid "This class already exists" msgstr "Этот класс уже существует" @@ -1712,6 +1771,9 @@ msgstr "Юнит" msgid "Unknown Object" msgstr "Неизвестный объект" +msgid "Unknown author" +msgstr "" + msgid "Unknown command" msgstr "Неизвестная команда" @@ -1724,6 +1786,9 @@ msgstr "Неизвестная функция" msgid "Up (\\key gup;)" msgstr "Вверх (\\key gup;)" +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Uranium deposit (site for derrick)" msgstr "Запасы урана (место для буровой вышки)" @@ -1745,6 +1810,9 @@ msgstr "Переменная не инициализирована" msgid "Vault" msgstr "Хранилище" +msgid "Version" +msgstr "" + msgid "Vertical Synchronization\\Limits the number of frames per second to display frequency" msgstr "" @@ -1763,6 +1831,9 @@ msgstr "Оса смертельно ранена" msgid "Waste" msgstr "Мусор" +msgid "Website" +msgstr "" + msgid "Wheeled builder" msgstr "" @@ -1796,6 +1867,9 @@ msgstr "Летающий искатель" msgid "Withdraw shield (\\key action;)" msgstr "Снять щит (\\key action;)" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "" + msgid "Worm" msgstr "Червь" @@ -1944,6 +2018,9 @@ msgstr "\\Фиолетовый флаг" msgid "\\Yellow flags" msgstr "\\Желтый флаг" +msgid "by" +msgstr "" + msgid "colobot.info" msgstr "colobot.info" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b0dc5dd8..8dc9af86 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -145,6 +145,8 @@ set(BASE_SOURCES app/controller.h app/input.cpp app/input.h + app/modman.cpp + app/modman.h app/pathman.cpp app/pathman.h app/pausemanager.cpp @@ -568,6 +570,8 @@ set(BASE_SOURCES ui/screen/screen_loading.h ui/screen/screen_main_menu.cpp ui/screen/screen_main_menu.h + ui/screen/screen_mod_list.cpp + ui/screen/screen_mod_list.h ui/screen/screen_player_select.cpp ui/screen/screen_player_select.h ui/screen/screen_quit.cpp diff --git a/src/app/app.cpp b/src/app/app.cpp index f998133c..ad5d02cb 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -21,6 +21,7 @@ #include "app/controller.h" #include "app/input.h" +#include "app/modman.h" #include "app/pathman.h" #include "common/config_file.h" @@ -113,7 +114,8 @@ CApplication::CApplication(CSystemUtils* systemUtils) m_private(MakeUnique()), m_configFile(MakeUnique()), m_input(MakeUnique()), - m_pathManager(MakeUnique(systemUtils)) + m_pathManager(MakeUnique(systemUtils)), + m_modManager(MakeUnique(this, m_pathManager.get())) { m_exitCode = 0; m_active = false; @@ -220,6 +222,11 @@ CSoundInterface* CApplication::GetSound() return m_sound.get(); } +CModManager* CApplication::GetModManager() +{ + return m_modManager.get(); +} + void CApplication::LoadEnvironmentVariables() { auto dataDir = m_systemUtils->GetEnvVar("COLOBOT_DATA_DIR"); @@ -513,6 +520,10 @@ bool CApplication::Create() GetLogger()->Warn("Config could not be loaded. Default values will be used!\n"); } + m_modManager->FindMods(); + m_modManager->SaveMods(); + m_modManager->MountAllMods(); + // Create the sound instance. #ifdef OPENAL_SOUND if (!m_headless) @@ -698,21 +709,7 @@ bool CApplication::Create() // Create the robot application. m_controller = MakeUnique(); - CThread musicLoadThread([this]() - { - GetLogger()->Debug("Cache sounds...\n"); - SystemTimeStamp* musicLoadStart = m_systemUtils->CreateTimeStamp(); - m_systemUtils->GetCurrentTimeStamp(musicLoadStart); - - m_sound->CacheAll(); - - SystemTimeStamp* musicLoadEnd = m_systemUtils->CreateTimeStamp(); - m_systemUtils->GetCurrentTimeStamp(musicLoadEnd); - float musicLoadTime = m_systemUtils->TimeStampDiff(musicLoadStart, musicLoadEnd, STU_MSEC); - GetLogger()->Debug("Sound loading took %.2f ms\n", musicLoadTime); - }, - "Sound loading thread"); - musicLoadThread.Start(); + StartLoadingMusic(); if (m_runSceneCategory == LevelCategory::Max) m_controller->StartApp(); @@ -726,6 +723,15 @@ bool CApplication::Create() return true; } +void CApplication::ReloadResources() +{ + GetLogger()->Info("Reloading resources\n"); + m_engine->ReloadAllTextures(); + StartLoadingMusic(); + m_controller->GetRobotMain()->UpdateCustomLevelList(); +} + + bool CApplication::CreateVideoSurface() { Uint32 videoFlags = SDL_WINDOW_OPENGL; @@ -1537,6 +1543,26 @@ void CApplication::InternalResumeSimulation() m_absTimeBase = m_exactAbsTime; } +void CApplication::StartLoadingMusic() +{ + CThread musicLoadThread([this]() + { + GetLogger()->Debug("Cache sounds...\n"); + SystemTimeStamp* musicLoadStart = m_systemUtils->CreateTimeStamp(); + m_systemUtils->GetCurrentTimeStamp(musicLoadStart); + + m_sound->Reset(); + m_sound->CacheAll(); + + SystemTimeStamp* musicLoadEnd = m_systemUtils->CreateTimeStamp(); + m_systemUtils->GetCurrentTimeStamp(musicLoadEnd); + float musicLoadTime = m_systemUtils->TimeStampDiff(musicLoadStart, musicLoadEnd, STU_MSEC); + GetLogger()->Debug("Sound loading took %.2f ms\n", musicLoadTime); + }, + "Sound loading thread"); + musicLoadThread.Start(); +} + bool CApplication::GetSimulationSuspended() const { return m_simulationSuspended; diff --git a/src/app/app.h b/src/app/app.h index c55eca38..af385cf2 100644 --- a/src/app/app.h +++ b/src/app/app.h @@ -42,6 +42,7 @@ class CEventQueue; class CController; class CSoundInterface; class CInput; +class CModManager; class CPathManager; class CConfigFile; class CSystemUtils; @@ -162,6 +163,8 @@ public: CEventQueue* GetEventQueue(); //! Returns the sound subsystem CSoundInterface* GetSound(); + //! Returns the mod manager + CModManager* GetModManager(); public: //! Loads some data from environment variables @@ -170,6 +173,8 @@ public: ParseArgsStatus ParseArguments(int argc, char *argv[]); //! Initializes the application bool Create(); + //! Reloads the application resources, e.g. mods + void ReloadResources(); //! Main event loop int Run(); //! Returns the code to be returned at main() exit @@ -301,6 +306,9 @@ protected: //! Internal procedure to reset time counters void InternalResumeSimulation(); + //! Loads music in a new thread + void StartLoadingMusic(); + protected: //! System utils instance CSystemUtils* m_systemUtils; @@ -322,6 +330,8 @@ protected: std::unique_ptr m_input; //! Path manager std::unique_ptr m_pathManager; + //! Mod manager + std::unique_ptr m_modManager; //! Code to return at exit int m_exitCode; diff --git a/src/app/modman.cpp b/src/app/modman.cpp new file mode 100644 index 00000000..42ba11be --- /dev/null +++ b/src/app/modman.cpp @@ -0,0 +1,358 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#include "app/modman.h" + +#include "common/config.h" + +#include "app/app.h" +#include "app/pathman.h" + +#include "common/config_file.h" +#include "common/logger.h" + +#include "common/resources/resourcemanager.h" + +#include "level/parser/parser.h" + +#include +#include +#include +#include + +using namespace boost::filesystem; + +CModManager::CModManager(CApplication* app, CPathManager* pathManager) + : m_app{app}, + m_pathManager{pathManager} +{ +} + +void CModManager::FindMods() +{ + m_mods.clear(); + m_userChanges = false; + + // Load names from the config file + std::vector savedModNames; + GetConfigFile().GetArrayProperty("Mods", "Names", savedModNames); + std::vector savedEnabled; + GetConfigFile().GetArrayProperty("Mods", "Enabled", savedEnabled); + + // Transform the data into Mod structures + m_mods.reserve(savedModNames.size()); + for (size_t i = 0; i < savedModNames.size(); ++i) + { + Mod mod{}; + mod.name = savedModNames[i]; + if (i < savedEnabled.size()) + { + mod.enabled = savedEnabled[i]; + } + mod.path = ""; // Find the path later + m_mods.push_back(mod); + } + + // Search the folders for mods + auto rawPaths = m_pathManager->FindMods(); + std::map modPaths; + for (const auto& path : rawPaths) + { + auto modName = boost::filesystem::path(path).stem().string(); + modPaths.insert(std::make_pair(modName, path)); + } + + // Find paths for already saved mods + auto it = m_mods.begin(); + while (it != m_mods.end()) + { + auto& mod = *it; + const auto pathsIt = modPaths.find(mod.name); + if (pathsIt != modPaths.end()) + { + mod.path = (*pathsIt).second; + modPaths.erase(pathsIt); + ++it; + } + else + { + GetLogger()->Warn("Could not find mod %s, removing it from the list\n", mod.name.c_str()); + it = m_mods.erase(it); + } + } + + // Add the remaining found mods to the end of the list + for (const auto& newMod : modPaths) + { + Mod mod{}; + mod.name = newMod.first; + mod.path = newMod.second; + m_mods.push_back(mod); + } + + // Load the metadata for each mod + + // Unfortunately, the paths are distinguished by their real paths, not mount points + // So we must unmount mods temporarily + for (const auto& path : m_mountedModPaths) + { + UnmountMod(path); + } + + for (auto& mod : m_mods) + { + MountMod(mod, "/temp/mod"); + LoadModData(mod); + UnmountMod(mod); + } + + // Mount back + for (const auto& path : m_mountedModPaths) + { + MountMod(path); + } +} + +void CModManager::ReloadMods() +{ + UnmountAllMountedMods(); + MountAllMods(); + ReloadResources(); +} + +void CModManager::EnableMod(size_t i) +{ + m_mods[i].enabled = true; + m_userChanges = true; +} + +void CModManager::DisableMod(size_t i) +{ + m_mods[i].enabled = false; + m_userChanges = true; +} + +size_t CModManager::MoveUp(size_t i) +{ + if (i != 0) + { + std::swap(m_mods[i - 1], m_mods[i]); + m_userChanges = true; + return i - 1; + } + else + { + return i; + } +} + +size_t CModManager::MoveDown(size_t i) +{ + if (i != m_mods.size() - 1) + { + std::swap(m_mods[i], m_mods[i + 1]); + m_userChanges = true; + return i + 1; + } + else + { + return i; + } +} + +bool CModManager::Changes() +{ + std::vector paths; + for (const auto& mod : m_mods) + { + if (mod.enabled) + { + paths.push_back(mod.path); + } + } + return paths != m_mountedModPaths || m_userChanges; +} + +void CModManager::MountAllMods() +{ + for (const auto& mod : m_mods) + { + if (mod.enabled) + { + MountMod(mod); + m_mountedModPaths.push_back(mod.path); + } + } +} + +void CModManager::ReloadResources() +{ + m_app->ReloadResources(); +} + +void CModManager::SaveMods() +{ + std::vector savedNames; + savedNames.reserve(m_mods.size()); + std::transform(m_mods.begin(), m_mods.end(), std::back_inserter(savedNames), [](const Mod& mod) { return mod.name; }); + GetConfigFile().SetArrayProperty("Mods", "Names", savedNames); + + std::vector savedEnabled; + savedEnabled.reserve(m_mods.size()); + std::transform(m_mods.begin(), m_mods.end(), std::back_inserter(savedEnabled), [](const Mod& mod) { return mod.enabled; }); + GetConfigFile().SetArrayProperty("Mods", "Enabled", savedEnabled); + + GetConfigFile().Save(); + + m_userChanges = false; +} + +size_t CModManager::CountMods() const +{ + return m_mods.size(); +} + +const Mod& CModManager::GetMod(size_t i) const +{ + return m_mods[i]; +} + +const std::vector& CModManager::GetMods() const +{ + return m_mods; +} + +void CModManager::LoadModData(Mod& mod) +{ + auto& data = mod.data; + + data.displayName = mod.name; + + try + { + CLevelParser levelParser("temp/mod/manifest.txt"); + if (levelParser.Exists()) + { + levelParser.Load(); + + CLevelParserLine* line = nullptr; + + // DisplayName + line = levelParser.GetIfDefined("DisplayName"); + if (line != nullptr && line->GetParam("text")->IsDefined()) + { + data.displayName = line->GetParam("text")->AsString(); + } + + // Author + line = levelParser.GetIfDefined("Author"); + if (line != nullptr && line->GetParam("text")->IsDefined()) + { + data.author = line->GetParam("text")->AsString(); + } + + // Version + line = levelParser.GetIfDefined("Version"); + if (line != nullptr) + { + if (line->GetParam("text")->IsDefined()) + { + data.version = line->GetParam("text")->AsString(); + } + else if (line->GetParam("major")->IsDefined() && line->GetParam("minor")->IsDefined() && line->GetParam("patch")->IsDefined()) + { + auto major = boost::lexical_cast(line->GetParam("major")->AsInt()); + auto minor = boost::lexical_cast(line->GetParam("minor")->AsInt()); + auto patch = boost::lexical_cast(line->GetParam("patch")->AsInt()); + data.version = boost::algorithm::join(std::vector{ major, minor, patch }, "."); + } + } + + // Website + line = levelParser.GetIfDefined("Website"); + if (line != nullptr && line->GetParam("text")->IsDefined()) + { + data.website = line->GetParam("text")->AsString(); + } + + // Summary + line = levelParser.GetIfDefined("Summary"); + if (line != nullptr && line->GetParam("text")->IsDefined()) + { + data.summary = line->GetParam("text")->AsString(); + } + } + else + { + GetLogger()->Warn("No manifest file for mod %s\n", mod.name.c_str()); + } + } + catch (CLevelParserException& e) + { + GetLogger()->Warn("Failed parsing manifest for mod %s: %s\n", mod.name.c_str(), e.what()); + } + + // Changes + data.changes = CResourceManager::ListDirectories("temp/mod"); + auto levelsIt = std::find(data.changes.begin(), data.changes.end(), "levels"); + if (levelsIt != data.changes.end()) + { + auto levelsDirs = CResourceManager::ListDirectories("temp/mod/levels"); + if (!levelsDirs.empty()) + { + std::transform(levelsDirs.begin(), levelsDirs.end(), levelsDirs.begin(), [](const std::string& dir) { return "levels/" + dir; }); + levelsIt = data.changes.erase(levelsIt); + data.changes.insert(levelsIt, levelsDirs.begin(), levelsDirs.end()); + } + } +} + +void CModManager::MountMod(const Mod& mod, const std::string& mountPoint) +{ + MountMod(mod.path, mountPoint); +} + +void CModManager::MountMod(const std::string& path, const std::string& mountPoint) +{ + GetLogger()->Debug("Mounting mod: '%s' at path %s\n", path.c_str(), mountPoint.c_str()); + CResourceManager::AddLocation(path, true, mountPoint); +} + +void CModManager::UnmountMod(const Mod& mod) +{ + UnmountMod(mod.path); +} + +void CModManager::UnmountMod(const std::string& path) +{ + if (CResourceManager::LocationExists(path)) + { + GetLogger()->Debug("Unmounting mod: '%s'\n", path.c_str()); + CResourceManager::RemoveLocation(path); + } +} + +void CModManager::UnmountAllMountedMods() +{ + for (const auto& path : m_mountedModPaths) + { + UnmountMod(path); + } + m_mountedModPaths.clear(); +} diff --git a/src/app/modman.h b/src/app/modman.h new file mode 100644 index 00000000..09f8b0aa --- /dev/null +++ b/src/app/modman.h @@ -0,0 +1,124 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#pragma once + +#include +#include +#include + +class CApplication; +class CPathManager; + +struct ModData +{ + std::string displayName{}; + std::string author{}; + std::string version{}; + std::string website{}; + std::string summary{}; + std::vector changes{}; +}; + +struct Mod +{ + std::string name{}; + std::string path{}; + bool enabled = false; + ModData data{}; +}; + +/** + * \class CModManager + * \brief This class handles the list of mods. + * + * The order matters since the order in which files are loaded matters, + * because some files can be overwritten. + * + * The changes in the list do not immediately apply. + */ +class CModManager +{ +public: + CModManager(CApplication* app, CPathManager* pathManager); + + //! Finds all the mods along with their metadata + void FindMods(); + + //! Applies the current configuration and reloads the application + void ReloadMods(); + + //! Removes a mod from the list of loaded mods + void EnableMod(size_t i); + + //! Adds a mod to the list of loaded mods + void DisableMod(size_t i); + + //! Moves the selected mod up in the list so that it's loaded sooner than others, returns the new index + size_t MoveUp(size_t i); + + //! Moves the selected mod down in the list so that it's loaded later than others, returns the new index + size_t MoveDown(size_t i); + + //! Checks if the list of currently used mods differs from the current configuration or there were changes made by the user + bool Changes(); + + //! Saves the current configuration of mods to the config file + void SaveMods(); + + //! Number of mods loaded + size_t CountMods() const; + + //! Returns the reference to the mod in given position + const Mod& GetMod(size_t i) const; + + //! Returns the list of mods + const std::vector& GetMods() const; + +private: + // Allow access to MountAllMods() as CApplication doesn't want to reload itself during initialization + friend CApplication; + + //! Reloads application resources so the enabled mods are applied + void ReloadResources(); + + //! Load mod data into mod + void LoadModData(Mod& mod); + + //! Updates the paths in Path Manager according to the current mod configuration + void MountAllMods(); + + void MountMod(const Mod& mod, const std::string& mountPoint = ""); + void MountMod(const std::string& path, const std::string& mountPoint = ""); + void UnmountMod(const Mod& mod); + void UnmountMod(const std::string& path); + void UnmountAllMountedMods(); + +private: + CApplication* m_app; + CPathManager* m_pathManager; + + //! Paths to mods already in the virtual filesystem + std::vector m_mountedModPaths; + + //! List of mods + std::vector m_mods; + + bool m_userChanges = false; +}; diff --git a/src/app/pathman.cpp b/src/app/pathman.cpp index 690f6d34..e4c385dd 100644 --- a/src/app/pathman.cpp +++ b/src/app/pathman.cpp @@ -41,8 +41,7 @@ CPathManager::CPathManager(CSystemUtils* systemUtils) : m_dataPath(systemUtils->GetDataPath()) , m_langPath(systemUtils->GetLangPath()) , m_savePath(systemUtils->GetSaveDir()) - , m_modAutoloadDir{} - , m_mods{} + , m_modSearchDirs{} { } @@ -65,16 +64,6 @@ void CPathManager::SetSavePath(const std::string &savePath) m_savePath = savePath; } -void CPathManager::AddModAutoloadDir(const std::string &modAutoloadDirPath) -{ - m_modAutoloadDir.push_back(modAutoloadDirPath); -} - -void CPathManager::AddMod(const std::string &modPath) -{ - m_mods.push_back(modPath); -} - const std::string& CPathManager::GetDataPath() { return m_dataPath; @@ -131,40 +120,18 @@ void CPathManager::InitPaths() GetLogger()->Info("Data path: %s\n", m_dataPath.c_str()); GetLogger()->Info("Save path: %s\n", m_savePath.c_str()); - m_modAutoloadDir.push_back(m_dataPath + "/mods"); - m_modAutoloadDir.push_back(m_savePath + "/mods"); + m_modSearchDirs.push_back(m_dataPath + "/mods"); + m_modSearchDirs.push_back(m_savePath + "/mods"); - if (!m_modAutoloadDir.empty()) + if (!m_modSearchDirs.empty()) { - GetLogger()->Info("Mod autoload dirs:\n"); - for(const std::string& modAutoloadDir : m_modAutoloadDir) - GetLogger()->Info(" * %s\n", modAutoloadDir.c_str()); - } - if (!m_mods.empty()) - { - GetLogger()->Info("Mods:\n"); - for(const std::string& modPath : m_mods) - GetLogger()->Info(" * %s\n", modPath.c_str()); + GetLogger()->Info("Mod search dirs:\n"); + for(const std::string& modSearchDir : m_modSearchDirs) + GetLogger()->Info(" * %s\n", modSearchDir.c_str()); } CResourceManager::AddLocation(m_dataPath); - for (const std::string& modAutoloadDir : m_modAutoloadDir) - { - GetLogger()->Trace("Searching for mods in '%s'...\n", modAutoloadDir.c_str()); - for (const std::string& modPath : FindModsInDir(modAutoloadDir)) - { - GetLogger()->Info("Autoloading mod: '%s'\n", modPath.c_str()); - CResourceManager::AddLocation(modPath); - } - } - - for (const std::string& modPath : m_mods) - { - GetLogger()->Info("Loading mod: '%s'\n", modPath.c_str()); - CResourceManager::AddLocation(modPath); - } - CResourceManager::SetSaveLocation(m_savePath); CResourceManager::AddLocation(m_savePath); @@ -174,7 +141,45 @@ void CPathManager::InitPaths() GetLogger()->Debug(" * %s\n", path.c_str()); } -std::vector CPathManager::FindModsInDir(const std::string &dir) +void CPathManager::AddMod(const std::string &path) +{ + m_mods.push_back(path); +} + +std::vector CPathManager::FindMods() const +{ + std::vector mods; + GetLogger()->Info("Found mods:\n"); + for (const auto &searchPath : m_modSearchDirs) + { + for (const auto &modPath : FindModsInDir(searchPath)) + { + GetLogger()->Info(" * %s\n", modPath.c_str()); + mods.push_back(modPath); + } + } + GetLogger()->Info("Additional mod paths:\n"); + for (const auto& modPath : m_mods) + { + if (boost::filesystem::exists(modPath)) + { + GetLogger()->Info(" * %s\n", modPath.c_str()); + mods.push_back(modPath); + } + else + { + GetLogger()->Warn("Mod does not exist: %s\n", modPath.c_str()); + } + } + return mods; +} + +void CPathManager::AddModSearchDir(const std::string &modSearchDirPath) +{ + m_modSearchDirs.push_back(modSearchDirPath); +} + +std::vector CPathManager::FindModsInDir(const std::string &dir) const { std::vector ret; try diff --git a/src/app/pathman.h b/src/app/pathman.h index 561f66f3..d1ba4bbe 100644 --- a/src/app/pathman.h +++ b/src/app/pathman.h @@ -37,8 +37,6 @@ public: void SetDataPath(const std::string &dataPath); void SetLangPath(const std::string &langPath); void SetSavePath(const std::string &savePath); - void AddModAutoloadDir(const std::string &modAutoloadDirPath); - void AddMod(const std::string &modPath); const std::string& GetDataPath(); const std::string& GetLangPath(); @@ -49,9 +47,15 @@ public: //! Loads configured paths void InitPaths(); + //! Adds a path to a mod + void AddMod(const std::string& path); + //! Find paths to mods in mod search directories + std::vector FindMods() const; + //! Adds a mod search directory + void AddModSearchDir(const std::string &modSearchDirPath); + private: - //! Loads all mods from given directory - std::vector FindModsInDir(const std::string &dir); + std::vector FindModsInDir(const std::string &dir) const; private: //! Data path @@ -60,8 +64,8 @@ private: std::string m_langPath; //! Save path std::string m_savePath; - //! Mod autoload paths - std::vector m_modAutoloadDir; - //! Mod paths + //! Mod search paths + std::vector m_modSearchDirs; + //! Additional mod paths std::vector m_mods; }; diff --git a/src/common/config_file.h b/src/common/config_file.h index 6fbeae71..6e95f481 100644 --- a/src/common/config_file.h +++ b/src/common/config_file.h @@ -26,9 +26,15 @@ #include "common/singleton.h" +#include "common/logger.h" + #include +#include #include +#include +#include +#include /** @@ -100,6 +106,76 @@ public: */ bool GetBoolProperty(std::string section, std::string key, bool &value); + /** Gets an array of values of type T in section under specified key + * The value separator is ','. + * \a array will only be changed if key exists + * \return return true on success + */ + template + bool SetArrayProperty(std::string section, std::string key, const std::vector& array) + { + try + { + std::string convertedValue = ArrayToString(array); + m_propertyTree.put(section + "." + key, convertedValue); + m_needsSave = true; + } + catch (std::exception & e) + { + GetLogger()->Error("Error on editing config file: %s\n", e.what()); + return false; + } + return true; + } + + /** Sets an array of values of type T in section under specified key. + * The value separator is ','. + * \a array will only be changed if key exists + * \return return true on success + */ + template + bool GetArrayProperty(std::string section, std::string key, std::vector& array) + { + try + { + std::string readValue = m_propertyTree.get(section + "." + key); + std::vector convertedValue = StringToArray(readValue); + array = std::move(convertedValue); + } + catch (std::exception & e) + { + GetLogger()->Log(m_loaded ? LOG_INFO : LOG_TRACE, "Error on parsing config file: %s\n", e.what()); + return false; + } + return true; + } + +private: + template + std::vector StringToArray(const std::string& s) + { + std::vector result; + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, ',')) + { + result.push_back(boost::lexical_cast(item)); + } + return result; + } + + template + std::string ArrayToString(const std::vector &array) + { + std::ostringstream oss; + if (!array.empty()) + { + std::copy(array.begin(), array.end() - 1, std::ostream_iterator(oss, ",")); + oss << array.back(); + } + return oss.str(); + } + private: boost::property_tree::ptree m_propertyTree; bool m_needsSave; diff --git a/src/common/event.cpp b/src/common/event.cpp index 214fd20e..5bae84f2 100644 --- a/src/common/event.cpp +++ b/src/common/event.cpp @@ -190,6 +190,7 @@ void InitializeEventTypeTexts() EVENT_TYPE_TEXT[EVENT_INTERFACE_ABORT] = "EVENT_INTERFACE_ABORT"; EVENT_TYPE_TEXT[EVENT_INTERFACE_USER] = "EVENT_INTERFACE_USER"; EVENT_TYPE_TEXT[EVENT_INTERFACE_SATCOM] = "EVENT_INTERFACE_SATCOM"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MODS] = "EVENT_INTERFACE_MODS"; EVENT_TYPE_TEXT[EVENT_INTERFACE_CHAP] = "EVENT_INTERFACE_CHAP"; EVENT_TYPE_TEXT[EVENT_INTERFACE_LIST] = "EVENT_INTERFACE_LIST"; @@ -272,6 +273,16 @@ void InitializeEventTypeTexts() EVENT_TYPE_TEXT[EVENT_INTERFACE_JOYSTICK_CAM_Y_INVERT]= "EVENT_INTERFACE_JOYSTICK_CAM_Y_INVERT"; EVENT_TYPE_TEXT[EVENT_INTERFACE_JOYSTICK_CAM_Z_INVERT]= "EVENT_INTERFACE_JOYSTICK_CAM_Z_INVERT"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MOD_LIST] = "EVENT_INTERFACE_MOD_LIST"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_WORKSHOP] = "EVENT_INTERFACE_WORKSHOP"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MODS_DIR] = "EVENT_INTERFACE_MODS_DIR"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE] = "EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MODS_APPLY] = "EVENT_INTERFACE_MODS_APPLY"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MOD_DETAILS] = "EVENT_INTERFACE_MOD_DETAILS"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MOD_MOVE_UP] = "EVENT_INTERFACE_MOD_MOVE_UP"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MOD_MOVE_DOWN] = "EVENT_INTERFACE_MOD_MOVE_DOWN"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MODS_REFRESH] = "EVENT_INTERFACE_MODS_REFRESH"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_GLINTl] = "EVENT_INTERFACE_GLINTl"; EVENT_TYPE_TEXT[EVENT_INTERFACE_GLINTr] = "EVENT_INTERFACE_GLINTr"; EVENT_TYPE_TEXT[EVENT_INTERFACE_GLINTu] = "EVENT_INTERFACE_GLINTu"; diff --git a/src/common/event.h b/src/common/event.h index c2029e82..d55c9664 100644 --- a/src/common/event.h +++ b/src/common/event.h @@ -225,6 +225,7 @@ enum EventType EVENT_INTERFACE_ABORT = 412, EVENT_INTERFACE_USER = 413, EVENT_INTERFACE_SATCOM = 414, + EVENT_INTERFACE_MODS = 416, EVENT_INTERFACE_CHAP = 420, EVENT_INTERFACE_LIST = 421, @@ -311,6 +312,17 @@ enum EventType EVENT_INTERFACE_JOYSTICK_CAM_Y_INVERT = 573, EVENT_INTERFACE_JOYSTICK_CAM_Z_INVERT = 574, + EVENT_INTERFACE_MOD_LIST = 580, + EVENT_INTERFACE_WORKSHOP = 581, + EVENT_INTERFACE_MODS_DIR = 582, + EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE = 583, + EVENT_INTERFACE_MODS_APPLY = 584, + EVENT_INTERFACE_MOD_SUMMARY = 585, + EVENT_INTERFACE_MOD_DETAILS = 586, + EVENT_INTERFACE_MOD_MOVE_UP = 587, + EVENT_INTERFACE_MOD_MOVE_DOWN = 888, + EVENT_INTERFACE_MODS_REFRESH = 589, + EVENT_INTERFACE_GLINTl = 590, EVENT_INTERFACE_GLINTr = 591, EVENT_INTERFACE_GLINTu = 592, diff --git a/src/common/resources/resourcemanager.cpp b/src/common/resources/resourcemanager.cpp index b51325b8..7c572483 100644 --- a/src/common/resources/resourcemanager.cpp +++ b/src/common/resources/resourcemanager.cpp @@ -62,9 +62,9 @@ std::string CResourceManager::CleanPath(const std::string& path) } -bool CResourceManager::AddLocation(const std::string &location, bool prepend) +bool CResourceManager::AddLocation(const std::string &location, bool prepend, const std::string &mountPoint) { - if (!PHYSFS_mount(location.c_str(), nullptr, prepend ? 0 : 1)) + if (!PHYSFS_mount(location.c_str(), mountPoint.c_str(), prepend ? 0 : 1)) { GetLogger()->Error("Error while mounting \"%s\": %s\n", location.c_str(), PHYSFS_getLastError()); return false; @@ -95,6 +95,12 @@ std::vector CResourceManager::GetLocations() return ret; } +bool CResourceManager::LocationExists(const std::string& location) +{ + auto locations = GetLocations(); + auto it = std::find(locations.cbegin(), locations.cend(), location); + return it != locations.cend(); +} bool CResourceManager::SetSaveLocation(const std::string &location) { diff --git a/src/common/resources/resourcemanager.h b/src/common/resources/resourcemanager.h index 8c09b91a..d15a6f02 100644 --- a/src/common/resources/resourcemanager.h +++ b/src/common/resources/resourcemanager.h @@ -36,11 +36,13 @@ public: static std::string CleanPath(const std::string &path); //! Add a location to the search path - static bool AddLocation(const std::string &location, bool prepend = true); + static bool AddLocation(const std::string &location, bool prepend = true, const std::string &mountPoint = ""); //! Remove a location from the search path static bool RemoveLocation(const std::string &location); //! List all locations in the search path static std::vector GetLocations(); + //! Check if given location is in the search path + static bool LocationExists(const std::string &location); static bool SetSaveLocation(const std::string &location); static std::string GetSaveLocation(); diff --git a/src/common/restext.cpp b/src/common/restext.cpp index cd05d59f..becde931 100644 --- a/src/common/restext.cpp +++ b/src/common/restext.cpp @@ -71,12 +71,13 @@ void InitializeRestext() stringsText[RT_TITLE_MISSION] = TR("Missions"); stringsText[RT_TITLE_FREE] = TR("Free game"); stringsText[RT_TITLE_USER] = TR("User levels"); - stringsText[RT_TITLE_CODE_BATTLES]=TR("Code battles"); + stringsText[RT_TITLE_CODE_BATTLES] = TR("Code battles"); stringsText[RT_TITLE_SETUP] = TR("Options"); stringsText[RT_TITLE_NAME] = TR("Player's name"); stringsText[RT_TITLE_PERSO] = TR("Customize your appearance"); stringsText[RT_TITLE_WRITE] = TR("Save the current mission"); stringsText[RT_TITLE_READ] = TR("Load a saved mission"); + stringsText[RT_TITLE_MODS] = TR("Mods"); stringsText[RT_PLAY_CHAP_CHAPTERS] = TR("Chapters:"); stringsText[RT_PLAY_CHAP_PLANETS] = TR("Planets:"); @@ -108,6 +109,11 @@ void InitializeRestext() stringsText[RT_DIALOG_OK] = TR("OK"); stringsText[RT_DIALOG_NOUSRLVL_TITLE] = TR("No userlevels installed!"); stringsText[RT_DIALOG_NOUSRLVL_TEXT] = TR("This menu is for userlevels from mods, but you didn't install any"); + stringsText[RT_DIALOG_OPEN_PATH_FAILED_TITLE] = TR("Could not open the file explorer!"); + stringsText[RT_DIALOG_OPEN_PATH_FAILED_TEXT] = TR("The path %s could not be opened in a file explorer."); + stringsText[RT_DIALOG_OPEN_WEBSITE_FAILED_TITLE] = TR("Could not open the web browser!"); + stringsText[RT_DIALOG_OPEN_WEBSITE_FAILED_TEXT] = TR("The address %s could not be opened in a web browser."); + stringsText[RT_DIALOG_CHANGES_QUESTION] = TR("There are unsaved changes. Do you want to save them before leaving?"); stringsText[RT_STUDIO_LISTTT] = TR("Keyword help(\\key cbot;)"); stringsText[RT_STUDIO_COMPOK] = TR("Compilation ok (0 errors)"); @@ -153,7 +159,18 @@ void InitializeRestext() stringsText[RT_SCOREBOARD_RESULTS_TIME]= TR("Time: %s"); stringsText[RT_SCOREBOARD_RESULTS_LINE]= TR("%s: %d pts"); - + stringsText[RT_MOD_LIST] = TR("Mods:"); + stringsText[RT_MOD_DETAILS] = TR("Information:"); + stringsText[RT_MOD_SUMMARY] = TR("Description:"); + stringsText[RT_MOD_ENABLE] = TR("Enable\\Enable the selected mod"); + stringsText[RT_MOD_DISABLE] = TR("Disable\\Disable the selected mod"); + stringsText[RT_MOD_UNKNOWN_AUTHOR] = TR("Unknown author"); + stringsText[RT_MOD_AUTHOR_FIELD_NAME] = TR("by"); + stringsText[RT_MOD_VERSION_FIELD_NAME] = TR("Version"); + stringsText[RT_MOD_WEBSITE_FIELD_NAME] = TR("Website"); + stringsText[RT_MOD_CHANGES_FIELD_NAME] = TR("Changes"); + stringsText[RT_MOD_NO_SUMMARY] = TR("No description."); + stringsText[RT_MOD_NO_CHANGES] = TR("No changes."); stringsEvent[EVENT_LABEL_CODE_BATTLE] = TR("Code battle"); @@ -173,6 +190,7 @@ void InitializeRestext() stringsEvent[EVENT_INTERFACE_CODE_BATTLES] = TR("Code battles\\Program your robot to be the best of them all!"); stringsEvent[EVENT_INTERFACE_USER] = TR("Custom levels\\Levels from mods created by the users"); stringsEvent[EVENT_INTERFACE_SATCOM] = TR("SatCom"); + stringsEvent[EVENT_INTERFACE_MODS] = TR("Mods\\Mod manager"); stringsEvent[EVENT_INTERFACE_NAME] = TR("Change player\\Change player"); stringsEvent[EVENT_INTERFACE_SETUP] = TR("Options\\Preferences"); stringsEvent[EVENT_INTERFACE_AGAIN] = TR("Restart\\Restart the mission from the beginning"); @@ -182,6 +200,12 @@ void InitializeRestext() stringsEvent[EVENT_INTERFACE_QUIT] = TR("Quit\\Quit Colobot: Gold Edition"); stringsEvent[EVENT_INTERFACE_BACK] = TR("<< Back \\Back to the previous screen"); stringsEvent[EVENT_INTERFACE_PLAY] = TR("Play\\Start mission!"); + stringsEvent[EVENT_INTERFACE_WORKSHOP] = TR("Workshop\\Open the workshop to search for mods"); + stringsEvent[EVENT_INTERFACE_MODS_DIR] = TR("Open Directory\\Open the mods directory"); + stringsEvent[EVENT_INTERFACE_MODS_APPLY] = TR("Apply\\Apply the current mod configuration"); + stringsEvent[EVENT_INTERFACE_MOD_MOVE_UP] = TR("Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)"); + stringsEvent[EVENT_INTERFACE_MOD_MOVE_DOWN] = TR("Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)"); + stringsEvent[EVENT_INTERFACE_MODS_REFRESH] = TR("Refresh\\Refresh the list of currently installed mods"); stringsEvent[EVENT_INTERFACE_SETUPd] = TR("Device\\Driver and resolution settings"); stringsEvent[EVENT_INTERFACE_SETUPg] = TR("Graphics\\Graphics settings"); stringsEvent[EVENT_INTERFACE_SETUPp] = TR("Game\\Game settings"); diff --git a/src/common/restext.h b/src/common/restext.h index c33a864f..165c52cd 100644 --- a/src/common/restext.h +++ b/src/common/restext.h @@ -71,6 +71,7 @@ enum ResTextType RT_TITLE_WRITE = 50, RT_TITLE_READ = 51, RT_TITLE_USER = 52, + RT_TITLE_MODS = 54, RT_PLAY_CHAP_CHAPTERS = 60, RT_PLAY_CHAP_PLANETS = 61, @@ -102,6 +103,11 @@ enum ResTextType RT_DIALOG_OK = 110, RT_DIALOG_NOUSRLVL_TITLE = 111, RT_DIALOG_NOUSRLVL_TEXT = 112, + RT_DIALOG_OPEN_PATH_FAILED_TITLE = 113, + RT_DIALOG_OPEN_PATH_FAILED_TEXT = 114, + RT_DIALOG_OPEN_WEBSITE_FAILED_TITLE = 115, + RT_DIALOG_OPEN_WEBSITE_FAILED_TEXT = 116, + RT_DIALOG_CHANGES_QUESTION = 117, RT_STUDIO_LISTTT = 120, RT_STUDIO_COMPOK = 121, @@ -147,6 +153,18 @@ enum ResTextType RT_SCOREBOARD_RESULTS_TIME= 232, RT_SCOREBOARD_RESULTS_LINE= 233, + RT_MOD_LIST = 234, + RT_MOD_DETAILS = 235, + RT_MOD_SUMMARY = 236, + RT_MOD_ENABLE = 237, + RT_MOD_DISABLE = 238, + RT_MOD_UNKNOWN_AUTHOR = 239, + RT_MOD_AUTHOR_FIELD_NAME = 240, + RT_MOD_VERSION_FIELD_NAME = 241, + RT_MOD_WEBSITE_FIELD_NAME = 242, + RT_MOD_CHANGES_FIELD_NAME = 243, + RT_MOD_NO_SUMMARY = 244, + RT_MOD_NO_CHANGES = 245, RT_MAX //! < number of values }; diff --git a/src/common/system/system.cpp b/src/common/system/system.cpp index a815964d..b4893655 100644 --- a/src/common/system/system.cpp +++ b/src/common/system/system.cpp @@ -215,3 +215,13 @@ std::string CSystemUtils::GetEnvVar(const std::string& name) { return ""; } + +bool CSystemUtils::OpenPath(const std::string& path) +{ + return false; +} + +bool CSystemUtils::OpenWebsite(const std::string& url) +{ + return false; +} diff --git a/src/common/system/system.h b/src/common/system/system.h index ebc92269..f20ee443 100644 --- a/src/common/system/system.h +++ b/src/common/system/system.h @@ -145,6 +145,14 @@ public: //! Returns the environment variable with the given name or an empty string if it does not exist virtual std::string GetEnvVar(const std::string &name); + //! Opens a path with default file browser + /** \returns true if successful */ + virtual bool OpenPath(const std::string& path); + + //! Opens a website with default web browser + /** \returns true if successful */ + virtual bool OpenWebsite(const std::string& url); + //! Sleep for given amount of microseconds virtual void Usleep(int usecs) = 0; diff --git a/src/common/system/system_linux.cpp b/src/common/system/system_linux.cpp index 459c071f..2b7d99fa 100644 --- a/src/common/system/system_linux.cpp +++ b/src/common/system/system_linux.cpp @@ -150,6 +150,28 @@ std::string CSystemUtilsLinux::GetEnvVar(const std::string& name) return ""; } +bool CSystemUtilsLinux::OpenPath(const std::string& path) +{ + int result = system(("xdg-open \"" + path + "\"").c_str()); + if (result != 0) + { + GetLogger()->Error("Failed to open path: %s, error code: %i\n", path.c_str(), result); + return false; + } + return true; +} + +bool CSystemUtilsLinux::OpenWebsite(const std::string& url) +{ + int result = system(("xdg-open \"" + url + "\"").c_str()); + if (result != 0) + { + GetLogger()->Error("Failed to open website: %s, error code: %i\n", url.c_str(), result); + return false; + } + return true; +} + void CSystemUtilsLinux::Usleep(int usec) { usleep(usec); diff --git a/src/common/system/system_linux.h b/src/common/system/system_linux.h index b3446aa6..6607b150 100644 --- a/src/common/system/system_linux.h +++ b/src/common/system/system_linux.h @@ -48,6 +48,9 @@ public: std::string GetEnvVar(const std::string& name) override; + bool OpenPath(const std::string& path) override; + bool OpenWebsite(const std::string& url) override; + void Usleep(int usec) override; private: diff --git a/src/common/system/system_macosx.cpp b/src/common/system/system_macosx.cpp index 752cf98f..d65fb6dc 100644 --- a/src/common/system/system_macosx.cpp +++ b/src/common/system/system_macosx.cpp @@ -119,6 +119,28 @@ std::string CSystemUtilsMacOSX::GetEnvVar(const std::string& str) return std::string(); } +bool CSystemUtilsMacOSX::OpenPath(const std::string& path) +{ + int result = system(("open \"" + path + "\"").c_str()); // TODO: Test on macOS + if (result != 0) + { + GetLogger()->Error("Failed to open path: %s, error code: %i\n", path.c_str(), result); + return false; + } + return true; +} + +bool CSystemUtilsMacOSX::OpenWebsite(const std::string& url) +{ + int result = system(("open \"" + url + "\"").c_str()); // TODO: Test on macOS + if (result != 0) + { + GetLogger()->Error("Failed to open website: %s, error code: %i\n", website.c_str(), result); + return false; + } + return true; +} + void CSystemUtilsMacOSX::Usleep(int usec) { usleep(usec); diff --git a/src/common/system/system_macosx.h b/src/common/system/system_macosx.h index dc3f785a..523e52e8 100644 --- a/src/common/system/system_macosx.h +++ b/src/common/system/system_macosx.h @@ -38,6 +38,9 @@ public: std::string GetEnvVar(const std::string& name) override; + bool OpenPath(const std::string& path) override; + bool OpenWebsite(const std::string& url) override; + void Usleep(int usec) override; private: diff --git a/src/common/system/system_windows.cpp b/src/common/system/system_windows.cpp index 413b0575..989d359b 100644 --- a/src/common/system/system_windows.cpp +++ b/src/common/system/system_windows.cpp @@ -21,6 +21,7 @@ #include "common/logger.h" +#include #include @@ -152,6 +153,28 @@ std::string CSystemUtilsWindows::GetEnvVar(const std::string& name) } } +bool CSystemUtilsWindows::OpenPath(const std::string& path) +{ + int result = system(("start explorer \"" + boost::filesystem::path(path).make_preferred().string() + "\"").c_str()); + if (result != 0) + { + GetLogger()->Error("Failed to open path: %s, error code: %i\n", path.c_str(), result); + return false; + } + return true; +} + +bool CSystemUtilsWindows::OpenWebsite(const std::string& url) +{ + int result = system(("rundll32 url.dll,FileProtocolHandler \"" + url + "\"").c_str()); + if (result != 0) + { + GetLogger()->Error("Failed to open website: %s, error code: %i\n", url.c_str(), result); + return false; + } + return true; +} + void CSystemUtilsWindows::Usleep(int usec) { LARGE_INTEGER ft; diff --git a/src/common/system/system_windows.h b/src/common/system/system_windows.h index 4bbd704f..90ef6b91 100644 --- a/src/common/system/system_windows.h +++ b/src/common/system/system_windows.h @@ -46,6 +46,9 @@ public: std::string GetEnvVar(const std::string& name) override; + bool OpenPath(const std::string& path) override; + bool OpenWebsite(const std::string& url) override; + void Usleep(int usec) override; public: diff --git a/src/graphics/engine/engine.cpp b/src/graphics/engine/engine.cpp index 198e230d..c146d767 100644 --- a/src/graphics/engine/engine.cpp +++ b/src/graphics/engine/engine.cpp @@ -408,6 +408,7 @@ void CEngine::ReloadAllTextures() { FlushTextureCache(); m_text->FlushCache(); + m_text->ReloadFonts(); m_app->GetEventQueue()->AddEvent(Event(EVENT_RELOAD_TEXTURES)); UpdateGroundSpotTextures(); diff --git a/src/graphics/engine/engine.h b/src/graphics/engine/engine.h index af455819..aee24f34 100644 --- a/src/graphics/engine/engine.h +++ b/src/graphics/engine/engine.h @@ -1189,6 +1189,10 @@ public: void EnablePauseBlur(); void DisablePauseBlur(); + //! Reloads all textures + /** This additionally sends EVENT_RELOAD_TEXTURES to reload all textures not maintained by CEngine **/ + void ReloadAllTextures(); + protected: //! Resets some states and flushes textures after device was changed (e.g. resoulution changed) /** Instead of calling this directly, send EVENT_RESOLUTION_CHANGED event **/ @@ -1287,10 +1291,6 @@ protected: }; static void WriteScreenShotThread(std::unique_ptr data); - //! Reloads all textures - /** This additionally sends EVENT_RELOAD_TEXTURES to reload all textures not maintained by CEngine **/ - void ReloadAllTextures(); - protected: CApplication* m_app; CSystemUtils* m_systemUtils; diff --git a/src/graphics/engine/text.cpp b/src/graphics/engine/text.cpp index e0cf9c82..fd946cca 100644 --- a/src/graphics/engine/text.cpp +++ b/src/graphics/engine/text.cpp @@ -191,17 +191,32 @@ CText::~CText() bool CText::Create() { - CFontLoader fontLoader; - if (!fontLoader.Init()) - { - GetLogger()->Debug("Error on parsing fonts config file: failed to open file\n"); - } if (TTF_Init() != 0) { m_error = std::string("TTF_Init error: ") + std::string(TTF_GetError()); return false; } + if (!ReloadFonts()) + { + return false; + } + + return true; +} + +bool CText::ReloadFonts() +{ + CFontLoader fontLoader; + if (!fontLoader.Init()) + { + GetLogger()->Debug("Error on parsing fonts config file: failed to open file\n"); + } + + // Backup previous fonts + auto fonts = std::move(m_fonts); + m_fonts.clear(); + for (auto type : {FONT_COMMON, FONT_STUDIO, FONT_SATCOM}) { m_fonts[static_cast(type)] = MakeUnique(fontLoader.GetFont(type)); @@ -214,7 +229,10 @@ bool CText::Create() FontType type = (*it).first; CachedFont* cf = GetOrOpenFont(type, m_defaultSize); if (cf == nullptr || cf->font == nullptr) + { + m_fonts = std::move(fonts); return false; + } } return true; diff --git a/src/graphics/engine/text.h b/src/graphics/engine/text.h index d0abbcee..dc1be3db 100644 --- a/src/graphics/engine/text.h +++ b/src/graphics/engine/text.h @@ -256,6 +256,8 @@ public: //! Flushes cached textures void FlushCache(); + //! Try to load new font files + bool ReloadFonts(); //@{ //! Tab size management diff --git a/src/level/parser/parser.cpp b/src/level/parser/parser.cpp index e8f871bd..1bf78a24 100644 --- a/src/level/parser/parser.cpp +++ b/src/level/parser/parser.cpp @@ -41,6 +41,7 @@ #include #include #include +#include CLevelParser::CLevelParser() { @@ -172,13 +173,28 @@ void CLevelParser::Load() boost::replace_all(line, "\t", " "); // replace tab by space // ignore comments - std::size_t comment = line.find("//"); - if (comment != std::string::npos) - line = line.substr(0, comment); + size_t pos = 0; + std::string linesuffix = line; + boost::regex commentRegex{ R"(("[^"]*")|('[^']*')|(//.*$))" }; + boost::smatch matches; + while (boost::regex_search(linesuffix, matches, commentRegex)) + { + if (matches[3].matched) + { + pos += std::distance(linesuffix.cbegin(), matches.prefix().second); + line = line.substr(0, pos); + linesuffix = ""; + } + else + { + pos += std::distance(linesuffix.cbegin(), matches.suffix().first); + linesuffix = matches.suffix().str(); + } + } boost::algorithm::trim(line); - std::size_t pos = line.find_first_of(" \t\n"); + pos = line.find_first_of(" \t\n"); std::string command = line.substr(0, pos); if (pos != std::string::npos) { diff --git a/src/level/robotmain.cpp b/src/level/robotmain.cpp index 1a51c7e1..7092b32b 100644 --- a/src/level/robotmain.cpp +++ b/src/level/robotmain.cpp @@ -320,6 +320,7 @@ std::string PhaseToString(Phase phase) if (phase == PHASE_APPERANCE) return "PHASE_APPERANCE"; if (phase == PHASE_MAIN_MENU) return "PHASE_MAIN_MENU"; if (phase == PHASE_LEVEL_LIST) return "PHASE_LEVEL_LIST"; + if (phase == PHASE_MOD_LIST) return "PHASE_MOD_LIST"; if (phase == PHASE_SIMUL) return "PHASE_SIMUL"; if (phase == PHASE_SETUPd) return "PHASE_SETUPd"; if (phase == PHASE_SETUPg) return "PHASE_SETUPg"; @@ -3911,6 +3912,7 @@ void CRobotMain::ChangeColor() m_phase != PHASE_SETUPps && m_phase != PHASE_SETUPcs && m_phase != PHASE_SETUPss && + m_phase != PHASE_MOD_LIST && m_phase != PHASE_WIN && m_phase != PHASE_LOST && m_phase != PHASE_APPERANCE ) return; diff --git a/src/level/robotmain.h b/src/level/robotmain.h index e95d6671..731d7a1a 100644 --- a/src/level/robotmain.h +++ b/src/level/robotmain.h @@ -55,6 +55,7 @@ enum Phase PHASE_APPERANCE, PHASE_MAIN_MENU, PHASE_LEVEL_LIST, + PHASE_MOD_LIST, PHASE_SIMUL, PHASE_SETUPd, PHASE_SETUPg, diff --git a/src/sound/oalsound/alsound.cpp b/src/sound/oalsound/alsound.cpp index f27a462b..26006198 100644 --- a/src/sound/oalsound/alsound.cpp +++ b/src/sound/oalsound/alsound.cpp @@ -47,22 +47,7 @@ void CALSound::CleanUp() if (m_enabled) { GetLogger()->Info("Unloading files and closing device...\n"); - StopAll(); - StopMusic(); - - m_channels.clear(); - - m_currentMusic.reset(); - - m_oldMusic.clear(); - - m_previousMusic.music.reset(); - - m_sounds.clear(); - - m_music.clear(); - - m_enabled = false; + Reset(); alcDestroyContext(m_context); alcCloseDevice(m_device); @@ -99,6 +84,24 @@ bool CALSound::Create() return true; } +void CALSound::Reset() +{ + StopAll(); + StopMusic(); + + m_channels.clear(); + + m_currentMusic.reset(); + + m_oldMusic.clear(); + + m_previousMusic.music.reset(); + + m_sounds.clear(); + + m_music.clear(); +} + bool CALSound::GetEnable() { return m_enabled; diff --git a/src/sound/oalsound/alsound.h b/src/sound/oalsound/alsound.h index 6f76c268..75e3d7f6 100644 --- a/src/sound/oalsound/alsound.h +++ b/src/sound/oalsound/alsound.h @@ -84,6 +84,7 @@ public: ~CALSound(); bool Create() override; + void Reset() override; bool Cache(SoundType, const std::string &) override; void CacheMusic(const std::string &) override; bool IsCached(SoundType) override; diff --git a/src/sound/sound.cpp b/src/sound/sound.cpp index 1cc23c4b..87d7266c 100644 --- a/src/sound/sound.cpp +++ b/src/sound/sound.cpp @@ -52,6 +52,10 @@ void CSoundInterface::CacheAll() } } +void CSoundInterface::Reset() +{ +} + bool CSoundInterface::Cache(SoundType sound, const std::string &file) { return true; diff --git a/src/sound/sound.h b/src/sound/sound.h index 78a5449d..314eb856 100644 --- a/src/sound/sound.h +++ b/src/sound/sound.h @@ -72,6 +72,10 @@ public: */ void CacheAll(); + /** Stop all sounds and music and clean cache. + */ + virtual void Reset(); + /** Function called to cache sound effect file. * This function is called by plugin interface for each file. * \param sound - id of a file, will be used to identify sound files diff --git a/src/ui/controls/edit.cpp b/src/ui/controls/edit.cpp index 62987308..570bd518 100644 --- a/src/ui/controls/edit.cpp +++ b/src/ui/controls/edit.cpp @@ -1299,7 +1299,9 @@ void CEdit::SetText(const std::string& text, bool bNew) if( m_len >= GetMaxChar() ) m_len = GetMaxChar(); m_text.resize( m_len + 1, '\0' ); + m_text[m_len] = '\0'; m_format.resize( m_len + 1, m_fontType ); + m_format[m_len] = m_fontType; font = m_fontType; j = 0; diff --git a/src/ui/mainui.cpp b/src/ui/mainui.cpp index 4070e5ec..daca36c7 100644 --- a/src/ui/mainui.cpp +++ b/src/ui/mainui.cpp @@ -49,6 +49,7 @@ #include "ui/screen/screen_level_list.h" #include "ui/screen/screen_loading.h" #include "ui/screen/screen_main_menu.h" +#include "ui/screen/screen_mod_list.h" #include "ui/screen/screen_player_select.h" #include "ui/screen/screen_quit.h" #include "ui/screen/screen_setup_controls.h" @@ -80,6 +81,7 @@ CMainUserInterface::CMainUserInterface() m_screenIORead = MakeUnique(m_screenLevelList.get()); m_screenIOWrite = MakeUnique(m_screenLevelList.get()); m_screenLoading = MakeUnique(); + m_screenModList = MakeUnique(m_dialog.get(), m_app->GetModManager()); m_screenSetupControls = MakeUnique(); m_screenSetupDisplay = MakeUnique(); m_screenSetupGame = MakeUnique(); @@ -184,6 +186,10 @@ void CMainUserInterface::ChangePhase(Phase phase) m_screenLevelList->SetLevelCategory(m_main->GetLevelCategory()); m_currentScreen = m_screenLevelList.get(); } + if (m_phase == PHASE_MOD_LIST) + { + m_currentScreen = m_screenModList.get(); + } if (m_phase >= PHASE_SETUPd && m_phase <= PHASE_SETUPs) { CScreenSetup* screenSetup = GetSetupScreen(m_phase); @@ -531,6 +537,7 @@ void CMainUserInterface::FrameParticle(float rTime) } else if ( m_phase == PHASE_PLAYER_SELECT || m_phase == PHASE_LEVEL_LIST || + m_phase == PHASE_MOD_LIST || m_phase == PHASE_SETUPd || m_phase == PHASE_SETUPg || m_phase == PHASE_SETUPp || diff --git a/src/ui/mainui.h b/src/ui/mainui.h index 50888f5b..2cae1d18 100644 --- a/src/ui/mainui.h +++ b/src/ui/mainui.h @@ -50,6 +50,7 @@ class CScreenIOWrite; class CScreenLevelList; class CScreenLoading; class CScreenMainMenu; +class CScreenModList; class CScreenPlayerSelect; class CScreenQuit; class CScreenSetup; @@ -114,6 +115,7 @@ protected: std::unique_ptr m_screenLevelList; std::unique_ptr m_screenLoading; std::unique_ptr m_screenMainMenu; + std::unique_ptr m_screenModList; std::unique_ptr m_screenPlayerSelect; std::unique_ptr m_screenQuit; std::unique_ptr m_screenSetupControls; diff --git a/src/ui/screen/screen_main_menu.cpp b/src/ui/screen/screen_main_menu.cpp index 2d69ae1c..30c771a0 100644 --- a/src/ui/screen/screen_main_menu.cpp +++ b/src/ui/screen/screen_main_menu.cpp @@ -170,6 +170,13 @@ void CScreenMainMenu::CreateInterface() pb = pw->CreateButton(pos, ddim, 128+60, EVENT_INTERFACE_SATCOM); pb->SetState(STATE_SHADOW); + // Mods button + pos.x = 447.0f/640.0f; + pos.y = 313.0f/480.0f; + ddim.x = 0.09f; + pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_MODS); + pb->SetState(STATE_SHADOW); + SetBackground("textures/interface/interface.png"); CreateVersionDisplay(); } @@ -235,6 +242,9 @@ bool CScreenMainMenu::EventProcess(const Event &event) m_main->ChangePhase(PHASE_SATCOM); break; + case EVENT_INTERFACE_MODS: + m_main->ChangePhase(PHASE_MOD_LIST); + default: return true; } diff --git a/src/ui/screen/screen_mod_list.cpp b/src/ui/screen/screen_mod_list.cpp new file mode 100644 index 00000000..856f2a20 --- /dev/null +++ b/src/ui/screen/screen_mod_list.cpp @@ -0,0 +1,582 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#include "ui/screen/screen_mod_list.h" + +#include "common/config.h" + +#include "app/app.h" +#include "app/modman.h" + +#include "common/logger.h" +#include "common/restext.h" +#include "common/stringutils.h" + +#include "common/resources/resourcemanager.h" + +#include "common/system/system.h" + +#include "level/robotmain.h" + +#include "math/func.h" + +#include "sound/sound.h" + +#include "ui/controls/button.h" +#include "ui/controls/edit.h" +#include "ui/controls/interface.h" +#include "ui/controls/label.h" +#include "ui/controls/list.h" +#include "ui/controls/window.h" + +#include + +namespace Ui +{ + +CScreenModList::CScreenModList(CMainDialog* dialog, CModManager* modManager) + : m_dialog(dialog), + m_modManager(modManager) +{ +} + +void CScreenModList::CreateInterface() +{ + CWindow* pw; + CEdit* pe; + CLabel* pl; + CButton* pb; + CList* pli; + Math::Point pos, ddim; + std::string name; + + // Display the window + pos.x = 0.10f; + pos.y = 0.10f; + ddim.x = 0.80f; + ddim.y = 0.80f; + pw = m_interface->CreateWindows(pos, ddim, 12, EVENT_WINDOW5); + pw->SetClosable(true); + GetResource(RES_TEXT, RT_TITLE_MODS, name); + pw->SetName(name); + + pos.x = 0.10f; + pos.y = 0.40f; + ddim.x = 0.50f; + ddim.y = 0.50f; + pw->CreateGroup(pos, ddim, 5, EVENT_INTERFACE_GLINTl); // orange corner + pos.x = 0.40f; + pos.y = 0.10f; + ddim.x = 0.50f; + ddim.y = 0.50f; + pw->CreateGroup(pos, ddim, 4, EVENT_INTERFACE_GLINTr); // blue corner + + // Display the list of mods + pos.x = ox+sx*3; + pos.y = oy+sy*10.5f; + ddim.x = dim.x*7.5f; + ddim.y = dim.y*0.6f; + GetResource(RES_TEXT, RT_MOD_LIST, name); + pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL11, name); + pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT); + + pos.y = oy+sy*6.7f; + ddim.y = dim.y*4.6f; + ddim.x = dim.x*6.5f; + pli = pw->CreateList(pos, ddim, 0, EVENT_INTERFACE_MOD_LIST); + pli->SetState(STATE_SHADOW); + pli->SetState(STATE_EXTEND); + + // Displays the mod details + pos.x = ox+sx*9.5f; + pos.y = oy+sy*10.5f; + ddim.x = dim.x*7.5f; + ddim.y = dim.y*0.6f; + GetResource(RES_TEXT, RT_MOD_DETAILS, name); + pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL12, name); + pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT); + + pos.y = oy+sy*6.7f; + ddim.y = dim.y*4.3f; + ddim.x = dim.x*6.5f; + pe = pw->CreateEdit(pos, ddim, 0, EVENT_INTERFACE_MOD_DETAILS); + pe->SetState(STATE_SHADOW); + pe->SetMaxChar(500); + pe->SetEditCap(false); // just to see + pe->SetHighlightCap(true); + + pos = pli->GetPos(); + ddim = pli->GetDim(); + + // Displays the mod summary + pos.x = ox+sx*3; + pos.y = oy+sy*5.4f; + ddim.x = dim.x*6.5f; + ddim.y = dim.y*0.6f; + GetResource(RES_TEXT, RT_MOD_SUMMARY, name); + pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL13, name); + pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT); + + pos.x = ox+sx*3; + pos.y = oy+sy*3.6f; + ddim.x = dim.x*13.4f; + ddim.y = dim.y*1.9f; + pe = pw->CreateEdit(pos, ddim, 0, EVENT_INTERFACE_MOD_SUMMARY); + pe->SetState(STATE_SHADOW); + pe->SetMaxChar(500); + pe->SetEditCap(false); // just to see + pe->SetHighlightCap(true); + + // Apply button + pos.x = ox+sx*13.75f; + pos.y = oy+sy*2; + ddim.x = dim.x*2.0f; + ddim.y = dim.y*1; + pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_MODS_APPLY); + pb->SetState(STATE_SHADOW); + + // Display the enable/disable button + pos.x -= dim.x*2.3f; + ddim.x = dim.x*2.0f; + pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE); + pb->SetState(STATE_SHADOW); + + // Display the move up button + pos.x -= dim.x*0.8f; + pos.y = oy+sy*2.48; + ddim.x = dim.x*0.5; + ddim.y = dim.y*0.5; + pb = pw->CreateButton(pos, ddim, 49, EVENT_INTERFACE_MOD_MOVE_UP); + pb->SetState(STATE_SHADOW); + + // Display the move down button + pos.y = oy+sy*2; + pb = pw->CreateButton(pos, ddim, 50, EVENT_INTERFACE_MOD_MOVE_DOWN); + pb->SetState(STATE_SHADOW); + + // Display the refresh button + pos.x -= dim.x*1.3f; + pos.y = oy+sy*2; + ddim.x = dim.x*1; + ddim.y = dim.y*1; + pb = pw->CreateButton(pos, ddim, 87, EVENT_INTERFACE_MODS_REFRESH); + pb->SetState(STATE_SHADOW); + + // Display the open website button + pos.x -= dim.x*1.3f; + pb = pw->CreateButton(pos, ddim, 40, EVENT_INTERFACE_WORKSHOP); + pb->SetState(STATE_SHADOW); + + // Display the open directory button + pos.x -= dim.x*1.3f; + pb = pw->CreateButton(pos, ddim, 57, EVENT_INTERFACE_MODS_DIR); + pb->SetState(STATE_SHADOW); + + // Back button + pos.x = ox+sx*3; + ddim.x = dim.x*4; + pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_BACK); + pb->SetState(STATE_SHADOW); + + FindMods(); + UpdateAll(); + + // Background + SetBackground("textures/interface/interface.png"); + CreateVersionDisplay(); +} + +bool CScreenModList::EventProcess(const Event &event) +{ + CWindow* pw; + CList* pl; + + const std::string workshopUrl = "https://www.moddb.com/games/colobot-gold-edition"; + const std::string modDir = CResourceManager::GetSaveLocation() + "/mods"; + + auto systemUtils = CSystemUtils::Create(); // platform-specific utils + + Mod const * mod; + + pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return false; + + if (event.type == pw->GetEventTypeClose() || + event.type == EVENT_INTERFACE_BACK || + (event.type == EVENT_KEY_DOWN && event.GetData()->key == KEY(ESCAPE))) + { + if (m_modManager->Changes()) + { + m_dialog->StartQuestion(RT_DIALOG_CHANGES_QUESTION, true, true, false, + [this]() + { + ApplyChanges(); + CloseWindow(); + }, + [this]() + { + CloseWindow(); + }); + } + else + { + CloseWindow(); + } + return false; + } + + switch( event.type ) + { + case EVENT_INTERFACE_MOD_LIST: + pl = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_LIST)); + if (pl == nullptr) break; + m_modSelectedIndex = pl->GetSelect(); + UpdateModSummary(); + UpdateModDetails(); + UpdateEnableDisableButton(); + UpdateUpDownButtons(); + break; + + case EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE: + mod = &m_modManager->GetMod(m_modSelectedIndex); + if (mod->enabled) + { + m_modManager->DisableMod(m_modSelectedIndex); + } + else + { + m_modManager->EnableMod(m_modSelectedIndex); + } + UpdateModList(); + UpdateEnableDisableButton(); + UpdateApplyButton(); + break; + + case EVENT_INTERFACE_MOD_MOVE_UP: + m_modSelectedIndex = m_modManager->MoveUp(m_modSelectedIndex); + UpdateModList(); + UpdateUpDownButtons(); + UpdateApplyButton(); + break; + + case EVENT_INTERFACE_MOD_MOVE_DOWN: + m_modSelectedIndex = m_modManager->MoveDown(m_modSelectedIndex); + UpdateModList(); + UpdateUpDownButtons(); + UpdateApplyButton(); + break; + + case EVENT_INTERFACE_MODS_REFRESH: + // Apply any changes before refresh so that the config file + // is better synchronized with the state of the game + case EVENT_INTERFACE_MODS_APPLY: + ApplyChanges(); + UpdateAll(); + // Start playing the main menu music again + if (!m_app->GetSound()->IsPlayingMusic()) + { + m_app->GetSound()->PlayMusic("music/Intro1.ogg", false); + m_app->GetSound()->CacheMusic("music/Intro2.ogg"); + } + break; + + case EVENT_INTERFACE_MODS_DIR: + if (!systemUtils->OpenPath(modDir)) + { + std::string title, text; + GetResource(RES_TEXT, RT_DIALOG_OPEN_PATH_FAILED_TITLE, title); + GetResource(RES_TEXT, RT_DIALOG_OPEN_PATH_FAILED_TEXT, text); + + // Workaround for Windows: the label skips everything after the first \\ character + std::string modDirWithoutBackSlashes = modDir; + std::replace(modDirWithoutBackSlashes.begin(), modDirWithoutBackSlashes.end(), '\\', '/'); + + m_dialog->StartInformation(title, title, StrUtils::Format(text.c_str(), modDirWithoutBackSlashes.c_str())); + } + break; + + case EVENT_INTERFACE_WORKSHOP: + if (!systemUtils->OpenWebsite(workshopUrl)) + { + std::string title, text; + GetResource(RES_TEXT, RT_DIALOG_OPEN_WEBSITE_FAILED_TITLE, title); + GetResource(RES_TEXT, RT_DIALOG_OPEN_WEBSITE_FAILED_TEXT, text); + m_dialog->StartInformation(title, title, StrUtils::Format(text.c_str(), workshopUrl.c_str())); + } + break; + + default: + return true; + } + return false; +} + +void CScreenModList::FindMods() +{ + m_modManager->FindMods(); + if (m_modManager->CountMods() != 0) + { + m_modSelectedIndex = Math::Clamp(m_modSelectedIndex, static_cast(0), m_modManager->CountMods() - 1); + } +} + +void CScreenModList::ApplyChanges() +{ + m_modManager->SaveMods(); + m_modManager->ReloadMods(); +} + +void CScreenModList::CloseWindow() +{ + m_main->ChangePhase(PHASE_MAIN_MENU); +} + +void CScreenModList::UpdateAll() +{ + UpdateModList(); + UpdateModDetails(); + UpdateModSummary(); + UpdateEnableDisableButton(); + UpdateApplyButton(); + UpdateUpDownButtons(); +} + +void CScreenModList::UpdateModList() +{ + CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return; + + CList* pl = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_LIST)); + if (pl == nullptr) return; + + pl->Flush(); + + if (m_modManager->CountMods() == 0) + { + return; + } + + const auto& mods = m_modManager->GetMods(); + for (size_t i = 0; i < mods.size(); ++i) + { + const auto& mod = mods[i]; + const auto& name = mod.data.displayName; + pl->SetItemName(i, name); + pl->SetCheck(i, mod.enabled); + pl->SetEnable(i, true); + } + + pl->SetSelect(m_modSelectedIndex); + pl->ShowSelect(false); +} + +void CScreenModList::UpdateModDetails() +{ + CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return; + + CEdit* pe = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_DETAILS)); + if (pe == nullptr) return; + + if (m_modManager->CountMods() == 0) + { + pe->SetText("No information"); + return; + } + + std::string details{}; + + const auto& mod = m_modManager->GetMod(m_modSelectedIndex); + const auto data = mod.data; + + details += "\\b;" + data.displayName + '\n'; + + std::string authorFieldName; + GetResource(RES_TEXT, RT_MOD_AUTHOR_FIELD_NAME, authorFieldName); + details += "\\s;" + authorFieldName + " "; + if (!data.author.empty()) + { + details += data.author; + } + else + { + std::string unknownAuthor; + GetResource(RES_TEXT, RT_MOD_UNKNOWN_AUTHOR, unknownAuthor); + details += unknownAuthor; + } + details += '\n'; + + details += '\n'; + + if (!data.version.empty()) + { + std::string versionFieldName; + GetResource(RES_TEXT, RT_MOD_VERSION_FIELD_NAME, versionFieldName); + details += "\\t;" + versionFieldName + '\n' + data.version + '\n'; + } + + if (!data.website.empty()) + { + std::string websiteFieldName; + GetResource(RES_TEXT, RT_MOD_WEBSITE_FIELD_NAME, websiteFieldName); + details += "\\t;" + websiteFieldName + '\n' + data.website + '\n'; + } + + std::string changesFieldName; + GetResource(RES_TEXT, RT_MOD_CHANGES_FIELD_NAME, changesFieldName); + details += "\\t;" + changesFieldName + '\n'; + if (!data.changes.empty()) + { + for (const auto& change : data.changes) + { + details += change + '\n'; + } + } + else + { + std::string noChanges; + GetResource(RES_TEXT, RT_MOD_NO_CHANGES, noChanges); + details += noChanges; + } + + pe->SetText(details); + + pe->SetFirstLine(0); +} + +void CScreenModList::UpdateModSummary() +{ + CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return; + + CEdit* pe = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_SUMMARY)); + if (pe == nullptr) return; + + std::string noSummary; + GetResource(RES_TEXT, RT_MOD_NO_SUMMARY, noSummary); + + if (m_modManager->CountMods() == 0) + { + pe->SetText(noSummary); + return; + } + + const auto& mod = m_modManager->GetMod(m_modSelectedIndex); + + if (!mod.data.summary.empty()) + { + pe->SetText(mod.data.summary); + } + else + { + pe->SetText(noSummary); + } +} + +void CScreenModList::UpdateEnableDisableButton() +{ + CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return; + + CButton* pb = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE)); + if (pb == nullptr) return; + + std::string buttonName{}; + + if (m_modManager->CountMods() == 0) + { + pb->ClearState(STATE_ENABLE); + + // Set some default name + GetResource(RES_TEXT, RT_MOD_ENABLE, buttonName); + pb->SetName(buttonName); + + return; + } + + const auto& mod = m_modManager->GetMod(m_modSelectedIndex); + + if (mod.enabled) + { + GetResource(RES_TEXT, RT_MOD_DISABLE, buttonName); + pb->SetName(buttonName); + } + else + { + GetResource(RES_TEXT, RT_MOD_ENABLE, buttonName); + pb->SetName(buttonName); + } +} + +void CScreenModList::UpdateApplyButton() +{ + CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return; + + CButton* pb = static_cast(pw->SearchControl(EVENT_INTERFACE_MODS_APPLY)); + if (pb == nullptr) return; + + if (m_modManager->Changes()) + { + pb->SetState(STATE_ENABLE); + } + else + { + pb->ClearState(STATE_ENABLE); + } +} + +void CScreenModList::UpdateUpDownButtons() +{ + CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return; + + CButton* pb_up = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_MOVE_UP)); + if (pb_up == nullptr) return; + + CButton* pb_down = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_MOVE_DOWN)); + if (pb_down == nullptr) return; + + if (m_modManager->CountMods() == 0) + { + pb_up->ClearState(STATE_ENABLE); + pb_down->ClearState(STATE_ENABLE); + return; + } + + if (m_modSelectedIndex == 0) + { + pb_up->ClearState(STATE_ENABLE); + } + else + { + pb_up->SetState(STATE_ENABLE); + } + + if (m_modSelectedIndex >= m_modManager->CountMods() - 1) + { + pb_down->ClearState(STATE_ENABLE); + } + else + { + pb_down->SetState(STATE_ENABLE); + } +} + +} // namespace Ui diff --git a/src/ui/screen/screen_mod_list.h b/src/ui/screen/screen_mod_list.h new file mode 100644 index 00000000..ca2c130b --- /dev/null +++ b/src/ui/screen/screen_mod_list.h @@ -0,0 +1,66 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#pragma once + +#include "app/modman.h" + +#include "ui/maindialog.h" + +#include "ui/screen/screen.h" + +#include + +namespace Ui +{ + +/** + * \class CScreenModList + * \brief This class is the front-end for the \ref CModManager. + */ +class CScreenModList : public CScreen +{ +public: + CScreenModList(CMainDialog* dialog, CModManager* modManager); + + void CreateInterface() override; + bool EventProcess(const Event &event) override; + +protected: + void FindMods(); + void ApplyChanges(); + void CloseWindow(); + + void UpdateAll(); + void UpdateModList(); + void UpdateModDetails(); + void UpdateModSummary(); + void UpdateEnableDisableButton(); + void UpdateApplyButton(); + void UpdateUpDownButtons(); + +protected: + Ui::CMainDialog* m_dialog; + + CModManager* m_modManager; + + size_t m_modSelectedIndex = 0; +}; + +} // namespace Ui diff --git a/test/unit/common/colobot.ini b/test/unit/common/colobot.ini index c4d21623..7cc649b5 100644 --- a/test/unit/common/colobot.ini +++ b/test/unit/common/colobot.ini @@ -6,3 +6,8 @@ string_value=Hello world [test_int] int_value=42 + +[test_array] +string_array=AAA,Hello world,Gold Edition +int_array=2,3,1 +bool_array=1,0,1 diff --git a/test/unit/common/config_file_test.cpp b/test/unit/common/config_file_test.cpp index fcd8f036..762c3307 100644 --- a/test/unit/common/config_file_test.cpp +++ b/test/unit/common/config_file_test.cpp @@ -52,3 +52,25 @@ TEST_F(CConfigFileTest, ReadTest) ASSERT_TRUE(m_configFile.GetFloatProperty("test_float", "float_value", float_value)); ASSERT_FLOAT_EQ(1.5, float_value); } + +TEST_F(CConfigFileTest, ReadArrayTest) +{ + m_configFile.SetUseCurrentDirectory(true); + + ASSERT_TRUE(m_configFile.Init()); // load colobot.ini file + + std::vector expected_string_values = { "AAA", "Hello world", "Gold Edition" }; + std::vector string_values; + ASSERT_TRUE(m_configFile.GetArrayProperty("test_array", "string_array", string_values)); + ASSERT_EQ(expected_string_values, string_values); + + std::vector expected_int_values = { 2, 3, 1 }; + std::vector int_values; + ASSERT_TRUE(m_configFile.GetArrayProperty("test_array", "int_array", int_values)); + ASSERT_EQ(expected_int_values, int_values); + + std::vector expected_bool_values = { true, false, true }; + std::vector bool_values; + ASSERT_TRUE(m_configFile.GetArrayProperty("test_array", "bool_array", bool_values)); + ASSERT_EQ(expected_bool_values, bool_values); +}