Tabbed options (#5081)

* Tabbed Options Screen

* Tabbed Options Screen - atlas
This commit is contained in:
SomeTroglodyte
2021-09-04 20:30:39 +02:00
committed by GitHub
parent 60500d17a6
commit 6bc58ab5a3
14 changed files with 1655 additions and 1080 deletions

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

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.
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.
@ -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.
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'.
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!
# Buildings
# Buildings
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 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
Get [unitName] = Erhalte [unitName]
Hydro Plant = Wasserkraftwerk
[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 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
@ -50,12 +50,12 @@ Requires [PolicyOrNationalWonder] = Benötigt [PolicyOrNationalWonder]
Cannot be purchased = Kann nicht gekauft werden
Can only be purchased = Kann nur gekauft werden
See also = Siehe auch
Requires at least one of the following: = Benötigt eine der folgenden Vorraussetzungen:
Requires all of the following: = Benötigt folgende Vorraussetzungen:
Leads to [techName] = [techName] kann nun erforscht werden
Leads to: = Ermöglicht die Erforschung von:
Current construction = Aktuelle Produktion
Construction queue = Produktionswarteschlange
Pick a construction = Wähle ein Bauwerk
@ -66,7 +66,7 @@ Show stats drilldown = Zeige Statistiken
Show construction queue = Zeige Produktionswarteschlange
Save = Speichern
Cancel = Abbrechen
Diplomacy = Diplomatie
War = Krieg
Peace = Frieden
@ -94,7 +94,7 @@ Indeed! = Auf jeden Fall!
Denounce [civName]? = [civName] anprangern?
Denounce ([numberOfTurns] turns) = Anprangern ([numberOfTurns] Runden)
We will remember this. = Das werden wir nie vergessen!
[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 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?
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)
Unforgivable = Todfeind
Afraid = Gefürchtet
Enemy = Feind
@ -111,12 +111,12 @@ Neutral = Neutral
Favorable = Beliebt
Friend = Freund
Ally = Verbündeter
[questName] (+[influenceAmount] influence) = [questName] (+[influenceAmount] Einfluss)
[remainingTurns] turns remaining = [remainingTurns] Runden verbleiben
## Diplomatic modifiers
## Diplomatic modifiers
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.
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!
You have stolen our lands! = Ihr habt unser Land geraubt!
You gave us units! = Ihr habt uns Einheiten geschenkt!
Demands = Forderungen
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.
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.
# City-States
# City-States
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 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!
# Trades
# Trades
Trade = Handel
Offer trade = Handel anbieten
@ -231,17 +231,17 @@ Declare war on [nation] = [nation] den Krieg erklären
Luxury resources = Luxusressourcen
Strategic resources = Strategische Ressourcen
Owned: [amountOwned] = Im Besitz: [amountOwned]
# Nation picker
# Nation picker
[resourceName] not required = [resourceName] nicht erforderlich
Lost ability = Verlorene Fähigkeit
National ability = Nationalfähigkeit
[firstValue] vs [secondValue] = [firstValue] anstatt [secondValue]
# New game screen
# New game screen
Uniques = Unikate
Promotions = Beförderungen
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 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
Difficulty = Schwierigkeitsgrad
AI = KI
Remove = Entfernen
Random = Zufall
@ -315,14 +315,14 @@ Human = Mensch
Hotseat = Schleudersitz
User ID = Spieler-ID
Click to copy = Anklicken zum Kopieren
Game Speed = Spielgeschwindigkeit
Quick = Schnell
Standard = Standard
Epic = Episch
Marathon = Marathon
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!
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] Religions = [amount] Religionen
[amount] Beliefs = [amount] Glaubenssätze
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!
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.
# Multiplayer
# Multiplayer
Username = Spielername
Multiplayer = Mehrspieler
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?
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
# Save game menu
# Save game menu
Current saves = Gespeicherte Spiele
Show autosaves = Zeige automatisch gespeicherte Spiele an
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!
Save to custom location = Speichern in externem Speicherort
Could not save game to custom location! = Speichern in externem Speicherort fehlgeschlagen!
# Options
# Options
Options = Optionen
Display options = Anzeigeeinstellungen
Gameplay options = Spielmechanikeinstellungen
Other options = Andere Einstellungen
About = Über
Display = Anzeige
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
Sound effects volume = Lautstärke Soundeffekte
Music volume = Lautstärke Musik
@ -444,16 +452,18 @@ Show tile yields = Felderträge anzeigen
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.
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
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!
Enable portrait orientation = Hochkant-Orientierung zulassen
Generate translation files = Erstelle Übersetzungsdateien
Translation files are generated successfully. = Die Übersetzungsdateien wurden erfolgreich erstellt.
Locate mod errors = Mod-Fehler lokalisieren
# Notifications
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
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 [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!
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 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!
[cityName] has grown! = [cityName] ist gewachsen!
[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!
# World Screen UI
# World Screen UI
Working... = Bitte warten...
Waiting for other players... = Warte auf andere Spieler...
in = in
@ -617,7 +627,7 @@ Yes = Ja
No = Nein
Acquire = Übernehmen
Under construction = Im Bau
Food = Nahrung
Production = Produktion
Gold = Gold
@ -625,7 +635,7 @@ Happiness = Zufriedenheit
Culture = Kultur
Science = Wissenschaft
Faith = Glaube
Crop Yield = Ernteertrag
Territory = Territorium
Force = Kampfkraft
@ -634,7 +644,7 @@ Golden Age = Goldenes Zeitalter
[year] BC = [year] v. Chr.
[year] AD = [year] n. Chr.
Civilopedia = Civilopedia
Start new game = Neues Spiel
Save game = Spiel speichern
Load game = Spiel laden
@ -650,9 +660,9 @@ Close = Schließen
Do you want to exit the game? = Willst du das Spiel beenden?
Start bias: = Start-Präferenz:
Avoid [terrain] = Meide [terrain]
# City screen
Exit city = Stadt verlassen
Raze city = Stadt niederreißen
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. =
# Requires translation!
Please enter some text =
# Technology UI
Pick a tech = Technologie auswählen
Pick a free tech = Kostenlose Technologie auswählen
Research [technology] = [technology] erforschen
@ -729,9 +739,9 @@ Attack = Angreifen
Bombard = Bombardieren
NUKE = Atomisieren
Captured! = Gefangen!
# Battle modifier categories
defence vs ranged = Verteidigung gegen Fernkampf
[percentage] to unit defence = [percentage] erhöhte Verteidigungsstärke
Attacker Bonus = Angriffsbonus
@ -753,11 +763,11 @@ defence vs [unitType] = Verteidigung gegen [unitType]
[tileFilter] defence = [tileFilter] Verteidigung
Defensive Bonus = Verteidigungsbonus
Stacked with [unitType] = Auf gleichem Feld mit [unitType]
The following improvements [stats]: = Die folgenden Verbesserungen [stats]:
The following improvements on [tileType] tiles [stats]: = Die folgenden Verbesserungen auf [tileType] Feldern [stats]:
Hurry Research = Forschung beschleunigen
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!
@ -780,9 +790,9 @@ Policies = Politiken
Base happiness = Grundzufriedenheit
Occupied City = Besetzte Städte
Buildings = Gebäude
# terrainFilters (so for uniques like: "[stats] from [terrainFilter] tiles")
All = Alle
Water = Wasser
Land = Land
@ -800,14 +810,14 @@ Strategic resource = Strategische Ressource
Fresh water = Frischwasser
non-fresh water = nicht frisches Wasser
Natural Wonder = Naturwunder
# improvementFilters
# improvementFilters
All Road = Alle Straßen
Great Improvement = Große Verbesserung
Great = Große
Wonders = Wunder
Base values = Grundwerte
Bonuses = Boni
@ -833,9 +843,9 @@ Known and defeated ([numberOfCivs]) = Bekannt und besiegt ([numberOfCivs])
Tiles = Felder
Natural Wonders = Naturwunder
Treasury deficit = Schatzkammerdefizit
# Victory
# Victory
Science victory = Wissenschaftssieg
Cultural victory = Kultursieg
Conquest victory = Dominanzsieg
@ -874,9 +884,9 @@ Vote for [civilizationName] = Abstimmen für [civilizationName]
Continue = Fortfahren
Abstained = Enthalten
Vote for World Leader = Stimme für den Anführer der Welt ab
# Capturing a city
What would you like to do with the city? = Was möchtet Ihr mit dieser Stadt machen?
Annex = Annektieren
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!
Sorry. = Entschuldigung.
Never! = Niemals!
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?
Sign Declaration of Friendship ([30] turns) = Freundschaftserklärung unterzeichnen ([30] Runden)
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!
[otherCiv] has denied our Declaration of Friendship! = [otherCiv] hat unsere Freundschaftserklärung abgelehnt!
Basics = Spielkonzepte
Resources = Ressourcen
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!
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?
# Civilopedia difficulty levels
Player settings = Spieler-Einstellungen
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
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
# Other civilopedia things
Nations = Nationen
Available for [unitTypes] = Verfügbar für [unitTypes]
@ -1010,9 +1020,9 @@ Granted by [param] = Von [param] erteilt
Granted by: = Erteilt von:
[bonus] with [tech] = [bonus] mit [tech]
Difficulty levels = Schwierigkeitsgrade
# Policies
Adopt policy = Politik verabschieden
Adopt free policy = Freie Politik verabschieden
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
# Terrains
Impassable = Unpassierbar
Rare feature = Seltene Geländeform
# Resources
Bison = Bisons
Copper = Kupfer
Cocoa = Kakao
@ -1063,9 +1073,9 @@ Truffles = Trüffel
Strategic = Strategisch
Bonus = Bonus
Luxury = Luxus
# Unit types
City = Stadt
Civilian = Zivilist
Melee = Nahkampf
@ -1074,21 +1084,21 @@ Scout = Späher
Mounted = Beritten
Armor = Panzerung
Siege = Belagerung
WaterCivilian = Wasser-Zivilist
WaterMelee = Wassernahkampf
WaterRanged = Wasserfernkampf
WaterSubmarine = U-Boote
WaterAircraftCarrier = Flugzeugträger
Fighter = Jagdflugzeug
Bomber = Bomber
AtomicBomber = Atombomber
Missile = Rakete
# Unit filters and other unit related things
Air = Luft
air units = Lufteinheiten
Barbarian = Barbar
@ -1106,13 +1116,13 @@ Unbuildable = nicht baubar
water units = Wassereinheiten
wounded units = verwundete Einheiten
Wounded = Verwundet
# For the All "newly-trained [relevant] units in this city receive the [] promotion" translation. Relevant as in 'units that can receive'
relevant = relevante
# Promotions
Pick promotion = Wähle eine Beförderung
OR = ODER
units in open terrain = Einheiten im offenen Gelände
@ -1125,10 +1135,9 @@ Dogfighting II = Kurvenkampf II
Dogfighting III = Kurvenkampf III
Choose name for [unitName] = Wähle Namen für [unitName]
[unitFilter] units gain the [promotion] promotion = [unitFilter] Einheiten erhalten die [promotion] Beförderung
# Multiplayer Turn Checker Service
Multiplayer options = Mehrspieler Einstellungen
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)
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?
ID successfully set! = Spieler-ID erfolgreich gesetzt!
Invalid ID! = Ungültige Spieler-ID!
# Mods
Mods = Modifikationen
Download [modName] = [modName] herunterladen
Update [modName] = [modName] aktualisieren
@ -1167,9 +1176,9 @@ No description provided = Keine Beschreibung mitgeliefert
Author: [author] = Autor: [author]
Size: [size] kB = Größe: [size] kB
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
[stats] from every [param] = Alle [param] geben [stats]
[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
@ -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
Does not need removal of [feature] = Hierfür muß [feature] nicht entfernt werden
Gain a free [building] [cityFilter] = Erhalte [building] umsonst [cityFilter]
# City filters
in this city = in dieser Stadt
in all cities = in allen Städten

View File

@ -420,9 +420,16 @@ Could not save game to custom location! =
# Options
Options =
Display options =
Gameplay options =
Other options =
About =
Display =
Gameplay =
Sound =
Advanced =
Multiplayer =
Locate mod errors =
Debug =
See online Readme =
Turns between autosaves =
Sound effects volume =
Music volume =
@ -444,18 +451,19 @@ off =
Show pixel units =
Show pixel improvements =
Enable nuclear weapons =
Fontset =
Show tile yields =
Continuous rendering =
When disabled, saves battery life but certain animations will be suspended =
Order trade offers by amount =
Check extension mods based on vanilla =
Checking mods for errors... =
Show experimental world wrap for maps =
HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED! =
HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES! =
Enable portrait orientation =
Generate translation files =
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
@ -1135,7 +1143,6 @@ Choose name for [unitName] =
# Multiplayer Turn Checker Service
Multiplayer options =
Enable out-of-game turn notifications =
Time between turn checks out-of-game (in minutes) =
Show persistent notification for turn notifier service =

View File

@ -43,7 +43,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
*/
var viewEntireMapForDebug = false
/** 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.
* Does not update World View changes until finished.

View File

@ -1,69 +1,31 @@
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.UncivGame
import com.unciv.models.translations.tr
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
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)
}
}
class LanguagePickerScreen : PickerScreen(){
/** A [PickerScreen] to select a language, used once on the initial run after a fresh install.
* After that, [OptionsPopup] provides the functionality.
* Reusable code is in [LanguageTable] and [addLanguageTables].
*/
class LanguagePickerScreen : PickerScreen() {
var chosenLanguage = "English"
private val languageTables = ArrayList<LanguageTable>()
private val languageTables: ArrayList<LanguageTable>
fun update(){
fun update() {
languageTables.forEach { it.update(chosenLanguage) }
}
init {
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
.percentCompleteOfLanguages
languageTables.addAll(languageCompletionPercentage
.map { LanguageTable(it.key,if(it.key=="English") 100 else it.value) }
.sortedByDescending { it.percentComplete} )
languageTables = topTable.addLanguageTables(stage.width - 60f)
languageTables.forEach {
it.onClick {
@ -71,7 +33,6 @@ class LanguagePickerScreen : PickerScreen(){
rightSideButton.enable()
update()
}
tableLanguages.add(it).row()
}
rightSideButton.setText("Pick language".tr())
@ -89,4 +50,4 @@ class LanguagePickerScreen : PickerScreen(){
game.setScreen(MainMenuScreen())
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.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.*
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.MainMenuScreen
import com.unciv.UncivGame
import com.unciv.logic.civilization.PlayerType
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.tilesets.TileSetCache
import com.unciv.models.translations.TranslationFileWriter
import com.unciv.models.translations.Translations
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.LanguageTable.Companion.addLanguageTables
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
import com.unciv.ui.worldscreen.WorldScreen
import java.util.*
import kotlin.concurrent.thread
import kotlin.math.min
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 {
val spaceSplitLang = language.replace("_"," ")
return "$spaceSplitLang - $percentComplete%"
}
}
class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScreen) {
private var selectedLanguage: String = "English"
/**
* The Options (Settings) Popup
* @param previousScreen Tha caller - note if this is a [WorldScreen] or [MainMenuScreen] they will be rebuilt when major options change.
*/
//region Fields
class OptionsPopup(val previousScreen: CameraStageBaseScreen) : Popup(previousScreen) {
private val settings = previousScreen.game.settings
private val optionsTable = Table(CameraStageBaseScreen.skin)
private val resolutionArray = GdxArray(arrayOf("750x500", "900x600", "1050x700", "1200x800", "1500x1000"))
private val tabs: TabbedPager
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 {
settings.addCompletedTutorialTask("Open the options table")
optionsTable.defaults().pad(2.5f)
rebuildOptionsTable()
innerTable.pad(0f)
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)
scrollPane.setOverscroll(false, false)
scrollPane.fadeScrollBars = false
scrollPane.setScrollingDisabled(true, false)
add(scrollPane).maxHeight(screen.stage.height * 0.6f).row()
tabs.addPage("About", getAboutTab(), ImageGetter.getExternalImage("Icon.png"), 24f)
tabs.addPage("Display", getDisplayTab(), ImageGetter.getImage("UnitPromotionIcons/Scouting"), 24f)
tabs.addPage("Gameplay", getGamePlayTab(), ImageGetter.getImage("OtherIcons/Options"), 24f)
tabs.addPage("Language", getLanguageTab(), ImageGetter.getImage("FlagIcons/${settings.language}"), 24f)
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 {
previousScreen.game.limitOrientationsHelper?.allowPortrait(settings.allowAndroidPortrait)
if (previousScreen is WorldScreen)
previousScreen.enableNextTurnButtonAfterOptions()
}
}.padBottom(10f)
pack() // Needed to show the background.
center(previousScreen.stage)
}
private fun addHeader(text: String) {
optionsTable.add(text.toLabel(fontSize = 24)).colspan(2).padTop(if (optionsTable.cells.isEmpty) 0f else 20f).row()
}
private fun addYesNoRow(text: String, initialValue: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)) {
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()
override fun setVisible(visible: Boolean) {
super.setVisible(visible)
if (!visible) return
tabs.askForPassword(secretHashCode = 2747985)
if (tabs.activePage < 0) tabs.selectPage(2)
}
/** Reload this Popup after major changes (resolution, tileset, language) */
private fun reloadWorldAndOptions() {
settings.save()
if (previousScreen is WorldScreen) {
previousScreen.game.worldScreen = WorldScreen(previousScreen.gameInfo, previousScreen.viewingCiv)
previousScreen.game.setWorldScreen()
} else if (previousScreen is MainMenuScreen) {
previousScreen.game.setScreen(MainMenuScreen())
}
(previousScreen.game.screen as CameraStageBaseScreen).openOptionsPopup()
}
private fun rebuildOptionsTable() {
settings.save()
optionsTable.clear()
//region Page builders
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 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 improvements", settings.showPixelImprovements, true) { settings.showPixelImprovements = it }
addLanguageSelectBox()
addResolutionSelectBox()
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"
optionsTable.add(continuousRenderingDescription.toLabel(fontSize = 14)).colspan(2).padTop(20f).row()
addHeader("Gameplay options")
val continuousRenderingLabel = WrappableLabel(continuousRenderingDescription,
tabs.prefWidth, Color.ORANGE.cpy().lerp(Color.WHITE, 0.7f), 14)
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("Move units with a single tap", settings.singleTapMove) { settings.singleTapMove = it }
addYesNoRow("Auto-assign city production", settings.autoAssignCityProduction, true) {
settings.autoAssignCityProduction = it
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 ->
city.cityConstructions.chooseNextConstruction()
}
@ -130,25 +197,54 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
addYesNoRow("Auto-build roads", settings.autoBuildingRoads) { settings.autoBuildingRoads = it }
addYesNoRow("Automated workers replace improvements", settings.automatedWorkersReplaceImprovements) { settings.automatedWorkersReplaceImprovements = 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()
// at the moment the notification service only exists on Android
addNotificationOptions()
addHeader("Other options")
addYesNoRow("{Show experimental world wrap for maps}\n{HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED!}".tr(),
settings.showExperimentalWorldWrap) {
addYesNoRow("{Show experimental world wrap for maps}\n{HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED!}",
settings.showExperimentalWorldWrap) {
settings.showExperimentalWorldWrap = it
}
addYesNoRow("{Enable experimental religion in start games}\n{HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES!}".tr(),
settings.showExperimentalReligion) {
addYesNoRow("{Enable experimental religion in start games}\n{HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES!}",
settings.showExperimentalReligion) {
settings.showExperimentalReligion = it
}
if (previousScreen.game.limitOrientationsHelper != null) {
addYesNoRow("Enable portrait orientation", settings.allowAndroidPortrait) {
settings.allowAndroidPortrait = it
@ -157,26 +253,77 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
}
}
addSoundEffectsVolumeSlider()
addMusicVolumeSlider()
addTranslationGeneration()
addModCheckerPopup()
addSetUserId()
optionsTable.add("Version".toLabel()).pad(10f)
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()
addSetUserId()
}
private fun addMinimapSizeSlider() {
optionsTable.add("Show minimap".tr())
private fun getModCheckTab() = Table(CameraStageBaseScreen.skin).apply {
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
// [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)
previousScreen.shouldUpdate = true
}
optionsTable.add(minimapSlider).pad(10f).row()
add(minimapSlider).pad(10f).row()
}
private fun 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())
private fun Table.addResolutionSelectBox() {
add("Resolution".toLabel()).left().fillX()
val resolutionSelectBox = SelectBox<String>(skin)
resolutionSelectBox.items = resolutionArray
resolutionSelectBox.selected = settings.resolution
add(resolutionSelectBox).minWidth(selectBoxMinWidth).pad(10f).row()
resolutionSelectBox.onChange {
settings.resolution = resolutionSelectBox.selected
reloadWorldAndOptions()
}
}
private fun Table.addTileSetSelectBox() {
add("Tileset".toLabel()).left().fillX()
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) {
val generateTranslationsButton = "Generate translation files".toTextButton()
val generateAction = {
@ -259,218 +518,55 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
generateTranslationsButton.onClick(generateAction)
keyPressDispatcher[Input.Keys.F12] = generateAction
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() {
optionsTable.add("Sound effects volume".tr())
val soundEffectsVolumeSlider = UncivSlider(0f, 1.0f, 0.1f,
initial = settings.soundEffectsVolume
) {
settings.soundEffectsVolume = it
private fun Table.addYesNoRow(text: String, initialValue: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)) {
val wrapWidth = tabs.prefWidth - 60f
add(WrappableLabel(text, wrapWidth).apply { wrap = true })
.left().fillX()
.maxWidth(wrapWidth)
val button = YesNoButton(initialValue, CameraStageBaseScreen.skin) {
action(it)
settings.save()
if (updateWorld && previousScreen is WorldScreen)
previousScreen.shouldUpdate = true
}
optionsTable.add(soundEffectsVolumeSlider).pad(5f).row()
add(button).row()
}
private fun addMusicVolumeSlider() {
val musicLocation = Gdx.files.local(previousScreen.game.musicLocation)
if (musicLocation.exists()) {
optionsTable.add("Music volume".tr())
//endregion
val musicVolumeSlider = UncivSlider(0f, 1.0f, 0.1f,
initial = settings.musicVolume,
sound = UncivSound.Silent
) {
settings.musicVolume = it
settings.save()
/**
* 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 class YesNoButton(
initialValue: Boolean,
skin: Skin,
action: (Boolean) -> Unit
) : TextButton (initialValue.toYesNo(), skin ) {
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
var value = initialValue
private set(value) {
field = value
setText(value.toYesNo())
}
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 {
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 {
rebuildOptionsTable()
previousScreen.game.startMusic()
}
} catch (ex: Exception) {
Gdx.app.postRunnable {
errorTable.clear()
errorTable.add("Could not download music!".toLabel(Color.RED))
}
}
}
init {
color = ImageGetter.getBlue()
onClick {
value = !value
action.invoke(value)
}
}
}
private fun addResolutionSelectBox() {
optionsTable.add("Resolution".toLabel())
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)
companion object {
fun Boolean.toYesNo(): String = (if (this) Constants.yes else Constants.no).tr()
}
}
}