Merge remote-tracking branch 'origin/master'

This commit is contained in:
yairm210 2021-09-04 21:40:41 +03:00
commit a1fdb612a9
17 changed files with 1657 additions and 1082 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 138 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 967 KiB

After

Width:  |  Height:  |  Size: 997 KiB

View File

@ -1,6 +1,6 @@
# Tutorial tasks # Tutorial tasks
Move a unit!\nClick on a unit > Click on a destination > Click the arrow popup = Eine Einheit bewegen!\nKlicke auf eine Einheit > Klicke auf ein Ziel > Klicke auf das Pfeil-Popup. Move a unit!\nClick on a unit > Click on a destination > Click the arrow popup = Eine Einheit bewegen!\nKlicke auf eine Einheit > Klicke auf ein Ziel > Klicke auf das Pfeil-Popup.
Found a city!\nSelect the Settler (flag unit) > Click on 'Found city' (bottom-left corner) = Eine Stadt gründen!\nWähle den Siedler (Flaggensymbol) > Klicke auf 'Stadt gründen' (unten links). Found a city!\nSelect the Settler (flag unit) > Click on 'Found city' (bottom-left corner) = Eine Stadt gründen!\nWähle den Siedler (Flaggensymbol) > Klicke auf 'Stadt gründen' (unten links).
Enter the city screen!\nClick the city button twice = Öffne den Stadtbildschirm!\n Klicke zweimal den Stadtknopf. Enter the city screen!\nClick the city button twice = Öffne den Stadtbildschirm!\n Klicke zweimal den Stadtknopf.
@ -15,24 +15,24 @@ Create a trade route!\nConstruct roads between your capital and another city\nOr
Conquer a city!\nBring an enemy city down to low health > \nEnter the city with a melee unit = Erobere eine Stadt!\nBringe eine feindliche Stadt auf wenig Leben > \nBetrete die Stadt mit einer Nahkampfeinheit. Conquer a city!\nBring an enemy city down to low health > \nEnter the city with a melee unit = Erobere eine Stadt!\nBringe eine feindliche Stadt auf wenig Leben > \nBetrete die Stadt mit einer Nahkampfeinheit.
Move an air unit!\nSelect an air unit > select another city within range > \nMove the unit to the other city = Bewege eine Lufteinheit!\nWähle eine Lufteinheit > Wähle eine andere Stadt in Reichweite > \nVerschiebe die Einheit zu der anderen Stadt. Move an air unit!\nSelect an air unit > select another city within range > \nMove the unit to the other city = Bewege eine Lufteinheit!\nWähle eine Lufteinheit > Wähle eine andere Stadt in Reichweite > \nVerschiebe die Einheit zu der anderen Stadt.
See your stats breakdown!\nEnter the Overview screen (top right corner) >\nClick on 'Stats' = Schaue deine Statistiken an!\nGehe in den Übersichtsbildschirm (obere rechte Ecke) >\nKlicke auf 'Statistiken'. See your stats breakdown!\nEnter the Overview screen (top right corner) >\nClick on 'Stats' = Schaue deine Statistiken an!\nGehe in den Übersichtsbildschirm (obere rechte Ecke) >\nKlicke auf 'Statistiken'.
Oh no! It looks like something went DISASTROUSLY wrong! This is ABSOLUTELY not supposed to happen! Please send me (yairm210@hotmail.com) an email with the game information (menu -> save game -> copy game info -> paste into email) and I'll try to fix it as fast as I can! = Oh nein! Sieht aus, als wäre etwas katastrophal schief gelaufen! Das darf auf keinen Fall passieren! Bitte sende mir (yairm210@hotmail.com) eine Email mit den Spielinformationen (Menü -> Spiel speichern -> Spielinfo kopieren -> in Email einfügen) und ich werde versuchen, es so schnell wie möglich zu beheben! Oh no! It looks like something went DISASTROUSLY wrong! This is ABSOLUTELY not supposed to happen! Please send me (yairm210@hotmail.com) an email with the game information (menu -> save game -> copy game info -> paste into email) and I'll try to fix it as fast as I can! = Oh nein! Sieht aus, als wäre etwas katastrophal schief gelaufen! Das darf auf keinen Fall passieren! Bitte sende mir (yairm210@hotmail.com) eine Email mit den Spielinformationen (Menü -> Spiel speichern -> Spielinfo kopieren -> in Email einfügen) und ich werde versuchen, es so schnell wie möglich zu beheben!
Oh no! It looks like something went DISASTROUSLY wrong! This is ABSOLUTELY not supposed to happen! Please send us an report and we'll try to fix it as fast as we can! = Oh nein! Sieht aus, als wäre etwas katastrophal schief gelaufen! Das darf auf keinen Fall passieren! Bitte sende uns einen Bericht und wir werden versuchen, es so schnell wie möglich zu beheben! Oh no! It looks like something went DISASTROUSLY wrong! This is ABSOLUTELY not supposed to happen! Please send us an report and we'll try to fix it as fast as we can! = Oh nein! Sieht aus, als wäre etwas katastrophal schief gelaufen! Das darf auf keinen Fall passieren! Bitte sende uns einen Bericht und wir werden versuchen, es so schnell wie möglich zu beheben!
# Buildings # Buildings
Unsellable = Unverkäuflich Unsellable = Unverkäuflich
Not displayed as an available construction unless [building] is built = Wird nicht als verfügbares Bauwerk angezeigt, bis [building] gebaut ist Not displayed as an available construction unless [building] is built = Wird nicht als verfügbares Bauwerk angezeigt, bis [building] gebaut ist
Not displayed as an available construction without [resource] = Wird nicht als verfügbares Bauwerk angezeigt, solange [resource] fehlt Not displayed as an available construction without [resource] = Wird nicht als verfügbares Bauwerk angezeigt, solange [resource] fehlt
Choose a free great person = Wähle eine kostenlose Große Persönlichkeit Choose a free great person = Wähle eine kostenlose Große Persönlichkeit
Get [unitName] = Erhalte [unitName] Get [unitName] = Erhalte [unitName]
Hydro Plant = Wasserkraftwerk Hydro Plant = Wasserkraftwerk
[buildingName] obsoleted = [buildingName] ist nun veraltet [buildingName] obsoleted = [buildingName] ist nun veraltet
# Diplomacy,Trade,Nations # Diplomacy,Trade,Nations
Requires [buildingName] to be built in the city = Benötigt den Bau von [buildingName] in der Stadt Requires [buildingName] to be built in the city = Benötigt den Bau von [buildingName] in der Stadt
Requires [buildingName] to be built in all cities = Benötigt den Bau von [buildingName] in allen Städten Requires [buildingName] to be built in all cities = Benötigt den Bau von [buildingName] in allen Städten
Provides a free [buildingName] in the city = Stellt das Gebäude [buildingName] in der Stadt kostenlos bereit Provides a free [buildingName] in the city = Stellt das Gebäude [buildingName] in der Stadt kostenlos bereit
@ -50,12 +50,12 @@ Requires [PolicyOrNationalWonder] = Benötigt [PolicyOrNationalWonder]
Cannot be purchased = Kann nicht gekauft werden Cannot be purchased = Kann nicht gekauft werden
Can only be purchased = Kann nur gekauft werden Can only be purchased = Kann nur gekauft werden
See also = Siehe auch See also = Siehe auch
Requires at least one of the following: = Benötigt eine der folgenden Vorraussetzungen: Requires at least one of the following: = Benötigt eine der folgenden Vorraussetzungen:
Requires all of the following: = Benötigt folgende Vorraussetzungen: Requires all of the following: = Benötigt folgende Vorraussetzungen:
Leads to [techName] = [techName] kann nun erforscht werden Leads to [techName] = [techName] kann nun erforscht werden
Leads to: = Ermöglicht die Erforschung von: Leads to: = Ermöglicht die Erforschung von:
Current construction = Aktuelle Produktion Current construction = Aktuelle Produktion
Construction queue = Produktionswarteschlange Construction queue = Produktionswarteschlange
Pick a construction = Wähle ein Bauwerk Pick a construction = Wähle ein Bauwerk
@ -66,7 +66,7 @@ Show stats drilldown = Zeige Statistiken
Show construction queue = Zeige Produktionswarteschlange Show construction queue = Zeige Produktionswarteschlange
Save = Speichern Save = Speichern
Cancel = Abbrechen Cancel = Abbrechen
Diplomacy = Diplomatie Diplomacy = Diplomatie
War = Krieg War = Krieg
Peace = Frieden Peace = Frieden
@ -94,7 +94,7 @@ Indeed! = Auf jeden Fall!
Denounce [civName]? = [civName] anprangern? Denounce [civName]? = [civName] anprangern?
Denounce ([numberOfTurns] turns) = Anprangern ([numberOfTurns] Runden) Denounce ([numberOfTurns] turns) = Anprangern ([numberOfTurns] Runden)
We will remember this. = Das werden wir nie vergessen! We will remember this. = Das werden wir nie vergessen!
[civName] has declared war on [targetCivName]! = [civName] hat [targetCivName] den Krieg erklärt! [civName] has declared war on [targetCivName]! = [civName] hat [targetCivName] den Krieg erklärt!
[civName] and [targetCivName] have signed a Peace Treaty! = [civName] und [targetCivName] haben einen Friedensvertrag unterzeichnet! [civName] and [targetCivName] have signed a Peace Treaty! = [civName] und [targetCivName] haben einen Friedensvertrag unterzeichnet!
[civName] and [targetCivName] have signed the Declaration of Friendship! = [civName] und [targetCivName] haben die Freundschaftserklärung unterzeichnet! [civName] and [targetCivName] have signed the Declaration of Friendship! = [civName] und [targetCivName] haben die Freundschaftserklärung unterzeichnet!
@ -102,7 +102,7 @@ We will remember this. = Das werden wir nie vergessen!
Do you want to break your promise to [leaderName]? = Möchtest du dein Versprechen gegenüber [leaderName] brechen? Do you want to break your promise to [leaderName]? = Möchtest du dein Versprechen gegenüber [leaderName] brechen?
We promised not to settle near them ([count] turns remaining) = Wir haben versprochen, nicht in ihrer Nähe zu siedeln ([count] Runden verbleiben) We promised not to settle near them ([count] turns remaining) = Wir haben versprochen, nicht in ihrer Nähe zu siedeln ([count] Runden verbleiben)
They promised not to settle near us ([count] turns remaining) = Sie haben versprochen, nicht in unserer Nähe zu siedeln ([count] Runden verbleiben) They promised not to settle near us ([count] turns remaining) = Sie haben versprochen, nicht in unserer Nähe zu siedeln ([count] Runden verbleiben)
Unforgivable = Todfeind Unforgivable = Todfeind
Afraid = Gefürchtet Afraid = Gefürchtet
Enemy = Feind Enemy = Feind
@ -111,12 +111,12 @@ Neutral = Neutral
Favorable = Beliebt Favorable = Beliebt
Friend = Freund Friend = Freund
Ally = Verbündeter Ally = Verbündeter
[questName] (+[influenceAmount] influence) = [questName] (+[influenceAmount] Einfluss) [questName] (+[influenceAmount] influence) = [questName] (+[influenceAmount] Einfluss)
[remainingTurns] turns remaining = [remainingTurns] Runden verbleiben [remainingTurns] turns remaining = [remainingTurns] Runden verbleiben
## Diplomatic modifiers ## Diplomatic modifiers
You declared war on us! = Ihr habt uns den Krieg erklärt! You declared war on us! = Ihr habt uns den Krieg erklärt!
Your warmongering ways are unacceptable to us. = Euer kriegerisches Verhalten ist für uns inakzeptabel. Your warmongering ways are unacceptable to us. = Euer kriegerisches Verhalten ist für uns inakzeptabel.
You have captured our cities! = Ihr habt unsere Städte erobert! You have captured our cities! = Ihr habt unsere Städte erobert!
@ -139,15 +139,15 @@ Your arrogant demands are in bad taste = Eure arroganten Forderungen sind geschm
Your use of nuclear weapons is disgusting! = Euer Einsatz von Atomwaffen ist ekelhaft! Your use of nuclear weapons is disgusting! = Euer Einsatz von Atomwaffen ist ekelhaft!
You have stolen our lands! = Ihr habt unser Land geraubt! You have stolen our lands! = Ihr habt unser Land geraubt!
You gave us units! = Ihr habt uns Einheiten geschenkt! You gave us units! = Ihr habt uns Einheiten geschenkt!
Demands = Forderungen Demands = Forderungen
Please don't settle new cities near us. = Bitte gründet keine neuen Städte in unserer Nähe. Please don't settle new cities near us. = Bitte gründet keine neuen Städte in unserer Nähe.
Very well, we shall look for new lands to settle. = Nun gut, wir werden uns nach neuem Land umsehen, um es zu besiedeln. Very well, we shall look for new lands to settle. = Nun gut, wir werden uns nach neuem Land umsehen, um es zu besiedeln.
We shall do as we please. = Wir werden tun, wie es uns beliebt. We shall do as we please. = Wir werden tun, wie es uns beliebt.
We noticed your new city near our borders, despite your promise. This will have....implications. = Wir haben eure neue Stadt in der Nähe unserer Grenzen bemerkt, entgegen eures Versprechens. Dies wird....Konsequenzen haben. We noticed your new city near our borders, despite your promise. This will have....implications. = Wir haben eure neue Stadt in der Nähe unserer Grenzen bemerkt, entgegen eures Versprechens. Dies wird....Konsequenzen haben.
# City-States # City-States
Provides [amountOfCulture] culture at 30 Influence = Liefert [amountOfCulture] Kultur ab einem Einfluss von 30 Provides [amountOfCulture] culture at 30 Influence = Liefert [amountOfCulture] Kultur ab einem Einfluss von 30
Provides 3 food in capital and 1 food in other cities at 30 Influence = Liefert 3 Nahrung in die Hauptstadt und 1 Nahrung in alle anderen Städte ab einem Einfluss von 30 Provides 3 food in capital and 1 food in other cities at 30 Influence = Liefert 3 Nahrung in die Hauptstadt und 1 Nahrung in alle anderen Städte ab einem Einfluss von 30
Provides 3 happiness at 30 Influence = Liefert 3 Zufriedenheit ab einem Einfluss von 30 Provides 3 happiness at 30 Influence = Liefert 3 Zufriedenheit ab einem Einfluss von 30
@ -203,7 +203,7 @@ Take worker (-50 Influence) = Arbeiter nehmen (-50 Einfluss)
[civName] is afraid of your military power! = [civName] fürchtet sich vor deiner militärischen Macht! [civName] is afraid of your military power! = [civName] fürchtet sich vor deiner militärischen Macht!
# Trades # Trades
Trade = Handel Trade = Handel
Offer trade = Handel anbieten Offer trade = Handel anbieten
@ -231,17 +231,17 @@ Declare war on [nation] = [nation] den Krieg erklären
Luxury resources = Luxusressourcen Luxury resources = Luxusressourcen
Strategic resources = Strategische Ressourcen Strategic resources = Strategische Ressourcen
Owned: [amountOwned] = Im Besitz: [amountOwned] Owned: [amountOwned] = Im Besitz: [amountOwned]
# Nation picker # Nation picker
[resourceName] not required = [resourceName] nicht erforderlich [resourceName] not required = [resourceName] nicht erforderlich
Lost ability = Verlorene Fähigkeit Lost ability = Verlorene Fähigkeit
National ability = Nationalfähigkeit National ability = Nationalfähigkeit
[firstValue] vs [secondValue] = [firstValue] anstatt [secondValue] [firstValue] vs [secondValue] = [firstValue] anstatt [secondValue]
# New game screen # New game screen
Uniques = Unikate Uniques = Unikate
Promotions = Beförderungen Promotions = Beförderungen
Load copied data = Aus Zwischenablage laden Load copied data = Aus Zwischenablage laden
@ -305,9 +305,9 @@ World wrap requires a minimum width of 32 tiles = 'World Wrap' Karten müssen mi
The provided map dimensions were too small = Die angegebenen Dimensionen waren zu klein The provided map dimensions were too small = Die angegebenen Dimensionen waren zu klein
The provided map dimensions were too big = Die angegebenen Dimensionen waren zu groß The provided map dimensions were too big = Die angegebenen Dimensionen waren zu groß
The provided map dimensions had an unacceptable aspect ratio = Die angegebenen Dimensionen hatten ein zu extremes Seitenverhältnis The provided map dimensions had an unacceptable aspect ratio = Die angegebenen Dimensionen hatten ein zu extremes Seitenverhältnis
Difficulty = Schwierigkeitsgrad Difficulty = Schwierigkeitsgrad
AI = KI AI = KI
Remove = Entfernen Remove = Entfernen
Random = Zufall Random = Zufall
@ -315,14 +315,14 @@ Human = Mensch
Hotseat = Schleudersitz Hotseat = Schleudersitz
User ID = Spieler-ID User ID = Spieler-ID
Click to copy = Anklicken zum Kopieren Click to copy = Anklicken zum Kopieren
Game Speed = Spielgeschwindigkeit Game Speed = Spielgeschwindigkeit
Quick = Schnell Quick = Schnell
Standard = Standard Standard = Standard
Epic = Episch Epic = Episch
Marathon = Marathon Marathon = Marathon
Starting Era = Startzeitalter Starting Era = Startzeitalter
It looks like we can't make a map with the parameters you requested! = Mit den von dir angegebenen Parametern kann keine Karte erzeugt werden! It looks like we can't make a map with the parameters you requested! = Mit den von dir angegebenen Parametern kann keine Karte erzeugt werden!
Maybe you put too many players into too small a map? = Vielleicht hast du zu viele Spieler in eine zu kleine Karte gepackt? Maybe you put too many players into too small a map? = Vielleicht hast du zu viele Spieler in eine zu kleine Karte gepackt?
@ -339,14 +339,14 @@ Base Ruleset = Basisregelsatz
[amount] Improvements = [amount] Feldverbesserungen [amount] Improvements = [amount] Feldverbesserungen
[amount] Religions = [amount] Religionen [amount] Religions = [amount] Religionen
[amount] Beliefs = [amount] Glaubenssätze [amount] Beliefs = [amount] Glaubenssätze
World Wrap = World Wrap World Wrap = World Wrap
World wrap maps are very memory intensive - creating large world wrap maps on Android can lead to crashes! = 'World Wrap' Karten verbrauchen sehr viel Speicher - Das erstellen von großen 'World Wrap' Karten kann bei Android zu einem Absturz führen! World wrap maps are very memory intensive - creating large world wrap maps on Android can lead to crashes! = 'World Wrap' Karten verbrauchen sehr viel Speicher - Das erstellen von großen 'World Wrap' Karten kann bei Android zu einem Absturz führen!
Anything above 80 by 50 may work very slowly on Android! = Auf Android kann alles über 80 mal 50 sehr langsam sein. Anything above 80 by 50 may work very slowly on Android! = Auf Android kann alles über 80 mal 50 sehr langsam sein.
Anything above 40 may work very slowly on Android! = Auf Android kann alles über 40 sehr langsam sein. Anything above 40 may work very slowly on Android! = Auf Android kann alles über 40 sehr langsam sein.
# Multiplayer # Multiplayer
Username = Spielername Username = Spielername
Multiplayer = Mehrspieler Multiplayer = Mehrspieler
Could not download game! = Konnte das Spiel nicht herunterladen! Could not download game! = Konnte das Spiel nicht herunterladen!
@ -382,9 +382,9 @@ Resign = Aufgeben
Are you sure you want to resign? = Willst du wirklich aufgeben? Are you sure you want to resign? = Willst du wirklich aufgeben?
You can only resign if it's your turn = Du kannst nur aufgeben, wenn du am Zug bist You can only resign if it's your turn = Du kannst nur aufgeben, wenn du am Zug bist
[civName] resigned and is now controlled by AI = [civName] hat aufgegeben und wird nun von der KI gespielt [civName] resigned and is now controlled by AI = [civName] hat aufgegeben und wird nun von der KI gespielt
# Save game menu # Save game menu
Current saves = Gespeicherte Spiele Current saves = Gespeicherte Spiele
Show autosaves = Zeige automatisch gespeicherte Spiele an Show autosaves = Zeige automatisch gespeicherte Spiele an
Saved game name = Name des gespeicherten Spiels Saved game name = Name des gespeicherten Spiels
@ -411,13 +411,21 @@ Load from custom location = Laden von externem Speicherort
Could not load game from custom location! = Laden von externem Speicherort fehlgeschlagen! Could not load game from custom location! = Laden von externem Speicherort fehlgeschlagen!
Save to custom location = Speichern in externem Speicherort Save to custom location = Speichern in externem Speicherort
Could not save game to custom location! = Speichern in externem Speicherort fehlgeschlagen! Could not save game to custom location! = Speichern in externem Speicherort fehlgeschlagen!
# Options # Options
Options = Optionen Options = Optionen
Display options = Anzeigeeinstellungen About = Über
Gameplay options = Spielmechanikeinstellungen Display = Anzeige
Other options = Andere Einstellungen Gameplay = Spielmechanik
Sound = Sound
Multiplayer = Mehrspieler
Advanced = Erweitert
Locate mod errors = Mod-Probleme
Debug = Nur für Eingeweihte
See online Readme = Readme online öffnen
Visit repository = Repository der Entwickler besuchen
Turns between autosaves = Runden bis zum nächsten automatischen Speichern Turns between autosaves = Runden bis zum nächsten automatischen Speichern
Sound effects volume = Lautstärke Soundeffekte Sound effects volume = Lautstärke Soundeffekte
Music volume = Lautstärke Musik Music volume = Lautstärke Musik
@ -444,16 +452,18 @@ Show tile yields = Felderträge anzeigen
Continuous rendering = Kontinuierliches Rendern Continuous rendering = Kontinuierliches Rendern
When disabled, saves battery life but certain animations will be suspended = Es spart Akku, wenn es deaktiviert ist, aber bestimmte Animationen werden nicht angezeigt. When disabled, saves battery life but certain animations will be suspended = Es spart Akku, wenn es deaktiviert ist, aber bestimmte Animationen werden nicht angezeigt.
Order trade offers by amount = Handelsangebote nach Menge sortieren Order trade offers by amount = Handelsangebote nach Menge sortieren
Check extension mods based on vanilla = Erweiterungs-Mods mit Vanilla-Regelsatz prüfen
Checking mods for errors... = Mods werden geprüft...
Show experimental world wrap for maps = 'World Wrap'-Option für neue Karten anbieten Show experimental world wrap for maps = 'World Wrap'-Option für neue Karten anbieten
HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED! = WARNUNG: HOCHGRADIG EXPERIMENTELL - DU WURDEST GEWARNT! HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED! = WARNUNG: HOCHGRADIG EXPERIMENTELL - DU WURDEST GEWARNT!
HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES! = WARNUNG: HOCHGRADIG EXPERIMENTELL - UPDATES WERDEN SPEICHERSTÄNDE ZERSTÖREN! HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES! = WARNUNG: HOCHGRADIG EXPERIMENTELL - UPDATES WERDEN SPEICHERSTÄNDE ZERSTÖREN!
Enable portrait orientation = Hochkant-Orientierung zulassen Enable portrait orientation = Hochkant-Orientierung zulassen
Generate translation files = Erstelle Übersetzungsdateien Generate translation files = Erstelle Übersetzungsdateien
Translation files are generated successfully. = Die Übersetzungsdateien wurden erfolgreich erstellt. Translation files are generated successfully. = Die Übersetzungsdateien wurden erfolgreich erstellt.
Locate mod errors = Mod-Fehler lokalisieren Please note that translations are a community-based work in progress and are INCOMPLETE! The percentage shown is how much of the language is translated in-game. If you want to help translating the game into your language, click here. = Bitte beachte, daß die Übersetzungen eine andauernde Leistung einer Gemeinschaft von Freiwilligen sind und damit oft unvollständig. Die angezeigte Prozentzahl bedeutet den Anteil übersetzter Texte im gesamten Spiel. Wenn Du helfen willst, die Übersetzungen zu verbessern - dies ist ein Link zur Anleitung.
# Notifications # Notifications
Research of [technologyName] has completed! = [technologyName] wurde erforscht! Research of [technologyName] has completed! = [technologyName] wurde erforscht!
[construction] has become obsolete and was removed from the queue in [cityName]! = [construction] ist veraltet und wurde in [cityName] aus der Warteschlange entfernt! [construction] has become obsolete and was removed from the queue in [cityName]! = [construction] ist veraltet und wurde in [cityName] aus der Warteschlange entfernt!
[construction] has become obsolete and was removed from the queue in [amount] cities! = [construction] ist veraltet und wurde in [amount] Städten aus der Warteschlange entfernt! [construction] has become obsolete and was removed from the queue in [amount] cities! = [construction] ist veraltet und wurde in [amount] Städten aus der Warteschlange entfernt!
@ -466,7 +476,7 @@ You have entered a Golden Age! = Ein Goldenes Zeitalter hat begonnen!
A [greatPerson] has been born in [cityName]! = [cityName] - Ein [greatPerson] wurde geboren! A [greatPerson] has been born in [cityName]! = [cityName] - Ein [greatPerson] wurde geboren!
We have encountered [civName]! = Wir sind auf [civName] getroffen! We have encountered [civName]! = Wir sind auf [civName] getroffen!
[cityStateName] has given us [stats] as a token of goodwill for meeting us = [cityStateName] hat uns [stats] als Zeichen des guten Willens für unsere Begegnung übergeben [cityStateName] has given us [stats] as a token of goodwill for meeting us = [cityStateName] hat uns [stats] als Zeichen des guten Willens für unsere Begegnung übergeben
[cityStateName] has given us [stats] as we are the first major civ to meet them = [cityStateName] hat uns [stats] übergeben, da wir die erste bedeutende Zivilisation sind, die er getroffen hat [cityStateName] has given us [stats] as we are the first major civ to meet them = [cityStateName] hat uns [stats] übergeben, da wir die erste bedeutende Zivilisation sind, die sie getroffen haben
Cannot provide unit upkeep for [unitName] - unit has been disbanded! = Der Unterhalt für [unitName] konnte nicht bezahlt werden - Einheit wurde aufgelöst! Cannot provide unit upkeep for [unitName] - unit has been disbanded! = Der Unterhalt für [unitName] konnte nicht bezahlt werden - Einheit wurde aufgelöst!
[cityName] has grown! = [cityName] ist gewachsen! [cityName] has grown! = [cityName] ist gewachsen!
[cityName] is starving! = [cityName] verhungert! [cityName] is starving! = [cityName] verhungert!
@ -563,8 +573,8 @@ Your city [cityName] was converted to [religionName]! = Deine Stadt [cityName] k
Your [unitName] lost its faith after spending too long inside enemy territory! = Deine [unitName] Einheit hat ihren Glauben verloren, nachdem sie zu lange in feindlichem Gebiet war! Your [unitName] lost its faith after spending too long inside enemy territory! = Deine [unitName] Einheit hat ihren Glauben verloren, nachdem sie zu lange in feindlichem Gebiet war!
# World Screen UI # World Screen UI
Working... = Bitte warten... Working... = Bitte warten...
Waiting for other players... = Warte auf andere Spieler... Waiting for other players... = Warte auf andere Spieler...
in = in in = in
@ -617,7 +627,7 @@ Yes = Ja
No = Nein No = Nein
Acquire = Übernehmen Acquire = Übernehmen
Under construction = Im Bau Under construction = Im Bau
Food = Nahrung Food = Nahrung
Production = Produktion Production = Produktion
Gold = Gold Gold = Gold
@ -625,7 +635,7 @@ Happiness = Zufriedenheit
Culture = Kultur Culture = Kultur
Science = Wissenschaft Science = Wissenschaft
Faith = Glaube Faith = Glaube
Crop Yield = Ernteertrag Crop Yield = Ernteertrag
Territory = Territorium Territory = Territorium
Force = Kampfkraft Force = Kampfkraft
@ -634,7 +644,7 @@ Golden Age = Goldenes Zeitalter
[year] BC = [year] v. Chr. [year] BC = [year] v. Chr.
[year] AD = [year] n. Chr. [year] AD = [year] n. Chr.
Civilopedia = Civilopedia Civilopedia = Civilopedia
Start new game = Neues Spiel Start new game = Neues Spiel
Save game = Spiel speichern Save game = Spiel speichern
Load game = Spiel laden Load game = Spiel laden
@ -650,9 +660,9 @@ Close = Schließen
Do you want to exit the game? = Willst du das Spiel beenden? Do you want to exit the game? = Willst du das Spiel beenden?
Start bias: = Start-Präferenz: Start bias: = Start-Präferenz:
Avoid [terrain] = Meide [terrain] Avoid [terrain] = Meide [terrain]
# City screen # City screen
Exit city = Stadt verlassen Exit city = Stadt verlassen
Raze city = Stadt niederreißen Raze city = Stadt niederreißen
Stop razing city = Niederreißen der Stadt stoppen Stop razing city = Niederreißen der Stadt stoppen
@ -704,9 +714,9 @@ Move to city = Zur Stadt bewegen
Invalid input! Please enter a different string. = Invalid input! Please enter a different string. =
# Requires translation! # Requires translation!
Please enter some text = Please enter some text =
# Technology UI # Technology UI
Pick a tech = Technologie auswählen Pick a tech = Technologie auswählen
Pick a free tech = Kostenlose Technologie auswählen Pick a free tech = Kostenlose Technologie auswählen
Research [technology] = [technology] erforschen Research [technology] = [technology] erforschen
@ -729,9 +739,9 @@ Attack = Angreifen
Bombard = Bombardieren Bombard = Bombardieren
NUKE = Atomisieren NUKE = Atomisieren
Captured! = Gefangen! Captured! = Gefangen!
# Battle modifier categories # Battle modifier categories
defence vs ranged = Verteidigung gegen Fernkampf defence vs ranged = Verteidigung gegen Fernkampf
[percentage] to unit defence = [percentage] erhöhte Verteidigungsstärke [percentage] to unit defence = [percentage] erhöhte Verteidigungsstärke
Attacker Bonus = Angriffsbonus Attacker Bonus = Angriffsbonus
@ -753,11 +763,11 @@ defence vs [unitType] = Verteidigung gegen [unitType]
[tileFilter] defence = [tileFilter] Verteidigung [tileFilter] defence = [tileFilter] Verteidigung
Defensive Bonus = Verteidigungsbonus Defensive Bonus = Verteidigungsbonus
Stacked with [unitType] = Auf gleichem Feld mit [unitType] Stacked with [unitType] = Auf gleichem Feld mit [unitType]
The following improvements [stats]: = Die folgenden Verbesserungen [stats]: The following improvements [stats]: = Die folgenden Verbesserungen [stats]:
The following improvements on [tileType] tiles [stats]: = Die folgenden Verbesserungen auf [tileType] Feldern [stats]: The following improvements on [tileType] tiles [stats]: = Die folgenden Verbesserungen auf [tileType] Feldern [stats]:
Hurry Research = Forschung beschleunigen Hurry Research = Forschung beschleunigen
Conduct Trade Mission = Handelsmission durchführen Conduct Trade Mission = Handelsmission durchführen
Your trade mission to [civName] has earned you [goldAmount] gold and [influenceAmount] influence! = Deine Handelsmission zu [civName] hat dir [goldAmount] Gold und [influenceAmount] Einfluss eingebracht! Your trade mission to [civName] has earned you [goldAmount] gold and [influenceAmount] influence! = Deine Handelsmission zu [civName] hat dir [goldAmount] Gold und [influenceAmount] Einfluss eingebracht!
@ -780,9 +790,9 @@ Policies = Politiken
Base happiness = Grundzufriedenheit Base happiness = Grundzufriedenheit
Occupied City = Besetzte Städte Occupied City = Besetzte Städte
Buildings = Gebäude Buildings = Gebäude
# terrainFilters (so for uniques like: "[stats] from [terrainFilter] tiles") # terrainFilters (so for uniques like: "[stats] from [terrainFilter] tiles")
All = Alle All = Alle
Water = Wasser Water = Wasser
Land = Land Land = Land
@ -800,14 +810,14 @@ Strategic resource = Strategische Ressource
Fresh water = Frischwasser Fresh water = Frischwasser
non-fresh water = nicht frisches Wasser non-fresh water = nicht frisches Wasser
Natural Wonder = Naturwunder Natural Wonder = Naturwunder
# improvementFilters # improvementFilters
All Road = Alle Straßen All Road = Alle Straßen
Great Improvement = Große Verbesserung Great Improvement = Große Verbesserung
Great = Große Great = Große
Wonders = Wunder Wonders = Wunder
Base values = Grundwerte Base values = Grundwerte
Bonuses = Boni Bonuses = Boni
@ -833,9 +843,9 @@ Known and defeated ([numberOfCivs]) = Bekannt und besiegt ([numberOfCivs])
Tiles = Felder Tiles = Felder
Natural Wonders = Naturwunder Natural Wonders = Naturwunder
Treasury deficit = Schatzkammerdefizit Treasury deficit = Schatzkammerdefizit
# Victory # Victory
Science victory = Wissenschaftssieg Science victory = Wissenschaftssieg
Cultural victory = Kultursieg Cultural victory = Kultursieg
Conquest victory = Dominanzsieg Conquest victory = Dominanzsieg
@ -874,9 +884,9 @@ Vote for [civilizationName] = Abstimmen für [civilizationName]
Continue = Fortfahren Continue = Fortfahren
Abstained = Enthalten Abstained = Enthalten
Vote for World Leader = Stimme für den Anführer der Welt ab Vote for World Leader = Stimme für den Anführer der Welt ab
# Capturing a city # Capturing a city
What would you like to do with the city? = Was möchtet Ihr mit dieser Stadt machen? What would you like to do with the city? = Was möchtet Ihr mit dieser Stadt machen?
Annex = Annektieren Annex = Annektieren
Annexed cities become part of your regular empire. = Annektierte Städte werden Teil Eures Reichs Annexed cities become part of your regular empire. = Annektierte Städte werden Teil Eures Reichs
@ -896,14 +906,14 @@ Destroying the city instantly razes the city to the ground. = Zerstören macht d
Remove your troops in our border immediately! = Entferne sofort deine Truppen aus unserem Gebiet! Remove your troops in our border immediately! = Entferne sofort deine Truppen aus unserem Gebiet!
Sorry. = Entschuldigung. Sorry. = Entschuldigung.
Never! = Niemals! Never! = Niemals!
Offer Declaration of Friendship ([30] turns) = Freundschaftserklärung anbieten ([30] Runden) Offer Declaration of Friendship ([30] turns) = Freundschaftserklärung anbieten ([30] Runden)
My friend, shall we declare our friendship to the world? = Mein Freund, sollen wir unsere Freundschaft der Welt kundtun? My friend, shall we declare our friendship to the world? = Mein Freund, sollen wir unsere Freundschaft der Welt kundtun?
Sign Declaration of Friendship ([30] turns) = Freundschaftserklärung unterzeichnen ([30] Runden) Sign Declaration of Friendship ([30] turns) = Freundschaftserklärung unterzeichnen ([30] Runden)
We are not interested. = Wir sind nicht interessiert. We are not interested. = Wir sind nicht interessiert.
We have signed a Declaration of Friendship with [otherCiv]! = Wir haben eine Freundschaftserklärung mit [otherCiv] unterzeichnet! We have signed a Declaration of Friendship with [otherCiv]! = Wir haben eine Freundschaftserklärung mit [otherCiv] unterzeichnet!
[otherCiv] has denied our Declaration of Friendship! = [otherCiv] hat unsere Freundschaftserklärung abgelehnt! [otherCiv] has denied our Declaration of Friendship! = [otherCiv] hat unsere Freundschaftserklärung abgelehnt!
Basics = Spielkonzepte Basics = Spielkonzepte
Resources = Ressourcen Resources = Ressourcen
Terrains = Gelände Terrains = Gelände
@ -971,7 +981,7 @@ Terrain feature [feature] does not exist in ruleset! = Geländemerkmal [feature]
Resource [resource] does not exist in ruleset! = Ressource [resource] fehlt im Regelsatz! Resource [resource] does not exist in ruleset! = Ressource [resource] fehlt im Regelsatz!
Improvement [improvement] does not exist in ruleset! = Verbesserung [improvement] fehlt im Regelsatz! Improvement [improvement] does not exist in ruleset! = Verbesserung [improvement] fehlt im Regelsatz!
Change map to fit selected ruleset? = Karte ändern, um sie dem neuen Regelsatz anzupassen? Change map to fit selected ruleset? = Karte ändern, um sie dem neuen Regelsatz anzupassen?
# Civilopedia difficulty levels # Civilopedia difficulty levels
Player settings = Spieler-Einstellungen Player settings = Spieler-Einstellungen
Base Happiness = Basiszufriedenheit Base Happiness = Basiszufriedenheit
@ -997,7 +1007,7 @@ Major AI civilization bonus starting units = Haupt-KI Zivilisationsbonus Startei
City state bonus starting units = Stadtstaaten Bonus Starteinheiten City state bonus starting units = Stadtstaaten Bonus Starteinheiten
Turns until barbarians enter player tiles = Züge bis Barbaren Spielerfelder betreten Turns until barbarians enter player tiles = Züge bis Barbaren Spielerfelder betreten
Gold reward for clearing barbarian camps = Gold-Belohnung für das Räumen von Barbarenlagern Gold reward for clearing barbarian camps = Gold-Belohnung für das Räumen von Barbarenlagern
# Other civilopedia things # Other civilopedia things
Nations = Nationen Nations = Nationen
Available for [unitTypes] = Verfügbar für [unitTypes] Available for [unitTypes] = Verfügbar für [unitTypes]
@ -1010,9 +1020,9 @@ Granted by [param] = Von [param] erteilt
Granted by: = Erteilt von: Granted by: = Erteilt von:
[bonus] with [tech] = [bonus] mit [tech] [bonus] with [tech] = [bonus] mit [tech]
Difficulty levels = Schwierigkeitsgrade Difficulty levels = Schwierigkeitsgrade
# Policies # Policies
Adopt policy = Politik verabschieden Adopt policy = Politik verabschieden
Adopt free policy = Freie Politik verabschieden Adopt free policy = Freie Politik verabschieden
Unlocked at = Freigeschaltet bei Unlocked at = Freigeschaltet bei
@ -1048,12 +1058,12 @@ Cities following this religion: = Städte die dieser Religion folgen
Click an icon to see the stats of this religion = Klicke auf ein Icon, um die Statistiken dieser Religion anzuzeigen Click an icon to see the stats of this religion = Klicke auf ein Icon, um die Statistiken dieser Religion anzuzeigen
# Terrains # Terrains
Impassable = Unpassierbar Impassable = Unpassierbar
Rare feature = Seltene Geländeform Rare feature = Seltene Geländeform
# Resources # Resources
Bison = Bisons Bison = Bisons
Copper = Kupfer Copper = Kupfer
Cocoa = Kakao Cocoa = Kakao
@ -1063,9 +1073,9 @@ Truffles = Trüffel
Strategic = Strategisch Strategic = Strategisch
Bonus = Bonus Bonus = Bonus
Luxury = Luxus Luxury = Luxus
# Unit types # Unit types
City = Stadt City = Stadt
Civilian = Zivilist Civilian = Zivilist
Melee = Nahkampf Melee = Nahkampf
@ -1074,21 +1084,21 @@ Scout = Späher
Mounted = Beritten Mounted = Beritten
Armor = Panzerung Armor = Panzerung
Siege = Belagerung Siege = Belagerung
WaterCivilian = Wasser-Zivilist WaterCivilian = Wasser-Zivilist
WaterMelee = Wassernahkampf WaterMelee = Wassernahkampf
WaterRanged = Wasserfernkampf WaterRanged = Wasserfernkampf
WaterSubmarine = U-Boote WaterSubmarine = U-Boote
WaterAircraftCarrier = Flugzeugträger WaterAircraftCarrier = Flugzeugträger
Fighter = Jagdflugzeug Fighter = Jagdflugzeug
Bomber = Bomber Bomber = Bomber
AtomicBomber = Atombomber AtomicBomber = Atombomber
Missile = Rakete Missile = Rakete
# Unit filters and other unit related things # Unit filters and other unit related things
Air = Luft Air = Luft
air units = Lufteinheiten air units = Lufteinheiten
Barbarian = Barbar Barbarian = Barbar
@ -1106,13 +1116,13 @@ Unbuildable = nicht baubar
water units = Wassereinheiten water units = Wassereinheiten
wounded units = verwundete Einheiten wounded units = verwundete Einheiten
Wounded = Verwundet Wounded = Verwundet
# For the All "newly-trained [relevant] units in this city receive the [] promotion" translation. Relevant as in 'units that can receive' # For the All "newly-trained [relevant] units in this city receive the [] promotion" translation. Relevant as in 'units that can receive'
relevant = relevante relevant = relevante
# Promotions # Promotions
Pick promotion = Wähle eine Beförderung Pick promotion = Wähle eine Beförderung
OR = ODER OR = ODER
units in open terrain = Einheiten im offenen Gelände units in open terrain = Einheiten im offenen Gelände
@ -1125,10 +1135,9 @@ Dogfighting II = Kurvenkampf II
Dogfighting III = Kurvenkampf III Dogfighting III = Kurvenkampf III
Choose name for [unitName] = Wähle Namen für [unitName] Choose name for [unitName] = Wähle Namen für [unitName]
[unitFilter] units gain the [promotion] promotion = [unitFilter] Einheiten erhalten die [promotion] Beförderung [unitFilter] units gain the [promotion] promotion = [unitFilter] Einheiten erhalten die [promotion] Beförderung
# Multiplayer Turn Checker Service # Multiplayer Turn Checker Service
Multiplayer options = Mehrspieler Einstellungen
Enable out-of-game turn notifications = Aktiviere Zug Benachrichtigungen außerhalb des Spiels Enable out-of-game turn notifications = Aktiviere Zug Benachrichtigungen außerhalb des Spiels
Time between turn checks out-of-game (in minutes) = Intervall zwischen Zug Prüfungen (in Minuten) Time between turn checks out-of-game (in minutes) = Intervall zwischen Zug Prüfungen (in Minuten)
Show persistent notification for turn notifier service = Zeige dauerhafte Benachrichtigung für den Zug-Benachrichtungsdienst Show persistent notification for turn notifier service = Zeige dauerhafte Benachrichtigung für den Zug-Benachrichtungsdienst
@ -1136,10 +1145,10 @@ Take user ID from clipboard = Spieler-ID aus der Zwischenablage übernehmen
Doing this will reset your current user ID to the clipboard contents - are you sure? = Dies wird deine Spieler-ID auf den Inhalt der Zwischenablage zurücksetzen - bist du sicher? Doing this will reset your current user ID to the clipboard contents - are you sure? = Dies wird deine Spieler-ID auf den Inhalt der Zwischenablage zurücksetzen - bist du sicher?
ID successfully set! = Spieler-ID erfolgreich gesetzt! ID successfully set! = Spieler-ID erfolgreich gesetzt!
Invalid ID! = Ungültige Spieler-ID! Invalid ID! = Ungültige Spieler-ID!
# Mods # Mods
Mods = Modifikationen Mods = Modifikationen
Download [modName] = [modName] herunterladen Download [modName] = [modName] herunterladen
Update [modName] = [modName] aktualisieren Update [modName] = [modName] aktualisieren
@ -1167,9 +1176,9 @@ No description provided = Keine Beschreibung mitgeliefert
Author: [author] = Autor: [author] Author: [author] = Autor: [author]
Size: [size] kB = Größe: [size] kB Size: [size] kB = Größe: [size] kB
The mod you selected is incompatible with the defined ruleset! = Die gewählte Modifikation ist inkompatibel! The mod you selected is incompatible with the defined ruleset! = Die gewählte Modifikation ist inkompatibel!
# Uniques that are relevant to more than one type of game object # Uniques that are relevant to more than one type of game object
[stats] from every [param] = Alle [param] geben [stats] [stats] from every [param] = Alle [param] geben [stats]
[stats] from [param] tiles in this city = [stats] von [param] Feld in dieser Stadt [stats] from [param] tiles in this city = [stats] von [param] Feld in dieser Stadt
[stats] from every [param] on [tileFilter] tiles = [stats] von jedem [param] auf [tileFilter] Feldern [stats] from every [param] on [tileFilter] tiles = [stats] von jedem [param] auf [tileFilter] Feldern
@ -1183,7 +1192,7 @@ Can only be built on [tileFilter] tiles = Kann nur auf [tileFilter]-Feldern geba
Cannot be built on [tileFilter] tiles = Kann nicht auf [tileFilter]-Feldern gebaut werden Cannot be built on [tileFilter] tiles = Kann nicht auf [tileFilter]-Feldern gebaut werden
Does not need removal of [feature] = Hierfür muß [feature] nicht entfernt werden Does not need removal of [feature] = Hierfür muß [feature] nicht entfernt werden
Gain a free [building] [cityFilter] = Erhalte [building] umsonst [cityFilter] Gain a free [building] [cityFilter] = Erhalte [building] umsonst [cityFilter]
# City filters # City filters
in this city = in dieser Stadt in this city = in dieser Stadt
in all cities = in allen Städten in all cities = in allen Städten

View File

@ -420,9 +420,16 @@ Could not save game to custom location! =
# Options # Options
Options = Options =
Display options = About =
Gameplay options = Display =
Other options = Gameplay =
Sound =
Advanced =
Multiplayer =
Locate mod errors =
Debug =
See online Readme =
Turns between autosaves = Turns between autosaves =
Sound effects volume = Sound effects volume =
Music volume = Music volume =
@ -444,18 +451,19 @@ off =
Show pixel units = Show pixel units =
Show pixel improvements = Show pixel improvements =
Enable nuclear weapons = Enable nuclear weapons =
Fontset =
Show tile yields = Show tile yields =
Continuous rendering = Continuous rendering =
When disabled, saves battery life but certain animations will be suspended = When disabled, saves battery life but certain animations will be suspended =
Order trade offers by amount = Order trade offers by amount =
Check extension mods based on vanilla =
Checking mods for errors... =
Show experimental world wrap for maps = Show experimental world wrap for maps =
HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED! = HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED! =
HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES! = HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES! =
Enable portrait orientation = Enable portrait orientation =
Generate translation files = Generate translation files =
Translation files are generated successfully. = Translation files are generated successfully. =
Locate mod errors = Please note that translations are a community-based work in progress and are INCOMPLETE! The percentage shown is how much of the language is translated in-game. If you want to help translating the game into your language, click here. =
# Notifications # Notifications
@ -1135,7 +1143,6 @@ Choose name for [unitName] =
# Multiplayer Turn Checker Service # Multiplayer Turn Checker Service
Multiplayer options =
Enable out-of-game turn notifications = Enable out-of-game turn notifications =
Time between turn checks out-of-game (in minutes) = Time between turn checks out-of-game (in minutes) =
Show persistent notification for turn notifier service = Show persistent notification for turn notifier service =

View File

@ -43,7 +43,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
*/ */
var viewEntireMapForDebug = false var viewEntireMapForDebug = false
/** For when you need to test something in an advanced game and don't have time to faff around */ /** For when you need to test something in an advanced game and don't have time to faff around */
val superchargedForDebug = false var superchargedForDebug = false
/** Simulate until this turn on the first "Next turn" button press. /** Simulate until this turn on the first "Next turn" button press.
* Does not update World View changes until finished. * Does not update World View changes until finished.

View File

@ -367,8 +367,8 @@ class TileMap {
} }
} else { } else {
// Yes the map generator calls this repeatedly, and we don't want to end up with an oversized tileMatrix // Yes the map generator calls this repeatedly, and we don't want to end up with an oversized tileMatrix
// rightX is -leftX or -leftX + 1 // rightX is -leftX or -leftX + 1 or -leftX + 2
if (tileMatrix.size != 1 - 2 * leftX && tileMatrix.size != 2 - 2 * leftX) if (tileMatrix.size !in (1 - 2 * leftX)..(3 - 2 * leftX))
throw(IllegalStateException("TileMap.setTransients called on existing tileMatrix of different size")) throw(IllegalStateException("TileMap.setTransients called on existing tileMatrix of different size"))
} }

View File

@ -1,69 +1,31 @@
package com.unciv.ui package com.unciv.ui
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.MainMenuScreen import com.unciv.MainMenuScreen
import com.unciv.UncivGame
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.pickerscreens.PickerScreen
import com.unciv.ui.utils.* import com.unciv.ui.utils.enable
import com.unciv.ui.utils.onClick
import com.unciv.ui.utils.LanguageTable
import com.unciv.ui.utils.LanguageTable.Companion.addLanguageTables
import com.unciv.ui.worldscreen.mainmenu.OptionsPopup
/** A [PickerScreen] to select a language, used once on the initial run after a fresh install.
class LanguageTable(val language:String, val percentComplete: Int):Table(){ * After that, [OptionsPopup] provides the functionality.
private val blue = ImageGetter.getBlue() * Reusable code is in [LanguageTable] and [addLanguageTables].
private val darkBlue = blue.cpy().lerp(Color.BLACK,0.5f)!! */
class LanguagePickerScreen : PickerScreen() {
init{
pad(10f)
defaults().pad(10f)
left()
if(ImageGetter.imageExists("FlagIcons/$language"))
add(ImageGetter.getImage("FlagIcons/$language")).size(40f)
val spaceSplitLang = language.replace("_"," ")
add("$spaceSplitLang ($percentComplete%)".toLabel())
update("")
touchable = Touchable.enabled // so click listener is activated when any part is clicked, not only children
pack()
}
fun update(chosenLanguage:String){
background = ImageGetter.getBackground( if(chosenLanguage==language) blue else darkBlue)
}
}
class LanguagePickerScreen : PickerScreen(){
var chosenLanguage = "English" var chosenLanguage = "English"
private val languageTables = ArrayList<LanguageTable>() private val languageTables: ArrayList<LanguageTable>
fun update(){ fun update() {
languageTables.forEach { it.update(chosenLanguage) } languageTables.forEach { it.update(chosenLanguage) }
} }
init { init {
closeButton.isVisible = false closeButton.isVisible = false
/// trimMargin is overhead, but easier to maintain and see when it might get trimmed without wrap:
val translationDisclaimer = """
|Please note that translations are a community-based work in progress and are INCOMPLETE!
|The percentage shown is how much of the language is translated in-game.
|If you want to help translating the game into your language,
| instructions are in the Github readme! (Menu > Community > Github)
""".trimMargin()
topTable.add(translationDisclaimer.toLabel()).pad(10f).row()
val tableLanguages = Table()
tableLanguages.defaults().uniformX()
tableLanguages.defaults().pad(10.0f)
tableLanguages.defaults().fillX()
topTable.add(tableLanguages).row()
val languageCompletionPercentage = UncivGame.Current.translations languageTables = topTable.addLanguageTables(stage.width - 60f)
.percentCompleteOfLanguages
languageTables.addAll(languageCompletionPercentage
.map { LanguageTable(it.key,if(it.key=="English") 100 else it.value) }
.sortedByDescending { it.percentComplete} )
languageTables.forEach { languageTables.forEach {
it.onClick { it.onClick {
@ -71,7 +33,6 @@ class LanguagePickerScreen : PickerScreen(){
rightSideButton.enable() rightSideButton.enable()
update() update()
} }
tableLanguages.add(it).row()
} }
rightSideButton.setText("Pick language".tr()) rightSideButton.setText("Pick language".tr())
@ -89,4 +50,4 @@ class LanguagePickerScreen : PickerScreen(){
game.setScreen(MainMenuScreen()) game.setScreen(MainMenuScreen())
dispose() dispose()
} }
} }

View File

@ -0,0 +1,68 @@
package com.unciv.ui.utils
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.UncivGame
import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.MarkupRenderer
import java.util.ArrayList
/** Represents a row in the Language picker, used both in OptionsPopup and in LanguagePickerScreen */
internal class LanguageTable(val language:String, val percentComplete: Int): Table(){
private val blue = ImageGetter.getBlue()
private val darkBlue = blue.cpy().lerp(Color.BLACK,0.5f)!!
init{
pad(10f)
defaults().pad(10f)
left()
if(ImageGetter.imageExists("FlagIcons/$language"))
add(ImageGetter.getImage("FlagIcons/$language")).size(40f)
val spaceSplitLang = language.replace("_"," ")
add("$spaceSplitLang ($percentComplete%)".toLabel())
update("")
touchable =
Touchable.enabled // so click listener is activated when any part is clicked, not only children
pack()
}
fun update(chosenLanguage:String){
background = ImageGetter.getBackground(if (chosenLanguage == language) blue else darkBlue)
}
companion object {
/** Extension to add the Language boxes to a Table, used both in OptionsPopup and in LanguagePickerScreen */
internal fun Table.addLanguageTables(expectedWidth: Float): ArrayList<LanguageTable> {
val languageTables = ArrayList<LanguageTable>()
val translationDisclaimer = FormattedLine(
text = "Please note that translations are a community-based work in progress and are" +
" INCOMPLETE! The percentage shown is how much of the language is translated in-game." +
" If you want to help translating the game into your language, click here.",
link = "https://github.com/yairm210/Unciv/wiki/Translating",
size = 15
)
add(MarkupRenderer.render(listOf(translationDisclaimer),expectedWidth)).pad(5f).row()
val tableLanguages = Table()
tableLanguages.defaults().uniformX()
tableLanguages.defaults().pad(10.0f)
tableLanguages.defaults().fillX()
val languageCompletionPercentage = UncivGame.Current.translations
.percentCompleteOfLanguages
languageTables.addAll(languageCompletionPercentage
.map { LanguageTable(it.key, if (it.key == "English") 100 else it.value) }
.sortedByDescending { it.percentComplete} )
languageTables.forEach {
tableLanguages.add(it).row()
}
add(tableLanguages).row()
return languageTables
}
}
}

View File

@ -0,0 +1,355 @@
package com.unciv.ui.utils
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.ui.*
import com.unciv.UncivGame
import kotlin.math.min
/*
Unimplemented ideas:
Allow "fixed header" content that does not participate in scrolling
(OptionsPopup mod check tab)
`scrollAlign: Align` property controls initial content scroll position (currently it's Align.top)
*/
/**
* Implements a 'Tabs' widget where different pages can be switched by selecting a header button.
*
* Each page is an Actor, passed to the Widget via [addPage]. Pages can be [removed][removePage],
* [replaced][replacePage] or dynamically added after the Widget is already shown.
* Pages are automatically scrollable, switching pages preserves scroll positions individually.
* Pages can be disabled or secret - any 'secret' pages added require a later call to [askForPassword]
* to activate them (or discard if the password is wrong).
*
* The size parameters are lower and upper bounds of the page content area. The widget will always report
* these bounds (plus header height) as layout properties min/max-Width/Height, and measure the content
* area of added pages and set the reported pref-W/H to their maximum within these bounds. But, if a
* maximum is not specified, that coordinate will grow with content unlimited, and layout max-W/H will
* always report the same as pref-W/H.
*/
//region Fields and initialization
@Suppress("MemberVisibilityCanBePrivate", "unused") // All member are part of our API
class TabbedPager(
private val minimumWidth: Float = 0f,
private var maximumWidth: Float = Float.MAX_VALUE,
private val minimumHeight: Float = 0f,
private var maximumHeight: Float = Float.MAX_VALUE,
private val headerFontSize: Int = 18,
private val headerFontColor: Color = Color.WHITE,
private val highlightColor: Color = Color.BLUE,
backgroundColor: Color = ImageGetter.getBlue().lerp(Color.BLACK, 0.5f),
private val headerPadding: Float = 10f,
capacity: Int = 4
) : Table() {
private class PageState(
var content: Actor,
var disabled: Boolean = false,
val onActivation: ((Int, String)->Unit)? = null
) {
var scrollX = 0f
var scrollY = 0f
var button: Button = Button(CameraStageBaseScreen.skin)
var buttonX = 0f
var buttonW = 0f
}
private var preferredWidth = minimumWidth
private val growMaxWidth = maximumWidth == Float.MAX_VALUE
private val limitWidth = maximumWidth
private var preferredHeight = minimumHeight
private val growMaxHeight = maximumHeight == Float.MAX_VALUE
private val limitHeight = maximumHeight
private val pages = ArrayList<PageState>(capacity)
/**
* Index of currently selected page, or -1 of none. Read-only, use [selectPage] to change.
*/
var activePage = -1
private set
private val header = Table(CameraStageBaseScreen.skin)
private val headerScroll = AutoScrollPane(header)
private var headerHeight = 0f
private val contentScroll = AutoScrollPane(null)
private val deferredSecretPages = ArrayDeque<PageState>(0)
private var askPasswordLock = false
init {
background = ImageGetter.getBackground(backgroundColor)
header.defaults().pad(headerPadding, headerPadding * 0.5f)
headerScroll.setOverscroll(false,false)
headerScroll.setScrollingDisabled(false, true)
// Measure header height, most likely its final value
removePage(addPage("Dummy"))
add(headerScroll).growX().minHeight(headerHeight).row()
add(contentScroll).grow().row()
}
//endregion
//region Widget interface
// The following are part of the Widget interface and serve dynamic sizing
override fun getPrefWidth() = preferredWidth
fun setPrefWidth(width: Float) {
if (width !in minimumWidth..maximumWidth) throw IllegalArgumentException()
preferredWidth = width
invalidateHierarchy()
}
override fun getPrefHeight() = preferredHeight + headerHeight
fun setPrefHeight(height: Float) {
if (height - headerHeight !in minimumHeight..maximumHeight) throw IllegalArgumentException()
preferredHeight = height - headerHeight
invalidateHierarchy()
}
override fun getMinWidth() = minimumWidth
override fun getMaxWidth() = maximumWidth
override fun getMinHeight() = headerHeight + minimumHeight
override fun getMaxHeight() = headerHeight + maximumHeight
//endregion
//region API
/** @return Number of pages currently stored */
fun pageCount() = pages.size
/** @return index of a page by its (untranslated) caption, or -1 if no such page exists */
fun getPageIndex(caption: String) = pages.indexOfLast { it.button.name == caption }
/** Change the selected page by using its index.
* @param index Page number or -1 to deselect the current page.
* @return `true` if the page was successfully changed.
*/
fun selectPage(index: Int): Boolean {
if (index !in -1 until pages.size) return false
if (activePage == index) return false
if (index >= 0 && pages[index].disabled) return false
if (activePage != -1) {
pages[activePage].apply {
button.color = Color.WHITE
scrollX = contentScroll.scrollX
scrollY = contentScroll.scrollY
contentScroll.removeActor(content)
}
}
activePage = index
if (index != -1) {
pages[index].apply {
button.color = highlightColor
contentScroll.actor = content
contentScroll.layout()
if (scrollX < 0f) // was marked to center on first show
scrollX = ((content.width - this@TabbedPager.width) / 2).coerceIn(0f, contentScroll.maxX)
contentScroll.scrollX = scrollX
contentScroll.scrollY = scrollY
contentScroll.updateVisualScroll()
headerScroll.let {
it.scrollX = (buttonX + (buttonW - it.width) / 2).coerceIn(0f, it.maxX)
}
onActivation?.invoke(index, button.name)
}
}
return true
}
/** Change the selected page by using its caption.
* @param caption Caption of the page to select. A nonexistent name will deselect the current page.
* @return `true` if the page was successfully changed.
*/
fun selectPage(caption: String) = selectPage(getPageIndex(caption))
private fun selectPage(page: PageState) = selectPage(getPageIndex(page))
/** Change the disabled property of a page by its index.
* @return previous value or `false` if index invalid.
*/
fun setPageDisabled(index: Int, disabled: Boolean): Boolean {
if (index !in 0 until pages.size) return false
val page = pages[index]
val oldValue = page.disabled
page.disabled = disabled
page.button.isEnabled = !disabled
if (disabled && index == activePage) selectPage(-1)
return oldValue
}
/** Change the disabled property of a page by its caption.
* @return previous value or `false` if caption not found.
*/
fun setPageDisabled(caption: String, disabled: Boolean) = setPageDisabled(getPageIndex(caption), disabled)
/** Remove a page by its index.
* @return `true` if page successfully removed */
fun removePage(index: Int): Boolean {
if (index !in 0 until pages.size) return false
if (index == activePage) selectPage(-1)
val page = pages.removeAt(index)
header.getCell(page.button).clearActor()
header.cells.removeIndex(index)
return true
}
/** Remove a page by its caption.
* @return `true` if page successfully removed */
fun removePage(caption: String) = removePage(getPageIndex(caption))
/** Replace a page's content by its index. */
fun replacePage(index: Int, content: Actor) {
if (index !in 0 until pages.size) return
val isActive = index == activePage
if (isActive) selectPage(-1)
pages[index].content = content
if (isActive) selectPage(index)
}
/** Replace a page's content by its caption. */
fun replacePage(caption: String, content: Actor) = replacePage(getPageIndex(caption), content)
/** Add a page!
* @param caption Text to be shown on the header button (automatically translated), can later be used to reference the page in other calls.
* @param content Actor to show when this page is selected.
* @param icon Actor, typically an [Image], to show before the caption.
* @param iconSize Size for [icon] - if not zero, the icon is wrapped to allow a [setSize] even on [Image] which ignores size.
* @param insertBefore -1 to add at the end or index of existing page to insert this before
* @param secret Marks page as 'secret'. A password is asked once per [TabbedPager] and if it does not match the has passed in the constructor the page and all subsequent secret pages are dropped.
* @param disabled Initial disabled state. Disabled pages cannot be selected even with [selectPage], their button is dimmed.
* @param onActivation _Optional_ callback called when this page is shown (per actual change to this page, not per header click). Lambda arguments are page index and caption.
* @return The new page's index or -1 if it could not be immediately added (secret).
*/
fun addPage(
caption: String,
content: Actor? = null,
icon: Actor? = null,
iconSize: Float = 0f,
insertBefore: Int = -1,
secret: Boolean = false,
disabled: Boolean = false,
onActivation: ((Int, String)->Unit)? = null
): Int {
// Build page descriptor and header button
val page = PageState(content ?: Group(), disabled, onActivation)
page.button.apply {
name = caption // enable finding pages by untranslated caption without needing our own field
if (icon != null) {
if (iconSize != 0f) {
val wrapper = Group().apply {
isTransform =
false // performance helper - nothing here is rotated or scaled
setSize(iconSize, iconSize)
icon.setSize(iconSize, iconSize)
icon.center(this)
addActor(icon)
}
add(wrapper).padRight(headerPadding * 0.5f)
} else {
add(icon)
}
}
add(caption.toLabel(headerFontColor, headerFontSize))
isEnabled = !disabled
onClick {
selectPage(page)
}
pack()
if (height + 2 * headerPadding > headerHeight) {
headerHeight = height + 2 * headerPadding
if (activePage >= 0) this@TabbedPager.invalidateHierarchy()
}
}
// Support 'secret' pages
if (secret) {
deferredSecretPages.addLast(page)
return -1
}
return addAndShowPage(page, insertBefore)
}
/**
* Activate any [secret][addPage] pages by asking for the password.
*
* If the parent of this Widget is a Popup, then this needs to be called _after_ the parent
* is shown to ensure proper popup stacking.
*/
fun askForPassword(secretHashCode: Int = 0) {
class PassPopup(screen: CameraStageBaseScreen, unlockAction: ()->Unit, lockAction: ()->Unit) : Popup(screen) {
val passEntry = TextField("", CameraStageBaseScreen.skin)
init {
passEntry.isPasswordMode = true
add(passEntry).row()
addOKButton {
if (passEntry.text.hashCode() == secretHashCode) unlockAction() else lockAction()
}
this.keyboardFocus = passEntry
}
}
if (!UncivGame.isCurrentInitialized() || askPasswordLock || deferredSecretPages.isEmpty()) return
askPasswordLock = true // race condition: Popup closes _first_, then deferredSecretPages is emptied -> parent shows and calls us again
PassPopup(UncivGame.Current.screen as CameraStageBaseScreen, {
addDeferredSecrets()
}, {
deferredSecretPages.clear()
}).open(true)
}
//endregion
//region Helper routines
private fun getPageIndex(page: PageState) = pages.indexOf(page)
private fun addAndShowPage(page: PageState, insertBefore: Int): Int {
// Update pages array and header table
val newIndex: Int
val buttonCell: Cell<Button>
if (insertBefore >= 0 && insertBefore < pages.size) {
newIndex = insertBefore
pages.add(insertBefore, page)
header.addActorAt(insertBefore, page.button)
buttonCell = header.getCell(page.button)
} else {
newIndex = pages.size
pages.add(page)
buttonCell = header.add(page.button)
}
page.buttonX = if (newIndex == 0) 0f else pages[newIndex-1].run { buttonX + buttonW }
page.buttonW = buttonCell.run { prefWidth + padLeft + padRight }
for (i in newIndex + 1 until pages.size)
pages[i].buttonX += page.buttonW
// Content Sizing
if (page.content is WidgetGroup) {
(page.content as WidgetGroup).packIfNeeded()
val contentWidth = min(page.content.width, limitWidth)
if (contentWidth > preferredWidth) {
preferredWidth = contentWidth
if (activePage >= 0) invalidateHierarchy()
}
val contentHeight = min(page.content.height, limitHeight)
if (contentHeight > preferredHeight) {
preferredHeight = contentHeight
if (activePage >= 0) invalidateHierarchy()
}
page.scrollX = -1f // mark to center later when all pages are measured
}
if (growMaxWidth) maximumWidth = minimumWidth
if (growMaxHeight) maximumHeight = minimumHeight
return newIndex
}
private fun addDeferredSecrets() {
while (true) {
val page = deferredSecretPages.removeFirstOrNull() ?: return
addAndShowPage(page, -1)
}
}
}

View File

@ -0,0 +1,44 @@
package com.unciv.ui.utils
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.unciv.models.translations.tr
import kotlin.math.min
/** A [Label] that unlike the original participates correctly in layout
* Caveat: You still need to turn wrap on _after_ instantiation, doing it here in init leads to hell.
*
* @param text Automatically translated text
* @param expectedWidth Upper limit for the preferred width the Label will report
*/
class WrappableLabel(
text: String,
private val expectedWidth: Float,
fontColor: Color = Color.WHITE,
fontSize: Int = 18
) : Label(text.tr(), CameraStageBaseScreen.skin) {
private var _measuredWidth = 0f
init {
if (fontColor != Color.WHITE || fontSize!=18) {
val style = LabelStyle(this.style)
style.fontColor = fontColor
if (fontSize != 18) {
style.font = Fonts.font
setFontScale(fontSize / Fonts.ORIGINAL_FONT_SIZE)
}
setStyle(style)
}
}
override fun setWrap(wrap: Boolean) {
_measuredWidth = super.getPrefWidth()
super.setWrap(wrap)
}
private fun getMeasuredWidth(): Float = if (wrap) _measuredWidth else super.getPrefWidth()
override fun getMinWidth() = 48f // ~ 2 chars
override fun getPrefWidth() = min(getMeasuredWidth(), expectedWidth)
override fun getMaxWidth() = getMeasuredWidth()
}

View File

@ -3,93 +3,157 @@ package com.unciv.ui.worldscreen.mainmenu
import com.badlogic.gdx.Application import com.badlogic.gdx.Application
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input import com.badlogic.gdx.Input
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.* import com.badlogic.gdx.scenes.scene2d.ui.*
import com.badlogic.gdx.utils.Align
import com.unciv.Constants import com.unciv.Constants
import com.unciv.MainMenuScreen import com.unciv.MainMenuScreen
import com.unciv.UncivGame
import com.unciv.logic.civilization.PlayerType import com.unciv.logic.civilization.PlayerType
import com.unciv.models.UncivSound import com.unciv.models.UncivSound
import com.unciv.models.metadata.BaseRuleset
import com.unciv.models.ruleset.Ruleset.CheckModLinksStatus
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.tilesets.TileSetCache import com.unciv.models.tilesets.TileSetCache
import com.unciv.models.translations.TranslationFileWriter import com.unciv.models.translations.TranslationFileWriter
import com.unciv.models.translations.Translations import com.unciv.models.translations.Translations
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.MarkupRenderer
import com.unciv.ui.civilopedia.SimpleCivilopediaText
import com.unciv.ui.utils.* import com.unciv.ui.utils.*
import com.unciv.ui.utils.LanguageTable.Companion.addLanguageTables
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
import com.unciv.ui.worldscreen.WorldScreen import com.unciv.ui.worldscreen.WorldScreen
import java.util.* import java.util.*
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.math.min
import com.badlogic.gdx.utils.Array as GdxArray import com.badlogic.gdx.utils.Array as GdxArray
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
class Language(val language:String, val percentComplete:Int){ /**
override fun toString(): String { * The Options (Settings) Popup
val spaceSplitLang = language.replace("_"," ") * @param previousScreen Tha caller - note if this is a [WorldScreen] or [MainMenuScreen] they will be rebuilt when major options change.
return "$spaceSplitLang - $percentComplete%" */
} //region Fields
} class OptionsPopup(val previousScreen: CameraStageBaseScreen) : Popup(previousScreen) {
class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScreen) {
private var selectedLanguage: String = "English"
private val settings = previousScreen.game.settings private val settings = previousScreen.game.settings
private val optionsTable = Table(CameraStageBaseScreen.skin) private val tabs: TabbedPager
private val resolutionArray = GdxArray(arrayOf("750x500", "900x600", "1050x700", "1200x800", "1500x1000")) private val resolutionArray = com.badlogic.gdx.utils.Array(arrayOf("750x500", "900x600", "1050x700", "1200x800", "1500x1000"))
private var modCheckFirstRun = true // marker for automatic first run on selecting the page
private var modCheckCheckBox: CheckBox? = null
private var modCheckResultCell: Cell<Actor>? = null
private val selectBoxMinWidth: Float
//endregion
init { init {
settings.addCompletedTutorialTask("Open the options table") settings.addCompletedTutorialTask("Open the options table")
optionsTable.defaults().pad(2.5f) innerTable.pad(0f)
rebuildOptionsTable() val tabMaxWidth: Float
val tabMinWidth: Float
val tabMaxHeight: Float
previousScreen.run {
selectBoxMinWidth = if (stage.width < 600f) 200f else 240f
tabMaxWidth = if (isPortrait()) stage.width - 10f else 0.8f * stage.width
tabMinWidth = 0.6f * stage.width
tabMaxHeight = (if (isPortrait()) 0.7f else 0.8f) * stage.height
}
tabs = TabbedPager(tabMinWidth, tabMaxWidth, 0f, tabMaxHeight,
headerFontSize = 21, backgroundColor = Color.CLEAR, capacity = 8)
add(tabs).pad(0f).grow().row()
val scrollPane = ScrollPane(optionsTable, skin) tabs.addPage("About", getAboutTab(), ImageGetter.getExternalImage("Icon.png"), 24f)
scrollPane.setOverscroll(false, false) tabs.addPage("Display", getDisplayTab(), ImageGetter.getImage("UnitPromotionIcons/Scouting"), 24f)
scrollPane.fadeScrollBars = false tabs.addPage("Gameplay", getGamePlayTab(), ImageGetter.getImage("OtherIcons/Options"), 24f)
scrollPane.setScrollingDisabled(true, false) tabs.addPage("Language", getLanguageTab(), ImageGetter.getImage("FlagIcons/${settings.language}"), 24f)
add(scrollPane).maxHeight(screen.stage.height * 0.6f).row() tabs.addPage("Sound", getSoundTab(), ImageGetter.getImage("OtherIcons/Speaker"), 24f)
// at the moment the notification service only exists on Android
if (Gdx.app.type == Application.ApplicationType.Android)
tabs.addPage("Multiplayer", getMultiplayerTab(), ImageGetter.getImage("OtherIcons/Multiplayer"), 24f)
tabs.addPage("Advanced", getAdvancedTab(), ImageGetter.getImage("OtherIcons/Settings"), 24f)
if (RulesetCache.size > 1) {
tabs.addPage("Locate mod errors", getModCheckTab(), ImageGetter.getImage("OtherIcons/Mods"), 24f) { _, _ ->
if (modCheckFirstRun) runModChecker()
}
}
if (Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT) && Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT)) {
tabs.addPage("Debug", getDebugTab(), ImageGetter.getImage("OtherIcons/SecretOptions"), 24f, secret = true)
}
addCloseButton { addCloseButton {
previousScreen.game.limitOrientationsHelper?.allowPortrait(settings.allowAndroidPortrait) previousScreen.game.limitOrientationsHelper?.allowPortrait(settings.allowAndroidPortrait)
if (previousScreen is WorldScreen) if (previousScreen is WorldScreen)
previousScreen.enableNextTurnButtonAfterOptions() previousScreen.enableNextTurnButtonAfterOptions()
} }.padBottom(10f)
pack() // Needed to show the background. pack() // Needed to show the background.
center(previousScreen.stage) center(previousScreen.stage)
} }
private fun addHeader(text: String) { override fun setVisible(visible: Boolean) {
optionsTable.add(text.toLabel(fontSize = 24)).colspan(2).padTop(if (optionsTable.cells.isEmpty) 0f else 20f).row() super.setVisible(visible)
} if (!visible) return
tabs.askForPassword(secretHashCode = 2747985)
private fun addYesNoRow(text: String, initialValue: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)) { if (tabs.activePage < 0) tabs.selectPage(2)
optionsTable.add(text.toLabel())
val button = YesNoButton(initialValue, CameraStageBaseScreen.skin) {
action(it)
settings.save()
if (updateWorld && previousScreen is WorldScreen)
previousScreen.shouldUpdate = true
}
optionsTable.add(button).row()
} }
/** Reload this Popup after major changes (resolution, tileset, language) */
private fun reloadWorldAndOptions() { private fun reloadWorldAndOptions() {
settings.save() settings.save()
if (previousScreen is WorldScreen) { if (previousScreen is WorldScreen) {
previousScreen.game.worldScreen = WorldScreen(previousScreen.gameInfo, previousScreen.viewingCiv) previousScreen.game.worldScreen = WorldScreen(previousScreen.gameInfo, previousScreen.viewingCiv)
previousScreen.game.setWorldScreen() previousScreen.game.setWorldScreen()
} else if (previousScreen is MainMenuScreen) { } else if (previousScreen is MainMenuScreen) {
previousScreen.game.setScreen(MainMenuScreen()) previousScreen.game.setScreen(MainMenuScreen())
} }
(previousScreen.game.screen as CameraStageBaseScreen).openOptionsPopup() (previousScreen.game.screen as CameraStageBaseScreen).openOptionsPopup()
} }
private fun rebuildOptionsTable() { //region Page builders
settings.save()
optionsTable.clear()
addHeader("Display options") private fun getAboutTab(): Table {
defaults().pad(5f)
val version = previousScreen.game.version
val versionAnchor = version.replace(".","")
val lines = sequence {
yield(FormattedLine(extraImage = "banner", imageSize = 240f, centered = true))
yield(FormattedLine())
yield(FormattedLine("{Version}: $version", link = "https://github.com/yairm210/Unciv/blob/master/changelog.md#$versionAnchor"))
yield(FormattedLine("See online Readme", link = "https://github.com/yairm210/Unciv/blob/master/README.md#unciv---foss-civ-v-for-androiddesktop"))
yield(FormattedLine("Visit repository", link = "https://github.com/yairm210/Unciv"))
}
return MarkupRenderer.render(lines.toList()).pad(20f)
}
private fun getLanguageTab() = Table(CameraStageBaseScreen.skin).apply {
val languageTables = this.addLanguageTables(tabs.prefWidth * 0.9f - 10f)
var chosenLanguage = settings.language
fun selectLanguage() {
settings.language = chosenLanguage
previousScreen.game.translations.tryReadTranslationForCurrentLanguage()
reloadWorldAndOptions()
}
fun updateSelection() {
languageTables.forEach { it.update(chosenLanguage) }
if (chosenLanguage != settings.language)
selectLanguage()
}
updateSelection()
languageTables.forEach {
it.onClick {
chosenLanguage = it.language
updateSelection()
}
}
}
private fun getDisplayTab() = Table(CameraStageBaseScreen.skin).apply {
pad(10f)
defaults().pad(2.5f)
addYesNoRow("Show worked tiles", settings.showWorkedTiles, true) { settings.showWorkedTiles = it } addYesNoRow("Show worked tiles", settings.showWorkedTiles, true) { settings.showWorkedTiles = it }
addYesNoRow("Show resources and improvements", settings.showResourcesAndImprovements, true) { settings.showResourcesAndImprovements = it } addYesNoRow("Show resources and improvements", settings.showResourcesAndImprovements, true) { settings.showResourcesAndImprovements = it }
@ -100,8 +164,6 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
addYesNoRow("Show pixel units", settings.showPixelUnits, true) { settings.showPixelUnits = it } addYesNoRow("Show pixel units", settings.showPixelUnits, true) { settings.showPixelUnits = it }
addYesNoRow("Show pixel improvements", settings.showPixelImprovements, true) { settings.showPixelImprovements = it } addYesNoRow("Show pixel improvements", settings.showPixelImprovements, true) { settings.showPixelImprovements = it }
addLanguageSelectBox()
addResolutionSelectBox() addResolutionSelectBox()
addTileSetSelectBox() addTileSetSelectBox()
@ -112,16 +174,21 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
} }
val continuousRenderingDescription = "When disabled, saves battery life but certain animations will be suspended" val continuousRenderingDescription = "When disabled, saves battery life but certain animations will be suspended"
optionsTable.add(continuousRenderingDescription.toLabel(fontSize = 14)).colspan(2).padTop(20f).row() val continuousRenderingLabel = WrappableLabel(continuousRenderingDescription,
tabs.prefWidth, Color.ORANGE.cpy().lerp(Color.WHITE, 0.7f), 14)
addHeader("Gameplay options") continuousRenderingLabel.wrap = true
add(continuousRenderingLabel).colspan(2).padTop(10f).row()
}
private fun getGamePlayTab() = Table(CameraStageBaseScreen.skin).apply {
pad(10f)
defaults().pad(5f)
addYesNoRow("Check for idle units", settings.checkForDueUnits, true) { settings.checkForDueUnits = it } addYesNoRow("Check for idle units", settings.checkForDueUnits, true) { settings.checkForDueUnits = it }
addYesNoRow("Move units with a single tap", settings.singleTapMove) { settings.singleTapMove = it } addYesNoRow("Move units with a single tap", settings.singleTapMove) { settings.singleTapMove = it }
addYesNoRow("Auto-assign city production", settings.autoAssignCityProduction, true) { addYesNoRow("Auto-assign city production", settings.autoAssignCityProduction, true) {
settings.autoAssignCityProduction = it settings.autoAssignCityProduction = it
if (it && previousScreen is WorldScreen && if (it && previousScreen is WorldScreen &&
previousScreen.viewingCiv.isCurrentPlayer() && previousScreen.viewingCiv.playerType == PlayerType.Human) { previousScreen.viewingCiv.isCurrentPlayer() && previousScreen.viewingCiv.playerType == PlayerType.Human) {
previousScreen.gameInfo.currentPlayerCiv.cities.forEach { city -> previousScreen.gameInfo.currentPlayerCiv.cities.forEach { city ->
city.cityConstructions.chooseNextConstruction() city.cityConstructions.chooseNextConstruction()
} }
@ -130,25 +197,54 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
addYesNoRow("Auto-build roads", settings.autoBuildingRoads) { settings.autoBuildingRoads = it } addYesNoRow("Auto-build roads", settings.autoBuildingRoads) { settings.autoBuildingRoads = it }
addYesNoRow("Automated workers replace improvements", settings.automatedWorkersReplaceImprovements) { settings.automatedWorkersReplaceImprovements = it } addYesNoRow("Automated workers replace improvements", settings.automatedWorkersReplaceImprovements) { settings.automatedWorkersReplaceImprovements = it }
addYesNoRow("Order trade offers by amount", settings.orderTradeOffersByAmount) { settings.orderTradeOffersByAmount = it } addYesNoRow("Order trade offers by amount", settings.orderTradeOffersByAmount) { settings.orderTradeOffersByAmount = it }
}
private fun getSoundTab() = Table(CameraStageBaseScreen.skin).apply {
pad(10f)
defaults().pad(5f)
addSoundEffectsVolumeSlider()
val musicLocation = Gdx.files.local(previousScreen.game.musicLocation)
if (musicLocation.exists())
addMusicVolumeSlider()
else
addDownloadMusic(musicLocation)
}
private fun getMultiplayerTab(): Table = Table(CameraStageBaseScreen.skin).apply {
pad(10f)
defaults().pad(5f)
addYesNoRow("Enable out-of-game turn notifications", settings.multiplayerTurnCheckerEnabled) {
settings.multiplayerTurnCheckerEnabled = it
settings.save()
tabs.replacePage("Multiplayer", getMultiplayerTab())
}
if (settings.multiplayerTurnCheckerEnabled) {
addMultiplayerTurnCheckerDelayBox()
addYesNoRow("Show persistent notification for turn notifier service", settings.multiplayerTurnCheckerPersistentNotificationEnabled)
{ settings.multiplayerTurnCheckerPersistentNotificationEnabled = it }
}
}
private fun getAdvancedTab() = Table(CameraStageBaseScreen.skin).apply {
pad(10f)
defaults().pad(5f)
addAutosaveTurnsSelectBox() addAutosaveTurnsSelectBox()
// at the moment the notification service only exists on Android addYesNoRow("{Show experimental world wrap for maps}\n{HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED!}",
addNotificationOptions() settings.showExperimentalWorldWrap) {
addHeader("Other options")
addYesNoRow("{Show experimental world wrap for maps}\n{HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED!}".tr(),
settings.showExperimentalWorldWrap) {
settings.showExperimentalWorldWrap = it settings.showExperimentalWorldWrap = it
} }
addYesNoRow("{Enable experimental religion in start games}\n{HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES!}".tr(), addYesNoRow("{Enable experimental religion in start games}\n{HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES!}",
settings.showExperimentalReligion) { settings.showExperimentalReligion) {
settings.showExperimentalReligion = it settings.showExperimentalReligion = it
} }
if (previousScreen.game.limitOrientationsHelper != null) { if (previousScreen.game.limitOrientationsHelper != null) {
addYesNoRow("Enable portrait orientation", settings.allowAndroidPortrait) { addYesNoRow("Enable portrait orientation", settings.allowAndroidPortrait) {
settings.allowAndroidPortrait = it settings.allowAndroidPortrait = it
@ -157,26 +253,77 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
} }
} }
addSoundEffectsVolumeSlider()
addMusicVolumeSlider()
addTranslationGeneration() addTranslationGeneration()
addModCheckerPopup()
addSetUserId()
optionsTable.add("Version".toLabel()).pad(10f) addSetUserId()
val versionLabel = previousScreen.game.version.toLabel()
if (previousScreen.game.version[0] in '0'..'9')
versionLabel.onClick {
val url = "https://github.com/yairm210/Unciv/blob/master/changelog.md#" +
previousScreen.game.version.replace(".","")
Gdx.net.openURI(url)
}
optionsTable.add(versionLabel).pad(10f).row()
} }
private fun addMinimapSizeSlider() { private fun getModCheckTab() = Table(CameraStageBaseScreen.skin).apply {
optionsTable.add("Show minimap".tr()) defaults().pad(10f).align(Align.top)
modCheckCheckBox = "Check extension mods based on vanilla".toCheckBox {
runModChecker(it)
}
add(modCheckCheckBox).row()
modCheckResultCell = add("Checking mods for errors...".toLabel())
}
private fun runModChecker(complex: Boolean = false) {
modCheckFirstRun = false
if (modCheckCheckBox == null) return
modCheckCheckBox!!.disable()
if (modCheckResultCell == null) return
thread(name="ModChecker") {
val lines = ArrayList<FormattedLine>()
var noProblem = true
for (mod in RulesetCache.values.sortedBy { it.name }) {
val modLinks = if (complex) RulesetCache.checkCombinedModLinks(linkedSetOf(mod.name))
else mod.checkModLinks()
val color = when (modLinks.status) {
CheckModLinksStatus.OK -> "#0F0"
CheckModLinksStatus.Warning -> "#FF0"
CheckModLinksStatus.Error -> "#F00"
}
val label = if (mod.name.isEmpty()) BaseRuleset.Civ_V_Vanilla.fullName else mod.name
lines += FormattedLine("$label{}", starred = true, color = color, header = 3)
if (modLinks.isNotOK()) {
lines += FormattedLine(modLinks.message)
noProblem = false
}
lines += FormattedLine()
}
if (noProblem) lines += FormattedLine("{No problems found}.")
Gdx.app.postRunnable {
val result = SimpleCivilopediaText(lines).renderCivilopediaText(tabs.prefWidth - 25f)
modCheckResultCell?.setActor(result)
modCheckCheckBox!!.enable()
}
}
}
private fun getDebugTab() = Table(CameraStageBaseScreen.skin).apply {
pad(10f)
defaults().pad(5f)
val game = UncivGame.Current
add("Supercharged".toCheckBox(game.superchargedForDebug) {
game.superchargedForDebug = it
}).row()
add("View entire map".toCheckBox(game.viewEntireMapForDebug) {
game.viewEntireMapForDebug = it
}).row()
if (game.isGameInfoInitialized()) {
add("God mode (current game)".toCheckBox(game.gameInfo.gameParameters.godMode) {
game.gameInfo.gameParameters.godMode = it
}).row()
}
}
//endregion
//region Row builders
private fun Table.addMinimapSizeSlider() {
add("Show minimap".toLabel()).left().fillX()
// The meaning of the values needs a formula to be synchronized between here and // The meaning of the values needs a formula to be synchronized between here and
// [Minimap.init]. It goes off-10%-11%..29%-30%-35%-40%-45%-50% - and the percentages // [Minimap.init]. It goes off-10%-11%..29%-30%-35%-40%-45%-50% - and the percentages
@ -203,49 +350,161 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
if (previousScreen is WorldScreen) if (previousScreen is WorldScreen)
previousScreen.shouldUpdate = true previousScreen.shouldUpdate = true
} }
optionsTable.add(minimapSlider).pad(10f).row() add(minimapSlider).pad(10f).row()
} }
private fun addSetUserId() { private fun Table.addResolutionSelectBox() {
val idSetLabel = "".toLabel() add("Resolution".toLabel()).left().fillX()
val takeUserIdFromClipboardButton = "Take user ID from clipboard".toTextButton()
.onClick { val resolutionSelectBox = SelectBox<String>(skin)
try { resolutionSelectBox.items = resolutionArray
val clipboardContents = Gdx.app.clipboard.contents.trim() resolutionSelectBox.selected = settings.resolution
UUID.fromString(clipboardContents) add(resolutionSelectBox).minWidth(selectBoxMinWidth).pad(10f).row()
YesNoPopup("Doing this will reset your current user ID to the clipboard contents - are you sure?",
{ resolutionSelectBox.onChange {
settings.userId = clipboardContents settings.resolution = resolutionSelectBox.selected
settings.save() reloadWorldAndOptions()
idSetLabel.setFontColor(Color.WHITE).setText("ID successfully set!".tr()) }
}, previousScreen).open(true) }
idSetLabel.isVisible = true
} catch (ex: Exception) { private fun Table.addTileSetSelectBox() {
idSetLabel.isVisible = true add("Tileset".toLabel()).left().fillX()
idSetLabel.setFontColor(Color.RED).setText("Invalid ID!".tr())
val tileSetSelectBox = SelectBox<String>(skin)
val tileSetArray = GdxArray<String>()
val tileSets = ImageGetter.getAvailableTilesets()
for (tileset in tileSets) tileSetArray.add(tileset)
tileSetSelectBox.items = tileSetArray
tileSetSelectBox.selected = settings.tileSet
add(tileSetSelectBox).minWidth(selectBoxMinWidth).pad(10f).row()
tileSetSelectBox.onChange {
settings.tileSet = tileSetSelectBox.selected
TileSetCache.assembleTileSetConfigs()
reloadWorldAndOptions()
}
}
private fun Table.addSoundEffectsVolumeSlider() {
add("Sound effects volume".tr()).left().fillX()
val soundEffectsVolumeSlider = UncivSlider(0f, 1.0f, 0.1f,
initial = settings.soundEffectsVolume
) {
settings.soundEffectsVolume = it
settings.save()
}
add(soundEffectsVolumeSlider).pad(5f).row()
}
private fun Table.addMusicVolumeSlider() {
add("Music volume".tr()).left().fillX()
val musicVolumeSlider = UncivSlider(0f, 1.0f, 0.1f,
initial = settings.musicVolume,
sound = UncivSound.Silent
) {
settings.musicVolume = it
settings.save()
val music = previousScreen.game.music
if (music == null) // restart music, if it was off at the app start
thread(name = "Music") { previousScreen.game.startMusic() }
music?.volume = 0.4f * it
}
musicVolumeSlider.value = settings.musicVolume
add(musicVolumeSlider).pad(5f).row()
}
private fun Table.addDownloadMusic(musicLocation: FileHandle) {
val downloadMusicButton = "Download music".toTextButton()
add(downloadMusicButton).colspan(2).row()
val errorTable = Table()
add(errorTable).colspan(2).row()
downloadMusicButton.onClick {
downloadMusicButton.disable()
errorTable.clear()
errorTable.add("Downloading...".toLabel())
// So the whole game doesn't get stuck while downloading the file
thread(name = "Music") {
try {
val file = DropBox.downloadFile("/Music/thatched-villagers.mp3")
musicLocation.write(file, false)
Gdx.app.postRunnable {
tabs.replacePage("Sound", getSoundTab())
previousScreen.game.startMusic()
}
} catch (ex: Exception) {
Gdx.app.postRunnable {
errorTable.clear()
errorTable.add("Could not download music!".toLabel(Color.RED))
} }
} }
optionsTable.add(takeUserIdFromClipboardButton).pad(5f).colspan(2).row()
optionsTable.add(idSetLabel).colspan(2).row()
}
private fun addNotificationOptions() {
if (Gdx.app.type == Application.ApplicationType.Android) {
addHeader("Multiplayer options")
addYesNoRow("Enable out-of-game turn notifications", settings.multiplayerTurnCheckerEnabled)
{ settings.multiplayerTurnCheckerEnabled = it }
if (settings.multiplayerTurnCheckerEnabled) {
addMultiplayerTurnCheckerDelayBox()
addYesNoRow("Show persistent notification for turn notifier service", settings.multiplayerTurnCheckerPersistentNotificationEnabled)
{ settings.multiplayerTurnCheckerPersistentNotificationEnabled = it }
} }
} }
} }
private fun addTranslationGeneration() { private fun Table.addMultiplayerTurnCheckerDelayBox() {
add("Time between turn checks out-of-game (in minutes)".toLabel()).left().fillX()
val checkDelaySelectBox = SelectBox<Int>(skin)
val possibleDelaysArray = GdxArray<Int>()
possibleDelaysArray.addAll(1, 2, 5, 15)
checkDelaySelectBox.items = possibleDelaysArray
checkDelaySelectBox.selected = settings.multiplayerTurnCheckerDelayInMinutes
add(checkDelaySelectBox).pad(10f).row()
checkDelaySelectBox.onChange {
settings.multiplayerTurnCheckerDelayInMinutes = checkDelaySelectBox.selected
settings.save()
}
}
private fun Table.addSetUserId() {
val idSetLabel = "".toLabel()
val takeUserIdFromClipboardButton = "Take user ID from clipboard".toTextButton()
.onClick {
try {
val clipboardContents = Gdx.app.clipboard.contents.trim()
UUID.fromString(clipboardContents)
YesNoPopup("Doing this will reset your current user ID to the clipboard contents - are you sure?",
{
settings.userId = clipboardContents
settings.save()
idSetLabel.setFontColor(Color.WHITE).setText("ID successfully set!".tr())
}, previousScreen).open(true)
idSetLabel.isVisible = true
} catch (ex: Exception) {
idSetLabel.isVisible = true
idSetLabel.setFontColor(Color.RED).setText("Invalid ID!".tr())
}
}
add(takeUserIdFromClipboardButton).pad(5f).colspan(2).row()
add(idSetLabel).colspan(2).row()
}
private fun Table.addAutosaveTurnsSelectBox() {
add("Turns between autosaves".toLabel()).left().fillX()
val autosaveTurnsSelectBox = SelectBox<Int>(skin)
val autosaveTurnsArray = GdxArray<Int>()
autosaveTurnsArray.addAll(1, 2, 5, 10)
autosaveTurnsSelectBox.items = autosaveTurnsArray
autosaveTurnsSelectBox.selected = settings.turnsBetweenAutosaves
add(autosaveTurnsSelectBox).pad(10f).row()
autosaveTurnsSelectBox.onChange {
settings.turnsBetweenAutosaves = autosaveTurnsSelectBox.selected
settings.save()
}
}
private fun Table.addTranslationGeneration() {
if (Gdx.app.type == Application.ApplicationType.Desktop) { if (Gdx.app.type == Application.ApplicationType.Desktop) {
val generateTranslationsButton = "Generate translation files".toTextButton() val generateTranslationsButton = "Generate translation files".toTextButton()
val generateAction = { val generateAction = {
@ -259,218 +518,55 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
generateTranslationsButton.onClick(generateAction) generateTranslationsButton.onClick(generateAction)
keyPressDispatcher[Input.Keys.F12] = generateAction keyPressDispatcher[Input.Keys.F12] = generateAction
generateTranslationsButton.addTooltip("F12",18f) generateTranslationsButton.addTooltip("F12",18f)
optionsTable.add(generateTranslationsButton).colspan(2).row() add(generateTranslationsButton).colspan(2).row()
} }
} }
private fun addModCheckerPopup() {
//if (RulesetCache.isEmpty()) return
val modCheckerButton = "Locate mod errors".toTextButton()
modCheckerButton.onClick {
val lines = ArrayList<String>()
for (mod in RulesetCache.values) {
val modLinks = mod.checkModLinks()
if (modLinks.isNotOK()) {
lines += ""
lines += mod.name
lines += ""
lines += modLinks.message
lines += ""
}
}
if (lines.isEmpty()) lines += "{No problems found}."
val popup = Popup(screen)
popup.name = "ModCheckerPopup"
popup.add(ScrollPane(lines.joinToString("\n").toLabel()).apply { setOverscroll(false, false) })
.maxHeight(screen.stage.height / 2).row()
popup.addCloseButton()
popup.open(true)
}
optionsTable.add(modCheckerButton).colspan(2).row()
}
private fun addSoundEffectsVolumeSlider() { private fun Table.addYesNoRow(text: String, initialValue: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)) {
optionsTable.add("Sound effects volume".tr()) val wrapWidth = tabs.prefWidth - 60f
add(WrappableLabel(text, wrapWidth).apply { wrap = true })
val soundEffectsVolumeSlider = UncivSlider(0f, 1.0f, 0.1f, .left().fillX()
initial = settings.soundEffectsVolume .maxWidth(wrapWidth)
) { val button = YesNoButton(initialValue, CameraStageBaseScreen.skin) {
settings.soundEffectsVolume = it action(it)
settings.save() settings.save()
if (updateWorld && previousScreen is WorldScreen)
previousScreen.shouldUpdate = true
} }
optionsTable.add(soundEffectsVolumeSlider).pad(5f).row() add(button).row()
} }
private fun addMusicVolumeSlider() { //endregion
val musicLocation = Gdx.files.local(previousScreen.game.musicLocation)
if (musicLocation.exists()) {
optionsTable.add("Music volume".tr())
val musicVolumeSlider = UncivSlider(0f, 1.0f, 0.1f, /**
initial = settings.musicVolume, * This TextButton subclass helps to keep looks and behaviour of our Yes/No
sound = UncivSound.Silent * in one place, but it also helps keeping context for those action lambdas.
) { *
settings.musicVolume = it * Usage: YesNoButton(someSetting: Boolean, skin) { someSetting = it; sideEffects() }
settings.save() */
private class YesNoButton(
initialValue: Boolean,
skin: Skin,
action: (Boolean) -> Unit
) : TextButton (initialValue.toYesNo(), skin ) {
val music = previousScreen.game.music var value = initialValue
if (music == null) // restart music, if it was off at the app start private set(value) {
thread(name = "Music") { previousScreen.game.startMusic() } field = value
setText(value.toYesNo())
music?.volume = 0.4f * it
} }
musicVolumeSlider.value = settings.musicVolume
optionsTable.add(musicVolumeSlider).pad(5f).row()
} else {
val downloadMusicButton = "Download music".toTextButton()
optionsTable.add(downloadMusicButton).colspan(2).row()
val errorTable = Table()
optionsTable.add(errorTable).colspan(2).row()
downloadMusicButton.onClick { init {
downloadMusicButton.disable() color = ImageGetter.getBlue()
errorTable.clear() onClick {
errorTable.add("Downloading...".toLabel()) value = !value
action.invoke(value)
// So the whole game doesn't get stuck while downloading the file
thread(name = "Music") {
try {
val file = DropBox.downloadFile("/Music/thatched-villagers.mp3")
musicLocation.write(file, false)
Gdx.app.postRunnable {
rebuildOptionsTable()
previousScreen.game.startMusic()
}
} catch (ex: Exception) {
Gdx.app.postRunnable {
errorTable.clear()
errorTable.add("Could not download music!".toLabel(Color.RED))
}
}
}
} }
} }
}
private fun addResolutionSelectBox() { companion object {
optionsTable.add("Resolution".toLabel()) fun Boolean.toYesNo(): String = (if (this) Constants.yes else Constants.no).tr()
val resolutionSelectBox = SelectBox<String>(skin)
resolutionSelectBox.items = resolutionArray
resolutionSelectBox.selected = settings.resolution
optionsTable.add(resolutionSelectBox).minWidth(240f).pad(10f).row()
resolutionSelectBox.onChange {
settings.resolution = resolutionSelectBox.selected
reloadWorldAndOptions()
}
}
private fun addTileSetSelectBox() {
optionsTable.add("Tileset".toLabel())
val tileSetSelectBox = SelectBox<String>(skin)
val tileSetArray = GdxArray<String>()
val tileSets = ImageGetter.getAvailableTilesets()
for (tileset in tileSets) tileSetArray.add(tileset)
tileSetSelectBox.items = tileSetArray
tileSetSelectBox.selected = settings.tileSet
optionsTable.add(tileSetSelectBox).minWidth(240f).pad(10f).row()
tileSetSelectBox.onChange {
settings.tileSet = tileSetSelectBox.selected
TileSetCache.assembleTileSetConfigs()
reloadWorldAndOptions()
}
}
private fun addAutosaveTurnsSelectBox() {
optionsTable.add("Turns between autosaves".toLabel())
val autosaveTurnsSelectBox = SelectBox<Int>(skin)
val autosaveTurnsArray = GdxArray<Int>()
autosaveTurnsArray.addAll(1, 2, 5, 10)
autosaveTurnsSelectBox.items = autosaveTurnsArray
autosaveTurnsSelectBox.selected = settings.turnsBetweenAutosaves
optionsTable.add(autosaveTurnsSelectBox).pad(10f).row()
autosaveTurnsSelectBox.onChange {
settings.turnsBetweenAutosaves = autosaveTurnsSelectBox.selected
settings.save()
}
}
private fun addMultiplayerTurnCheckerDelayBox() {
optionsTable.add("Time between turn checks out-of-game (in minutes)".toLabel())
val checkDelaySelectBox = SelectBox<Int>(skin)
val possibleDelaysArray = GdxArray<Int>()
possibleDelaysArray.addAll(1, 2, 5, 15)
checkDelaySelectBox.items = possibleDelaysArray
checkDelaySelectBox.selected = settings.multiplayerTurnCheckerDelayInMinutes
optionsTable.add(checkDelaySelectBox).pad(10f).row()
checkDelaySelectBox.onChange {
settings.multiplayerTurnCheckerDelayInMinutes = checkDelaySelectBox.selected
settings.save()
}
}
private fun addLanguageSelectBox() {
val languageSelectBox = SelectBox<Language>(skin)
val languageArray = GdxArray<Language>()
previousScreen.game.translations.percentCompleteOfLanguages
.map { Language(it.key, if (it.key == "English") 100 else it.value) }
.sortedByDescending { it.percentComplete }
.forEach { languageArray.add(it) }
if (languageArray.size == 0) return
optionsTable.add("Language".toLabel())
languageSelectBox.items = languageArray
val matchingLanguage = languageArray.firstOrNull { it.language == settings.language }
languageSelectBox.selected = matchingLanguage ?: languageArray.first()
optionsTable.add(languageSelectBox).minWidth(240f).pad(10f).row()
languageSelectBox.onChange {
// Sometimes the "changed" is triggered even when we didn't choose something
selectedLanguage = languageSelectBox.selected.language
if (selectedLanguage != settings.language)
selectLanguage()
}
}
private fun selectLanguage() {
settings.language = selectedLanguage
previousScreen.game.translations.tryReadTranslationForCurrentLanguage()
reloadWorldAndOptions()
}
}
/*
This TextButton subclass helps to keep looks and behaviour of our Yes/No
in one place, but it also helps keeping context for those action lambdas.
Usage: YesNoButton(someSetting: Boolean, skin) { someSetting = it; sideEffects() }
*/
private fun Boolean.toYesNo(): String = (if (this) Constants.yes else Constants.no).tr()
private class YesNoButton(initialValue: Boolean, skin: Skin, action: (Boolean) -> Unit)
: TextButton (initialValue.toYesNo(), skin ) {
var value = initialValue
private set(value) {
field = value
setText(value.toYesNo())
}
init {
color = ImageGetter.getBlue()
onClick {
value = !value
action.invoke(value)
} }
} }
} }