diff --git a/.github/workflows/buildAndDeploy.yml b/.github/workflows/buildAndDeploy.yml
index 01a86dfa57a28..3c9ad90c6571b 100644
--- a/.github/workflows/buildAndDeploy.yml
+++ b/.github/workflows/buildAndDeploy.yml
@@ -132,7 +132,7 @@ jobs:
keyPassword: ${{ secrets.BUNDLE_SIGNING_KEY_PASSWORD }}
- name: Upload AAB to google play
- uses: r0adkll/upload-google-play@v1.0.15
+ uses: r0adkll/upload-google-play@v1.1.2
continue-on-error: true
with:
serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
@@ -140,6 +140,7 @@ jobs:
releaseFiles: android/build/outputs/bundle/release/Unciv-release.aab
track: production
userFraction: 0.1
+ status: inProgress
whatsNewDirectory: whatsNewDirectory
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 09e615327e60c..8cf946f5021b0 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -7,7 +7,6 @@
-
@@ -34,4 +33,4 @@
-
\ No newline at end of file
+
diff --git a/android/Images.UnitPromotionIcons/UnitPromotionIcons/Fallback.png b/android/Images.UnitPromotionIcons/UnitPromotionIcons/Fallback.png
new file mode 100644
index 0000000000000..f3fd7fa07b39c
Binary files /dev/null and b/android/Images.UnitPromotionIcons/UnitPromotionIcons/Fallback.png differ
diff --git a/android/assets/UnitPromotionIcons.atlas b/android/assets/UnitPromotionIcons.atlas
index 87864d620add7..aa45f211997f7 100644
--- a/android/assets/UnitPromotionIcons.atlas
+++ b/android/assets/UnitPromotionIcons.atlas
@@ -165,28 +165,35 @@ UnitPromotionIcons/Operational Range
orig: 50, 50
offset: 0, 0
index: -1
-UnitPromotionIcons/Flight Deck
+UnitPromotionIcons/Fallback
rotate: false
xy: 1156, 62
size: 50, 50
orig: 50, 50
offset: 0, 0
index: -1
-UnitPromotionIcons/Formation
+UnitPromotionIcons/Great Generals
rotate: false
- xy: 1214, 62
+ xy: 1156, 62
size: 50, 50
orig: 50, 50
offset: 0, 0
index: -1
-UnitPromotionIcons/Great Generals
+UnitPromotionIcons/Quick Study
rotate: false
- xy: 1272, 62
+ xy: 1156, 62
size: 50, 50
orig: 50, 50
offset: 0, 0
index: -1
-UnitPromotionIcons/Quick Study
+UnitPromotionIcons/Flight Deck
+ rotate: false
+ xy: 1214, 62
+ size: 50, 50
+ orig: 50, 50
+ offset: 0, 0
+ index: -1
+UnitPromotionIcons/Formation
rotate: false
xy: 1272, 62
size: 50, 50
diff --git a/android/assets/UnitPromotionIcons.png b/android/assets/UnitPromotionIcons.png
index 2214c76e5d9de..32e890b463135 100644
Binary files a/android/assets/UnitPromotionIcons.png and b/android/assets/UnitPromotionIcons.png differ
diff --git a/android/assets/jsons/Civ V - Gods & Kings/Buildings.json b/android/assets/jsons/Civ V - Gods & Kings/Buildings.json
index e36c5edf02001..72743bf58b924 100644
--- a/android/assets/jsons/Civ V - Gods & Kings/Buildings.json
+++ b/android/assets/jsons/Civ V - Gods & Kings/Buildings.json
@@ -23,7 +23,7 @@
},
{
"name": "Stele",
- "replaces": "Monument",
+ "replaces": "Monument",
"uniqueTo": "Ethiopia",
"culture": 2,
"faith": 2,
@@ -458,7 +458,8 @@
"greatPersonPoints": {"Great Engineer": 1},
"isWonder": true,
"uniques": ["Gain a free [Mosque] [in this city]", "Hidden when religion is disabled",
- "[Missionary] units built [in this city] can [Spread Religion] [1] extra times", "[Great Prophet] units built [in this city] can [Spread Religion] [1] extra times"],
+ "All newly-trained [Missionary] units [in this city] receive the [Devout] promotion",
+ "All newly-trained [Great Prophet] units [in this city] receive the [Devout] promotion"],
"requiredTech": "Theology",
"quote": "'With the magnificence of eternity before us, let time, with all its fluctuations, dwindle into its own littleness.' - Thomas Chalmers"
},
diff --git a/android/assets/jsons/Civ V - Gods & Kings/Nations.json b/android/assets/jsons/Civ V - Gods & Kings/Nations.json
index 3beb204a5e999..9c36307b207b7 100644
--- a/android/assets/jsons/Civ V - Gods & Kings/Nations.json
+++ b/android/assets/jsons/Civ V - Gods & Kings/Nations.json
@@ -2,7 +2,7 @@
//Spectator
{
"name": "Spectator",
- "outerColor": [255,255,255]
+ "outerColor": [255,255,255],
// "innerColor": [255,255,255]
},
@@ -65,7 +65,7 @@
"uniques": ["[-50]% City-State Influence degradation", "City-State Influence recovers at twice the normal rate", "City-State territory always counts as friendly territory"],
"cities": ["Athens","Sparta","Corinth","Argos","Knossos","Mycenae","Pharsalos","Ephesus","Halicarnassus","Rhodes",
"Eretria","Pergamon","Miletos","Megara","Phocaea","Sicyon","Tiryns","Samos","Mytilene","Chios",
- "Paros","Elis","Syracuse","Herakleia","Gortyn","Chalkis","Pylos","Pella","Naxos","Sicyon",
+ "Paros","Elis","Syracuse","Herakleia","Gortyn","Chalkis","Pylos","Pella","Naxos",
"Larissa","Apollonia","Messene","Orchomenos","Ambracia","Kos","Knidos","Amphipolis",
"Patras","Lamia","Nafplion","Apolyton"],
"spyNames": ["Jason", "Helena", "Alexa", "Cletus", "Kassandra", "Andres", "Desdemona", "Anthea", "Aeneas", "Leander"]
@@ -90,7 +90,7 @@
"outerColor": [9, 112, 84],
"innerColor": [255,255,255],
- "favouredReligion": "Taoism",
+ "favoredReligion": "Taoism",
"uniqueName": "Art of War",
"uniques": ["Great General provides double combat bonus", "[Great General] is earned [50]% faster"],
"cities": ["Beijing","Shanghai","Guangzhou","Nanjing","Xian","Chengdu","Hangzhou","Tianjin","Macau","Shandong",
@@ -128,7 +128,7 @@
"cities": ["Thebes","Memphis","Heliopolis","Elephantine","Alexandria","Pi-Ramesses","Giza","Byblos","Akhetaten",
"Hieraconpolis","Abydos","Asyut","Avaris","Lisht","Buto","Edfu","Pithom","Busiris","Kahun","Athribis",
"Mendes","Elashmunein","Tanis","Bubastis","Oryx","Sebennytus","Akhmin","Karnak","Luxor","El Kab","Armant",
- "Balat","Ellahun","Hawara","Dashur","Damanhur","Ellahun","Abusir","Ellahun","Herakleopolis","Akoris",
+ "Balat","Ellahun","Hawara","Dashur","Damanhur","Abusir","Herakleopolis","Akoris",
"Benihasan","Badari","Hermopolis","Amrah","Koptos","Ombos","Naqada","Semna","Soleb"],
"spyNames": ["Refaat", "Heba", "Salah", "Ahmed", "Zakaria", "Bastet", "Ma'at", "Nebhet", "Tefenet", "Neuth"]
},
@@ -452,7 +452,7 @@
"cities": ["Seoul","Busan","Jeonju","Daegu","Pyongyang","Kaesong","Suwon","Gwangju","Gangneung","Hamhung","Wonju","Ulsan",
"Changwon","Andong","Gongju","Haeju","Cheongju","Mokpo","Dongducheon","Geoje","Suncheon","Jinju","Sangju",
"Rason","Gyeongju","Chungju","Sacheon","Gimje","Anju"],
- "spyNames": ["Kim", "Park", "Han", "Na", "Kong", "Yu", "Ahn", "Na", "Da", "Eun"]
+ "spyNames": ["Kim", "Park", "Han", "Na", "Kong", "Yu", "Ahn", "Da", "Eun"]
},
{
@@ -708,16 +708,7 @@
"introduction": "How are you? You stand before Pachacuti Inca Yupanqui.",
"neutralHello": "How are you doing?",
- "neutralLetsHearIt": ["Go on.","What do you say?","You may begin."],
- "neutralNo": ["We absolutely refuse.","No!","I refuse."],
- "neutralYes": ["Very good.","Of course, yes.","That is very good."],
-
"hateHello": "What do you want now?",
- "hateLetsHearIt": ["Speak!","What do you say?","Go on."],
- "hateNo": ["That is not possible!","We are not agreeing to that!","I beg your pardon?"],
- "hateYes": ["That is fine.","That is good enough.","Very well..."],
-
- "afterPeace": "Viracocha has frowned upon our war, and has agreed to our peace agreements.",
"tradeRequest": "The Incan people offer this fair trade.",
"outerColor": [255,184,33],
@@ -732,7 +723,7 @@
"Andahuaylas","Ica","Arequipa","Nasca","Atico","Juli","Chuito","Chuquiapo","Huanuco Pampa","Tamboccocha",
"Huaras","Riobamba","Caxamalca","Sausa","Tambo Colorado","Huaca","Tumbes","Chan Chan","Sipan","Pachacamac",
"Llactapata","Pisac","Kuelap","Pajaten","Chucuito","Choquequirao"],
- "spyNames": ["Amaru", "Apichu", "Pariapichiu", "Puma", "Quenti", "Suyuntu", "Uturuncu", "Uturuncu", "Purutu", "Ozcollo"]
+ "spyNames": ["Amaru", "Apichu", "Pariapichiu", "Puma", "Quenti", "Suyuntu", "Uturuncu", "Purutu", "Ozcollo"]
},
{
"name": "Denmark",
@@ -954,18 +945,10 @@
"defeated": "Vile ruler, know you have won this war in name alone. Your cities lie buried and your troops defeated. I have my own victory.",
"introduction": "I am Boudicca, Queen of the Celts. Let no-one underestimate me!",
"tradeRequest": "Let us join our forces together and reap the rewards.",
- "afterPeace": "Well played, fellow warrior.",
"neutralHello": "God has given good to you.",
- "neutralLetsHearIt": ["We are all ears…","We are all ears…","Forward."],
- "neutralNo": ["We must decline.","No.","We refuse."],
- "neutralYes": ["Okay.","Sure, it shall be so.","Accepted."],
"hateHello": "Well?",
- "hateLetsHearIt": ["Forward! Before I change my mind!","And?","Speak!"],
- "hateNo": ["That's unacceptable!","A thousand times no!","Never!"],
- "hateYes": ["Eugh! Fine then…","Disgusting! Okay then…","But if I have to…"],
-
"outerColor": [12,75,25],
"innerColor": [157,172,255],
@@ -995,17 +978,10 @@
"defeated": "God and history will remember your actions this day. I hope you are ready for your impending judgment.",
"introduction": "A thousand welcomes to our fair nation. I am Selassie, the Ras Tafari Makonnen and Emperor of Ethiopia, your humble servant.",
"tradeRequest": "I request that you consider this offer between our two peoples. I believe it will do us both good.",
- "afterPeace": "Today is the day on which we defeated our enemy. However, we shall only rejoice in our hearts, and in the spirit of heaven.",
"neutralHello": "Welcome.",
- "neutralLetsHearIt": ["Continue.","I am listening.","Yes?"],
- "neutralNo": ["It is not right.","We oppose because it is not right.","But I feel the agreement is done."],
- "neutralYes": ["Great.","An agreement has been reached.","Our heart and soul is very much satisfied."],
"hateHello": "What do you want?",
- "hateLetsHearIt": ["You said it.","What?","Continue."],
- "hateNo": ["That is unacceptable.","You are not sure about it.","Please repeat for me."],
- "hateYes": ["Fine, in my understanding.","It is clear.","It is clear."],
"outerColor": [18, 66, 22],
"innerColor": [255, 56, 56],
@@ -1036,18 +1012,6 @@
"tradeRequest": "Friend, I believe I may have found a way to save us all! Look, look and accept my offering!",
"neutralHello": "A fine day, it helps you.",
-/* Not used by Unciv, and uncertain
- "afterPeace": "",
-
- "neutralLetsHearIt": ["If you must show me."],
- "neutralNo": ["No no, too much trouble.","No!","Not good enough."],
- "neutralYes": ["Okay.","Fine.","Accepted."],
-
- "hateHello": "You.",
- "hateLetsHearIt": ["Talk.","So?","Speak!"],
- "hateNo": ["That's unacceptable!","A thousand times no!","Never!"],
- "hateYes": ["Oh… Fine, okay."],
-*/
"outerColor": [198, 141, 99],
"innerColor": [24, 63, 66],
"favoredReligion": "Christianity",
@@ -1126,7 +1090,6 @@
},
{
"name": "Lhasa",
- "translatedName": "Lhasa",
"adjective": ["tibetano"],
"cityStateType": "Cultured",
@@ -1250,7 +1213,6 @@
},
{
"name": "Vancouver",
- "translatedName": "Vancouver",
"adjective": ["Vancouverite"],
"cityStateType": "Maritime",
diff --git a/android/assets/jsons/Civ V - Gods & Kings/UnitPromotions.json b/android/assets/jsons/Civ V - Gods & Kings/UnitPromotions.json
index c6739b13d7234..8414b359de1f0 100644
--- a/android/assets/jsons/Civ V - Gods & Kings/UnitPromotions.json
+++ b/android/assets/jsons/Civ V - Gods & Kings/UnitPromotions.json
@@ -707,6 +707,10 @@
"name": "Home Sweet Home", // only for Mehal Sefari and subsequent upgrades
"uniques": ["[+30]% Strength decreasing with distance from the capital"]
},
+ {
+ "name": "Devout",
+ "uniques": ["Can Spread Religion <[1] additional time(s)>"]
+ },
{
"name": "[Mohawk Warrior] ability",
"uniques": ["[+33]% Strength ", "[+33]% Strength "]
diff --git a/android/assets/jsons/Civ V - Gods & Kings/Units.json b/android/assets/jsons/Civ V - Gods & Kings/Units.json
index 3c7cf42f5dc30..2cfc4fb767e3e 100644
--- a/android/assets/jsons/Civ V - Gods & Kings/Units.json
+++ b/android/assets/jsons/Civ V - Gods & Kings/Units.json
@@ -1636,10 +1636,10 @@
"unitType": "Civilian",
"uniques": [
"Can instantly construct a [Holy site] improvement ",
- "Can [Spread Religion] [4] times",
+ "Can Spread Religion <[4] times> ",
"Removes other religions when spreading religion",
- "May found a religion",
- "May enhance a religion",
+ "May found a religion ",
+ "May enhance a religion ",
"May enter foreign tiles without open borders", "[-1] Sight", "Great Person - [Faith]",
"Unbuildable", "Religious Unit", "Hidden when religion is disabled",
"Takes your religion over the one in their birth city"],
@@ -1675,7 +1675,9 @@
{
"name": "Missionary",
"unitType": "Civilian",
- "uniques": ["Can [Spread Religion] [2] times", "May enter foreign tiles without open borders, but loses [250] religious strength each turn it ends there",
+ "uniques": [
+ "Can Spread Religion <[2] times> ",
+ "May enter foreign tiles without open borders, but loses [250] religious strength each turn it ends there",
"Can be purchased with [Faith] [in all cities in which the majority religion is a major religion]",
"[-1] Sight", "Unbuildable", "Religious Unit", "Hidden when religion is disabled"],
"movement": 4,
@@ -1685,8 +1687,8 @@
"name": "Inquisitor",
"unitType": "Civilian",
"uniques": ["Prevents spreading of religion to the city it is next to",
- "Can [Remove Foreign religions from your own cities] [1] times",
- "Can be purchased with [Faith] [in all cities in which the majority religion is an enhanced religion]",
+ "Can remove other religions from cities ",
+ "Can be purchased with [Faith] [in all cities in which the majority religion is a major religion]",
"[+1] Sight", "Hidden when religion is disabled", "Unbuildable", "Religious Unit"
],
"movement": 3
diff --git a/android/assets/jsons/Civ V - Vanilla/Nations.json b/android/assets/jsons/Civ V - Vanilla/Nations.json
index 5ddc6cf19e251..9d22ce18b096e 100644
--- a/android/assets/jsons/Civ V - Vanilla/Nations.json
+++ b/android/assets/jsons/Civ V - Vanilla/Nations.json
@@ -62,7 +62,7 @@
"uniques": ["[-50]% City-State Influence degradation", "City-State Influence recovers at twice the normal rate", "City-State territory always counts as friendly territory"],
"cities": ["Athens","Sparta","Corinth","Argos","Knossos","Mycenae","Pharsalos","Ephesus","Halicarnassus","Rhodes",
"Eretria","Pergamon","Miletos","Megara","Phocaea","Sicyon","Tiryns","Samos","Mytilene","Chios",
- "Paros","Elis","Syracuse","Herakleia","Gortyn","Chalkis","Pylos","Pella","Naxos","Sicyon",
+ "Paros","Elis","Syracuse","Herakleia","Gortyn","Chalkis","Pylos","Pella","Naxos",
"Larissa","Apollonia","Messene","Orchomenos","Ambracia","Kos","Knidos","Amphipolis",
"Patras","Lamia","Nafplion","Apolyton"]
},
@@ -120,7 +120,7 @@
"cities": ["Thebes","Memphis","Heliopolis","Elephantine","Alexandria","Pi-Ramesses","Giza","Byblos","Akhetaten",
"Hieraconpolis","Abydos","Asyut","Avaris","Lisht","Buto","Edfu","Pithom","Busiris","Kahun","Athribis",
"Mendes","Elashmunein","Tanis","Bubastis","Oryx","Sebennytus","Akhmin","Karnak","Luxor","El Kab","Armant",
- "Balat","Ellahun","Hawara","Dashur","Damanhur","Ellahun","Abusir","Ellahun","Herakleopolis","Akoris",
+ "Balat","Ellahun","Hawara","Dashur","Damanhur","Abusir","Herakleopolis","Akoris",
"Benihasan","Badari","Hermopolis","Amrah","Koptos","Ombos","Naqada","Semna","Soleb"]
},
{
@@ -658,16 +658,8 @@
"introduction": "How are you? You stand before Pachacuti Inca Yupanqui.",
"neutralHello": "How are you doing?",
- "neutralLetsHearIt": ["Go on.","What do you say?","You may begin."],
- "neutralNo": ["We absolutely refuse.","No!","I refuse."],
- "neutralYes": ["Very good.","Of course, yes.","That is very good."],
"hateHello": "What do you want now?",
- "hateLetsHearIt": ["Speak!","What do you say?","Go on."],
- "hateNo": ["That is not possible!","We are not agreeing to that!","I beg your pardon?"],
- "hateYes": ["That is fine.","That is good enough.","Very well..."],
-
- "afterPeace": "Viracocha has frowned upon our war, and has agreed to our peace agreements.",
"tradeRequest": "The Incan people offer this fair trade.",
"outerColor": [255,184,33],
@@ -776,7 +768,6 @@
},
{
"name": "Lhasa",
- "translatedName": "Lhasa",
"adjective": ["tibetano"],
"cityStateType": "Cultured",
@@ -900,7 +891,6 @@
},
{
"name": "Vancouver",
- "translatedName": "Vancouver",
"adjective": ["Vancouverite"],
"cityStateType": "Maritime",
diff --git a/android/assets/jsons/Civ V - Vanilla/Units.json b/android/assets/jsons/Civ V - Vanilla/Units.json
index 8ef7308d82a6c..b0d9e5110f27d 100644
--- a/android/assets/jsons/Civ V - Vanilla/Units.json
+++ b/android/assets/jsons/Civ V - Vanilla/Units.json
@@ -1289,21 +1289,6 @@
"Great Person - [Production]", "Unbuildable", "Uncapturable"],
"movement": 2
},
- {
- "name": "Great Prophet",
- "unitType": "Civilian",
- "uniques": [
- "Can instantly construct a [Holy site] improvement ",
- "Can [Spread Religion] [4] times",
- "Removes other religions when spreading religion",
- "May found a religion",
- "May enhance a religion",
- "May enter foreign tiles without open borders", "[-1] Sight", "Great Person - [Faith]",
- "Unbuildable", "Religious Unit", "Hidden when religion is disabled",
- "Takes your religion over the one in their birth city"],
- "movement": 2,
- "religiousStrength": 1000
- },
{
"name": "Great General",
"unitType": "Civilian",
@@ -1328,28 +1313,6 @@
"movement": 5
},
- /* Religious units */
-
- {
- "name": "Missionary",
- "unitType": "Civilian",
- "uniques": ["Can [Spread Religion] [2] times", "May enter foreign tiles without open borders, but loses [250] religious strength each turn it ends there",
- "Can be purchased with [Faith] [in all cities in which the majority religion is a major religion]",
- "[-1] Sight", "Unbuildable", "Religious Unit", "Hidden when religion is disabled"],
- "movement": 4,
- "religiousStrength": 1000
- },
- {
- "name": "Inquisitor",
- "unitType": "Civilian",
- "uniques": ["Prevents spreading of religion to the city it is next to",
- "Can [Remove Foreign religions from your own cities] [1] times",
- "Can be purchased with [Faith] [in all cities in which the majority religion is an enhanced religion]",
- "[+1] Sight", "Hidden when religion is disabled", "Unbuildable", "Religious Unit"
- ],
- "movement": 3
- }
-
/* Spaceship Parts */
// Must be transported to the Capital for launch.
// Can be destroyed by any hostile military unit walking in their tile.
diff --git a/android/assets/jsons/Tutorials.json b/android/assets/jsons/Tutorials.json
index f61e0de97ac0c..66a5052f11e99 100644
--- a/android/assets/jsons/Tutorials.json
+++ b/android/assets/jsons/Tutorials.json
@@ -292,7 +292,8 @@
"A pantheon will provide a small bonus for your civilization that will apply to all cities that have it as a majority religion.",
"Each civilization can only choose a single pantheon belief, and each pantheon can only be chosen once.",
"Generating more ☮Faith will allow you to found a religion."
- ]
+ ],
+ "uniques": ["Hidden when religion is disabled"]
},
{
"name": "Religion",
@@ -305,14 +306,16 @@
"One of these great prophets can then be used to enhance your religion.",
"This will allow you to choose another follower belief, as well as an enhancer belief, that only applies to you.",
"Do take care founding a religion soon, only about half the players in the game are able to found a religion!"
- ]
+ ],
+ "uniques": ["Hidden when religion is disabled"]
},
{
"name": "Beliefs",
"steps": [
"There are four types of beliefs: Pantheon, Founder, Follower and Enhancer beliefs.",
"Pantheon and Follower beliefs apply to each city following your religion, while Founder and Enhancer beliefs only apply to the founder of a religion."
- ]
+ ],
+ "uniques": ["Hidden when religion is disabled"]
},
{
"name": "Religion inside cities",
@@ -324,7 +327,8 @@
"In both places, a tap/click on the icon of a religion will show detailed information with its effects.",
"Based on this, you can get a feel for which religions have a lot of pressure built up in the city, and which have almost none.",
"The city follows a religion if a majority of its population follows that religion, and will only then receive the effects of Follower and Pantheon beliefs of that religion."
- ]
+ ],
+ "uniques": ["Hidden when religion is disabled"]
},
{
"name": "Spreading Religion",
@@ -345,7 +349,8 @@
"Holy cities also provide +30 pressure of the religion founded there to themselves, making it very difficult to effectively convert a holy city.",
"Lastly, before founding a religion, new cities you settle will start with 200 pressure for your pantheon.",
"This way, all your cities will starting following your pantheon as long as you haven't founded a religion yet."
- ]
+ ],
+ "uniques": ["Hidden when religion is disabled"]
},
{
"name": "Inquisitors",
@@ -355,7 +360,8 @@
"Great prophets also have this ability, and remove all other religions in the city when spreading their religion.",
"Often this results in the city immediately converting to their religion",
"Additionally, when an inquisitor is stationed in or directly next to a city center, units of other religions cannot spread their faith there, though natural spread is uneffected."
- ]
+ ],
+ "uniques": ["Hidden when religion is disabled"]
},
{
"name": "Maya Long Count calendar cycle",
diff --git a/android/assets/jsons/translations/Afrikaans.properties b/android/assets/jsons/translations/Afrikaans.properties
index 6dd4ecd4aa563..63604d63c173a 100644
--- a/android/assets/jsons/translations/Afrikaans.properties
+++ b/android/assets/jsons/translations/Afrikaans.properties
@@ -1023,6 +1023,9 @@ Show zoom buttons in world screen =
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1353,7 +1356,13 @@ Your city [cityName] can bombard the enemy! =
# Requires translation!
[amount] enemy units were spotted in our territory =
# Requires translation!
-A(n) [nukeType] exploded in our territory! =
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
# Requires translation!
After being hit by our [nukeType], [civName] has declared war on us! =
# Requires translation!
@@ -1363,6 +1372,8 @@ The City-State of [name] has been destroyed! =
# Requires translation!
Your [ourUnit] captured an enemy [theirUnit]! =
# Requires translation!
+Your captured [unitName] has been returned by [civName] =
+ # Requires translation!
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] =
# Requires translation!
We have captured a barbarian encampment and recovered [goldAmount] gold! =
@@ -3350,8 +3361,6 @@ May not generate great prophet equivalents naturally =
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
# Requires translation!
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times =
- # Requires translation!
Starting tech =
# Requires translation!
Starts with [tech] =
@@ -3434,14 +3443,18 @@ Founds a new city =
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
# Requires translation!
-Can build [improvementFilter/terrainFilter] improvements on tiles =
+Can Spread Religion =
# Requires translation!
-May create improvements on water resources =
+Can remove other religions from cities =
# Requires translation!
May found a religion =
# Requires translation!
May enhance a religion =
# Requires translation!
+Can build [improvementFilter/terrainFilter] improvements on tiles =
+ # Requires translation!
+May create improvements on water resources =
+ # Requires translation!
Can be added to [comment] in the Capital =
# Requires translation!
Prevents spreading of religion to the city it is next to =
@@ -3452,8 +3465,6 @@ May Paradrop up to [amount] tiles from inside friendly territory =
# Requires translation!
Can perform Air Sweep =
# Requires translation!
-Can [action] [amount] times =
- # Requires translation!
Can speed up construction of a building =
# Requires translation!
Can speed up the construction of a wonder =
@@ -4166,6 +4177,8 @@ FollowerBelief =
# Requires translation!
Building =
# Requires translation!
+UnitAction =
+ # Requires translation!
Unit =
# Requires translation!
UnitType =
@@ -8994,21 +9007,12 @@ Great Merchant =
# Requires translation!
Great Engineer =
- # Requires translation!
-Great Prophet =
-
# Requires translation!
Great General =
# Requires translation!
Khan =
- # Requires translation!
-Missionary =
-
- # Requires translation!
-Inquisitor =
-
# Requires translation!
SS Booster =
@@ -9409,6 +9413,8 @@ Wu =
Zhou =
# Requires translation!
Sun =
+ # Requires translation!
+Taoism =
# Requires translation!
Refaat =
@@ -10808,9 +10814,6 @@ Judaism =
# Requires translation!
Sikhism =
- # Requires translation!
-Taoism =
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -10923,6 +10926,10 @@ Truffles =
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
# Requires translation!
Hussar =
@@ -11011,6 +11018,17 @@ Machine Gun =
Landship =
+ # Requires translation!
+Great Prophet =
+
+
+ # Requires translation!
+Missionary =
+
+ # Requires translation!
+Inquisitor =
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Belarusian.properties b/android/assets/jsons/translations/Belarusian.properties
index 15a36e323a89d..3a1d3c1151e4e 100644
--- a/android/assets/jsons/translations/Belarusian.properties
+++ b/android/assets/jsons/translations/Belarusian.properties
@@ -1249,6 +1249,9 @@ Show zoom buttons in world screen =
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1579,7 +1582,13 @@ Your city [cityName] can bombard the enemy! =
# Requires translation!
[amount] enemy units were spotted in our territory =
# Requires translation!
-A(n) [nukeType] exploded in our territory! =
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
# Requires translation!
After being hit by our [nukeType], [civName] has declared war on us! =
# Requires translation!
@@ -1589,6 +1598,8 @@ The City-State of [name] has been destroyed! =
# Requires translation!
Your [ourUnit] captured an enemy [theirUnit]! =
# Requires translation!
+Your captured [unitName] has been returned by [civName] =
+ # Requires translation!
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] =
# Requires translation!
We have captured a barbarian encampment and recovered [goldAmount] gold! =
@@ -3576,8 +3587,6 @@ May not generate great prophet equivalents naturally =
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
# Requires translation!
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times =
- # Requires translation!
Starting tech =
# Requires translation!
Starts with [tech] =
@@ -3660,14 +3669,18 @@ Founds a new city =
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
# Requires translation!
-Can build [improvementFilter/terrainFilter] improvements on tiles =
+Can Spread Religion =
# Requires translation!
-May create improvements on water resources =
+Can remove other religions from cities =
# Requires translation!
May found a religion =
# Requires translation!
May enhance a religion =
# Requires translation!
+Can build [improvementFilter/terrainFilter] improvements on tiles =
+ # Requires translation!
+May create improvements on water resources =
+ # Requires translation!
Can be added to [comment] in the Capital =
# Requires translation!
Prevents spreading of religion to the city it is next to =
@@ -3678,8 +3691,6 @@ May Paradrop up to [amount] tiles from inside friendly territory =
# Requires translation!
Can perform Air Sweep =
# Requires translation!
-Can [action] [amount] times =
- # Requires translation!
Can speed up construction of a building =
# Requires translation!
Can speed up the construction of a wonder =
@@ -4392,6 +4403,8 @@ FollowerBelief =
# Requires translation!
Building =
# Requires translation!
+UnitAction =
+ # Requires translation!
Unit =
# Requires translation!
UnitType =
@@ -9246,21 +9259,12 @@ Great Merchant =
# Requires translation!
Great Engineer =
- # Requires translation!
-Great Prophet =
-
# Requires translation!
Great General =
# Requires translation!
Khan =
- # Requires translation!
-Missionary =
-
- # Requires translation!
-Inquisitor =
-
# Requires translation!
SS Booster =
@@ -9661,6 +9665,8 @@ Wu =
Zhou =
# Requires translation!
Sun =
+ # Requires translation!
+Taoism =
# Requires translation!
Refaat =
@@ -11060,9 +11066,6 @@ Judaism =
# Requires translation!
Sikhism =
- # Requires translation!
-Taoism =
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -11175,6 +11178,10 @@ Truffles =
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
# Requires translation!
Hussar =
@@ -11263,6 +11270,17 @@ Machine Gun =
Landship =
+ # Requires translation!
+Great Prophet =
+
+
+ # Requires translation!
+Missionary =
+
+ # Requires translation!
+Inquisitor =
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Brazilian_Portuguese.properties b/android/assets/jsons/translations/Brazilian_Portuguese.properties
index fd7cd4567cbb7..1b1809a3cf24a 100644
--- a/android/assets/jsons/translations/Brazilian_Portuguese.properties
+++ b/android/assets/jsons/translations/Brazilian_Portuguese.properties
@@ -748,6 +748,8 @@ Reset = Resetar
Show zoom buttons in world screen = Mostrar botões de zoom na tela do mundo
Experimental Demographics scoreboard = Placar de dados demográficos experimentais
+Size of Unitset art in Civilopedia = Tamanho da arte do conjunto de unidades na Civilopedia
+
### Visual Hints subgroup
Visual Hints = Dicas Visuais
@@ -929,11 +931,15 @@ Your city [cityName] can bombard the enemy! = Sua cidade [cityName] pode bombard
[amount] of your cities can bombard the enemy! = [amount] cidade(s) podem bombardear o inimgo!
[amount] enemy units were spotted near our territory = [amount] unidades inimigas foram observadas perto de nosso território
[amount] enemy units were spotted in our territory = [amount] unidades inimigas foram observadas em nosso território
-A(n) [nukeType] exploded in our territory! = Uma [nukeType] explodiu no seu território!
+A(n) [nukeType] from [civName] has exploded in our territory! = Um(a) [nukeType] do(a) [civName] explodiu em nosso território!
+A(n) [nukeType] has been detonated by [civName]! = Um(a) [nukeType] foi detonado por [civName]!
+A(n) [nukeType] has been detonated by an unkown civilization! = Um(a) [nukeType] foi detonado por uma civilização desconhecida!
+After an attempted attack by our [nukeType], [civName] has declared war on us! = Após uma tentativa de ataque do nosso(a) [nukeType], [civName] declarou guerra contra nós!
After being hit by our [nukeType], [civName] has declared war on us! = Depois de serem atacados por nossa [nukeType], [civName] declarou guerra contra nós!
The civilization of [civName] has been destroyed! = A civilização de [civName] foi destruida!
The City-State of [name] has been destroyed! = A Cidade-estado do(a) [name] foi destruída!
Your [ourUnit] captured an enemy [theirUnit]! = Seu(a) [ourUnit] capturou um inimigo [theirUnit]!
+Your captured [unitName] has been returned by [civName] = Seu(a) [unitName] capturado(a) foi retornado(a) por [civName]
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Seu(a) [ourUnit] saqueou [amount] [Stat] de [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Nós capturamos um acampamento bárbaro e recuperamos [goldAmount] de ouro!
An enemy [unitType] has joined us! = Um(a) inimigo(a) [unitType] se juntou a nós!
@@ -1414,8 +1420,7 @@ Civilization Info = Informações da civilizção
Relations = Relações
Trade request = Solicitação de troca
Garrisoned by unit = Guarnecido por unidade
- # Requires translation!
-Status\n(puppet, resistance or being razed) =
+Status\n(puppet, resistance or being razed) = Status\n(fantoche, resistindo ou sendo destruido)
# Victory
@@ -1997,7 +2002,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = A religião naturalmente se espalha para as cidades a [amount] painéis de distância
May not generate great prophet equivalents naturally = Pode não gerar Grandes equivalentes de Profetas naturalmente
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% custo de fé para gerar equivalentes do Grande Profeta
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [baseUnitFilter] unidades construídas [cityFilter] podem [action] [amount] vezes extra
Starting tech = Tecnologia inicial
Starts with [tech] = Começa com [tech]
Starts with [policy] adopted = Começa com [policy] adotada
@@ -2039,16 +2043,17 @@ Automatically built in all cities where it is buildable = Construído automatica
Creates a [improvementName] improvement on a specific tile = Cria uma melhoria [improvementName] em um painel específico
Founds a new city = Estabelecer uma nova cidade
Can instantly construct a [improvementFilter] improvement = Pode construir instantaneamente uma melhoria [improvementFilter]
-Can build [improvementFilter/terrainFilter] improvements on tiles = Pode construir melhorias [improvementFilter/terrainFilter] em painéis
-May create improvements on water resources = Pode criar melhorias em recursos aquáticos
+Can Spread Religion = Pode espalhar a religião
+Can remove other religions from cities = Pode remover outras religiões das cidades
May found a religion = Pode fundar uma religião
May enhance a religion = Pode melhorar uma religião
+Can build [improvementFilter/terrainFilter] improvements on tiles = Pode construir melhorias [improvementFilter/terrainFilter] em painéis
+May create improvements on water resources = Pode criar melhorias em recursos aquáticos
Can be added to [comment] in the Capital = Pode ser adicionado a [comment] na Capital
Prevents spreading of religion to the city it is next to = Impede a propagação da religião para a cidade que fica ao lado
Removes other religions when spreading religion = Remove outras religiões ao espalhar religião
May Paradrop up to [amount] tiles from inside friendly territory = Pode saltar até [amount] de painéis dentro do território aliado
Can perform Air Sweep = Pode executar varredura de ar
-Can [action] [amount] times = Pode [action] [amount] vezes
Can speed up construction of a building = Pode acelerar a construção de um edifício
Can speed up the construction of a wonder = Pode acelerar a construção de uma maravilha
Can hurry technology research = Pode apressar pesquisa tecnológica
@@ -2423,6 +2428,7 @@ Policy = Política
FounderBelief = Crença Fundadora
FollowerBelief = Crença do Seguidor
Building = Construção
+UnitAction = Ação da Unidade
Unit = Unidade
UnitType = Tipo de Unidade
Promotion = Promoção
@@ -5142,16 +5148,10 @@ Great Merchant = Grande Mercador
Great Engineer = Grande Engenheiro
-Great Prophet = Grande Profeta
-
Great General = Grande General
Khan = Cã
-Missionary = Missionário
-
-Inquisitor = Inquisidor
-
SS Booster = Propulsor de Nave Espacial
SS Cockpit = Cabine de Nave Espacial
@@ -5415,6 +5415,7 @@ Zhao = Zhao
Wu = Wu
Zhou = Zhou
Sun = Sun
+Taoism = Taoísmo
Refaat = Refaat
Heba = Heba
@@ -6139,8 +6140,6 @@ Judaism = Judaismo
Sikhism = Sikhismo
-Taoism = Taoísmo
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6223,6 +6222,9 @@ Truffles = Trufas
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+Devout = Devoto(a)
+
+
Hussar = Hussardo
@@ -6289,6 +6291,14 @@ Machine Gun = Metralhadora robusta
Landship = Tanque da 1a Guerra Mundial
+Great Prophet = Grande Profeta
+
+
+Missionary = Missionário
+
+Inquisitor = Inquisidor
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Bulgarian.properties b/android/assets/jsons/translations/Bulgarian.properties
index 7c7b08f2003d1..0f09807041f84 100644
--- a/android/assets/jsons/translations/Bulgarian.properties
+++ b/android/assets/jsons/translations/Bulgarian.properties
@@ -366,8 +366,7 @@ Domination = Доминация
Cultural = Културни
Diplomatic = Дипломация
Time = Време
- # Requires translation!
-Your previous options needed to be reset to defaults. =
+Your previous options needed to be reset to defaults. = Предишните ви настройки трябваше да бъдат върнати по подразбиране
# Used for random nation indicator in empire selector and unknown nation icons in various overview screens.
# Should be a single character, or at least visually square.
@@ -375,11 +374,9 @@ Your previous options needed to be reset to defaults. =
? =
Map Shape = Форма на Карта
- # Requires translation!
-Enabled Map Shapes =
+Enabled Map Shapes = Включени Форми на Карта
Hexagonal = Шестоъгълна
- # Requires translation!
-Flat Earth Hexagonal =
+Flat Earth Hexagonal = Плоска Шестоъгълна Земя
Rectangular = Правоъгълна
Height = Височина
Width = Ширина
@@ -393,8 +390,7 @@ Sparse = Оскъдни
Abundant = Изобилни
Strategic Balance = Стратегически Баланс
Legendary Start = Легендарен Старт
- # Requires translation!
-This is used for painting resources, not in map generator steps: =
+This is used for painting resources, not in map generator steps: = Това се използва за рисуването на ресурси, не в стъпките за генериране на карта:
Advanced Settings = Разширени Настройки
# Requires translation!
@@ -410,9 +406,8 @@ Biome areas extension = Разширяване на растителна пло
Water level = Ниво на водата
Online Multiplayer = Онлайн Мултиплейър
- # Requires translation!
-You're currently using the default multiplayer server, which is based on a free Dropbox account. Because a lot of people use this, it is uncertain if you'll actually be able to access it consistently. Consider using a custom server instead. =
-Open Documentation = Отворена Документация
+You're currently using the default multiplayer server, which is based on a free Dropbox account. Because a lot of people use this, it is uncertain if you'll actually be able to access it consistently. Consider using a custom server instead. = В момента използвате мултиплейър сървъра по подразбиране, който се базира на безплатен Dropbox акаунт. Понеже много хора използват това, не е ясно дали всъщност ще е възможно да получавате достъп постоянно. Помислете за
+Open Documentation = Отворена Документацияличен сървър като опция вместо това.
Don't show again = Не показвай отново
World Size = Размер на Света
@@ -455,24 +450,16 @@ Mods: = Модове:
Extension mods = Допълнителни модове
Base ruleset: = Базов набор от правила:
# Note - do not translate the colour names between «». Changing them works if you know what you're doing.
- # Requires translation!
-The mod you selected is incorrectly defined! =
- # Requires translation!
-The mod you selected is «RED»incorrectly defined!«» =
- # Requires translation!
-The mod combination you selected is incorrectly defined! =
- # Requires translation!
-The mod combination you selected is «RED»incorrectly defined!«» =
+The mod you selected is incorrectly defined! = Модът, който избрахте, е неправилно дефиниран!
+The mod you selected is «RED»incorrectly defined!«» = Модът, който избрахте, е «RED»неправилно дефиниран!
+The mod combination you selected is incorrectly defined! = Комбинацията от модове, която избрахте, е неправилно дефинирана!
+The mod combination you selected is «RED»incorrectly defined!«» = Комбинацията от модове, която избрахте, е «RED»неправилно дефинирана!
The mod combination you selected has problems. = Комбинацията от модове, която сте избрали, има проблеми.
You can play it, but don't expect everything to work! = Можете да играете, но не очаквайте всичко да работи!
- # Requires translation!
-The mod combination you selected «GOLD»has problems«». =
- # Requires translation!
-You can play it, but «GOLDENROD»don't expect everything to work!«» =
- # Requires translation!
-This base ruleset is not compatible with the previously selected\nextension mods. They have been disabled. =
- # Requires translation!
-Are you really sure you want to play with the following known problems? =
+The mod combination you selected «GOLD»has problems«». = Комбинацията от модове, която сте избрали, «GOLD»има проблеми«».
+You can play it, but «GOLDENROD»don't expect everything to work!«» = Можете да играете, но «GOLDENROD»не очаквайте всичко да работи!«»
+This base ruleset is not compatible with the previously selected\nextension mods. They have been disabled. = Този базов набор от правила не е съвместим с предишните избрани\nдопълнителни модове. Те бяха изключени.
+Are you really sure you want to play with the following known problems? = Сигурни ли сте, че искате да играете със следните проблеми?
Base Ruleset = Базов Набор от Правила
[amount] Techs = [amount] Технологии
[amount] Nations = [amount] Нации
@@ -512,22 +499,18 @@ Are you sure you want to delete this map? = Изтриване на картат
It looks like your map can't be saved! = Изглежда, че твоята карта не може да бъде запазена!
Exit map editor = Изход от редактора
Change map ruleset = Промени наборът от правила на картата
-Change the map to use the ruleset selected on this page = Промете картата, за да използвате наборът от правила на тази страница.
- # Requires translation!
-Revert to map ruleset =
+Change the map to use the ruleset selected on this page = Променете картата, за да използвате наборът от правила на тази страница
+Revert to map ruleset = Връщане на набора от правила за карта
# Requires translation!
Reset the controls to reflect the current map ruleset =
- # Requires translation!
-Features =
+Features = Характеристики
Starting locations = Начални локации
# Requires translation!
Tile Matching Criteria =
Complete match = Пълно съвпадение
Except improvements = С изключение на подобренията
- # Requires translation!
-Base and terrain features =
- # Requires translation!
-Base terrain only =
+Base and terrain features = Базови и теренни характеристики
+Base terrain only = Само базов терен
Land or water only = Само земя или вода
## Labels/messages
@@ -565,14 +548,10 @@ The incompatible elements have been removed. = Несъвместимите ел
Current map: World Wrap =
# Requires translation!
Overlay image =
- # Requires translation!
-Click to choose a file =
- # Requires translation!
-Choose an image =
- # Requires translation!
-Overlay opacity: =
- # Requires translation!
-Invalid overlay image =
+Click to choose a file = Натисни за да избереш файл
+Choose an image = Избери изображение
+Overlay opacity: = Непрозрачност на наслагването:
+Invalid overlay image = Невалидно изображение за наслагване
# Requires translation!
World wrap is incompatible with an overlay and was deactivated. =
# Requires translation!
@@ -622,8 +601,7 @@ Username = Потребителско Име
Multiplayer = Multiplayer
Could not download game! = Изтеглянето не е възможно!
Could not upload game! = Качването не е възможно!
- # Requires translation!
-Couldn't connect to Multiplayer Server! =
+Couldn't connect to Multiplayer Server! = Неуспешно свързване към сървъра!
Retry = Опитай пак
Join game = Присъединяване
Invalid game ID! = Невалидно ID!
@@ -633,20 +611,14 @@ UserID copied to clipboard = UserID е копирано
Game ID copied to clipboard! = GameID е копирано
Friend name = Име на приятел
Player ID = ID на играч
- # Requires translation!
-Please input a name for your friend! =
- # Requires translation!
-Please input a player ID for your friend! =
- # Requires translation!
-Are you sure you want to delete this friend? =
+Please input a name for your friend! = Моля въведете име за Вашия приятел!
+Please input a player ID for your friend! = Моля въведете ID на играч за Вашия приятел!
+Are you sure you want to delete this friend? = Сигурни ли сте, че искате да изтриете този приятел?
# Requires translation!
Paste player ID from clipboard =
- # Requires translation!
-Player name already used! =
- # Requires translation!
-Player ID already used! =
- # Requires translation!
-Player ID is incorrect =
+Player name already used! = Това име за играч вече е заето!
+Player ID already used! = Това ID за играч вече е заето!
+Player ID is incorrect = ID на играча е грешно
Select friend = Изберете приятел
# Requires translation!
Select [thingToSelect] =
@@ -861,6 +833,9 @@ Show zoom buttons in world screen =
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1119,7 +1094,13 @@ Your city [cityName] can bombard the enemy! =
[amount] enemy units were spotted near our territory = [amount] вражески единици бяха забелязани в близост до наша територия
[amount] enemy units were spotted in our territory = [amount] вражески единици бяха забелязани в наша територия
# Requires translation!
-A(n) [nukeType] exploded in our territory! =
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
# Requires translation!
After being hit by our [nukeType], [civName] has declared war on us! =
The civilization of [civName] has been destroyed! = Цивилизацията [civName] беше унищожена!
@@ -1127,6 +1108,8 @@ The City-State of [name] has been destroyed! = Градът [name] беше ун
# Requires translation!
Your [ourUnit] captured an enemy [theirUnit]! =
# Requires translation!
+Your captured [unitName] has been returned by [civName] =
+ # Requires translation!
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] =
We have captured a barbarian encampment and recovered [goldAmount] gold! = Превзехме варварски лагер и добавихме [goldAmount] злато!
# Requires translation!
@@ -2683,8 +2666,6 @@ May not generate great prophet equivalents naturally =
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
# Requires translation!
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times =
- # Requires translation!
Starting tech =
# Requires translation!
Starts with [tech] =
@@ -2766,14 +2747,18 @@ Founds a new city =
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
# Requires translation!
-Can build [improvementFilter/terrainFilter] improvements on tiles =
+Can Spread Religion =
# Requires translation!
-May create improvements on water resources =
+Can remove other religions from cities =
# Requires translation!
May found a religion =
# Requires translation!
May enhance a religion =
# Requires translation!
+Can build [improvementFilter/terrainFilter] improvements on tiles =
+ # Requires translation!
+May create improvements on water resources =
+ # Requires translation!
Can be added to [comment] in the Capital =
# Requires translation!
Prevents spreading of religion to the city it is next to =
@@ -2784,8 +2769,6 @@ May Paradrop up to [amount] tiles from inside friendly territory =
# Requires translation!
Can perform Air Sweep =
# Requires translation!
-Can [action] [amount] times =
- # Requires translation!
Can speed up construction of a building =
# Requires translation!
Can speed up the construction of a wonder =
@@ -3464,6 +3447,8 @@ FollowerBelief =
# Requires translation!
Building =
# Requires translation!
+UnitAction =
+ # Requires translation!
Unit =
# Requires translation!
UnitType =
@@ -7218,16 +7203,10 @@ Great Merchant = Велик Търговец
Great Engineer = Велик Инженер
-Great Prophet = Велик Пророк
-
Great General = Велик Генерал
Khan = Кан
-Missionary = Мисионер
-
-Inquisitor = Инквизитор
-
SS Booster = КК Ускорител
SS Cockpit = КК Кабина
@@ -7494,18 +7473,15 @@ Alhambra =
Ceilidh Hall =
- # Requires translation!
-Leaning Tower of Pisa =
- # Requires translation!
-'Don't clap too hard - it's a very old building.' - John Osbourne =
+Leaning Tower of Pisa = Наклонената Кула в Пиза
+'Don't clap too hard - it's a very old building.' - John Osbourne = 'Не пляскай твърде силно - това е много стара сграда.' - Джон Озборн
# Requires translation!
Coffee House =
- # Requires translation!
-Neuschwanstein =
+Neuschwanstein = Нойшванщайн
# Requires translation!
'...the location is one of the most beautiful to be found, holy and unapproachable, a worthy temple for the divine friend who has brought salvation and true blessing to the world.' - King Ludwig II of Bavaria =
@@ -7609,6 +7585,7 @@ Wu = Ву
# Requires translation!
Zhou =
Sun = Сун
+Taoism = Даоизъм
# Requires translation!
Refaat =
@@ -8919,8 +8896,6 @@ Judaism = Юдаизъм
Sikhism = Сикхизъм
-Taoism = Даоизъм
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -9027,6 +9002,10 @@ Truffles = Трюфели
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
# Requires translation!
Hussar =
@@ -9112,6 +9091,14 @@ Machine Gun = Картечница
Landship =
+Great Prophet = Велик Пророк
+
+
+Missionary = Мисионер
+
+Inquisitor = Инквизитор
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Catalan.properties b/android/assets/jsons/translations/Catalan.properties
index 37af03f124a7f..45eb4e88c9312 100644
--- a/android/assets/jsons/translations/Catalan.properties
+++ b/android/assets/jsons/translations/Catalan.properties
@@ -748,6 +748,9 @@ Reset = Restableix
Show zoom buttons in world screen = Mostra els botons de zoom a la vista del món
Experimental Demographics scoreboard = Utilitza la puntuació demogràfica experimental
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
Visual Hints = Ajuts visuals
@@ -929,11 +932,20 @@ Your city [cityName] can bombard the enemy! = [cityName] pot bombardejar l’ene
[amount] of your cities can bombard the enemy! = [amount] ciutats vostres poden bombardejar l’enemic!
[amount] enemy units were spotted near our territory = s’han vist [amount] unitats enemigues a prop del nostre territori.
[amount] enemy units were spotted in our territory = s’han vist [amount] unitats enemigues al nostre territori.
-A(n) [nukeType] exploded in our territory! = Ha explotat un [nukeType] al nostre territori!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Després que la nostra [nukeType] hagi impactat al seu territori, [civName] ens ha declarat la guerra!
The civilization of [civName] has been destroyed! = La civilització [civName] ha sigut destruïda!
The City-State of [name] has been destroyed! = La ciutat-estat de [name] ha sigut destruïda!
Your [ourUnit] captured an enemy [theirUnit]! = El vostre [ourUnit] ha capturat un [theirUnit] enemic.
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = El vostre [ourUnit] ha saquejat [amount] [Stat] de [theirUnit].
We have captured a barbarian encampment and recovered [goldAmount] gold! = Hem capturat un campament bàrbar i aconseguim [goldAmount] d’or!
An enemy [unitType] has joined us! = Se’ns ha unit un [unitType] enemic!
@@ -1997,7 +2009,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = La religió es difon de manera natural a ciutats que estiguin fins a [amount] caselles de distància.
May not generate great prophet equivalents naturally = No es generaran equivalents dels grans profetes de manera natural.
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount] % de punts de fe per generar equivalents de grans profetes
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = Les unitats [baseUnitFilter] construïdes en [cityFilter] poden [action] [amount] vegades addicionals
Starting tech = Tecnologia inicial
Starts with [tech] = Comença amb [tech].
Starts with [policy] adopted = Comença amb [policy] ja adoptada.
@@ -2039,16 +2050,19 @@ Automatically built in all cities where it is buildable = Es construeix automàt
Creates a [improvementName] improvement on a specific tile = Crea una millora [improvementName] en una casella específica.
Founds a new city = Funda una ciutat nova
Can instantly construct a [improvementFilter] improvement = Pot construir una millora de [improvementFilter] immediatament
-Can build [improvementFilter/terrainFilter] improvements on tiles = Pot construir millores de casella com ara [improvementFilter/terrainFilter].
-May create improvements on water resources = Crea millores en recursos aquàtics
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Pot fundar una religió.
May enhance a religion = Pot difondre una religió.
+Can build [improvementFilter/terrainFilter] improvements on tiles = Pot construir millores de casella com ara [improvementFilter/terrainFilter].
+May create improvements on water resources = Crea millores en recursos aquàtics
Can be added to [comment] in the Capital = Es pot col·locar a [comment] a la capital
Prevents spreading of religion to the city it is next to = Evita la difusió de la religió a les ciutats que tingui adjacents
Removes other religions when spreading religion = Treu les altres religions quan difon la seva
May Paradrop up to [amount] tiles from inside friendly territory = Es pot llançar a una distància de fins a [amount] caselles des de territori amic.
Can perform Air Sweep = Pot realitzar escombrats aeris.
-Can [action] [amount] times = La unitat pot realitzar l’acció «[action]» [amount] vegades
Can speed up construction of a building = Pot accelerar la construcció d’un edifici
Can speed up the construction of a wonder = Pot accelerar la construcció d’una meravella
Can hurry technology research = Pot accelerar la recerca científica
@@ -2423,6 +2437,8 @@ Policy = Política
FounderBelief = Creença de fundador
FollowerBelief = Creença de seguidor
Building = Edifici
+ # Requires translation!
+UnitAction =
Unit = Unitat
UnitType = Tipus d’unitat
Promotion = Ascens
@@ -5142,16 +5158,10 @@ Great Merchant = Gran mercader
Great Engineer = Gran enginyer
-Great Prophet = Gran profeta
-
Great General = Gran general
Khan = Khan
-Missionary = Missioner
-
-Inquisitor = Inquisidor
-
SS Booster = (NE) Propulsor
SS Cockpit = (NE) Cabina
@@ -5415,6 +5425,7 @@ Zhao = Zhao
Wu = Wu
Zhou = Zhou
Sun = Sun
+Taoism = Taoisme
Refaat = Refaat
Heba = Heba
@@ -6139,8 +6150,6 @@ Judaism = Judaisme
Sikhism = Sikhisme
-Taoism = Taoisme
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6223,6 +6232,10 @@ Truffles = Trufes
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Hússar
@@ -6289,6 +6302,14 @@ Machine Gun = Metralladora
Landship = Tanc Mark I
+Great Prophet = Gran profeta
+
+
+Missionary = Missioner
+
+Inquisitor = Inquisidor
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Czech.properties b/android/assets/jsons/translations/Czech.properties
index 88cac40734033..2ea95fd9cbb0e 100644
--- a/android/assets/jsons/translations/Czech.properties
+++ b/android/assets/jsons/translations/Czech.properties
@@ -756,6 +756,9 @@ Reset = Restartovat
Show zoom buttons in world screen = Zobrazit tlačítka zoomu na obrazovce světa
Experimental Demographics scoreboard = Experimentální demografický žebříček
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
Visual Hints = Vizuální pomůcky
@@ -944,11 +947,20 @@ Your city [cityName] can bombard the enemy! = Vaše město [cityName] může bom
[amount] of your cities can bombard the enemy! = [amount] vašich měst může bombardovat nepřítele!
[amount] enemy units were spotted near our territory = [amount] nepřátelských jednotek bylo spatřeno poblíž našeho území
[amount] enemy units were spotted in our territory = [amount] nepřátelských jednotek bylo spatřeno na našem území
-A(n) [nukeType] exploded in our territory! = [nukeType] vybuchla na našem území!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Protože ji naše [nukeType] zasáhlala, civilizace [civName] nám vyhlásila válku!
The civilization of [civName] has been destroyed! = Civilizace [civName] byla zničena!
The City-State of [name] has been destroyed! = Městský stát [name] byl zničen!
Your [ourUnit] captured an enemy [theirUnit]! = Jednotka [ourUnit] zajala nepřátelskou jednotku [theirUnit]!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Jednotka [ourUnit] vydrancovala [amount] [Stat] od jednotky [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Zneškodnili jsme barbarský tábor a získali [goldAmount] zlata
An enemy [unitType] has joined us! = Nepřátelská jednotka [unitType] se k nám přidala!
@@ -2257,7 +2269,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = Náboženství se přirozeně šíří do měst [amount] políček daleko
May not generate great prophet equivalents naturally = Nesmí přirozeně generovat ekvivalenty velkých proroků
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% náklady na víru při generování ekvivalentů Velkého proroka
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = Jednotky [baseUnitFilter] postavené [cityFilter] mohou [action] [amount] krát navíc
Starting tech = Výchozí technologie
Starts with [tech] = Začíná s technologií [tech]
# Requires translation!
@@ -2306,17 +2317,20 @@ Creates a [improvementName] improvement on a specific tile =
Founds a new city = Zakládá nové město
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
-Can build [improvementFilter/terrainFilter] improvements on tiles = Může budovat [improvementFilter/terrainFilter] vylepšení políček
-May create improvements on water resources = Může vybudovat vylepšení na vodních políčkách se surovinami
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Může založit náboženství
May enhance a religion = Může vylepšit náboženství
+Can build [improvementFilter/terrainFilter] improvements on tiles = Může budovat [improvementFilter/terrainFilter] vylepšení políček
+May create improvements on water resources = Může vybudovat vylepšení na vodních políčkách se surovinami
Can be added to [comment] in the Capital = Může být přidáno do [comment] v hlavním městě
Prevents spreading of religion to the city it is next to = Brání šíření náboženství do města, u kterého stojí
Removes other religions when spreading religion = Při rozšiřování náboženství odstraní jiná
May Paradrop up to [amount] tiles from inside friendly territory = Může provést Výsadek až [amount] políček od přátelského území
# Requires translation!
Can perform Air Sweep =
-Can [action] [amount] times = Může [action] [amount] krát
Can speed up construction of a building = Může uspíšit stavbu budovy
# Requires translation!
Can speed up the construction of a wonder =
@@ -2810,6 +2824,8 @@ FollowerBelief =
# Requires translation!
Building =
# Requires translation!
+UnitAction =
+ # Requires translation!
Unit =
# Requires translation!
UnitType =
@@ -5632,16 +5648,10 @@ Great Merchant = Velký obchodník
Great Engineer = Velký inženýr
-Great Prophet = Velký prorok
-
Great General = Velký generál
Khan = Chán
-Missionary = Misionář
-
-Inquisitor = Inkvizitor
-
SS Booster = Pomocný motor
SS Cockpit = Kokpit lodi
@@ -5944,6 +5954,7 @@ Wu =
Zhou =
# Requires translation!
Sun =
+Taoism = Taoismus
# Requires translation!
Refaat =
@@ -6975,8 +6986,6 @@ Judaism = Judaismus
Sikhism = Sikhismus
-Taoism = Taoismus
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -7059,6 +7068,10 @@ Truffles = Lanýže
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Husar
@@ -7126,6 +7139,14 @@ Machine Gun = Kulometčík
Landship = Pozemní obrněnec
+Great Prophet = Velký prorok
+
+
+Missionary = Misionář
+
+Inquisitor = Inkvizitor
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Dutch.properties b/android/assets/jsons/translations/Dutch.properties
index 68e4bd88733e3..1300898278537 100644
--- a/android/assets/jsons/translations/Dutch.properties
+++ b/android/assets/jsons/translations/Dutch.properties
@@ -748,6 +748,9 @@ Reset = Opnieuw instellen
Show zoom buttons in world screen = Toon zoomknoppen in het wereldscherm
Experimental Demographics scoreboard = Experimenteel Demografisch scorebord
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
Visual Hints = Visuele Hints
@@ -929,11 +932,19 @@ Your city [cityName] can bombard the enemy! = Jouw stad [cityName] kan de vijand
[amount] of your cities can bombard the enemy! = [amount] van jouw steden kunnen de vijand bombarderen
[amount] enemy units were spotted near our territory = [amount] vijandelijke eenheden werden gezien in de buurt van ons territorium
[amount] enemy units were spotted in our territory = [amount] vijandelijke eenheden werden gespot op ons grondgebied
-A(n) [nukeType] exploded in our territory! = Een [nukeType] is in ons gebied ontploft!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Na getroffen te zijn door onze [nukeType], heeft [civName] ons de oorlog verklaard!
The civilization of [civName] has been destroyed! = De [civName] beschaving is vernietigt!
The City-State of [name] has been destroyed! = De stadstaat van [name] is vernietigd!
Your [ourUnit] captured an enemy [theirUnit]! = Je [ourUnit] heeft zojuist een [theirUnit] gevangen genomen!
+Your captured [unitName] has been returned by [civName] = Je gevangengenomen [unitName] is teruggebracht door [civName]
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Je [ourUnit] heeft [amount] [Stat] van [theirUnit] geplunderd
We have captured a barbarian encampment and recovered [goldAmount] gold! = We hebben een barabarenkamp veroverd en bekwamen [goldAmount] goud!
An enemy [unitType] has joined us! = Een vijandige [unitType] heeft zich bij ons aangesloten!
@@ -1414,8 +1425,7 @@ Civilization Info = Beschaving informatie
Relations = Relaties
Trade request = Handelsverzoek
Garrisoned by unit = Versterkt door eenheid
- # Requires translation!
-Status\n(puppet, resistance or being razed) =
+Status\n(puppet, resistance or being razed) = Status\n(vazal, opstand of aan het verwoest worden)
# Victory
@@ -1997,7 +2007,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = Religie verspreidt zich vanzelf naar steden op [amount] tegels afstand
May not generate great prophet equivalents naturally = Kan de equivalenten van een grote profeet niet spontaan genereren
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% Geloofskosten voor het genereren van Groot Profeet equivalenten
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [baseUnitFilter] eenheden gebouwd in [cityFilter] kunnen [action] [amount] extra keren
Starting tech = Starttechnologie
Starts with [tech] = Begin het spel met [tech]
Starts with [policy] adopted = Start met [policy] aangenomen
@@ -2039,16 +2048,19 @@ Automatically built in all cities where it is buildable = Wordt automatisch gebo
Creates a [improvementName] improvement on a specific tile = Plaatst een [improvementName] verbetering op een specifieke tegel
Founds a new city = Sticht een nieuwe stad
Can instantly construct a [improvementFilter] improvement = Kan onmiddelijk een [improvementFilter] verbetering bouwen
-Can build [improvementFilter/terrainFilter] improvements on tiles = Kan [improvementFilter/terrainFilter] verbeteringen bouwen op tegels
-May create improvements on water resources = Kan verbeteringen op watergrondstoffen maken
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Kan een religie stichten
May enhance a religion = Kan een religie uitbreiden
+Can build [improvementFilter/terrainFilter] improvements on tiles = Kan [improvementFilter/terrainFilter] verbeteringen bouwen op tegels
+May create improvements on water resources = Kan verbeteringen op watergrondstoffen maken
Can be added to [comment] in the Capital = Kan worden toegevoegd aan [comment] in de Hoofdstad
Prevents spreading of religion to the city it is next to = Verhinderd de verspreiding van religie naar de stad waar het naast staat
Removes other religions when spreading religion = Verwijderd andere religies bij het verspreiden van een religie
May Paradrop up to [amount] tiles from inside friendly territory = Kan gedropt worden tot [amount] tegels vanaf bevriend gebied
Can perform Air Sweep = Kan Luchtaanval uitvoeren
-Can [action] [amount] times = Kan [action] [amount] keer
Can speed up construction of a building = Kan de bouw van een gebouw versnellen
Can speed up the construction of a wonder = Kan de bouw van een wonder versnellen
Can hurry technology research = Can technologisch onderzoek versnellen
@@ -2423,6 +2435,8 @@ Policy = Policy
FounderBelief = FounderBelief
FollowerBelief = FollowerBelief
Building = Building
+ # Requires translation!
+UnitAction =
Unit = Unit
UnitType = UnitType
Promotion = Promotion
@@ -5142,16 +5156,10 @@ Great Merchant = Grote Handelaar
Great Engineer = Grote Ingenieur
-Great Prophet = Grote Profeet
-
Great General = Grote Generaal
Khan = Khan
-Missionary = Zendeling
-
-Inquisitor = Inquisiteur
-
SS Booster = SS Booster
SS Cockpit = SS Cockpit
@@ -5415,6 +5423,7 @@ Zhao = Zhao
Wu = Wu
Zhou = Zhou
Sun = Sun
+Taoism = Taoïsme
Refaat = Refaat
Heba = Heba
@@ -6139,8 +6148,6 @@ Judaism = Jodendom
Sikhism = Sikhisme
-Taoism = Taoïsme
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6223,6 +6230,10 @@ Truffles = Truffels
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Huzaar
@@ -6289,6 +6300,14 @@ Machine Gun = Machinegeweer
Landship = Landschip
+Great Prophet = Grote Profeet
+
+
+Missionary = Zendeling
+
+Inquisitor = Inquisiteur
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/English.properties b/android/assets/jsons/translations/English.properties
index 203d317d4e9a2..fc990e75b582c 100644
--- a/android/assets/jsons/translations/English.properties
+++ b/android/assets/jsons/translations/English.properties
@@ -1371,6 +1371,9 @@ Show zoom buttons in world screen =
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1701,7 +1704,13 @@ Your city [cityName] can bombard the enemy! =
# Requires translation!
[amount] enemy units were spotted in our territory =
# Requires translation!
-A(n) [nukeType] exploded in our territory! =
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
# Requires translation!
After being hit by our [nukeType], [civName] has declared war on us! =
# Requires translation!
@@ -1711,6 +1720,8 @@ The City-State of [name] has been destroyed! =
# Requires translation!
Your [ourUnit] captured an enemy [theirUnit]! =
# Requires translation!
+Your captured [unitName] has been returned by [civName] =
+ # Requires translation!
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] =
# Requires translation!
We have captured a barbarian encampment and recovered [goldAmount] gold! =
@@ -3699,8 +3710,6 @@ May not generate great prophet equivalents naturally =
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
# Requires translation!
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times =
- # Requires translation!
Starting tech =
# Requires translation!
Starts with [tech] =
@@ -3783,14 +3792,18 @@ Founds a new city =
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
# Requires translation!
-Can build [improvementFilter/terrainFilter] improvements on tiles =
+Can Spread Religion =
# Requires translation!
-May create improvements on water resources =
+Can remove other religions from cities =
# Requires translation!
May found a religion =
# Requires translation!
May enhance a religion =
# Requires translation!
+Can build [improvementFilter/terrainFilter] improvements on tiles =
+ # Requires translation!
+May create improvements on water resources =
+ # Requires translation!
Can be added to [comment] in the Capital =
# Requires translation!
Prevents spreading of religion to the city it is next to =
@@ -3801,8 +3814,6 @@ May Paradrop up to [amount] tiles from inside friendly territory =
# Requires translation!
Can perform Air Sweep =
# Requires translation!
-Can [action] [amount] times =
- # Requires translation!
Can speed up construction of a building =
# Requires translation!
Can speed up the construction of a wonder =
@@ -4515,6 +4526,8 @@ FollowerBelief =
# Requires translation!
Building =
# Requires translation!
+UnitAction =
+ # Requires translation!
Unit =
# Requires translation!
UnitType =
@@ -9365,21 +9378,12 @@ Great Merchant =
# Requires translation!
Great Engineer =
- # Requires translation!
-Great Prophet =
-
# Requires translation!
Great General =
# Requires translation!
Khan =
- # Requires translation!
-Missionary =
-
- # Requires translation!
-Inquisitor =
-
# Requires translation!
SS Booster =
@@ -9780,6 +9784,8 @@ Wu =
Zhou =
# Requires translation!
Sun =
+ # Requires translation!
+Taoism =
# Requires translation!
Refaat =
@@ -11179,9 +11185,6 @@ Judaism =
# Requires translation!
Sikhism =
- # Requires translation!
-Taoism =
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -11294,6 +11297,10 @@ Truffles =
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
# Requires translation!
Hussar =
@@ -11382,6 +11389,17 @@ Machine Gun =
Landship =
+ # Requires translation!
+Great Prophet =
+
+
+ # Requires translation!
+Missionary =
+
+ # Requires translation!
+Inquisitor =
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Filipino.properties b/android/assets/jsons/translations/Filipino.properties
index 46fd2b934181b..bc27c116c5cb2 100644
--- a/android/assets/jsons/translations/Filipino.properties
+++ b/android/assets/jsons/translations/Filipino.properties
@@ -794,6 +794,9 @@ Reset = Magsimula muli
Show zoom buttons in world screen = Ipakita ang zoom na button sa screen ng mundo.
Experimental Demographics scoreboard = Eksperimental na Talaan ng mga Demograpiko
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -986,11 +989,20 @@ Your city [cityName] can bombard the enemy! = Ang ating lungsod ng [cityName] ay
[amount] of your cities can bombard the enemy! = [amount] ng iyong mga lungsod ay maaaring mambomba ng kalaban!
[amount] enemy units were spotted near our territory = [amount] na kalaban ang namataan malapit sa ating sinasakupan
[amount] enemy units were spotted in our territory = [amount] na kalaban ang namataan sa loob ng ating sinasakupan
-A(n) [nukeType] exploded in our territory! = Isa(ng) [nukeType] ang sumabog sa loob ng ating sinasakupan!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Pagkatapos matamaan ng ating [nukeType], nagdeklara ng digmaan ang [civName] sa atin!
The civilization of [civName] has been destroyed! = Nawasak na ang sibilisasyon ng [civName]!
The City-State of [name] has been destroyed! = Nawasak na ang Lungsod-Estado ng [name]!
Your [ourUnit] captured an enemy [theirUnit]! = Nadakip ng ating [ourUnit] ang kalaban na [theirUnit]!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Dumambong ang ating [ourUnit] ng [amount] [Stat] kay [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Nadakip natin ang kampo ng mga salbahe at nakalikom ng [goldAmount] na ginto!
An enemy [unitType] has joined us! = Isang kalaban na [unitType] ang sumali sa atin!
@@ -2118,7 +2130,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = natural na kumakalat ang relihiyon sa mga lungsod [amount] mga tile na layo
May not generate great prophet equivalents naturally = Hindi maaaring gumawa ng mga natural na pamalit sa mga dakilang propeta
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% gastos sa Paniniwala mula sa pagkuha ng Dakilang Propeta at mga katulad nito
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [baseUnitFilter] yunit na ginawa [cityFilter] ay maaaring [action] nang dagdag na [amount] beses
Starting tech = Panimulang teknolohiya
Starts with [tech] = Nagsisimula kasama ang [tech]
Starts with [policy] adopted = Nagsisimula sa pagtibay ng [policy]
@@ -2163,16 +2174,19 @@ Automatically built in all cities where it is buildable =
Creates a [improvementName] improvement on a specific tile = Gumagawa ng [improvementName] na pagbubuti sa isang tiyak na tile
Founds a new city = Nagtatatag ng bagong lungsod
Can instantly construct a [improvementFilter] improvement = Kayang gumawa kaagad ng isang pagbubuti na [improvementFilter]
-Can build [improvementFilter/terrainFilter] improvements on tiles = Maaaring magtayo ng [improvementFilter/terrainFilter] na pagbubuti sa mga tiles
-May create improvements on water resources = Maaaring lumikha ng mga pagbubuti sa mga pagkukunan na nasa tubig
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Maaaring magtatag ng isang relihiyon
May enhance a religion = Maaaring magpabuti ng isang relihiyon
+Can build [improvementFilter/terrainFilter] improvements on tiles = Maaaring magtayo ng [improvementFilter/terrainFilter] na pagbubuti sa mga tiles
+May create improvements on water resources = Maaaring lumikha ng mga pagbubuti sa mga pagkukunan na nasa tubig
Can be added to [comment] in the Capital = Kayang idagdag sa [comment] na nasa loob ng Kabisera
Prevents spreading of religion to the city it is next to = Pinipigilan ang pagkalat ng relihiyn sa lungsod na katabi nito
Removes other religions when spreading religion = Nagtatanggal ng ibang relihiyon tuwing nagpapalaganap ng relihiyon
May Paradrop up to [amount] tiles from inside friendly territory = Maaaring magparasyut hanggang [amount] na tile mula sa teritoryo ng kaibigan
Can perform Air Sweep = Kayang gumawa ng Air Sweep
-Can [action] [amount] times = Kayang [action] ng [amount] na beses
Can speed up construction of a building = Kayang bilisan ang pagtatayo ng isang gusali
Can speed up the construction of a wonder = Maaring magpabilis ng isang kamanghaan
Can hurry technology research = Kayang madaliin ang pagsasaliksik ng teknolohiya
@@ -2562,6 +2576,8 @@ Policy = Patakaran
FounderBelief = TagapagtatagNaPaniniwala
FollowerBelief = TagasunodNaPaniniwala
Building = Gusali
+ # Requires translation!
+UnitAction =
Unit = Yunit
UnitType = TipoNgUnit
Promotion = Promosyon
@@ -5351,16 +5367,10 @@ Great Merchant = Dakilang Mangangalakal
Great Engineer = Dakilang Inhinyero
-Great Prophet = Dakilang Propeta
-
Great General = Dakilang Heneral
Khan = Khan
-Missionary = Misyonero
-
-Inquisitor = Inkisitor
-
SS Booster = Booster ng SP
SS Cockpit = Cockpit ng SP
@@ -5624,6 +5634,7 @@ Zhao = Zhao
Wu = Wu
Zhou = Zhou
Sun = Sun
+Taoism = Taoismo
Refaat = Refaat
Heba = Heba
@@ -6348,8 +6359,6 @@ Judaism = Judaismo
Sikhism = Sikhismo
-Taoism = Taoismo
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6432,6 +6441,10 @@ Truffles = Trupel
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Hussar
@@ -6498,6 +6511,14 @@ Machine Gun = De-makinang Baril
Landship = Barkong Panlupa
+Great Prophet = Dakilang Propeta
+
+
+Missionary = Misyonero
+
+Inquisitor = Inkisitor
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Finnish.properties b/android/assets/jsons/translations/Finnish.properties
index b882289242168..96537fd790a64 100644
--- a/android/assets/jsons/translations/Finnish.properties
+++ b/android/assets/jsons/translations/Finnish.properties
@@ -1035,6 +1035,9 @@ Show zoom buttons in world screen =
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1291,11 +1294,20 @@ Your city [cityName] can bombard the enemy! =
[amount] of your cities can bombard the enemy! =
[amount] enemy units were spotted near our territory = [amount] vihollisen yksikkä havaittiin lähellä maitamme!
[amount] enemy units were spotted in our territory = [amount] vihollisen yksikkä havaittiin maillamme!
-A(n) [nukeType] exploded in our territory! = Ydinase [nukeType] on räjähtänyt maillamme!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Ydinaseemme [nukeType] räjähtämisen jälkeen, [civName] on julistanut meitä vastaan sodan!
The civilization of [civName] has been destroyed! = Sivilisaatio [civName] on tuhottu!
The City-State of [name] has been destroyed! = Kaupunkivaltio [name] on tuhottu!
Your [ourUnit] captured an enemy [theirUnit]! = Yksikkösi [ourUnit] on kaapannut vihollisyksikön [theirUnit]!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Yksikkösi [ourUnit] ryösti [amount] [Stat] vihollisyksiköltä [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Valloitimme barbaarileirin, ja saimme [goldAmount] kultaa!
# Requires translation!
@@ -2875,8 +2887,6 @@ Religion naturally spreads to cities [amount] tiles away =
May not generate great prophet equivalents naturally =
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
- # Requires translation!
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times =
Starting tech = Aloitusteknologia
# Requires translation!
Starts with [tech] =
@@ -2954,14 +2964,18 @@ Founds a new city = Perustaa uuden kaupungin
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
# Requires translation!
-Can build [improvementFilter/terrainFilter] improvements on tiles =
+Can Spread Religion =
# Requires translation!
-May create improvements on water resources =
+Can remove other religions from cities =
# Requires translation!
May found a religion =
# Requires translation!
May enhance a religion =
# Requires translation!
+Can build [improvementFilter/terrainFilter] improvements on tiles =
+ # Requires translation!
+May create improvements on water resources =
+ # Requires translation!
Can be added to [comment] in the Capital =
# Requires translation!
Prevents spreading of religion to the city it is next to =
@@ -2972,8 +2986,6 @@ May Paradrop up to [amount] tiles from inside friendly territory =
# Requires translation!
Can perform Air Sweep =
# Requires translation!
-Can [action] [amount] times =
- # Requires translation!
Can speed up construction of a building =
# Requires translation!
Can speed up the construction of a wonder =
@@ -3647,6 +3659,8 @@ FollowerBelief =
# Requires translation!
Building =
# Requires translation!
+UnitAction =
+ # Requires translation!
Unit =
# Requires translation!
UnitType =
@@ -7457,21 +7471,12 @@ Great Merchant = Merkittävä Kauppias
# Requires translation!
Great Engineer =
- # Requires translation!
-Great Prophet =
-
# Requires translation!
Great General =
# Requires translation!
Khan =
- # Requires translation!
-Missionary =
-
- # Requires translation!
-Inquisitor =
-
SS Booster = AA Raketti
SS Cockpit = AA Ohjaamo
@@ -7861,6 +7866,8 @@ Wu =
Zhou =
# Requires translation!
Sun =
+ # Requires translation!
+Taoism =
# Requires translation!
Refaat =
@@ -9260,9 +9267,6 @@ Judaism =
# Requires translation!
Sikhism =
- # Requires translation!
-Taoism =
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -9371,6 +9375,10 @@ Truffles = Tryffelit
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
# Requires translation!
Hussar =
@@ -9458,6 +9466,17 @@ Machine Gun =
Landship =
+ # Requires translation!
+Great Prophet =
+
+
+ # Requires translation!
+Missionary =
+
+ # Requires translation!
+Inquisitor =
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/French.properties b/android/assets/jsons/translations/French.properties
index aaa75b15911c2..f42b3fd429b17 100644
--- a/android/assets/jsons/translations/French.properties
+++ b/android/assets/jsons/translations/French.properties
@@ -748,6 +748,8 @@ Reset = Réinitialiser
Show zoom buttons in world screen = Afficher les boutons de zoom
Experimental Demographics scoreboard = Tableau comparatif des scores
+Size of Unitset art in Civilopedia = Taille des sprites d'unités dans la Civilopédia
+
### Visual Hints subgroup
Visual Hints = Visuels
@@ -929,11 +931,15 @@ Your city [cityName] can bombard the enemy! = Votre ville [cityName] peut bombar
[amount] of your cities can bombard the enemy! = [amount] de vos villes peuvent bombarder l'ennemi !
[amount] enemy units were spotted near our territory = [amount] unités ennemies repérées près de notre territoire
[amount] enemy units were spotted in our territory = [amount] unités ennemies repérées sur notre territoire
-A(n) [nukeType] exploded in our territory! = Un(e) [nukeType] a explosé sur notre territoire !
+A(n) [nukeType] from [civName] has exploded in our territory! = Un(e) [nukeType] de [civName] a explosé sur notre territoire !
+A(n) [nukeType] has been detonated by [civName]! = Un(e) [nukeType] a été utilisé(e) par [civName] !
+A(n) [nukeType] has been detonated by an unkown civilization! = Un(e) [nukeType] a été utilisé(e) par une civilisation inconnue !
+After an attempted attack by our [nukeType], [civName] has declared war on us! = Suite à notre tentative d'utiliser un(e) [nukeType], [civName] nous a déclaré la guerre !
After being hit by our [nukeType], [civName] has declared war on us! = Apres avoir été frappé par notre [nukeType], [civName] nous a déclaré la guerre !
The civilization of [civName] has been destroyed! = La civilisation [civName] a été détruite !
The City-State of [name] has been destroyed! = La Cité-État [name] a été détruite !
Your [ourUnit] captured an enemy [theirUnit]! = Notre [ourUnit] a capturé un(e) [theirUnit] ennemi(e) !
+Your captured [unitName] has been returned by [civName] = Votre [unitName] capturé(e) a été rendu(e) par [civName]
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Votre [ourUnit] a dépouillé [theirUnit] de [amount] [Stat]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Nous avons conquis un campement barbare et récolté [goldAmount] Or !
An enemy [unitType] has joined us! = Un(e) [unitType] ennemi(e) a rejoint nos rangs !
@@ -1211,7 +1217,7 @@ Stopped expansion = Expansion stoppée
Food converts to production = Nourriture convertie en production
[turnsToStarvation] turns to lose population = Perte de population dans [turnsToStarvation] tours
Stopped population growth = Population stagnante
-In resistance for another [numberOfTurns] turns = En révolte pour encore [numberOfTurns] tours
+In resistance for another [numberOfTurns] turns = En résistance pour encore [numberOfTurns] tours
We Love The King Day for another [numberOfTurns] turns = Fête du Roi pour encore [numberOfTurns] tours
Demanding [resource] = Demande [resource]
Sell for [sellAmount] gold = Vendre pour [sellAmount] or
@@ -1414,8 +1420,7 @@ Civilization Info = Info Civilisation
Relations = Relations
Trade request = Échanges proposés
Garrisoned by unit = Unité en garnison
- # Requires translation!
-Status\n(puppet, resistance or being razed) =
+Status\n(puppet, resistance or being razed) = Statut\n(fantoche, résistance, ou en démolition)
# Victory
@@ -1997,7 +2002,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = La religion se diffuse naturellement aux villes à [amount] cases que la normale
May not generate great prophet equivalents naturally = Ne peut pas générer l'équivalent des grands prophètes naturellement
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% coût en Foi pour générer l'équivalent de Grand Prophète
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = Les unités [baseUnitFilter] construites [cityFilter] peuvent [action] [amount] fois de plus
Starting tech = Technologie initiale
Starts with [tech] = Débute avec [tech]
Starts with [policy] adopted = Démarre avec la doctrine [policy] adoptée
@@ -2039,16 +2043,17 @@ Automatically built in all cities where it is buildable = Construit(e) automatiq
Creates a [improvementName] improvement on a specific tile = Crée un aménagement [improvementName] sur une case spécifique
Founds a new city = Fonde une nouvelle ville
Can instantly construct a [improvementFilter] improvement = Peut construire instantanément un(e) [improvementFilter]
-Can build [improvementFilter/terrainFilter] improvements on tiles = Peut bâtir des aménagements [improvementFilter/terrainFilter] sur les cases
-May create improvements on water resources = Peut créer des aménagements de ressources aquatiques
+Can Spread Religion = Peut diffuser la religion
+Can remove other religions from cities = Peut supprimer les autres religions dans les villes
May found a religion = Peut fonder une religion
May enhance a religion = Peut renforcer une religion
+Can build [improvementFilter/terrainFilter] improvements on tiles = Peut bâtir des aménagements [improvementFilter/terrainFilter] sur les cases
+May create improvements on water resources = Peut créer des aménagements de ressources aquatiques
Can be added to [comment] in the Capital = Peut être ajouté à [comment] dans la Capitale
Prevents spreading of religion to the city it is next to = Empêche la diffusion d'une religion dans la ville où il se trouve
Removes other religions when spreading religion = Supprime les autres religions en diffusant la sienne
May Paradrop up to [amount] tiles from inside friendly territory = Peut être parachuté jusqu'à [amount] cases depuis un territoire allié
Can perform Air Sweep = Peut effectuer un Balayage aérien
-Can [action] [amount] times = Peut [action] [amount] fois
Can speed up construction of a building = Peut accélérer la construction d'un bâtiment
Can speed up the construction of a wonder = Peut accélérer la construction d'une merveille
Can hurry technology research = Peut accélérer une recherche technologique
@@ -2148,7 +2153,7 @@ for [amount] movement = pour [amount] mouvement(s)
once = une fois
[amount] times = [amount] fois
[amount] additional time(s) = [amount] fois de plus
-after which this unit is consumed = après quoi cette unité est consommée
+after which this unit is consumed = puis cette unité est consommée
Grants 500 Gold to the first civilization to discover it = Accorde 500 Or à la première civilisation qui le découvre
Units ending their turn on this terrain take [amount] damage = Les unités terminant leur tour sur ce terrain subissent [amount] dégâts
Grants [promotion] ([comment]) to adjacent [mapUnitFilter] units for the rest of the game = Attribue [promotion] ([comment]) aux unités [mapUnitFilter] adjacentes pour le reste de la partie
@@ -2423,6 +2428,7 @@ Policy = Doctrine
FounderBelief = Croyance Fondateur
FollowerBelief = Croyance Fidèle
Building = Bâtiment
+UnitAction = Action unité
Unit = Unité
UnitType = Type Unité
Promotion = Promotion
@@ -5142,16 +5148,10 @@ Great Merchant = Grand Marchand
Great Engineer = Grand Ingénieur
-Great Prophet = Grand Prophète
-
Great General = Grand Général
Khan = Khan
-Missionary = Missionnaire
-
-Inquisitor = Inquisiteur
-
SS Booster = Propulseur (Vaisseau Spatial)
SS Cockpit = Cabine (Vaisseau Spatial)
@@ -5415,6 +5415,7 @@ Zhao = Zhao
Wu = Wu
Zhou = Zhou
Sun = Sun
+Taoism = Taoïsme
Refaat = Refaat
Heba = Heba
@@ -6139,8 +6140,6 @@ Judaism = Judaïsme
Sikhism = Sikhisme
-Taoism = Taoïsme
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6223,6 +6222,9 @@ Truffles = Truffes
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+Devout = Dévot
+
+
Hussar = Hussard
@@ -6289,6 +6291,14 @@ Machine Gun = Mitrailleuse
Landship = Char
+Great Prophet = Grand Prophète
+
+
+Missionary = Missionnaire
+
+Inquisitor = Inquisiteur
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/German.properties b/android/assets/jsons/translations/German.properties
index 25ed1ced251ad..c4d849424e42a 100644
--- a/android/assets/jsons/translations/German.properties
+++ b/android/assets/jsons/translations/German.properties
@@ -748,6 +748,9 @@ Reset = Zurücksetzen
Show zoom buttons in world screen = Zoom-Tasten in der Weltansicht anzeigen
Experimental Demographics scoreboard = Experimentelle Demographien-Übersicht
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
Visual Hints = Visuelle Hinweise
@@ -929,11 +932,15 @@ Your city [cityName] can bombard the enemy! = Deine Stadt [cityName] kann den Fe
[amount] of your cities can bombard the enemy! = [amount] deiner Städte können den Feind bombardieren!
[amount] enemy units were spotted near our territory = [amount] feindliche Einheiten wurden nahe unseres Territoriums entdeckt
[amount] enemy units were spotted in our territory = [amount] feindliche Einheiten wurden in unserem Territorium entdeckt
-A(n) [nukeType] exploded in our territory! = Eine [nukeType] ist in unserem Territorium explodiert!
+A(n) [nukeType] from [civName] has exploded in our territory! = Von [civName] explodierte eine [nukeType] in unserem Territorium!
+A(n) [nukeType] has been detonated by [civName]! = Von [civName] detonierte eine [nukeType].
+A(n) [nukeType] has been detonated by an unkown civilization! = Von einer unbekannten Zivilistation detonierte eine [nukeType].
+After an attempted attack by our [nukeType], [civName] has declared war on us! = Nach einem Anschlagsversuch durch unsere [nukeType], hat uns [civName] den Krieg erklärt.
After being hit by our [nukeType], [civName] has declared war on us! = Nach einem Treffer mit unserer [nukeType], hat [civName] uns den Krieg erklärt!
The civilization of [civName] has been destroyed! = Die Zivilisation [civName] wurde besiegt!
The City-State of [name] has been destroyed! = Der Stadtstaat von [name] wurde zerstört!
Your [ourUnit] captured an enemy [theirUnit]! = Deine [ourUnit] Einheit hat die gegnerische [theirUnit] Einheit gefangen!
+Your captured [unitName] has been returned by [civName] = Deine gefangene [unitName] Einheit wurde von [civName] zurückgebracht
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Deine [ourUnit] Einheit hat [amount] [Stat] von [theirUnit] Einheit geplündert
We have captured a barbarian encampment and recovered [goldAmount] gold! = Wir haben ein Lager der Barbaren erobert und [goldAmount] Gold gefunden!
An enemy [unitType] has joined us! = Eine feindliche [unitType] Einheit hat sich uns angeschlossen!
@@ -1414,8 +1421,7 @@ Civilization Info = Zivilisations-Informationen
Relations = Beziehungen
Trade request = Handelsanfrage
Garrisoned by unit = Besetzt durch Einheit
- # Requires translation!
-Status\n(puppet, resistance or being razed) =
+Status\n(puppet, resistance or being razed) = Status\n(Marionette, Widerstand oder Auslöschung)
# Victory
@@ -1997,7 +2003,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = Religion breitet sich auf natürliche Weise in [amount] Felder entfernte Städte aus
May not generate great prophet equivalents naturally = Darf keine 'Großer Prophet'-Äquivalente auf natürliche Weise erzeugen
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% Glaubenskosten für die Generierung eines Großen Propheten
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [baseUnitFilter] Einheiten, welche [cityFilter] gebaut wurden, können [action] [amount] Mal öfter ausführen
Starting tech = Start-Technologie
Starts with [tech] = Startet mit [tech]
Starts with [policy] adopted = Startet mit bereits verabschiedeter [policy] Politik
@@ -2039,16 +2044,19 @@ Automatically built in all cities where it is buildable = Automatisch gebaut in
Creates a [improvementName] improvement on a specific tile = Erschafft eine [improvementName] Verbesserung auf einem bestimmten Feld
Founds a new city = Gründet eine neue Stadt
Can instantly construct a [improvementFilter] improvement = Kann sofort eine [improvementFilter] Verbesserung bauen
-Can build [improvementFilter/terrainFilter] improvements on tiles = Kann [improvementFilter/terrainFilter] Verbesserungen auf Feldern bauen
-May create improvements on water resources = Kann Verbesserungen auf Ressourcen im Wasser bauen
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Darf eine Religion gründen
May enhance a religion = Kann eine Religion verbessern
+Can build [improvementFilter/terrainFilter] improvements on tiles = Kann [improvementFilter/terrainFilter] Verbesserungen auf Feldern bauen
+May create improvements on water resources = Kann Verbesserungen auf Ressourcen im Wasser bauen
Can be added to [comment] in the Capital = Kann zu [comment] in der Hauptstadt hinzugefügt werden
Prevents spreading of religion to the city it is next to = Verhindert die Verbreitung der Religion in der benachbarten Stadt
Removes other religions when spreading religion = Durch 'Religion verbreiten' werden anderen Religionen entfernt
May Paradrop up to [amount] tiles from inside friendly territory = Kann aus befreundetem Territorium heraus einen Fallschirmabsprung bis zu [amount] Felder weit durchführen
Can perform Air Sweep = Kann eine Luftraumsäuberung durchführen
-Can [action] [amount] times = Kann [action] [amount] Mal
Can speed up construction of a building = Kann den Bau eines Gebäudes beschleunigen
Can speed up the construction of a wonder = Kann den Bau eines Wunders beschleunigen
Can hurry technology research = Kann Erforschung von Technologien beschleunigen
@@ -2423,6 +2431,8 @@ Policy = Politik
FounderBelief = Gründerglaube
FollowerBelief = Anhängerglaube
Building = Gebäude
+ # Requires translation!
+UnitAction =
Unit = Einheit
UnitType = Einheitentyp
Promotion = Beförderung
@@ -5142,16 +5152,10 @@ Great Merchant = Großer Händler
Great Engineer = Großer Ingenieur
-Great Prophet = Großer Prophet
-
Great General = Großer General
Khan = Khan
-Missionary = Missionar
-
-Inquisitor = Inquisitor
-
SS Booster = Raumschiff-Booster
SS Cockpit = Raumschiff-Cockpit
@@ -5415,6 +5419,7 @@ Zhao = Zhao
Wu = Wu
Zhou = Zhou
Sun = Sun
+Taoism = Taoismus
Refaat = Refaat
Heba = Heba
@@ -6139,8 +6144,6 @@ Judaism = Judentum
Sikhism = Sikhismus
-Taoism = Taoismus
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6223,6 +6226,10 @@ Truffles = Trüffel
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Hussar
@@ -6289,6 +6296,14 @@ Machine Gun = Maschinengewehr
Landship = Landkreuzer
+Great Prophet = Großer Prophet
+
+
+Missionary = Missionar
+
+Inquisitor = Inquisitor
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Greek.properties b/android/assets/jsons/translations/Greek.properties
index 050307fc56998..29c91ada26dbf 100644
--- a/android/assets/jsons/translations/Greek.properties
+++ b/android/assets/jsons/translations/Greek.properties
@@ -1163,6 +1163,9 @@ Show zoom buttons in world screen =
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1474,12 +1477,20 @@ Your city [cityName] can bombard the enemy! =
# Requires translation!
[amount] enemy units were spotted in our territory =
# Requires translation!
-A(n) [nukeType] exploded in our territory! =
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Έχοντας κτυπηθεί από τον/την/το/τους/τις/τα [nukeType] μάς, [civName] διακήρυξε τον πόλεμο σε εμάς!
The civilization of [civName] has been destroyed! = Ο πολιτισμός του/της/των [civName] καταστράφηκε!
The City-State of [name] has been destroyed! = Η πόλη-κράτος [name] καταστράφηκε!
Your [ourUnit] captured an enemy [theirUnit]! = Ο/Η/Το/Οι/Τα [ourUnit] αιχμαλώτισε/αν μία αντίπαλη μονάδα [theirUnit]!
# Requires translation!
+Your captured [unitName] has been returned by [civName] =
+ # Requires translation!
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] =
# Requires translation!
We have captured a barbarian encampment and recovered [goldAmount] gold! =
@@ -3315,8 +3326,6 @@ Religion naturally spreads to cities [amount] tiles away =
May not generate great prophet equivalents naturally =
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
- # Requires translation!
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times =
Starting tech = Α
# Requires translation!
Starts with [tech] =
@@ -3399,14 +3408,18 @@ Founds a new city =
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
# Requires translation!
-Can build [improvementFilter/terrainFilter] improvements on tiles =
+Can Spread Religion =
# Requires translation!
-May create improvements on water resources =
+Can remove other religions from cities =
# Requires translation!
May found a religion =
# Requires translation!
May enhance a religion =
# Requires translation!
+Can build [improvementFilter/terrainFilter] improvements on tiles =
+ # Requires translation!
+May create improvements on water resources =
+ # Requires translation!
Can be added to [comment] in the Capital =
# Requires translation!
Prevents spreading of religion to the city it is next to =
@@ -3417,8 +3430,6 @@ May Paradrop up to [amount] tiles from inside friendly territory =
# Requires translation!
Can perform Air Sweep =
# Requires translation!
-Can [action] [amount] times =
- # Requires translation!
Can speed up construction of a building =
# Requires translation!
Can speed up the construction of a wonder =
@@ -4105,6 +4116,8 @@ FollowerBelief =
# Requires translation!
Building =
# Requires translation!
+UnitAction =
+ # Requires translation!
Unit =
# Requires translation!
UnitType =
@@ -8597,21 +8610,12 @@ Great Merchant =
# Requires translation!
Great Engineer =
- # Requires translation!
-Great Prophet =
-
# Requires translation!
Great General =
# Requires translation!
Khan =
- # Requires translation!
-Missionary =
-
- # Requires translation!
-Inquisitor =
-
# Requires translation!
SS Booster =
@@ -8992,6 +8996,8 @@ Wu =
Zhou =
# Requires translation!
Sun =
+ # Requires translation!
+Taoism =
# Requires translation!
Refaat =
@@ -10391,9 +10397,6 @@ Judaism =
# Requires translation!
Sikhism =
- # Requires translation!
-Taoism =
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -10505,6 +10508,10 @@ Truffles =
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
# Requires translation!
Hussar =
@@ -10593,6 +10600,17 @@ Machine Gun =
Landship =
+ # Requires translation!
+Great Prophet =
+
+
+ # Requires translation!
+Missionary =
+
+ # Requires translation!
+Inquisitor =
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Hungarian.properties b/android/assets/jsons/translations/Hungarian.properties
index fbab4773a6969..5fe0bc1b4e8cb 100644
--- a/android/assets/jsons/translations/Hungarian.properties
+++ b/android/assets/jsons/translations/Hungarian.properties
@@ -756,6 +756,9 @@ Reset = Újraindítás
Show zoom buttons in world screen = Pályanagyításgombok mutatása
Experimental Demographics scoreboard = Demográfiai eredményjelző (kísérleti)
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
Visual Hints = Látható segítségek
@@ -939,11 +942,20 @@ Your city [cityName] can bombard the enemy! = Városunk, [cityName] meg tudja bo
[amount] of your cities can bombard the enemy! = [amount] városunk meg tudja bombázni az ellenséget!
[amount] enemy units were spotted near our territory = [amount] ellenséges egység tűnt fel a területünk közelében
[amount] enemy units were spotted in our territory = [amount] ellenséges egység tűnt fel a területünkön
-A(n) [nukeType] exploded in our territory! = Egy [nukeType] robbant a területünkön!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Az általunk küldött [nukeType] miatt [civName] hadat üzent nekünk!
The civilization of [civName] has been destroyed! = [civName] civilizációja megsemmisült!
The City-State of [name] has been destroyed! = [name] városállama megsemmisült!
Your [ourUnit] captured an enemy [theirUnit]! = A mi [ourUnit] egységünk elfogott egy ellenséges [theirUnit] egységet!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = A mi [ourUnit] egységünk [amount] [Stat] zsákmányt rabolt az ellenség [theirUnit] egységétől
We have captured a barbarian encampment and recovered [goldAmount] gold! = Elfoglaltunk egy barbár tábort és zsákmányoltunk [goldAmount] aranyat!
An enemy [unitType] has joined us! = Egy ellenséges [unitType] csatlakozott hozzánk!
@@ -2037,7 +2049,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = [amount] mezővel megnő a vallás városok közötti természetes terjedése
May not generate great prophet equivalents naturally = A híres próféták és az ennek megfelelő egységek már nem keletkeznek maguktól
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]%-kal változik a hit költsége a különféle híres próféták születésének
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [cityFilter] készült [baseUnitFilter] egységek [amount] alkalommal többször tudnak [action]
Starting tech = Kezdőtechnológia
Starts with [tech] = [tech] feltalálva kezdéskor
Starts with [policy] adopted = A(z) [policy] eszme beiktatásával kezdődik
@@ -2080,16 +2091,19 @@ Automatically built in all cities where it is buildable = Magától megépül az
Creates a [improvementName] improvement on a specific tile = [improvementName] területfejlesztést létesít egy meghatározott mezőn
Founds a new city = Új várost alapít
Can instantly construct a [improvementFilter] improvement = Képes azonnal létrehozni [improvementFilter] területfejlesztést
-Can build [improvementFilter/terrainFilter] improvements on tiles = Képes [improvementFilter/terrainFilter] területfejlesztést építeni a mezőkön
-May create improvements on water resources = Vízi erőforrásmezők fejlesztésére használható
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Képes a vallás megalapítására
May enhance a religion = Képes a vallás kibővítésére
+Can build [improvementFilter/terrainFilter] improvements on tiles = Képes [improvementFilter/terrainFilter] területfejlesztést építeni a mezőkön
+May create improvements on water resources = Vízi erőforrásmezők fejlesztésére használható
Can be added to [comment] in the Capital = A fővárosban hozzáadható [comment] részeként
Prevents spreading of religion to the city it is next to = Megakadályozza más vallások terjedését abba városba, amely mellett tartózkodik
Removes other religions when spreading religion = Hittérítéskor eltávolít más vallásokat
May Paradrop up to [amount] tiles from inside friendly territory = Ledobható [amount] mező távoságra barátságos területről indítva
Can perform Air Sweep = Végre tud hajtani légtértisztogatást
-Can [action] [amount] times = Képes [action] [amount] alkalommal
Can speed up construction of a building = Fel tudja gyorsítani egy épület építését
Can speed up the construction of a wonder = Felgyorsítható egy csoda építése
Can hurry technology research = Meggyorsítja a technológiák kutatását
@@ -2470,6 +2484,8 @@ Policy = Eszme
FounderBelief = AlapítóiTan
FollowerBelief = KövetőiTan
Building = Épület
+ # Requires translation!
+UnitAction =
Unit = Egység
UnitType = Egységtípus
Promotion = Előléptetés
@@ -5581,16 +5597,10 @@ Great Merchant = Híres kereskedő
Great Engineer = Híres mérnök
-Great Prophet = Híres próféta
-
Great General = Híres tábornok
Khan = Kán
-Missionary = Misszionárius
-
-Inquisitor = Inkvizítor
-
SS Booster = Űrhajó-gyorsítórakéta
SS Cockpit = Űrhajó-vezérlőterem
@@ -5891,6 +5901,7 @@ Wu =
Zhou =
# Requires translation!
Sun =
+Taoism = Taoizmus
# Requires translation!
Refaat =
@@ -7245,8 +7256,6 @@ Judaism = Judaizmus
Sikhism = Szikhizmus
-Taoism = Taoizmus
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -7336,6 +7345,10 @@ Truffles = Szarvasgomba
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Huszár
@@ -7405,6 +7418,14 @@ Machine Gun = Géppuska
Landship = Harckocsi
+Great Prophet = Híres próféta
+
+
+Missionary = Misszionárius
+
+Inquisitor = Inkvizítor
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Indonesian.properties b/android/assets/jsons/translations/Indonesian.properties
index 1d33036f3d852..8129a9d9cd88e 100644
--- a/android/assets/jsons/translations/Indonesian.properties
+++ b/android/assets/jsons/translations/Indonesian.properties
@@ -756,6 +756,9 @@ Reset = Atur ulang
Show zoom buttons in world screen = Tampilkan tombol perbesaran di layar dunia
Experimental Demographics scoreboard = Papan Skor Demografik Eksperimental
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
Visual Hints = Petunjuk Visual
@@ -939,11 +942,20 @@ Your city [cityName] can bombard the enemy! = Kota [cityName] kepunyaanmu bisa m
[amount] of your cities can bombard the enemy! = [amount] kotamu bisa membombardir musuh!
[amount] enemy units were spotted near our territory = [amount] unit musuh terlihat di dekat daerah kita
[amount] enemy units were spotted in our territory = [amount] unit musuh terlihat di daerah kita
-A(n) [nukeType] exploded in our territory! = Sebuah [nukeType] telah meledak di daerah kita!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Setelah diserang dengan [nukeType], [civName] telah menyatakan perang terhadap negara kita!
The civilization of [civName] has been destroyed! = Peradaban [civName] telah hancur!
The City-State of [name] has been destroyed! = Negara-Kota [name] telah hancur!
Your [ourUnit] captured an enemy [theirUnit]! = [ourUnit] kamu telah menawan [theirUnit] musuh!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = [ourUnit] kamu telah menjarah [amount] [Stat] dari [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Kita telah menguasai sebuah perkemahan orang barbar dan mendapat emas sebanyak [goldAmount]!
An enemy [unitType] has joined us! = Unit [unitType] musuh telah bergabung dengan kita!
@@ -2030,7 +2042,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = Agama menyebar ke kota-kota yang berjarak hingga [amount] daerah dengan sendirinya
May not generate great prophet equivalents naturally = Tidak bisa memunculkan nabi besar atau orang-orang sejenisnya secara alami
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% biaya Iman untuk menghasilkan orang yang setara Nabi Besar
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = Unit [action] yang dibangun [cityFilter] dapat [baseUnitFilter] [amount] kali lagi
Starting tech = Teknologi mula-mula
Starts with [tech] = Memulai dengan [tech]
Starts with [policy] adopted = Memulai dengan kebijakan [policy]
@@ -2072,16 +2083,19 @@ Automatically built in all cities where it is buildable = Otomatis dibangun di s
Creates a [improvementName] improvement on a specific tile = Membuat peningkatan [improvementName] di daerah yang spesifik
Founds a new city = Mendirikan kota baru
Can instantly construct a [improvementFilter] improvement = Bisa langsung membangun peningkatan [improvementFilter]
-Can build [improvementFilter/terrainFilter] improvements on tiles = Dapat membangun peningkatan [improvementFilter/terrainFilter] di daerah
-May create improvements on water resources = Dapat membuat peningkatan di sumber daya dalam air
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Dapat mendirikan suatu agama
May enhance a religion = Dapat memperkuat suatu agama
+Can build [improvementFilter/terrainFilter] improvements on tiles = Dapat membangun peningkatan [improvementFilter/terrainFilter] di daerah
+May create improvements on water resources = Dapat membuat peningkatan di sumber daya dalam air
Can be added to [comment] in the Capital = Dapat ditambahkan ke [comment] di ibu kota
Prevents spreading of religion to the city it is next to = Mencegah penyebaran agama ke kota di sebelahnya
Removes other religions when spreading religion = Menghilangkan agama lain ketika menyebarkan agama
May Paradrop up to [amount] tiles from inside friendly territory = Dapat terjun payung hingga jarak [amount] daerah dari dalam daerah kekaisaran
Can perform Air Sweep = Dapat melakukan Sweeping Udara
-Can [action] [amount] times = Dapat [action] [amount] kali
Can speed up construction of a building = Dapat mempercepat pembangunan sebuah bangunan
Can speed up the construction of a wonder = Dapat mempercepat pembangunan Keajaiban Dunia
Can hurry technology research = Dapat mempercepat riset teknologi
@@ -2462,6 +2476,8 @@ Policy = Kebijakan
FounderBelief = KepercayaanPendiri
FollowerBelief = KepercayaanPengikut
Building = Bangunan
+ # Requires translation!
+UnitAction =
Unit = Unit
UnitType = JenisUnit
Promotion = Promosi
@@ -5187,16 +5203,10 @@ Great Merchant = Pedagang Hebat
Great Engineer = Insinyur Hebat
-Great Prophet = Nabi Besar
-
Great General = Jenderal Hebat
Khan = Khan
-Missionary = Misionaris
-
-Inquisitor = Inkuisitor
-
SS Booster = Pendorong PA
SS Cockpit = Kokpit PA
@@ -5460,6 +5470,7 @@ Zhao = Zhao
Wu = Wu
Zhou = Zhou
Sun = Sun
+Taoism = Taoisme
Refaat = Refaat
Heba = Heba
@@ -6184,8 +6195,6 @@ Judaism = Yudaisme
Sikhism = Sikhisme
-Taoism = Taoisme
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6268,6 +6277,10 @@ Truffles = Jamur Truffle
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Hussar
@@ -6334,6 +6347,14 @@ Machine Gun = Senapan Mesin
Landship = Tank Kapaldarat
+Great Prophet = Nabi Besar
+
+
+Missionary = Misionaris
+
+Inquisitor = Inkuisitor
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Italian.properties b/android/assets/jsons/translations/Italian.properties
index df0d88b53b107..99ba7802accce 100644
--- a/android/assets/jsons/translations/Italian.properties
+++ b/android/assets/jsons/translations/Italian.properties
@@ -748,6 +748,8 @@ Reset = Resetta
Show zoom buttons in world screen = Mostra tasti zoom nella schermata mondiale
Experimental Demographics scoreboard = Punteggio demografico sperimentale
+Size of Unitset art in Civilopedia = Grandezza pixel Unitset sulla Civilopedia
+
### Visual Hints subgroup
Visual Hints = Indizi visivi
@@ -929,11 +931,15 @@ Your city [cityName] can bombard the enemy! = [cityName] può bombardare il nemi
[amount] of your cities can bombard the enemy! = [amount] tue città possono bombardare il nemico!
[amount] enemy units were spotted near our territory = Abbiamo avvistato [amount] unità nemiche nei pressi dei nostri confini!
[amount] enemy units were spotted in our territory = Abbiamo avvistato [amount] unità nemiche nel nostro territorio!
-A(n) [nukeType] exploded in our territory! = È esploso l'ordigno [nukeType] nel nostro territorio!
-After being hit by our [nukeType], [civName] has declared war on us! = A seguito di un colpo di [nukeType], [civName] ci ha dichiarato guerra!
+A(n) [nukeType] from [civName] has exploded in our territory! = [civName] ha sganciato [nukeType] nel nostro territorio!
+A(n) [nukeType] has been detonated by [civName]! = [civName] ha detonato [nukeType]!
+A(n) [nukeType] has been detonated by an unkown civilization! = Una civiltà sconosciuta ha detonato [nukeType]!
+After an attempted attack by our [nukeType], [civName] has declared war on us! = [civName] ha risposto al nostro tentativo d'attacco nucleare [nukeType] con una dichiarazione di guerra!
+After being hit by our [nukeType], [civName] has declared war on us! = [civName] ha risposto al nostro attacco nucleare [nukeType] con una dichiarazione di guerra!
The civilization of [civName] has been destroyed! = La civiltà [civName] è stata distrutta!
The City-State of [name] has been destroyed! = La Città-Stato [name] è stata distrutta!
Your [ourUnit] captured an enemy [theirUnit]! = [ourUnit] ha catturato l'unità nemica [theirUnit]!
+Your captured [unitName] has been returned by [civName] = [civName] ci ha restituito [unitName]
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = [ourUnit] ha saccheggiato [amount] [Stat] da [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Abbiamo distrutto un accampamento barbaro e recuperato [goldAmount] Oro!
An enemy [unitType] has joined us! = L'unità nemica [unitType] si è unita a noi!
@@ -1414,8 +1420,7 @@ Civilization Info = Info civiltà
Relations = Rapporti
Trade request = Richiesta commerciale
Garrisoned by unit = Con guarnigione
- # Requires translation!
-Status\n(puppet, resistance or being razed) =
+Status\n(puppet, resistance or being razed) = Status\n(fantoccio, resistenza o sacco in corso)
# Victory
@@ -1997,7 +2002,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = La religione si diffonde naturalmente nelle città a [amount] caselle di distanza
May not generate great prophet equivalents naturally = Non può generare equivalenti al Grande Profeta naturalmente
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% costi in Fede nel generare Grandi Profeti equivalenti
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = Le unità [baseUnitFilter] costruite [cityFilter] possono usare [action] [amount] volte in più
Starting tech = Tecnologia iniziale
Starts with [tech] = Cominci con la tecnologia [tech]
Starts with [policy] adopted = Comincia adottando la politica [policy]
@@ -2039,16 +2043,17 @@ Automatically built in all cities where it is buildable = Costruito automaticame
Creates a [improvementName] improvement on a specific tile = Crea un miglioramento [improvementName] su una casella specifica
Founds a new city = Può fondare una nuova città
Can instantly construct a [improvementFilter] improvement = Può costruire instantaneamente un miglioramento [improvementFilter]
-Can build [improvementFilter/terrainFilter] improvements on tiles = Può costruire miglioramenti [improvementFilter/terrainFilter]
-May create improvements on water resources = Può costruire miglioramenti sulle risorse acquatiche
+Can Spread Religion = Può diffondere una Religione
+Can remove other religions from cities = Può rimuovere religioni straniere dalle città
May found a religion = Può fondare una religione
May enhance a religion = Può migliorare una religione
+Can build [improvementFilter/terrainFilter] improvements on tiles = Può costruire miglioramenti [improvementFilter/terrainFilter]
+May create improvements on water resources = Può costruire miglioramenti sulle risorse acquatiche
Can be added to [comment] in the Capital = Aggiungibile a [comment] nella Capitale
Prevents spreading of religion to the city it is next to = Previene la diffusione della religione alla città nelle sue vicinanze
Removes other religions when spreading religion = Rimuove le altre religioni quando diffonde la propria
May Paradrop up to [amount] tiles from inside friendly territory = Può paracadutarsi fino a [amount] caselle in territorio amico
Can perform Air Sweep = Può eseguire una Perlustrazione
-Can [action] [amount] times = Può usare [action] [amount] volte
Can speed up construction of a building = Può accelerare la costruzione di un edificio
Can speed up the construction of a wonder = Può accelerare la costruzione di una meraviglia
Can hurry technology research = Può accelerare la ricerca tecnologica
@@ -2423,6 +2428,8 @@ Policy = Politica
FounderBelief = Credenza del Fondatore
FollowerBelief = Credenza del Seguace
Building = Edificio
+ # Requires translation!
+UnitAction =
Unit = Unità
UnitType = TipoUnità
Promotion = Promozione
@@ -4313,7 +4320,7 @@ Contest Faith = Gara di Fede
The civilization with the largest Faith growth will gain a reward. = Questa Città-stato è alla ricerca della Fede più potente e dei sacerdoti più influenti. La civiltà che accumulerà più Fede verrà ricompensata.
Contest Technologies = Gara tecnologica
-The civilization with the largest number of new Technologies researched will gain a reward. = Una Città-stato ha indetto una fiera tecnologica! La civiltà che scoprirà il maggior numero di tecnologie verrà ricompensata.
+The civilization with the largest number of new Technologies researched will gain a reward. = La Città-stato ha indetto una fiera tecnologica! La civiltà che scoprirà il maggior numero di tecnologie verrà ricompensata.
Invest = Investimento
Our people are rejoicing thanks to a tourism boom. For a certain amount of time, any Gold donation will yield [50]% extra Influence. = Il nostro popolo gioisce a causa di un boom turistico. Per un certo lasso di tempo, le donazioni in Oro frutteranno il [50]% in più di Influenza.
@@ -5142,16 +5149,10 @@ Great Merchant = Grande Mercante
Great Engineer = Grande Ingegnere
-Great Prophet = Grande Profeta
-
Great General = Grande Generale
Khan = Khan
-Missionary = Missionario
-
-Inquisitor = Inquisitore
-
SS Booster = Propulsore dell'astronave
SS Cockpit = Abitacolo dell'astronave
@@ -5415,6 +5416,7 @@ Zhao = Zhao
Wu = Wu
Zhou = Zhou
Sun = Sun
+Taoism = Taoismo
Refaat = Refaat
Heba = Heba
@@ -6139,8 +6141,6 @@ Judaism = Giudaismo
Sikhism = Sikhismo
-Taoism = Taoismo
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6223,6 +6223,9 @@ Truffles = Tartufi
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+Devout = Devoto
+
+
Hussar = Ussaro
@@ -6289,6 +6292,14 @@ Machine Gun = Mitragliatrice
Landship = Nave di terra
+Great Prophet = Grande Profeta
+
+
+Missionary = Missionario
+
+Inquisitor = Inquisitore
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Japanese.properties b/android/assets/jsons/translations/Japanese.properties
index 7a830bd8bf6f1..b6b013f99ddd9 100644
--- a/android/assets/jsons/translations/Japanese.properties
+++ b/android/assets/jsons/translations/Japanese.properties
@@ -770,6 +770,9 @@ Reset = リセット
Show zoom buttons in world screen = ワールドスクリーンにズームを表示
Experimental Demographics scoreboard = デモグラフィックスコアを表示(試験的機能)
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
Visual Hints = ビジュアルヒント
@@ -955,11 +958,20 @@ Your city [cityName] can bombard the enemy! = [cityName]が敵を爆撃できま
[amount] of your cities can bombard the enemy! = [amount]の都市が敵を爆撃できます
[amount] enemy units were spotted near our territory = 我々の領土の近くで敵のユニットを[amount]体確認しました
[amount] enemy units were spotted in our territory = 我々の領土内で敵のユニットを[amount]体確認しました
-A(n) [nukeType] exploded in our territory! = 我々の領土内に[nukeType]が投下されました
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = 我々の[nukeType]を投下され、[civName]が我々に宣戦布告しました
The civilization of [civName] has been destroyed! = [civName]は崩壊しました
The City-State of [name] has been destroyed! = 都市国家[name]は破壊されました
Your [ourUnit] captured an enemy [theirUnit]! = [ourUnit]が敵の[theirUnit]を捕獲しました
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = [ourUnit]が[theirUnit]から[amount][Stat]を奪いました
We have captured a barbarian encampment and recovered [goldAmount] gold! = 蛮族の野営地を占領し、[goldAmount]ゴールドを回収しました
An enemy [unitType] has joined us! = 敵の[unitType]が入隊しました
@@ -2068,7 +2080,6 @@ Religion naturally spreads to cities [amount] tiles away = 宗教が自然に[am
May not generate great prophet equivalents naturally = 大預言者が自動生成されない
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [cityFilter]建設後、[baseUnitFilter]は追加で[amount]回[action]ことができる
Starting tech = スタートアップ技術
Starts with [tech] = [tech]で始まる
Starts with [policy] adopted = [policy]を採用した状態で始まる
@@ -2110,17 +2121,20 @@ Automatically built in all cities where it is buildable = 建設可能なすべ
Creates a [improvementName] improvement on a specific tile = 特定のタイル上に改良[improvementName]を造り出す
Founds a new city = 新しい都市を建設できる
Can instantly construct a [improvementFilter] improvement = タイルに[improvementFilter]を即時整備できる
-Can build [improvementFilter/terrainFilter] improvements on tiles = [improvementFilter/terrainFilter]を整備できる
-May create improvements on water resources = 水上の資源上でタイル整備することができる
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = 宗教を創始できる
May enhance a religion = 宗教を教化できる
+Can build [improvementFilter/terrainFilter] improvements on tiles = [improvementFilter/terrainFilter]を整備できる
+May create improvements on water resources = 水上の資源上でタイル整備することができる
Can be added to [comment] in the Capital = [comment]に追加することができる
Prevents spreading of religion to the city it is next to = 隣の都市への宗教の拡散を防ぐ
Removes other religions when spreading religion = 布教時に他の宗教を排除する
May Paradrop up to [amount] tiles from inside friendly territory = 領土から[amount]タイル以内に降下できる
# Requires translation!
Can perform Air Sweep =
-Can [action] [amount] times = [amount]回[action]できる
Can speed up construction of a building = 建造物の建設を進められる
Can speed up the construction of a wonder = 遺産の建設を加速できる
Can hurry technology research = テクノロジーの研究を早める
@@ -2558,6 +2572,8 @@ FounderBelief =
# Requires translation!
FollowerBelief =
Building = 建物
+ # Requires translation!
+UnitAction =
Unit = ユニット
UnitType = ユニットタイプ
Promotion = 昇進
@@ -5394,16 +5410,10 @@ Great Merchant = 大商人
Great Engineer = 大技術者
-Great Prophet = 大預言者
-
Great General = 大将軍
Khan = カン
-Missionary = 伝道者
-
-Inquisitor = 審問官
-
SS Booster = 宇宙船推進装置
SS Cockpit = 宇宙船コクピット
@@ -5694,6 +5704,7 @@ Zhao = 趙
Wu = 呉
Zhou = 周
Sun = 孫
+Taoism = 道教
# Requires translation!
Refaat =
@@ -6844,8 +6855,6 @@ Judaism = ユダヤ教
Sikhism = シーク教
-Taoism = 道教
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6932,6 +6941,10 @@ Truffles = トリュフ
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = 軽騎兵
@@ -6998,6 +7011,14 @@ Machine Gun = マシンガン
Landship = 陸上船
+Great Prophet = 大預言者
+
+
+Missionary = 伝道者
+
+Inquisitor = 審問官
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Korean.properties b/android/assets/jsons/translations/Korean.properties
index 7b978d6e0c26d..062c49f310d75 100644
--- a/android/assets/jsons/translations/Korean.properties
+++ b/android/assets/jsons/translations/Korean.properties
@@ -765,6 +765,9 @@ Reset = 초기화
Show zoom buttons in world screen = 세계 화면에서 확대/축소 버튼 보기
Experimental Demographics scoreboard = 인구통계학적 순위 보기
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
Visual Hints = 힌트 표시
@@ -950,11 +953,20 @@ Your city [cityName] can bombard the enemy! = [cityName]이(가) 적을 공격
[amount] of your cities can bombard the enemy! = [amount]개 도시가 적을 공격할 수 있습니다!
[amount] enemy units were spotted near our territory = 적 [amount]명이 아군 영토 근처에서 발견되었습니다.
[amount] enemy units were spotted in our territory = 적 [amount]명이 아군 영토 내에서 발견되었습니다.
-A(n) [nukeType] exploded in our territory! = 아군 영토에 [nukeType] 폭탄이 떨어졌습니다!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = [civName]이(가) 아군 [nukeType]에 피격되어 전쟁을 선포했습니다!
The civilization of [civName] has been destroyed! = [civName] 문명이 멸망했습니다!
The City-State of [name] has been destroyed! = [name] 도시국가가 멸망했습니다!
Your [ourUnit] captured an enemy [theirUnit]! = 아군 [ourUnit]이(가) 적 [theirUnit]을(를) 포로로 잡았습니다.
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = 아군 [ourUnit]이(가) 적 [theirUnit]을(를) 잡아 [amount] [Stat]를 얻었습니다.
We have captured a barbarian encampment and recovered [goldAmount] gold! = 야만인 주둔지를 점령하여 [goldAmount]골드를 얻었습니다!
An enemy [unitType] has joined us! = 적 [unitType]이(가) 아군이 되었습니다!
@@ -2042,7 +2054,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = 종교 전파 거리 [amount]
May not generate great prophet equivalents naturally = 위대한 선지자를 탄생시킬 수 없습니다.
[relativeAmount]% Faith cost of generating Great Prophet equivalents = 위대한 선지자 출현에 필요한 신앙 [relativeAmount]%
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [cityFilter]에서 생산된 [baseUnitFilter]의 [action] 횟수 [amount]회 증가
Starting tech = 기술 개시
Starts with [tech] = [tech] 기술을 가지고 시작함
Starts with [policy] adopted = [policy] 채택한 상태로 시작
@@ -2084,16 +2095,19 @@ Automatically built in all cities where it is buildable = 생산 가능한 모
Creates a [improvementName] improvement on a specific tile = [improvementName] 시설을 적절한 타일에 건설
Founds a new city = 새로운 도시를 건설함
Can instantly construct a [improvementFilter] improvement = [improvementFilter] 즉시 건설 가능
-Can build [improvementFilter/terrainFilter] improvements on tiles = [improvementFilter/terrainFilter] 건설 가능
-May create improvements on water resources = 해양 자원에 시설 건설 가능
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = 종교 창시 가능
May enhance a religion = 종교 강화 가능
+Can build [improvementFilter/terrainFilter] improvements on tiles = [improvementFilter/terrainFilter] 건설 가능
+May create improvements on water resources = 해양 자원에 시설 건설 가능
Can be added to [comment] in the Capital = 수도에서 [comment] 조립 가능
Prevents spreading of religion to the city it is next to = 인접한 도시에 타 종교 전파를 막음
Removes other religions when spreading religion = 종교 전파시 타 종교 제거
May Paradrop up to [amount] tiles from inside friendly territory = 우호 영토에서 시작시 최대 [amount]타일까지 강하 가능
Can perform Air Sweep = 대공 무력화 가능
-Can [action] [amount] times = [action] [amount]회 가능
Can speed up construction of a building = 건설 가속 가능
Can speed up the construction of a wonder = 불가사의 건설 가속 가능
Can hurry technology research = 연구 가속 가능
@@ -2489,6 +2503,8 @@ Policy = 정책
FounderBelief = 창시자 교리
FollowerBelief = 신도 교리
Building = 건물
+ # Requires translation!
+UnitAction =
Unit = 유닛
UnitType = 유닛
Promotion = 승급
@@ -5250,16 +5266,10 @@ Great Merchant = 위대한 상인
Great Engineer = 위대한 기술자
-Great Prophet = 위대한 선지자
-
Great General = 위대한 장군
Khan = 칸
-Missionary = 선교사
-
-Inquisitor = 이단심문관
-
SS Booster = SS 추진기
SS Cockpit = SS 조종부
@@ -5523,6 +5533,7 @@ Zhao = 조
Wu = 무
Zhou = 저우
Sun = 쑨
+Taoism = 도교
Refaat = 리파트
Heba = 헤바
@@ -6247,8 +6258,6 @@ Judaism = 유대교
Sikhism = 시크교
-Taoism = 도교
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6331,6 +6340,10 @@ Truffles = 송로버섯
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = 후사르
@@ -6397,6 +6410,14 @@ Machine Gun = 기관총
Landship = 지상함
+Great Prophet = 위대한 선지자
+
+
+Missionary = 선교사
+
+Inquisitor = 이단심문관
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Lithuanian.properties b/android/assets/jsons/translations/Lithuanian.properties
index 4d72f6e9b50ae..18ca62784c74a 100644
--- a/android/assets/jsons/translations/Lithuanian.properties
+++ b/android/assets/jsons/translations/Lithuanian.properties
@@ -837,6 +837,9 @@ Show zoom buttons in world screen = Rodyti artinimo/tolinimo mygtukus
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1042,11 +1045,20 @@ Your city [cityName] can bombard the enemy! = Mūsų miestas: [cityName] gali ap
[amount] of your cities can bombard the enemy! = [amount] mūsų miestų gali apšaudyti priešą!
[amount] enemy units were spotted near our territory = [amount] priešo dalinių pastebėti šalia mūsų žemių!
[amount] enemy units were spotted in our territory = [amount] priešo dalinių pastebėti mūsų žemėse!
-A(n) [nukeType] exploded in our territory! = [nukeType] sprogo mūsų žemėse!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Po mūsų: [nukeType] sprogimo, [civName] skelbia mums karą!
The civilization of [civName] has been destroyed! = Civilizacija [civName] sunaikinta!
The City-State of [name] has been destroyed! = Laisvasis Miestas [name] sunaikintas!
Your [ourUnit] captured an enemy [theirUnit]! = Mūsų [ourUnit] paėmė į nelaisvę priešo: [theirUnit]!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Mūsų [ourUnit] pagrobė: [amount] [Stat] iš priešo: [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Barbarų stovykloje radome [goldAmount] aukso!
An enemy [unitType] has joined us! = Priešo [unitType] prisijungė prie mūsų!
@@ -2228,7 +2240,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = Religija plinta savaime [amount] laukelių atstumu
May not generate great prophet equivalents naturally = Didysis pranašas savaime neatsiranda.
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% tikėjimo kiekiui, kad įgyti Didįjį Pranašą
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [baseUnitFilter], pagaminti mieste: [cityFilter], gali [amount] kartų daugiau: [action]
Starting tech = Pradžios technologija
Starts with [tech] = Pradeda su: [tech]
Starts with [policy] adopted = Pradeda su: [policy]
@@ -2274,16 +2285,19 @@ Creates a [improvementName] improvement on a specific tile = Stato: [improvement
Founds a new city = Gali įkurti miestą
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
-Can build [improvementFilter/terrainFilter] improvements on tiles = Gali statyti laukelių pagerinimus: [improvementFilter/terrainFilter]
-May create improvements on water resources = Gali kurti patobulinimus jūroje
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Gali įkurti religiją
May enhance a religion = Gali sustiprinti religiją
+Can build [improvementFilter/terrainFilter] improvements on tiles = Gali statyti laukelių pagerinimus: [improvementFilter/terrainFilter]
+May create improvements on water resources = Gali kurti patobulinimus jūroje
Can be added to [comment] in the Capital = Galima pridėti prie: [comment] sostinėje
Prevents spreading of religion to the city it is next to = Trukdo platinti religiją mieste šalia kurio stovi
Removes other religions when spreading religion = Pašalinas kitas religijas kai platina savają
May Paradrop up to [amount] tiles from inside friendly territory = Gali iššokti su parašiutais iki [amount] laukelių nuo draugiškos teritorijos
Can perform Air Sweep = Gali kautis oro mūšyje
-Can [action] [amount] times = Gali [amount] kartus: [action]
Can speed up construction of a building = Gali pagreitinti pastatų statybas
Can speed up the construction of a wonder = Gali pagreitinti pasaulio stebuklo statybas
Can hurry technology research = Gali pagreitinti technologijų tyrimus
@@ -2717,6 +2731,8 @@ Policy = Santvarkos institutas
FounderBelief = Įkūrėjo tikėjimas
FollowerBelief = Sekėjo tikėjimas
Building = Pastatas
+ # Requires translation!
+UnitAction =
Unit = Dalinys
UnitType = Dalinio tipas
Promotion = Paaukštinmas
@@ -5517,16 +5533,10 @@ Great Merchant = Didis pirklys
Great Engineer = Didis inžinierius
-Great Prophet = Didis pranašas
-
Great General = Didis karvedys
Khan = Chanas karvedys
-Missionary = Misijonierius
-
-Inquisitor = Inkvizitorius
-
SS Booster = Erdvėlaivio raketa-nešėja
SS Cockpit = Erdvėlaivio tiltelis
@@ -5790,6 +5800,7 @@ Zhao = Zhao
Wu = Wu
Zhou = Zhou
Sun = Sun
+Taoism = Daoizmas
Refaat = Refaat
Heba = Heba
@@ -6514,8 +6525,6 @@ Judaism = Judaizmas
Sikhism = Sikhtizmas
-Taoism = Daoizmas
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6598,6 +6607,10 @@ Truffles = Triufeliai
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Husaras
@@ -6664,6 +6677,14 @@ Machine Gun = Kulkosvaidis
Landship = Tanketė
+Great Prophet = Didis pranašas
+
+
+Missionary = Misijonierius
+
+Inquisitor = Inkvizitorius
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Malay.properties b/android/assets/jsons/translations/Malay.properties
index fd1c70349024a..b19fdc154795f 100644
--- a/android/assets/jsons/translations/Malay.properties
+++ b/android/assets/jsons/translations/Malay.properties
@@ -1065,6 +1065,9 @@ Show zoom buttons in world screen =
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1324,11 +1327,20 @@ Your city [cityName] can bombard the enemy! =
[amount] of your cities can bombard the enemy! =
[amount] enemy units were spotted near our territory = [amount] unit musuh terjumpa berhampiran kawasan kami
[amount] enemy units were spotted in our territory = [amount] unit musuh terjumpa di dalam kawasan kami
-A(n) [nukeType] exploded in our territory! = [nukeType] terletup di dalam kawasan kami!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Selepas terkena [nukeType] kami, [civName] mengisytiharkan perang pada kami!
The civilization of [civName] has been destroyed! = Tamadun [civName] telah dijatuhkan!
The City-State of [name] has been destroyed! = Negara Kota [name] telah dimusnahkan!
Your [ourUnit] captured an enemy [theirUnit]! = [ourUnit] anda telah menangkap [theirUnit] musuh!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = [ourUnit] anda telah menjalah [amount] [Stat] daripada [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Kami telah menangkapkan sebuah kem barbar dan memulihkan [goldAmount] emas!
# Requires translation!
@@ -2878,8 +2890,6 @@ May not generate great prophet equivalents naturally =
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
# Requires translation!
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times =
- # Requires translation!
Starting tech =
# Requires translation!
Starts with [tech] =
@@ -2961,14 +2971,18 @@ Founds a new city =
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
# Requires translation!
-Can build [improvementFilter/terrainFilter] improvements on tiles =
+Can Spread Religion =
# Requires translation!
-May create improvements on water resources =
+Can remove other religions from cities =
# Requires translation!
May found a religion =
# Requires translation!
May enhance a religion =
# Requires translation!
+Can build [improvementFilter/terrainFilter] improvements on tiles =
+ # Requires translation!
+May create improvements on water resources =
+ # Requires translation!
Can be added to [comment] in the Capital =
# Requires translation!
Prevents spreading of religion to the city it is next to =
@@ -2979,8 +2993,6 @@ May Paradrop up to [amount] tiles from inside friendly territory =
# Requires translation!
Can perform Air Sweep =
# Requires translation!
-Can [action] [amount] times =
- # Requires translation!
Can speed up construction of a building =
# Requires translation!
Can speed up the construction of a wonder =
@@ -3648,6 +3660,8 @@ FollowerBelief =
# Requires translation!
Building =
# Requires translation!
+UnitAction =
+ # Requires translation!
Unit =
# Requires translation!
UnitType =
@@ -8185,21 +8199,12 @@ Great Merchant =
# Requires translation!
Great Engineer =
- # Requires translation!
-Great Prophet =
-
# Requires translation!
Great General =
# Requires translation!
Khan =
- # Requires translation!
-Missionary =
-
- # Requires translation!
-Inquisitor =
-
# Requires translation!
SS Booster =
@@ -8594,6 +8599,8 @@ Wu =
Zhou =
# Requires translation!
Sun =
+ # Requires translation!
+Taoism =
# Requires translation!
Refaat =
@@ -9993,9 +10000,6 @@ Judaism =
# Requires translation!
Sikhism =
- # Requires translation!
-Taoism =
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -10095,6 +10099,10 @@ Truffles = Truffle
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
# Requires translation!
Hussar =
@@ -10182,6 +10190,17 @@ Machine Gun = Mesingan
Landship =
+ # Requires translation!
+Great Prophet =
+
+
+ # Requires translation!
+Missionary =
+
+ # Requires translation!
+Inquisitor =
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Persian_(Pinglish-DIN).properties b/android/assets/jsons/translations/Persian_(Pinglish-DIN).properties
index 74f0f7eb433bc..6f13ad7f6ae6f 100644
--- a/android/assets/jsons/translations/Persian_(Pinglish-DIN).properties
+++ b/android/assets/jsons/translations/Persian_(Pinglish-DIN).properties
@@ -1138,6 +1138,9 @@ Show zoom buttons in world screen =
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1415,7 +1418,13 @@ Your city [cityName] can bombard the enemy! =
[amount] enemy units were spotted near our territory = [amount] niroo ye došman kenār e ğalamro e mā dide šod
[amount] enemy units were spotted in our territory = [amount] niroo ye došman dar ğalamro e mā dide šod
# Requires translation!
-A(n) [nukeType] exploded in our territory! =
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
# Requires translation!
After being hit by our [nukeType], [civName] has declared war on us! =
The civilization of [civName] has been destroyed! = Tamaddon e [civName] nābood šod!
@@ -1423,6 +1432,8 @@ The City-State of [name] has been destroyed! = Šahrestān e [name] nābood šod
# Requires translation!
Your [ourUnit] captured an enemy [theirUnit]! =
# Requires translation!
+Your captured [unitName] has been returned by [civName] =
+ # Requires translation!
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] =
We have captured a barbarian encampment and recovered [goldAmount] gold! = Mā yek ordoogāh e Barbari rā tasḵir kadim va [goldAmount] Talā bedast āvardim!
# Requires translation!
@@ -3095,8 +3106,6 @@ May not generate great prophet equivalents naturally =
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
# Requires translation!
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times =
- # Requires translation!
Starting tech =
# Requires translation!
Starts with [tech] =
@@ -3177,14 +3186,18 @@ Founds a new city =
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
# Requires translation!
-Can build [improvementFilter/terrainFilter] improvements on tiles =
+Can Spread Religion =
# Requires translation!
-May create improvements on water resources =
+Can remove other religions from cities =
# Requires translation!
May found a religion =
# Requires translation!
May enhance a religion =
# Requires translation!
+Can build [improvementFilter/terrainFilter] improvements on tiles =
+ # Requires translation!
+May create improvements on water resources =
+ # Requires translation!
Can be added to [comment] in the Capital =
# Requires translation!
Prevents spreading of religion to the city it is next to =
@@ -3195,8 +3208,6 @@ May Paradrop up to [amount] tiles from inside friendly territory =
# Requires translation!
Can perform Air Sweep =
# Requires translation!
-Can [action] [amount] times =
- # Requires translation!
Can speed up construction of a building =
# Requires translation!
Can speed up the construction of a wonder =
@@ -3900,6 +3911,8 @@ FollowerBelief =
# Requires translation!
Building =
# Requires translation!
+UnitAction =
+ # Requires translation!
Unit =
# Requires translation!
UnitType =
@@ -8677,21 +8690,12 @@ Great Merchant =
# Requires translation!
Great Engineer =
- # Requires translation!
-Great Prophet =
-
# Requires translation!
Great General =
# Requires translation!
Khan =
- # Requires translation!
-Missionary =
-
- # Requires translation!
-Inquisitor =
-
# Requires translation!
SS Booster =
@@ -9088,6 +9092,8 @@ Wu =
Zhou =
# Requires translation!
Sun =
+ # Requires translation!
+Taoism =
# Requires translation!
Refaat =
@@ -10487,9 +10493,6 @@ Judaism =
# Requires translation!
Sikhism =
- # Requires translation!
-Taoism =
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -10598,6 +10601,10 @@ Truffles = Ğārč
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
# Requires translation!
Hussar =
@@ -10685,6 +10692,17 @@ Machine Gun =
Landship =
+ # Requires translation!
+Great Prophet =
+
+
+ # Requires translation!
+Missionary =
+
+ # Requires translation!
+Inquisitor =
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Persian_(Pinglish-UN).properties b/android/assets/jsons/translations/Persian_(Pinglish-UN).properties
index 1209b3ee14890..73e5da33ceb04 100644
--- a/android/assets/jsons/translations/Persian_(Pinglish-UN).properties
+++ b/android/assets/jsons/translations/Persian_(Pinglish-UN).properties
@@ -928,6 +928,9 @@ Show zoom buttons in world screen =
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1162,11 +1165,20 @@ Your city [cityName] can bombard the enemy! = Shahr e [cityName] e shomaa mitava
[amount] of your cities can bombard the enemy! = [amount] taa az shahr haaye shomaa mitavaanand doshman raa bombaaraan konand!
[amount] enemy units were spotted near our territory = [amount] niroo ye doshman kenaar e ghalamro e maa dide shod
[amount] enemy units were spotted in our territory = [amount] niroo ye doshman dar ghalamro e maa dide shod
-A(n) [nukeType] exploded in our territory! = Yek [nukeType] dar ghalamro e maa monfajer shod!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Pas az hadaf gharaar gereftan tavassot e [nukeType] e maa, [civName] be maa e’laam e jang karde ast!
The civilization of [civName] has been destroyed! = Tamaddon e [civName] naabood shod!
The City-State of [name] has been destroyed! = Shahrestaan e [name] naabood shod!
Your [ourUnit] captured an enemy [theirUnit]! = [ourUnit] e shomaa yek [theirUnit] e doshman raa asir kard!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = [ourUnit] e shomaa [amount] taa [Stat] az [theirUnit] be ghaarat bord
We have captured a barbarian encampment and recovered [goldAmount] gold! = Maa yek ordoogaah e Barbari raa taskhir kadim va [goldAmount] Talaa bedast aavardim!
An enemy [unitType] has joined us! = Yek [unitType] e doshman be maa molhagh shod!
@@ -2665,8 +2677,6 @@ May not generate great prophet equivalents naturally =
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
# Requires translation!
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times =
- # Requires translation!
Starting tech =
# Requires translation!
Starts with [tech] =
@@ -2747,14 +2757,18 @@ Founds a new city =
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
# Requires translation!
-Can build [improvementFilter/terrainFilter] improvements on tiles =
+Can Spread Religion =
# Requires translation!
-May create improvements on water resources =
+Can remove other religions from cities =
# Requires translation!
May found a religion =
# Requires translation!
May enhance a religion =
# Requires translation!
+Can build [improvementFilter/terrainFilter] improvements on tiles =
+ # Requires translation!
+May create improvements on water resources =
+ # Requires translation!
Can be added to [comment] in the Capital =
# Requires translation!
Prevents spreading of religion to the city it is next to =
@@ -2765,8 +2779,6 @@ May Paradrop up to [amount] tiles from inside friendly territory =
# Requires translation!
Can perform Air Sweep =
# Requires translation!
-Can [action] [amount] times =
- # Requires translation!
Can speed up construction of a building =
# Requires translation!
Can speed up the construction of a wonder =
@@ -3445,6 +3457,8 @@ FounderBelief =
# Requires translation!
FollowerBelief =
Building = Saakhtemaan
+ # Requires translation!
+UnitAction =
Unit = Niroo
# Requires translation!
UnitType =
@@ -7920,21 +7934,12 @@ Great Merchant =
# Requires translation!
Great Engineer =
- # Requires translation!
-Great Prophet =
-
# Requires translation!
Great General =
# Requires translation!
Khan =
- # Requires translation!
-Missionary =
-
- # Requires translation!
-Inquisitor =
-
# Requires translation!
SS Booster =
@@ -8322,6 +8327,8 @@ Wu =
Zhou =
# Requires translation!
Sun =
+ # Requires translation!
+Taoism =
# Requires translation!
Refaat =
@@ -9707,9 +9714,6 @@ Judaism =
# Requires translation!
Sikhism =
- # Requires translation!
-Taoism =
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -9816,6 +9820,10 @@ Truffles = Ghaarch
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
# Requires translation!
Hussar =
@@ -9900,6 +9908,17 @@ Machine Gun = Mosalsal
Landship =
+ # Requires translation!
+Great Prophet =
+
+
+ # Requires translation!
+Missionary =
+
+ # Requires translation!
+Inquisitor =
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Polish.properties b/android/assets/jsons/translations/Polish.properties
index cefb40e61bee5..f378eba8e8838 100644
--- a/android/assets/jsons/translations/Polish.properties
+++ b/android/assets/jsons/translations/Polish.properties
@@ -748,6 +748,8 @@ Reset = Zresetuj
Show zoom buttons in world screen = Pokaż przyciski Powiększ i Pomniejsz na ekranie gry
Experimental Demographics scoreboard = Eksperymentalna tablica Demografii
+Size of Unitset art in Civilopedia = Rozmiar obrazka jednostki w Civilopedii
+
### Visual Hints subgroup
Visual Hints = Wizualne wskazówki
@@ -929,11 +931,15 @@ Your city [cityName] can bombard the enemy! = [cityName] może ostrzeliwać wrog
[amount] of your cities can bombard the enemy! = [amount] nasze miasta mogą ostrzeliwać wroga!
[amount] enemy units were spotted near our territory = [amount] wrogie jednostki zauważono w pobliżu naszych granic!
[amount] enemy units were spotted in our territory = [amount] wrogie jednostki zauważono na naszym terenie!!
-A(n) [nukeType] exploded in our territory! = Pocisk: [nukeType] eksplodował na naszym terytorium!!
-After being hit by our [nukeType], [civName] has declared war on us! = [civName] wypowiada nam wojnę, po tym jak nasz pocisk [nukeType] uderzył w ich terytorium!!
+A(n) [nukeType] from [civName] has exploded in our territory! = [nukeType] cywilizacji [civName] eksplodował na naszym terytorium!
+A(n) [nukeType] has been detonated by [civName]! = Cywilizacja [civName] zdetonowała [nukeType]!
+A(n) [nukeType] has been detonated by an unkown civilization! = Nieznana cywilizacja zdetonowała [nukeType]!
+After an attempted attack by our [nukeType], [civName] has declared war on us! = Cywilizacja [civName] wypowiada nam wojnę, po tym jak nasz pocisk [nukeType] wyrządził szkody na ich terytorium!
+After being hit by our [nukeType], [civName] has declared war on us! = Cywilizacja [civName] wypowiada nam wojnę, po tym jak nasz pocisk [nukeType] uderzył w ich terytorium!
The civilization of [civName] has been destroyed! = Cywilizacja [civName] została zniszczona!!
The City-State of [name] has been destroyed! = Wolne Miasto [name] zostało zniszczone!
Your [ourUnit] captured an enemy [theirUnit]! = Nasza jednostka [ourUnit] przejęła wrogą jednostkę [theirUnit]!
+Your captured [unitName] has been returned by [civName] = Nasza pojmana jednostka [unitName] została zwrócona przez cywilizację [civName]
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Nasza jednostka: [ourUnit] splądrowała [amount] [Stat] od [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Zdobyliśmy obóz Barbarzyńców: +[goldAmount] Złota!
An enemy [unitType] has joined us! = Wroga jednostka [unitType] przeszła na naszą stronę!
@@ -1365,7 +1371,7 @@ Total Supply = Całkowite zaopatrzenie
In Use = W użyciu
Supply Deficit = Deficyt zaopatrzenia
Production Penalty = Kara za produkcję
-Increase your supply or reduce the amount of units to remove the production penalty = Zwiększ zaopatzenie lub zredukuj ilość jednostek by zniwelować karę za produkcję
+Increase your supply or reduce the amount of units to remove the production penalty = Zwiększ zaopatrzenie lub zredukuj ilość jednostek by zniwelować karę za produkcję
Name = Nazwa
Closest city = Najbliższe miasto
Action = Akcja
@@ -1414,8 +1420,7 @@ Civilization Info = Cywilizacje
Relations = Relacje
Trade request = Zapotrzebowanie na handel
Garrisoned by unit = Zgarnizowane przez jednostkę
- # Requires translation!
-Status\n(puppet, resistance or being razed) =
+Status\n(puppet, resistance or being razed) = Status\n(marionetka, stawiające opór lub będące w trakcie burzenia)
# Victory
@@ -1997,7 +2002,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = Religia naturalnie rozszerza się do miast w promieniu [amount] pól
May not generate great prophet equivalents naturally = Wielki Prorok nie będzie generowany w sposób naturalny
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% mniej kosztów ☮Wiary przy generowaniu Wielkiego Proroka
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [baseUnitFilter] zbudowany [cityFilter] może dodatkowo [amount] raz [action]
Starting tech = Technologia początkowa
Starts with [tech] = Na start otrzymuje technologię [tech]
Starts with [policy] adopted = Rozpoczyna z przyjętą polityką: [policy]
@@ -2039,16 +2043,17 @@ Automatically built in all cities where it is buildable = Automatycznie budowane
Creates a [improvementName] improvement on a specific tile = Tworzy ulepszenia [improvementName] na określonych polach
Founds a new city = Może zakładać nowe miasta
Can instantly construct a [improvementFilter] improvement = Może natychmiastowo wybudować [improvementFilter]
-Can build [improvementFilter/terrainFilter] improvements on tiles = Może budować ulepszenia [improvementFilter/terrainFilter]
-May create improvements on water resources = Może budować ulepszenia na wodzie
+Can Spread Religion = Może rozszerzać religię
+Can remove other religions from cities = Może wytępiać obce religie z miast
May found a religion = Może założyć religię
May enhance a religion = Może wzmacniać religię w dowolnym mieście
+Can build [improvementFilter/terrainFilter] improvements on tiles = Może budować ulepszenia [improvementFilter/terrainFilter]
+May create improvements on water resources = Może budować ulepszenia na wodzie
Can be added to [comment] in the Capital = Należy dostarczyć do Stolicy aby wybudować [comment]
Prevents spreading of religion to the city it is next to = Powstrzymuje przed szerzeniem religii na sąsiednie miasta.
Removes other religions when spreading religion = Usuwa inne religie podczas szerzenia własnej.
May Paradrop up to [amount] tiles from inside friendly territory = Możliwość desantu w promieniu do [amount] pól od przyjaznego terenu
Can perform Air Sweep = Może przeprowadzić Oczyszczanie nieba
-Can [action] [amount] times = Może [amount] raz/y [action]
Can speed up construction of a building = Może przyśpieszyć konstruowanie budynku
Can speed up the construction of a wonder = Może przyspieszyć budowę Cudu
Can hurry technology research = Może przyspieszyć badanie technologii
@@ -2423,6 +2428,7 @@ Policy = Ustrój
FounderBelief = Wierzenia Założyciela
FollowerBelief = Wierzenia Wyznawcy
Building = Budowla
+UnitAction = Ruch Jednostki
Unit = Jednostka
UnitType = Typ jednostki
Promotion = Awans
@@ -2472,7 +2478,7 @@ Our mutual military struggle brings us closer together. = Walka przeciw wspólne
We applaud your liberation of conquered cities! = Doceniamy, że wyzwalacie podbite miasta
We have signed a public declaration of friendship = Podpisaliśmy publiczną Deklarację Przyjaźni
You have declared friendship with our allies = Podpisaliście Deklarację Przyjaźni z naszymi sojusznikami
-We have signed a promise to protect each other. = Przyrzekliśmy nawzajem się bronić.
+We have signed a promise to protect each other. = Przyrzekliśmy nawzajem się bronić
You have declared a defensive pact with our allies = Podpisaliście Pakt Defensywny z naszymi sojusznikami
You have denounced our enemies = Potępiliście naszych wrogów
Our open borders have brought us closer together. = Otwarte granice zbliżyły nas do siebie
@@ -5142,16 +5148,10 @@ Great Merchant = Wielki Kupiec
Great Engineer = Wielki Inżynier
-Great Prophet = Wielki Prorok
-
Great General = Wielki Generał
Khan = Wielki Chan
-Missionary = Misjonarz
-
-Inquisitor = Inkwizytor
-
SS Booster = Dopalacz SK
SS Cockpit = Kokpit SK
@@ -5415,6 +5415,7 @@ Zhao = Czao
Wu = Wu
Zhou = Czou
Sun = Sun
+Taoism = Taoizm
Refaat = Refaat
Heba = Heba
@@ -6139,8 +6140,6 @@ Judaism = Judaizm
Sikhism = Sikhizm
-Taoism = Taoizm
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6223,6 +6222,9 @@ Truffles = Trufle
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+Devout = Pobożny
+
+
Hussar = Huzar
@@ -6289,6 +6291,14 @@ Machine Gun = Karabin Maszynowy
Landship = Wczesny Czołg
+Great Prophet = Wielki Prorok
+
+
+Missionary = Misjonarz
+
+Inquisitor = Inkwizytor
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Portuguese.properties b/android/assets/jsons/translations/Portuguese.properties
index 6735a01f400be..bd0d0ca81b778 100644
--- a/android/assets/jsons/translations/Portuguese.properties
+++ b/android/assets/jsons/translations/Portuguese.properties
@@ -814,6 +814,9 @@ Reset =
Show zoom buttons in world screen = Mostrar botões de zoom na tela do mundo
Experimental Demographics scoreboard = Placar de Demografia Experimental
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1016,11 +1019,20 @@ Your city [cityName] can bombard the enemy! = A tua cidade [cityName] pode bomba
[amount] of your cities can bombard the enemy! = [amount] das tuas cidades podem bombardear o inimigo!
[amount] enemy units were spotted near our territory = [amount] unidades inimigas foram observadas perto do nosso território
[amount] enemy units were spotted in our territory = [amount] unidades inimigas foram observadas no nosso território
-A(n) [nukeType] exploded in our territory! = Uma [nukeType] explodiu no nosso território!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Depois de ser atingido pela nossa [nukeType], [civName] declarou-nos guerra!
The civilization of [civName] has been destroyed! = A civilização de [civName] foi destruida!
The City-State of [name] has been destroyed! = A Cidade-estado nomeada [name] foi destruída!
Your [ourUnit] captured an enemy [theirUnit]! = A tua [ourUnit] capturou um inimigo [theirUnit]!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = A tua [ourUnit] roubou [amount] [Stat] de [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Nós capturamos um encampamento bárbaro e recuperamos [goldAmount] de ouro!
An enemy [unitType] has joined us! = Um(a) [unitType] inimigo(a) uniu-se à nós!
@@ -2224,8 +2236,6 @@ Religion naturally spreads to cities [amount] tiles away =
May not generate great prophet equivalents naturally =
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
- # Requires translation!
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times =
Starting tech = Tecnologia inicial
# Requires translation!
Starts with [tech] =
@@ -2288,12 +2298,16 @@ Founds a new city = Estabelecer uma nova cidade
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
# Requires translation!
-Can build [improvementFilter/terrainFilter] improvements on tiles =
-May create improvements on water resources = Pode criar melhorias em recursos aquáticos
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Podes fundar uma religião
# Requires translation!
May enhance a religion =
# Requires translation!
+Can build [improvementFilter/terrainFilter] improvements on tiles =
+May create improvements on water resources = Pode criar melhorias em recursos aquáticos
+ # Requires translation!
Can be added to [comment] in the Capital =
# Requires translation!
Prevents spreading of religion to the city it is next to =
@@ -2304,8 +2318,6 @@ May Paradrop up to [amount] tiles from inside friendly territory =
# Requires translation!
Can perform Air Sweep =
# Requires translation!
-Can [action] [amount] times =
- # Requires translation!
Can speed up construction of a building =
# Requires translation!
Can speed up the construction of a wonder =
@@ -2876,6 +2888,8 @@ FollowerBelief =
# Requires translation!
Building =
# Requires translation!
+UnitAction =
+ # Requires translation!
Unit =
# Requires translation!
UnitType =
@@ -6251,16 +6265,10 @@ Great Merchant = Grande Mercador
Great Engineer = Grande Engenheiro
-Great Prophet = Grande Profeta
-
Great General = Grande General
Khan = Cã
-Missionary = Pregador
-
-Inquisitor = Inquisidor
-
SS Booster = Propulsor de Nave Espacial
SS Cockpit = Cabine de Nave Espacial
@@ -6595,6 +6603,8 @@ Wu =
Zhou =
# Requires translation!
Sun =
+ # Requires translation!
+Taoism =
# Requires translation!
Refaat =
@@ -7994,9 +8004,6 @@ Judaism =
# Requires translation!
Sikhism =
- # Requires translation!
-Taoism =
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -8097,6 +8104,10 @@ Truffles = Trufas
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
# Requires translation!
Hussar =
@@ -8175,6 +8186,14 @@ Machine Gun = Metralhadora robusta
Landship = Navio terrestre
+Great Prophet = Grande Profeta
+
+
+Missionary = Pregador
+
+Inquisitor = Inquisidor
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Romanian.properties b/android/assets/jsons/translations/Romanian.properties
index faf7c4cc5f89b..24b19c247325c 100644
--- a/android/assets/jsons/translations/Romanian.properties
+++ b/android/assets/jsons/translations/Romanian.properties
@@ -846,6 +846,9 @@ Show zoom buttons in world screen = Afișați butoanele de zoom pe ecranul mondi
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1077,11 +1080,20 @@ Your city [cityName] can bombard the enemy! = Orașul tău [cityName] poate bomb
[amount] of your cities can bombard the enemy! = [amount] dintre orașele tale pot bombarda inamicul!
[amount] enemy units were spotted near our territory = [amount] unități inamice au fost detectate în aproprierea teritoriului nostru
[amount] enemy units were spotted in our territory = [amount] unități inamice au fost detectate pe teritoriul nostru
-A(n) [nukeType] exploded in our territory! = O(un) [nukeType] a explodat pe teritoriul nostru!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = După ce a fost lovită de [nukeType], [civName] ne-a declarat război!
The civilization of [civName] has been destroyed! = Civilizația [civName] a fost distrusă!
The City-State of [name] has been destroyed! = Orașul-stat [name] a fost distrus!
Your [ourUnit] captured an enemy [theirUnit]! = [ourUnit]ul tău a capturat un inamic [theirUnit]!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = [ourUnit]ul tău a jefuit [amount] [Stat] de la [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Am capturat tabăra barbară și am luat [goldAmount] aur!
# Requires translation!
@@ -2355,8 +2367,6 @@ Religion naturally spreads to cities [amount] tiles away =
May not generate great prophet equivalents naturally =
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
- # Requires translation!
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times =
Starting tech = Technologie de început
# Requires translation!
Starts with [tech] =
@@ -2418,13 +2428,17 @@ Founds a new city = Fondează un oraș nou
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
# Requires translation!
-Can build [improvementFilter/terrainFilter] improvements on tiles =
-May create improvements on water resources = Poate crea îmbunătățiri pe resurse acvatice
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
# Requires translation!
May found a religion =
# Requires translation!
May enhance a religion =
# Requires translation!
+Can build [improvementFilter/terrainFilter] improvements on tiles =
+May create improvements on water resources = Poate crea îmbunătățiri pe resurse acvatice
+ # Requires translation!
Can be added to [comment] in the Capital =
# Requires translation!
Prevents spreading of religion to the city it is next to =
@@ -2435,8 +2449,6 @@ May Paradrop up to [amount] tiles from inside friendly territory =
# Requires translation!
Can perform Air Sweep =
# Requires translation!
-Can [action] [amount] times =
- # Requires translation!
Can speed up construction of a building =
# Requires translation!
Can speed up the construction of a wonder =
@@ -3012,6 +3024,8 @@ FollowerBelief =
# Requires translation!
Building =
# Requires translation!
+UnitAction =
+ # Requires translation!
Unit =
# Requires translation!
UnitType =
@@ -5935,19 +5949,10 @@ Great Merchant = Mare Comerciant
Great Engineer = Mare Inginer
- # Requires translation!
-Great Prophet =
-
Great General = Mare General
Khan = Han
- # Requires translation!
-Missionary =
-
- # Requires translation!
-Inquisitor =
-
SS Booster = Propulsor pentru nave spațiale
SS Cockpit = Cabină pentru nave spațiale
@@ -6336,6 +6341,7 @@ Wu =
Zhou =
# Requires translation!
Sun =
+Taoism = Taoism
# Requires translation!
Refaat =
@@ -7451,8 +7457,6 @@ Judaism = Judaism
Sikhism = Sikhism
-Taoism = Taoism
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -7550,6 +7554,10 @@ Truffles = Trufe
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Hussar
@@ -7624,6 +7632,17 @@ Machine Gun = Mitralieră
Landship = Vehicul Terestru
+ # Requires translation!
+Great Prophet =
+
+
+ # Requires translation!
+Missionary =
+
+ # Requires translation!
+Inquisitor =
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Russian.properties b/android/assets/jsons/translations/Russian.properties
index 61c3d03288723..2effdc1f248e9 100644
--- a/android/assets/jsons/translations/Russian.properties
+++ b/android/assets/jsons/translations/Russian.properties
@@ -176,7 +176,7 @@ Current leader is [civInfo] with [amount] Technologies discovered. = Текущ
Demands = Требования
Please don't settle new cities near us. = Пожалуйста, не закладывайте новых городов рядом с нами.
Very well, we shall look for new lands to settle. = Хорошо, мы найдем другие земли для поселений.
-We shall do as we please. = Мы поступим, как сочтем нужным.
+We shall do as we please. = Мы поступим, как сочтём нужным.
We noticed your new city near our borders, despite your promise. This will have....implications. = Мы заметили ваш новый город около наших границ. Это может повлечь... последствия.
I've been informed that my armies have taken tribute from [civName], a city-state under your protection.\nI assure you, this was quite unintentional, and I hope that this does not serve to drive us apart. = Мне сообщили, что мои войска взяли дань с [civName], города-государства, находящегося под вашей защитой.\nУверяю вас, это было совершенно непреднамеренно, и я надеюсь, что это не будет причиной портить наши отношения.
We asked [civName] for a tribute recently and they gave in.\nYou promised to protect them from such things, but we both know you cannot back that up. = Мы не так давно попросили [civName] выплатить нам дань, и они согласились.\nВы обещали защищать их от подобных выходок, но мы оба прекрасно знаем, что ваши слова ничем не подкреплены.
@@ -212,9 +212,9 @@ We have married into the ruling family of [civName], bringing them under our con
You have broken your Pledge to Protect [civName]! = Вы нарушили свое обещание защищать [civName]!
City-States grow wary of your aggression. The resting point for Influence has decreased by [amount] for [civName]. = Города-государства опасаются вашей агрессии. Базовое влияние уменьшилось на [amount] для [civName].
-[cityState] is being attacked by [civName] and asks all major civilizations to help them out by gifting them military units. = Город [cityState] атакован державой [civName] и просит всех великих цивилизаций помочь им, подарив им военных юнитов.
+[cityState] is being attacked by [civName] and asks all major civilizations to help them out by gifting them military units. = Город [cityState] атакован державой [civName] и просит все великие цивилизации помочь им, подарив им военных юнитов.
[cityState] is being invaded by Barbarians! Destroy Barbarians near their territory to earn Influence. = [cityState] под нашествием варваров! Уничтожьте варваров рядом с их территорией, чтобы заработать влияние.
-[cityState] is grateful that you killed a Barbarian that was threatening them! = [cityState] благодарны вам за избавление от угрожавшим им варваров!
+[cityState] is grateful that you killed a Barbarian that was threatening them! = [cityState] благодарны вам за избавление от угрожавших им варваров!
[cityState] is being attacked by [civName]! Kill [amount] of the attacker's military units and they will be immensely grateful. = Город [cityState] атакован державой [civName]! Уничтожьте [amount] военных юнитов агрессора и вам будут безмерно благодарны.
[cityState] is deeply grateful for your assistance in the war against [civName]! = [cityState] безмерно благодарны вам за помощь в войне против державы [civName]!
[cityState] no longer needs your assistance against [civName]. = [cityState] больше не нуждается в вашей помощи против державы [civName].
@@ -443,7 +443,7 @@ No human players selected! = Не выбраны игроки (люди)!
Invalid Player ID! = Неверный ID игрока!
No victory conditions were selected! = Не выбраны условия победы!
Mods: = Моды:
-Extension mods = Моды - расширения
+Extension mods = Моды-расширения
Base ruleset: = Базовый набор правил:
# Note - do not translate the colour names between «». Changing them works if you know what you're doing.
The mod you selected is incorrectly defined! = Выбранный вами мод имеет неправильное описание!
@@ -748,6 +748,8 @@ Reset = Сбросить
Show zoom buttons in world screen = Показывать кнопки масштабирования на экране мира
Experimental Demographics scoreboard = Экспериментальная таблица рейтинга
+Size of Unitset art in Civilopedia = Размер изображений палитры юнитов в Цивилопедии
+
### Visual Hints subgroup
Visual Hints = Визуальные подсказки
@@ -929,11 +931,15 @@ Your city [cityName] can bombard the enemy! = Ваш город [cityName] мо
[amount] of your cities can bombard the enemy! = Враг может быть обстрелян [amount] вашими городами!
[amount] enemy units were spotted near our territory = [amount] вражеских юнитов замечено рядом с нашей территорией!
[amount] enemy units were spotted in our territory = [amount] вражеских юнитов замечено на нашей территории!
-A(n) [nukeType] exploded in our territory! = [nukeType] взорвалась на нашей территории!
+A(n) [nukeType] from [civName] has exploded in our territory! = [nukeType] державы [civName] взорвалась на нашей территории!
+A(n) [nukeType] has been detonated by [civName]! = [nukeType] была взорвана державой [civName]!
+A(n) [nukeType] has been detonated by an unkown civilization! = [nukeType] была взорвана неизвестной державой!
+After an attempted attack by our [nukeType], [civName] has declared war on us! = После нашей попытки нападения с помощью [nukeType], [civName] объявляет нам войну!
After being hit by our [nukeType], [civName] has declared war on us! = После взрыва нашей [nukeType], [civName] объявляет нам войну!
The civilization of [civName] has been destroyed! = Цивилизация [civName] была уничтожена!
The City-State of [name] has been destroyed! = Город-государство [name] был уничтожен!
Your [ourUnit] captured an enemy [theirUnit]! = Наш [ourUnit] захватил вражеского юнита [theirUnit]!
+Your captured [unitName] has been returned by [civName] = Захваченный у нас [unitName] был возвращён нам державой [civName]
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Наш [ourUnit] украл [amount] [Stat] у юнита [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Мы разгромили лагерь варваров и нашли в нем золото: [goldAmount]!
An enemy [unitType] has joined us! = Вражеский юнит [unitType] присоединяется к нам!
@@ -1414,8 +1420,7 @@ Civilization Info = Информация о цивилизации
Relations = Отношения
Trade request = Торговое предложение
Garrisoned by unit = Юнит в гарнизоне
- # Requires translation!
-Status\n(puppet, resistance or being razed) =
+Status\n(puppet, resistance or being razed) = Статус\n(сателлит, сопротивляется или разрушается)
# Victory
@@ -1997,7 +2002,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = Религия распространяется на города естественным образом на [amount] клеток дальше
May not generate great prophet equivalents naturally = Великий пророк не появляется самостоятельно
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% к затратам веры на появление Великого пророка
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [baseUnitFilter], созданные [cityFilter], могут на [amount] раз(а) больше: [action]
Starting tech = Начальная технология
Starts with [tech] = Начинает с технологией [tech]
Starts with [policy] adopted = Начинает с принятым институтом [policy]
@@ -2039,16 +2043,17 @@ Automatically built in all cities where it is buildable = Автоматичес
Creates a [improvementName] improvement on a specific tile = Создает улучшение [improvementName] на определенной клетке
Founds a new city = Может основать город
Can instantly construct a [improvementFilter] improvement = Может мгновенно создать [improvementFilter]
-Can build [improvementFilter/terrainFilter] improvements on tiles = Может создавать на клетках улучшения: [improvementFilter/terrainFilter]
-May create improvements on water resources = Может создавать улучшения на морских ресурсах
+Can Spread Religion = Может распространять религию
+Can remove other religions from cities = Может удалять другие религии из городов
May found a religion = Может основать религию
May enhance a religion = Может укрепить религию
+Can build [improvementFilter/terrainFilter] improvements on tiles = Может создавать на клетках улучшения: [improvementFilter/terrainFilter]
+May create improvements on water resources = Может создавать улучшения на морских ресурсах
Can be added to [comment] in the Capital = Может быть вставлен в [comment] в столице
Prevents spreading of religion to the city it is next to = Предотвращает распространение религии в рядом стоящем городе
Removes other religions when spreading religion = Очищает от остальных религий при распространении своей
May Paradrop up to [amount] tiles from inside friendly territory = Может десантироваться с парашютом максимум на [amount] клеток от дружественной территории
Can perform Air Sweep = Может проводить воздушную зачистку
-Can [action] [amount] times = Может [amount] раз(а): [action]
Can speed up construction of a building = Может ускорить строительство здания
Can speed up the construction of a wonder = Может ускорить строительство чуда
Can hurry technology research = Может ускорить изучение технологии
@@ -2423,6 +2428,7 @@ Policy = Общественный институт
FounderBelief = Верование основателя
FollowerBelief = Верование последователя
Building = Здание
+UnitAction = Действие юнита
Unit = Юнит
UnitType = Тип юнита
Promotion = Повышение
@@ -5142,16 +5148,10 @@ Great Merchant = Великий торговец
Great Engineer = Великий инженер
-Great Prophet = Великий пророк
-
Great General = Великий полководец
Khan = Хан
-Missionary = Миссионер
-
-Inquisitor = Инквизитор
-
SS Booster = Ракета-носитель КК
SS Cockpit = Мостик КК
@@ -5415,6 +5415,7 @@ Zhao = Чжао
Wu = У
Zhou = Чу
Sun = Сунь
+Taoism = Даосизм
Refaat = Рефат
Heba = Хеба
@@ -6139,8 +6140,6 @@ Judaism = Иудаизм
Sikhism = Сикхизм
-Taoism = Даосизм
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6223,7 +6222,10 @@ Truffles = Трюфели
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
-Hussar = Гуссар
+Devout = Набожный
+
+
+Hussar = Гусар
Hakkapeliitta = Хаккапелит
@@ -6289,6 +6291,14 @@ Machine Gun = Пулемёт
Landship = Танкетка
+Great Prophet = Великий пророк
+
+
+Missionary = Миссионер
+
+Inquisitor = Инквизитор
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Simplified_Chinese.properties b/android/assets/jsons/translations/Simplified_Chinese.properties
index f8fe91a1329e6..5d77d00aa468a 100644
--- a/android/assets/jsons/translations/Simplified_Chinese.properties
+++ b/android/assets/jsons/translations/Simplified_Chinese.properties
@@ -748,6 +748,8 @@ Reset = 重置
Show zoom buttons in world screen = 在游戏界面显示缩放按钮
Experimental Demographics scoreboard = 启用实验性统计记分板
+Size of Unitset art in Civilopedia = 文明百科规则集图片大小
+
### Visual Hints subgroup
Visual Hints = 视觉提示
@@ -929,11 +931,15 @@ Your city [cityName] can bombard the enemy! = 你的城市[cityName]可以轰击
[amount] of your cities can bombard the enemy! = 你有 [amount] 座城市可以轰击敌人!
[amount] enemy units were spotted near our territory = 我们的领土附近发现了 [amount] 个敌方单位
[amount] enemy units were spotted in our territory = 我们的领土内发现了 [amount] 个敌方单位
-A(n) [nukeType] exploded in our territory! = 一颗[nukeType]在我方领土上爆炸了!
+A(n) [nukeType] from [civName] has exploded in our territory! = 一枚来自 [civName] 的 [nukeType] 在我们领土上爆炸了!
+A(n) [nukeType] has been detonated by [civName]! = 一枚来自 [civName] 的 [nukeType] 被引爆了!
+A(n) [nukeType] has been detonated by an unkown civilization! = 一枚来自 未知文明 的 [nukeType] 被引爆了!
+After an attempted attack by our [nukeType], [civName] has declared war on us! = 由于受我们 [nukeType] 的打击, [civName] 对我们宣战!
After being hit by our [nukeType], [civName] has declared war on us! = 在被我们的[nukeType]打击后,[civName]向我们宣战了!
The civilization of [civName] has been destroyed! = [civName]文明已经灭亡!
The City-State of [name] has been destroyed! = 城邦[name]覆灭了!
Your [ourUnit] captured an enemy [theirUnit]! = 我们的[ourUnit]俘虏了敌方的[theirUnit]!
+Your captured [unitName] has been returned by [civName] = 我们被俘虏的 [unitName] 已经被 [civName] 解救并归还!
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = 我们的[ourUnit]从敌方的[theirUnit]掠夺了 [amount] [Stat]!
We have captured a barbarian encampment and recovered [goldAmount] gold! = 我们摧毁蛮族营地并获得了 [goldAmount] 金钱!
An enemy [unitType] has joined us! = 一个敌方[unitType]加入了我们!
@@ -1245,10 +1251,8 @@ Add to the top of the queue = 添加至 队列顶部
Add to the queue in all cities = 添加至 所有城市的队列
Add or move to the top in all cities = 添加或移至 所有城市的队列顶端
Remove from the queue in all cities = 从所有城市的队列中移除
- # Requires translation!
-Disable =
- # Requires translation!
-Enable =
+Disable = 禁用
+Enable = 启用
# Specialized Popups - Ask for text or numbers, file picker
@@ -1416,8 +1420,7 @@ Civilization Info = 文明信息
Relations = 关系
Trade request = 贸易请求
Garrisoned by unit = 按单位驻守
- # Requires translation!
-Status\n(puppet, resistance or being razed) =
+Status\n(puppet, resistance or being razed) = 状态\n(傀儡, 抵抗, 拆除中)
# Victory
@@ -1999,7 +2002,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = 宗教自然传播距离扩大[amount]地块
May not generate great prophet equivalents naturally = 不能自然产生大先知
[relativeAmount]% Faith cost of generating Great Prophet equivalents = 获得大先知必须的信仰[relativeAmount]%
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [cityFilter]建造的单位[baseUnitFilter]能额外[action][amount]次
Starting tech = 初始科技
Starts with [tech] = 初始即拥有科技:[tech]
Starts with [policy] adopted = 游戏开始时就推行[policy]
@@ -2041,16 +2043,17 @@ Automatically built in all cities where it is buildable = 在所有可建造的
Creates a [improvementName] improvement on a specific tile = 可以在一个特定的地块上建造[improvementName]
Founds a new city = 建立新城市
Can instantly construct a [improvementFilter] improvement = 可立即建造 [improvementFilter] 地块设施
-Can build [improvementFilter/terrainFilter] improvements on tiles = 可以建造地块设施:[improvementFilter/terrainFilter]
-May create improvements on water resources = 可在水上资源建造设施(工船将被消耗掉)
+Can Spread Religion = 可以传播宗教
+Can remove other religions from cities = 可以驱除城市异教
May found a religion = 可创建一个宗教
May enhance a religion = 可增强一个宗教
+Can build [improvementFilter/terrainFilter] improvements on tiles = 可以建造地块设施:[improvementFilter/terrainFilter]
+May create improvements on water resources = 可在水上资源建造设施(工船将被消耗掉)
Can be added to [comment] in the Capital = 可以被加入在首都的[comment]
Prevents spreading of religion to the city it is next to = 阻止紧邻城市的宗教传播
Removes other religions when spreading religion = 传播宗教时消除异端
May Paradrop up to [amount] tiles from inside friendly territory = 可空降到离友好领土[amount]格的位置
Can perform Air Sweep = 可以进行空中扫荡
-Can [action] [amount] times = 可以[action][amount]次
Can speed up construction of a building = 可以加速建筑物的建造
Can speed up the construction of a wonder = 可以加速一个奇观的建造
Can hurry technology research = 可以加速科技研究
@@ -2425,6 +2428,8 @@ Policy = 政策
FounderBelief = 创始人信仰
FollowerBelief = 追随者信仰
Building = 建筑
+ # Requires translation!
+UnitAction =
Unit = 单位
UnitType = 单位类别
Promotion = 晋升项
@@ -2542,8 +2547,7 @@ Buildable Buildings = 可建建筑
Buildable Wonders = 可建奇观
Buildable National Wonders = 可建国家奇观
Other Constructions = 其他建筑
- # Requires translation!
-Disabled Constructions =
+Disabled Constructions = 被禁用的建造
Next City = 下一个城市
Previous City = 上一个城市
Show Stats = 显示统计
@@ -5145,16 +5149,10 @@ Great Merchant = 大商业家
Great Engineer = 大工程师
-Great Prophet = 大先知
-
Great General = 大军事家
Khan = 可汗
-Missionary = 传教士
-
-Inquisitor = 审判官
-
SS Booster = 飞船助推器
SS Cockpit = 飞船驾驶舱
@@ -5418,6 +5416,7 @@ Zhao = 赵
Wu = 吴
Zhou = 周
Sun = 孙
+Taoism = 道教
Refaat = 里法特
Heba = 赫巴
@@ -6142,8 +6141,6 @@ Judaism = 犹太教
Sikhism = 锡克教
-Taoism = 道教
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6226,6 +6223,9 @@ Truffles = 松露
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+Devout = 神圣
+
+
Hussar = 轻骑兵
@@ -6292,6 +6292,14 @@ Machine Gun = 机关枪
Landship = 履带战车
+Great Prophet = 大先知
+
+
+Missionary = 传教士
+
+Inquisitor = 审判官
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
@@ -6359,7 +6367,7 @@ You have encountered another civilization!\nOther civilizations start out peacef
Once you have completed the Apollo Program, you can start constructing spaceship parts in your cities\n (with the relevant technologies) to win a Scientific Victory! = 当您完成了阿波罗计划,可以开始在您的城市中建造飞船部件(需要相应科技),\n来通过太空竞赛赢得科技胜利!
Injured Units = 受伤单位
-Injured units deal less damage, but recover after turns that they have been inactive.\nUnits heal 10 health per turn in enemy territory or neutral land,\n 20 inside your territory and 25 in your cities. = 报告大人,您的单位受伤了!\n受伤的单位会对敌方造成更少的伤害!您可能会好奇,怎么能治疗他们呢?其实很简单,您只需要在他们不受攻击的情况下原地待命,他们中的医疗兵就会开始工作了!\n在异国领土和无主的领土上,每回合会恢复15点生命值;在己方领土上,会恢复20点;在本方城市中,会恢复25点。
+Injured units deal less damage, but recover after turns that they have been inactive.\nUnits heal 10 health per turn in enemy territory or neutral land,\n 20 inside your territory and 25 in your cities. = 报告大人,您的单位受伤了!\n受伤的单位会对敌方造成更少的伤害!您可能会好奇,怎么能治疗他们呢?其实很简单,您只需要在他们不受攻击的情况下原地待命,他们中的医疗兵就会开始工作了!\n在异国领土和无主的领土上,每回合会恢复10点生命值;在己方领土上,会恢复20点;在本方城市中,会恢复25点。
Workers = 工人
Workers are vital to your cities' growth, since only they can construct improvements on tiles.\nImprovements raise the yield of your tiles, allowing your city to produce more and grow faster while working the same amount of tiles! = 工人对您所在城市的发展至关重要,因为只有他们才能在地块上建造设施。\n设施可以提高地块的产出,\n当市民在建有设施的地块工作时,将给您的城市带来更多的收益,同时城市也会发展得更快。
diff --git a/android/assets/jsons/translations/Spanish.properties b/android/assets/jsons/translations/Spanish.properties
index aa368727ac10f..0ac707d35fc9e 100644
--- a/android/assets/jsons/translations/Spanish.properties
+++ b/android/assets/jsons/translations/Spanish.properties
@@ -748,6 +748,9 @@ Reset = Reiniciar
Show zoom buttons in world screen = Mostrar Botones para Zoom del Mapa
Experimental Demographics scoreboard = Tabla de Puntaje Demográfica
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
Visual Hints = Pistas Visuales
@@ -929,14 +932,18 @@ Your city [cityName] can bombard the enemy! = ¡Tu ciudad de [cityName] puede bo
[amount] of your cities can bombard the enemy! = ¡[amount] de tus ciudades pueden bombardear al enemigo!
[amount] enemy units were spotted near our territory = Se han avistado [amount] unidades enemigas cerca de nuestro territorio
[amount] enemy units were spotted in our territory = Se han avistado [amount] unidades enemigas en nuestro territorio
-A(n) [nukeType] exploded in our territory! = ¡Un [nukeType] ha estallado en nuestro territorio!
-After being hit by our [nukeType], [civName] has declared war on us! = Tras ser impactados por nuestra [nukeType], ¡[civName] nos ha declarado la guerra!
+A(n) [nukeType] from [civName] has exploded in our territory! = ¡¡Un/a [nukeType] de [civName] ha explotado en nuestro territorio!!
+A(n) [nukeType] has been detonated by [civName]! = ¡Un/a [nukeType] han sido detonadas por [civName]!
+A(n) [nukeType] has been detonated by an unkown civilization! = ¡Un/a [nukeType] han sido detonadas por una civilización desconocida!
+After an attempted attack by our [nukeType], [civName] has declared war on us! = Luego de nuestro ataque con nuestr@ [nukeType], ¡[civName] nos ha declarado la guerra!
+After being hit by our [nukeType], [civName] has declared war on us! = Tras ser impactados por nuestr@ [nukeType], ¡[civName] nos ha declarado la guerra!
The civilization of [civName] has been destroyed! = ¡La civilización de [civName] ha sido destruida!
The City-State of [name] has been destroyed! = ¡La Ciudad-Estado de [name] ha sido destruida!
-Your [ourUnit] captured an enemy [theirUnit]! = ¡Tu [ourUnit] capturó un [theirUnit] enemigo!
+Your [ourUnit] captured an enemy [theirUnit]! = ¡Tu [ourUnit] capturó un/a [theirUnit] enemig@!
+Your captured [unitName] has been returned by [civName] = Tu [unitName] capturado ha sido rescatad@ por [civName]
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Tu [ourUnit] saqueó [amount] [Stat] de [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = ¡Hemos capturado un campamento bárbaro y recuperamos [goldAmount] de oro!
-An enemy [unitType] has joined us! = ¡Un [unitType] enemigo ha cambiado de bando!
+An enemy [unitType] has joined us! = ¡Un/a [unitType] enemig@ ha cambiado de bando!
[unitName] can be promoted! = ¡Asenso disponible para [unitName]!
# This might be needed for a rewrite of Germany's unique - see #7376
@@ -1414,8 +1421,7 @@ Civilization Info = Info. de la Civilización
Relations = Relaciones
Trade request = Propuesta de Comercio
Garrisoned by unit = Guarnición con unidad
- # Requires translation!
-Status\n(puppet, resistance or being razed) =
+Status\n(puppet, resistance or being razed) = Estatus\n(títere, resistencia o siendo arrasada)
# Victory
@@ -1997,7 +2003,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = Religión se difunde naturalmente a ciudades\n a una distancia de [amount] casillas
May not generate great prophet equivalents naturally = No se generarán equivalentes de grandes profetas por medio natural
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% coste de Fé para generar equivalentes de Grandes Profetas
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = Unidades [baseUnitFilter] construídas [cityFilter] pueden [action] [amount] vez/ces adicionales
Starting tech = Tecnología de inicio
Starts with [tech] = Comienza con [tech]
Starts with [policy] adopted = Comienza con [policy] adoptada
@@ -2039,16 +2044,19 @@ Automatically built in all cities where it is buildable = Construído automátic
Creates a [improvementName] improvement on a specific tile = Crea una mejora de [improvementName] en una casilla específica
Founds a new city = Funda una nueva ciudad
Can instantly construct a [improvementFilter] improvement = Puede construír una mejora de [improvementFilter] de inmediato
-Can build [improvementFilter/terrainFilter] improvements on tiles = Puede eregir: [improvementFilter/terrainFilter]
-May create improvements on water resources = Puede crear mejoras en recursos acuáticos
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Puede fundar una religión
May enhance a religion = Puede realzar una religión
+Can build [improvementFilter/terrainFilter] improvements on tiles = Puede eregir: [improvementFilter/terrainFilter]
+May create improvements on water resources = Puede crear mejoras en recursos acuáticos
Can be added to [comment] in the Capital = Puede ser añadido a [comment] en la Capitál
Prevents spreading of religion to the city it is next to = Previene la difusión a ciudades adyacentes de religiones
Removes other religions when spreading religion = Remueve otras religiones al difundir religión
May Paradrop up to [amount] tiles from inside friendly territory = Puede lanzarse a una distancia de [amount] casillas desde terreno amigo
Can perform Air Sweep = Puede hacer un Barrido Aéreo
-Can [action] [amount] times = Puede [action] [amount] veces
Can speed up construction of a building = Puede acelerar la construcción de un edificio
Can speed up the construction of a wonder = Puede acelerar la construcción de una maravilla
Can hurry technology research = Puede acelerar investigaciones
@@ -2423,6 +2431,8 @@ Policy = Política
FounderBelief = Creencia del fundador
FollowerBelief = Creencia del seguidor
Building = Edificio
+ # Requires translation!
+UnitAction =
Unit = Unidad
UnitType = Tipo de unidad
Promotion = Promoción
@@ -5142,16 +5152,10 @@ Great Merchant = Gran Mercader
Great Engineer = Gran Ingeniero
-Great Prophet = Gran Profeta
-
Great General = Gran General
Khan = Khan
-Missionary = Misionero
-
-Inquisitor = Inquisidor
-
SS Booster = Potenciador (Nave Espacial)
SS Cockpit = Cabina (Nave Espacial)
@@ -5415,6 +5419,7 @@ Zhao = Zhao
Wu = Wu
Zhou = Zhou
Sun = Sun
+Taoism = Taoísmo
Refaat = Refaat
Heba = Heba
@@ -6139,8 +6144,6 @@ Judaism = Judaísmo
Sikhism = Sijismo
-Taoism = Taoísmo
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6223,6 +6226,10 @@ Truffles = Trufas
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Husar
@@ -6289,6 +6296,14 @@ Machine Gun = Ametralladora Montada
Landship = Tanque de la Gran Guerra
+Great Prophet = Gran Profeta
+
+
+Missionary = Misionero
+
+Inquisitor = Inquisidor
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Swedish.properties b/android/assets/jsons/translations/Swedish.properties
index 9ff6e705624f4..4d8359ae51f98 100644
--- a/android/assets/jsons/translations/Swedish.properties
+++ b/android/assets/jsons/translations/Swedish.properties
@@ -811,6 +811,9 @@ Show zoom buttons in world screen = Visa zoomknappar i världsvyn
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1031,11 +1034,20 @@ Your city [cityName] can bombard the enemy! = Din stad [cityName] kan bombardera
[amount] of your cities can bombard the enemy! = [amount] av dina städer kan bombardera fienden!
[amount] enemy units were spotted near our territory = [amount] fientliga enheter siktades nära vårt territorium
[amount] enemy units were spotted in our territory = [amount] fientliga enheter siktades i vårt territorium
-A(n) [nukeType] exploded in our territory! = En [nukeType] exploderade i vårt territorium!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Efter att ha träffats av vår [nukeType], har [civName] förklarat krig mot oss!
The civilization of [civName] has been destroyed! = Civilisationen [civName] har utplånats!
The City-State of [name] has been destroyed! = Stadsstaten [name] har utplånats!
Your [ourUnit] captured an enemy [theirUnit]! = Din [ourUnit] har tillfångatagit en fientlig [theirUnit]!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Din [ourUnit] har plundrat [amount] [Stat] från [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Vi har erövrat ett barbarläger och återtagit [goldAmount] guld!
An enemy [unitType] has joined us! = En fientlig [unitType] har gått med oss!
@@ -2234,7 +2246,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = Religion sprids naturligt till städer [amount] rutor bort
May not generate great prophet equivalents naturally = Kanske inte genererar stora profet motsvarigheter naturligt
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% Trokostnad för att generera Stor Profet motsvarigheter
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [action] enheter byggda i [cityFilter] kan [baseUnitFilter] [amount] extra gånger
Starting tech = Startteknologi
Starts with [tech] = Börjar med [tech]
Starts with [policy] adopted = Startar med [policy] anammad
@@ -2280,17 +2291,20 @@ Creates a [improvementName] improvement on a specific tile = Skapar en [improvem
Founds a new city = Grundar en ny stad
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
-Can build [improvementFilter/terrainFilter] improvements on tiles = Kan bygga [improvementFilter/terrainFilter]-förbättringar på rutor
-May create improvements on water resources = Kan skapa förbättringar på vattenresurser
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Kan stifta en religion
May enhance a religion = Kan förbättra en religion
+Can build [improvementFilter/terrainFilter] improvements on tiles = Kan bygga [improvementFilter/terrainFilter]-förbättringar på rutor
+May create improvements on water resources = Kan skapa förbättringar på vattenresurser
Can be added to [comment] in the Capital = Kan läggas till [comment] i Huvudstaden
Prevents spreading of religion to the city it is next to = Förhindrar spridandet av religion till angränsande stad
Removes other religions when spreading religion = Tar bort andra religioner vid spridning av religion
May Paradrop up to [amount] tiles from inside friendly territory = Kan Fallskärmshoppa upp till [amount] rutor inifrån vänligt territorium
# Requires translation!
Can perform Air Sweep =
-Can [action] [amount] times = Kan [action] [amount] gånger
Can speed up construction of a building = Kan skynda på bygget av en byggnad
Can speed up the construction of a wonder = Kan snabba på bygget av ett underverk
Can hurry technology research = Kan påskynda forskning
@@ -2732,6 +2746,8 @@ Policy = Policy
FounderBelief = FounderBelief
FollowerBelief = FollowerBelief
Building = Byggnad
+ # Requires translation!
+UnitAction =
Unit = Enhet
UnitType = UnitType
Promotion = Befordran
@@ -5532,16 +5548,10 @@ Great Merchant = Stor Köpman
Great Engineer = Stor Ingenjör
-Great Prophet = Stor Profet
-
Great General = Stor General
Khan = Khan
-Missionary = Missionär
-
-Inquisitor = Inkvisitor
-
SS Booster = RS-Booster
SS Cockpit = RS-Cockpit
@@ -5835,6 +5845,7 @@ Wu =
Zhou =
# Requires translation!
Sun =
+Taoism = Daoism
# Requires translation!
Refaat =
@@ -6865,8 +6876,6 @@ Judaism = Judendom
Sikhism = Sikhism
-Taoism = Daoism
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6949,6 +6958,10 @@ Truffles = Tryffel
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Hussar
@@ -7015,6 +7028,14 @@ Machine Gun = Kulspruta
Landship = Landskepp
+Great Prophet = Stor Profet
+
+
+Missionary = Missionär
+
+Inquisitor = Inkvisitor
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Traditional_Chinese.properties b/android/assets/jsons/translations/Traditional_Chinese.properties
index 8968ae6726d8d..3e3b88d27bdf5 100644
--- a/android/assets/jsons/translations/Traditional_Chinese.properties
+++ b/android/assets/jsons/translations/Traditional_Chinese.properties
@@ -762,6 +762,9 @@ Reset = 重設
Show zoom buttons in world screen = 在世界地圖介面中顯示縮放按鈕
Experimental Demographics scoreboard = 啟用實驗性人口統計面板
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
Visual Hints = 視覺提示
@@ -946,11 +949,20 @@ Your city [cityName] can bombard the enemy! = 你的城市[cityName]可以轟炸
[amount] of your cities can bombard the enemy! = [amount]個你的城市可以轟炸敵人!
[amount] enemy units were spotted near our territory = 在我們的領土附近發現了[amount]個敵方單位
[amount] enemy units were spotted in our territory = 在我們的領土內發現了[amount]個敵方單位
-A(n) [nukeType] exploded in our territory! = A(n)[nukeType]在我們的領土上爆炸了!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = 在被我們的[nukeType]擊中後,[civName]向我們宣戰了!
The civilization of [civName] has been destroyed! = [civName]文明已經滅亡!
The City-State of [name] has been destroyed! = [name]城邦已經滅亡!
Your [ourUnit] captured an enemy [theirUnit]! = 你的[ourUnit]俘虜了一個敵方[theirUnit]!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = 你的[ourUnit]從[theirUnit]掠奪[amount][Stat]
We have captured a barbarian encampment and recovered [goldAmount] gold! = 我們摧毀蠻族營地並獲得了[goldAmount]金錢!
An enemy [unitType] has joined us! = 敵方[unitType]加入了我們!
@@ -2057,7 +2069,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = 宗教自然傳播範圍增加[amount]地塊
May not generate great prophet equivalents naturally = 可能不會自然產生大先知
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]%獲得大先知所需的信仰
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = [cityFilter]建造的單位[baseUnitFilter]能額外[action][amount]次
Starting tech = 初始科技
Starts with [tech] = 以[tech]開始
Starts with [policy] adopted = 遊戲開始時已推行[policy]政策
@@ -2101,16 +2112,19 @@ Automatically built in all cities where it is buildable =
Creates a [improvementName] improvement on a specific tile = 在一個特定的地塊上建造[improvementName]
Founds a new city = 建立新城市
Can instantly construct a [improvementFilter] improvement = 可立即建造 [improvementFilter] 地塊設施
-Can build [improvementFilter/terrainFilter] improvements on tiles = 可在地塊上建造[improvementFilter/terrainFilter]
-May create improvements on water resources = 可在水上資源建造設施(工船將被消耗掉)
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = 可以建立宗教
May enhance a religion = 可以強化宗教
+Can build [improvementFilter/terrainFilter] improvements on tiles = 可在地塊上建造[improvementFilter/terrainFilter]
+May create improvements on water resources = 可在水上資源建造設施(工船將被消耗掉)
Can be added to [comment] in the Capital = 可以加進位在首都的[comment]
Prevents spreading of religion to the city it is next to = 防止宗教傳播到鄰近的城市
Removes other religions when spreading religion = 傳播宗教時會清除其他宗教
May Paradrop up to [amount] tiles from inside friendly territory = 可空降到離友好領土[amount]格的位置
Can perform Air Sweep = 可以進行空中掃蕩
-Can [action] [amount] times = 總共可以[action][amount]次
Can speed up construction of a building = 可以加快建築的建設
Can speed up the construction of a wonder = 可以加快建造奇觀
Can hurry technology research = 可以加速科技研究
@@ -2494,6 +2508,8 @@ Policy = 政策
FounderBelief = 創始人信仰
FollowerBelief = 信徒信仰
Building = 建築
+ # Requires translation!
+UnitAction =
Unit = 單位
UnitType = 職業
Promotion = 晉升
@@ -5245,16 +5261,10 @@ Great Merchant = 大商業家
Great Engineer = 大工程師
-Great Prophet = 大先知
-
Great General = 大軍事家
Khan = 汗
-Missionary = 傳教士
-
-Inquisitor = 審判官
-
SS Booster = 飛船推進器
SS Cockpit = 飛船駕駛艙
@@ -5518,6 +5528,7 @@ Zhao = 趙
Wu = 吳
Zhou = 周
Sun = 孫
+Taoism = 道教
Refaat = 雷法特
Heba = 赫芭,
@@ -6242,8 +6253,6 @@ Judaism = 猶太教
Sikhism = 錫克教
-Taoism = 道教
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6326,6 +6335,10 @@ Truffles = 松露
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = 驃騎兵
@@ -6392,6 +6405,14 @@ Machine Gun = 機關槍
Landship = 履帶戰車
+Great Prophet = 大先知
+
+
+Missionary = 傳教士
+
+Inquisitor = 審判官
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Turkish.properties b/android/assets/jsons/translations/Turkish.properties
index 54fd955748907..4648211fc0334 100644
--- a/android/assets/jsons/translations/Turkish.properties
+++ b/android/assets/jsons/translations/Turkish.properties
@@ -69,8 +69,8 @@ Not displayed as an available construction unless [building] is built = [buildin
Not displayed as an available construction without [resource] = [resource] olmadığı sürece gözükmez
Cannot be hurried = Acele ettirilemez
-Choose a free great person = Ücretsiz bir Büyük Kişi seçiniz
-Get [unitName] = [unitName] 'ı alın
+Choose a free great person = Ücretsiz bir Harika Kişi seçiniz
+Get [unitName] = [unitName] birimini alın
Hydro Plant = Hidroelektrik Santrali
[buildingName] obsoleted = [buildingName] kullanımdan kaldırıldı
@@ -121,20 +121,17 @@ Peace = Barış
Research Agreement = Araştırma Antlaşması
Declare war = Savaş ilan et
Declare war on [civName]? = [civName] uygarlığına savaş açılsın mı?
- # Requires translation!
-[civName] will also join them in the war =
- # Requires translation!
-An unknown civilization will also join them in the war =
- # Requires translation!
-This will cancel your defensive pact with [civName] =
+[civName] will also join them in the war = [civName] uygarlığı onlara yardım amaçlı savaşa katılacak
+An unknown civilization will also join them in the war = Bilinmeyen bir uygarlık onlara yardım amaçlı savaşa katılacak
+This will cancel your defensive pact with [civName] = Bu sizin [civName] uygarlığı ile olan savunma paktınızı iptal edecek
Go to on map = Haritaya git
Let's begin! = Hadi Başlayalım!
[civName] has declared war on us! = [civName] bize savaş açtı!
[leaderName] of [nation] = [nation] halkının lideri [leaderName]
You'll pay for this! = Bunun bedelini ödeyeceksin!
-Negotiate Peace = Barışı Görüşmeleri Yapın
+Negotiate Peace = Barış Görüşmeleri Yapın
Peace with [civName]? = [civName] ile barışılsın mı?
-Very well. = Çok iyi.
+Very well. = Pekala.
Farewell. = Elveda.
Sounds good! = Kulağa güzel geliyor!
Not this time. = Bu kez olmaz.
@@ -143,7 +140,7 @@ How about something else... = Başka bir şeye ne dersin...
A pleasure to meet you. = Tanıştığımıza memnun oldum.
Our relationship = İlişkimiz
We have encountered the City-State of [name]! = [name] Şehir Devleti ile karşılaştık!
-Declare Friendship ([numberOfTurns] turns) = Arkadaşlık Bildir ([numberOfTurns] tur)
+Declare Friendship ([numberOfTurns] turns) = Arkadaşlık Teklif Et ([numberOfTurns] tur)
May our nations forever remain united! = Milletlerimiz sonsuza kadar birlik içinde olsun!
Indeed! = Cidden!
Denounce [civName]? = [civName] medeniyeti kınansın mı?
@@ -175,10 +172,8 @@ Ally = Müttefik
[questName] (+[influenceAmount] influence) = [questName] (+ [influenceAmount] etkisi)
[remainingTurns] turns remaining = [remainingTurns] tur kaldı
- # Requires translation!
-Current leader is [civInfo] with [amount] [stat] generated. =
- # Requires translation!
-Current leader is [civInfo] with [amount] Technologies discovered. =
+Current leader is [civInfo] with [amount] [stat] generated. = Şu an [amount] [stat] ürettiği için [civInfo] birinci.
+Current leader is [civInfo] with [amount] Technologies discovered. = Su an [amount] yeni teknoloji geliştirdiği için [civInfo] birinci.
Demands = Talepler
Please don't settle new cities near us. = Lütfen yakınımıza yeni şehirler kurmayın.
@@ -187,10 +182,8 @@ We shall do as we please. = İstediğimiz gibi yaparız.
We noticed your new city near our borders, despite your promise. This will have....implications. = Söz vermene rağmen yeni şehrini sınırlarımızın yakınına kurdun. Bunun ... yaptırımları olacak.
I've been informed that my armies have taken tribute from [civName], a city-state under your protection.\nI assure you, this was quite unintentional, and I hope that this does not serve to drive us apart. = Öğrendiğime göre benim askerlerim sizin himayeniz altındaki [civName] şehir devletinden haraç almış.\nBunun kasıtlı olmadığına dair sizi temin eder, bu tatsız hadisenin ilişkilerimizi bozmamasını umarım.
We asked [civName] for a tribute recently and they gave in.\nYou promised to protect them from such things, but we both know you cannot back that up. = Geçenlerde [civName]'den haraç istedik ve kabul ettiler.\nOnları bu tür şeylerden koruyacağına söz verdin, ama ikimiz de biliyoruz ki, bunu destekleyemezsin.
- # Requires translation!
-It's come to my attention that I may have attacked [civName], a city-state under your protection.\nWhile it was not my goal to be at odds with your empire, this was deemed a necessary course of action. =
- # Requires translation!
-I thought you might like to know that I've launched an invasion of one of your little pet states.\nThe lands of [civName] will make a fine addition to my own. =
+It's come to my attention that I may have attacked [civName], a city-state under your protection.\nWhile it was not my goal to be at odds with your empire, this was deemed a necessary course of action. = Senin koruyacağına söz verdiğin [civName]'e saldırmış olduğumu farkettim.\nAmacım senin imparatorluğuna ters düşmek değildi ama bu yaptığımı gerekli bir hamle olarak görüyorum.
+I thought you might like to know that I've launched an invasion of one of your little pet states.\nThe lands of [civName] will make a fine addition to my own. = Senin o minik şehir devletlerinden birine ordularımı gönderdim, sen de bilmek istersin diye söyleyeyim dedim.\n[civName]'in toprakları benim topraklarımın güzel bir parçası olacak.
Return [unitName] to [civName]? = [unitName] [civName] medeniyetine geri verilsin mi?
The [unitName] we liberated originally belonged to [civName]. They will be grateful if we return it to them. = Kurtardığımız [unitName] aslında [civName]'e aitti. Onlara geri verirsek minnettar olacaklar.
@@ -213,15 +206,13 @@ Pledge to protect = Koruma Taahhütü
Declare Protection of [cityStateName]? = [cityStateName]'e Koruma Taahhüdü ilan etmek istediğine emin misin?
Build [improvementName] on [resourceName] (200 Gold) = [resourceName] üstüne [improvementName] yap (200 Altın)
Gift Improvement = Geliştirme Hediye et
- # Requires translation!
-[civName] is able to provide [unitName] once [techName] is researched. =
+[civName] is able to provide [unitName] once [techName] is researched. = [civName], [techName] araştırmasından sonra [unitName] sağlayabilir.
Diplomatic Marriage ([amount] Gold) = Siyasî Evlilik ([amount] Altın)
We have married into the ruling family of [civName], bringing them under our control. = [civName] medeniyetinin hanedanıyla evlendik ve onları hakimiyetimiz altına aldık.
[civName] has married into the ruling family of [civName2], bringing them under their control. = [civName] medeniyeti [civName2] medeniyetinin hanedanıyla evlendi ve onları hakimiyet altına aldı.
You have broken your Pledge to Protect [civName]! = [civName] medeniyetini koruma taahhüdünüzü bozdunuz!
- # Requires translation!
-City-States grow wary of your aggression. The resting point for Influence has decreased by [amount] for [civName]. =
+City-States grow wary of your aggression. The resting point for Influence has decreased by [amount] for [civName]. = Şehir devletleri saldırganlığının farkına varıyorlar. [civName] ile ilişkilerde durma noktası [amount] azaldı.
[cityState] is being attacked by [civName] and asks all major civilizations to help them out by gifting them military units. = [cityState], [civName] tarafından saldırıya uğruyor ve tüm büyük uygarlıklardan kendilerine askeri birlikler hediye ederek onlara yardım etmelerini istiyor.
[cityState] is being invaded by Barbarians! Destroy Barbarians near their territory to earn Influence. = Barbarlar [cityState] şehir devletine saldırıyorlar! Topraklarının yakınlarındaki barbarları yen ve Nüfuz kazan.
@@ -255,8 +246,7 @@ When Allies: = Müttefikken:
The unique luxury is one of: = Eşsiz lüks:
Demand Tribute = Haraca Bağla
Tribute Willingness = Haraç İstekliliği
- # Requires translation!
-At least 0 to take gold, at least 30 and size 4 city for worker =
+At least 0 to take gold, at least 30 and size 4 city for worker = altın istemek için en az 0, işçi istemek için en az 30 ve 4 büyüklüğünde şehir
Take [amount] gold (-15 Influence) = [amount] altın al (-15 Nüfuz)
Take worker (-50 Influence) = İşçi al (-50 Nüfuz)
[civName] is afraid of your military power! = [civName] ordunuzun gücünden korkuyor!
@@ -268,11 +258,9 @@ Has Ally = Müttefiki Var
Has Protector = Koruyucusu Var
Demanding a Worker = İşçi talep etmek
Demanding a Worker from small City-State = Küçük Şehir Devletinden İşçi Talep Etmek
- # Requires translation!
-Very recently paid tribute =
- # Requires translation!
-Recently paid tribute =
-Influence below -30 = -30 altında Nüfuz
+Very recently paid tribute = Çok yakın zamanda haraç verdi
+Recently paid tribute = Yakın zamanda haraç verdi
+Influence below -30 = Nüfuz -30'un altında
Military Rank = Askeri Rütbe
Military near City-State = Şehir Devleti yakınında asker
Sum: = Toplam:
@@ -282,18 +270,15 @@ Sum: = Toplam:
Trade = Ticaret
Offer trade = Ticaret teklif et
- # Requires translation!
-They'll decide on their turn =
+They'll decide on their turn = Kendi sıralarında karar verecekler
Retract offer = Teklifi geri çek
What do you have in mind? = Aklında ne var?
Our items = Elimizdekiler
Our trade offer = Ticaret teklifimiz
[otherCiv]'s trade offer = [otherCiv] devletinin ticari teklifi
[otherCiv]'s items = [otherCiv] devletinin elindekiler
- # Requires translation!
-+[amount] untradable copy =
- # Requires translation!
-+[amount] untradable copies =
++[amount] untradable copy = +[amount] takas dışı kopya
++[amount] untradable copies = +[amount] takas dışı kopya
Pleasure doing business with you! = Seninle iş yapmak büyük bir zevk!
I think not. = Bence hayır.
That is acceptable. = Bu kabul edilebilir
@@ -302,8 +287,7 @@ Keep going = Devam et
There's nothing on the table = Masanın üstünde hiçbir şey yok
Peace Treaty = Barış Antlaşması
Agreements = Anlaşmalar
- # Requires translation!
-Defensive Pact =
+Defensive Pact = Savunma Paktı
Open Borders = Açık Sınırlar
Gold per turn = Tur başına altın
Cities = Şehirler
@@ -314,16 +298,13 @@ Declare war on [nation] = [nation] devletine savaş ilan et
Luxury resources = Lüks kaynaklar
Strategic resources = Taktiksel kaynaklar
Owned by you: [amountOwned] = Sizdeki miktarı: [amountOwned]
- # Requires translation!
-Non-existent city =
+Non-existent city = Var olmayan şehir
# Unit differences
[resourceName] not required = [resourceName] gerekli değil
- # Requires translation!
-Lost ability (vs [originalUnit]): [ability] =
- # Requires translation!
-Upgrade all [count] [unit] ([cost] gold) =
+Lost ability (vs [originalUnit]): [ability] = Şu özelliği kaybeder ([originalUnit] birimine kıyasla): [ability]
+Upgrade all [count] [unit] ([cost] gold) = Tüm [count] [unit] birimlerini geliştir ([cost] altın)
National ability = Ulusal yetenek
[firstValue] vs [secondValue] = [firstValue] - [secondValue]
Gained = Kazanılmış
@@ -337,10 +318,9 @@ Promotions = Terfiler
Load copied data = Kopyalanan verileri yükle
Reset to defaults = Varsayılanlara döndür
Select nations = Ulusları seçin
- # Requires translation!
-Set available nations for random pool =
-Available nations = Mevcut ülkeler
-Banned nations = Yasaklı ülkeler
+Set available nations for random pool = rastgele seçilebilecek ulusları belirleyin
+Available nations = Mevcut uluslar
+Banned nations = Yasaklı uluslar
Are you sure you want to reset all game options to defaults? = Bütün oyun ayarlarını varsayılana döndürmek istediğinize emin misiniz?
Start game! = Oyunu başlat!
Map Options = Harita seçenekleri
@@ -352,8 +332,7 @@ Max Turns = Azami Tur
Could not load map! = Harita yüklenemedi!
Generated = Oluşturulmuş
Random Generated = Rastgele Oluşturulmuş
- # Requires translation!
-Which options should be available to the random selection? =
+Which options should be available to the random selection? = Rastgele seçimde hangi seçenekler mevcut olmalı?
Existing = Mevcut
Custom = Özel
Map Generation Type = Harita oluşturma türü
@@ -363,23 +342,16 @@ Pangaea = Tekkıta
Continent and Islands = Kıta ve Adalar
Two Continents = İki kıta
Three Continents = Üç kıta
-Four Corners = Dört Kısım
+Four Corners = Dört Köşe
Archipelago = Takımadalar
Inner Sea = İç Deniz
- # Requires translation!
-Perlin =
- # Requires translation!
-Random number of Civilizations =
- # Requires translation!
-Min number of Civilizations =
- # Requires translation!
-Max number of Civilizations =
- # Requires translation!
-Random number of City-States =
- # Requires translation!
-Min number of City-States =
- # Requires translation!
-Max number of City-States =
+Perlin = Karışık
+Random number of Civilizations = Rastgele medeniyet sayısı
+Min number of Civilizations = Asgari medeniyet sayısı
+Max number of Civilizations = Azami medeniyet sayısı
+Random number of City-States = Rastgele şehir devleti sayısı
+Min number of City-States = Asgari şehir devleti sayısı
+Max number of City-States = Azami şehir devleti sayısı
One City Challenge = Tek Şehir Meydan Okuması
Enable Nuclear Weapons = Nükleer Silahları Aç
No City Razing = Şehir Yıkımı Yok
@@ -395,8 +367,7 @@ Domination = Hakimiyet
Cultural = Kültürel
Diplomatic = Diplomatik
Time = Süre
- # Requires translation!
-Your previous options needed to be reset to defaults. =
+Your previous options needed to be reset to defaults. = Önceki ayarlarınızın varsayılana çevirilmesi gerekti.
# Used for random nation indicator in empire selector and unknown nation icons in various overview screens.
# Should be a single character, or at least visually square.
@@ -404,14 +375,13 @@ Your previous options needed to be reset to defaults. =
Map Shape = Harita Şekli
Enabled Map Shapes = Etkin Harita Şekilleri
-Hexagonal = Altıgen
- # Requires translation!
-Flat Earth Hexagonal =
-Rectangular = Dikdörtgen
+Hexagonal = Altıgensel
+Flat Earth Hexagonal = Düz dünya altıgensel
+Rectangular = Dikdörtgensel
Height = Uzunluk
Width = Genişlik
Radius = Çap
-Enable Espionage = Casusu Etkinleştir
+Enable Espionage = Casusluğu Etkinleştir
Resource Setting = Kaynak Ayarı
Enabled Resource Settings = Etkin Kaynak Ayarları
@@ -420,15 +390,13 @@ Sparse = Seyrek
Abundant = Bol
Strategic Balance = Stratejik Denge
Legendary Start = Efsanevi Başlangıç
- # Requires translation!
-This is used for painting resources, not in map generator steps: =
+This is used for painting resources, not in map generator steps: = Bu kaynakları dağıtırken kullanılıyor, harita oluşturma adımlarında değil:
Advanced Settings = Gelişmiş Ayarlar
RNG Seed = RNG Kodu
Map Elevation = Harita Yüksekliği
Temperature extremeness = Sıcaklık aşırılığı
- # Requires translation!
-Temperature shift =
+Temperature shift = Sıcaklık değişimi
Resource richness = Kaynak zenginliği
Vegetation richness = Bitki örtüsü
Rare features richness = Nadir özellikler zenginliği
@@ -481,21 +449,15 @@ Extension mods = Uzantı modları
Base ruleset: = Temel kurallar:
# Note - do not translate the colour names between «». Changing them works if you know what you're doing.
The mod you selected is incorrectly defined! = Seçtiğin mod hatalı tanımlanmış!
- # Requires translation!
-The mod you selected is «RED»incorrectly defined!«» =
+The mod you selected is «RED»incorrectly defined!«» = Seçtiğin mod «RED»hatalı tanımlanmış!«»
The mod combination you selected is incorrectly defined! = Seçtiğin mod kombinasyonu hatalı tanımlanmış!
- # Requires translation!
-The mod combination you selected is «RED»incorrectly defined!«» =
+The mod combination you selected is «RED»incorrectly defined!«» = Seçtiğin mod kombinasyonu «RED»hatalı tanımlanmış!«»
The mod combination you selected has problems. = Seçtiğin mod kombinasyonunda problemler var.
You can play it, but don't expect everything to work! = Oynayabilirsin, fakat her şeyin çalışmasını bekleme!
- # Requires translation!
-The mod combination you selected «GOLD»has problems«». =
- # Requires translation!
-You can play it, but «GOLDENROD»don't expect everything to work!«» =
- # Requires translation!
-This base ruleset is not compatible with the previously selected\nextension mods. They have been disabled. =
- # Requires translation!
-Are you really sure you want to play with the following known problems? =
+The mod combination you selected «GOLD»has problems«». = Seçtiğin mod kombinasyonunda «GOLD»problemler var«».
+You can play it, but «GOLDENROD»don't expect everything to work!«» = Oynayabilirsin, fakat «GOLDENROD»her şeyin çalışmasını bekleme!«»
+This base ruleset is not compatible with the previously selected\nextension mods. They have been disabled. = Bu temel kurallar önceden seçilmiş eklenti modlarıyla uyumsuz.\nO modlar devre dışı bırakıldı.
+Are you really sure you want to play with the following known problems? = Aşağıdaki bilindik sorunlara rağmen oynamak istediğine emin misin?
Base Ruleset = Temel Kurallar
[amount] Techs = [amount] Teknoloji
[amount] Nations = [amount] Ulus
@@ -517,10 +479,8 @@ Anything above 40 may work very slowly on Android! = 40'ın üzerine çıkıldı
Map editor = Harita düzenleyicisi
View = Görüntüle
Generate = Oluştur
- # Requires translation!
-Partial =
- # Requires translation!
-Generator steps =
+Partial = Kısmen
+Generator steps = Oluşturucu adımları
Edit = Düzenle
Rivers = Irmaklar
Load = Yükle
@@ -534,20 +494,15 @@ Are you sure you want to delete this map? = Bu haritayı silmek istediğinize em
It looks like your map can't be saved! = Görünüşe göre haritan kaydedilemiyor!
Exit map editor = Harita düzenleyiciden çık
Change map ruleset = Harita kurallarını değiş
- # Requires translation!
-Change the map to use the ruleset selected on this page =
+Change the map to use the ruleset selected on this page = Haritayı bu sayfada seçili olan temel kuralları kullanacak şekilde değiştir
Revert to map ruleset = Harita kurallarını eski haline çevir
- # Requires translation!
-Reset the controls to reflect the current map ruleset =
+Reset the controls to reflect the current map ruleset = Kontrolleri şu anki haritadaki temel kuralları yansıtacak şekilde değiştir
Features = Özellikler
Starting locations = Başlangıç konumları
- # Requires translation!
-Tile Matching Criteria =
+Tile Matching Criteria = Karo eşleşme kriteri
Complete match = Tam eşleşme
- # Requires translation!
-Except improvements =
- # Requires translation!
-Base and terrain features =
+Except improvements = geliştirmeler hariç
+Base and terrain features = Temel ve yer şekilleri
Base terrain only = Sadece temel arazi
Land or water only = Sadece kara ya da su
@@ -555,53 +510,32 @@ Land or water only = Sadece kara ya da su
Brush ([size]): = Fırça ([size])
# The single letter shown in the [size] parameter above for setting "Floodfill".
# Please do not make this longer, the associated slider will not handle well.
- # Requires translation!
-Floodfill_Abbreviation =
+Floodfill_Abbreviation = K
Error loading map! = Harita yüklenirken sorun oluştu!
Map saved successfully! = Harita başarıyla kaydedildi!
- # Requires translation!
-Current map RNG seed: [amount] =
- # Requires translation!
-Map copy and paste =
+Current map RNG seed: [amount] = şu anki haritanın RNG kodu: [amount]
+Map copy and paste = harita kopyala ve yapıştır
Position: [param] = Konum: [param]
Starting location(s): [param] = Başlangıç konum(lar)ı: [param]
- # Requires translation!
-Continent: [param] ([amount] tiles) =
- # Requires translation!
-Resource abundance =
+Continent: [param] ([amount] tiles) = Kıta: [param] ([amount] karo)
+Resource abundance = Kaynak bolluğu
Change map to fit selected ruleset? = Harita, seçilen kural setine uyacak şekilde değiştirilsin mi?
- # Requires translation!
-Area: [amount] tiles, [amount2]% water, [amount3]% impassable, [amount4] continents/islands =
- # Requires translation!
-Do you want to leave without saving the recent changes? =
- # Requires translation!
-Leave =
- # Requires translation!
-Do you want to load another map without saving the recent changes? =
- # Requires translation!
-River generation failed! =
- # Requires translation!
-Please don't use step 'Landmass' with map type 'Empty', create a new empty map instead. =
- # Requires translation!
-This map has errors: =
- # Requires translation!
-The incompatible elements have been removed. =
- # Requires translation!
-Current map: World Wrap =
- # Requires translation!
-Overlay image =
- # Requires translation!
-Click to choose a file =
- # Requires translation!
-Choose an image =
- # Requires translation!
-Overlay opacity: =
- # Requires translation!
-Invalid overlay image =
- # Requires translation!
-World wrap is incompatible with an overlay and was deactivated. =
- # Requires translation!
-An overlay image is incompatible with world wrap and was deactivated. =
+Area: [amount] tiles, [amount2]% water, [amount3]% impassable, [amount4] continents/islands = Bölge: [amount] karo, %[amount2] su, %[amount3] geçilemez, [amount4] kıtalar/adalar
+Do you want to leave without saving the recent changes? = Son değişiklikleri kaydetmeden çıkmak istiyor musun?
+Leave = Çık
+Do you want to load another map without saving the recent changes? = Son değişiklikleri kaydetmeden yeni bir harita yüklemek istiyor musun?
+River generation failed! = Nehir oluşturma başarısız oldu!
+Please don't use step 'Landmass' with map type 'Empty', create a new empty map instead. = Lütfen 'kara kütlesi' adımını 'boş' harita türüyle kullanma, onun yerine yeni bir boş harita oluştur.
+This map has errors: = Bu haritada hatalar var:
+The incompatible elements have been removed. = Uyumsuz elementler kaldırıldı
+Current map: World Wrap = Şu anki harita: Yuvarlak Dünya
+Overlay image = Kaplama resmi
+Click to choose a file = Bir dosya seçmek için tıkla
+Choose an image = Bir resim seç
+Overlay opacity: = Kaplama resim opaklığı
+Invalid overlay image = Geçersiz kaplama resmi
+World wrap is incompatible with an overlay and was deactivated. = Yuvarlak Dünya kaplama resim ile uyumsuz ve devre dışı bırakıldı.
+An overlay image is incompatible with world wrap and was deactivated. = Kaplama resim Yuvarlak Dünya ile uyumsuz ve devre dışı bırakıldı
## Map/Tool names
My new map = Yeni haritam
@@ -612,23 +546,19 @@ Lakes and coastline = Göller ve sahil
Sprout vegetation = Bitki örtüsü filizle
Spawn rare features = Nadir özellikleri ortaya çıkar
Distribute ice = Buzu dağıt
- # Requires translation!
-Assign continent IDs =
+Assign continent IDs = Kıta kimliklerini ata
Place Natural Wonders = Doğal Harikalar yerleştir
Let the rivers flow = Nehirlerin akmasını sağla
Spread Resources = Kaynakları dağıt
Create ancient ruins = Antik kalıntılar oluştur
- # Requires translation!
-Floodfill =
+Floodfill = Kova
[nation] starting location = [nation] başlangıç yeri
- # Requires translation!
-Any Civ starting locations =
- # Requires translation!
-Any Civ =
+Any Civ starting locations = Herhangi bir medeniyet başlangıç yerleri
+Any Civ = Herhangi bir medeniyet
Remove features = Özellikleri kaldır
Remove improvement = Geliştirmeleri kaldır
Remove resource = Kaynakları kaldır
-Remove starting locations = Başlangıç konumlarını kaldır
+Remove starting locations = Başlangıç yerlerini kaldır
Remove rivers = Irmakları kaldır
Spawn river from/to = Şurada/Şuraya ırmak oluştur
Bottom left river = Sol alttaki ırmak
@@ -642,8 +572,7 @@ Username = Kullanıcı adı
Multiplayer = Çok oyunculu
Could not download game! = Oyun indirilemedi!
Could not upload game! = Oyun yüklenemedi!
- # Requires translation!
-Couldn't connect to Multiplayer Server! =
+Couldn't connect to Multiplayer Server! = Çok oyunculu sunucuya bağlanılamadı!
Retry = Yeniden dene
Join game = Oyuna Katıl
Invalid game ID! = Geçersiz oyun kimliği!
@@ -661,8 +590,7 @@ Player name already used! = Oyuncu ismi zaten kullanıldı!
Player ID already used! = Oyuncu kimliği zaten kullanıldı!
Player ID is incorrect = Oyuncu kimliği hatalı
Select friend = Arkadaş seç
- # Requires translation!
-Select [thingToSelect] =
+Select [thingToSelect] = [thingToSelect] seç
Friends list = Arkadaş listesi
Add friend = Arkadaş ekle
Edit friend = Arkadaşı düzenle
@@ -701,8 +629,7 @@ Paste gameID from clipboard = Panodan oyun kimliği yapıştır
GameID = Oyun kimliği
Game name = Oyun adı
Loading latest game state... = En son oyun durumu yükleniyor...
- # Requires translation!
-You are not allowed to spectate! =
+You are not allowed to spectate! = Seyretme iznin yok!
Couldn't download the latest game state! = En son oyun durumu indirilemiyor!
Resign = İstifa etmek
Are you sure you want to resign? = İstifa etmek istediğinizden emin misiniz?
@@ -720,30 +647,18 @@ Days = Gün
[amount] Days = [amount] Gün
Server limit reached! Please wait for [time] seconds = Sunucu limiti aşıldı! Lütfen [time] saniye bekleyin
File could not be found on the multiplayer server = Çevrimiçi sunucuda dosya bulunamadı
- # Requires translation!
-Unhandled problem, [errorMessage] =
- # Requires translation!
-Please enter your server password =
- # Requires translation!
-Set password =
- # Requires translation!
-Password must be at least 6 characters long =
- # Requires translation!
-Failed to set password! =
- # Requires translation!
-Password set successfully for server [serverURL] =
- # Requires translation!
-Password =
- # Requires translation!
-Your userId is password secured =
- # Requires translation!
-Set a password to secure your userId =
- # Requires translation!
-Authenticate =
- # Requires translation!
-This server does not support authentication =
- # Requires translation!
-Authentication failed =
+Unhandled problem, [errorMessage] = Çözülmemiş sorun, [errorMessage]
+Please enter your server password = Lütfen sunucunun şifresini gir
+Set password = Şifreyi belirle
+Password must be at least 6 characters long = Şifre en az 6 karakter olmalı
+Failed to set password! = Şifre belirlenemedi!
+Password set successfully for server [serverURL] = [serverURL] sunucusu için şifre başarıyla belirlendi
+Password = Şifre
+Your userId is password secured = Kullanıcı kimliğin şifre ile korunuyor
+Set a password to secure your userId = Kullanıcı kimliğini korumak için şifre koy
+Authenticate = Kimliği doğrula
+This server does not support authentication = Bu sunucu kimlik doğrulamayı desteklemiyor
+Authentication failed = Kimlik doğrulama başarısız oldu
# Save game menu
@@ -751,18 +666,14 @@ Current saves = Mevcut kayıtlar
Show autosaves = Otomatik kaydetmeleri göster
Saved game name = Kaydedilmiş oyun adı
# This is the save game name the dialog will suggest
- # Requires translation!
-[player] - [turns] turns =
+[player] - [turns] turns = [player] - [turns]. tur
Copy to clipboard = Panoya kopyala
Copy saved game to clipboard = Kaydedilmiş oyunu panoya kopyala
- # Requires translation!
-Could not load game! =
+Could not load game! = Oyun yüklenemedi!
Could not load game from clipboard! = Panodan oyun yüklenemedi!
Could not load game from custom location! = Oyun özel konumdan yüklenemiyor!
- # Requires translation!
-The file data seems to be corrupted. =
- # Requires translation!
-The save was created with an incompatible version of Unciv: [version]. Please update Unciv to this version or later and try again. =
+The file data seems to be corrupted. = Dosyadaki bilgiler bozulmuş gibi görünüyor.
+The save was created with an incompatible version of Unciv: [version]. Please update Unciv to this version or later and try again. = Bu kayıt uncivin uyumsuz bir sürümü ile kaydedilmiş: [version]. Lütfen uncivi bu sürüme ya da üstüne güncelleyip tekrar deneyin.
Load [saveFileName] = Yükle [saveFileName]
Are you sure you want to delete this save? = Bu kaydı silmek istediğine emin misin?
Delete save = Kayıtları sil
@@ -802,48 +713,35 @@ Display = Görünüş
### Screen subgroup
- # Requires translation!
-Screen =
+Screen = Ekran
Screen Mode = Ekran Kipi
Windowed = Pencereli
Fullscreen = Tam Ekran
Borderless = Kenarsız
- # Requires translation!
-Screen Size =
+Screen Size = Ekran Boyutu
### Enable panning the map when you move the mouse to the edge of the window
- # Requires translation!
-Map mouse auto-scroll =
- # Requires translation!
-Map panning speed =
+Map mouse auto-scroll = Harita fare ile otomatik hareket eder
+Map panning speed = Harita hareket hızı
### Graphics subgroup
Tileset = Grafik seti
- # Requires translation!
-Unitset =
- # Requires translation!
-UI Skin =
+Unitset = Birim seti
+UI Skin = Arayüz görünümü
### UI subgroup
- # Requires translation!
-UI =
+UI = Arayüz
- # Requires translation!
-Notifications on world screen =
- # Requires translation!
-Disabled =
- # Requires translation!
-Hidden =
- # Requires translation!
-Visible =
- # Requires translation!
-Permanent =
+Notifications on world screen = Dünya ekranındaki bildirimler
+Disabled = Devre dışı
+Hidden = Gizli
+Visible = Görünür
+Permanent = Kalıcı
- # Requires translation!
-Minimap size =
+Minimap size = Minik harita boyutu
# This is the leftmost Minimap size slider position
off = kapalı
@@ -853,23 +751,22 @@ Do you want to reset completed tutorials? = Tamamlanmış eğiticileri sıfırla
Reset = Sıfırla
Show zoom buttons in world screen = Dünya ekranında yakınlaştırma tuşlarını göster
+Experimental Demographics scoreboard = deneysel demografi skor tablosu
+
# Requires translation!
-Experimental Demographics scoreboard =
+Size of Unitset art in Civilopedia =
### Visual Hints subgroup
- # Requires translation!
-Visual Hints =
+Visual Hints = Görsel ipuçları
Show worked tiles = Çalışılan alanları göster
Show resources and improvements = Kaynakları ve geliştirmeleri göster
Show tile yields = Karo verimleri Göster
Show unit movement arrows = Birim hareket oklarını göster
- # Requires translation!
-Show suggested city locations for units that can found cities =
+Show suggested city locations for units that can found cities = Şehir kuran birlikler için önerilen şehir konumlarını göster
Show pixel units = Piksel birimlerini göster
Show pixel improvements = Piksel geliştirmeleri göster
- # Requires translation!
-Unit icon opacity =
+Unit icon opacity = Birim ikonu opaklığı
### Performance subgroup
@@ -879,22 +776,17 @@ When disabled, saves battery life but certain animations will be suspended = Dev
## Gameplay tab
Gameplay = Oynanış
Check for idle units = Boş birimleri kontrol edin
- # Requires translation!
-Auto Unit Cycle =
+Auto Unit Cycle = Otomatik Birim Döngüsü
Move units with a single tap = Tek dokunuşla birimleri taşı
Auto-assign city production = Şehir üretimini otomatik ata
Auto-build roads = Yolları otomatik oluştur
Automated workers replace improvements = Otomatik moddaki işciler geliştirmeleri değiştirebilsin
- # Requires translation!
-Automated units move on turn start =
- # Requires translation!
-Automated units can upgrade =
- # Requires translation!
-Automated units choose promotions =
+Automated units move on turn start = Otomatiğe alınmış birimler tur başında oynar
+Automated units can upgrade = Otomatiğe alınmış birimler kendini geliştirir
+Automated units choose promotions = Otomatiğe alınmış birimler kendi terfilerini seçer
Order trade offers by amount = Kaynakları ticaret penceresinde sayıya göre büyükten küçüğe sırala
Ask for confirmation when pressing next turn = Sonraki tura basınca onaylama sor
- # Requires translation!
-Notifications log max turns =
+Notifications log max turns = Bildirim günlüğü azami tur sayısı
## Language tab
@@ -915,12 +807,9 @@ Currently playing: [title] = Şimdi çalan: [title]
Download music = Müzik indir
Downloading... = İndiriliyor...
Could not download music! = Müzik indirilemedi!
- # Requires translation!
-—Paused— =
- # Requires translation!
-—Default— =
- # Requires translation!
-—History— =
+—Paused— = —Durdurulmuş—
+—Default— = —Varsayılan—
+—History— = —Geçmiş—
## Advanced tab
Advanced = Gelişmiş
@@ -937,38 +826,29 @@ Enable display cutout (requires restart) =
Max zoom out = Maks uzaklaştırma
Font family = Yazı Türü Ailesi
- # Requires translation!
-Font size multiplier =
+Font size multiplier = Yazı Türü katsayısı
Default Font = Varsayılan Yazı Türü
Generate translation files = Çeviri dosyaları oluştur
Translation files are generated successfully. = Çeviri dosyaları başarıyla oluşturuldu
# Requires translation!
Fastlane files are generated successfully. =
- # Requires translation!
-Update Mod categories =
+Update Mod categories = Mod kategorilerini güncelle
- # Requires translation!
-Enable Easter Eggs =
- # Requires translation!
-Enlarge selected notifications =
+Enable Easter Eggs = Sürpriz yumurtaları etkinleştir
+Enlarge selected notifications = Seçilmiş bildirimleri büyüt
## Keys tab
- # Requires translation!
-Keys =
- # Requires translation!
-Please see the Tutorial. =
- # Requires translation!
-Hit the desired key now =
+Keys = Tuşlar
+Please see the Tutorial. = Lütfen Öğreticiye bakın
+Hit the desired key now = Şimdi istenilen tuşa basın
## Locate mod errors tab
Locate mod errors = Mod hatalarını bulun
- # Requires translation!
-Check extension mods based on: =
- # Requires translation!
--none- =
+Check extension mods based on: = Eklenti modlarına şuna göre bak:
+-none- = -hiç-
Reload mods = Modları yeniden yükle
-Checking mods for errors... = Hatalar için modalar kontrol ediliyor...
+Checking mods for errors... = Hatalar için modlar kontrol ediliyor...
No problems found. = Sorun bulunamadı.
Autoupdate mod uniques = Mod özelliklerini otomatik güncelle
Uniques updated! = Özellikler güncellendi!
@@ -980,105 +860,78 @@ Debug = Hata Ayıklama
Show = Göster
Hide = Gizle
HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED! = AŞIRI DENEYSEL - UYARILDINIZ!
- # Requires translation!
-You need to restart the game for this change to take effect. =
+You need to restart the game for this change to take effect. = Bu değişimin etki göstermesi için oyunu yeniden başlatmanız gerekiyor.
# Notifications
Research of [technologyName] has completed! = [technologyName] araştırması tamamlandı!
- # Requires translation!
-We gained [amount] Science from Research Agreement =
-[construction] has become obsolete and was removed from the queue in [cityName]! = [construction] yapısı eskidi, [cityName] şehrindeki inşaat sırasından kaldırılacak!
-[construction] has become obsolete and was removed from the queue in [amount] cities! = [construction] üretimi gereksiz bulundu ve [amount] şehirde üretim sırasından kaldırıldı!
-[cityName] changed production from [oldUnit] to [newUnit] = [cityName] şehri üretimi [oldUnit]'ten [newUnit]'e çevirdi
-[amount] cities changed production from [oldUnit] to [newUnit] = [amount] tane şehir üretimi [oldUnit]'ten [newUnit]'e çevirdi
+We gained [amount] Science from Research Agreement = Araştırma Antlaşmasından [amount] Bilim elde ettik
+[construction] has become obsolete and was removed from the queue in [cityName]! = [construction] yapısı eskidi, [cityName] şehrindeki inşaat sırasından kaldırıldı!
+[construction] has become obsolete and was removed from the queue in [amount] cities! = [construction] yapısı eskidi ve [amount] şehirde üretim sırasından kaldırıldı!
+[cityName] changed production from [oldUnit] to [newUnit] = [cityName] şehri [oldUnit] üretimini [newUnit] üretimine çevirdi
+[amount] cities changed production from [oldUnit] to [newUnit] = [amount] tane şehir [oldUnit] üretimini [newUnit] üretimine çevirdi
Excess production for [wonder] converted to [goldAmount] gold = [wonder] için fazladan üretim puanları [goldAmount] altına çevrildi
You have entered a Golden Age! = Altın Çağ'a girdiniz!
-[resourceName] revealed near [cityName] = [resourceName], [cityName] yakınlarında görüldü
-[n] sources of [resourceName] revealed, e.g. near [cityName] = [cityName] civarı [n] tane [resourceName] kaynağı ortaya çıktı
+[resourceName] revealed near [cityName] = [resourceName], [cityName] yakınlarında ortaya çıktı
+[n] sources of [resourceName] revealed, e.g. near [cityName] = [cityName] civarında [n] tane [resourceName] kaynağı ortaya çıktı
A [greatPerson] has been born in [cityName]! = [cityName] şehrinde bir [greatPerson] doğdu!
We have encountered [civName]! = [civName] ile karşılaştık!
[cityStateName] has given us [stats] as a token of goodwill for meeting us = [cityStateName] tanışmamızın şerefine bize [stats] verdi!
[cityStateName] has given us [stats] as we are the first major civ to meet them = [cityStateName] onlar ile tanışan ilk büyük uygarlık olmamız şerefine bize [stats] verdi!
- # Requires translation!
-[cityStateName] has also given us [stats] =
- # Requires translation!
-[cityStateName] gave us a [unitName] as a gift! =
+[cityStateName] has also given us [stats] = [cityStateName] bize ayrıca [stats] verdi
+[cityStateName] gave us a [unitName] as a gift! = [cityStateName] bize bir [unitName] hediye etti!
Cannot provide unit upkeep for [unitName] - unit has been disbanded! = [unitName] için birim bakımı sağlanamıyor - birim dağıtıldı!
[cityName] has grown! = [cityName] büyüdü!
[cityName] is starving! = [cityName] açlıktan ölüyor!
[construction] has been built in [cityName] = [construction], [cityName] şehrinde yapıldı
-[wonder] has been built in a faraway land = [wonder] uzak bir ülkede inşa edilmiş
+[wonder] has been built in a faraway land = [wonder] uzak diyarlarda inşa edilmiş
[civName] has completed [construction]! = [civName] [construction] üretimini tamamladı!
-An unknown civilization has completed [construction]! = Bilinmeyen bir uygarlık [construction] üretimini tamamladı!
-The city of [cityname] has started constructing [construction]! = [cityname] şehri [construction] üretmeye başladı!
-[civilization] has started constructing [construction]! = [civilization] uygarlığı [construction] üretmeye başladı!
-An unknown civilization has started constructing [construction]! = Bilinmeyen bir uygarlık [construction] üretmeye başladı!
-Work has started on [construction] = [construction] üzerine çalışma başlatıldı
+An unknown civilization has completed [construction]! = Bilinmeyen bir uygarlık [construction] inşaatını tamamladı!
+The city of [cityname] has started constructing [construction]! = [cityname] şehri [construction] inşa etmeye başladı!
+[civilization] has started constructing [construction]! = [civilization] uygarlığı [construction] inşa etmeye başladı!
+An unknown civilization has started constructing [construction]! = Bilinmeyen bir uygarlık [construction] inşa etmeye başladı!
+Work has started on [construction] = [construction] inşa edilmeye başlandı
[cityName] cannot continue work on [construction] = [cityName], [construction] üzerinde çalışmaya devam edemiyor
[cityName] has expanded its borders! = [cityName] sınırlarını genişletti!
Your Golden Age has ended. = Altın Çağınız sona erdi.
[cityName] has been razed to the ground! = [cityName] yerle bir edildi!
We have conquered the city of [cityName]! = [cityName] şehrini fethettik!
- # Requires translation!
-Your citizens are revolting due to very high unhappiness! =
+Your citizens are revolting due to very high unhappiness! = Vatandaşların aşırı mutsuzluk yüzünden isyan ediyorlar!
An enemy [unit] has attacked [cityName] = Bir düşman [unit], [cityName] şehrine saldırdı
- # Requires translation!
-An enemy [unit] ([amount] HP) has attacked [cityName] ([amount2] HP) =
+An enemy [unit] ([amount] HP) has attacked [cityName] ([amount2] HP) = Bir düşman [unit] ([amount] can), [cityName] ([amount2] can) şehrine saldırdı
An enemy [unit] has attacked our [ourUnit] = Bir düşman [unit], [ourUnit] birimimize saldırdı
- # Requires translation!
-An enemy [unit] ([amount] HP) has attacked our [ourUnit] ([amount2] HP) =
+An enemy [unit] ([amount] HP) has attacked our [ourUnit] ([amount2] HP) = Bir düşman [unit] ([amount] can), [ourUnit] ([amount2] can) birimimize saldırdı
Enemy city [cityName] has attacked our [ourUnit] = Düşman şehri [cityName], [ourUnit] birimimize saldırdı
- # Requires translation!
-Enemy city [cityName] ([amount] HP) has attacked our [ourUnit] ([amount2] HP) =
+Enemy city [cityName] ([amount] HP) has attacked our [ourUnit] ([amount2] HP) = Düşman şehri [cityName] ([amount] can), [ourUnit] ([amount2] can) birimimize saldırdı
An enemy [unit] has captured [cityName] = Bir düşman [unit], [cityName] şehrini ele geçirdi
- # Requires translation!
-An enemy [unit] ([amount] HP) has captured [cityName] ([amount2] HP) =
-An enemy [unit] has raided [cityName] = Bir düşman [unit] [cityName] şehrini yağmaladı
- # Requires translation!
-An enemy [unit] ([amount] HP) has raided [cityName] ([amount2] HP) =
+An enemy [unit] ([amount] HP) has captured [cityName] ([amount2] HP) = Bir düşman [unit] ([amount] can), [cityName] ([amount2] can) şehrini ele geçirdi
+An enemy [unit] has raided [cityName] = Bir düşman [unit], [cityName] şehrini yağmaladı
+An enemy [unit] ([amount] HP) has raided [cityName] ([amount2] HP) = Bir düşman [unit] ([amount] can), [cityName] ([amount2] can) şehrini yağmaladı
An enemy [unit] has captured our [ourUnit] = Bir düşman [unit], [ourUnit] birimimizi ele geçirdi
- # Requires translation!
-An enemy [unit] ([amount] HP) has captured our [ourUnit] ([amount2] HP) =
+An enemy [unit] ([amount] HP) has captured our [ourUnit] ([amount2] HP) = Bir düşman [unit] ([amount] can), [ourUnit] ([amount2] can) birimimizi ele geçirdi
An enemy [unit] has destroyed our [ourUnit] = Bir düşman [unit], [ourUnit] birimimizi yok etti
- # Requires translation!
-An enemy [unit] ([amount] HP) has destroyed our [ourUnit] ([amount2] HP) =
+An enemy [unit] ([amount] HP) has destroyed our [ourUnit] ([amount2] HP) = Bir düşman [unit] ([amount] can), [ourUnit] ([amount2] can) birimimizi yok etti
Your [ourUnit] has destroyed an enemy [unit] = Sizin [ourUnit]'iniz bir düşman [unit]'ini yoketti.
- # Requires translation!
-Your [ourUnit] ([amount] HP) has destroyed an enemy [unit] ([amount2] HP) =
+Your [ourUnit] ([amount] HP) has destroyed an enemy [unit] ([amount2] HP) = Sizin [ourUnit]'iniz ([amount] can) bir düşman [unit]'ini ([amount2] can) yoketti.
An enemy [RangedUnit] has destroyed the defence of [cityName] = Bir düşman [RangedUnit], [cityName] şehrinin savunmasını yok etti
- # Requires translation!
-An enemy [RangedUnit] ([amount] HP) has destroyed the defence of [cityName] ([amount2] HP) =
+An enemy [RangedUnit] ([amount] HP) has destroyed the defence of [cityName] ([amount2] HP) = Bir düşman [RangedUnit] ([amount] can), [cityName] ([amount2] can) şehrinin savunmasını yok etti
Enemy city [cityName] has destroyed our [ourUnit] = Düşman şehri [cityName], [ourUnit] birimimizi yok etti
- # Requires translation!
-Enemy city [cityName] ([amount] HP) has destroyed our [ourUnit] ([amount2] HP) =
+Enemy city [cityName] ([amount] HP) has destroyed our [ourUnit] ([amount2] HP) = Düşman şehri [cityName] ([amount] can), [ourUnit] ([amount2] can) birimimizi yok etti
An enemy [unit] was destroyed while attacking [cityName] = Bir düşman [unit], [cityName] şehrine saldırırken yok edildi
- # Requires translation!
-An enemy [unit] ([amount] HP) was destroyed while attacking [cityName] ([amount2] HP) =
+An enemy [unit] ([amount] HP) was destroyed while attacking [cityName] ([amount2] HP) = Bir düşman [unit] ([amount] can), [cityName] ([amount2] can) şehrine saldırırken yok edildi
An enemy [unit] was destroyed while attacking our [ourUnit] = Bir düşman [unit], [ourUnit] birimimize saldırırken yok edildi
- # Requires translation!
-An enemy [unit] ([amount] HP) was destroyed while attacking our [ourUnit] ([amount2] HP) =
- # Requires translation!
-Our [attackerName] ([amount] HP) was destroyed by an intercepting [interceptorName] ([amount2] HP) =
- # Requires translation!
-Our [attackerName] ([amount] HP) was destroyed by an unknown interceptor =
- # Requires translation!
-Our [interceptorName] ([amount] HP) intercepted and destroyed an enemy [attackerName] ([amount2] HP) =
- # Requires translation!
-Our [attackerName] ([amount] HP) destroyed an intercepting [interceptorName] ([amount2] HP) =
- # Requires translation!
-Our [interceptorName] ([amount] HP) intercepted and was destroyed by an enemy [attackerName] ([amount2] HP) =
- # Requires translation!
-Our [interceptorName] ([amount] HP) intercepted and was destroyed by an unknown enemy =
- # Requires translation!
-Our [attackerName] ([amount] HP) was attacked by an intercepting [interceptorName] ([amount2] HP) =
- # Requires translation!
-Our [attackerName] ([amount] HP) was attacked by an unknown interceptor =
- # Requires translation!
-Our [interceptorName] ([amount] HP) intercepted and attacked an enemy [attackerName] ([amount2] HP) =
- # Requires translation!
-Nothing tried to intercept our [attackerName] =
+An enemy [unit] ([amount] HP) was destroyed while attacking our [ourUnit] ([amount2] HP) = Bir düşman [unit] ([amount] can), [ourUnit] ([amount2] can) birimimize saldırırken yok edildi
+Our [attackerName] ([amount] HP) was destroyed by an intercepting [interceptorName] ([amount2] HP) = Bizim [attackerName] ([amount] can) birimimiz, bir önleyici düşman [interceptorName] ([amount2] can) birimi tarafından yok edildi
+Our [attackerName] ([amount] HP) was destroyed by an unknown interceptor = Bizim [attackerName] ([amount] can) birimimiz, bilinmeyen bir önleyici tarafından yok edildi
+Our [interceptorName] ([amount] HP) intercepted and destroyed an enemy [attackerName] ([amount2] HP) = Bizim [interceptorName] ([amount] can) birimimiz, bir düşman [attackerName] ([amount2] can) birimini önledi ve yok etti
+Our [attackerName] ([amount] HP) destroyed an intercepting [interceptorName] ([amount2] HP) = Bizim [attackerName] ([amount] can) birimimiz, bir önleyici düşman [interceptorName] ([amount2] can) birimini yok etti
+Our [interceptorName] ([amount] HP) intercepted and was destroyed by an enemy [attackerName] ([amount2] HP) = Bizim [interceptorName] ([amount] can) birimimiz, bir düşman [attackerName] ([amount2] can) birimini önlerken yok edildi
+Our [interceptorName] ([amount] HP) intercepted and was destroyed by an unknown enemy = Bizim [interceptorName] ([amount] can) birimimiz, bilinmeyen bir düşmanı önlerken yok edildi
+Our [attackerName] ([amount] HP) was attacked by an intercepting [interceptorName] ([amount2] HP) = Bizim [attackerName] ([amount] can) birimimiz, bir önleyici [interceptorName] ([amount2] can) tarafından saldırıya uğradı
+Our [attackerName] ([amount] HP) was attacked by an unknown interceptor = Bizim [attackerName] ([amount] can) birimimiz bilinmeyen bir önleyici tarafından saldırıya uğradı
+Our [interceptorName] ([amount] HP) intercepted and attacked an enemy [attackerName] ([amount2] HP) = Bizim [interceptorName] ([amount] can) birimimiz, bir düşman [attackerName] ([amount2] can) birimini önledi ve saldırdı
+Nothing tried to intercept our [attackerName] = Bizim [attackerName] birimimizi hiçbir şey önlemeye çalışmadı
An enemy [unit] was spotted near our territory = Bölgemiz yakınında bir düşman [unit] tespit edildi
An enemy [unit] was spotted in our territory = Bölgemizde bir düşman [unit] tespit edildi
# Requires translation!
@@ -1087,22 +940,28 @@ Your city [cityName] can bombard the enemy! = [cityName] şehriniz düşmanı to
[amount] of your cities can bombard the enemy! = [amount] şehriniz düşmanı topa tutabilir!
[amount] enemy units were spotted near our territory = [amount] düşman birimi bölgemizin yakınında tespit edildi
[amount] enemy units were spotted in our territory = [amount] düşman birimi bölgemizde tespit edildi
-A(n) [nukeType] exploded in our territory! = Sınırlarımız dahilinde bir [nukeType] patladı!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Bizim [nukeType] füzemiz tarafından vurulduktan sonra [civName] bize savaş açtı!
The civilization of [civName] has been destroyed! = [civName] medeniyeti yok edildi!
The City-State of [name] has been destroyed! = [name] Şehir Devleti yok edildi!
Your [ourUnit] captured an enemy [theirUnit]! = [ourUnit] birliğiniz düşman [theirUnit] birliğini ele geçirdi!
+Your captured [unitName] has been returned by [civName] = Ele geçirilen [unitName] birimin [civName] uygarlığının yardımıyla geri verildi
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = [ourUnit] birliğiniz [theirUnit] birliğinden yağma yoluyla ganimet olarak [amount] [Stat] aldı
-We have captured a barbarian encampment and recovered [goldAmount] gold! = Bir barbar kampı aldık ve [goldAmount] altını geri kazandık!
- # Requires translation!
-An enemy [unitType] has joined us! =
- # Requires translation!
-[unitName] can be promoted! =
+We have captured a barbarian encampment and recovered [goldAmount] gold! = Bir barbar kampını ele geçirdik ve [goldAmount] altını geri kazandık!
+An enemy [unitType] has joined us! = Bir düşman [unitType] birimi bize katıldı!
+[unitName] can be promoted! = [unitName] terfi ettirilebilir!
# This might be needed for a rewrite of Germany's unique - see #7376
A barbarian [unitType] has joined us! = Bir barbar [unitType] bize katıldı!
-We have found survivors in the ruins - population added to [cityName] = Yıkıntıların arasında kurtulanlar bulduk - [cityName] şehrine nüfus eklendi
+We have found survivors in the ruins - population added to [cityName] = Yıkıntıların arasında hayatta kalanlar bulduk - [cityName] şehrine nüfus eklendi
We have discovered the lost technology of [techName] in the ruins! = Yıkıntılarda kayıp [techName] teknolojisini keşfettik!
A [unitName] has joined us! = Bir [unitName] bize katıldı!
An ancient tribe trains our [unitName] in their ways of combat! = Eski bir kabile [unitName] birimimizi kendi savaş teknikleriyle eğitti!
@@ -1119,19 +978,16 @@ You and [name] are no longer allies! = Siz ve [name] artık müttefik değilsini
[cityName] has been connected to your capital! = [cityName] başkentinize bağlandı!
[cityName] has been disconnected from your capital! = [cityName], başkentinizle bağlantısı kesildi!
[civName] has accepted your trade request = [civName] ticaret talebinizi kabul etti
- # Requires translation!
-[civName] has made a counteroffer to your trade request =
+[civName] has made a counteroffer to your trade request = [civName] teklifinize karşı bunu önerdi
[civName] has denied your trade request = [civName] ticaret talebinizi reddetti
[tradeOffer] from [otherCivName] has ended = [otherCivName] medeniyetinden gelen [tradeOffer] sona erdi
[tradeOffer] to [otherCivName] has ended = [otherCivName] uygarlığına giden [tradeOffer] teklifimiz sona erdi
- # Requires translation!
-[tradeOffer] from [otherCivName] will end in [amount] turns =
- # Requires translation!
-[tradeOffer] from [otherCivName] will end next turn =
+[tradeOffer] from [otherCivName] will end in [amount] turns = [otherCivName] uygarlığından gelen [tradeOffer] [amount] tur içinde bitecek
+[tradeOffer] from [otherCivName] will end next turn = [otherCivName] uygarlığından gelen [tradeOffer] sonraki tur bitecek
One of our trades with [nation] has ended = [nation] ile yaptığımız ticaretlerden biri sona erdi
One of our trades with [nation] has been cut short = [nation] ile yaptığımız ticaretlerden biri kısa kesildi
-[nation] agreed to stop settling cities near us! = [nation] yakınımızdaki şehirlere yerleşmeyi durdurmayı kabul etti!
-[nation] refused to stop settling cities near us! = [nation] yakınımızdaki şehirlere yerleşmeyi bırakmayı reddetti!
+[nation] agreed to stop settling cities near us! = [nation] yakınlarımıza şehir kurmaktan vazgeçmeyi kabul etti!
+[nation] refused to stop settling cities near us! = [nation] yakınlarımıza şehir kurmaktan vazgeçmeyi reddetti!
We have allied with [nation]. = [nation] ile ittifak oluşturduk.
We have lost alliance with [nation]. = [nation] ile ittifakımızı kaybettik.
We have discovered [naturalWonder]! = [naturalWonder] doğal harikasını keşfettik!
@@ -1144,22 +1000,17 @@ Received [goldAmount] Gold for capturing [cityName] = [cityName] ele geçirildi
Our proposed trade is no longer relevant! = Teklif edilen ticaret talebimiz artık geçerli değil!
[defender] could not withdraw from a [attacker] - blocked. = [defender], ona saldıran [attacker] biriminden kaçamadı.
[defender] withdrew from a [attacker] = [defender], ona saldıran [attacker] biriminden kaçtı
- # Requires translation!
-By expending your [unit] you gained [Stats]! =
+By expending your [unit] you gained [Stats]! = [unit] birimini harcadığın için [Stats] kazandın!
Your territory has been stolen by [civName]! = [civName] sizin toprağınızı çaldı!
-Clearing a [forest] has created [amount] Production for [cityName] = Bir [forest] temizlemek, [cityName] için [amount] Üretim yarattı
+Clearing a [forest] has created [amount] Production for [cityName] = Bir [forest] temizlemek, [cityName] için [amount] Üretim sağladı
[civName] assigned you a new quest: [questName]. = [civName] size yeni bir görev atadı: [questName].
[civName] rewarded you with [influence] influence for completing the [questName] quest. = [civName], [questName] görevini tamamlamanız için sizi [influence] nüfuz ile ödüllendirdi.
- # Requires translation!
-[civName] no longer needs your help with the [questName] quest. =
- # Requires translation!
-The [questName] quest for [civName] has ended. It was won by [civNames]. =
+[civName] no longer needs your help with the [questName] quest. = [civName] artık [questName] görevinde yardıma ihtiyaç duymuyor.
+The [questName] quest for [civName] has ended. It was won by [civNames]. = [civName] şehir devletinin [questName] görevi bitti. [civNames] kazandı.
The resistance in [cityName] has ended! = [cityName] şehrindeki direniş sona erdi!
[cityName] demands [resource]! = [cityName] [resource] istiyor!
- # Requires translation!
-Because they have [resource], the citizens of [cityName] are celebrating We Love The King Day! =
- # Requires translation!
-We Love The King Day in [cityName] has ended. =
+Because they have [resource], the citizens of [cityName] are celebrating We Love The King Day! = [resource] kaynağına sahip oldukları için [cityName] vatandaşları Çok Yaşa Kral Gününü kutluyorlar!
+We Love The King Day in [cityName] has ended. = [cityName] şehrinde Çok Yaşa Kral Günü sona erdi.
Our [name] took [tileDamage] tile damage and was destroyed = Bizim [name], [tileDamage] karo hasarı aldı ve yok edildi
Our [name] took [tileDamage] tile damage = Bizim [name], [tileDamage] karo hasarı aldı
[civName] has adopted the [policyName] policy = [civName] [policyName] politikasını kabul etti
@@ -1167,24 +1018,16 @@ An unknown civilization has adopted the [policyName] policy = Bilinmeyen bir uyg
You gained [Stats] as your religion was spread to [cityName] = Dininiz [cityName] şehrinde yayıldığı için [Stats] kazandınız
You gained [Stats] as your religion was spread to an unknown city = Dininiz bilinmeyen bir şehirde yayıldığı için [Stats] kazandınız
Your city [cityName] was converted to [religionName]! = [cityName] şehriniz [religionName] dinini benimsedi!
- # Requires translation!
-Your [unitName] lost its faith after spending too long inside enemy territory! =
- # Requires translation!
-An [unitName] has removed your religion [religionName] from its Holy City [cityName]! =
- # Requires translation!
-An [unitName] has restored [cityName] as the Holy City of your religion [religionName]! =
- # Requires translation!
-You have unlocked [ability] =
+Your [unitName] lost its faith after spending too long inside enemy territory! = [unitName] birimin düşman bölgesi içinde fazla vakit geçirdiği için inancını kaybetti
+An [unitName] has removed your religion [religionName] from its Holy City [cityName]! = [religionName] dininin Kutsal Şehri [cityName], bir [unitName] tarafından dininden vazgeçirildi!
+An [unitName] has restored [cityName] as the Holy City of your religion [religionName]! = [cityName] şehri, bir [unitName] tarafından tekrardan [religionName] dininin kutsal şehri olarak belirlendi!
+You have unlocked [ability] = [ability] kilidini açtın
# Requires translation!
A new b'ak'tun has just begun! =
- # Requires translation!
-A Great Person joins you! =
- # Requires translation!
-[civ1] has liberated [civ2] =
- # Requires translation!
-[civ] has liberated an unknown civilization =
- # Requires translation!
-An unknown civilization has liberated [civ] =
+A Great Person joins you! = Sana bir Harika Kişi katıldı!
+[civ1] has liberated [civ2] = [civ1] uygarlığı, [civ2] uygarlığını özgürlüğüne kavuşturdu
+[civ] has liberated an unknown civilization = [civ] uygarlığı, bilinmeyen bir uygarlığı özgürlüğüne kavuşturdu
+An unknown civilization has liberated [civ] = Bilinmeyen bir uygarlık, [civ] uygarlığını özgürlüğüne kavuşturdu
# Trigger notifications
@@ -1193,56 +1036,34 @@ An unknown civilization has liberated [civ] =
# If your language puts the effect before the cause - like {Gained [2] [Worker] unit(s)} {due to constructing [The Pyramids]} -
# put the english word 'true' behind the '=', otherwise 'false'.
# Don't translate these words to your language, only put 'true' or 'false'. Defaults to 'true'.
- # Requires translation!
-EffectBeforeCause =
+EffectBeforeCause = false
## Trigger effects
- # Requires translation!
-Gained [amount] [unitName] unit(s) =
- # Requires translation!
-Gained [stats] =
- # Requires translation!
-You may choose a free Policy =
- # Requires translation!
-You may choose [amount] free Policies =
- # Requires translation!
-You gain the [policy] Policy =
- # Requires translation!
-You enter a Golden Age =
- # Requires translation!
-You have gained [amount] [resourceName] =
- # Requires translation!
-You have lost [amount] [resourceName] =
+Gained [amount] [unitName] unit(s) = [amount] tane [unitName] birimi kazandık
+Gained [stats] = [stats] kazandık
+You may choose a free Policy = Yeni bir politika seçebilirsin
+You may choose [amount] free Policies = [amount] tane yeni politika seçebilirsin
+You gain the [policy] Policy = [policy] politikasına sahip olduk
+You enter a Golden Age = Altın Çağa girdin
+You have gained [amount] [resourceName] = [amount] [resourceName] kazandık
+You have lost [amount] [resourceName] = [amount] [resourceName] kaybettik
## Trigger causes
- # Requires translation!
-due to researching [tech] =
- # Requires translation!
-due to adopting [policy] =
- # Requires translation!
-due to discovering [naturalWonder] =
- # Requires translation!
-due to entering the [eraName] =
- # Requires translation!
-due to constructing [buildingName] =
- # Requires translation!
-due to gaining a [unitName] =
- # Requires translation!
-due to founding a city =
- # Requires translation!
-due to discovering a Natural Wonder =
- # Requires translation!
-due to our [unitName] defeating a [otherUnitName] =
- # Requires translation!
-due to our [unitName] being defeated by a [otherUnitName] =
- # Requires translation!
-due to our [unitName] losing [amount] HP =
- # Requires translation!
-due to our [unitName] being promoted =
- # Requires translation!
-from the ruins =
+due to researching [tech] = [tech] teknolojisini araştırdığımız için
+due to adopting [policy] = [policy] politikasını benimsediğimiz için
+due to discovering [naturalWonder] = keşfettiğimiz [naturalWonder] sayesinde
+due to entering the [eraName] = Şu anki dönemimiz, [eraName] olarak değiştiği için
+due to constructing [buildingName] = [buildingName] inşa ettiğimiz için
+due to gaining a [unitName] = [unitName] birimini elde ettiğimiz için
+due to founding a city = Yeni bir şehir kurduğumuz için
+due to discovering a Natural Wonder = Bir doğal harikayı keşfettiğimiz için
+due to our [unitName] defeating a [otherUnitName] = [unitName] birimimiz, [otherUnitName] birimini yendiği için
+due to our [unitName] being defeated by a [otherUnitName] = [unitName] birimimiz [otherUnitName] birimine yenildiği için
+due to our [unitName] losing [amount] HP = [unitName] birimimiz [amount] can haybettiği için
+due to our [unitName] being promoted = [unitName] birimimiz terfi ettiği için
+from the ruins = Yıkıntılardan
# World Screen UI
@@ -1252,8 +1073,7 @@ Waiting for [civName]... = [civName] bekleniyor...
in = içinde
Next turn = Sonraki tur
Confirm next turn = Sonraki turu onayla
- # Requires translation!
-Move automated units =
+Move automated units = Otomatiğe alınmış birimler hareket etsin
[currentPlayerCiv] ready? = [currentPlayerCiv] hazır mı?
1 turn = 1 tur
[numberOfTurns] turns = [numberOfTurns] tur
@@ -1268,15 +1088,13 @@ Strength = Güç
Ranged strength = Menzilli gücü
Bombard strength = Bombardıman gücü
Range = Menzil
- # Requires translation!
-XP =
+XP = Deneyim
Move unit = Birliği taşı
Stop movement = Hareketi durdur
- # Requires translation!
-Show unit destination =
+Show unit destination = Birimin gittiği hedefi göster
Swap units = Yer Değiş
Construct improvement = İyileştirme inşa et
-Automate = Otomatikleştir
+Automate = Otomatiğe al
Stop automation = Otomasyonu durdur
Construct road = Yol yap
Fortify = Tahkimat yap
@@ -1287,19 +1105,13 @@ Sleep until healed = İyileşene kadar uyu
Moving = Harekette
Set up = Kur
Paradrop = Paraşütle Atla
- # Requires translation!
-Air Sweep =
- # Requires translation!
-Add in capital =
- # Requires translation!
-Add to [comment] =
-Upgrade to [unitType] ([goldCost] gold) = [unitType] birliğine yükseltin ([goldCost] altın)
- # Requires translation!
-Upgrade to [unitType]\n([goldCost] gold, [resources]) =
- # Requires translation!
-Transform to [unitType] =
- # Requires translation!
-Transform to [unitType]\n([resources]) =
+Air Sweep = Hava Taraması
+Add in capital = Başkente ekle
+Add to [comment] = [comment]'e ekleme yap
+Upgrade to [unitType] ([goldCost] gold) = [unitType] birimine yükselt ([goldCost] altın)
+Upgrade to [unitType]\n([goldCost] gold, [resources]) = [unitType] birimine yükselt\n([goldCost] altın, [resources])
+Transform to [unitType] = [unitType] birimine çevir
+Transform to [unitType]\n([resources]) = [unitType] birimine çevir\n([resources])
Found city = Şehir kur
Promote = Terfi et
Health = Sağlık
@@ -1311,18 +1123,13 @@ Explore = Keşfet
Stop exploration = Keşfi bırak
Pillage = Yağmala
Pillage [improvement] = Yağmala [improvement]
- # Requires translation!
-[improvement] (Pillaged!) =
- # Requires translation!
-Repair [improvement] - [turns] =
+[improvement] (Pillaged!) = [improvement] (yağmalanmış!)
+Repair [improvement] - [turns] = [improvement] geliştirmesini tamir et - [turns]
Wait = Bekle
-Are you sure you want to pillage this [improvement]? = bu[improvement]'i yağmalamak istediğinizden emin misiniz?
- # Requires translation!
-We have looted [amount] from a [improvement] =
- # Requires translation!
-We have looted [amount] from a [improvement] which has been sent to [cityName] =
- # Requires translation!
-An enemy [unitName] has pillaged our [improvement] =
+Are you sure you want to pillage this [improvement]? = Bu [improvement] geliştirmesini yağmalamak istediğinizden emin misiniz?
+We have looted [amount] from a [improvement] = [improvement] geliştirmesinden [amount] altın yağmaladık
+We have looted [amount] from a [improvement] which has been sent to [cityName] = [improvement] geliştirmesinden [amount] altın yağmaladık ve [cityName] şehrine yolladık
+An enemy [unitName] has pillaged our [improvement] = Bir düşman [unitName] bizim [improvement] geliştirmemizi yağmaladı
Create [improvement] = [improvement] oluştur
Start Golden Age = Altın Çağı Başlat
# Requires translation!
@@ -1360,29 +1167,24 @@ Load game = Oyunu yükle
Main menu = Ana Menü
Resume = Devam et
Cannot resume game! = Oyuna devam edilemiyor!
-Not enough memory on phone to load game! = Telefonda oyunu yükleyemeye yetecek kadar hafıza yok!
+Not enough memory on phone to load game! = Telefonda kayıtlı oyunu yüklemeye yetecek kadar hafıza yok!
Quickstart = Hızlı Başla
- # Requires translation!
-Cannot start game with the default new game parameters! =
+Cannot start game with the default new game parameters! = Varsayılan yeni oyun ayarları ile oyun başlatılamıyor!
Victory status = Zafer durumu
Social policies = Sosyal politikalar
Community = Topluluk
Close = Kapat
Do you want to exit the game? = Oyundan çıkmak istiyor musunuz?
Exit = Çık
-Start bias: = Başlangıç eğilimleri:
+Start bias: = Başlangıç eğilimi:
Avoid [terrain] = [terrain] arazisinden kaçın
# Maya calendar popup
- # Requires translation!
-The Mayan Long Count =
- # Requires translation!
-Your scientists and theologians have devised a systematic approach to measuring long time spans - the Long Count. During the festivities whenever the current b'ak'tun ends, a Great Person will join you. =
- # Requires translation!
-While the rest of the world calls the current year [year], in the Maya Calendar that is: =
- # Requires translation!
-[amount] b'ak'tun, [amount2] k'atun, [amount3] tun =
+The Mayan Long Count = Maya Uzun Sayımı
+Your scientists and theologians have devised a systematic approach to measuring long time spans - the Long Count. During the festivities whenever the current b'ak'tun ends, a Great Person will join you. = Bilim ve din adamlarınız uzun zaman aralıklarını ölçmeye sistematik bir yaklaşım geliştirdiler - Uzun Sayım. Festivaller sırasında mevcut b'ak'tun bittiğinde, sana bir harika şahsiyet katılacak.
+While the rest of the world calls the current year [year], in the Maya Calendar that is: = Dünyanın kalanı şu anki yıla [year] derken, Maya takviminde ise bu yıla:
+[amount] b'ak'tun, [amount2] k'atun, [amount3] tun = [amount] b'ak'tun, [amount2] k'atun, [amount3] tun deniyor
# City screen
@@ -1392,46 +1194,34 @@ Stop razing city = Şehri yıkmayı bırak
Buy for [amount] gold = [amount] altına satın al
Buy = Satın al
Currently you have [amount] [stat]. = Şu an sizde [amount] [stat] var.
- # Requires translation!
-Would you like to purchase [constructionName] for [buildingGoldCost] [stat]? =
+Would you like to purchase [constructionName] for [buildingGoldCost] [stat]? = [constructionName] yapısını [buildingGoldCost] [stat] karşılığında satın almak istiyor musun?
Purchase = Satın al
No space available to place [unit] near [city] = [unit] için [city] şehrine yakın yerleştirilecek alan yok
Maintenance cost = Bakım maliyeti
Pick construction = Yapı seç
Pick improvement = İyileştirme seç
- # Requires translation!
-Tile owned by [civName] - [cityName] =
- # Requires translation!
-Tile owned by [civName] (You) =
- # Requires translation!
-Unowned tile =
+Tile owned by [civName] - [cityName] = Bu karonun sahibi [civName] - [cityName]
+Tile owned by [civName] (You) = Bu karonun sahibi [civName] (Sen)
+Unowned tile = Sahipsiz karo
Provides [resource] = [resource] sağlar
Provides [amount] [resource] = [amount] tane [resource] sağlar
Replaces [improvement] = [improvement] yerine geçer
- # Requires translation!
-Not in city work range =
+Not in city work range = Şehrin çalışma menzilinde değil
Pick now! = Şimdi seç!
- # Requires translation!
-Remove [feature] first =
- # Requires translation!
-Research [tech] first =
- # Requires translation!
-Have this tile close to your borders =
- # Requires translation!
-Have this tile inside your empire =
- # Requires translation!
-Acquire more [resource] =
-Build [building] = [building] yap
+Remove [feature] first = Önce [feature] kaldırılmalı
+Research [tech] first = önce [tech] araştırılmalı
+Have this tile close to your borders = Sınırlarına yakın bir karo olmalı
+Have this tile inside your empire = Sınırlarının içinde bir karo olmalı
+Acquire more [resource] = Daha fazla [resource] elde et
+Build [building] = [building] inşa et
Train [unit] = [unit] eğit
Produce [thingToProduce] = [thingToProduce] Üret
Nothing = Hiçbir şey
Annex city = Şehri ilhak et
Specialist Buildings = Uzmanların Binaları
Specialist Allocation = Uzman Tahsisi
- # Requires translation!
-Manual Specialists =
- # Requires translation!
-Auto Specialists =
+Manual Specialists = Uzamnları Elle Ata
+Auto Specialists = Otomatik Uzman Ataması
Specialists = Uzmanlar
[specialist] slots = [specialist] yuvaları
Food eaten = Yenen yiyecekler
@@ -1439,75 +1229,53 @@ Unassigned population = Atanmamış nüfus
[turnsToExpansion] turns to expansion = Genişlemeye [turnsToExpansion] tur
Stopped expansion = Genişleme durdu
[turnsToPopulation] turns to new population = Yeni nüfusa [turnsToPopulation] tur
-Food converts to production = Gıda üretime dönüştürür
-[turnsToStarvation] turns to lose population = Nüfusu kaybetmeye [turnsToStarvation] tur
+Food converts to production = Gıda üretime dönüşüyor
+[turnsToStarvation] turns to lose population = Nüfus kaybetmeye [turnsToStarvation] tur
Stopped population growth = Nüfus artışı durdurdu
-In resistance for another [numberOfTurns] turns = Başka [numberOfTurns] tur için direnç
- # Requires translation!
-We Love The King Day for another [numberOfTurns] turns =
+In resistance for another [numberOfTurns] turns = [numberOfTurns] tur boyunca direnmeye devam edecek
+We Love The King Day for another [numberOfTurns] turns = [numberOfTurns] tur boyunca Çok Yaş Kral Günü kutlanmaya devam edecek
Demanding [resource] = [resource] istiyor
Sell for [sellAmount] gold = [sellAmount] altına sat
- # Requires translation!
-Sell =
+Sell = Sat
Are you sure you want to sell this [building]? = Bu [building] yapısını satmak istediğinizden emin misiniz?
Free = Bedava
[greatPerson] points = [greatPerson] puanı
-Great person points = Büyük Kişi puanları
+Great person points = Harika Kişi puanları
Current points = Mevcut puan
Points per turn = Tur başına puan
Convert production to gold at a rate of 4 to 1 = Üretimi 4'e 1 oranında altına çevir
-Convert production to science at a rate of [rate] to 1 = Üretimi [rate]'e 1 oranında bilime dönüştür
- # Requires translation!
-Convert production to [stat] at a rate of [rate] to 1 =
- # Requires translation!
-Production to [stat] conversion in cities changed by [relativeAmount]% =
+Convert production to science at a rate of [rate] to 1 = Üretimi, her [rate] üretim 1 bilim olacak şekilde bilime dönüştür
+Convert production to [stat] at a rate of [rate] to 1 = Üretimi, her [rate] üretim 1 [stat] olacak şekilde dönüştür
+Production to [stat] conversion in cities changed by [relativeAmount]% = Şehirlerde [stat] olarak dönüştürülen üretimin dönüşüm oranı %[relativeAmount] değişti
The city will not produce anything. = Şehir hiçbir şey üretmeyecek
- # Requires translation!
-Owned by [cityName] =
-Worked by [cityName] = [cityName] tarafından çalıştı
+Owned by [cityName] = sahibi [cityName]
+Worked by [cityName] = [cityName] tarafından işleniyor
Lock = Kilitle
Unlock = Kilidi aç
-Move to city = Şehre taşın
- # Requires translation!
-Reset Citizens =
- # Requires translation!
-Citizen Management =
- # Requires translation!
-Avoid Growth =
- # Requires translation!
-Default Focus =
- # Requires translation!
-[stat] Focus =
+Move to city = Şehre git
+Reset Citizens = Vatandaşları sıfırla
+Citizen Management = Vatandaş yönetimi
+Avoid Growth = Büyümekten kaçın
+Default Focus = Normal Dağılım
+[stat] Focus = [stat] üretimine odaklan
Please enter a new name for your city = Lütfen şehrinize yeni bir ad girin
- # Requires translation!
-Please select a tile for this building's [improvement] =
- # Requires translation!
-Move to the top of the queue =
- # Requires translation!
-Move to the end of the queue =
- # Requires translation!
-Add to the top of the queue =
- # Requires translation!
-Add to the queue in all cities =
- # Requires translation!
-Add or move to the top in all cities =
- # Requires translation!
-Remove from the queue in all cities =
- # Requires translation!
-Disable =
- # Requires translation!
-Enable =
+Please select a tile for this building's [improvement] = Bu yapının [improvement] geliştirmesi için bir karo seçin
+Move to the top of the queue = Sıranın en üstüne yolla
+Move to the end of the queue = Sıranın en sonuna yolla
+Add to the top of the queue = Sıranın en üstüne ekle
+Add to the queue in all cities = Bütün şehirlerde sıraya ekle
+Add or move to the top in all cities = Bütün şehirlerde sıranın en üstüne yolla/ekle
+Remove from the queue in all cities = Bütün şehirlerde sıradan çıkar
+Disable = Devre dışı bırak
+Enable = Etkinleştir
# Specialized Popups - Ask for text or numbers, file picker
Invalid input! Please enter a different string. = Geçersiz giriş! Lütfen farklı bir yazı dene.
- # Requires translation!
-Invalid input! Please enter a valid number. =
+Invalid input! Please enter a valid number. = Geçersiz girdi! Lütfen geçerli bir sayı girin.
Please enter some text = Lütfen yazı gir
- # Requires translation!
-Please enter a file name =
- # Requires translation!
-File name: =
+Please enter a file name = Lütfen bir dosyanın ismini yaz
+File name: = Dosya ismi
# Technology UI
@@ -1538,16 +1306,16 @@ Cannot gain more XP from Barbarians = Barbarlardan daha fazla deneyim kazanamazs
# Battle modifier categories
defence vs ranged = menzilli birliğe karşı savunma
-[percentage] to unit defence = [percentage] birim savunmasına
-Attacker Bonus = Saldırgan Bonusu
-Defender Bonus = Savunma Bonusu
-Landing = İniş
-Boarding = Uçuş
-Flanking = Kuşatma
-vs [unitType] = vs [unitType]
+[percentage] to unit defence = birim savunmasına [percentage]
+Attacker Bonus = Saldıran Bonusu
+Defender Bonus = Savunan Bonusu
+Landing = Karaya çıkıyor
+Boarding = Suya giriyor
+Flanking = Arkalıyor
+vs [unitType] = [unitType] karşısında
Terrain = Arazi
-Tile = Bölge
-Missing resource = Eksik kaynak
+Tile = Karo
+Missing resource = Kaynağı eksik
Adjacent units = Bitişik birimler
Adjacent enemy units = Bitişik düşman birimleri
Combat Strength = Saldırı Gücü
@@ -1555,7 +1323,7 @@ Across river = Irmak üstünden
Temporary Bonus = Geçici Bonus
Garrisoned unit = Şehir içinde konumlanmış birim
Attacking Bonus = Saldırı Bonusu
-defence vs [unitType] = [unitType] birimine karşı savunma
+defence vs [unitType] = [unitType] karşısında savunma
[tileFilter] defence = [tileFilter] savunma
Defensive Bonus = Savunma Bonusu
# Requires translation!
@@ -1578,12 +1346,11 @@ Spread Religion = Dini Yay
Spread [religionName] = [religionName] Dinini Yay
Remove Heresy = Kafirleri Yok Et
Found a Religion = Bir Din Kur
- # Requires translation!
-Enhance a Religion =
+Enhance a Religion = Bir Dini Değiştir
Your citizens have been happy with your rule for so long that the empire enters a Golden Age! = Vatandaşlarınız, imparatorluğu Altın Çağ'a taşıyacak kadar uzun süredir yönetiminizden memnun kalmışlar!
You have entered the [newEra]! = [newEra]'a girdiniz!
[civName] has entered the [eraName]! = [civName] [eraName]'a girdi!
-[policyBranch] policy branch unlocked! = [policyBranch] siyaset dalının kilidi açıldı!
+[policyBranch] policy branch unlocked! = [policyBranch] politika dalının kilidi açıldı!
# Overview screens
@@ -1592,10 +1359,8 @@ Total = Toplam
Stats = İstatistikler
Policies = Politikalar
Base happiness = Temel mutluluk
- # Requires translation!
-Traded Luxuries =
- # Requires translation!
-City-State Luxuries =
+Traded Luxuries = Takas edilmiş lüksler
+City-State Luxuries = Şehir devleti lüksleri
Occupied City = İşgal Altındaki Şehir
Buildings = Binalar
Wonders = Harikalar
@@ -1606,119 +1371,85 @@ Final = Sonuç
Other = Diğer
Population = Nüfus
City-States = Şehir Devletleri
-Tile yields = Bölge verimi
+Tile yields = Karo getirileri
Trade routes = Ticaret yolları
Maintenance = Bakım
Transportation upkeep = Ulaştırma maliyeti
Unit upkeep = Birim bakımı
Trades = Ticaretler
- # Requires translation!
-Current trades =
- # Requires translation!
-Pending trades =
+Current trades = Şu anki ticaretler
+Pending trades = Bekleyen ticaretler
Score = Puan
Units = Birimler
-Unit Supply = Birim Temini
-Base Supply = Temel Tedarik
-Total Supply = Toplam Tedarik
+Unit Supply = Birim Kapasitesi
+Base Supply = Temel Kapasite
+Total Supply = Toplam Kapasite
In Use = Kullanımda
-Supply Deficit = Arz Açığı
+Supply Deficit = Kapasite Aşımı
Production Penalty = Üretim Cezası
-Increase your supply or reduce the amount of units to remove the production penalty = Üretim cezasını kaldırmak için tedariğini arttır ya da birimlerini azalt
+Increase your supply or reduce the amount of units to remove the production penalty = Üretim cezasını kaldırmak için kapasiteni artır ya da birimlerini azalt
Name = Ad
Closest city = En yakın şehir
Action = Eylem
-Upgrade = Güncelle
+Upgrade = Geliştir
Defeated = Mağlup
[numberOfCivs] Civilizations in the game = Oyunda [numberOfCivs] medeniyet var
Our Civilization: = Medeniyetimiz
Known and alive ([numberOfCivs]) = Bilinen ve hüküm süren ([numberOfCivs]) medeniyet
Known and defeated ([numberOfCivs]) = Bilinen ve yenilmiş ([numberOfCivs]) medeniyet
-Tiles = Bölgeler
+Tiles = Karolar
Natural Wonders = Doğal Harikalar
Treasury deficit = Hazine açığı
Unknown = Bilinmiyor
-Not built = Yapılmadı
-Not found = Bulunamadı
+Not built = İnşa edilmedi
+Not found = Bulunmadı
Known = Biliniyor
Owned = Sahipli
- # Requires translation!
-Near [city] =
+Near [city] = [city] yakınlarında
Somewhere around [city] = [city] şehrinin etrafında bir yerde
Far away = Çok uzak
Status = Durum
Current turn = Şu anki tur
Turn [turnNumber] = Tur [turnNumber]
Location = Konum
- # Requires translation!
-Unimproved =
- # Requires translation!
-Number of tiles with this resource\nin your territory, without an\nappropriate improvement to use it =
- # Requires translation!
-We Love The King Day =
- # Requires translation!
-WLTK+ =
- # Requires translation!
-Number of your cities celebrating\n'We Love The King Day' thanks\nto access to this resource =
- # Requires translation!
-WLTK demand =
- # Requires translation!
-WLTK- =
- # Requires translation!
-Trade offer =
- # Requires translation!
-Resources we're offering in trades =
- # Requires translation!
-Number of your cities\ndemanding this resource for\n'We Love The King Day' =
- # Requires translation!
-Politics =
- # Requires translation!
-Show global politics =
- # Requires translation!
-Show diagram =
- # Requires translation!
-At war with [enemy] =
- # Requires translation!
-Defensive pact with [civName] =
- # Requires translation!
-Friends with [civName] =
- # Requires translation!
-an unknown civilization =
- # Requires translation!
-[numberOfTurns] Turns Left =
- # Requires translation!
-Denounced [otherCiv] =
- # Requires translation!
-Allied with [civName] =
- # Requires translation!
-Civilization Info =
- # Requires translation!
-Relations =
- # Requires translation!
-Trade request =
- # Requires translation!
-Garrisoned by unit =
- # Requires translation!
-Status\n(puppet, resistance or being razed) =
+Unimproved = Geliştirilmemiş
+Number of tiles with this resource\nin your territory, without an\nappropriate improvement to use it = Bölgende bu kaynağı işleyebilecek\geliştirmeye sahip olmadan kaynağı\nbarındıran karo sayısı
+We Love The King Day = Çok Yaşa Kral Günü
+WLTK+ = ÇYKG+
+Number of your cities celebrating\n'We Love The King Day' thanks\nto access to this resource = Bu kaynağa sahip olduğu\niçin 'Çok Yaşa Kral Günü'nü kutlayan\nşehirlerinin sayısı
+WLTK demand = ÇYKG talebi
+WLTK- = ÇYKG-
+Trade offer = Ticaret teklifi
+Resources we're offering in trades = Ticaretlerde sunduğumuz kaynaklar
+Number of your cities\ndemanding this resource for\n'We Love The King Day' = 'Çok Yaşa Kral Günü'\niçin bu kaynağı talep eden\nşehirlerinin sayısı
+Politics = Siyaset
+Show global politics = Küresel Siyaseti göster
+Show diagram = Şemayı göster
+At war with [enemy] = [enemy] ile savaşta
+Defensive pact with [civName] = [civName] ile savunma paktı imzalamış
+Friends with [civName] = [civName] ile arkadaş
+an unknown civilization = Bilinmeyen bir uygarlık
+[numberOfTurns] Turns Left = [numberOfTurns] tur kaldı
+Denounced [otherCiv] = [otherCiv] uygarlığını kınadı
+Allied with [civName] = [civName] ile müttefik
+Civilization Info = Uygarlık bilgisi
+Relations = İlişkiler
+Trade request = Ticaret isteği
+Garrisoned by unit = Şehirde birim var
+Status\n(puppet, resistance or being razed) = Durumu\n(kukla mı, direniyor mu ya da yıkılıyor mu)
# Victory
- # Requires translation!
-[victoryType] Victory =
- # Requires translation!
-Built [building] =
- # Requires translation!
-Add all [comment] in capital =
- # Requires translation!
-Destroy all players =
+[victoryType] Victory = [victoryType] Zaferi
+Built [building] = [building] inşa etti
+Add all [comment] in capital = Bütün [comment] başkente ekle
+Destroy all players = Bütün oyuncuları yok et
Capture all capitals = Bütün başkentleri ele geçir
Complete [amount] Policy branches = [amount] Siyaset dalı tamamla
- # Requires translation!
-You have won a [victoryType] Victory! =
- # Requires translation!
-[civilization] has won a [victoryType] Victory! =
+You have won a [victoryType] Victory! = [victoryType] Zaferi kazandın!
+[civilization] has won a [victoryType] Victory! = [civilization] [victoryType] Zaferi kazandı!
Your civilization stands above all others! The exploits of your people shall be remembered until the end of civilization itself! = Medeniyetiniz diğerlerinin üzerinde duruyor! Halkınızın istismarları medeniyetin sonuna kadar hatırlanacak!
-You have been defeated. Your civilization has been overwhelmed by its many foes. But your people do not despair, for they know that one day you shall return - and lead them forward to victory! = Yenildin. Medeniyetiniz birçok düşmanı bunalmış durumda. Ama halkınız umutsuzluğa kapılmıyor, çünkü bir gün geri döneceğinizi biliyor ve onları zafere götürüyorlar!
+You have been defeated. Your civilization has been overwhelmed by its many foes. But your people do not despair, for they know that one day you shall return - and lead them forward to victory! = Yenildin. Medeniyetin çokça düşmanları tarafından alt edildi. Ama halkınız umutsuzluğa kapılmıyor, çünkü bir gün geri döneceğinizi ve onları zafere götüreceğinizi biliyorlar!
One more turn...! = Bir tur daha ...!
Destroy [civName] = [civName] medeniyetini yok et
Capture [cityName] = [cityName] şehrini ele geçir
@@ -1731,12 +1462,9 @@ Majority religion of ? * [civName] =
Our status = Durumumuz
Global status = Küresel vaziyet
Rankings = Sıralamalar
- # Requires translation!
-Charts =
- # Requires translation!
-Demographics =
- # Requires translation!
-Demographic =
+Charts = Tablolar
+Demographics = Demografikler
+Demographic = Demografik
Rank = Sıra
Value = Değer
Best = En İyi
@@ -1749,29 +1477,19 @@ Choose a civ to vote for = Oy vereceğiniz uygarlığı şeçin
Choose who should become the world leader and win a Diplomatic Victory! = Kimin dünya lideri olup diplomatik zafer kazanacağını seçin!
Vote for [civilizationName] = [civilizationName] lehine oy kullan
Vote for World Leader = Dünya Lideri için oy ver
- # Requires translation!
-Abstain =
+Abstain = Çekimser Kal
Continue = Devam et
Abstained = Çekimser
Voted for = Buna oy verdi
- # Requires translation!
-[number] votes =
- # Requires translation!
-[number] vote =
- # Requires translation!
-No valid votes were cast. =
- # Requires translation!
-Minimum votes for electing a world leader: [number] =
- # Requires translation!
-Tied in first position: [civNames] =
- # Requires translation!
-No world leader was elected. =
- # Requires translation!
-You have been elected world leader! =
- # Requires translation!
-[leaderName] of [civ] has been elected world leader! =
- # Requires translation!
-Replay =
+[number] votes = [number] oy
+[number] vote = [number] oy
+No valid votes were cast. = Hiç geçerli oy atılmadı
+Minimum votes for electing a world leader: [number] = Dünya lideri seçilebilmesi için gereken asgari oy: [number]
+Tied in first position: [civNames] = [civNames] berabere kaldılar
+No world leader was elected. = Dünya lideri seçilmedi
+You have been elected world leader! = Sen dünya lideri seçildin!
+[leaderName] of [civ] has been elected world leader! = [civ] uygarlığından [leaderName] dünya lideri olarak seçildi!
+Replay = Yeniden oynat
# Capturing a city
@@ -2371,7 +2089,7 @@ Gifts of Gold to City-States generate [relativeAmount]% more Influence = Şehir
Can spend Gold to annex or puppet a City-State that has been your ally for [amount] turns. =
# Requires translation!
City-State territory always counts as friendly territory =
-Allied City-States will occasionally gift Great People = İttifak olduğunuz şehir devletleri arada Büyük Şahsiyetler hediye eder
+Allied City-States will occasionally gift Great People = İttifak olduğunuz şehir devletleri arada Harika Şahsiyetler hediye eder
# Requires translation!
Will not be chosen for new games =
# Requires translation!
@@ -2450,7 +2168,7 @@ Provides a sum of gold each time you spend a Great Person = Her Harika Kişi har
[stats] whenever a Great Person is expended =
# Requires translation!
[relativeAmount]% Gold from Great Merchant trade missions =
-Great General provides double combat bonus = Büyük General çifte savaş bonusu sağlar
+Great General provides double combat bonus = Harika General çifte savaş bonusu sağlar
# Requires translation!
Receive a free Great Person at the end of every [comment] (every 394 years), after researching [tech]. Each bonus person can only be chosen once. =
# Requires translation!
@@ -2513,8 +2231,6 @@ Religion naturally spreads to cities [amount] tiles away =
May not generate great prophet equivalents naturally =
# Requires translation!
[relativeAmount]% Faith cost of generating Great Prophet equivalents =
- # Requires translation!
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times =
Starting tech = Başlangıç teknolojisi
Starts with [tech] = [tech] ile başlar
# Requires translation!
@@ -2562,7 +2278,7 @@ Indicates the capital city = Başkenti gösterir
Provides 1 extra copy of each improved luxury resource near this City = Bu Şehir civarında geliştirilmiş her Lüks kaynaktan 1 tane daha sağlar
Destroyed when the city is captured = Şehir ele geçirildiğinde yıkılır
Doubles Gold given to enemy if city is captured = Şehir ele geçirilirse düşmana verilen Altını ikiye katlar
-Remove extra unhappiness from annexed cities = Fethedilen şehirlerdeki fazladan mutsuzluğu giderir
+Remove extra unhappiness from annexed cities = İlhak edilmiş şehirlerdeki fazladan mutsuzluğu giderir
Connects trade routes over water = Ticaret yollarını su üzerinden bağlar
# Requires translation!
Automatically built in all cities where it is buildable =
@@ -2571,12 +2287,16 @@ Creates a [improvementName] improvement on a specific tile =
Founds a new city = Yeni bir şehir kurar
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
-Can build [improvementFilter/terrainFilter] improvements on tiles = karolara [improvementFilter/terrainFilter] geliştirmeleri inşa edebilir
-May create improvements on water resources = Su kaynakları üzerinde geliştirmeler yapabilir
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
# Requires translation!
May found a religion =
# Requires translation!
May enhance a religion =
+Can build [improvementFilter/terrainFilter] improvements on tiles = karolara [improvementFilter/terrainFilter] geliştirmeleri inşa edebilir
+May create improvements on water resources = Su kaynakları üzerinde geliştirmeler yapabilir
# Requires translation!
Can be added to [comment] in the Capital =
# Requires translation!
@@ -2586,8 +2306,6 @@ Removes other religions when spreading religion =
May Paradrop up to [amount] tiles from inside friendly territory = Arkadaş canlısı topraklardan [amount] karoya kadar paraşütle atlayabilir
# Requires translation!
Can perform Air Sweep =
- # Requires translation!
-Can [action] [amount] times =
Can speed up construction of a building = Yapının inşasını hızlandırabilir
# Requires translation!
Can speed up the construction of a wonder =
@@ -2716,7 +2434,7 @@ Religious Unit =
Spaceship part = Uzay gemisi parçası
# Requires translation!
Takes your religion over the one in their birth city =
-Great Person - [comment] = Büyük Kişi - [comment]
+Great Person - [comment] = Harika Kişi - [comment]
# Requires translation!
Is part of Great Person group [comment] =
# Requires translation!
@@ -2910,7 +2628,7 @@ Free Social Policy = Ücretsiz Sosyal Politika
Empire enters golden age = İmparatorluk altın çağa girer
# Requires translation!
Empire enters a [amount]-turn Golden Age =
-Free Great Person = Ücretsiz Büyük Kişi
+Free Great Person = Ücretsiz Harika Kişi
[amount] population [cityFilter] = [cityFilter] [amount] nüfus
# Requires translation!
[amount] population in a random city =
@@ -3033,7 +2751,7 @@ Water = Su
Air = Hava
non-air = hava olmayan
Nuclear Weapon = Nükleer Silah
-Great Person = Büyük Kişi
+Great Person = Harika Kişi
Barbarian = Barbar
relevant = ilgili
City = Şehir
@@ -3144,6 +2862,8 @@ FounderBelief =
# Requires translation!
FollowerBelief =
Building = Yapı
+ # Requires translation!
+UnitAction =
Unit = Birim
# Requires translation!
UnitType =
@@ -3579,15 +3299,13 @@ Medical Lab = Tıbbi Laboratuvar
Stadium = Stadyum
-Sydney Opera House = Sydney Opera Binası
- # Requires translation!
-'Those who lose dreaming are lost.' - Australian Aboriginal saying =
+Sydney Opera House = Sidney Opera Binası
+'Those who lose dreaming are lost.' - Australian Aboriginal saying = 'Hayal kurmayı kaybedenler kaybolurlar.' - Avustralya Aborjinlerinin deyişi
Manhattan Project = Manhattan Projesi
Pentagon = Pentagon
- # Requires translation!
-'In preparing for battle I have always found that plans are useless, but planning is indispensable.' - Dwight D. Eisenhower =
+'In preparing for battle I have always found that plans are useless, but planning is indispensable.' - Dwight D. Eisenhower = 'Savaşa hazırlanırken hep planların faydasız olduğunu, ama planlamanın da vazgeçilmez olduğunu görmüşümdür.' - Dwight D. Eisenhower
Solar Plant = Güneş Paneli
@@ -3598,8 +3316,7 @@ Apollo Program = Apollo Programı
Spaceship Factory = Uzay Gemisi Fabrikası
United Nations = Birleşmiş Milletler
- # Requires translation!
-'More than ever before in human history, we share a common destiny. We can master it only if we face it together. And that is why we have the United Nations.' - Kofi Annan =
+'More than ever before in human history, we share a common destiny. We can master it only if we face it together. And that is why we have the United Nations.' - Kofi Annan = 'Şu anda, insanlık tarihinde hiç olmadığı kadar tek bir kaderi paylaşıyoruz. Sadece birleşirsek bunun üstesinden gelebiliriz. İşte Birleşmiş Milletler bu yüzden var.' - Kofi Annan
Utopia Project = Ütopya Projesi
@@ -3642,10 +3359,10 @@ Medieval era = Orta Çağ
Pikeman = Kargılı
Renaissance era = Rönesans dönemi
-Musketman = Arkebüslü
+Musketman = Arkebüzlü
Industrial era = Endüstriyel dönem
-Rifleman = Tüfekli asker
+Rifleman = Tüfekli Asker
Modern era = Modern çağ
Infantry = Piyade
@@ -3712,10 +3429,10 @@ Tayma = Tayma
Alexander = İskender
You are in my way, you must be destroyed. = Yolumdasın, yok edilmelisin.
-As a matter of fact I too grow weary of peace. = Bilgine, ben de savaştan yoruluyorum.
-You have somehow become my undoing! What kind of beast are you? = Bir şekilde beni silen olun! Nasıl bir canavarsın?
+As a matter of fact I too grow weary of peace. = Belirtmek isterim ki ben de barıştan yoruluyorum.
+You have somehow become my undoing! What kind of beast are you? = Bir şekilde beni silen sen oldun! Ne tür bir canavarsın?
Hello stranger! I am Alexandros, son of kings and grandson of the gods! = Merhabalar yabancı, ben İskender, kralların çocuğu ve tanrıların torunu!
-My friend, does this seem reasonable to you? = Arkadaşım, bu sana mantıklı geliyor mu?
+My friend, does this seem reasonable to you? = Arkadaşım, bu sence de mantıklı mı?
Greetings! = Selamlar!
What? = Ne?
Hellenic League = Helen Birliği
@@ -4375,13 +4092,10 @@ Yalova = Yalova
The Ottomans = Osmanlılar
Sejong = Sejong
- # Requires translation!
-Jip-hyun-jun (Hall of Worthies) will no longer tolerate your irksome behavior. We will liberate the citizens under your oppression even with force, and enlighten them! =
- # Requires translation!
-Foolish, miserable wretch! You will be crushed by this country's magnificent scientific power! =
+Jip-hyun-jun (Hall of Worthies) will no longer tolerate your irksome behavior. We will liberate the citizens under your oppression even with force, and enlighten them! = Jip-Hyun-Jun (değerliler salonu) artık senin bıktırıcı davranışlarına katlanmayacak. Himayen altındaki vatandaşlarını güç kuvvetle de olsa özgür edip onları aydınlatacağız!
+Foolish, miserable wretch! You will be crushed by this country's magnificent scientific power! = Ahmak, sefil zavallı! Bu ülkenin muhteşem teknolojik gücü altında ezileceksin!
Now the question is who will protect my people. A dark age has come. = Şimdi soru halkımı kimin koruyacağıdır. Kara bir çağ geldi.
- # Requires translation!
-Welcome to the palace of Choson, stranger. I am the learned King Sejong, who looks after his great people. =
+Welcome to the palace of Choson, stranger. I am the learned King Sejong, who looks after his great people. = Joseon Sarayına hoş geldin yabancı. Ben yüce halkına iyi bakan, bilgili Kral Sejong.
We have many things to discuss and have much to benefit from each other. = Tartışacağımız ve birbirimizden yararlanacağımız çok şey var.
Oh, it's you = Ah, sensin
Scholars of the Jade Hall = Yeşil Koridor'un Bilginleri
@@ -4457,16 +4171,11 @@ Hahta = Hahta
Iroquois = İrokua
Darius I = I. Darius
- # Requires translation!
-Your continue existence is an embarrassment to all leaders everywhere! You must be destroyed! =
- # Requires translation!
-Curse you! You are beneath me, son of a donkey driver! I will crush you! =
- # Requires translation!
-You mongrel! Cursed be you! The world will long lament your heinous crime! =
- # Requires translation!
-Peace be on you! I am Darius, the great and outstanding king of kings of great Persia... but I suppose you knew that. =
- # Requires translation!
-In my endless magnanimity, I am making you this offer. You agree, of course? =
+Your continue existence is an embarrassment to all leaders everywhere! You must be destroyed! = Senin varoluşun dünya üstündeki bütün liderler için bir utanç kaynağı! Yok edilmen lazım!
+Curse you! You are beneath me, son of a donkey driver! I will crush you! = Lanet olsun sana! Sen benden aşağıdasın, eşekçinin oğlu! Seni ezeceğim!
+You mongrel! Cursed be you! The world will long lament your heinous crime! = Seni melez! Sana lanetler olsun! Dünya uzun süreler iğrenç suçlarına ağıtlar yakacak!
+Peace be on you! I am Darius, the great and outstanding king of kings of great Persia... but I suppose you knew that. = Huzurlar sana! Ben büyük Pers imparatorluğunun büyük ve seçkin krallar kralı Darius. Ama bunu zaten biliyordun değil mi.
+In my endless magnanimity, I am making you this offer. You agree, of course? = Sonsuz cömertliğimle, sana bu teklifi yapıyorum. Tabiki de kabul ediyorsun öyle değil mi?
Good day to you! = İyi günler!
Ahh... you... = Ah... sen...
Achaemenid Legacy = Ahemeniş Mirası
@@ -4676,13 +4385,10 @@ Tenerife = Tenerife
Spain = İspanya
Askia = Askia
- # Requires translation!
-You are an abomination to heaven and earth, the chief of ignorant savages! You must be destroyed! =
+You are an abomination to heaven and earth, the chief of ignorant savages! You must be destroyed! = Dünyaya ve cennete karşı bir iğrençliksin, cahil vahşilerin şefi! Yok edilmelisin!
Fool! You have doomed your people to fire and destruction! = Aptal! Halkını Ateş ve Yokoluşa sürükledin!
- # Requires translation!
-We have been consumed by the fires of hatred and rage. Enjoy your victory in this world - you shall pay a heavy price in the next! =
- # Requires translation!
-I am Askia of the Songhai. We are a fair people - but those who cross us will find only destruction. You would do well to avoid repeating the mistakes others have made in the past. =
+We have been consumed by the fires of hatred and rage. Enjoy your victory in this world - you shall pay a heavy price in the next! = Nefret ve öfkemizin ateşleri bizi yaktı. Bu dünyadaki zaferinin tadını çıkar - öteki dünyada ağır bedeller ödeyeceksin!
+I am Askia of the Songhai. We are a fair people - but those who cross us will find only destruction. You would do well to avoid repeating the mistakes others have made in the past. = Ben Songhai'li Askia'yım. Biz adil insanlarız - ama bize karşı duranlar sadece yok oluş bulur. Geçmiştekilerin yaptıkları hataları tekrarlamazsan iyi edersin.
Can I interest you in this deal? = Bu takas ile ilgilenir miydiniz?
River Warlord = Irmak Savaş Lordu
Gao = Gao
@@ -4719,14 +4425,10 @@ Songhai = Songhay
Genghis Khan = Cengiz Han
You stand in the way of my armies. Let us solve this like warriors! = Ordularımın yolunu kapatıyorsun. Gel de bu sorunu savaşçılar gibi çözelim!
- # Requires translation!
-No more words. Today, Mongolia charges toward your defeat. =
- # Requires translation!
-You have hobbled the Mongolian clans. My respect for you nearly matches the loathing. I am waiting for my execution. =
- # Requires translation!
-I am Temuujin, conqueror of cities and countries. Before me lie future Mongolian lands. Behind me is the only cavalry that matters. =
- # Requires translation!
-I am not always this generous, but we hope you take this rare opportunity we give you. =
+No more words. Today, Mongolia charges toward your defeat. = Artık konuşmak yok. Bugün, Moğolistan senin mağlubiyetin için hücum ediyor.
+You have hobbled the Mongolian clans. My respect for you nearly matches the loathing. I am waiting for my execution. = Moğol Klanlarını Yıktın. Sana olan saygım neredeyse nefretimi geçecek. İdam edileceğim günü bekliyorum.
+I am Temuujin, conqueror of cities and countries. Before me lie future Mongolian lands. Behind me is the only cavalry that matters. = Ben şehirlerin ve ülkelerin fâtihi Temuçin. Önümde ileride Moğolistan olacak topraklar, arkamda ise değer verdiğim tek süvariler var.
+I am not always this generous, but we hope you take this rare opportunity we give you. = Her zaman bu kadar cömert değilim, ama umarız ki sana teklif ettiğimiz bu nadir fırsatı kabul edersin.
So what now? = Şimdi ne var?
Mongol Terror = Moğol Dehşeti
Karakorum = Karakurum
@@ -4809,11 +4511,9 @@ Iztapam = Iztapam
Aztecs = Aztekler
Pachacuti = Pachacuti
- # Requires translation!
-Resistance is futile! You cannot hope to stand against the mighty Incan empire. If you will not surrender immediately, then prepare for war! =
-Declare war on me?!? You can't, because I declare war on you first! = Bana savaş mı açıyorsun?!? Açamazsın, çünkü ilk ben sana savaş açıyorum!
- # Requires translation!
-How did you darken the sun? I ruled with diligence and mercy—see that you do so as well. =
+Resistance is futile! You cannot hope to stand against the mighty Incan empire. If you will not surrender immediately, then prepare for war! = Direnmek anlamsız! Güçlü İnka İmparatorluğunun karşısında durman imkansız. Eğer hemen şimdi teslim olmayacaksan, savaşa hazırlan!
+Declare war on me?!? You can't, because I declare war on you first! = Bana savaş mı açıyorsun?!? Açamazsın, çünkü önce ben sana savaş açtım!
+How did you darken the sun? I ruled with diligence and mercy—see that you do so as well. = Güneşi nasıl kararttın? Ben gayretle ve merhametle yönettim, ama görüyorum ki sen de öyle yapmışsın.
How are you? You stand before Pachacuti Inca Yupanqui. = Nasılsın? Pachacuti Inca Yupanqui'nin huzurunda bulunuyorsun.
The Incan people offer this fair trade. = İnka halkı bu adil teklifi sunuyor.
How are you doing? = Nasılsın?
@@ -4857,13 +4557,10 @@ Choquequirao = Choquequirao
Inca = İnka
Harald Bluetooth = Mavi Diş Harald
- # Requires translation!
-If I am to be honest, I tire of those pointless charades. Why don't we settle our disputes on the field of battle, like true men? Perhaps the skalds will sing of your valor... or mine! =
+If I am to be honest, I tire of those pointless charades. Why don't we settle our disputes on the field of battle, like true men? Perhaps the skalds will sing of your valor... or mine! = Dürüst olmak gerekirse, bu anlamsız yalanlardan ben de sıkılıyorum. Neden gerçek erkekler gibi sorunlarımızı savaş ortamında çözmüyoruz? Belki de Skaldlar yiğitliğin üzerine şarkılar yazar... ya da benim yiğitliğim üzerine!
Ahahah! You seem to show some skills of a true Viking! Too bad that I'll probably kill you! = Gerçek bir Viking'in yeteneklerini sergiliyorsun, seni büyük ihtimal öldürecek olmam kötü.
- # Requires translation!
-Loki must have stood by you, for a common man alone could not have defeated me... Oh well! I will join the einherjar in Valhalla and feast, while you toil away here. =
- # Requires translation!
-Harald Bluetooth bids you welcome to his lands, a Viking unlike any the seas and lands have ever known! Hah, are you afraid? =
+Loki must have stood by you, for a common man alone could not have defeated me... Oh well! I will join the einherjar in Valhalla and feast, while you toil away here. = Loki senin tarafını tutmuş olmalı, çünkü sıradan bir adam tek başına beni yenemezdi... Aman neyse! Sen burada didinirken ben gidip Valhalla'da Einherjar'lara katılıp ziyafet çekeceğim.
+Harald Bluetooth bids you welcome to his lands, a Viking unlike any the seas and lands have ever known! Hah, are you afraid? = Mavi Diş Harald, karaların ve denizlerin daha önce hiç görmediği türden bir viking seni kendi diyarında karşılıyor! Hah, korktun mu?
This is a fine deal! Even a drunk beggar would agree! = Bu harika bir anlaşma! Sarhoş bir dilenci bile aynı şeyi düşünür!
Hail to you. = Sizi selamlıyoruz.
Viking Fury = Viking Hırsı
@@ -4952,7 +4649,7 @@ Helsinki = Helsinki
Ah, Gods! Why have you forsaken us? = Ah, Tanrılar! Neden bizi terkettiniz?
Manila = Manila
-Congratulations, conqueror. This tribe serves you now. = Tebrikler fâtih. Bu kabile artık size hizmet edecektir.
+Congratulations, conqueror. This tribe serves you now. = Tebrikler fâtih. Bu kabile artık size hizmet ediyor.
Mogadishu = Mogadişu
I have to do this, for the sake of progress if nothing else. You must be opposed! = Hiç birşey için değilse bile, sırf ilerleme için bunu yapmalıyım. Birisinin sana karşı durması gerekiyor!
@@ -4966,8 +4663,7 @@ The principles for which we have fought will survive longer than any nation you
Sydney = Sidney
I will enjoy hearing your last breath as you witness the destruction of your realm! = Kendi dünyanın yok oluşuna şahit olurken verdiğin son nefesi keyifle izleyeceğim.
- # Requires translation!
-Why do we fight? Because Inanna demands it. Now, witness the power of the Sumerians! =
+Why do we fight? Because Inanna demands it. Now, witness the power of the Sumerians! = Neden mi savaşıyoruz? Çünkü İnanna öyle emrediyor. Şimdi, Sümerlilerin gücüne şahit ol!
What treachery has struck us? No, what evil? = Nasıl bir hainlik bizi vurdu? Hayır, nasıl bir şeytanlık?
Ur = Ur
@@ -4977,7 +4673,7 @@ I regret not defending my country to the last, although it was not of use. = Her
Vancouver = Vancouver
You have revealed your purposes a bit too early, my friend... = Amacını çok erkenden belli ettin dostum...
-A wrong calculation, on my part. = Yaptığım yanlış bir hesaplamaydı bu.
+A wrong calculation, on my part. = Hesapta bir yanlışlık olmuş, benim hatam.
Venice = Venedik
They will write songs of this.... pray that they shall be in your favor. = Bunun hakkında şarkılar yazacaklar... dua et de o şarkılar seni övsünler.
@@ -4992,7 +4688,7 @@ Kathmandu = Katmandu
Perhaps, in another world, we could have been friends... = Kim bilir, başka bir dünyada belki arakadaş olabilirdik...
Singapore = Singapur
-We never fully trusted you from the start. = En başından beri sana tamamen güvenmemiştik.
+We never fully trusted you from the start. = Hiçbir zaman sana tamamen güvenmemiştik zaten.
Tyre = Sur Şehri
May the Heavens forgive you for inflicting this humiliation to our people. = İnsanlarımıza bu utancı yaşattığın için Tanrı seni affetsin.
@@ -5002,10 +4698,8 @@ How could we fall to the likes of you?! = Nasıl sana kandık?!
Almaty = Almatı
Let's have a nice little War, shall we? = Küçük, güzel bir Savaş yapalım, olur mu?
- # Requires translation!
-If you need your nose bloodied, we'll happily serve. =
- # Requires translation!
-The serbian guerilla will never stop haunting you! =
+If you need your nose bloodied, we'll happily serve. = Burnunu kanlar içinde istiyorsan, biz büyük bir zevkle yardımcı oluruz.
+The serbian guerilla will never stop haunting you! = Sırp Gerillası asla peşini bırakmayacak!
Belgrade = Belgrad
War lingers in our hearts. Why carry on with a false peace? = Savaş isteği ikimizin de kalbinde. Neden böyle sahte bir barışı sürdürelim ki?
@@ -5014,28 +4708,22 @@ A lonely wind blows through the highlands today. A dirge for Ireland. Can you he
Dublin = Dublin
You shall stain this land no longer with your vileness! To arms, my countrymen - we ride to war! = Aşağılığın ile bu toprakları kirletmene artık izin vermeyeceğim! Savaşa gidiyoruz vatandaşlarım - Hazırlanın!
- # Requires translation!
-Traitorous man! The Celtic peoples will not stand for such wanton abuse and slander - I shall have your head! =
-Vile ruler, know that you 'won' this war in name only! = Kötü lider, bu savaşı sadece sözde 'kazandığını' bil!
+Traitorous man! The Celtic peoples will not stand for such wanton abuse and slander - I shall have your head! = Hain adam! Keltler bu ahlaksız iftiralara ve suistimale boyun eğmeyecek - Kelleni alacağım!
+Vile ruler, know that you 'won' this war in name only! = Alçak lider, bu savaşı sadece sözde 'kazandığını' bil!
Edinburgh = Edinburg
- # Requires translation!
-Do you really think you can walk over us so easily? I will not let it happen. Not to Kongo - not to my people! =
- # Requires translation!
-We are no strangers to war. You have strayed from the right path, and now we will correct it. =
-You are nothing but a glorified barbarian. Cruel, and ruthless. = Sen büyütülmüş bir barbarın tekisin. Acımasız ve merhametsiz.
+Do you really think you can walk over us so easily? I will not let it happen. Not to Kongo - not to my people! = Gerçekten bizi bu kadar kolay geçebileceğini mi sanıyorsun? Buna izin vermeyeceğim. Kongo'ya yapamazsın - halkıma yapamazsın!
+We are no strangers to war. You have strayed from the right path, and now we will correct it. = Savaşa yabancı değiliz. Doğru yoldan saptın, seni doğru yola geri sokacağız.
+You are nothing but a glorified barbarian. Cruel, and ruthless. = Sen yüceltilmiş bir barbarın tekisin. Acımasız ve merhametsiz.
M'Banza-Kongo = M'Banza-Kongo
What a fine battle! Sidon is willing to serve you! = Nede harika bir savaş! Sayda sana hizmet etmek ister!
Sidon = Sayda
- # Requires translation!
-We don't like your face. To arms! =
- # Requires translation!
-You will see you have just bitten off more than you can chew. =
- # Requires translation!
-This ship may sink, but our spirits will linger. =
-Valletta = Valletta
+We don't like your face. To arms! = Suratını beğenmiyoruz. Savaşa!
+You will see you have just bitten off more than you can chew. = Yutabileceğinden fazlasını ısırdığını göreceksin.
+This ship may sink, but our spirits will linger. = Gemi batabilir, ama ruhlarımız batmaz.
+Valletta = Valetta
#################### Lines from Policies from Civ V - Vanilla ####################
@@ -5077,10 +4765,8 @@ Aesthetics = Estetik
Scholasticism = Skolastizm
Cultural Diplomacy = Kültürel Diplomasi
Educated Elite = Eğitimli Elitler
- # Requires translation!
-Patronage Complete =
- # Requires translation!
-Patronage =
+Patronage Complete = Himaye Tamamlandı
+Patronage = Himaye
Naval Tradition = Deniz Geleneği
Trade Unions = Sendikalar
@@ -5088,8 +4774,7 @@ Merchant Navy = Ticaret Donanması
Mercantilism = Merkantilizm
Protectionism = Korumacılık
Commerce Complete = Ticaret Tamamlandı
- # Requires translation!
-Commerce =
+Commerce = Ticaret
Secularism = Laiklik
Humanism = Hümanizm
@@ -5097,17 +4782,15 @@ Free Thought = Serbest Düşünce
Sovereignty = Egemenlik
Scientific Revolution = Bilim Devrimi
Rationalism Complete = Akılcılık Tamamlandı
- # Requires translation!
-Rationalism =
+Rationalism = Akılcılık
Constitution = Anayasa
Universal Suffrage = Evrensel Oy Hakkı
Civil Society = Sivil Toplum
Free Speech = İfade Özgürlüğü
Democracy = Demokrasi
-Freedom Complete = Özgürlük Tamamlandı
- # Requires translation!
-Freedom =
+Freedom Complete = Hürriyet Tamamlandı
+Freedom = Hürriyet
Populism = Halkçılık
Militarism = Militarizm
@@ -5115,8 +4798,7 @@ Fascism = Faşizm
Police State = Polis Devleti
Total War = Topyekün Savaş
Autocracy Complete = Otokrasi Tamamlandı
- # Requires translation!
-Autocracy =
+Autocracy = Otokrasi
United Front = Birleşik Cephe
Planned Economy = Planlı Ekonomi
@@ -5124,8 +4806,7 @@ Nationalism = Milliyetçilik
Socialism = Sosyalizm
Communism = Komünizm
Order Complete = Düzen tamamlandı
- # Requires translation!
-Order =
+Order = Düzen
#################### Lines from Quests from Civ V - Vanilla ####################
@@ -5136,14 +4817,14 @@ Build a road to connect your capital to our city. = Başkentinizi şehrimize ba
Clear Barbarian Camp = Barbar Kamplarını Yok Et
We feel threatened by a Barbarian Camp near our city. Please take care of it. = Yakınımızdaki bir Barbar Kampından korkuyoruz. Lütfen onlarla ilgilenin.
-Connect Resource = Kaynağı ticaret ağına bağla
-In order to make our civilizations stronger, connect [tileResource] to your trade network. = Uygarlıklarımızı güçlendirmek adına [tileResource]'ı ticaret ağına bağla.
+Connect Resource = Kaynağı Ticaret Ağına Bağla
+In order to make our civilizations stronger, connect [tileResource] to your trade network. = Uygarlıklarımızı güçlendirmek adına [tileResource] kaynağını ticaret ağına bağla.
-Construct Wonder = Eseri inşa et
+Construct Wonder = Eseri İnşa Et
We recommend you to start building [wonder] to show the whole world your civilization strength. = Uygarlığınızın gücünü tüm dünyaya göstermek için [wonder]'ı inşa etmeye başlamanızı tavsiye ediyoruz.
-Acquire Great Person = Büyük Kişi Edin
-Great People can change the course of a Civilization! You will be rewarded for acquiring a new [greatPerson]. = Büyük Şahsiyetler bir uygarlığın geleceğini değiştirebilir! Yeni bir [greatPerson] edinmeniz ödüllendirilmenizle sonuçlanacaktır.
+Acquire Great Person = Harika Kişi Edin
+Great People can change the course of a Civilization! You will be rewarded for acquiring a new [greatPerson]. = Harika Şahsiyetler bir uygarlığın geleceğini değiştirebilir! Yeni bir [greatPerson] edinmeniz ödüllendirilmenizle sonuçlanacaktır.
Conquer City State = Şehir Devleti Fethet
It's time to erase the City-State of [cityState] from the map. You will be greatly rewarded for conquering them! = [cityState] Şehir Devletini haritadan silmenin vakti geldi. Onları fethederseniz ödüllendileceksiniz!
@@ -5155,43 +4836,28 @@ Find Natural Wonder = Doğal Harika Bul
Send your best explorers on a quest to discover Natural Wonders. Nobody knows the location of [naturalWonder] yet. = En iyi gezginlerinizi bir Doğa harikası bulması için gönderiniz. Henüz kimse [naturalWonder]'ın nerede olduğunu bilmiyor.
Give Gold = Altın Ver
- # Requires translation!
-We are suffering great poverty after being robbed by [civName], and unless we receive a sum of Gold, it's only a matter of time before we collapse. =
+We are suffering great poverty after being robbed by [civName], and unless we receive a sum of Gold, it's only a matter of time before we collapse. = [civName] uygarlığı tarafından soyulduk ve sefalet içindeyiz, bir miktar altın elimize geçmezse çöküşümüz an meselesi.
- # Requires translation!
-Pledge to Protect =
- # Requires translation!
-We need your protection to stop the aggressions of [civName]. By signing a Pledge of Protection, you'll confirm the bond that ties us. =
+Pledge to Protect = Koruma Yemini Et
+We need your protection to stop the aggressions of [civName]. By signing a Pledge of Protection, you'll confirm the bond that ties us. = [civName] uygarlığının saldırganlığını durdurmak için senin korumana ihtiyacımız var. Koruma Yeminini imzalayarak bizi bağlayan bağı doğrulamış olacaksın.
- # Requires translation!
-Contest Culture =
- # Requires translation!
-The civilization with the largest Culture growth will gain a reward. =
+Contest Culture = Kültür Yarışması
+The civilization with the largest Culture growth will gain a reward. = En çok Kültür üreten uygarlık bir ödül kazanacak.
- # Requires translation!
-Contest Faith =
- # Requires translation!
-The civilization with the largest Faith growth will gain a reward. =
+Contest Faith = İnanç Yarışması
+The civilization with the largest Faith growth will gain a reward. = En çok İnanç toplayan uygarlık bir ödül kazanacak.
- # Requires translation!
-Contest Technologies =
- # Requires translation!
-The civilization with the largest number of new Technologies researched will gain a reward. =
+Contest Technologies = Teknoloji Yarışması
+The civilization with the largest number of new Technologies researched will gain a reward. = En çok yeni teknoloji keşfeden uygarlık ödül kazanacak.
- # Requires translation!
-Invest =
- # Requires translation!
-Our people are rejoicing thanks to a tourism boom. For a certain amount of time, any Gold donation will yield [50]% extra Influence. =
+Invest = Yatırım Yap
+Our people are rejoicing thanks to a tourism boom. For a certain amount of time, any Gold donation will yield [50]% extra Influence. = Turizm patlamasına bağlı olarak halkımız çok mutlu. Belli bir süre boyunca bütün altın bağışları %[50] daha fazla nüfuz sağlar.
- # Requires translation!
-Bully City State =
- # Requires translation!
-We are tired of the pretensions of [cityState]. If someone were to put them in their place by Demanding Tribute from them, they would be rewarded. =
+Bully City State = Şehir Devletine Zorbalık Yap
+We are tired of the pretensions of [cityState]. If someone were to put them in their place by Demanding Tribute from them, they would be rewarded. = [cityState] Şehir Devletinin gösterişlerinden bıktık. Onlardan haraç isteyerek onlara dersini verecek birileri olursa ödüllendirilecekler.
- # Requires translation!
-Denounce Civilization =
- # Requires translation!
-We have been forced to pay tribute to [civName]! We need you to tell the world of their ill deeds. =
+Denounce Civilization = Uygarlığı Kına
+We have been forced to pay tribute to [civName]! We need you to tell the world of their ill deeds. = [civName] uygarlığına haraç vermeye zorlandık! Onların yaptığı kötülükleri bütün dünyaya duyurmanı istiyoruz.
# Requires translation!
We have heard the tenets of [religionName] and are most curious. Will you send missionaries to teach us about your religion? =
@@ -5208,27 +4874,21 @@ squatters willing to work for you =
# Requires translation!
squatters wishing to settle under your rule =
- # Requires translation!
-An ancient tribe trained us in their ways of combat! =
+An ancient tribe trained us in their ways of combat! = Oranın Yerlileri bize kendi savaşma yöntemlerini öğretti!
your exploring unit receives training = gezgin birliğiniz eğitim alır
- # Requires translation!
-We have found survivors in the ruins! Population added to [cityName]. =
-survivors (adds population to a city) = sağ kalanları bir grup insan bulursunuz (bir şehre nüfus sağlar)
+We have found survivors in the ruins! Population added to [cityName]. = Yıkıntılar içinde hayatta kalanlar bulduk! [cityName] şehrine nüfus eklendi.
+survivors (adds population to a city) = sağ kalan bir grup insan bulursunuz (bir şehre nüfus sağlar)
a stash of gold = altın zulası bulursunuz
discover a lost technology = kayıp bir teknolojiyi bulursunuz
- # Requires translation!
-Our unit finds advanced weaponry hidden in the ruins! =
- # Requires translation!
-advanced weaponry for your explorer =
+Our unit finds advanced weaponry hidden in the ruins! = Birliğimiz Yıkıntılar arasında gizlenmiş gelişmiş silahlar buluyor!
+advanced weaponry for your explorer = Kaşifin için gelişmiş silahlar
- # Requires translation!
-You find evidence of Barbarian activity. Nearby Barbarian camps are revealed! =
- # Requires translation!
-reveal nearby Barbarian camps =
+You find evidence of Barbarian activity. Nearby Barbarian camps are revealed! = Barbarların aktivitelerine ilişkin kanıt buldun. Yakınlardaki barbar kampları belli oldu!
+reveal nearby Barbarian camps = Yakınlardaki barbar kamplarını gösterir
find a crudely-drawn map = kaba taslak bir hatira bulursunuz
@@ -5278,7 +4938,7 @@ Bronze Working = Tunç İşleme
'He made an instrument to know if the moon shine at full or no.' - Samuel Butler = 'Ayın tamamen parlayıp parlamadığını bilmek için bir araç yaptı.' - Samuel Butler
Optics = Optik
-'There is only one good, knowledge, and one evil, ignorance.' - Socrates = 'Yalnızca bir iyilik vardır, bilgi, ve yalnızca bir kötülük vardır, cahillik.' - Sokrates
+'There is only one good, knowledge, and one evil, ignorance.' - Socrates = 'Yalnızca bir iyi vardır, bilgi, ve yalnızca bir kötü vardır, cahillik.' - Sokrates
Philosophy = Felsefe
'A Horse! A Horse! My kingdom for a horse!' - Shakespeare (Richard III) = 'Bir at! Bir at! Bir at için krallığımı veririm! ' - Shakespeare (III. Richard)
Horseback Riding = At Binme
@@ -5345,7 +5005,7 @@ Fertilizer = Gübre
Rifling = Tüfek
'If the brain were so simple we could understand it, we would be so simple we couldn't.' - Lyall Watson = 'Beyin anlayabileceğimiz kadar basit olsaydı, onu anlayamayacak kadar basit olurduk.' - Lyall Watson
-Biology = Yaşam Bilimi
+Biology = Biyoloji
'The nations of the West hope that by means of steam communication all the world will become as one family.' - Townsend Harris = 'Batı ulusları, buharlı iletişim yoluyla bütün dünyanın tek bir aile olacağını umuyorlar.' - Townsend Harris
Steam Power = Buhar Gücü
'As soon as men decide that all means are permitted to fight an evil, then their good becomes indistinguishable from the evil that they set out to destroy.' - Christopher Dawson = 'İnsanlar bir kötülükle savaşmak için her yolun mübah olduğuna karar verdikleri anda, kendi iyilikleri, yok etmek için yola çıktıkları kötülükten ayırt edilemez hale gelir.' - Christopher Dawson
@@ -5364,7 +5024,7 @@ Refrigeration = Soğutma
Telegraph = Telgraf
'The whole country was tied together by radio. We all experienced the same heroes and comedians and singers. They were giants.' - Woody Allen = 'Bütün ülke radyoyla birbirine bağlıydı. Hepimiz aynı kahramanları, komedyenleri ve şarkıcıları deneyimledik. Onlar devdi. ' - Woody Allen
Radio = Radyo
-'Aeronautics was neither an industry nor a science. It was a miracle.' - Igor Sikorsky = 'Havacılık ne bir endüstri ne de bir bilimdi. O bir mucizeydi.' - İgor Sikorskı
+'Aeronautics was neither an industry nor a science. It was a miracle.' - Igor Sikorsky = 'Havacılık ne bir endüstri ne de bir bilimdi. O bir mucizeydi.' - İgor Sikorski
Flight = Uçuş
'Any man who can drive safely while kissing a pretty girl is simply not giving the kiss the attention it deserves.' - Albert Einstein = 'Güzel bir kızı öperken güvenli bir şekilde araba kullanabilen bir erkek, öpücüğe hak ettiği ilgiyi göstermiyor demektir.' - Albert Einstein
Combustion = Yanmalı Motor
@@ -5395,18 +5055,18 @@ Nuclear Fission = Çekirdek Bölünmesi
'The new electronic interdependence recreates the world in the image of a global village.' - Marshall McLuhan = 'Yeni elektronik birbirine bağımlılık, dünyayı küresel bir köy şeklinde yeniden yaratıyor.' - Marshall McLuhan
Globalization = Küreselleşme
-'1. A robot may not injure a human being or, through inaction, allow a human being to come to harm. 2. A robot must obey any orders given to it by human beings, except when such orders would conflict with the First Law. 3. A robot must protect its own existence as long as such protection does not conflict with the First or Second Law.' - Isaac Asimov = '1. Bir robot bir insana zarar vermeyebilir veya eylemsizlik yoluyla bir insanın zarar görmesine izin vermeyebilir. 2. Bir robot, bu emirlerin Birinci Kanunla çeliştiği durumlar dışında, insanlar tarafından verilen emirlere uymak zorundadır. 3. Bir robot Birinci veya İkinci Kanunla çelişmediği sürece bir robot kendi varlığını korumalıdır. ' - Isaac asimov
-Robotics = Robotlar
+'1. A robot may not injure a human being or, through inaction, allow a human being to come to harm. 2. A robot must obey any orders given to it by human beings, except when such orders would conflict with the First Law. 3. A robot must protect its own existence as long as such protection does not conflict with the First or Second Law.' - Isaac Asimov = '1. Bir robot bir insana zarar veremez veya eylemsizlik yoluyla bir insanın zarar görmesine izin veremez. 2. Bir robot, bu emirlerin Birinci Kanunla çeliştiği durumlar dışında, insanlar tarafından verilen emirlere uymak zorundadır. 3. Bir robot Birinci veya İkinci Kanunla çelişmediği sürece kendi varlığını korumalıdır.' - Isaac asimov
+Robotics = Robotik
'Now, somehow, in some new way, the sky seemed almost alien.' - Lyndon B. Johnson = 'Şimdi, bir şekilde, yeni bir şekilde, gökyüzü neredeyse yabancı görünüyordu.' - Lyndon B. Johnson
Satellites = Uydular
-'Be extremely subtle, even to the point of formlessness, be extremely mysterious, even to the point of soundlessness. Thereby you can be the director of the opponent's fate.' - Sun Tzu = 'Şekilsizliğe bile varacak kadar son derece incelikli olun, sessizliğe bile varacak kadar son derece gizemli olun. Böylece rakibin kaderinin yöneticisi olabilirsiniz.' - Sun Tzu
+'Be extremely subtle, even to the point of formlessness, be extremely mysterious, even to the point of soundlessness. Thereby you can be the director of the opponent's fate.' - Sun Tzu = 'Şekilsizliğe varacak kadar incelikli olun, sessizliğe varacak kadar gizemli olun. Böylece rakibin kaderinin yöneticisi olabilirsiniz.' - Sun Tzu
Stealth = Gizlilik
'Our scientific power has outrun our spiritual power, we have guided missiles and misguided men.' – Martin Luther King Jr. = 'Bilimsel gücümüz manevi gücümüzün önüne geçti, yönlendirilmiş füzelerimiz ve yanlış yönlendirilmiş insanlarımız var.' - Martin Luther King Jr.
Advanced Ballistics = Gelişmiş Balistik
-'Every particle of matter is attracted by or gravitates to every other particle of matter with a force inversely proportional to the squares of their distances.' - Isaac Newton = “Her madde parçacığı, mesafelerinin kareleri ile ters orantılı bir kuvvetle maddenin her parçacığı tarafından çekilir veya ona çekilir.”-Isaac Newton
+'Every particle of matter is attracted by or gravitates to every other particle of matter with a force inversely proportional to the squares of their distances.' - Isaac Newton = “Her madde parçacığı, mesafelerinin kareleri ile ters orantılı bir kuvvetle maddenin her parçacığı tarafından çekilir veya ona çekilir.” - Isaac Newton
Particle Physics = Parçacık Fiziği
-'The release of atomic energy has not created a new problem. It has readily made more urgent the necessity of solving an existing one.' - Albert Einstein = 'Atom Enerjisinin yayılması yeni sorunlar yaratmadı. Atom Enerjisi halihazırda bulunan sorunları çözmeyi daha acil hale getirdi.'
+'The release of atomic energy has not created a new problem. It has readily made more urgent the necessity of solving an existing one.' - Albert Einstein = 'Atom Enerjisinin yayılması yeni sorunlar yaratmadı. Atom Enerjisi halihazırda bulunan sorunları çözmeyi daha acil hale getirdi.' - Albert Einstein
Nuclear Fusion = Çekirdek Kaynaşması
'The impact of nanotechnology is expected to exceed the impact that the electronics revolution has had on our lives.' - Richard Schwartz = "Nanoteknolojinin etkisinin, elektronik devriminin hayatlarımız üzerindeki etkisini aşması bekleniyor." - Richard Schwartz
@@ -5445,10 +5105,9 @@ A Camp can be built here without cutting it down = Buraya buradaki ağaçların
Jungle = Cengel
Marsh = Bataklık
- # Requires translation!
-Only Polders can be built here =
+Only Polders can be built here = Buraya sadece polder inşa edilebilir
-Fallout = Nükleer atık
+Fallout = Radyoaktif döküntü
Oasis = Vaha
@@ -5493,8 +5152,7 @@ Camp = Kamp
Oil well = Petrol kuyusu
- # Requires translation!
-Offshore Platform =
+Offshore Platform = Petrol Platformu
Pasture = Otlak
@@ -5515,8 +5173,7 @@ Railroad = Demiryolu
Reduces movement cost to ⅒ if the other tile also has a Railroad = Diğer karoda da bir Demiryolu varsa hareket maliyetini ⅒'a düşürür
Remove Forest = Ormanı ortadan kaldır
- # Requires translation!
-Provides a one-time Production bonus depending on distance to the closest city once finished =
+Provides a one-time Production bonus depending on distance to the closest city once finished = Bitirildiğinde sadece bir kereye mahsus en yakın şehre olan uzaklığına bağlı değişen miktarda bir üretim bonusu verir
Remove Jungle = Cengeli ortadan kaldır
@@ -5530,8 +5187,7 @@ Remove Railroad = Demiryolunu ortadan kaldır
Cancel improvement order = Geliştirme emrini iptal et
- # Requires translation!
-Repairs a pillaged Improvement or Route =
+Repairs a pillaged Improvement or Route = Yağmalanmış bir geliştirme veya yolu tamir eder
Academy = Akademi
@@ -5559,8 +5215,7 @@ Marks the center of a city = Şehir merkezini işaret eder
Appearance changes with the technological era of the owning civilization = Dış görünüşü uygarlığın içinde bulunduğu çağ ile değişir
Barbarian encampment = Barbar Kampı
- # Requires translation!
-Home to uncivilized barbarians, will spawn a hostile unit from time to time =
+Home to uncivilized barbarians, will spawn a hostile unit from time to time = Medeniyetsiz barbarlara ev sahipliği yapar, zaman zaman düşman birimleri oluşturur.
#################### Lines from TileResources from Civ V - Vanilla ####################
@@ -5638,17 +5293,17 @@ Ranged Water = Menzilli Deniz
Submarine = Denizaltı
Heal Instantly = Anında iyileştir
-Accuracy I = İsabet 1
+Accuracy I = İsabet I
-Accuracy II = İsabet 2
+Accuracy II = İsabet II
-Accuracy III = İsabet 3
+Accuracy III = İsabet III
-Barrage I = Engel 1
+Barrage I = Engel I
-Barrage II = Engel 2
+Barrage II = Engel II
-Barrage III = Engel 3
+Barrage III = Engel III
Volley = Yaylım Ateşi
@@ -5656,111 +5311,109 @@ Extended Range = Genişletilmiş Menzil
Indirect Fire = Dolaylı Ateş
-Shock I = Şok 1
+Shock I = Şok I
-Shock II = Şok 2
+Shock II = Şok II
-Shock III = Şok 3
+Shock III = Şok III
-Drill I = Delme 1
+Drill I = Delme I
-Drill II = Delme 2
+Drill II = Delme II
-Drill III = Delme 3
+Drill III = Delme III
-Charge = Hücüm
+Charge = Hücum
Besiege = Kuşatma
-Formation I = Diziliş 1
+Formation I = Diziliş I
-Formation II = Diziliş 2
+Formation II = Diziliş II
-Blitz = Hava Saldırısı
+Blitz = Yıldırım Saldırı
Woodsman = Oduncu
Amphibious = Yüzergezer
-Medic = Doktor 1
+Medic = Doktor I
-Medic II = Doktor 2
+Medic II = Doktor II
-Scouting I = Keşif 1
+Scouting I = Keşif I
-Scouting II = Keşif 2
+Scouting II = Keşif II
-Scouting III = Keşif 3
+Scouting III = Keşif III
-Survivalism I = Hayatta Kalma 1
+Survivalism I = Hayatta Kalma I
-Survivalism II = Hayatta Kalma 2
+Survivalism II = Hayatta Kalma II
-Survivalism III = Hayatta Kalma 3
+Survivalism III = Hayatta Kalma III
-Boarding Party I = Korsan Partisi 1
+Boarding Party I = Korsan Tayfa I
-Boarding Party II = Korsan Partisi 2
+Boarding Party II = Korsan Tayfa II
-Boarding Party III = Korsan Partisi 3
+Boarding Party III = Korsan Tayfa III
-Coastal Raider I = Kıyı Baskını 1
+Coastal Raider I = Kıyı Baskını I
-Coastal Raider II = Kıyı Baskını 2
+Coastal Raider II = Kıyı Baskını II
-Coastal Raider III = Kıyı Baskını 3
+Coastal Raider III = Kıyı Baskını III
- # Requires translation!
-Landing Party =
+Landing Party = Çıkarma Tayfası
-Targeting I = Hedefleme 1
+Targeting I = Hedefleme I
-Targeting II = Hedefleme 2
+Targeting II = Hedefleme II
-Targeting III = Hedefleme 3
+Targeting III = Hedefleme III
-Wolfpack I = Kurt Sürüsü 1
+Wolfpack I = Grup Taktiği I
-Wolfpack II = Kurt Sürüsü 2
+Wolfpack II = Grup Taktiği II
-Wolfpack III = Kurt Sürüsü 3
+Wolfpack III = Grup Taktiği III
Aircraft Carrier = Uçak Gemisi
-Armor Plating I = Zırh Kaplama 1
+Armor Plating I = Zırh Kaplama I
-Armor Plating II = Zırh Kaplama 2
+Armor Plating II = Zırh Kaplama II
-Armor Plating III = Zırh Kaplama 3
+Armor Plating III = Zırh Kaplama III
-Flight Deck I = Uçuş Güvertesi 1
+Flight Deck I = Uçuş Güvertesi I
-Flight Deck II = Uçuş Güvertesi 2
+Flight Deck II = Uçuş Güvertesi II
-Flight Deck III = Uçuş Güvertesi 3
+Flight Deck III = Uçuş Güvertesi III
- # Requires translation!
-Supply =
+Supply = Tedarik
Bomber = Bombardıman Uçağı
-Siege I = Kuşatma 1
+Siege I = Kuşatma I
-Siege II = Kuşatma 2
+Siege II = Kuşatma II
-Siege III = Kuşatma 3
+Siege III = Kuşatma III
Evasion = Kaçınma
Fighter = Savaş Uçağı
-Interception I = Engelleme 1
+Interception I = Önlleme I
-Interception II = Engelleme 2
+Interception II = Önlleme II
-Interception III = Engelleme 3
+Interception III = Önlleme III
-Air Targeting I = Hava Hedefi 1
+Air Targeting I = Hava Hedefleme I
-Air Targeting II = Hava Hedefi 2
+Air Targeting II = Hava Hedefleme II
Sortie = Yarma
@@ -5769,23 +5422,19 @@ Operational Range = Çalışma Menzili
Helicopter = Helikopter
Air Repair = Hava onarımı
- # Requires translation!
-Mobility I =
+Mobility I = Hareketlilik I
- # Requires translation!
-Mobility II =
+Mobility II = Hareketlilik II
- # Requires translation!
-Anti-Armor I =
+Anti-Armor I = Zırh-Delici I
- # Requires translation!
-Anti-Armor II =
+Anti-Armor II = Zırh-Delici II
-Cover I = Sığınak 1
+Cover I = Siper I
-Cover II = Sığınak 2
+Cover II = Siper II
-March = Yürüyüş
+March = Uygun Adım
Mobility = Hareketlilik
@@ -5793,21 +5442,21 @@ Sentry = Nöbetçi
Logistics = Lojistik
-Ambush I = Pusu 1
+Ambush I = Pusu I
-Ambush II = Pusu 2
+Ambush II = Pusu II
-Bombardment I = Bombardıman 1
+Bombardment I = Bombardıman I
-Bombardment II = Bombardıman 2
+Bombardment II = Bombardıman II
-Bombardment III = Bombardıman 3
+Bombardment III = Bombardıman III
Morale = Moral
-Great Generals I = Harika Generaller 1
+Great Generals I = Harika Generaller I
-Great Generals II = Harika Generaller 2
+Great Generals II = Harika Generaller II
Quick Study = Hızlı Çalışma
@@ -5817,17 +5466,13 @@ Rejuvenation = Gençleştirme
Slinger Withdraw = Sapancı geri çekilmesi
- # Requires translation!
-Ignore terrain cost =
+Ignore terrain cost = Arazi zorluğunu görmezden gel
- # Requires translation!
-Pictish Courage =
+Pictish Courage = Pikt Cesareti
- # Requires translation!
-Home Sweet Home =
+Home Sweet Home = Evim Güzel Evim
- # Requires translation!
-[unit] ability =
+[unit] ability = [unit] özelliği
#################### Lines from UnitTypes from Civ V - Vanilla ####################
@@ -5848,13 +5493,13 @@ Armor = Zırh
WaterCivilian = DenizSivil
-WaterMelee = Deniz YakınDövüş
+WaterMelee = DenizYakınDövüş
WaterRanged = DenizMenzilli
-WaterSubmarine = Su Denizaltısı
+WaterSubmarine = Denizaltı
-WaterAircraftCarrier = Uçak gemisi
+WaterAircraftCarrier = Uçakgemisi
AtomicBomber = AtomBombası
@@ -5876,8 +5521,7 @@ Bowman = Babil okçusu
Slinger = Sapancı
- # Requires translation!
-Skirmisher =
+Skirmisher = Çatışmacı
Work Boats = İş Tekneleri
@@ -5904,7 +5548,7 @@ Companion Cavalry = Destek Süvarisi
Catapult = Mancınık
-Ballista = Ballista
+Ballista = Balista
Swordsman = Kılıçlı
@@ -5931,13 +5575,13 @@ Crossbowman = Tataryaylı
Chu-Ko-Nu = Chu-Ko-Nu
-Longbowman = Uzman Okçu
+Longbowman = Uzun Yaylı Okçu
Trebuchet = Trebüşe
Hwach'a = Hwach'a
-Longswordsman = Uzman Kılıçlı
+Longswordsman = Uzun Kılıçlı Asker
Samurai = Samuray
@@ -5973,7 +5617,7 @@ Cavalry = Süvari
Cossack = Kazak
-Ironclad = Zırhlı
+Ironclad = Zırhlı Savaş Gemisi
Artillery = Top
@@ -5987,7 +5631,7 @@ Battleship = Savaş Gemisi
Anti-Aircraft Gun = Uçak Savar
-Destroyer = Destroyer
+Destroyer = Muhrip
Zero = Zero
@@ -6004,9 +5648,9 @@ Anti-Tank Gun = Tanksavar Silahı
Atomic Bomb = Atom Bombası
-Rocket Artillery = Roket Topçuları
+Rocket Artillery = Çoklu Roketatar
-Mobile SAM = Mobil SAM
+Mobile SAM = Uçaksavar Füze Aracı
Guided Missile = Güdümlü Füze
@@ -6014,7 +5658,7 @@ Nuclear Missile = Nükleer Füze
Helicopter Gunship = Savaş Helikopteri
-Nuclear Submarine = Nukleer Denizaltı
+Nuclear Submarine = Nükleer Denizaltı
Mechanized Infantry = Mekanize Piyade
@@ -6022,12 +5666,11 @@ Missile Cruiser = Füze Kruvazörü
Modern Armor = Modern Zırhlı
-Jet Fighter = Jet Savaşcısı
+Jet Fighter = Savaş Jeti
Giant Death Robot = Dev Ölüm Robotu
- # Requires translation!
-Stealth Bomber =
+Stealth Bomber = Hayalet Bombardıman Uçağı
Great Artist = Harika Sanatçı
@@ -6037,75 +5680,53 @@ Great Merchant = Harika Tüccar
Great Engineer = Harika Mühendis
-Great Prophet = Harika Peygamber
-
Great General = Harika General
Khan = Kağan
-Missionary = Misyoner
-
- # Requires translation!
-Inquisitor =
-
SS Booster = UG Roketi
SS Cockpit = UG Kokpiti
SS Engine = UG Motoru
-SS Stasis Chamber = UG Dolaşım Odası
+SS Stasis Chamber = UG Uzay Uykusu Odası
#################### Lines from VictoryTypes from Civ V - Vanilla ####################
- # Requires translation!
-Complete all the spaceship parts\nto win! =
- # Requires translation!
-spaceship parts =
-You have achieved victory through mastery of Science! You have conquered the mysteries of nature and led your people on a voyage to a brave new world! Your triumph will be remembered as long as the stars burn in the night sky! = Bilimin ustalığıyla zafere ulaştınız! Doğanın gizemlerini fethettiniz ve halkınızı cesur yeni bir dünyaya yolculuk ettiniz! Yıldızlar gece gökyüzünde yantığı sürece zaferin hatırlanacak!
+Complete all the spaceship parts\nto win! = Kazanmak için bütün uzay gemisi\nparçalarını tamamla!
+spaceship parts = Uzay gemisi parçaları
+You have achieved victory through mastery of Science! You have conquered the mysteries of nature and led your people on a voyage to a brave new world! Your triumph will be remembered as long as the stars burn in the night sky! = Bilimin ustalığıyla zafere ulaştınız! Doğanın gizemlerini fethettiniz ve halkınızla yepyeni dünyalara yolculuk yaptınız! Yıldızlar gece gökyüzünde yandığı sürece zaferin hatırlanacak!
- # Requires translation!
-Complete 5 policy branches and\nbuild the Utopia Project to win! =
+Complete 5 policy branches and\nbuild the Utopia Project to win! = Kazanmak için 5 politika dalını tamamla\nve ütopya projesini inşa et!
You have achieved victory through the awesome power of your Culture. Your civilization's greatness - the magnificence of its monuments and the power of its artists - have astounded the world! Poets will honor you as long as beauty brings gladness to a weary heart. = Kültürünüzün müthiş gücü ile zafer kazandınız. Medeniyetinizin büyüklüğü - anıtlarının ihtişamı ve sanatçılarının gücü - dünyayı hayrete düşürdü! Güzellik yorgun bir kalbe mutluluk getirdiği sürece şairler sizi onurlandıracaktır.
- # Requires translation!
-Destroy all enemies\nto win! =
-The world has been convulsed by war. Many great and powerful civilizations have fallen, but you have survived - and emerged victorious! The world will long remember your glorious triumph! = Dünya savaşla sarsıldı. Birçok büyük ve güçlü medeniyet düştü, ama hayatta kaldın - ve galip geldin! Dünya uzun zamandır görkemli zaferini hatırlayacak!
+Destroy all enemies\nto win! = Kazanmak için bütün\ndüşmanlarını yok et!
+The world has been convulsed by war. Many great and powerful civilizations have fallen, but you have survived - and emerged victorious! The world will long remember your glorious triumph! = Dünya savaşla sarsıldı. Birçok büyük ve güçlü medeniyet düştü, ama hayatta kaldın - ve galip geldin! Dünya uzun zamanlar boyunca görkemli zaferini hatırlayacak!
- # Requires translation!
-Build the UN and be voted\nworld leader to win! =
- # Requires translation!
-Anyone should build [buildingFilter] =
- # Requires translation!
-Win diplomatic vote =
- # Requires translation!
-You have triumphed over your foes through the art of diplomacy! Your cunning and wisdom have earned you great friends - and divided and sown confusion among your enemies! Forever will you be remembered as the leader who brought peace to this weary world! =
+Build the UN and be voted\nworld leader to win! = Kazanmak için Birleşmiş Milletleri\nkur ve dünya lideri seçil!
+Anyone should build [buildingFilter] = Herhangi birisi [buildingFilter] inşa etmeli
+Win diplomatic vote = Diplomatik oylamayı kazan
+You have triumphed over your foes through the art of diplomacy! Your cunning and wisdom have earned you great friends - and divided and sown confusion among your enemies! Forever will you be remembered as the leader who brought peace to this weary world! = Diplomasi sanatıyla düşmanlarını alt ettin! Kurnazlığın ve bilgeliğin sana değerli arkadaşlar kazandırdı - ve düşmanlarını bölüp aralarına kurt düşürdü! Sonsuza kadar bu yıpranmış dünyaya barış getiren lider olarak hatırlanacaksın!
- # Requires translation!
-Do things to win! =
- # Requires translation!
-Have highest score after max turns =
+Do things to win! = Kazanmak için bir şeyler yap!
+Have highest score after max turns = Tur limitine ulaşıldığında en yüksek puana sahip ol
#################### Lines from Beliefs from Civ V - Gods & Kings ####################
Ancestor Worship = Atalara Tapınma
- # Requires translation!
-Dance of the Aurora =
+Dance of the Aurora = Aurora Dansı
- # Requires translation!
-Desert Folklore =
+Desert Folklore = Çöl Folkloru
- # Requires translation!
-Faith Healers =
+Faith Healers = İmanlı Şifacılar
- # Requires translation!
-Fertility Rites =
+Fertility Rites = Doğurganlık Ayinleri
- # Requires translation!
-God of Craftsman =
+God of Craftsman = Zanaatkarlık Tanrısı
God of the Open Sky = Gök Tanrısı
@@ -6123,118 +5744,92 @@ Goddess of the Hunt = Av Tanrıçası
Messenger of the Gods = Tanrıların Elçisi
- # Requires translation!
-Monument to the Gods =
+Monument to the Gods = Tanrılara Anıt
One with Nature = Doğayla Bir
Oral Tradition = Sözlü Gelenek
- # Requires translation!
-Religious Idols =
+Religious Idols = Dini Putlar
Religious Settlements = Dinî Yerleşmeler
- # Requires translation!
-Sacred Path =
+Sacred Path = Kutsal Yol
- # Requires translation!
-Sacred Waters =
+Sacred Waters = Kutsal Sular
- # Requires translation!
-Stone Circles =
+Stone Circles = Taş Çemberler
Follower = Takipçi
Asceticism = Zühd
Cathedrals = Katedraller
- # Requires translation!
-Choral Music =
+Choral Music = Koro müziği
Divine inspiration = İlahî ilham
- # Requires translation!
-Feed the World =
+Feed the World = Dünyayı Besle
Guruship = Guruluk
Holy Warriors = Kutsal Savaşçılar
- # Requires translation!
-Liturgical Drama =
+Liturgical Drama = Litürjik Dram
Monasteries = Manastırlar
Mosques = Camiler
- # Requires translation!
-Pagodas =
+Pagodas = Pagodalar
- # Requires translation!
-Peace Gardens =
+Peace Gardens = Huzur Bahçeleri
Religious Art = Dinî Sanat
- # Requires translation!
-Religious Center =
+Religious Center = Dini Merkez
- # Requires translation!
-Religious Community =
+Religious Community = Dini Topluluk
- # Requires translation!
-Swords into Ploughshares =
+Swords into Ploughshares = Kılıçlar Pulluklara
Founder = Kurucu
- # Requires translation!
-Ceremonial Burial =
+Ceremonial Burial = Cenaze Töreni
- # Requires translation!
-Church Property =
+Church Property = Kilise Malı
- # Requires translation!
-Initiation Rites =
+Initiation Rites = Kabul Etme Ayini
Interfaith Dialogue = Dinlerarası Diyalog
- # Requires translation!
-Papal Primacy =
+Papal Primacy = Papalık
- # Requires translation!
-Peace Loving =
+Peace Loving = Barış Seven
Pilgrimage = Hac
- # Requires translation!
-Tithe =
+Tithe = Aşar vergisi
World Church = Dünya Kilisesi
- # Requires translation!
-Enhancer =
- # Requires translation!
-Defender of the Faith =
+Enhancer = Değiştiren
+Defender of the Faith = İmanın Savunucusu
- # Requires translation!
-Holy Order =
+Holy Order = Kutsal Emir
- # Requires translation!
-Itinerant Preachers =
+Itinerant Preachers = Gezgin Vaizler
- # Requires translation!
-Just War =
+Just War = Haklı Savaş
Messiah = Mesih
- # Requires translation!
-Missionary Zeal =
+Missionary Zeal = Misyonerlik Gayreti
Religious Texts = Dinî Metinler
Religious Unity = Dinî Birlik
- # Requires translation!
-Reliquary =
+Reliquary = Kutsal Emanet
#################### Lines from Buildings from Civ V - Gods & Kings ####################
@@ -6249,60 +5844,50 @@ Pyramid = Piramit
Terracotta Army = Terracotta Ordusu
- # Requires translation!
-'Regard your soldiers as your children, and they will follow you into the deepest valleys; look on them as your own beloved sons, and they will stand by you even unto death.' - Sun Tzu =
+'Regard your soldiers as your children, and they will follow you into the deepest valleys; look on them as your own beloved sons, and they will stand by you even unto death.' - Sun Tzu = 'Askerlerine çocuklarınmış gibi davran ki seni en derin vadilere takip etsinler, Onlara biricik oğullarınmış gibi bak ki senin yanında ölüme bile göğüs gersinler.' - Sun Tzu
-Amphitheater = Amfiteatr
+Amphitheater = Amfitiyatro
Petra = Petra
- # Requires translation!
-'...who drinks the water I shall give him, says the Lord, will have a spring inside him welling up for eternal life. Let them bring me to your holy mountain in the place where you dwell. Across the desert and through the mountain to the Canyon of the Crescent Moon...' - Indiana Jones =
+'...who drinks the water I shall give him, says the Lord, will have a spring inside him welling up for eternal life. Let them bring me to your holy mountain in the place where you dwell. Across the desert and through the mountain to the Canyon of the Crescent Moon...' - Indiana Jones = '...Kendisine vereceğim suyu içen kişinin içinde sonsuz yaşam pınarı fışkıracak; beni, senin yaşadığın kutsal dağa getirmelerine izin ver. Çölü aşıp dağın içinden Hilal Ay kanyonuna...' - İndiana Jones
- # Requires translation!
-Great Mosque of Djenne =
- # Requires translation!
-'With the magnificence of eternity before us, let time, with all its fluctuations, dwindle into its own littleness.' - Thomas Chalmers =
+Great Mosque of Djenne = Cenne Büyük Camii
+'With the magnificence of eternity before us, let time, with all its fluctuations, dwindle into its own littleness.' - Thomas Chalmers = 'Önümüzde sonsuzluğun ihtişamı varken, bırakın zaman bütün dalgalanmalarıyla birlikte küçülüp gitsin' - Thomas Chalmers
- # Requires translation!
-Grand Temple =
+Grand Temple = Büyük Tapınak
Alhambra = Elhamra
- # Requires translation!
-'Justice is an unassailable fortress, built on the brow of a mountain which cannot be overthrown by the violence of torrents, nor demolished by the force of armies.' - Joseph Addison =
+'Justice is an unassailable fortress, built on the brow of a mountain which cannot be overthrown by the violence of torrents, nor demolished by the force of armies.' - Joseph Addison = 'Adalet, şiddetli sellerin yıkamadığı bir dağın yamacına inşa edilmiş, orduların gücüyle bile yıkılamayan bir kaledir' - Joseph Addison
- # Requires translation!
-Ceilidh Hall =
+Ceilidh Hall = Ceilidh Salonu
Leaning Tower of Pisa = Pisa Kulesi
-'Don't clap too hard - it's a very old building.' - John Osbourne = Çok sert alkışlamayın - bu çok eski bir bina. - John Osbourne
+'Don't clap too hard - it's a very old building.' - John Osbourne = 'Çok sert alkışlamayın - bu çok eski bir bina.' - John Osbourne
Coffee House = Kahvehane
Neuschwanstein = Neuschwanstein Kalesi
- # Requires translation!
-'...the location is one of the most beautiful to be found, holy and unapproachable, a worthy temple for the divine friend who has brought salvation and true blessing to the world.' - King Ludwig II of Bavaria =
+'...the location is one of the most beautiful to be found, holy and unapproachable, a worthy temple for the divine friend who has brought salvation and true blessing to the world.' - King Ludwig II of Bavaria = '...Görülmüş en güzel mekanlardan birisi, kutsal ve ulaşılmaz, Dünyaya kurtuluşu ve gerçek bereketi getiren ilahı dosta yakışır bir yer.' - Bavyera Kralı 2. Ludwig
Recycling Center = Geri Dönüştürme Merkezi
CN Tower = CN Kulesi
- # Requires translation!
-'Nothing travels faster than light with the possible exception of bad news, which obeys its own special rules.' - Douglas Adams =
+'Nothing travels faster than light with the possible exception of bad news, which obeys its own special rules.' - Douglas Adams = 'Hiçbir şey ışıktan hızlı gitmez; kötü haberler hariç, onların kendine özgü kuralları vardır.' - Douglas Adams
-Bomb Shelter = Bomba barınağı
+Bomb Shelter = Bomba Sığınağı
Hubble Space Telescope = Hubble Uzay Teleskobu
- # Requires translation!
-'The wonder is, not that the field of stars is so vast, but that man has measured it.' - Anatole France =
+'The wonder is, not that the field of stars is so vast, but that man has measured it.' - Anatole France = 'Burada olağanüstü olan şey, uzay boşluğunun büyüklüğü değil, insanoğlunun onu ölçebilmiş olmasıdır.' - Anatole France
Cathedral = Katedral
@@ -6392,6 +5977,7 @@ Wu =
Zhou =
# Requires translation!
Sun =
+Taoism = Taoculuk
# Requires translation!
Refaat =
@@ -6848,7 +6434,7 @@ I grow tired of this throne. I think I should like to have yours instead. = Bu t
Now what is this?! You ask me to add your riches to my great avails. The invitation is accepted. = Bu da ne şimdi?! Benden büyük servetime senin servetini de mi katmamı istiyorsun? Teklifin kabul edildi.
My people will mourn me not with tears, but with human blood. = İnsanlarım beni gözyaşlarıyla değil, kanla anacaklar.
You are in the presence of Attila, scourge of Rome. Do not let hubris be your downfall as well. = Roma'nın belası Attila'nın huzurundasınız. Gururunun senin de sonun olmasına izin verme.
-This is better than you deserve, but let it not be said that I am an unfair man. = Hakettiğinizden fazlasıdır bu, ama bana adaletsiz biri denmesini istemem.
+This is better than you deserve, but let it not be said that I am an unfair man. = Hakettiğinizden fazlasıdır bu, ama sonra Atilla haksızlık ediyor demesinler.
Good day to you. = Size iyi günler dilerim.
Scourge of God = Tanrı'nın Kırbacı
# Requires translation!
@@ -6967,9 +6553,9 @@ Magnus =
Vilma =
# Requires translation!
Kusin =
-Stockholm = Stockholm
+Stockholm = Stokholm
Uppsala = Uppsala
-Gothenburg = Gothenburg
+Gothenburg = Göteborg
Malmö = Malmö
Linköping = Linköping
Kalmar = Kalmar
@@ -7201,17 +6787,12 @@ Saldae =
Units ending their turn on [Mountain] tiles take [50] damage =
Theodora = Theodora
-It is always a shame to destroy a thing of beauty. Happily, you are not one. = Bir güzelliği yok etmek her zaman utanç vericidir. Neyse ki sen öyle birisi değilsin.
- # Requires translation!
-Now darling, tantrums are most unbecoming. I shall have to teach you a lesson. =
- # Requires translation!
-Like a child playing with toys you are. My people will never love you, nor suffer this indignation gracefully. =
- # Requires translation!
-My, isn't this a pleasant surprise - what may I call you, oh mysterious stranger? I am Theodora, beloved of Byzantium. =
- # Requires translation!
-I have heard that you adept at certain kinds of ... interactions. Show me. =
- # Requires translation!
-Hello again. =
+It is always a shame to destroy a thing of beauty. Happily, you are not one. = Bir güzelliği yok etmek her zaman utanç vericidir. Neyse ki sen güzel birisi değilsin.
+Now darling, tantrums are most unbecoming. I shall have to teach you a lesson. = Öfke nöbetleri hiç hoş değil şekerim, görünüşe göre sana bir ders vermem gerekiyor.
+Like a child playing with toys you are. My people will never love you, nor suffer this indignation gracefully. = Oyuncaklarla oynayan bir çocuk gibisin. Halkım seni asla sevmeyecek ve de bu haksızlığa boyun eğmeyecek.
+My, isn't this a pleasant surprise - what may I call you, oh mysterious stranger? I am Theodora, beloved of Byzantium. = Ne kadar güzel bir sürpriz - İsmin nedir, gizemli yabancı? Ben Theodora, Bizansın biricik imparatoriçesi.
+I have heard that you adept at certain kinds of ... interactions. Show me. = Bazı ... etkileşimlerde usta olduğunu duydum. Göster bana.
+Hello again. = Tekrardan merhabalar.
# Requires translation!
Patriarchate of Constantinople =
# Requires translation!
@@ -7593,27 +7174,19 @@ Kabah =
The Maya = Maya
- # Requires translation!
-I didn't want to do this. We declare war. =
- # Requires translation!
-I will fear no evil. For god is with me! =
- # Requires translation!
-Why have you forsaken us my lord? =
+I didn't want to do this. We declare war. = Bunu yapmak istememiştim. Savaş açıyoruz.
+I will fear no evil. For god is with me! = Hiçbir kötülükten korkmam. Bilirim ki Tanrı benimledir!
+Why have you forsaken us my lord? = Tanrım neden bizi yalnız bıraktın?
Bratislava = Bratislava
- # Requires translation!
-We have wanted this for a LONG time. War it shall be. =
- # Requires translation!
-Very well, we will kick you back to the ancient era! =
+We have wanted this for a LONG time. War it shall be. = UZUN süredir bunu bekliyorduk. Savaş zamanı.
+Very well, we will kick you back to the ancient era! = Peki o zaman, seni antik çağlara geri atacağız!
This isn't how it is supposed to be! = Böyle olmaması gerekiyordu!
Cahokia = Kahokya
- # Requires translation!
-By god's grace we will not allow these atrocities to occur any longer. We declare war! =
- # Requires translation!
-May god have mercy on your evil soul. =
- # Requires translation!
-I for one welcome our new conquer overlord! =
+By god's grace we will not allow these atrocities to occur any longer. We declare war! = Tanrı'nın izniyle bu vahşetlerin devam etmesine izin vermeyeceğiz. Savaş açıyoruz!
+May god have mercy on your evil soul. = Tanrı senin zalim ruhuna merhamet etsin.
+I for one welcome our new conquer overlord! = Ben şahsen fetheden derebeyimize hoşgeldiniz diyorum!
Jerusalem = Kudüs
@@ -7631,21 +7204,15 @@ Judaism = Yahudilik
Sikhism = Sihizm
-Taoism = Taoculuk
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
- # Requires translation!
-We have found holy symbols in the ruins, giving us a deeper understanding of religion! (+[faithAmount] Faith) =
- # Requires translation!
-discover holy symbols =
+We have found holy symbols in the ruins, giving us a deeper understanding of religion! (+[faithAmount] Faith) = Yıkıntılarda kutsal semboller bulduk, bu dini daha derinden anlamamızı sağladı! (+[faithAmount] İnanç)
+discover holy symbols = Kutsal semboller keşfet
- # Requires translation!
-We have found an ancient prophecy in the ruins, greatly increasing our spiritual connection! (+[faithAmount] Faith) =
- # Requires translation!
-an ancient prophecy =
+We have found an ancient prophecy in the ruins, greatly increasing our spiritual connection! (+[faithAmount] Faith) = Yıkıntılarda antik bir kehanet bulduk, bu bizim ruhani bağlantımızı güçlendirdi! (+[faithAmount] İnanç)
+an ancient prophecy = Antik bir kehanet
#################### Lines from Specialists from Civ V - Gods & Kings ####################
@@ -7671,33 +7238,29 @@ Architecture = Mimari
Industrialization = Sanayileşme
-'Men, like bullets, go farthest when they are smoothest.' - Jean Paul = 'Erkekler, mermiler gibi, en yumuşak olduklarında en uzağa giderler.' - Jean Paul
+'Men, like bullets, go farthest when they are smoothest.' - Jean Paul = 'Erkekler de mermiler gibi, düzgün olduklarında en uzağa giderler.' - Jean Paul
Ballistics = Balistik
'The root of the evil is not the construction of new, more dreadful weapons. It is the spirit of conquest.' - Ludwig von Mises = 'Kötülüğün kökü yeni, daha korkunç silahların yapımı değildir. Bu fetih ruhudur.' - Ludwig von Mises
-Combined Arms = Birleşik Silahlanma
+Combined Arms = Karma Askerler
- # Requires translation!
-'The more we elaborate our means of communication, the less we communicate.' - J.B. Priestly =
+'The more we elaborate our means of communication, the less we communicate.' - J.B. Priestly = 'İletişim yöntemlerimizi geliştirdikçe birbirimizle daha az iletişim kuruyoruz' - J.B. Priestly
Telecommunications = Telekomünikasyon
-'All men can see these tactics whereby I conquer, but what none can see is the strategy out of which victory is evolved.' - Sun Tzu = 'Herkes fethettiğim bu taktikleri görebilirler, ancak hiç kimsenin göremediği, zaferin geliştiği stratejidir.' - Sun Tzu
+'All men can see these tactics whereby I conquer, but what none can see is the strategy out of which victory is evolved.' - Sun Tzu = 'Zafer esnasında uyguladığım taktikleri herkes görebilir, ancak kimsenin göremediği, zafer yolunu açan stratejilerimdir.' - Sun Tzu
Mobile Tactics = Gelişmiş Taktikler
#################### Lines from Terrains from Civ V - Gods & Kings ####################
- # Requires translation!
-Mount Kailash =
+Mount Kailash = Kailaş Dağı
Mount Sinai = Sina Dağı
- # Requires translation!
-Sri Pada =
+Sri Pada = Adem Tepesi
- # Requires translation!
-Uluru =
+Uluru = Uluru
#################### Lines from TileImprovements from Civ V - Gods & Kings ####################
@@ -7724,7 +7287,10 @@ Truffles = Yermantarı
# Requires translation!
-Hussar =
+Devout =
+
+
+Hussar = Hüssar
Hakkapeliitta = Hakkapeliitta
@@ -7736,15 +7302,12 @@ Hakkapeliitta = Hakkapeliitta
#################### Lines from Units from Civ V - Gods & Kings ####################
- # Requires translation!
-Atlatlist =
+Atlatlist = Atlatlçı
- # Requires translation!
-Quinquereme =
+Quinquereme = Quinquereme
- # Requires translation!
-Dromon =
+Dromon = Dromon
Horse Archer = Atlı Okçu
@@ -7752,14 +7315,12 @@ Horse Archer = Atlı Okçu
Battering Ram = Koçbaşı
- # Requires translation!
-Pictish Warrior =
+Pictish Warrior = Pikt Savaşçı
African Forest Elephant = Afrika Orman Fili
- # Requires translation!
-Cataphract =
+Cataphract = Katafrakt
Composite Bowman = Karma Okçular
@@ -7778,8 +7339,7 @@ Gatling Gun = Mitralyöz
Carolean = Carollu
- # Requires translation!
-Mehal Sefari =
+Mehal Sefari = Mehal Sefari
Great War Infantry = 1. Dünya Savaşı Piyadesi
@@ -7796,6 +7356,14 @@ Machine Gun = Makineli Tüfek
Landship = Kara Gemisi
+Great Prophet = Harika Peygamber
+
+
+Missionary = Misyoner
+
+Inquisitor = Engizisyon Üyesi
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
@@ -7806,42 +7374,29 @@ Welcome to Unciv!\nBecause this is a complex game, there are basic tasks to help
Your first mission is to found your capital city.\nThis is actually an important task because your capital city will probably be your most prosperous.\nMany game bonuses apply only to your capital city and it will probably be the center of your empire. = İlk görevin başkentini kurmak.\nBu aslında önemli bir görev çünkü başkentin muhtemelen en kazançlı şehrin olacak.\nOyundaki çoğu bonus başkentin için geçerli ve başkentin muhtemelen imparatorluğunun merkezinde yer alacak.
How do you know a spot is appropriate?\nThat’s not an easy question to answer, but looking for and building next to luxury resources is a good rule of thumb.\nLuxury resources are tiles that have things like gems, cotton, or silk (indicated by a smiley next to the resource icon)\nThese resources make your civilization happy. You should also keep an eye out for resources needed to build units, such as iron. Cities cannot be built within 3 tiles of existing cities, which is another thing to watch out for! = Bir yerin bir şehir için doğru yer olduğunu nerden bilirsiniz?\n Bu kolay bir soru değil ancak Lüks kaynakların yakınına bakmak çoğu zaman iyi bir yönem olabilir.\nLüks kaynaklar üzerinde Cevherler, Pamuk, veya İpek ve türevleri bulunan karolardır, kaynak ikonunun yanında bir gülücük ile belirtilir\nBu kaynaklar uygarlığını mutlu eder. Ayrıca birlik üretimi için gerekli olan kaynakları da dikkate almalısın, demir gibi. Şehirler varolan şehirlerin 3 karo yakınında kurulamaz, bu da dikkat edilecek ayrı bir durumdur.
- # Requires translation!
-However, cities don’t have a set area that they can work - more on that later!\nThis means you don’t have to settle cities right next to resources.\nLet’s say, for example, that you want access to some iron – but the resource is right next to a desert.\nYou don’t have to settle your city next to the desert. You can settle a few tiles away in more prosperous lands.\nYour city will grow and eventually gain access to the resource.\nYou only need to settle right next to resources if you need them immediately – \n which might be the case now and then, but you’ll usually have the luxury of time. =
+However, cities don’t have a set area that they can work - more on that later!\nThis means you don’t have to settle cities right next to resources.\nLet’s say, for example, that you want access to some iron – but the resource is right next to a desert.\nYou don’t have to settle your city next to the desert. You can settle a few tiles away in more prosperous lands.\nYour city will grow and eventually gain access to the resource.\nYou only need to settle right next to resources if you need them immediately – \n which might be the case now and then, but you’ll usually have the luxury of time. = Ancak şehirler sadece belli bir yere özel değildir.\nBu da şehirleri her zaman kaynakların yanına kurman gerekmediği anlamına gelir.\nMesela diyelim ki sana demir lazım, ama demir kaynağı hemen çölün yanında.\nŞehrini çölün ortasına kurmak zorunda değilsin. Birkaç karo uzakta daha bereketli topraklara kurabilirsin.\nŞehrin zamanla zaten büyüyüp demir kaynağını içine alacaktır.\nEğer kaynaklar acil lazım ise şehri kaynakların yanına kur.\nBazen böyle acil durumlar olsa da kaynaklar için genellikle bolca vaktin olur.
The first thing coming out of your city should be either a Scout or Warrior.\nI generally prefer the Warrior because it can be used for defense and because it can be upgraded\n to the Swordsman unit later in the game for a relatively modest sum of gold.\nScouts can be effective, however, if you seem to be located in an area of dense forest and hills.\nScouts don’t suffer a movement penalty in this terrain.\nIf you’re a veteran of the 4x strategy genre your first Warrior or Scout will be followed by a Settler.\nFast expanding is absolutely critical in most games of this type. = Şehrinizin eğittiği ilk birlik bir Gözcü veya Savaşcı olmalıdır.\n ben genellikle savunma için kullanılması ve ileride\nmakul bir miktara Kılıçlıya yükseltilebildiği için savaşcıyı tercih ederim ama bulunduğunuz yer engebeli veya ormanlarla dolu ise Gözcü bu ortamlarda daha iyi hareket eder.\neğer 4X oyun oynarsanız sınırları genişletmenin önemli olduğunu bilirsiniz, bu durum\nbu oyundada geçerli, Gözcü veya Savaşcıyı eğittikten sonra yerleşmeci üretmeniz iyi olur.
- # Requires translation!
-In your first couple of turns, you will have very little options, but as your civilization grows, so do the number of things requiring your attention. =
+In your first couple of turns, you will have very little options, but as your civilization grows, so do the number of things requiring your attention. = İlk birkaç turda yapacak çok şey olmayabilir, ama uygarlığın büyüdükçe, dikkat etmen gereken şeylerin sayısı da artacak.
Culture and Policies = Kültür ve Politikalar
- # Requires translation!
-Each turn, the culture you gain from all your cities is added to your Civilization's culture.\nWhen you have enough culture, you may pick a Social Policy, each one giving you a certain bonus. =
- # Requires translation!
-The policies are organized into branches, with each branch providing a bonus ability when all policies in the branch have been adopted. =
- # Requires translation!
-With each policy adopted, and with each city built,\n the cost of adopting another policy rises - so choose wisely! =
+Each turn, the culture you gain from all your cities is added to your Civilization's culture.\nWhen you have enough culture, you may pick a Social Policy, each one giving you a certain bonus. = Her tur, şehirlerinin her birinde ürettiğin kültür uygarlığının toplam kültürüne eklenir. Yeterince kültürün biriktiği zaman, her biri farklı bir avantaj sağlayan politikalardan bir politika seçebilirsin.
+The policies are organized into branches, with each branch providing a bonus ability when all policies in the branch have been adopted. = Politikalar dallara ayrılmıştır, bu dallar bir daldaki 5 politika da seçildikten sonra dal tamamlandığı için bir bonus daha sağlar.
+With each policy adopted, and with each city built,\n the cost of adopting another policy rises - so choose wisely! = Her politika seçiminden sonra ve kurulan her yeni şehirle\n yeni bir politika seçmenin kültür bedeli artar - o yüzden seçimini iyi yap!
City Expansion = Şehir Genişlemesi
- # Requires translation!
-Once a city has gathered enough Culture, it will expand into a neighboring tile.\nYou have no control over the tile it will expand into, but tiles with resources and higher yields are prioritized. =
- # Requires translation!
-Each additional tile will require more culture, but generally your first cities will eventually expand to a wide tile range. =
- # Requires translation!
-Although your city will keep expanding forever, your citizens can only work 3 tiles away from city center.\nThis should be taken into account when placing new cities. =
+Once a city has gathered enough Culture, it will expand into a neighboring tile.\nYou have no control over the tile it will expand into, but tiles with resources and higher yields are prioritized. = Bir şehir yeterince kültür ürettiği zaman komşu karolardan birine genişler.\n Bunun hangi karo olacağını seçemezsin ama değerli kaynakları olan ya da verimli karolara öncelik verir.
+Each additional tile will require more culture, but generally your first cities will eventually expand to a wide tile range. = Üst üste gelen her genişleme bir öncekinden daha fazla kültür isteyecek ama çoğunlukla ilk kurduğun şehirler uzun zaman sonra baya geniş alanlara yayılır.
+Although your city will keep expanding forever, your citizens can only work 3 tiles away from city center.\nThis should be taken into account when placing new cities. = Şehrin genişlemeyi hiç bırakmasa da nüfusun sadece şehir merkezinden en fazla 3 karo uzaklıkta çalışabilir.\n Yeni şehirler kurarken buna dikkat etmelisin.
- # Requires translation!
-As cities grow in size and influence, you have to deal with a happiness mechanic that is no longer tied to each individual city.\nInstead, your entire empire shares the same level of satisfaction.\nAs your cities grow in population you’ll find that it is more and more difficult to keep your empire happy. =
- # Requires translation!
-In addition, you can’t even build any city improvements that increase happiness until you’ve done the appropriate research.\nIf your empire’s happiness ever goes below zero the growth rate of your cities will be hurt.\nIf your empire becomes severely unhappy (as indicated by the smiley-face icon at the top of the interface)\n your armies will have a big penalty slapped on to their overall combat effectiveness. =
- # Requires translation!
-This means that it is very difficult to expand quickly in Unciv.\nIt isn’t impossible, but as a new player you probably shouldn't do it.\nSo what should you do? Chill out, scout, and improve the land that you do have by building Workers.\nOnly build new cities once you have found a spot that you believe is appropriate. =
+As cities grow in size and influence, you have to deal with a happiness mechanic that is no longer tied to each individual city.\nInstead, your entire empire shares the same level of satisfaction.\nAs your cities grow in population you’ll find that it is more and more difficult to keep your empire happy. = Şehirlerin büyüyüp geliştikçe artık tek bir şehre bağlı olmayan bir mutluluk mekaniğiyle uğraşman gerekicek.\nOnun yerine bütün imparatorluğun aynı mutluluğu paylaşacak.\nŞehirlerinin nüfusu arttıkça onları mutlu tutmanın daha da zorlaştığını farkedeceksin.
+In addition, you can’t even build any city improvements that increase happiness until you’ve done the appropriate research.\nIf your empire’s happiness ever goes below zero the growth rate of your cities will be hurt.\nIf your empire becomes severely unhappy (as indicated by the smiley-face icon at the top of the interface)\n your armies will have a big penalty slapped on to their overall combat effectiveness. = Ayrıca mutluluğu artıran binaları şehrine inşa etmen için önce onlar için gereken teknolojiyi araştırman gerek.\n İmparatorluğunun mutluluğu 0'ın altına inerse şehirlerinin büyüme oranı düşecek.\nEğer imparatorluğun aşırı mutsuz olursa (buna arayüzün en üstündeki gülücük ifadesinden bakabilirsin)\nordularının savaştaki performansı oldukça düşecek.
+This means that it is very difficult to expand quickly in Unciv.\nIt isn’t impossible, but as a new player you probably shouldn't do it.\nSo what should you do? Chill out, scout, and improve the land that you do have by building Workers.\nOnly build new cities once you have found a spot that you believe is appropriate. = Bu da Unciv'de çok hızlı genişlemenin zor olduğu anlamında geliyor.\nİmkansız değil ama yeni bir oyuncu olarak böyle bir şey yapmamalısın.\nPeki ne yapmalısın? Yavaştan al, keşifler yap, önce işçileri kullanarak elindeki toprakları iyileştir.\nSadece iyi olduğuna inandığın bir yer bulduğun zaman yeni bir şehir kur.
Unhappiness = Mutsuzluk
- # Requires translation!
-It seems that your citizens are unhappy!\nWhile unhappy, your civilization will suffer many detrimental effects, increasing in severity as unhappiness gets higher. =
-Unhappiness has two main causes: Population and cities.\n Each city causes 3 unhappiness, and each population, 1 = Mutsuzluğun iki ana sebebi vardır: Nüfus ve Şehirler.\n Her şehir 3 Mutsuzluk puanı sağlar ve her nüfus 2 Mutsuzluk puanı sağlar
- # Requires translation!
-There are 2 main ways to combat unhappiness:\n by building happiness buildings for your population\n or by having improved luxury resources within your borders. =
+It seems that your citizens are unhappy!\nWhile unhappy, your civilization will suffer many detrimental effects, increasing in severity as unhappiness gets higher. = Görünüşe göre vatandaşların mutsuz!\nMutsuzluğun uygarlığına bir sürü kötü etkisi var, mutsuzluk arttıkça bu etkiler de kötüleşiyor.
+Unhappiness has two main causes: Population and cities.\n Each city causes 3 unhappiness, and each population, 1 = Mutsuzluğun iki ana sebebi vardır: Nüfus ve Şehirler.\n Her şehir 3 Mutsuzluk puanı sağlar ve her nüfus 2 Mutsuzluk puanı sağlar.
+There are 2 main ways to combat unhappiness:\n by building happiness buildings for your population\n or by having improved luxury resources within your borders. = Mutsuzlukla savaşmanın iki yolu vardır:\n nüfusun için mutluluk veren binalar inşa etmek ya da sınırların içinde işlenen lüks kaynaklara sahip olmak.
You have entered a Golden Age!\nGolden age points are accumulated each turn by the total happiness \n of your civilization\nWhen in a golden age, culture and production generation increases +20%,\n and every tile already providing at least one gold will provide an extra gold. = Altın Çağa girdin!\n Altın Çağ puanları her tur mutluluğunuzca artar.\nAltın Çağdayken Kültür ve Üretim %20 artar ve en az 1 altın sağlayan karolar 1 tane daha sağlar.
@@ -7850,13 +7405,11 @@ Connecting your cities to the capital by roads\n will generate gold via the tra
Victory Types = Zafer Türleri
Once you’ve settled your first two or three cities you’re probably 100 to 150 turns into the game.\nNow is a good time to start thinking about how, exactly, you want to win – if you haven’t already. = İlk 2 veya 3 şehrinizi kurduysanız 100 veya 150 civarı bir turdasınız demektir\n bu durumda bir an önce Zafer seçiminizi yapmanız yararınıza olacaktır!
- # Requires translation!
-There are four ways to win in Unciv. They are:\n - Cultural Victory: Complete 5 Social Policy Trees and build the Utopia Project\n - Domination Victory: Survive as the last civilization\n - Science Victory: Be the first to construct a spaceship to Alpha Centauri\n - Diplomatic Victory: Build the United Nations and win the vote =
- # Requires translation!
-So to sum it up, these are the basics of Unciv – Found a prosperous first city, expand slowly to manage happiness, and set yourself up for the victory condition you wish to pursue.\nObviously, there is much more to it than that, but it is important not to jump into the deep end before you know how to swim. =
+There are four ways to win in Unciv. They are:\n - Cultural Victory: Complete 5 Social Policy Trees and build the Utopia Project\n - Domination Victory: Survive as the last civilization\n - Science Victory: Be the first to construct a spaceship to Alpha Centauri\n - Diplomatic Victory: Build the United Nations and win the vote = Unciv'de kazanmanın dört yolu vardır. Bunlar sırasıyla:\n - Kültürel Zafer: 5 tane politika dalını tamamla ve Ütopya projesini inşa et.\n - Hakimiyet Zaferi: Ayakta kalan son uygarlık ol.\n - Bilimsel Zafer: Alfa Centauri'ye ilk uzay gemisini fırlat.\n - Diplomatik Zafer: Birleşmiş Milletleri kur ve oylamayı kazan.
+So to sum it up, these are the basics of Unciv – Found a prosperous first city, expand slowly to manage happiness, and set yourself up for the victory condition you wish to pursue.\nObviously, there is much more to it than that, but it is important not to jump into the deep end before you know how to swim. = Unciv'in ana kısmını özetlemek gerekirse - Varlıklı bir şehir kur, yavaşça genişleyerek mutluluğu yönet ve kendini istediğin zafer türü için hazırla.\n Tabiki kazanmak için bundan fazlasını bilmen gerekiyor ama yüzmeyi öğrenmeden derin tarafa atlamamak lazım.
Enemy City = Düşman Şehri
-Cities can be conquered by reducing their health to 1, and entering the city with a melee unit.\nSince cities heal each turn, it is best to attack with ranged units and use your melee units to defend them until the city has been defeated! = Şehirler canlarını 1 e indirip şehre bir yakın dövüş birliği ile girerek fethedilebilir.\nŞehirler her tur kaybettikleri canı geri kazandığından şehirlere Menzilli birlikler ile saldırıp yakın dövüş birlikleri ile fethetmek en iyisidir.
+Cities can be conquered by reducing their health to 1, and entering the city with a melee unit.\nSince cities heal each turn, it is best to attack with ranged units and use your melee units to defend them until the city has been defeated! = Şehirler canlarını 1 e indirip şehre bir yakın dövüş birliği ile girerek fethedilebilir.\nŞehirler her tur kaybettikleri canın bir kısmını geri kazandığından şehirlere Menzilli birlikler ile saldırıp yakın dövüş birlikleri ile fethetmek en iyisidir.
Luxury Resource = Lüks Kaynak
Luxury resources within your domain and with their specific improvement are connected to your trade network.\nEach unique Luxury resource you have adds 5 happiness to your civilization, but extra resources of the same type don't add anything, so use them for trading with other civilizations! = Bir Lüks Kaynak sizin sahip olduğunuz bir karoda ise ve o karo geliştirilip bir nüfus atanmış ise Ticaret Ağınıza bağlanmış olur.\nSahip olduğunuz her Lüks Kaynak(Ticaret ile edinilmiş bile olsa) Uygarlığınıza 5 Mutluluk puanı katar, ancak fazladan Lüks Kaynaklar\n bir işe yaramaz, bu yüzden fazladan kaynakları diğer uygarlıklar ile ticsrette kullanmalısınız!
@@ -7868,9 +7421,8 @@ Unlike Luxury Resources, each Strategic Resource on the map provides more than o
The city can no longer put up any resistance!\nHowever, to conquer it, you must enter the city with a melee unit = Bu şehrin artık sana karşı direnecek gücü yok!\n Ancak, burayı fethetmek için şehre bir yakın dövüş birliğiyle girmelisin
After Conquering = Fetihten Sonra
-When conquering a city, you can choose to liberate, annex, puppet, or raze the city. = Bir şehri fethederken o şehri işgal edebilir, kuklanız yapabilir, veya şehri yıkabilirsiniz>\nBir şehri yıkmak şehir yokolana kadar nüfusunu her tur 1 birim azaltır. (Translation update needed!)
- # Requires translation!
-\nLiberating the city will return it to its original owner, giving you a massive diplomatic boost with them!\n\nAnnexing the city will give you full control over it, but also increase the citizens' unhappiness to 2x!\nThis can be mitigated by building a courthouse in the city, returning the citizen's unhappiness to normal.\n\nPuppeting the city will mean that you have no control on the city's production.\nThe city will not increase your tech or policy cost.\nA puppeted city can be annexed at any time, but annexed cities cannot be returned to a puppeted state!\n\nRazing the city will lower its population by 1 each turn until the city is destroyed!\nYou cannot raze a city that is either the starting capital of a civilization or the holy city of a religion. =
+When conquering a city, you can choose to liberate, annex, puppet, or raze the city. = Bir şehri fethederken o şehri ilhak edebilir, kuklanız yapabilir, şehri özgür yapabilir veya şehri yıkabilirsiniz.
+\nLiberating the city will return it to its original owner, giving you a massive diplomatic boost with them!\n\nAnnexing the city will give you full control over it, but also increase the citizens' unhappiness to 2x!\nThis can be mitigated by building a courthouse in the city, returning the citizen's unhappiness to normal.\n\nPuppeting the city will mean that you have no control on the city's production.\nThe city will not increase your tech or policy cost.\nA puppeted city can be annexed at any time, but annexed cities cannot be returned to a puppeted state!\n\nRazing the city will lower its population by 1 each turn until the city is destroyed!\nYou cannot raze a city that is either the starting capital of a civilization or the holy city of a religion. = Bir şehri özgür yapmak o şehri ilk kuran sahibine devreder ve diplomatik ilişkilerinizi büyük miktarda iyileştirir!\n Şehri ilhak etmek size şehir üstünde tam kontrol sağlar ama şehirdeki vatandaşların mutsuzluğunu 2 katına çıkarır!\nBu durumu şehre bir mahkeme inşa ederek çözebilirsin, mahkeme vatandaşların mutsuzluğunu normal haline getirir!\n\nKuklan yaptığın bir şehrin üretimine karışamazsın\nama kukla şehir teknoloji ve politika fiyatlarını artırmaz.\nKuklan yaptığın bir şehri istediğin zaman ilhak edebilirsin ama ilhak ettiğin bir şehri geri kuklan yapamazsın!\n\nBir şehri yıkmak şehir yok olana kadar nüfusunu her tur 1 birim azaltır.\nBir uygarlığın ilk başkentini veya bir dinin kutsal şehrini yıkamazsın.
You have encountered a barbarian unit!\nBarbarians attack everyone indiscriminately, so don't let your \n civilian units go near them, and be careful of your scout! = Bir barbar birliğiyle karşılaştın!\nBarbarlar ayırt etmeksizin herkese saldırırlar, bu yüzden \n sivil birliklerinizi onların yanına götürmeyin, izcinize de dikkat edin!
@@ -7879,8 +7431,7 @@ You have encountered another civilization!\nOther civilizations start out peacef
Once you have completed the Apollo Program, you can start constructing spaceship parts in your cities\n (with the relevant technologies) to win a Scientific Victory! = Apollo Programını tamamladıktan sonra şehirlerinizde (gerekli teknolojiler araştırıldığında) uzay gemisi parçaları üretmeye başlayabilirsiniz\n hepsini üretince Bilimsel Zafer elde edersiniz!
Injured Units = Yaralı Birlikler
- # Requires translation!
-Injured units deal less damage, but recover after turns that they have been inactive.\nUnits heal 10 health per turn in enemy territory or neutral land,\n 20 inside your territory and 25 in your cities. =
+Injured units deal less damage, but recover after turns that they have been inactive.\nUnits heal 10 health per turn in enemy territory or neutral land,\n 20 inside your territory and 25 in your cities. = Yaralı birimler daha az hasar vurur, ama bir şey yapmadıklarında zamanla iyileşirler.\nBirimler düşman bölgesinde veya sahipsiz arazilerde her tur 10 can iyileşir,\nkendi sınırların içerisinde 20 can ve şehir merkezlerinde ise 25 can iyileşirler.
Workers = İşçiler
Workers are vital to your cities' growth, since only they can construct improvements on tiles.\nImprovements raise the yield of your tiles, allowing your city to produce more and grow faster while working the same amount of tiles! = İşciler geliştirmeler inşa edebilmeleri sebebiyle şehirlerinin büyümelerinde en önemli faktörlerdir\ngeliştirmeler bir karonun verimini arttırır ve bu sayede aynı sayıda karoyu işlerken daha fazla büyürsünüz!
@@ -7889,26 +7440,19 @@ Siege Units = Kuşatma Birlikleri
Siege units are extremely powerful against cities, but need to be Set Up before they can attack.\nOnce your siege unit is set up, it can attack from the current tile,\n but once moved to another tile, it will need to be set up again. = Kuşatma birlikleri şehirlere karşı aşırı önemlidir ancak saldırıdan önce hazırlanmaları gereklidir.\nKuşatma birliğin hazır olduğunda şuan üstünde bulundugu karodan saldırabilir\n ancak başka karoys hareket ettiğinde yeniden hazırlanması gerekir.
Embarking = Denize Giriş
- # Requires translation!
-Once a certain tech is researched, your land units can embark, allowing them to traverse water tiles.\nEntering or leaving water takes the entire turn. =
- # Requires translation!
-Units are defenseless while embarked (cannot use modifiers), and have a fixed Defending Strength based on your tech Era, so be careful!\nRanged Units can't attack, Melee Units have a Strength penalty, and all have limited vision. =
+Once a certain tech is researched, your land units can embark, allowing them to traverse water tiles.\nEntering or leaving water takes the entire turn. = Belli bir teknolojiden sonra kara birimleri denize girebilir, bu onların suda hareket etmesini sağlar.\nSuya girmek veya sudan çıkmak birimin bütün turunu harcar.
+Units are defenseless while embarked (cannot use modifiers), and have a fixed Defending Strength based on your tech Era, so be careful!\nRanged Units can't attack, Melee Units have a Strength penalty, and all have limited vision. = Sudaki birimler savunmasızdır (arazi avantajı diye bir şey kalmaz), ayrıca içinde bulunduğun çağa göre belli bir savunma gücüne sahip olurlar yani dikkatli ol!\nUzaktan saldıran birimler saldıramaz, yakın dövüş birimlerinin ise güç dezavantajı olur ve ikisinin de görüş alanı kısıtlı olur.
Idle Units = Boştaki Birimler
- # Requires translation!
-If you don't want to move a unit this turn, you can skip it by clicking 'Next unit' again.\nIf you won't be moving it for a while, you can have the unit enter Fortify or Sleep mode - \n units in Fortify or Sleep are not considered idle units.\nIf you have not decided yet what an unit should do for the current turn, choose the 'Wait' command. A 'waiting' unit will be selected again at the end of the 'Next Unit' cycle, once all other units have received their orders.\nIf you want to disable the 'Next unit' feature entirely, you can toggle it in Menu -> Check for idle units. =
+If you don't want to move a unit this turn, you can skip it by clicking 'Next unit' again.\nIf you won't be moving it for a while, you can have the unit enter Fortify or Sleep mode - \n units in Fortify or Sleep are not considered idle units.\nIf you have not decided yet what an unit should do for the current turn, choose the 'Wait' command. A 'waiting' unit will be selected again at the end of the 'Next Unit' cycle, once all other units have received their orders.\nIf you want to disable the 'Next unit' feature entirely, you can toggle it in Menu -> Check for idle units. = Eğer bir birliği bu tur hareket ettirmek istemiyorsan 'sonraki birim' tuşuna bir daha basman yeterli.\nEğer o birliği uzun bir süre hareket ettirmeyeceksen birliği 'uyku' ya da 'tahkimat' moduna alabilirsin\nTahkimat yapan veya uyuyan birimler boşta gözükmez\nEğer bir birliğe ne emir vereceğine daha karar vermediysen 'bekle' diyebilirsin, 'bekleyen' bir birim 'sonraki birim' döngüsünün en sonunda yani diğer birimlerin hamlelerini seçtikten sonra olacaktır.\nSonraki birim özelliğinden kurtulmak istiyorsan ayarlardan 'boş birimleri kontrol edin' özelliğini kapat.
Contact Me = Bana Ulaşın
- # Requires translation!
-Hi there! If you've played this far, you've probably seen that the game is currently incomplete.\n Unciv is meant to be open-source and free, forever.\n That means no ads or any other nonsense. =
- # Requires translation!
-What motivates me to keep working on it, \n besides the fact I think it's amazingly cool that I can,\n is the support from the players - you guys are the best! =
- # Requires translation!
-Every rating and review that I get puts a smile on my face =)\n So contact me! Send me an email, review, Github issue\n or mail pigeon, and let's figure out how to make the game \n even more awesome!\n(Contact info is in the Play Store) =
+Hi there! If you've played this far, you've probably seen that the game is currently incomplete.\n Unciv is meant to be open-source and free, forever.\n That means no ads or any other nonsense. = Merhaba! Buraya kadar oynadıysan büyük ihtimal farketmişsindir ki bu daha tamamlanmış bir oyun değil.\nUnciv sonsuza kadar bedava ve açık kaynaklı olmayı amaçlıyor.\nBu da demek oluyor ki reklam ve benzeri saçmalıklar yok.
+What motivates me to keep working on it, \n besides the fact I think it's amazingly cool that I can,\n is the support from the players - you guys are the best! = Böyle bir oyunu yapabiliyor olmamın çok fevkalade bir şey olması\n dışında beni motive eden şey oyuncuların desteği - sizler bir numarasınız!
+Every rating and review that I get puts a smile on my face =)\n So contact me! Send me an email, review, Github issue\n or mail pigeon, and let's figure out how to make the game \n even more awesome!\n(Contact info is in the Play Store) = Gelen her yorum ve puanlama beni çok mutlu ediyor =)\nO yüzden bana ulaşın! Bana email gönderin, yorum yapın, github sorunu bildirin\n posta güvercini bile yollayabilirsiniz ve birlikte bu oyunu nasıl daha da\n muhteşem yapabileceğimize bakarız.
-Pillaging = Yağma
- # Requires translation!
-Military units can pillage improvements, which heals them 25 health and ruins the improvement.\nThe tile can still be worked, but advantages from the improvement - stat bonuses and resources - will be lost.\nWorkers can repair these improvements, which takes less time than building the improvement from scratch.\nPillaging certain improvements will result in your units looting gold from the improvement. =
+Pillaging = Yağmalamak
+Military units can pillage improvements, which heals them 25 health and ruins the improvement.\nThe tile can still be worked, but advantages from the improvement - stat bonuses and resources - will be lost.\nWorkers can repair these improvements, which takes less time than building the improvement from scratch.\nPillaging certain improvements will result in your units looting gold from the improvement. = Askeri birlikler arazi geliştirmelerini yağmalayabilir, bu onları 25 can iyileştirir ve geliştirmeyi mahveder.\nBu karoda hala çalışılabilir ama arazi geliştirmesinin sağladığı kaynaklar ve üretim bonusları kaybolur.\nİşçiler bu geliştirmeleri tamir edebilir, bu baştan inşa etmekten daha hızlıdır.\nBazı geliştirmeler yağmalandığı zaman yağmalayana altın kazandırır.
Experience = Deneyim
Units that enter combat gain experience, which can then be used on promotions for that unit.\nUnits gain more experience when in Melee combat than Ranged, and more when attacking than when defending. = Dövüşe katılan birlikler rütbe yükseltmelerinde kullanılabilen deneyim kazanır.\nBirlikler Yakın Dövüş yaparken Menzilli Dövüşe göre daha fazla deneyim kazanır, saldırmak savunmaya kıyasla daha çok deneyim verir.
@@ -7920,310 +7464,172 @@ Units use the 'Strength' value as the base combat value when melee attacking and
Ranged attacks can be done from a distance, dependent on the 'Range' value of the unit.\nWhile melee attacks allow the defender to damage the attacker in retaliation, ranged attacks do not. = Menzilli saldırılar mesafeli şekilde yspılabilir, bu mesafe ise birliğin 'Menzil' değerine bağlıdır.\nYakın saldırılar savunucunun saldırgana geri hasar vermesini sağlarken menzilli saldırılar saldırgana hasar vermez.
Research Agreements = Araştırma Antlaşmaları
- # Requires translation!
-In research agreements, you and another civilization decide to jointly research technology.\nAt the end of the agreement, you will both receive a 'lump sum' of Science, which will go towards one of your unresearched technologies. =
- # Requires translation!
-The amount of ⍾Science you receive at the end is dependent on the ⍾Science generated by your cities and the other civilization's cities during the agreement - the more, the better! =
+In research agreements, you and another civilization decide to jointly research technology.\nAt the end of the agreement, you will both receive a 'lump sum' of Science, which will go towards one of your unresearched technologies. = Araştırma Antlaşmalarında sen ve başka bir medeniyet kafa kafaya verip teknoloji araştırıyorsunuz.\nAntlaşmanın sonunda ikiniz de yüklü miktarda bilim puanı elde edeceksiniz, bu bilim puanı daha keşfedilmemiş teknolojileri araştırmana yardımcı olur.
+The amount of ⍾Science you receive at the end is dependent on the ⍾Science generated by your cities and the other civilization's cities during the agreement - the more, the better! = Antlaşmanın sonunda elde ettiğin ⍾Bilim miktarı senin uygarlığının ve diğer uygarlığın antlaşma esnasında ürettiği ⍾Bilim miktarına bağlıdır - ne kadar fazla, o kadar iyi!
- # Requires translation!
-Defensive Pacts =
- # Requires translation!
-Defensive pacts allow you and another civ to protect one another from aggressors.\nOnce the defensive pact is signed, you will be drawn into their future defensive wars, just as they will be drawn into your future defensive wars. Declaring war on any Civ will remove all of your defensive pacts. You will have to re-sign them to use their effect. =
- # Requires translation!
-Be cautious when signing defensive pacts because they can bring you into wars that you might not want to be in. =
- # Requires translation!
-The AI is very careful and will not accept defensive pacts with less than 80 influence. =
+Defensive Pacts = Savunma Paktları
+Defensive pacts allow you and another civ to protect one another from aggressors.\nOnce the defensive pact is signed, you will be drawn into their future defensive wars, just as they will be drawn into your future defensive wars. Declaring war on any Civ will remove all of your defensive pacts. You will have to re-sign them to use their effect. = Savunma paktları seni ve bir diğer uygarlığı düşmanlarından korumak içindir.\nSavunma paktı imzalandıktan sonra imzalayan uygarlıklardan birine açılan savaş, ikisine birden açılmış gibi görünür. Savunma paktı etkinken herhangi bir uygarlığa savaş açmak savunma paktını iptal eder, eğer savunma paktından hala faydalanmak istiyorsan savaştan sonra yeniden imzalaman gerekir.
+Be cautious when signing defensive pacts because they can bring you into wars that you might not want to be in. = Savunma paktlarını imzalarken diffatli ol çünkü seni içinde olmak istemeyeceğin savaşlara sürükleyebilirler.
+The AI is very careful and will not accept defensive pacts with less than 80 influence. = YZ bu konuda oldukça dikkatli ve ilişkiniz 80'in altındaysa asla savunma paktlarını kabul etmez.
- # Requires translation!
-Not all nations are contending with you for victory.\nCity-States are nations that can't win, can't be traded with, and instead confer certain bonuses to friendly civilizations. =
- # Requires translation!
-Instead, diplomatic relations with City-States are determined by Influence - a meter of 'how much the City-State likes you'.\nInfluence can be increased by attacking their enemies, liberating their city, and giving them sums of gold. =
- # Requires translation!
-Certain bonuses are given when you are at above 30 influence.\nWhen you have above 60 Influence, and you have the highest influence with them of all civilizations, you are considered their 'Ally', and gain further bonuses and access to the Luxury and Strategic resources in their lands. =
+Not all nations are contending with you for victory.\nCity-States are nations that can't win, can't be traded with, and instead confer certain bonuses to friendly civilizations. = Her ülke seninle kazanmak için yarışmaz.\nŞehir devletleri oyunu kazanamayan ülkelerdir, onlarla ticaret yapılamaz, onun yerine onlara dost olan ülkelere belli avantajlar sağlarlar.
+Instead, diplomatic relations with City-States are determined by Influence - a meter of 'how much the City-State likes you'.\nInfluence can be increased by attacking their enemies, liberating their city, and giving them sums of gold. = Şehir devletlerinde diplomasi yerine 'nüfuz' sistemi mevcuttur - Bir şehir devletinin seni ne kadar sevdiğini gösterir.\nNüfuz artırmak için şehir devletlerinin düşmanlarına saldırabilir, onlara para verebilir veya başka uygarlıkların kontrolü altına geçmişlerse onlara özgürlüklerini geri verebilirsin.
+Certain bonuses are given when you are at above 30 influence.\nWhen you have above 60 Influence, and you have the highest influence with them of all civilizations, you are considered their 'Ally', and gain further bonuses and access to the Luxury and Strategic resources in their lands. = 30 nüfuzun üstüne çıktığın zaman belli bonuslar sağlarlar.\n60 nüfuzun üstünde ise, ki diğer uygarlıkların nüfuzundan da yüksek olman gerekir, Şehir devleti seni 'müttefik'i olarak görür, verdiği bonuslar artar ayrıca ülkelerindeki stratejik ve lüks kaynakları seninle paylaşır.
-Great People = Büyük Şahsiyetler
- # Requires translation!
-Certain buildings, and specialists in cities, generate Great Person points per turn.\nThere are several types of Great People, and their points accumulate separately.\nThe number of points per turn and accumulated points can be viewed in the Overview screen. =
-Once enough points have been accumulated, a Great Person of that type will be created!\nEach Great Person can construct a certain Great Improvement which gives large yields over time, or immediately consumed to provide a certain bonus now. = Yeterince puan birikince puanın türünde bir Büyük Kişi ortaya çıkacaktır!\nHer Büyük Şahsiyet kendine özel bir Büyük Geliştirme inşa edebilir, bu İyileştirme ise zamanla büyük verim sağlar, ancak geliştirmeler inşa etmek yerine seçildiğinde Büyük Şahsiyeti kullanacak bonuslar alabilirsiniz.
-Great Improvements also provide any strategic resources that are under them, so you don't need to worry if resources are revealed underneath your improvements! = Büyük Geliştirmeler üzerinde bulunduğu herhangi bir stratejik kaynağı yinede sağlar, bu sebepten ötürü bir büyük geliştirmenin altında bir kaynak keşfedilirse hala o kaynağı çıkartabilirsiniz!
+Great People = Harika Şahsiyetler
+Certain buildings, and specialists in cities, generate Great Person points per turn.\nThere are several types of Great People, and their points accumulate separately.\nThe number of points per turn and accumulated points can be viewed in the Overview screen. = Bazı binalar ve şehirde çalışan uzmanlar her tur Harika insan puanı üretir.\nBirçok tür harika şahsiyet vardır, her birinin puanı birbirinden ayrı toplanır.\nHer tur elde ettiğin puan sayısı ve birikmiş toplam puan sayısına genel bakış menüsünden erişebilirsin.
+Once enough points have been accumulated, a Great Person of that type will be created!\nEach Great Person can construct a certain Great Improvement which gives large yields over time, or immediately consumed to provide a certain bonus now. = Yeterince puan birikince puanın türünde bir Harika Kişi ortaya çıkacaktır!\nHer Harika Şahsiyet kendine özel bir Harika Geliştirme inşa edebilir, bu İyileştirme ise zamanla büyük verim sağlar, ancak geliştirmeler inşa etmek yerine seçildiğinde Harika Şahsiyeti harcayacak büyük bonuslar alabilirsiniz.
+Great Improvements also provide any strategic resources that are under them, so you don't need to worry if resources are revealed underneath your improvements! = Harika Geliştirmeler üzerinde bulunduğu herhangi bir stratejik kaynağı yinede sağlar, bu sebepten ötürü bir büyük geliştirmenin altında bir kaynak keşfedilirse hala o kaynağı çıkartabilirsiniz!
Removing Terrain Features = Arazi Özelliklerini Kaldırmak
- # Requires translation!
-Certain tiles have terrain features - like Flood plains or Forests - on top of them. Some of these layers, like Jungle, Marsh and Forest, can be removed by workers.\nRemoving the terrain feature does not remove any resources in the tile, and is usually required in order to add improvements exploiting those resources. =
+Certain tiles have terrain features - like Flood plains or Forests - on top of them. Some of these layers, like Jungle, Marsh and Forest, can be removed by workers.\nRemoving the terrain feature does not remove any resources in the tile, and is usually required in order to add improvements exploiting those resources. = Bazı karoların üstünde fazladan bir katman arazi özelliği bulunur - Taşkın ovaları ya da ormanlar gibi. Cengel, orman veya bataklık gibi bu katmanlardan bazıları işçiler tarafından kaldırılabilir.\nBu katmanları kaldırmak karoda bulunan kaynaklara zarar vermez, hatta çoğu zaman kaynakları işlemek için gereken geliştirmeleri yapmak için gereklidir.
- # Requires translation!
-Natural Wonders, such as the Mt. Fuji, the Rock of Gibraltar and the Great Barrier Reef, are unique, impassable terrain features, masterpieces of mother Nature, which possess exceptional qualities that make them very different from the average terrain.\nThey benefit by giving you large sums of Culture, Science, Gold or Production if worked by your Cities, which is why you might need to bring them under your empire as soon as possible. =
+Natural Wonders, such as the Mt. Fuji, the Rock of Gibraltar and the Great Barrier Reef, are unique, impassable terrain features, masterpieces of mother Nature, which possess exceptional qualities that make them very different from the average terrain.\nThey benefit by giving you large sums of Culture, Science, Gold or Production if worked by your Cities, which is why you might need to bring them under your empire as soon as possible. = Fuji Dağı, Cebelitarık Kayası, Büyük set resifi gibi doğal harikalar eşsiz, geçilmez arazi türleridir. Doğa Ananın bu şaheserleri onları özel kılan, normal araziden farklı özelliklere sahiptir.\nŞehrin nüfusu tarafından işlendiğinde uygarlığına yüklü miktarlarda kültür, bilim, altın, inanç veya üretim verirler, bu yüzden onları en kısa sürede topraklarına katmak isteyebilirsin.
Keyboard = Klavye
- # Requires translation!
-If you have a keyboard, some shortcut keys become available. Unit command or improvement picker keys, for example, are shown directly in their corresponding buttons. =
- # Requires translation!
-On the world screen the hotkeys are as follows: =
- # Requires translation!
-Space or 'N' - Next unit or turn\n'E' - Empire overview (last viewed page)\n'+', '-' - Zoom in / out\nHome - center on capital or open its city screen if already centered =
- # Requires translation!
-F1 - Open Civilopedia\nF2 - Empire overview Trades\nF3 - Empire overview Units\nF4 - Empire overview Diplomacy\nF5 - Social policies\nF6 - Technologies\nF7 - Empire overview Cities\nF8 - Victory Progress\nF9 - Empire overview Stats\nF10 - Empire overview Resources\nF11 - Quicksave\nF12 - Quickload =
- # Requires translation!
-Ctrl-R - Toggle tile resource display\nCtrl-Y - Toggle tile yield display\nCtrl-O - Game options\nCtrl-S - Save game\nCtrl-L - Load game\nCtrl-U - Toggle UI (World Screen only) =
-
- # Requires translation!
-This is where you spend most of your time playing Unciv. See the world, control your units, access other screens from here. =
- # Requires translation!
-①: The menu button - civilopedia, save, load, options... =
- # Requires translation!
-②: The player/nation whose turn it is - click for diplomacy overview. =
- # Requires translation!
-③: The Technology Button - shows the tech tree which allows viewing or researching technologies. =
- # Requires translation!
-④: The Social Policies Button - shows enacted and selectable policies, and with enough culture points you can enact new ones. =
- # Requires translation!
-⑤: The Diplomacy Button - shows the diplomacy manager where you can talk to other civilizations. =
- # Requires translation!
-⑥: Unit Action Buttons - while a unit is selected its possible actions appear here. =
- # Requires translation!
-⑦: The unit/city info pane - shows information about a selected unit or city. =
- # Requires translation!
-⑧: The name (and unit icon) of the selected unit or city, with current health if wounded. Clicking a unit name or icon will open its civilopedia entry. =
- # Requires translation!
-⑨: The arrow buttons allow jumping to the next/previous unit. =
- # Requires translation!
-⑩: For a selected unit, its promotions appear here, and clicking leads to the promotions screen for that unit. =
- # Requires translation!
-⑪: Remaining/per turn movement points, strength and experience / XP needed for promotion. For cities, you get its combat strength. =
- # Requires translation!
-⑫: This button closes the selected unit/city info pane. =
- # Requires translation!
-⑬: This pane appears when you order a unit to attack an enemy. On top are attacker and defender with their respective base strengths. =
- # Requires translation!
-⑭: Below that are strength bonuses or penalties and health bars projecting before / after the attack. =
- # Requires translation!
-⑮: The Attack Button - let blood flow! =
- # Requires translation!
-⑯: The minimap shows an overview over the world, with known cities, terrain and fog of war. Clicking will position the main map. =
- # Requires translation!
-⑰: To the side of the minimap are display feature toggling buttons - tile yield, worked indicator, show/hide resources. These mirror setting on the options screen and are hidden if you deactivate the minimap. =
- # Requires translation!
-⑱: Tile information for the selected hex - current or potential yield, terrain, effects, present units, city located there and such. Where appropriate, clicking a line opens the corresponding civilopedia entry. =
- # Requires translation!
-⑲: Notifications - what happened during the last 'next turn' phase. Some are clickable to show a relevant place on the map, some even show several when you click repeatedly. =
- # Requires translation!
-⑳: The Next Turn Button - unless there are things to do, in which case the label changes to 'next unit', 'pick policy' and so on. =
- # Requires translation!
-㉑: The Multiplayer Button - Here you can easily check your active multiplayer games. =
- # Requires translation!
-ⓐ: The overview button leads to the empire overview screen with various tabs (the last one viewed is remembered) holding vital information about the state of your civilization in the world. =
- # Requires translation!
-ⓑ: The ♪Culture icon shows accumulated ♪Culture and ♪Culture needed for the next policy - in this case, the exclamation mark tells us a next policy can be enacted. Clicking is another way to the policies manager. =
- # Requires translation!
-ⓒ: Your known strategic resources are displayed here with the available (usage already deducted) number - click to go to the resources overview screen. =
- # Requires translation!
-ⓓ: Happiness/unhappiness balance and either golden age with turns left or accumulated happiness with amount needed for a golden age is shown next to the smiley. Clicking also leads to the resources overview screen as luxury resources are a way to improve happiness. =
- # Requires translation!
-ⓔ: The ⍾Science icon shows the number of ⍾Science points produced per turn. Clicking leads to the technology tree. =
- # Requires translation!
-ⓕ: Number of turns played with translation into calendar years. Click to see the victory overview. =
- # Requires translation!
-ⓖ: The number of gold coins in your treasury and income. Clicks lead to the Stats overview screen. =
- # Requires translation!
-ⓗ: The quantity of ☮Faith your citizens have generated, or 'off' if religion is disabled. Clicking it makes you go to the religion overview screen. =
- # Requires translation!
-ⓧ: In the center of all this - the world map! Here, the "X" marks a spot outside the map. Yes, unless the wrap option was used, Unciv worlds are flat. Don't worry, your ships won't fall off the edge. =
- # Requires translation!
-ⓨ: By the way, here's how an empire border looks like - it's in the national colours of the nation owning the territory. =
- # Requires translation!
-ⓩ: And this is the red targeting circle that led to the attack pane back under ⑬. =
- # Requires translation!
-What you don't see: The phone/tablet's back button will pop the question whether you wish to leave Unciv and go back to Real Life. On desktop versions, you can use the ESC key. =
-
- # Requires translation!
-After building a shrine, your civilization will start generating ☮Faith. =
- # Requires translation!
-When enough ☮Faith has been generated, you will be able to found a pantheon. =
- # Requires translation!
-A pantheon will provide a small bonus for your civilization that will apply to all cities that have it as a majority religion. =
- # Requires translation!
-Each civilization can only choose a single pantheon belief, and each pantheon can only be chosen once. =
- # Requires translation!
-Generating more ☮Faith will allow you to found a religion. =
-
- # Requires translation!
-Keep generating ☮Faith, and eventually a great prophet will be born in one of your cities. =
- # Requires translation!
-This great prophet can be used for multiple things: Constructing a holy site, founding a religion and spreading your religion. =
- # Requires translation!
-When founding your religion, you may choose another two beliefs. The founder belief will only apply to you, while the follower belief will apply to all cities following your religion. =
- # Requires translation!
-Additionally, the city where you used your great prophet will become the holy city of that religion. =
- # Requires translation!
-Once you have founded a religion, great prophets will keep being born every so often, though the amount of Faith☮ you have to save up will be higher. =
- # Requires translation!
-One of these great prophets can then be used to enhance your religion. =
- # Requires translation!
-This will allow you to choose another follower belief, as well as an enhancer belief, that only applies to you. =
- # Requires translation!
-Do take care founding a religion soon, only about half the players in the game are able to found a religion! =
+If you have a keyboard, some shortcut keys become available. Unit command or improvement picker keys, for example, are shown directly in their corresponding buttons. = Eğer bir klavyen varsa, bazı kısayol tuşları etkin olur. Mesela birimlere komut verme ya da karo geliştirmesi seçme tuşları hemen kendi seçeneklerinin altında gözükür.
+On the world screen the hotkeys are as follows: = Dünya Ekranında kısayol tuşları aşağıdaki gibidir:
+Space or 'N' - Next unit or turn\n'E' - Empire overview (last viewed page)\n'+', '-' - Zoom in / out\nHome - center on capital or open its city screen if already centered = Boşluk ya da 'N' - Sıradaki birim ya da tur\n'E' imparatorluğun genel bakışı\n'+', '-' - yakınlaştırır ve uzaklaştırır\n Home tuşu - kamerayı başkente ortalar, zaten ortalanmışsa başkent şehir ekranını açar.
+F1 - Open Civilopedia\nF2 - Empire overview Trades\nF3 - Empire overview Units\nF4 - Empire overview Diplomacy\nF5 - Social policies\nF6 - Technologies\nF7 - Empire overview Cities\nF8 - Victory Progress\nF9 - Empire overview Stats\nF10 - Empire overview Resources\nF11 - Quicksave\nF12 - Quickload = F1 - civilopedia'yı açar\nF2 - İmparatorluk genel bakışın ticaret menüsünü açar\nF3 - İmparatorluk genel bakışın birimler menüsünü açar\nF4 - İmparatorluk genel bakışın diplomasi menüsünü açar\nF5 - Politika ekranını açar\nF6 - Teknoloji ekranını açar\nF7 - İmparatorluk genel bakışın şehirler menüsünü açar\nF8 - Zafer durumunu açar\nF9 - İmparatorluk genel bakışın istatistikler menüsünü açar\nF10 - İmparatorluk genel bakışın kaynaklar menüsünü açar\nF11 - Hızlı oyun kaydı alır\nF12 - Alınan hızlı oyun kaydını yükler
+Ctrl-R - Toggle tile resource display\nCtrl-Y - Toggle tile yield display\nCtrl-O - Game options\nCtrl-S - Save game\nCtrl-L - Load game\nCtrl-U - Toggle UI (World Screen only) = Ctrl-R - Karo kaynakları göstermeyi aç/kapa\nCtrl-Y - Karo potansiyel üretimini göstermeyi aç/kapa\nCtrl-O - Oyun ayarları\nCtrl-S - Oyunu kaydet\nCtrl-L - Oyunu yükle\nCtrl-U - Arayüzü aç/kapa (sadece dünya ekranı için)
+
+This is where you spend most of your time playing Unciv. See the world, control your units, access other screens from here. = Unciv oynarken vaktinin çoğunluğu bu ekranda geçecek. Burdan dünyayı izleyebilir, birimlerini kontrol edebilir ve diğer ekranlara ulaşabilirsin.
+①: The menu button - civilopedia, save, load, options... = ①: Menü tuşu - civilopedia, oyun kaydetme ve yükleme, ayarlar...
+②: The player/nation whose turn it is - click for diplomacy overview. = ②: Oynama sırası gelen oyuncu/uygarlık - buraya tıklayarak diplomasi ekranını açabilirsin.
+③: The Technology Button - shows the tech tree which allows viewing or researching technologies. = ③: Teknoloji Tuşu - teknoloji ağacını gösterir, teknolojilerine bakmayı ya da yeni teknolojiler araştırmayı sağlar.
+④: The Social Policies Button - shows enacted and selectable policies, and with enough culture points you can enact new ones. = ④: Sosyal Politikalar Tuşu - benimsenmiş ve seçilebilecek politikaları gösterir, yeterince kültür puanıyla yeni politikalar benimseyebilirsin.
+⑤: The Diplomacy Button - shows the diplomacy manager where you can talk to other civilizations. = ⑤: Diplomasi Tuşu - Diplomasi ekranını gösterir, burada diğer uygarlıklarla konuşabilirsin.
+⑥: Unit Action Buttons - while a unit is selected its possible actions appear here. = ⑥: Birim Eylem Tuşları - Bir birim seçiliyken onun yapabileceği eylemler burada gösterilir.
+⑦: The unit/city info pane - shows information about a selected unit or city. = ⑦: Birim/Şehir bilgi panosu - seçilmiş birim veya şehir hakkında bilgi gösterir.
+⑧: The name (and unit icon) of the selected unit or city, with current health if wounded. Clicking a unit name or icon will open its civilopedia entry. = ⑧: Seçilmiş birim veya şehrin ismi, ikonu ve yaralıysa canı burada gözükür. Birimin ismine veya ikonuna basmak o birimin civilopedia sayfasını açar.
+⑨: The arrow buttons allow jumping to the next/previous unit. = ⑨: Ok tuşları bir sonraki/bir önceki birime geçmeyi sağlar.
+⑩: For a selected unit, its promotions appear here, and clicking leads to the promotions screen for that unit. = ⑩: Seçilmiş birimin terfileri burada gözükür, buraya tıklamak sizi birimin terfi ekranına götürür.
+⑪: Remaining/per turn movement points, strength and experience / XP needed for promotion. For cities, you get its combat strength. = ⑪: kalan/tur başına izin verilen hareket puanları, birimin gücü ve bir sonraki terfisine kalan deneyim puanı sayısını gösterir, şehirler için ise de şehrin gücü burada gözükür.
+⑫: This button closes the selected unit/city info pane. = ⑫: Bu tuş seçili birimin/şehrin bilgi panosunu kapatır.
+⑬: This pane appears when you order a unit to attack an enemy. On top are attacker and defender with their respective base strengths. = ⑬: Bu pano bir birime saldırma emri verdiğin zaman gözükür. En üsttekiler ana güç puanlarıyla birlikte saldıran ve savunanlardır.
+⑭: Below that are strength bonuses or penalties and health bars projecting before / after the attack. = ⑭: Onun altında ise gücü etkileyen etkenler yazılıdır ve saldırıdan öncesini / sonrasını gösteren can barı bulunur.
+⑮: The Attack Button - let blood flow! = ⑮: Saldır Tuşu - etrafı kana bula!
+⑯: The minimap shows an overview over the world, with known cities, terrain and fog of war. Clicking will position the main map. = ⑯: Mini harita sana dünyanın uzaktan görünüşünü gösterir ama sadece bilinen şehirler ve araziyi görebilirsin, savaş sisi mevcuttur. Buraya tıklamak ana ekranı tıkladığın yere götürür.
+⑰: To the side of the minimap are display feature toggling buttons - tile yield, worked indicator, show/hide resources. These mirror setting on the options screen and are hidden if you deactivate the minimap. = ⑰: Mini haritanın solunda görüntü ayarları bulunur, bunlar karoların üretimlerini, çalışan nüfusu vb. görünür hale getirmek için kullanılabilir, ayar menüsündekilerle aynıdırlar yani birini değiştirmek diğerini de değiştirir.
+⑱: Tile information for the selected hex - current or potential yield, terrain, effects, present units, city located there and such. Where appropriate, clicking a line opens the corresponding civilopedia entry. = ⑱: Seçili karonun bilgileri - mevcut ya da potansiyel üretim, arazi, etkiler, oradaki birimler, orada bulunan şehir vb. buradaki yazılardan birine tıklamak eğer varsa sizi tıkladığınız şeyin civilopedia sayfasına götürür.
+⑲: Notifications - what happened during the last 'next turn' phase. Some are clickable to show a relevant place on the map, some even show several when you click repeatedly. = ⑲: Bildirimler - 'sonraki tur' tuşuna bastıktan sonra gelişen olaylar burada gözükür. Bazılarına tıkladığın zaman haritada ilgili yeri gösterirler hatta bazıları her tıklayışında değiştirmek kaydıyla birden çok yeri bile göstererbilir.
+⑳: The Next Turn Button - unless there are things to do, in which case the label changes to 'next unit', 'pick policy' and so on. = ⑳: Bir Sonraki Tur Tuşu - yapılacak bir şey kalmadığı zaman gözükür, diğer zamanlarda ise üstünde 'sonraki birim', 'politika seçin' gibi şeyler yazar.
+㉑: The Multiplayer Button - Here you can easily check your active multiplayer games. = ㉑: Çok Oyunculu Tuşu - Buradan etkin çok oyunculu oyunlarına bakabilirsin.
+ⓐ: The overview button leads to the empire overview screen with various tabs (the last one viewed is remembered) holding vital information about the state of your civilization in the world. = ⓐ: Genel Bakış tuşu seni imparatorluğunun genel bakış ekranına götürür, buradan uygarlığınla ilgili en önemli bilgileri detaylı bir şekilde görebilirsin.
+ⓑ: The ♪Culture icon shows accumulated ♪Culture and ♪Culture needed for the next policy - in this case, the exclamation mark tells us a next policy can be enacted. Clicking is another way to the policies manager. = ⓑ: ♪Kültür ikonu her tur biriken ve kaç tur sonra yeni politika seçilecek kadar ♪Kültür birikeceğini gösterir, burada ünlem görürsen yeni politika seçebilirsin demektir. Buraya tıklamak politika ekranına gitmeni sağlar.
+ⓒ: Your known strategic resources are displayed here with the available (usage already deducted) number - click to go to the resources overview screen. = ⓒ:Kullanımda olmayan stratejik kaynakların burada gözükür - tıklayarak kaynaklar menüsüne gidebilirsin.
+ⓓ: Happiness/unhappiness balance and either golden age with turns left or accumulated happiness with amount needed for a golden age is shown next to the smiley. Clicking also leads to the resources overview screen as luxury resources are a way to improve happiness. = ⓓ: mutluluk/mutsuzluk dengesi burada yazılı, ya altın çağ ve yanında bitmesine kaç tur kaldığını görürsün ya da birikmiş mutluluğunu ve altın çağ için kaç mutluluk gerektiğini görürsün. Buraya tıklamak da kaynaklar ekranına götürür çünkü lüks kaynaklar mutluluğu artırır.
+ⓔ: The ⍾Science icon shows the number of ⍾Science points produced per turn. Clicking leads to the technology tree. = ⓔ: ⍾Bilim ikonu her tur üretilen ⍾Bilim miktarını gösterir. Tıklamak teknoloji ekranını açar.
+ⓕ: Number of turns played with translation into calendar years. Click to see the victory overview. = ⓕ: Oyunun kaçıncı turunda olduğunuz ve takvim yıllarında hangi yıla denk geldiğini gösterir. tıklayarak zafer durumuna gidebilirsin.
+ⓖ: The number of gold coins in your treasury and income. Clicks lead to the Stats overview screen. = ⓖ: Hazinendeki ve her tur kazandığın altın miktarını gösterir.
+ⓗ: The quantity of ☮Faith your citizens have generated, or 'off' if religion is disabled. Clicking it makes you go to the religion overview screen. = ⓗ: Vatandaşlarının üretmiş olduğu ☮İnanç miktarı, oyun din olmadan oynanıyorsa onun yerine 'kapalı' yazar. Tıklamak seni din ekranına götürür.
+ⓧ: In the center of all this - the world map! Here, the "X" marks a spot outside the map. Yes, unless the wrap option was used, Unciv worlds are flat. Don't worry, your ships won't fall off the edge. = ⓧ: Ve bu her şeyin ortasındaki ise - Dünya Haritası! Buradaki 'X' haritanın dışındaki bir yerde. Evet, yuvarlak dünya seçilmediği sürece unciv dünyaları düzdür. Endişelenme, gemilerin dünyanın kenarından düşmez.
+ⓨ: By the way, here's how an empire border looks like - it's in the national colours of the nation owning the territory. = ⓨ: Bu arada, imparatorlukların sınırları böyle gözükür - Bölgeye sahip olan ülkenin milli renkleriyle boyanmıs bir sınırla çevrilidir.
+ⓩ: And this is the red targeting circle that led to the attack pane back under ⑬. = ⓩ: Ve bu da ⑬'de gördüğümüz saldırı panelini açmayı sağlayan kırmızı hedef alma çizgisi.
+What you don't see: The phone/tablet's back button will pop the question whether you wish to leave Unciv and go back to Real Life. On desktop versions, you can use the ESC key. = Burada görmediğin bir ayrıntı daha: Telefonun/tabletin geri tuşu sana uncivden çıkıp gerçek hayat dönmek isteyip istemediğini soracak. Bilgisayar sürümlerinde ise esc bu iş için kullanılabilir.
+
+After building a shrine, your civilization will start generating ☮Faith. = Bir türbe inşa ettikten sonra uygarlığın ☮İnanç üretmeye başlayacak.
+When enough ☮Faith has been generated, you will be able to found a pantheon. = Yeterince ☮İnanç üretildiğinde bir Panteon kurabileceksin.
+A pantheon will provide a small bonus for your civilization that will apply to all cities that have it as a majority religion. = Panteon, şehirlerinde çoğunluğun takip ettiği din olursa o şehirlere küçük avantajlar sağlar.
+Each civilization can only choose a single pantheon belief, and each pantheon can only be chosen once. = Her medeniyet sadece bir Panteon inanışı seçebilir ve her Panteon yalnız bir defa seçilebilir.
+Generating more ☮Faith will allow you to found a religion. = Panteondan sonra da ☮İnanç üretmeye devam edersen bir din kurabilirsin.
+
+Keep generating ☮Faith, and eventually a great prophet will be born in one of your cities. = ☮İnanç üretmeye devam edersen eninde sonunda şehirlerinden birinde bir harika peygamber doğacaktır.
+This great prophet can be used for multiple things: Constructing a holy site, founding a religion and spreading your religion. = Bu harika peygamber birden fazla amaç için kullanılabilir; kutsal bölge inşa etmek, yeni bir din kurmak ve bir dini yaymak.
+When founding your religion, you may choose another two beliefs. The founder belief will only apply to you, while the follower belief will apply to all cities following your religion. = Bir din kurarken, iki yeni inanış seçebilirsin; Kurucu inanışı sadece senin için geçerli olacak, İnanan inanışı ise dinine sahip bütün şehirler için geçerli olacak.
+Additionally, the city where you used your great prophet will become the holy city of that religion. = Ayrıca, harika peygamberini kullandığın şehir yeni kurduğun dinin kutsal şehri olacak.
+Once you have founded a religion, great prophets will keep being born every so often, though the amount of Faith☮ you have to save up will be higher. = Bir din kurduktan sonra, harika peygamberler yine de şehirlerinde doğabilirler, yalnız biriktirmen gereken ☮İnanç miktarı öncekinden daha fazla olacak.
+One of these great prophets can then be used to enhance your religion. = Bu yeni harika peygamberlerden birisi dinini geliştirmek için kullanilabilir.
+This will allow you to choose another follower belief, as well as an enhancer belief, that only applies to you. = Bu senin dinin için bir İnanan inanışı daha ve yanında bir de sadece senin için geçerli olan Değiştiren inanışı seçmen anlamına geliyor.
+Do take care founding a religion soon, only about half the players in the game are able to found a religion! = Erkenden bir din kurmaya özen göster, çünkü oyundaki oyuncuların yaklaşık yarısı din kurabilir.
Beliefs = İnançlar
- # Requires translation!
-There are four types of beliefs: Pantheon, Founder, Follower and Enhancer beliefs. =
- # Requires translation!
-Pantheon and Follower beliefs apply to each city following your religion, while Founder and Enhancer beliefs only apply to the founder of a religion. =
-
- # Requires translation!
-Religion inside cities =
- # Requires translation!
-When founding a city, it won't follow a religion immediately. =
- # Requires translation!
-The religion a city follows depends on the total pressure each religion has within the city. =
- # Requires translation!
-Followers are allocated in the same proportions as these pressures, and these followers can be viewed in the city screen. =
- # Requires translation!
-You are allowed to check religious followers and pressures in cities you do not own by selecting them. =
- # Requires translation!
-In both places, a tap/click on the icon of a religion will show detailed information with its effects. =
- # Requires translation!
-Based on this, you can get a feel for which religions have a lot of pressure built up in the city, and which have almost none. =
- # Requires translation!
-The city follows a religion if a majority of its population follows that religion, and will only then receive the effects of Follower and Pantheon beliefs of that religion. =
-
- # Requires translation!
-Spreading Religion =
- # Requires translation!
-Spreading religion happens naturally, but can be sped up using missionaries or great prophets. =
- # Requires translation!
-Missionaries can be bought in cities following a major religion, and will take the religion of that city. =
- # Requires translation!
-So do take care where you are buying them! If another civilization has converted one of your cities to their religion, missionaries bought there will follow their religion. =
- # Requires translation!
-Great prophets always have your religion when they appear, even if they are bought in cities following other religions, but captured great prophets do retain their original religion. =
- # Requires translation!
-Both great prophets and missionaries are able to spread religion to cities when they are inside its borders, even cities of other civilizations. =
- # Requires translation!
-These two units can even enter tiles of civilizations with whom you don't have an open borders agreement! =
- # Requires translation!
-But do take care, missionaries will lose 250 religious strength each turn they end while in foreign lands. =
- # Requires translation!
-This diminishes their effectiveness when spreading religion, and if their religious strength ever reaches 0, they have lost their faith and disappear. =
- # Requires translation!
-When you do spread your religion, the religious strength of the unit is added as pressure for that religion. =
- # Requires translation!
-Cities also passively add pressure of their majority religion to nearby cities. =
- # Requires translation!
-Each city provides +6 pressure per turn to all cities within 10 tiles, though the exact amount of pressure depends on the game speed. =
- # Requires translation!
-This pressure can also be seen in the city screen, and gives you an idea of how religions in your cities will evolve if you don't do anything. =
- # Requires translation!
-Holy cities also provide +30 pressure of the religion founded there to themselves, making it very difficult to effectively convert a holy city. =
- # Requires translation!
-Lastly, before founding a religion, new cities you settle will start with 200 pressure for your pantheon. =
- # Requires translation!
-This way, all your cities will starting following your pantheon as long as you haven't founded a religion yet. =
-
- # Requires translation!
-Inquisitors =
- # Requires translation!
-Inquisitors are the last religious unit, and their strength is removing other religions. =
- # Requires translation!
-They can remove all other religions from one of your own cities, removing any pressures built up. =
- # Requires translation!
-Great prophets also have this ability, and remove all other religions in the city when spreading their religion. =
- # Requires translation!
-Often this results in the city immediately converting to their religion =
- # Requires translation!
-Additionally, when an inquisitor is stationed in or directly next to a city center, units of other religions cannot spread their faith there, though natural spread is uneffected. =
-
- # Requires translation!
-The Mayan unique ability, 'The Long Count', comes with a side effect: =
- # Requires translation!
-Once active, the game's year display will use mayan notation. =
+There are four types of beliefs: Pantheon, Founder, Follower and Enhancer beliefs. = Dört farklı inanış türü vardır: Panteon, Kurucu, İnanan ve Değiştiren inanışları.
+Pantheon and Follower beliefs apply to each city following your religion, while Founder and Enhancer beliefs only apply to the founder of a religion. = Panteon ve İnanan inanışları o dine inanan bütün şehirler için geçerlidir. Kurucu ve Değiştiren inanışları ise sadece o dini kuran medeniyet için geçerlidir.
+
+Religion inside cities = Şehir içinde dinler
+When founding a city, it won't follow a religion immediately. = Bir şehir kurulduğunda anında bir dine sahip olmaz.
+The religion a city follows depends on the total pressure each religion has within the city. = Bir şehrin sahip olacağı din her bir dinin o şehirde yaptığı toplam baskıya bağldır.
+Followers are allocated in the same proportions as these pressures, and these followers can be viewed in the city screen. = Bu baskıların oranları ile aynı oranda insanlar bu dine sahip olur, buna şehir ekranından bakabilirsin.
+You are allowed to check religious followers and pressures in cities you do not own by selecting them. = Senin olmayan şehirlerde dine sahip insanlara ve dinlerin baskı miktarlarına o şehri seçerek bakabilirsin.
+In both places, a tap/click on the icon of a religion will show detailed information with its effects. = şehrin din ikonuna tıklamak/basmak sana o şehrin dini yapısı hakkında detaylı bilgi verecektir.
+Based on this, you can get a feel for which religions have a lot of pressure built up in the city, and which have almost none. = Bu bilgilere dayanarak hangi dinlerin şehirde çok baskısının olduğunu ve hangi dinlerin neredeyse hiç baskısının olmadığını anlayabilirsin.
+The city follows a religion if a majority of its population follows that religion, and will only then receive the effects of Follower and Pantheon beliefs of that religion. = Bir şehir ancak çoğunluğu tek bir dine inanırsa o dinin Panteon ve İnanan inanışlarının etkilerine sahip olur.
+
+Spreading Religion = Din Yaymak
+Spreading religion happens naturally, but can be sped up using missionaries or great prophets. = Din doğal olarak kendiliğinden yayılır, ama misyoner veya harika peygamber kullanılarak bu işlem hızlandırılabilir.
+Missionaries can be bought in cities following a major religion, and will take the religion of that city. = Misyonerler büyük bir dine sahip şehirlerde satın alınabilir ve o şehrin dinine sahip olurlar.
+So do take care where you are buying them! If another civilization has converted one of your cities to their religion, missionaries bought there will follow their religion. = Bu yüzden onları nerden aldığında dikkat et! Başka bir medeniyet şehrini onların kendi dinine çevirmişse o şehirden alacağın misyoner o dine sahip olur.
+Great prophets always have your religion when they appear, even if they are bought in cities following other religions, but captured great prophets do retain their original religion. = Harika peygamberler ne olursa olsun senin kurduğun dine sahip olacaklar, başka dine sahip şehirlerden alındıkları zaman bile, ama esir alınmış harika peygamberler kendi dinlerini korurlar.
+Both great prophets and missionaries are able to spread religion to cities when they are inside its borders, even cities of other civilizations. = Harika peygamberler ve misyonerler herhangi bir şehrin sınırları içinde olduğu sürece dinlerini yayabilirler, bu başka medeniyetlerin şehirleri için de geçerli.
+These two units can even enter tiles of civilizations with whom you don't have an open borders agreement! = Bu iki birim size sınırlarını açmamış bir medeniyetin karolarına bile girebilirler!
+But do take care, missionaries will lose 250 religious strength each turn they end while in foreign lands. = Ama dikkatli ol, misyonerler yabancı şehirlerde bitirdikleri her tur başına 250 inanç gücü kaybeder.
+This diminishes their effectiveness when spreading religion, and if their religious strength ever reaches 0, they have lost their faith and disappear. = Bu onları din yayma konusunda etkiler ve inanç güçleri 0'a ulaşırsa inançlarını kaybederler ve yok olurlar.
+When you do spread your religion, the religious strength of the unit is added as pressure for that religion. = Dinini yaydığın zaman, yayan birimin inanç gücü yayılan dinin baskısıymış gibi eklenir.
+Cities also passively add pressure of their majority religion to nearby cities. = Şehirler de pasif olarak kendi coğunluk dinlerini yakındaki şehirlere baskı olarak ekler.
+Each city provides +6 pressure per turn to all cities within 10 tiles, though the exact amount of pressure depends on the game speed. = Her şehir, 10 karo uzaklığa kadar olan bütün şehirlere +6 baskı ekler, yalnız bu baskı miktarı oyunun hızına göre değişebilir.
+This pressure can also be seen in the city screen, and gives you an idea of how religions in your cities will evolve if you don't do anything. = Bu baskı şehir ekranından görülebilir ve hiçbir şey yapmaman durumunda şehrinin dini yapısının nasıl gelişeceği hakkında bir fikir verebilir.
+Holy cities also provide +30 pressure of the religion founded there to themselves, making it very difficult to effectively convert a holy city. = Kutsal şehirler orada kurulmuş olan dine sadece kendileri için +30 baskı eklerler, bu da kutsal şehirleri etkili bir biçimde diğer dinlere çevirmeyi çok zorlaştırır.
+Lastly, before founding a religion, new cities you settle will start with 200 pressure for your pantheon. = Son olarak, bir din kurmadan önce kurduğun yeni şehirler senin panteonuna 200 baskı ile başlarlar.
+This way, all your cities will starting following your pantheon as long as you haven't founded a religion yet. = Bu şekilde, daha din kurmadıysan yeni kurulan bütün şehirlerin panteonuna inanacaktır.
+
+Inquisitors = Engizisyon üyesi
+Inquisitors are the last religious unit, and their strength is removing other religions. = Engizisyon üyeleri son dini birim ve güçleri diğer dinleri kaldırmak.
+They can remove all other religions from one of your own cities, removing any pressures built up. = Kendi şehirlerinde diğer bütün dinleri kökten kaldırabilir, ayrıca oluşmuş baskıları da kaldırır.
+Great prophets also have this ability, and remove all other religions in the city when spreading their religion. = Harika Peygamberler de bu özelliğe sahip, kendi dinlerini yayarken yaydıkları şehirdeki diğer bütün dinleri kaldırırlar.
+Often this results in the city immediately converting to their religion = Bu çoğunlukla şehrin komple yayılan dine çevrilmesine sebep olur.
+Additionally, when an inquisitor is stationed in or directly next to a city center, units of other religions cannot spread their faith there, though natural spread is uneffected. = Son olarak, bir engizisyon üyesi şehir merkezinde veya bitişiğinde duruyorsa diğer dinlerin birimleri orada kendi dinlerini yayamaz, ama doğal din yayılımına bir etkisi yoktur.
+
+The Mayan unique ability, 'The Long Count', comes with a side effect: = Maya medeniyetinin eşsiz özelliği 'Uzun Sayım', bir yan etkiyle beraber geliyor
+Once active, the game's year display will use mayan notation. = Etkinleştiği zaman, oyundaki yıl Maya takvimine göre güsterilir.
# Requires translation!
The Maya measured time in days from what we would call 11th of August, 3114 BCE. A day is called K'in, 20 days are a Winal, 18 Winals are a Tun, 20 Tuns are a K'atun, 20 K'atuns are a B'ak'tun, 20 B'ak'tuns a Piktun, and so on. =
# Requires translation!
Unciv only displays ය B'ak'tuns, ඹ K'atuns and ම Tuns (from left to right) since that is enough to approximate gregorian calendar years. The Maya numerals are pretty obvious to understand. Have fun deciphering them! =
- # Requires translation!
-Your cities will periodically demand different luxury goods to satisfy their desire for new things in life. =
- # Requires translation!
-If you manage to acquire the demanded luxury by trade, expansion, or conquest, the city will celebrate We Love The King Day for 20 turns. =
- # Requires translation!
-During the We Love The King Day, the city will grow 25% faster. =
- # Requires translation!
-This means exploration and trade is important to grow your cities! =
-
- # Requires translation!
-Air Sweeps =
- # Requires translation!
-Fighter units are able to perform Air Sweeps over a tile helping clear out potential enemy Air, Sea, or Land Interceptions that can reach that tile. =
- # Requires translation!
-While this Action will take an Attack, the benefit is drawing out Interceptions to help protect your other Air Units. Especially your Bombers. =
- # Requires translation!
-Your unit will always draw an Interception, if one can reach the target tile, even if the Intercepting unit has a chance to miss. =
- # Requires translation!
-If the Interceptor is not an Air Unit (eg Land or Sea), the Air Sweeping unit and Interceptor take no damage! =
- # Requires translation!
-If the Interceptor is an Air Unit, the two units will damage each other in a straight fight with no Interception bonuses. And only the Attacking Air Sweep Unit gets any Air Sweep strength bonuses. =
-
- # Requires translation!
-City Tile Blockade =
- # Requires translation!
-One of your tiles is blocked by an enemy: when an enemy unit stands on a tile you own, the tile will not produce yields and cannot be worked by a city this turn. City will reallocate population from a blocked tile automatically. =
- # Requires translation!
-Enemy military land units block tiles they are standing on. Enemy military naval units additionally block adjacent water tiles. To protect your tiles from blockade, place a friendly military unit on it or fight off invaders. =
-
- # Requires translation!
-City Blockade =
- # Requires translation!
-One of your cities is under a naval blockade! When all adjacent water tiles of a coastal city are blocked - city loses harbor connection to all other cities, including capital. Make sure to de-blockade cities by deploying friendly military naval units to fight off invaders. =
-
- # Requires translation!
-Keyboard Bindings =
- # Requires translation!
-Limitations =
- # Requires translation!
-This is a work in progress. =
- # Requires translation!
-For technical reasons, only direct keys or Ctrl-Letter combinations can be used. =
- # Requires translation!
-The Escape key is intentionally excluded from being reassigned. =
- # Requires translation!
-Using the Keys page =
- # Requires translation!
-Each binding has a button with an image looking like this: =
- # Requires translation!
-While hovering the mouse over the key button, you can press a desired key directly to assign it. =
- # Requires translation!
-Double-click the image to reset the binding to default. =
- # Requires translation!
-Bindings mapped to their default keys are displayed in gray, those reassigned by you in white. =
- # Requires translation!
-Conflicting assignments are marked red. Conflicts can exist across categories, like World Screen / Unit Actions. Note that at the moment, the game does not prevent saving conflicting assignments, though the result may be unexpected. =
- # Requires translation!
-For discussion about missing entries, see the linked github issue. =
-
- # Requires translation!
-Welcome to the Civilopedia! =
- # Requires translation!
-Here you can find information - general help, rules, and what makes up the game world. =
- # Requires translation!
-How to find information =
- # Requires translation!
-Select categories with the buttons on top of the screen. Also up there is the button to leave Civilopedia and go back to where you were before. =
- # Requires translation!
-Each category has a list of entries on the left of the screen, sorted alphabetically (with few exceptions). Clicking an entry will update the center pane were you are currently reading this. =
- # Requires translation!
-Lines can link to other Civilopedia entries, they are marked with a chain link symbol like this one. You can click anywhere on the line to follow the link. =
- # Requires translation!
-The current category is special - all articles on general concepts are here. It is called 'Tutorials' because you can revisit these here, too. =
- # Requires translation!
-What information can I find =
- # Requires translation!
-The data shown is not dependent on your current game's situation, e.g. bonuses for the nation you are playing or difficulty modifiers will not affect the numbers. =
- # Requires translation!
-However, it will reflect the mods you are playing! The combination of base ruleset and extension mods you select define the rules of a game, what objects exist and how they interact, and the Civilopedia mirrors these rules. =
- # Requires translation!
-If you opened the Civilopedia from the main menu, the "Ruleset" will be that of the last game you started. =
- # Requires translation!
-Letters can select categories, and when there are multiple categories matching the same letter, you can press that repeatedly to cycle between these. =
- # Requires translation!
-The arrow keys allow navigation as well - left/right for categories, up/down for entries. =
+Your cities will periodically demand different luxury goods to satisfy their desire for new things in life. = Şehirlerinin arada senden lüks istekleri olacak.
+If you manage to acquire the demanded luxury by trade, expansion, or conquest, the city will celebrate We Love The King Day for 20 turns. = Eğer istenilen lüksü takas, genişleme ya da savaşma yoluyla elde edersen, şehir 20 tur boyunca Çok Yaşa Kral Gününü kutlar.
+During the We Love The King Day, the city will grow 25% faster. = Çok Yaşa Kral Günü boyunca şehir %25 daha hızlı büyür.
+This means exploration and trade is important to grow your cities! = Bu da keşif ve takas yapmanın şehirlerini büyütmek için önemli olduğu anlamında gelir!
+
+Air Sweeps = Hava taramaları
+Fighter units are able to perform Air Sweeps over a tile helping clear out potential enemy Air, Sea, or Land Interceptions that can reach that tile. = Savaş uçakları bir karoya hava taraması yapabilir, bu taramalar o karoya yetişen önleyici birimleri hava, kara, su farketmeksizin temizlemek içindir.
+While this Action will take an Attack, the benefit is drawing out Interceptions to help protect your other Air Units. Especially your Bombers. = Bu eylem saldırı yerine geçse de amacı; diğer hava birimleri için önleyici birimlerin saldırılarını harcatmaktır, özellikle bombardıman uçakların için.
+Your unit will always draw an Interception, if one can reach the target tile, even if the Intercepting unit has a chance to miss. = Yolladığın uçak önleyici birimin ıskalama şansı olsa bile her zaman önleyici birimleri kendine çeker (tabiki önleyici birimler o karoya yetişebiliyorsa).
+If the Interceptor is not an Air Unit (eg Land or Sea), the Air Sweeping unit and Interceptor take no damage! = Önleyici birim bir hava birimi değilse (mesela kara veya su birimi), hem hava taraması yapan hem de önleyen birim hasar almaz!
+If the Interceptor is an Air Unit, the two units will damage each other in a straight fight with no Interception bonuses. And only the Attacking Air Sweep Unit gets any Air Sweep strength bonuses. = Önleyici birim eğer bir hava birimiyse, önleme terfi bonusları sayılmadan ikisi birbiriyle savaşır. Sadece hava taramasını yapan birim hava taraması terfi bonusundan faydalanır.
+
+City Tile Blockade = Şehir Karo Engellemesi
+One of your tiles is blocked by an enemy: when an enemy unit stands on a tile you own, the tile will not produce yields and cannot be worked by a city this turn. City will reallocate population from a blocked tile automatically. = Karolarından biri düşman tarafından engellenmiş: Bir düşman birim senin karon üstünde duruyorsa, o karo hiçbir şey üretmez ve kimse o karoda çalışamaz. Şehir o karodaki nüfusu otomatik olarak başka bir karoya atayacaktır.
+Enemy military land units block tiles they are standing on. Enemy military naval units additionally block adjacent water tiles. To protect your tiles from blockade, place a friendly military unit on it or fight off invaders. = Düşman birimler üstünde durdukları karoyu, düşman donanması hem üstünde durduğu karoyu hem de etrafını engeller. Karolarını engellenmekten korumak için üstüne sana ait olan bir askeri birim yerleştirebilir, ya da düşman birimiyle savaşabilirsin.
+
+City Blockade = Şehir Engellemesi
+One of your cities is under a naval blockade! When all adjacent water tiles of a coastal city are blocked - city loses harbor connection to all other cities, including capital. Make sure to de-blockade cities by deploying friendly military naval units to fight off invaders. = Şehirlerinden birisi deniz yoluyla engellenmiş! Şehrin bitişiğindeki bütün karolar engellendiği zaman - şehir diğer bütün şehirlere olan deniz yolu bağlantısını kaybeder, başkent dahil. Şehrin deniz yolunu geri açmak için düşman gemilerinin şehrinin yakınlarında durmasına izin verme, bunun için askeri gemi birimlerini kullan.
+
+Keyboard Bindings = Klavye Atamaları
+Limitations = Sınırlamalar
+This is a work in progress. = Bu daha yapım aşamasındadır.
+For technical reasons, only direct keys or Ctrl-Letter combinations can be used. = Teknik sebeplerden dolayı sadece direkt tuşlar ya da ctrl+harf kombinasyonları kullanılabilir.
+The Escape key is intentionally excluded from being reassigned. = Esc tuşu bilinçli olarak yeniden atamadan mahrum tutulmuştur.
+Using the Keys page = Tuşlar Sekmesini Kullanmak
+Each binding has a button with an image looking like this: = Her tuş atamasının şöyle gözüken bir butonu var:
+While hovering the mouse over the key button, you can press a desired key directly to assign it. = Fareyi herhangi bir tuşun butonu üstünde gezdirirken, istediğin bir tuşa doğrudan basıp onu atayabilirsin.
+Double-click the image to reset the binding to default. = Resme çift tıklayarak atamayı varsayılana çevirebilirsin.
+Bindings mapped to their default keys are displayed in gray, those reassigned by you in white. = Kendi varsayılan tuşlarına atanmış aksiyonlar gri ile gösterilir, senin kendin atadıkların ise beyazla.
+Conflicting assignments are marked red. Conflicts can exist across categories, like World Screen / Unit Actions. Note that at the moment, the game does not prevent saving conflicting assignments, though the result may be unexpected. = Birbiriyle çakışan atamalar kırmızı ile gösterilir, farklı kategorideki atamalar bu konuda birbirini etkileyebilir dünya ekranı/birim eylemleri gibi, oyun çakışan atamaları kaydetmene izin verir ancak sonuçları beklenmedik olabilir.
+For discussion about missing entries, see the linked github issue. = Eksik olan sayfalar hakkında konuşmak için linke tıklayıp github sorununa bakınız.
+
+Welcome to the Civilopedia! = Civilopedia'ya hoş geldin!
+Here you can find information - general help, rules, and what makes up the game world. = Burada her tür bilgi, genel yardım, kurallar ve oyun dünyasıyla ilgili birçok şeyi bulabilirsin.
+How to find information = Bilgileri nasıl bulurum
+Select categories with the buttons on top of the screen. Also up there is the button to leave Civilopedia and go back to where you were before. = Yukarıdaki tuşlarla istediğin kategoriyi seçebilirsin, Yine yukarıda civilopedia'dan çıkıp eski ekranına dönmek için bir çıkma tuşu da bulunuyor.
+Each category has a list of entries on the left of the screen, sorted alphabetically (with few exceptions). Clicking an entry will update the center pane were you are currently reading this. = Bir kategoriyi seçtiğinde o kategoride bulunan sayfalar solda alfabetik sıraya göre dizilecektir (istisnalar mevcut). Sayfalardan birine tıklamak o sayfayı orta panelde açacaktır (orta panel şu an bu yazıyı okuduğun yer).
+Lines can link to other Civilopedia entries, they are marked with a chain link symbol like this one. You can click anywhere on the line to follow the link. = Yazılar seni başka civilopedia sayfalarına götürebilir, bu yazıların kenarında bir zincir işareti bulunur. Seni götürecekleri sayfaya gitmen için zincirli yazının herhangi bir yerine tıklaman yeterli.
+The current category is special - all articles on general concepts are here. It is called 'Tutorials' because you can revisit these here, too. = Şu anki kategori özel - oyunun genel konseptleriyle ilgili bütün yazılar burada. İsmi 'öğreticiler' diye geçiyor çünkü oyundaki öğreticileri kaçırman veya unutman durumunda burada mevcutlar.
+What information can I find = Ne tür bilgiler bulabilirim
+The data shown is not dependent on your current game's situation, e.g. bonuses for the nation you are playing or difficulty modifiers will not affect the numbers. = Buradaki bilgiler oyundan oyuna değişmez, oyunun zorluk seviyesi veya seçtiğin medeniyetin bonusları buradaki sayılara etki etmez.
+However, it will reflect the mods you are playing! The combination of base ruleset and extension mods you select define the rules of a game, what objects exist and how they interact, and the Civilopedia mirrors these rules. = Ama oyunundaki modları yansıtır! Seçtiğin ana kurallar ve eklenti modları oyunun ana kurallarını, oyunda neler bulunduğunu ve bunların birbiriyle nasıl etkileştiğini değiştireceği için civilopedia bunları içinde barındıracak şekilde değişecektir.
+If you opened the Civilopedia from the main menu, the "Ruleset" will be that of the last game you started. = Eğer civilopedia'yı ana menüden açtıysan, 'kural seti' başlattığın son oyunun kural seti olarak belirlenir.
+Letters can select categories, and when there are multiple categories matching the same letter, you can press that repeatedly to cycle between these. = Harf tuşlarına basmak o harfle başlayan belirli bir kategoriyi seçer, birden fazla kategori aynı harfle başlıyorsa aynı harf tuşuna tekrar basmak aynı harfle başlayan bir sonraki kategoriyi seçecektir.
+The arrow keys allow navigation as well - left/right for categories, up/down for entries. = Yön tuşları da seçim konusunda yardımcı olurlar - sağ/sol kategori seçiminde, aşağı/yukarı sayfa seçiminde kullanılabilir.
diff --git a/android/assets/jsons/translations/Ukrainian.properties b/android/assets/jsons/translations/Ukrainian.properties
index 1ca83dd70eb18..5ff4e1c4e7fa2 100644
--- a/android/assets/jsons/translations/Ukrainian.properties
+++ b/android/assets/jsons/translations/Ukrainian.properties
@@ -757,6 +757,9 @@ Reset = Скинути
Show zoom buttons in world screen = Показувати кнопки наближення на екрані світу
Experimental Demographics scoreboard = Експериментальний Демографічний рейтинг
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
Visual Hints = Візуальні підказки
@@ -941,11 +944,20 @@ Your city [cityName] can bombard the enemy! = Ваше місто [cityName] м
[amount] of your cities can bombard the enemy! = [amount] ваших міст(а) можуть обстріляти ворога!
[amount] enemy units were spotted near our territory = Біля нашої території помічено ворожих підрозділів: [amount]
[amount] enemy units were spotted in our territory = На нашій території помічено ворожих підрозділів: [amount]
-A(n) [nukeType] exploded in our territory! = [nukeType] вибухнула на нашій території!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Потрапивши під удар нашої [nukeType], нація [civName] оголосила нам війну!
The civilization of [civName] has been destroyed! = Цивілізацію [civName] було знищено!
The City-State of [name] has been destroyed! = Місто-держава [name] було зруйновано!
Your [ourUnit] captured an enemy [theirUnit]! = Ваш підрозділ [ourUnit] захопив ворожий підрозділ [theirUnit]!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = Ваш підрозділ [ourUnit] награбував [amount] [Stat] з [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Ми захопили Варварський табір і отримали [goldAmount] ¤Золота!
An enemy [unitType] has joined us! = Ворожий підрозділ [unitType] приєднався до нас!
@@ -2045,7 +2057,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = Релігія природно поширюється у міста на відстані [amount] клітинок
May not generate great prophet equivalents naturally = Видатні пророки та їх аналоги не з'являються самі собою
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% ☮Віри за створення аналогів Видатного пророка
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = Підрозділи [baseUnitFilter] збудовані [cityFilter] можуть [action] [amount] додаткових разів
Starting tech = Початкова технологія
Starts with [tech] = Розпочинає гру з технологією [tech]
Starts with [policy] adopted = Починається, коли прийнято [policy]
@@ -2088,16 +2099,19 @@ Automatically built in all cities where it is buildable = Автоматично
Creates a [improvementName] improvement on a specific tile = Створює покращення [improvementName] на певній клітинці
Founds a new city = Може заснувати нове місто
Can instantly construct a [improvementFilter] improvement = Може миттєво створити покращення: [improvementFilter]
-Can build [improvementFilter/terrainFilter] improvements on tiles = Може будувати на клітинках покращення: [improvementFilter/terrainFilter]
-May create improvements on water resources = Може створювати покращення на водних ресурсах
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Може заснувати релігію
May enhance a religion = Може покращити релігію
+Can build [improvementFilter/terrainFilter] improvements on tiles = Може будувати на клітинках покращення: [improvementFilter/terrainFilter]
+May create improvements on water resources = Може створювати покращення на водних ресурсах
Can be added to [comment] in the Capital = Може бути додано до [comment] у столиці
Prevents spreading of religion to the city it is next to = Запобігає поширенню інших релігій у місті, поряд із яким знаходиться.
Removes other religions when spreading religion = Прибирає інші релігії під час поширення власної
May Paradrop up to [amount] tiles from inside friendly territory = Може десантуватись на [amount] клітинок із дружньої території
Can perform Air Sweep = Може виконувати повітряну зачистку
-Can [action] [amount] times = Може [action] [amount] рази.
Can speed up construction of a building = Може прискорити будівництво споруди.
Can speed up the construction of a wonder = Може прискорити будівництво дива
Can hurry technology research = Може пришвидшити дослідження технології
@@ -2480,6 +2494,8 @@ Policy = Політика
FounderBelief = Засновник вірування
FollowerBelief = Послідовник вірування
Building = Споруда
+ # Requires translation!
+UnitAction =
Unit = Підрозділ
UnitType = Тип підрозділу
Promotion = Підвищення
@@ -5226,16 +5242,10 @@ Great Merchant = Видатний торговець
Great Engineer = Видатний інженер
-Great Prophet = Видатний пророк
-
Great General = Видатний генерал
Khan = Хан
-Missionary = Місіонер
-
-Inquisitor = Інквізитор
-
SS Booster = Ракетний прискорювач
SS Cockpit = Кабіна екіпажу
@@ -5499,6 +5509,7 @@ Zhao = Жао
Wu = Ву
Zhou = Жоу
Sun = Сан
+Taoism = Таоізм
Refaat = Рефаат
Heba = Геба
@@ -6223,8 +6234,6 @@ Judaism = Іудаїзм
Sikhism = Сикхізм
-Taoism = Таоізм
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6307,6 +6316,10 @@ Truffles = Трюфелі
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Гусари
@@ -6373,6 +6386,14 @@ Machine Gun = Кулемет
Landship = Танкетка
+Great Prophet = Видатний пророк
+
+
+Missionary = Місіонер
+
+Inquisitor = Інквізитор
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/Vietnamese.properties b/android/assets/jsons/translations/Vietnamese.properties
index b29ea2ce654c9..2d3c2a62aa9ba 100644
--- a/android/assets/jsons/translations/Vietnamese.properties
+++ b/android/assets/jsons/translations/Vietnamese.properties
@@ -837,6 +837,9 @@ Show zoom buttons in world screen = Hiển thị các nút thu phóng trên màn
# Requires translation!
Experimental Demographics scoreboard =
+ # Requires translation!
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
# Requires translation!
@@ -1042,11 +1045,20 @@ Your city [cityName] can bombard the enemy! = Thành phố [cityName] của bạ
[amount] of your cities can bombard the enemy! = [amount] các thành phố của bạn có thể bắn phá kẻ thù!
[amount] enemy units were spotted near our territory = [amount] đơn vị địch đã được phát hiện gần địa phận của chúng ta
[amount] enemy units were spotted in our territory = [amount] đơn vị địch đã được phát hiện trong địa phận của chúngn ta
-A(n) [nukeType] exploded in our territory! = Một(n) [nukeType] đã bùng nổ trong lãnh thổ của chúng ta!
+ # Requires translation!
+A(n) [nukeType] from [civName] has exploded in our territory! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by [civName]! =
+ # Requires translation!
+A(n) [nukeType] has been detonated by an unkown civilization! =
+ # Requires translation!
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! = Sau khi bị đánh bởi [nukeType] của chúng tôi, [civName] đã tuyên chiến với chúng tôi!
The civilization of [civName] has been destroyed! = Nền văn minh [civName] đã bị hủy diệt!
The City-State of [name] has been destroyed! = Thành bang [name] đã bị hủy diệt!
Your [ourUnit] captured an enemy [theirUnit]! = [ourUnit] của bạn đã bắt được một kẻ thù [theirUnit]!
+ # Requires translation!
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] = [ourUnit] của bạn đã cướp [amount] [Stat] từ [theirUnit]
We have captured a barbarian encampment and recovered [goldAmount] gold! = Ta đã tiêu diệt trại người man rợn và nhận được [goldAmount] vàng!
An enemy [unitType] has joined us! = Kẻ thù [unitType] đã tham gia cùng chúng ta!
@@ -2227,7 +2239,6 @@ May choose [amount] additional belief(s) of any type when [foundingOrEnhancing]
Religion naturally spreads to cities [amount] tiles away = Tôn giáo tự nhiên lan rộng đến các thành phố [amount] ô đi
May not generate great prophet equivalents naturally = Có thể không tạo ra những nhà tiên tri vĩ đại tương đương một cách tự nhiên
[relativeAmount]% Faith cost of generating Great Prophet equivalents = [relativeAmount]% Niềm tin chi phí để tạo ra các vật phẩm tương đương với Great Prophet
-[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times = Các đơn vị [baseUnitFilter] được xây dựng [cityFilter] có thể [action] thêm [amount] lần
Starting tech = Bắt đầu công nghệ
Starts with [tech] = Bắt đầu với [tech]
Starts with [policy] adopted = Bắt đầu với [policy] được thông qua
@@ -2273,16 +2284,19 @@ Creates a [improvementName] improvement on a specific tile = Tạo một cải t
Founds a new city = Lập thành phố mới
# Requires translation!
Can instantly construct a [improvementFilter] improvement =
-Can build [improvementFilter/terrainFilter] improvements on tiles = Có thể xây dựng các cải tiến [improvementFilter/terrainFilter] trên các ô xếp
-May create improvements on water resources = Có thể cải thiện nguồn nước
+ # Requires translation!
+Can Spread Religion =
+ # Requires translation!
+Can remove other religions from cities =
May found a religion = Có thể tìm thấy một tôn giáo
May enhance a religion = Có thể nâng cao một tôn giáo
+Can build [improvementFilter/terrainFilter] improvements on tiles = Có thể xây dựng các cải tiến [improvementFilter/terrainFilter] trên các ô xếp
+May create improvements on water resources = Có thể cải thiện nguồn nước
Can be added to [comment] in the Capital = Có thể được thêm vào [comment] ở Capital
Prevents spreading of religion to the city it is next to = Ngăn cản sự lan truyền của tôn giáo đến thành phố bên cạnh
Removes other religions when spreading religion = Loại bỏ các tôn giáo khác khi truyền bá tôn giáo
May Paradrop up to [amount] tiles from inside friendly territory = Có thể Trình diễn lên đến [amount] ô từ bên trong lãnh thổ thân thiện
Can perform Air Sweep = Có thể thực hiện Air Sweep
-Can [action] [amount] times = Có thể [action] [amount] lần
Can speed up construction of a building = Có thể tăng tốc độ xây dựng một tòa nhà
Can speed up the construction of a wonder = Có thể tăng tốc độ xây dựng một kỳ quan
Can hurry technology research = Có thể gấp rút nghiên cứu công nghệ
@@ -2712,6 +2726,8 @@ Policy = Chính sách
FounderBelief = TínngưỡngNgườisánglập
FollowerBelief = TínngưỡngTínđồ
Building = Tòa nhà
+ # Requires translation!
+UnitAction =
Unit = Đơn vị
UnitType = LoạiĐơnvị
Promotion = Khuyến mại
@@ -5512,16 +5528,10 @@ Great Merchant = Thương gia vĩ đại
Great Engineer = Kỹ sư vĩ đại
-Great Prophet = Nhà tiên tri vĩ đại
-
Great General = Đại tướng quân
Khan = Khan
-Missionary = Người truyền giáo
-
-Inquisitor = Người tìm hiểu
-
SS Booster = SS Booster
SS Cockpit = Buồng lái SS
@@ -5785,6 +5795,7 @@ Zhao = Zhao
Wu = Wu
Zhou = Zhou
Sun = Mặt trời
+Taoism = Đạo giáo
Refaat = Đặt lại
Heba = Heba
@@ -6509,8 +6520,6 @@ Judaism = Do Thái giáo
Sikhism = Đạo Sikh
-Taoism = Đạo giáo
-
#################### Lines from Ruins from Civ V - Gods & Kings ####################
@@ -6593,6 +6602,10 @@ Truffles = Nấm cục
#################### Lines from UnitPromotions from Civ V - Gods & Kings ####################
+ # Requires translation!
+Devout =
+
+
Hussar = Khinh kỵ binh
@@ -6659,6 +6672,14 @@ Machine Gun = Súng máy
Landship = Tàu đổ bộ
+Great Prophet = Nhà tiên tri vĩ đại
+
+
+Missionary = Người truyền giáo
+
+Inquisitor = Người tìm hiểu
+
+
#################### Lines from VictoryTypes from Civ V - Gods & Kings ####################
diff --git a/android/assets/jsons/translations/completionPercentages.properties b/android/assets/jsons/translations/completionPercentages.properties
index 6dd4fb03114d3..36ea30a896e6e 100644
--- a/android/assets/jsons/translations/completionPercentages.properties
+++ b/android/assets/jsons/translations/completionPercentages.properties
@@ -1,25 +1,25 @@
Persian_(Pinglish-UN) = 28
Italian = 99
-Russian = 99
+Russian = 100
Belarusian = 2
Afrikaans = 7
German = 99
Swedish = 85
-Turkish = 68
+Turkish = 79
Ukrainian = 98
Filipino = 93
-French = 99
+French = 100
Portuguese = 62
-Indonesian = 99
+Indonesian = 98
Catalan = 99
Finnish = 36
Spanish = 99
Malay = 22
-Brazilian_Portuguese = 99
+Brazilian_Portuguese = 100
Traditional_Chinese = 97
-Polish = 99
+Polish = 100
Lithuanian = 92
-Romanian = 71
+Romanian = 70
Simplified_Chinese = 99
Bulgarian = 43
Korean = 97
diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties
index a692c05f5ee13..6ff8b02ae1fe3 100644
--- a/android/assets/jsons/translations/template.properties
+++ b/android/assets/jsons/translations/template.properties
@@ -748,6 +748,8 @@ Reset =
Show zoom buttons in world screen =
Experimental Demographics scoreboard =
+Size of Unitset art in Civilopedia =
+
### Visual Hints subgroup
Visual Hints =
@@ -929,11 +931,15 @@ Your city [cityName] can bombard the enemy! =
[amount] of your cities can bombard the enemy! =
[amount] enemy units were spotted near our territory =
[amount] enemy units were spotted in our territory =
-A(n) [nukeType] exploded in our territory! =
+A(n) [nukeType] from [civName] has exploded in our territory! =
+A(n) [nukeType] has been detonated by [civName]! =
+A(n) [nukeType] has been detonated by an unkown civilization! =
+After an attempted attack by our [nukeType], [civName] has declared war on us! =
After being hit by our [nukeType], [civName] has declared war on us! =
The civilization of [civName] has been destroyed! =
The City-State of [name] has been destroyed! =
Your [ourUnit] captured an enemy [theirUnit]! =
+Your captured [unitName] has been returned by [civName] =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] =
We have captured a barbarian encampment and recovered [goldAmount] gold! =
An enemy [unitType] has joined us! =
diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt
index 38c6e586f1d3b..37a9a9955c3d9 100644
--- a/buildSrc/src/main/kotlin/BuildConfig.kt
+++ b/buildSrc/src/main/kotlin/BuildConfig.kt
@@ -4,8 +4,8 @@ package com.unciv.build
object BuildConfig {
const val kotlinVersion = "1.8.21"
const val appName = "Unciv"
- const val appCodeNumber = 914
- const val appVersion = "4.8.6"
+ const val appCodeNumber = 921
+ const val appVersion = "4.8.11"
const val gdxVersion = "1.11.0"
const val ktorVersion = "2.2.3"
diff --git a/changelog.md b/changelog.md
index 6504e2b23595f..84bcefed9c6d7 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,84 @@
+## 4.8.11
+
+Religion fixes:
+- Great Prophets spawn again
+- Civilian units can get promotions upon being built (Great Mosque of Djenne)
+- Missionaries consumed upon expending all usages
+
+By SomeTroglodyte:
+- Fade in/out for City Ambiance Sounds
+- Fix Tutorial loader for mods on Android
+- Fix ai tile purchase
+
+By tuvus:
+- Defensive pact button shows on both sides when a DoF is about to end
+- Defensive pact functionality is now canceled with otherCiv before calling in defensive pact allies
+
+## 4.8.10
+
+Performance enhancement for first turn AI settling
+
+Modding:
+- Added UnitAction unique type for modder clarity and ruleset validation
+- Converted "May enhance a religion" , "May found a religion" uniques to UnitAction
+
+Golden age points decrease with negative happiness - By Framonti
+
+City-States don't trigger defensive pacts - By tuvus
+
+By SomeTroglodyte:
+- City overview top bar fix
+- Fixed crashes in Android for unit art in civilopedia
+
+Fixed free building errors - By SeventhM
+
+## 4.8.9
+
+New online multiplayer no longer stuck when first player is human spectator
+
+Modding:
+- Replaced old religion style actions! Paves the way for unit action generalization
+- Mod checker displays *all* unknown uniques
+
+By SomeTroglodyte:
+- Pedia pixel units
+- Fix top bar layout
+- City overview restore fixed header
+
+Apply conditionals for free buildings to the destination city instead of the originating city - By SeventhM
+
+Test city conquest functions - By Framonti
+
+## 4.8.8
+
+performance:
+- Faster ruleset validation
+- Faster ruleset loading
+
+modding: Added json schemas for autocomplete and error detection
+
+By tuvus:
+- AI Open Borders Offer fix
+- Fix Nuke Notification
+
+Fix City construction context menu changing Puppets - By SomeTroglodyte
+
+## 4.8.7
+
+Reload images when downloading or removing a mod
+
+Better mod compatibility autochanges (remove removed units/improvements correctly)
+
+By tuvus:
+- Added 'civ returned worker' notification
+- Liberating civ grants open borders
+
+By SomeTroglodyte:
+- Reorganized World Screen Top Bar in small screens
+- Allow Space Key to close 'Player Ready' screen (hotseat)
+
+Added tests for most nuke functionalities - By Framonti
+
## 4.8.6
Mod checker accepts era for unit type
diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt
index 6772e1a448ff2..add6f3ba079b9 100644
--- a/core/src/com/unciv/UncivGame.kt
+++ b/core/src/com/unciv/UncivGame.kt
@@ -193,7 +193,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
}
ImageGetter.resetAtlases()
- ImageGetter.setNewRuleset(ImageGetter.ruleset) // This needs to come after the settings, since we may have default visual mods
+ ImageGetter.reloadImages() // This needs to come after the settings, since we may have default visual mods
val imageGetterTilesets = ImageGetter.getAvailableTilesets()
val availableTileSets = TileSetCache.getAvailableTilesets(imageGetterTilesets)
if (settings.tileSet !in availableTileSets) { // If the configured tileset is no longer available, default back
@@ -541,7 +541,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
companion object {
//region AUTOMATICALLY GENERATED VERSION DATA - DO NOT CHANGE THIS REGION, INCLUDING THIS COMMENT
- val VERSION = Version("4.8.6", 914)
+ val VERSION = Version("4.8.11", 921)
//endregion
lateinit var Current: UncivGame
diff --git a/core/src/com/unciv/json/UncivJson.kt b/core/src/com/unciv/json/UncivJson.kt
index 8b10e4c42fe47..53a933e782592 100644
--- a/core/src/com/unciv/json/UncivJson.kt
+++ b/core/src/com/unciv/json/UncivJson.kt
@@ -34,12 +34,20 @@ fun json() = Json(JsonWriter.OutputType.json).apply {
}
/**
- * @throws SerializationException
+ * Load a json file by [filePath] from Gdx.files.internal
+ * (meaning from jar/apk for packaged release code, and not appropriate for mod files)
+ * @throws SerializationException
*/
fun Json.fromJsonFile(tClass: Class, filePath: String): T = fromJsonFile(tClass, Gdx.files.internal(filePath))
/**
- * @throws SerializationException
+ * Load a json [file] - by handle, so internal/external/local is caller's decision.
+ *
+ * Reminder:
+ * * `internal` for Unciv-packaged assets, loaded from jar/apk, e.g. Built-in ruleset files.
+ * * `local` for mods and settings - Android will place that under /data/data/com.unciv.app/files.
+ * * `external` for saves - Android will place that under /sdcard/Android/data/com.unciv.app/files.
+ * @throws SerializationException
*/
fun Json.fromJsonFile(tClass: Class, file: FileHandle): T {
try {
diff --git a/core/src/com/unciv/logic/BackwardCompatibility.kt b/core/src/com/unciv/logic/BackwardCompatibility.kt
index 9558d86bc7997..e2de48aacca84 100644
--- a/core/src/com/unciv/logic/BackwardCompatibility.kt
+++ b/core/src/com/unciv/logic/BackwardCompatibility.kt
@@ -39,7 +39,7 @@ object BackwardCompatibility {
private fun GameInfo.removeUnitsAndPromotions() {
for (tile in tileMap.values) {
- for (unit in tile.getUnits()) {
+ for (unit in tile.getUnits().toList()) {
if (!ruleset.units.containsKey(unit.name)) tile.removeUnit(unit)
for (promotion in unit.promotions.promotions.toList())
diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt
index bced47333c3f2..eb0771583d790 100644
--- a/core/src/com/unciv/logic/GameInfo.kt
+++ b/core/src/com/unciv/logic/GameInfo.kt
@@ -615,7 +615,9 @@ class GameInfo (private val overwriteGameId: UUID? = null) : IsPartOfGameInfoSer
tileMap.setTransients(ruleset)
- if (currentPlayer == "") currentPlayer = civilizations.first { it.isHuman() }.civName
+ if (currentPlayer == "") currentPlayer =
+ if (gameParameters.isOnlineMultiplayer) civilizations.first { it.isHuman() && !it.isSpectator() }.civName // For MP, spectator doesn't get a 'turn'
+ else civilizations.first { it.isHuman() }.civName // for non-MP games, you can be a spectator of an AI-only match, and you *do* get a turn, sort of
currentPlayerCiv = getCivilization(currentPlayer)
difficultyObject = ruleset.difficulties[difficulty]!!
diff --git a/core/src/com/unciv/logic/GameStarter.kt b/core/src/com/unciv/logic/GameStarter.kt
index 7e1bfa51fa614..06c17fbcaba88 100644
--- a/core/src/com/unciv/logic/GameStarter.kt
+++ b/core/src/com/unciv/logic/GameStarter.kt
@@ -433,7 +433,7 @@ object GameStarter {
//We may need the starting location for some uniques, which is why we're doing it now
val startingTriggers = (ruleset.globalUniques.uniqueObjects + civ.nation.uniqueObjects)
for (unique in startingTriggers.filter { !it.hasTriggerConditional() })
- if(unique.isTriggerable)
+ if (unique.isTriggerable)
UniqueTriggerActivation.triggerCivwideUnique(unique, civ, tile = startingLocation)
}
}
diff --git a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt
index d0c8af544c78c..c800cc33f76dc 100644
--- a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt
+++ b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt
@@ -276,7 +276,7 @@ object NextTurnAutomation {
if (popupAlert.type == AlertType.DeclarationOfFriendship) {
val requestingCiv = civInfo.gameInfo.getCivilization(popupAlert.value)
val diploManager = civInfo.getDiplomacyManager(requestingCiv)
- if (civInfo.diplomacyFunctions.canSignDeclarationOfFriendshipWith(requestingCiv)
+ if (civInfo.diplomacyFunctions.canSignDeclarationOfFriendshipWith(requestingCiv)
&& wantsToSignDeclarationOfFrienship(civInfo,requestingCiv)) {
diploManager.signDeclarationOfFriendship()
requestingCiv.addNotification("We have signed a Declaration of Friendship with [${civInfo.civName}]!", NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, civInfo.civName)
@@ -284,7 +284,7 @@ object NextTurnAutomation {
diploManager.otherCivDiplomacy().setFlag(DiplomacyFlags.DeclinedDeclarationOfFriendship, 10)
requestingCiv.addNotification("[${civInfo.civName}] has denied our Declaration of Friendship!", NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, civInfo.civName)
}
-
+
}
}
@@ -422,6 +422,7 @@ object NextTurnAutomation {
goldSpent += goldCostOfTile
} else {
ranOutOfMoney = true
+ break
}
}
if (ranOutOfMoney) {
@@ -433,7 +434,7 @@ object NextTurnAutomation {
}
}
- private fun valueCityStateAlliance(civInfo: Civilization, cityState: Civilization): Int {
+ internal fun valueCityStateAlliance(civInfo: Civilization, cityState: Civilization): Int {
var value = 0
if (civInfo.wantsToFocusOn(Victory.Focus.Culture) && cityState.cityStateFunctions.canProvideStat(Stat.Culture)) {
@@ -790,13 +791,13 @@ object NextTurnAutomation {
val diploManager = civInfo.getDiplomacyManager(otherCiv)
// Shortcut, if it is below favorable then don't consider it
if (diploManager.isRelationshipLevelLT(RelationshipLevel.Favorable)) return false
-
+
val numOfFriends = civInfo.diplomacy.count { it.value.hasFlag(DiplomacyFlags.DeclarationOfFriendship) }
val knownCivs = civInfo.getKnownCivs().count { it.isMajorCiv() && it.isAlive() }
val allCivs = civInfo.gameInfo.civilizations.count { it.isMajorCiv() } - 1 // Don't include us
val deadCivs = civInfo.gameInfo.civilizations.count { it.isMajorCiv() && !it.isAlive() }
val allAliveCivs = allCivs - deadCivs
-
+
// Motivation should be constant as the number of civs changes
var motivation = diploManager.opinionOfOtherCiv().toInt() - 40
@@ -808,7 +809,7 @@ object NextTurnAutomation {
ThreatLevel.VeryLow -> -5
else -> 0
}
-
+
// Try to ally with a fourth of the civs in play
val civsToAllyWith = 0.25f * allAliveCivs
if (numOfFriends < civsToAllyWith) {
@@ -828,19 +829,21 @@ object NextTurnAutomation {
// Goes from -30 to 0 when we know 75% of allCivs
val civsToKnow = 0.75f * allAliveCivs
motivation -= ((civsToKnow - knownCivs) / civsToKnow * 30f).toInt().coerceAtLeast(0)
-
+
motivation -= hasAtLeastMotivationToAttack(civInfo, otherCiv, motivation / 2) * 2
-
+
return motivation > 0
}
-
+
private fun offerOpenBorders(civInfo: Civilization) {
- val civsThatWeCanDeclareFriendshipWith = civInfo.getKnownCivs()
- .filter { it.isMajorCiv() && !civInfo.isAtWarWith(it)
+ if (!civInfo.hasUnique(UniqueType.EnablesOpenBorders)) return
+ val civsThatWeCanOpenBordersWith = civInfo.getKnownCivs()
+ .filter { it.isMajorCiv() && !civInfo.isAtWarWith(it)
+ && it.hasUnique(UniqueType.EnablesOpenBorders)
&& !civInfo.getDiplomacyManager(it).hasOpenBorders
&& !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.DeclinedOpenBorders) }
.sortedByDescending { it.getDiplomacyManager(civInfo).relationshipLevel() }.toList()
- for (otherCiv in civsThatWeCanDeclareFriendshipWith) {
+ for (otherCiv in civsThatWeCanOpenBordersWith) {
// Default setting is 3, this will be changed according to different civ.
if ((1..10).random() <= 3 && wantsToOpenBorders(civInfo, otherCiv)) {
val tradeLogic = TradeLogic(civInfo, otherCiv)
@@ -851,17 +854,17 @@ object NextTurnAutomation {
}
}
}
-
+
fun wantsToOpenBorders(civInfo: Civilization, otherCiv: Civilization): Boolean {
if (civInfo.getDiplomacyManager(otherCiv).isRelationshipLevelLT(RelationshipLevel.Favorable)) return false
// Don't accept if they are at war with our friends, they might use our land to attack them
if (civInfo.diplomacy.values.any { it.isRelationshipLevelGE(RelationshipLevel.Friend) && it.otherCiv().isAtWarWith(otherCiv)})
return false
- if (hasAtLeastMotivationToAttack(civInfo, otherCiv, civInfo.getDiplomacyManager(otherCiv).opinionOfOtherCiv().toInt()) > 0)
+ if (hasAtLeastMotivationToAttack(civInfo, otherCiv, (civInfo.getDiplomacyManager(otherCiv).opinionOfOtherCiv()/ 2 - 10).toInt()) >= 0)
return false
return true
}
-
+
private fun offerResearchAgreement(civInfo: Civilization) {
if (!civInfo.diplomacyFunctions.canSignResearchAgreement()) return // don't waste your time
@@ -901,12 +904,12 @@ object NextTurnAutomation {
val tradeLogic = TradeLogic(civInfo, otherCiv)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.defensivePact, TradeType.Treaty))
tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.defensivePact, TradeType.Treaty))
-
+
otherCiv.tradeRequests.add(TradeRequest(civInfo.civName, tradeLogic.currentTrade.reverse()))
}
}
}
-
+
fun wantsToSignDefensivePact(civInfo: Civilization, otherCiv: Civilization): Boolean {
val diploManager = civInfo.getDiplomacyManager(otherCiv)
if (diploManager.isRelationshipLevelLT(RelationshipLevel.Ally)) return false
@@ -924,10 +927,10 @@ object NextTurnAutomation {
val allCivs = civInfo.gameInfo.civilizations.count { it.isMajorCiv() } - 1 // Don't include us
val deadCivs = civInfo.gameInfo.civilizations.count { it.isMajorCiv() && !it.isAlive() }
val allAliveCivs = allCivs - deadCivs
-
+
// We have to already be at RelationshipLevel.Ally, so we must have 80 oppinion of them
var motivation = diploManager.opinionOfOtherCiv().toInt() - 80
-
+
// If they are stronger than us, then we value it a lot more
// If they are weaker than us, then we don't value it
motivation += when (Automation.threatAssessment(civInfo,otherCiv)) {
@@ -937,7 +940,7 @@ object NextTurnAutomation {
ThreatLevel.VeryLow -> -30
else -> 0
}
-
+
// If they have a defensive pact with another civ then we would get drawn into thier battles as well
motivation -= 10 * otherCivNonOverlappingDefensivePacts
@@ -1282,31 +1285,6 @@ object NextTurnAutomation {
diplomacyManager.removeFlag(DiplomacyFlags.SettledCitiesNearUs)
}
- /** Handle decision making after city conquest, namely whether the AI should liberate, puppet,
- * or raze a city */
- fun onConquerCity(civInfo: Civilization, city: City) {
- if (!city.hasDiplomaticMarriage()) {
- val foundingCiv = civInfo.gameInfo.getCivilization(city.foundingCiv)
- var valueAlliance = valueCityStateAlliance(civInfo, foundingCiv)
- if (civInfo.getHappiness() < 0)
- valueAlliance -= civInfo.getHappiness() // put extra weight on liberating if unhappy
- if (foundingCiv.isCityState() && city.civ != civInfo && foundingCiv != civInfo
- && !civInfo.isAtWarWith(foundingCiv)
- && valueAlliance > 0) {
- city.liberateCity(civInfo)
- return
- }
- }
-
- city.puppetCity(civInfo)
- if ((city.population.population < 4 || civInfo.isCityState())
- && city.foundingCiv != civInfo.civName && city.canBeDestroyed(justCaptured = true)) {
- // raze if attacker is a city state
- if (!civInfo.hasUnique(UniqueType.MayNotAnnexCities)) { city.annexCity() }
- city.isBeingRazed = true
- }
- }
-
fun getMinDistanceBetweenCities(civ1: Civilization, civ2: Civilization): Int {
return getClosestCities(civ1, civ2)?.aerialDistance ?: Int.MAX_VALUE
}
diff --git a/core/src/com/unciv/logic/automation/civilization/ReligionAutomation.kt b/core/src/com/unciv/logic/automation/civilization/ReligionAutomation.kt
index 090f9f7467ba8..f37f53877f05c 100644
--- a/core/src/com/unciv/logic/automation/civilization/ReligionAutomation.kt
+++ b/core/src/com/unciv/logic/automation/civilization/ReligionAutomation.kt
@@ -2,7 +2,6 @@ package com.unciv.logic.automation.civilization
import com.unciv.Constants
import com.unciv.logic.city.City
-import com.unciv.models.ruleset.INonPerpetualConstruction
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.managers.ReligionState
import com.unciv.logic.map.tile.Tile
@@ -41,7 +40,10 @@ object ReligionAutomation {
val citiesWithoutOurReligion = civInfo.cities.filter { it.religion.getMajorityReligion() != civInfo.religionManager.religion!! }
// The original had a cap at 4 missionaries total, but 1/4 * the number of cities should be more appropriate imo
if (citiesWithoutOurReligion.count() >
- 4 * civInfo.units.getCivUnits().count { it.canDoLimitedAction(Constants.spreadReligion) || it.canDoLimitedAction(Constants.removeHeresy) }
+ 4 * civInfo.units.getCivUnits().count {
+ it.canDoLimitedAction(Constants.spreadReligion) // OLD
+ || it.hasUnique(UniqueType.CanSpreadReligion) // NEW
+ || it.canDoLimitedAction(Constants.removeHeresy) }
) {
val (city, pressureDifference) = citiesWithoutOurReligion.map { city ->
city to city.religion.getPressureDeficit(civInfo.religionManager.religion?.name)
@@ -77,7 +79,10 @@ object ReligionAutomation {
// Todo: buy Great People post industrial era
// Just buy missionaries to spread our religion outside of our civ
- if (civInfo.units.getCivUnits().count { it.canDoLimitedAction(Constants.spreadReligion) } < 4) {
+ if (civInfo.units.getCivUnits().count {
+ it.canDoLimitedAction(Constants.spreadReligion) // OLD
+ || it.hasUnique(UniqueType.CanSpreadReligion) // NEW
+ } < 4) {
buyMissionaryInAnyCity(civInfo)
return
}
@@ -103,7 +108,10 @@ object ReligionAutomation {
private fun buyMissionaryInAnyCity(civInfo: Civilization) {
if (civInfo.religionManager.religionState < ReligionState.Religion) return
var missionaries = civInfo.gameInfo.ruleset.units.values.filter { unit ->
+ // OLD
unit.getMatchingUniques(UniqueType.CanActionSeveralTimes).filter { it.params[0] == Constants.spreadReligion }.any()
+ // NEW
+ || unit.hasUnique(UniqueType.CanSpreadReligion)
}
missionaries = missionaries.map { civInfo.getEquivalentUnit(it) }
@@ -127,7 +135,12 @@ object ReligionAutomation {
if (validCitiesToBuy.isEmpty()) return
val citiesWithBonusCharges = validCitiesToBuy.filter { city ->
- city.getMatchingUniques(UniqueType.UnitStartingActions).filter { it.params[2] == Constants.spreadReligion }.any()
+ city.getMatchingUniques(UniqueType.UnitStartingActions).any { it.params[2] == Constants.spreadReligion }
+ || city.getMatchingUniques(UniqueType.UnitStartingPromotions).any {
+ val promotionName = it.params[2]
+ val promotion = city.getRuleset().unitPromotions[promotionName] ?: return@any false
+ promotion.hasUnique(UniqueType.CanSpreadReligion)
+ }
}
val holyCity = validCitiesToBuy.firstOrNull { it.isHolyCityOf(civInfo.religionManager.religion!!.name) }
diff --git a/core/src/com/unciv/logic/automation/unit/AirUnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/AirUnitAutomation.kt
new file mode 100644
index 0000000000000..100700b63fdd7
--- /dev/null
+++ b/core/src/com/unciv/logic/automation/unit/AirUnitAutomation.kt
@@ -0,0 +1,188 @@
+package com.unciv.logic.automation.unit
+
+import com.unciv.logic.battle.MapUnitCombatant
+import com.unciv.logic.battle.Nuke
+import com.unciv.logic.battle.TargetHelper
+import com.unciv.logic.civilization.Civilization
+import com.unciv.logic.map.mapunit.MapUnit
+import com.unciv.logic.map.tile.Tile
+
+object AirUnitAutomation {
+
+ fun automateFighter(unit: MapUnit) {
+ val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
+ val enemyAirUnitsInRange = tilesInRange
+ .flatMap { it.airUnits.asSequence() }.filter { it.civ.isAtWarWith(unit.civ) }
+
+ if (enemyAirUnitsInRange.any()) return // we need to be on standby in case they attack
+
+ if (BattleHelper.tryAttackNearbyEnemy(unit)) return
+
+ if (tryRelocateToCitiesWithEnemyNearBy(unit)) return
+
+ val pathsToCities = unit.movement.getAerialPathsToCities()
+ if (pathsToCities.isEmpty()) return // can't actually move anywhere else
+
+ val citiesByNearbyAirUnits = pathsToCities.keys
+ .groupBy { key ->
+ key.getTilesInDistance(unit.getMaxMovementForAirUnits())
+ .count {
+ val firstAirUnit = it.airUnits.firstOrNull()
+ firstAirUnit != null && firstAirUnit.civ.isAtWarWith(unit.civ)
+ }
+ }
+
+ if (citiesByNearbyAirUnits.keys.any { it != 0 }) {
+ val citiesWithMostNeedOfAirUnits = citiesByNearbyAirUnits.maxByOrNull { it.key }!!.value
+ //todo: maybe group by size and choose highest priority within the same size turns
+ val chosenCity = citiesWithMostNeedOfAirUnits.minByOrNull { pathsToCities.getValue(it).size }!! // city with min path = least turns to get there
+ val firstStepInPath = pathsToCities.getValue(chosenCity).first()
+ unit.movement.moveToTile(firstStepInPath)
+ return
+ }
+
+ // no city needs fighters to defend, so let's attack stuff from the closest possible location
+ tryMoveToCitiesToAerialAttackFrom(pathsToCities, unit)
+
+ }
+
+ fun automateBomber(unit: MapUnit) {
+ if (BattleHelper.tryAttackNearbyEnemy(unit)) return
+
+ if (tryRelocateToCitiesWithEnemyNearBy(unit)) return
+
+ val pathsToCities = unit.movement.getAerialPathsToCities()
+ if (pathsToCities.isEmpty()) return // can't actually move anywhere else
+ tryMoveToCitiesToAerialAttackFrom(pathsToCities, unit)
+ }
+
+ private fun tryMoveToCitiesToAerialAttackFrom(pathsToCities: HashMap>, airUnit: MapUnit) {
+ val citiesThatCanAttackFrom = pathsToCities.keys
+ .filter { destinationCity ->
+ destinationCity != airUnit.currentTile
+ && destinationCity.getTilesInDistance(airUnit.getRange())
+ .any { TargetHelper.containsAttackableEnemy(it, MapUnitCombatant(airUnit)) }
+ }
+ if (citiesThatCanAttackFrom.isEmpty()) return
+
+ //todo: this logic looks similar to some parts of automateFighter, maybe pull out common code
+ //todo: maybe group by size and choose highest priority within the same size turns
+ val closestCityThatCanAttackFrom =
+ citiesThatCanAttackFrom.minByOrNull { pathsToCities[it]!!.size }!!
+ val firstStepInPath = pathsToCities[closestCityThatCanAttackFrom]!!.first()
+ airUnit.movement.moveToTile(firstStepInPath)
+ }
+
+ fun automateNukes(unit: MapUnit) {
+ if (!unit.civ.isAtWar()) return
+ // We should *Almost* never want to nuke our own city, so don't consider it
+ val tilesInRange = unit.currentTile.getTilesInDistanceRange(2..unit.getRange())
+ var highestTileNukeValue = 0
+ var tileToNuke: Tile? = null
+ tilesInRange.forEach {
+ val value = getNukeLocationValue(unit, it)
+ if (value > highestTileNukeValue) {
+ highestTileNukeValue = value
+ tileToNuke = it
+ }
+ }
+ if (highestTileNukeValue > 0) {
+ Nuke.NUKE(MapUnitCombatant(unit), tileToNuke!!)
+ }
+ tryRelocateMissileToNearbyAttackableCities(unit)
+ }
+
+ /**
+ * Ranks the tile to nuke based off of all tiles in it's blast radius
+ * By default the value is -500 to prevent inefficient nuking.
+ */
+ private fun getNukeLocationValue(nuke: MapUnit, tile: Tile): Int {
+ val civ = nuke.civ
+ if (!Nuke.mayUseNuke(MapUnitCombatant(nuke), tile)) return Int.MIN_VALUE
+ val blastRadius = nuke.getNukeBlastRadius()
+ val tilesInBlastRadius = tile.getTilesInDistance(blastRadius)
+ val civsInBlastRadius = tilesInBlastRadius.mapNotNull { it.getOwner() } +
+ tilesInBlastRadius.mapNotNull { it.getFirstUnit()?.civ }
+
+ // Don't nuke if it means we will be declaring war on someone!
+ if (civsInBlastRadius.any { it != civ && !it.isAtWarWith(civ) }) return -100000
+ // If there are no enemies to hit, don't nuke
+ if (!civsInBlastRadius.any { it.isAtWarWith(civ) }) return -100000
+
+ // Launching a Nuke uses resources, therefore don't launch it by default
+ var explosionValue = -500
+
+ // Returns either ourValue or thierValue depending on if the input Civ matches the Nuke's Civ
+ fun evaluateCivValue(targetCiv: Civilization, ourValue: Int, theirValue: Int): Int {
+ if (targetCiv == civ) // We are nuking something that we own!
+ return ourValue
+ return theirValue // We are nuking an enemy!
+ }
+ for (targetTile in tilesInBlastRadius) {
+ // We can only account for visible units
+ if (tile.isVisible(civ)) {
+ if (targetTile.militaryUnit != null && !targetTile.militaryUnit!!.isInvisible(civ))
+ explosionValue += evaluateCivValue(targetTile.militaryUnit?.civ!!, -150, 50)
+ if (targetTile.civilianUnit != null && !targetTile.civilianUnit!!.isInvisible(civ))
+ explosionValue += evaluateCivValue(targetTile.civilianUnit?.civ!!, -100, 25)
+ }
+ // Never nuke our own Civ, don't nuke single enemy civs as well
+ if (targetTile.isCityCenter()
+ && !(targetTile.getCity()!!.health <= 50f
+ && targetTile.neighbors.any {it.militaryUnit?.civ == civ})) // Prefer not to nuke cities that we are about to take
+ explosionValue += evaluateCivValue(targetTile.getCity()?.civ!!, -100000, 250)
+ else if (targetTile.owningCity != null) {
+ val owningCiv = targetTile.owningCity?.civ!!
+ // If there is a tile to add fallout to there is a 50% chance it will get fallout
+ if (!(tile.isWater || tile.isImpassible() || targetTile.terrainFeatures.any { it == "Fallout" }))
+ explosionValue += evaluateCivValue(owningCiv, -40, 10)
+ // If there is an improvment to pillage
+ if (targetTile.improvement != null && !targetTile.improvementIsPillaged)
+ explosionValue += evaluateCivValue(owningCiv, -40, 20)
+ }
+ // If the value is too low end the search early
+ if (explosionValue < -1000) return explosionValue
+ }
+ return explosionValue
+ }
+
+ // This really needs to be changed, to have better targeting for missiles
+ fun automateMissile(unit: MapUnit) {
+ if (BattleHelper.tryAttackNearbyEnemy(unit)) return
+ tryRelocateMissileToNearbyAttackableCities(unit)
+ }
+
+ private fun tryRelocateMissileToNearbyAttackableCities(unit: MapUnit) {
+ val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
+ val immediatelyReachableCities = tilesInRange
+ .filter { unit.movement.canMoveTo(it) }
+
+ for (city in immediatelyReachableCities) if (city.getTilesInDistance(unit.getRange())
+ .any { it.isCityCenter() && it.getOwner()!!.isAtWarWith(unit.civ) }
+ ) {
+ unit.movement.moveToTile(city)
+ return
+ }
+
+ val pathsToCities = unit.movement.getAerialPathsToCities()
+ if (pathsToCities.isEmpty()) return // can't actually move anywhere else
+ tryMoveToCitiesToAerialAttackFrom(pathsToCities, unit)
+ }
+
+ private fun tryRelocateToCitiesWithEnemyNearBy(unit: MapUnit): Boolean {
+ val immediatelyReachableCitiesAndCarriers = unit.currentTile
+ .getTilesInDistance(unit.getMaxMovementForAirUnits()).filter { unit.movement.canMoveTo(it) }
+
+ for (city in immediatelyReachableCitiesAndCarriers) {
+ if (city.getTilesInDistance(unit.getRange())
+ .any {
+ it.isVisible(unit.civ) &&
+ TargetHelper.containsAttackableEnemy(it,MapUnitCombatant(unit))
+ }) {
+ unit.movement.moveToTile(city)
+ return true
+ }
+ }
+ return false
+ }
+}
diff --git a/core/src/com/unciv/logic/automation/unit/CivilianUnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/CivilianUnitAutomation.kt
new file mode 100644
index 0000000000000..2628fe75ae2d9
--- /dev/null
+++ b/core/src/com/unciv/logic/automation/unit/CivilianUnitAutomation.kt
@@ -0,0 +1,181 @@
+package com.unciv.logic.automation.unit
+
+import com.unciv.Constants
+import com.unciv.logic.civilization.Civilization
+import com.unciv.logic.civilization.managers.ReligionState
+import com.unciv.logic.map.mapunit.MapUnit
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.UnitActionType
+import com.unciv.models.ruleset.unique.UniqueType
+import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions
+
+object CivilianUnitAutomation {
+
+ fun automateCivilianUnit(unit: MapUnit) {
+ if (tryRunAwayIfNeccessary(unit)) return
+
+ if (unit.currentTile.isCityCenter() && unit.currentTile.getCity()!!.isCapital()
+ && !unit.hasUnique(UniqueType.AddInCapital)
+ && unit.civ.units.getCivUnits().any { unit.hasUnique(UniqueType.AddInCapital) }){
+ // First off get out of the way, then decide if you actually want to do something else
+ val tilesCanMoveTo = unit.movement.getDistanceToTiles()
+ .filter { unit.movement.canMoveTo(it.key) }
+ if (tilesCanMoveTo.isNotEmpty())
+ unit.movement.moveToTile(tilesCanMoveTo.minByOrNull { it.value.totalDistance }!!.key)
+ }
+
+ val tilesWhereWeWillBeCaptured = unit.currentTile.getTilesInDistance(5)
+ .mapNotNull { it.militaryUnit }
+ .filter { it.civ.isAtWarWith(unit.civ) }
+ .flatMap { it.movement.getReachableTilesInCurrentTurn() }
+ .filter { it.militaryUnit?.civ != unit.civ }
+ .toSet()
+
+ if (unit.hasUnique(UniqueType.FoundCity))
+ return SpecificUnitAutomation.automateSettlerActions(unit, tilesWhereWeWillBeCaptured)
+
+ if (unit.cache.hasUniqueToBuildImprovements)
+ return unit.civ.getWorkerAutomation().automateWorkerAction(unit, tilesWhereWeWillBeCaptured)
+
+ if (unit.cache.hasUniqueToCreateWaterImprovements){
+ if (!unit.civ.getWorkerAutomation().automateWorkBoats(unit))
+ UnitAutomation.tryExplore(unit)
+ return
+ }
+
+ if (unit.hasUnique(UniqueType.MayFoundReligion)
+ && unit.civ.religionManager.religionState < ReligionState.Religion
+ && unit.civ.religionManager.mayFoundReligionAtAll()
+ )
+ return ReligiousUnitAutomation.foundReligion(unit)
+
+ if (unit.hasUnique(UniqueType.MayEnhanceReligion)
+ && unit.civ.religionManager.religionState < ReligionState.EnhancedReligion
+ && unit.civ.religionManager.mayEnhanceReligionAtAll(unit)
+ )
+ return ReligiousUnitAutomation.enhanceReligion(unit)
+
+ // We try to add any unit in the capital we can, though that might not always be desirable
+ // For now its a simple option to allow AI to win a science victory again
+ if (unit.hasUnique(UniqueType.AddInCapital))
+ return SpecificUnitAutomation.automateAddInCapital(unit)
+
+ //todo this now supports "Great General"-like mod units not combining 'aura' and citadel
+ // abilities, but not additional capabilities if automation finds no use for those two
+ if (unit.cache.hasStrengthBonusInRadiusUnique
+ && SpecificUnitAutomation.automateGreatGeneral(unit))
+ return
+ if (unit.cache.hasCitadelPlacementUnique && SpecificUnitAutomation.automateCitadelPlacer(unit))
+ return
+ if (unit.cache.hasCitadelPlacementUnique || unit.cache.hasStrengthBonusInRadiusUnique)
+ return SpecificUnitAutomation.automateGreatGeneralFallback(unit)
+
+ if (unit.civ.religionManager.maySpreadReligionAtAll(unit))
+ return ReligiousUnitAutomation.automateMissionary(unit)
+
+ if (unit.hasUnique(UniqueType.PreventSpreadingReligion) || unit.canDoLimitedAction(Constants.removeHeresy))
+ return ReligiousUnitAutomation.automateInquisitor(unit)
+
+ val isLateGame = isLateGame(unit.civ)
+ // Great scientist -> Hurry research if late game
+ if (isLateGame) {
+ val hurriedResearch = UnitActions.invokeUnitAction(unit, UnitActionType.HurryResearch)
+ if (hurriedResearch) return
+ }
+
+ // Great merchant -> Conduct trade mission if late game and if not at war.
+ // TODO: This could be more complex to walk to the city state that is most beneficial to
+ // also have more influence.
+ if (unit.hasUnique(UniqueType.CanTradeWithCityStateForGoldAndInfluence)
+ // Don't wander around with the great merchant when at war. Barbs might also be a
+ // problem, but hopefully by the time we have a great merchant, they're under control.
+ && !unit.civ.isAtWar()
+ && isLateGame
+ ) {
+ val tradeMissionCanBeConductedEventually =
+ SpecificUnitAutomation.conductTradeMission(unit)
+ if (tradeMissionCanBeConductedEventually)
+ return
+ }
+
+ // Great engineer -> Try to speed up wonder construction if late game
+ if (isLateGame &&
+ (unit.hasUnique(UniqueType.CanSpeedupConstruction)
+ || unit.hasUnique(UniqueType.CanSpeedupWonderConstruction))) {
+ val wonderCanBeSpedUpEventually = SpecificUnitAutomation.speedupWonderConstruction(unit)
+ if (wonderCanBeSpedUpEventually)
+ return
+ }
+
+
+ // This has to come after the individual abilities for the great people that can also place
+ // instant improvements (e.g. great scientist).
+ if (unit.hasUnique(UniqueType.ConstructImprovementInstantly)) {
+ // catch great prophet for civs who can't found/enhance/spread religion
+ // includes great people plus moddable units
+ val improvementCanBePlacedEventually =
+ SpecificUnitAutomation.automateImprovementPlacer(unit)
+ if (!improvementCanBePlacedEventually)
+ UnitActions.invokeUnitAction(unit, UnitActionType.StartGoldenAge)
+ }
+
+ // TODO: The AI tends to have a lot of great generals. Maybe there should be a cutoff
+ // (depending on number of cities) and after that they should just be used to start golden
+ // ages?
+
+ return // The AI doesn't know how to handle unknown civilian units
+ }
+
+ private fun isLateGame(civ: Civilization): Boolean {
+ val researchCompletePercent =
+ (civ.tech.researchedTechnologies.size * 1.0f) / civ.gameInfo.ruleset.technologies.size
+ return researchCompletePercent >= 0.8f
+ }
+
+ /** Returns whether the civilian spends its turn hiding and not moving */
+ private fun tryRunAwayIfNeccessary(unit: MapUnit): Boolean {
+ // This is a little 'Bugblatter Beast of Traal': Run if we can attack an enemy
+ // Cheaper than determining which enemies could attack us next turn
+ val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
+ .filter { UnitAutomation.containsEnemyMilitaryUnit(unit, it) }
+
+ if (enemyUnitsInWalkingDistance.isNotEmpty() && !unit.baseUnit.isMilitary()
+ && unit.getTile().militaryUnit == null && !unit.getTile().isCityCenter()) {
+ runAway(unit)
+ return true
+ }
+
+ return false
+ }
+
+ private fun runAway(unit: MapUnit) {
+ val reachableTiles = unit.movement.getDistanceToTiles()
+ val enterableCity = reachableTiles.keys
+ .firstOrNull { it.isCityCenter() && unit.movement.canMoveTo(it) }
+ if (enterableCity != null) {
+ unit.movement.moveToTile(enterableCity)
+ return
+ }
+ val defensiveUnit = reachableTiles.keys
+ .firstOrNull {
+ it.militaryUnit != null && it.militaryUnit!!.civ == unit.civ && it.civilianUnit == null
+ }
+ if (defensiveUnit != null) {
+ unit.movement.moveToTile(defensiveUnit)
+ return
+ }
+ val tileFurthestFromEnemy = reachableTiles.keys
+ .filter { unit.movement.canMoveTo(it) && unit.getDamageFromTerrain(it) < unit.health }
+ .maxByOrNull { countDistanceToClosestEnemy(unit, it) }
+ ?: return // can't move anywhere!
+ unit.movement.moveToTile(tileFurthestFromEnemy)
+ }
+
+
+ private fun countDistanceToClosestEnemy(unit: MapUnit, tile: Tile): Int {
+ for (i in 1..3)
+ if (tile.getTilesAtDistance(i).any { UnitAutomation.containsEnemyMilitaryUnit(unit, it) })
+ return i
+ return 4
+ }
+}
diff --git a/core/src/com/unciv/logic/automation/unit/HeadTowardsEnemyCityAutomation.kt b/core/src/com/unciv/logic/automation/unit/HeadTowardsEnemyCityAutomation.kt
new file mode 100644
index 0000000000000..748d6bdee9fd3
--- /dev/null
+++ b/core/src/com/unciv/logic/automation/unit/HeadTowardsEnemyCityAutomation.kt
@@ -0,0 +1,145 @@
+package com.unciv.logic.automation.unit
+
+import com.unciv.logic.automation.civilization.NextTurnAutomation
+import com.unciv.logic.battle.BattleDamage
+import com.unciv.logic.battle.CityCombatant
+import com.unciv.logic.battle.MapUnitCombatant
+import com.unciv.logic.city.City
+import com.unciv.logic.map.mapunit.MapUnit
+import com.unciv.logic.map.mapunit.movement.PathsToTilesWithinTurn
+import com.unciv.logic.map.tile.Tile
+
+object HeadTowardsEnemyCityAutomation {
+
+ /** @returns whether the unit has taken this action */
+ fun tryHeadTowardsEnemyCity(unit: MapUnit): Boolean {
+ if (unit.civ.cities.isEmpty()) return false
+
+ // only focus on *attacking* 1 enemy at a time otherwise you'll lose on both fronts
+ val closestReachableEnemyCity = getEnemyCitiesByPriority(unit)
+ .firstOrNull { unit.movement.canReach(it.getCenterTile()) }
+ ?: return false // No enemy city reachable
+
+ return headTowardsEnemyCity(
+ unit,
+ closestReachableEnemyCity.getCenterTile(),
+ // This should be cached after the `canReach` call above.
+ unit.movement.getShortestPath(closestReachableEnemyCity.getCenterTile())
+ )
+ }
+
+ private fun getEnemyCitiesByPriority(unit: MapUnit):Sequence{
+ val enemies = unit.civ.getKnownCivs()
+ .filter { unit.civ.isAtWarWith(it) && it.cities.isNotEmpty() }
+
+ val closestEnemyCity = enemies
+ .mapNotNull { NextTurnAutomation.getClosestCities(unit.civ, it) }
+ .minByOrNull { it.aerialDistance }?.city2
+ ?: return emptySequence() // no attackable cities found
+
+ // Our main attack target is the closest city, but we're fine with deviating from that a bit
+ var enemyCitiesByPriority = closestEnemyCity.civ.cities
+ .associateWith { it.getCenterTile().aerialDistanceTo(closestEnemyCity.getCenterTile()) }
+ .asSequence().filterNot { it.value > 10 } // anything 10 tiles away from the target is irrelevant
+ .sortedBy { it.value }.map { it.key } // sort the list by closeness to target - least is best!
+
+ if (unit.baseUnit.isRanged()) // ranged units don't harm capturable cities, waste of a turn
+ enemyCitiesByPriority = enemyCitiesByPriority.filterNot { it.health == 1 }
+
+ return enemyCitiesByPriority
+ }
+
+
+ private const val maxDistanceFromCityToConsiderForLandingArea = 5
+ private const val minDistanceFromCityToConsiderForLandingArea = 3
+
+ /** @returns whether the unit has taken this action */
+ fun headTowardsEnemyCity(
+ unit: MapUnit,
+ closestReachableEnemyCity: Tile,
+ shortestPath: List
+ ): Boolean {
+ val unitDistanceToTiles = unit.movement.getDistanceToTiles()
+
+ val unitRange = unit.getRange()
+ if (unitRange > 2) { // long-ranged unit, should never be in a bombardable position
+ return headTowardsEnemyCityLongRange(closestReachableEnemyCity, unitDistanceToTiles, unitRange, unit)
+ }
+
+ val nextTileInPath = shortestPath[0]
+
+ // None of the stuff below is relevant if we're still quite far away from the city, so we
+ // short-circuit here for performance reasons.
+ if (unit.currentTile.aerialDistanceTo(closestReachableEnemyCity) > maxDistanceFromCityToConsiderForLandingArea
+ // Even in the worst case of only being able to move 1 tile per turn, we would still
+ // not overshoot.
+ && shortestPath.size > minDistanceFromCityToConsiderForLandingArea ) {
+ unit.movement.moveToTile(nextTileInPath)
+ return true
+ }
+
+ val ourUnitsAroundEnemyCity = closestReachableEnemyCity.getTilesInDistance(6)
+ .flatMap { it.getUnits() }
+ .filter { it.isMilitary() && it.civ == unit.civ }
+
+ val city = closestReachableEnemyCity.getCity()!!
+
+ if (cannotTakeCitySoon(ourUnitsAroundEnemyCity, city)){
+ return headToLandingGrounds(closestReachableEnemyCity, unit)
+ }
+
+ unit.movement.moveToTile(nextTileInPath) // go for it!
+
+ return true
+ }
+
+ /** Cannot take within 5 turns */
+ private fun cannotTakeCitySoon(
+ ourUnitsAroundEnemyCity: Sequence,
+ city: City
+ ): Boolean {
+ val cityCombatant = CityCombatant(city)
+ val expectedDamagePerTurn = ourUnitsAroundEnemyCity
+ .sumOf { BattleDamage.calculateDamageToDefender(MapUnitCombatant(it), cityCombatant) }
+
+ val cityHealingPerTurn = 20
+ return expectedDamagePerTurn < city.health && // Cannot take immediately
+ (expectedDamagePerTurn <= cityHealingPerTurn // No lasting damage
+ || city.health / (expectedDamagePerTurn - cityHealingPerTurn) > 5) // Can damage, but will take more than 5 turns
+ }
+
+ private fun headToLandingGrounds(closestReachableEnemyCity: Tile, unit: MapUnit): Boolean {
+ // don't head straight to the city, try to head to landing grounds -
+ // this is against tha AI's brilliant plan of having everyone embarked and attacking via sea when unnecessary.
+ val tileToHeadTo = closestReachableEnemyCity.getTilesInDistanceRange(minDistanceFromCityToConsiderForLandingArea..maxDistanceFromCityToConsiderForLandingArea)
+ .filter { it.isLand && unit.getDamageFromTerrain(it) <= 0 } // Don't head for hurty terrain
+ .sortedBy { it.aerialDistanceTo(unit.currentTile) }
+ .firstOrNull { (unit.movement.canMoveTo(it) || it == unit.currentTile) && unit.movement.canReach(it) }
+
+ if (tileToHeadTo != null) { // no need to worry, keep going as the movement alg. says
+ unit.movement.headTowards(tileToHeadTo)
+ }
+ return true
+ }
+
+ private fun headTowardsEnemyCityLongRange(
+ closestReachableEnemyCity: Tile,
+ unitDistanceToTiles: PathsToTilesWithinTurn,
+ unitRange: Int,
+ unit: MapUnit
+ ): Boolean {
+ val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2).toSet()
+ val tileToMoveTo =
+ unitDistanceToTiles.asSequence()
+ .filter {
+ it.key.aerialDistanceTo(closestReachableEnemyCity) <=
+ unitRange && it.key !in tilesInBombardRange
+ && unit.getDamageFromTerrain(it.key) <= 0 // Don't set up on a mountain
+ }
+ .minByOrNull { it.value.totalDistance }?.key ?: return false // return false if no tile to move to
+
+ // move into position far away enough that the bombard doesn't hurt
+ unit.movement.headTowards(tileToMoveTo)
+ return true
+ }
+}
diff --git a/core/src/com/unciv/logic/automation/unit/ReligiousUnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/ReligiousUnitAutomation.kt
new file mode 100644
index 0000000000000..b9558f0aac471
--- /dev/null
+++ b/core/src/com/unciv/logic/automation/unit/ReligiousUnitAutomation.kt
@@ -0,0 +1,151 @@
+package com.unciv.logic.automation.unit
+
+import com.unciv.Constants
+import com.unciv.logic.city.City
+import com.unciv.logic.map.mapunit.MapUnit
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.UnitActionType
+import com.unciv.models.ruleset.unique.UniqueType
+import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions
+
+object ReligiousUnitAutomation {
+
+ fun automateMissionary(unit: MapUnit) {
+ if (unit.religion != unit.civ.religionManager.religion?.name || unit.religion == null)
+ return unit.disband()
+
+ val ourCitiesWithoutReligion = unit.civ.cities.filter {
+ it.religion.getMajorityReligion() != unit.civ.religionManager.religion
+ }
+
+ val city =
+ if (ourCitiesWithoutReligion.any())
+ ourCitiesWithoutReligion.minByOrNull { it.getCenterTile().aerialDistanceTo(unit.getTile()) }
+ else unit.civ.gameInfo.getCities()
+ .filter { it.religion.getMajorityReligion() != unit.civ.religionManager.religion }
+ .filter { it.civ.knows(unit.civ) && !it.civ.isAtWarWith(unit.civ) }
+ .filterNot { it.religion.isProtectedByInquisitor(unit.religion) }
+ .minByOrNull { it.getCenterTile().aerialDistanceTo(unit.getTile()) }
+
+ if (city == null) return
+ val destination = city.getTiles()
+ .filter { unit.movement.canMoveTo(it) || it == unit.getTile() }
+ .sortedBy { it.aerialDistanceTo(unit.getTile()) }
+ .firstOrNull { unit.movement.canReach(it) } ?: return
+
+ unit.movement.headTowards(destination)
+
+ if (unit.getTile() in city.getTiles() && unit.civ.religionManager.maySpreadReligionNow(unit)) {
+ UnitActions.invokeUnitAction(unit, UnitActionType.SpreadReligion)
+ }
+ }
+
+ fun automateInquisitor(unit: MapUnit) {
+ val civReligion = unit.civ.religionManager.religion
+
+ if (unit.religion != civReligion?.name || unit.religion == null)
+ return unit.disband() // No need to keep a unit we can't use, as it only blocks religion spreads of religions other that its own
+
+ val holyCity = unit.civ.religionManager.getHolyCity()
+ val cityToConvert = determineBestInquisitorCityToConvert(unit) // Also returns null if the inquisitor can't convert cities
+ val pressureDeficit =
+ if (cityToConvert == null) 0
+ else cityToConvert.religion.getPressureDeficit(civReligion?.name)
+
+ val citiesToProtect = unit.civ.cities.asSequence()
+ .filter { it.religion.getMajorityReligion() == civReligion }
+ // We only look at cities that are not currently protected or are protected by us
+ .filter { !it.religion.isProtectedByInquisitor() || unit.getTile() in it.getCenterTile().getTilesInDistance(1) }
+
+ // cities with most populations will be prioritized by the AI
+ val cityToProtect = citiesToProtect.maxByOrNull { it.population.population }
+
+ var destination: Tile?
+
+ destination = when {
+ cityToConvert != null
+ && (cityToConvert == holyCity
+ || pressureDeficit > Constants.aiPreferInquisitorOverMissionaryPressureDifference
+ || cityToConvert.religion.isBlockedHolyCity && cityToConvert.religion.religionThisIsTheHolyCityOf == civReligion?.name
+ ) && unit.canDoLimitedAction(Constants.removeHeresy) -> {
+ cityToConvert.getCenterTile()
+ }
+ cityToProtect != null && unit.hasUnique(UniqueType.PreventSpreadingReligion) -> {
+ if (holyCity != null && !holyCity.religion.isProtectedByInquisitor())
+ holyCity.getCenterTile()
+ else cityToProtect.getCenterTile()
+ }
+ cityToConvert != null -> cityToConvert.getCenterTile()
+ else -> null
+ }
+
+ if (destination == null) return
+
+ if (!unit.movement.canReach(destination)) {
+ destination = destination.neighbors
+ .filter { unit.movement.canMoveTo(it) || it == unit.getTile() }
+ .sortedBy { it.aerialDistanceTo(unit.currentTile) }
+ .firstOrNull { unit.movement.canReach(it) }
+ ?: return
+ }
+
+ unit.movement.headTowards(destination)
+
+ if (cityToConvert != null && unit.getTile().getCity() == destination.getCity()) {
+ UnitActions.invokeUnitAction(unit, UnitActionType.RemoveHeresy)
+ }
+ }
+
+ private fun determineBestInquisitorCityToConvert(
+ unit: MapUnit,
+ ): City? {
+ if (unit.religion != unit.civ.religionManager.religion?.name || !unit.canDoLimitedAction(Constants.removeHeresy))
+ return null
+
+ val holyCity = unit.civ.religionManager.getHolyCity()
+ if (holyCity != null && holyCity.religion.getMajorityReligion() != unit.civ.religionManager.religion!!)
+ return holyCity
+
+ val blockedHolyCity = unit.civ.cities.firstOrNull { it.religion.isBlockedHolyCity && it.religion.religionThisIsTheHolyCityOf == unit.religion }
+ if (blockedHolyCity != null)
+ return blockedHolyCity
+
+ return unit.civ.cities.asSequence()
+ .filter { it.religion.getMajorityReligion() != null }
+ .filter { it.religion.getMajorityReligion()!! != unit.civ.religionManager.religion }
+ // Don't go if it takes too long
+ .filter { it.getCenterTile().aerialDistanceTo(unit.currentTile) <= 20 }
+ .maxByOrNull { it.religion.getPressureDeficit(unit.civ.religionManager.religion?.name) }
+ }
+
+
+ fun foundReligion(unit: MapUnit) {
+ val cityToFoundReligionAt =
+ if (unit.getTile().isCityCenter() && !unit.getTile().owningCity!!.isHolyCity()) unit.getTile().owningCity
+ else unit.civ.cities.firstOrNull {
+ !it.isHolyCity()
+ && unit.movement.canMoveTo(it.getCenterTile())
+ && unit.movement.canReach(it.getCenterTile())
+ }
+ if (cityToFoundReligionAt == null) return
+ if (unit.getTile() != cityToFoundReligionAt.getCenterTile()) {
+ unit.movement.headTowards(cityToFoundReligionAt.getCenterTile())
+ return
+ }
+
+ UnitActions.invokeUnitAction(unit, UnitActionType.FoundReligion)
+ }
+
+ fun enhanceReligion(unit: MapUnit) {
+ // Try go to a nearby city
+ if (!unit.getTile().isCityCenter())
+ UnitAutomation.tryEnterOwnClosestCity(unit)
+
+ // If we were unable to go there this turn, unable to do anything else
+ if (!unit.getTile().isCityCenter())
+ return
+
+ UnitActions.invokeUnitAction(unit, UnitActionType.EnhanceReligion)
+ }
+
+}
diff --git a/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt
index d8c7d57e92895..cbde7d2c08da7 100644
--- a/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt
+++ b/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt
@@ -1,24 +1,18 @@
package com.unciv.logic.automation.unit
-import com.unciv.Constants
import com.unciv.logic.automation.Automation
-import com.unciv.logic.battle.Battle
import com.unciv.logic.battle.GreatGeneralImplementation
-import com.unciv.logic.battle.MapUnitCombatant
-import com.unciv.logic.battle.TargetHelper
import com.unciv.logic.city.City
-import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.logic.map.tile.Tile
-import com.unciv.models.UnitAction
import com.unciv.models.UnitActionType
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions
-import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsReligion
+import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsFromUniques
object SpecificUnitAutomation {
@@ -58,13 +52,13 @@ object SpecificUnitAutomation {
if (tileToSteal != null) {
unit.movement.headTowards(tileToSteal)
if (unit.currentMovement > 0 && unit.currentTile == tileToSteal)
- UnitActions.getImprovementConstructionActions(unit, unit.currentTile).firstOrNull()?.action?.invoke()
+ UnitActionsFromUniques.getImprovementConstructionActions(unit, unit.currentTile).firstOrNull()?.action?.invoke()
return true
}
// try to build a citadel for defensive purposes
if (unit.civ.getWorkerAutomation().evaluateFortPlacement(unit.currentTile, true)) {
- UnitActions.getImprovementConstructionActions(unit, unit.currentTile).firstOrNull()?.action?.invoke()
+ UnitActionsFromUniques.getImprovementConstructionActions(unit, unit.currentTile).firstOrNull()?.action?.invoke()
return true
}
return false
@@ -98,22 +92,19 @@ object SpecificUnitAutomation {
}
unit.movement.headTowards(tileForCitadel)
if (unit.currentMovement > 0 && unit.currentTile == tileForCitadel)
- UnitActions.getImprovementConstructionActions(unit, unit.currentTile)
+ UnitActionsFromUniques.getImprovementConstructionActions(unit, unit.currentTile)
.firstOrNull()?.action?.invoke()
}
fun automateSettlerActions(unit: MapUnit, tilesWhereWeWillBeCaptured: Set) {
if (unit.civ.gameInfo.turns == 0) { // Special case, we want AI to settle in place on turn 1.
- val foundCityAction = UnitActions.getFoundCityAction(unit, unit.getTile())
+ val foundCityAction = UnitActionsFromUniques.getFoundCityAction(unit, unit.getTile())
// Depending on era and difficulty we might start with more than one settler. In that case settle the one with the best location
val otherSettlers = unit.civ.units.getCivUnits().filter { it.currentMovement > 0 && it.baseUnit == unit.baseUnit }
+ val unitTileRanking = CityLocationTileRanker.rankTileAsCityCenter(unit.getTile(), unit.civ)
if (foundCityAction?.action != null &&
otherSettlers.none {
- CityLocationTileRanker.rankTileAsCityCenter(
- it.getTile(), unit.civ
- ) > CityLocationTileRanker.rankTileAsCityCenter(
- unit.getTile(), unit.civ
- )
+ CityLocationTileRanker.rankTileAsCityCenter(it.getTile(), unit.civ) > unitTileRanking
}
) {
foundCityAction.action.invoke()
@@ -146,7 +137,7 @@ object SpecificUnitAutomation {
return
}
- val foundCityAction = UnitActions.getFoundCityAction(unit, bestCityLocation)
+ val foundCityAction = UnitActionsFromUniques.getFoundCityAction(unit, bestCityLocation)
if (foundCityAction?.action == null) { // this means either currentMove == 0 or city within 3 tiles
if (unit.currentMovement > 0) // therefore, city within 3 tiles
throw Exception("City within distance")
@@ -215,10 +206,8 @@ object SpecificUnitAutomation {
unit.movement.headTowards(chosenTile)
if (unit.currentTile == chosenTile) {
if (unit.currentTile.isPillaged())
- UnitActions.getRepairAction(unit).invoke()
- else
- UnitActions.getImprovementConstructionActions(unit, unit.currentTile)
- .firstOrNull()?.action?.invoke()
+ UnitActions.invokeUnitAction(unit, UnitActionType.Repair)
+ else UnitActions.invokeUnitAction(unit, UnitActionType.Create)
return true
}
return unitTileBeforeMovement != unit.currentTile
@@ -249,12 +238,8 @@ object SpecificUnitAutomation {
.minByOrNull { it.second }?.first
?: return false
- val conductTradeMissionAction = UnitActions.getUnitActions(unit)
- .firstOrNull { it.type == UnitActionType.ConductTradeMission }
- if (conductTradeMissionAction?.action != null) {
- conductTradeMissionAction.action.invoke()
- return true
- }
+ val conductedTradeMission = UnitActions.invokeUnitAction(unit, UnitActionType.ConductTradeMission)
+ if (conductedTradeMission) return true
val unitTileBeforeMovement = unit.currentTile
unit.movement.headTowards(closestCityStateTile)
@@ -293,12 +278,8 @@ object SpecificUnitAutomation {
wonderToHurry.name
)
unit.showAdditionalActions = false // make sure getUnitActions doesn't skip the important ones
- val unitAction = UnitActions.getUnitActions(unit).firstOrNull {
- it.type == UnitActionType.HurryBuilding || it.type == UnitActionType.HurryWonder
- } ?: return false
- if (unitAction.action == null) return false
- unitAction.action.invoke()
- return true
+ return UnitActions.invokeUnitAction(unit, UnitActionType.HurryBuilding)
+ || UnitActions.invokeUnitAction(unit, UnitActionType.HurryWonder)
}
// Walk towards the city.
@@ -320,334 +301,8 @@ object SpecificUnitAutomation {
if (unit.movement.canReach(capitalTile))
unit.movement.headTowards(capitalTile)
if (unit.getTile() == capitalTile) {
- UnitActions.getAddInCapitalAction(unit, capitalTile).action!!()
- return
- }
- }
-
- fun automateMissionary(unit: MapUnit) {
- if (unit.religion != unit.civ.religionManager.religion?.name || unit.religion == null)
- return unit.disband()
-
- val ourCitiesWithoutReligion = unit.civ.cities.filter {
- it.religion.getMajorityReligion() != unit.civ.religionManager.religion
- }
-
- val city =
- if (ourCitiesWithoutReligion.any())
- ourCitiesWithoutReligion.minByOrNull { it.getCenterTile().aerialDistanceTo(unit.getTile()) }
- else unit.civ.gameInfo.getCities()
- .filter { it.religion.getMajorityReligion() != unit.civ.religionManager.religion }
- .filter { it.civ.knows(unit.civ) && !it.civ.isAtWarWith(unit.civ) }
- .filterNot { it.religion.isProtectedByInquisitor(unit.religion) }
- .minByOrNull { it.getCenterTile().aerialDistanceTo(unit.getTile()) }
-
- if (city == null) return
- val destination = city.getTiles()
- .filter { unit.movement.canMoveTo(it) || it == unit.getTile() }
- .sortedBy { it.aerialDistanceTo(unit.getTile()) }
- .firstOrNull { unit.movement.canReach(it) } ?: return
-
- unit.movement.headTowards(destination)
-
- if (unit.getTile() in city.getTiles() && unit.civ.religionManager.maySpreadReligionNow(unit)) {
- doReligiousAction(unit, unit.getTile())
+ UnitActionsFromUniques.getAddInCapitalAction(unit, capitalTile).action?.invoke()
}
}
- fun automateInquisitor(unit: MapUnit) {
- val civReligion = unit.civ.religionManager.religion
-
- if (unit.religion != civReligion?.name || unit.religion == null)
- return unit.disband() // No need to keep a unit we can't use, as it only blocks religion spreads of religions other that its own
-
- val holyCity = unit.civ.religionManager.getHolyCity()
- val cityToConvert = determineBestInquisitorCityToConvert(unit) // Also returns null if the inquisitor can't convert cities
- val pressureDeficit =
- if (cityToConvert == null) 0
- else cityToConvert.religion.getPressureDeficit(civReligion?.name)
-
- val citiesToProtect = unit.civ.cities.asSequence()
- .filter { it.religion.getMajorityReligion() == civReligion }
- // We only look at cities that are not currently protected or are protected by us
- .filter { !it.religion.isProtectedByInquisitor() || unit.getTile() in it.getCenterTile().getTilesInDistance(1) }
-
- // cities with most populations will be prioritized by the AI
- val cityToProtect = citiesToProtect.maxByOrNull { it.population.population }
-
- var destination: Tile?
-
- destination = when {
- cityToConvert != null
- && (cityToConvert == holyCity
- || pressureDeficit > Constants.aiPreferInquisitorOverMissionaryPressureDifference
- || cityToConvert.religion.isBlockedHolyCity && cityToConvert.religion.religionThisIsTheHolyCityOf == civReligion?.name
- ) && unit.canDoLimitedAction(Constants.removeHeresy) -> {
- cityToConvert.getCenterTile()
- }
- cityToProtect != null && unit.hasUnique(UniqueType.PreventSpreadingReligion) -> {
- if (holyCity != null && !holyCity.religion.isProtectedByInquisitor())
- holyCity.getCenterTile()
- else cityToProtect.getCenterTile()
- }
- cityToConvert != null -> cityToConvert.getCenterTile()
- else -> null
- }
-
- if (destination == null) return
-
- if (!unit.movement.canReach(destination)) {
- destination = destination.neighbors
- .filter { unit.movement.canMoveTo(it) || it == unit.getTile() }
- .sortedBy { it.aerialDistanceTo(unit.currentTile) }
- .firstOrNull { unit.movement.canReach(it) }
- ?: return
- }
-
- unit.movement.headTowards(destination)
-
- if (cityToConvert != null && unit.getTile().getCity() == destination.getCity()) {
- doReligiousAction(unit, destination)
- }
- }
-
- private fun determineBestInquisitorCityToConvert(
- unit: MapUnit,
- ): City? {
- if (unit.religion != unit.civ.religionManager.religion?.name || !unit.canDoLimitedAction(Constants.removeHeresy))
- return null
-
- val holyCity = unit.civ.religionManager.getHolyCity()
- if (holyCity != null && holyCity.religion.getMajorityReligion() != unit.civ.religionManager.religion!!)
- return holyCity
-
- val blockedHolyCity = unit.civ.cities.firstOrNull { it.religion.isBlockedHolyCity && it.religion.religionThisIsTheHolyCityOf == unit.religion }
- if (blockedHolyCity != null)
- return blockedHolyCity
-
- return unit.civ.cities.asSequence()
- .filter { it.religion.getMajorityReligion() != null }
- .filter { it.religion.getMajorityReligion()!! != unit.civ.religionManager.religion }
- // Don't go if it takes too long
- .filter { it.getCenterTile().aerialDistanceTo(unit.currentTile) <= 20 }
- .maxByOrNull { it.religion.getPressureDeficit(unit.civ.religionManager.religion?.name) }
- }
-
- fun automateFighter(unit: MapUnit) {
- val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
- val enemyAirUnitsInRange = tilesInRange
- .flatMap { it.airUnits.asSequence() }.filter { it.civ.isAtWarWith(unit.civ) }
-
- if (enemyAirUnitsInRange.any()) return // we need to be on standby in case they attack
-
- if (BattleHelper.tryAttackNearbyEnemy(unit)) return
-
- if (tryRelocateToCitiesWithEnemyNearBy(unit)) return
-
- val pathsToCities = unit.movement.getAerialPathsToCities()
- if (pathsToCities.isEmpty()) return // can't actually move anywhere else
-
- val citiesByNearbyAirUnits = pathsToCities.keys
- .groupBy { key ->
- key.getTilesInDistance(unit.getMaxMovementForAirUnits())
- .count {
- val firstAirUnit = it.airUnits.firstOrNull()
- firstAirUnit != null && firstAirUnit.civ.isAtWarWith(unit.civ)
- }
- }
-
- if (citiesByNearbyAirUnits.keys.any { it != 0 }) {
- val citiesWithMostNeedOfAirUnits = citiesByNearbyAirUnits.maxByOrNull { it.key }!!.value
- //todo: maybe group by size and choose highest priority within the same size turns
- val chosenCity = citiesWithMostNeedOfAirUnits.minByOrNull { pathsToCities.getValue(it).size }!! // city with min path = least turns to get there
- val firstStepInPath = pathsToCities.getValue(chosenCity).first()
- unit.movement.moveToTile(firstStepInPath)
- return
- }
-
- // no city needs fighters to defend, so let's attack stuff from the closest possible location
- tryMoveToCitiesToAerialAttackFrom(pathsToCities, unit)
-
- }
-
- fun automateBomber(unit: MapUnit) {
- if (BattleHelper.tryAttackNearbyEnemy(unit)) return
-
- if (tryRelocateToCitiesWithEnemyNearBy(unit)) return
-
- val pathsToCities = unit.movement.getAerialPathsToCities()
- if (pathsToCities.isEmpty()) return // can't actually move anywhere else
- tryMoveToCitiesToAerialAttackFrom(pathsToCities, unit)
- }
-
- private fun tryMoveToCitiesToAerialAttackFrom(pathsToCities: HashMap>, airUnit: MapUnit) {
- val citiesThatCanAttackFrom = pathsToCities.keys
- .filter { destinationCity ->
- destinationCity != airUnit.currentTile
- && destinationCity.getTilesInDistance(airUnit.getRange())
- .any { TargetHelper.containsAttackableEnemy(it, MapUnitCombatant(airUnit)) }
- }
- if (citiesThatCanAttackFrom.isEmpty()) return
-
- //todo: this logic looks similar to some parts of automateFighter, maybe pull out common code
- //todo: maybe group by size and choose highest priority within the same size turns
- val closestCityThatCanAttackFrom =
- citiesThatCanAttackFrom.minByOrNull { pathsToCities[it]!!.size }!!
- val firstStepInPath = pathsToCities[closestCityThatCanAttackFrom]!!.first()
- airUnit.movement.moveToTile(firstStepInPath)
- }
-
- fun automateNukes(unit: MapUnit) {
- if (!unit.civ.isAtWar()) return
- // We should *Almost* never want to nuke our own city, so don't consider it
- val tilesInRange = unit.currentTile.getTilesInDistanceRange(2..unit.getRange())
- var highestTileNukeValue = 0
- var tileToNuke: Tile? = null
- tilesInRange.forEach {
- val value = getNukeLocationValue(unit, it)
- if (value > highestTileNukeValue) {
- highestTileNukeValue = value
- tileToNuke = it
- }
- }
- if (highestTileNukeValue > 0) {
- Battle.NUKE(MapUnitCombatant(unit), tileToNuke!!)
- }
- tryRelocateToNearbyAttackableCities(unit)
- }
-
- /**
- * Ranks the tile to nuke based off of all tiles in it's blast radius
- * By default the value is -500 to prevent inefficient nuking.
- */
- fun getNukeLocationValue(nuke: MapUnit, tile: Tile): Int {
- val civ = nuke.civ
- if (!Battle.mayUseNuke(MapUnitCombatant(nuke), tile)) return Int.MIN_VALUE
- val blastRadius = nuke.getNukeBlastRadius()
- val tilesInBlastRadius = tile.getTilesInDistance(blastRadius)
- val civsInBlastRadius = tilesInBlastRadius.mapNotNull { it.getOwner() } +
- tilesInBlastRadius.mapNotNull { it.getFirstUnit()?.civ }
-
- // Don't nuke if it means we will be declaring war on someone!
- if (civsInBlastRadius.any { it != civ && !it.isAtWarWith(civ) }) return -100000
- // If there are no enemies to hit, don't nuke
- if (!civsInBlastRadius.any { it.isAtWarWith(civ) }) return -100000
-
- // Launching a Nuke uses resources, therefore don't launch it by default
- var explosionValue = -500
-
- // Returns either ourValue or thierValue depending on if the input Civ matches the Nuke's Civ
- fun evaluateCivValue(targetCiv: Civilization, ourValue: Int, theirValue: Int): Int {
- if (targetCiv == civ) // We are nuking something that we own!
- return ourValue
- return theirValue // We are nuking an enemy!
- }
- for (targetTile in tilesInBlastRadius) {
- // We can only account for visible units
- if (tile.isVisible(civ)) {
- if (targetTile.militaryUnit != null && !targetTile.militaryUnit!!.isInvisible(civ))
- explosionValue += evaluateCivValue(targetTile.militaryUnit?.civ!!, -150, 50)
- if (targetTile.civilianUnit != null && !targetTile.civilianUnit!!.isInvisible(civ))
- explosionValue += evaluateCivValue(targetTile.civilianUnit?.civ!!, -100, 25)
- }
- // Never nuke our own Civ, don't nuke single enemy civs as well
- if (targetTile.isCityCenter()
- && !(targetTile.getCity()!!.health <= 50f
- && targetTile.neighbors.any {it.militaryUnit?.civ == civ})) // Prefer not to nuke cities that we are about to take
- explosionValue += evaluateCivValue(targetTile.getCity()?.civ!!, -100000, 250)
- else if (targetTile.owningCity != null) {
- val owningCiv = targetTile.owningCity?.civ!!
- // If there is a tile to add fallout to there is a 50% chance it will get fallout
- if (!(tile.isWater || tile.isImpassible() || targetTile.terrainFeatures.any { it == "Fallout" }))
- explosionValue += evaluateCivValue(owningCiv, -40, 10)
- // If there is an improvment to pillage
- if (targetTile.improvement != null && !targetTile.improvementIsPillaged)
- explosionValue += evaluateCivValue(owningCiv, -40, 20)
- }
- // If the value is too low end the search early
- if (explosionValue < -1000) return explosionValue
- }
- return explosionValue
- }
-
- // This really needs to be changed, to have better targeting for missiles
- fun automateMissile(unit: MapUnit) {
- if (BattleHelper.tryAttackNearbyEnemy(unit)) return
- tryRelocateToNearbyAttackableCities(unit)
- }
-
- private fun tryRelocateToNearbyAttackableCities(unit: MapUnit) {
- val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
- val immediatelyReachableCities = tilesInRange
- .filter { unit.movement.canMoveTo(it) }
-
- for (city in immediatelyReachableCities) if (city.getTilesInDistance(unit.getRange())
- .any { it.isCityCenter() && it.getOwner()!!.isAtWarWith(unit.civ) }
- ) {
- unit.movement.moveToTile(city)
- return
- }
-
- if (unit.baseUnit.isAirUnit()) {
- val pathsToCities = unit.movement.getAerialPathsToCities()
- if (pathsToCities.isEmpty()) return // can't actually move anywhere else
- tryMoveToCitiesToAerialAttackFrom(pathsToCities, unit)
- } else UnitAutomation.tryHeadTowardsEnemyCity(unit)
- }
-
- private fun tryRelocateToCitiesWithEnemyNearBy(unit: MapUnit): Boolean {
- val immediatelyReachableCitiesAndCarriers = unit.currentTile
- .getTilesInDistance(unit.getMaxMovementForAirUnits()).filter { unit.movement.canMoveTo(it) }
-
- for (city in immediatelyReachableCitiesAndCarriers) {
- if (city.getTilesInDistance(unit.getRange())
- .any {
- it.isVisible(unit.civ) &&
- TargetHelper.containsAttackableEnemy(
- it,
- MapUnitCombatant(unit)
- )
- }) {
- unit.movement.moveToTile(city)
- return true
- }
- }
- return false
- }
-
- fun foundReligion(unit: MapUnit) {
- val cityToFoundReligionAt =
- if (unit.getTile().isCityCenter() && !unit.getTile().owningCity!!.isHolyCity()) unit.getTile().owningCity
- else unit.civ.cities.firstOrNull {
- !it.isHolyCity()
- && unit.movement.canMoveTo(it.getCenterTile())
- && unit.movement.canReach(it.getCenterTile())
- }
- if (cityToFoundReligionAt == null) return
- if (unit.getTile() != cityToFoundReligionAt.getCenterTile()) {
- unit.movement.headTowards(cityToFoundReligionAt.getCenterTile())
- return
- }
-
- UnitActionsReligion.getFoundReligionAction(unit)()
- }
-
- fun enhanceReligion(unit: MapUnit) {
- // Try go to a nearby city
- if (!unit.getTile().isCityCenter())
- UnitAutomation.tryEnterOwnClosestCity(unit)
-
- // If we were unable to go there this turn, unable to do anything else
- if (!unit.getTile().isCityCenter())
- return
-
- UnitActionsReligion.getEnhanceReligionAction(unit)()
- }
-
- private fun doReligiousAction(unit: MapUnit, destination: Tile) {
- val religiousActions = ArrayList()
- UnitActionsReligion.addActionsWithLimitedUses(unit, religiousActions, destination)
- if (religiousActions.firstOrNull()?.action == null) return
- religiousActions.first().action!!.invoke()
- }
}
diff --git a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt
index a438d1a3eeb5e..fafc48b8c13c8 100644
--- a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt
+++ b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt
@@ -3,7 +3,6 @@ package com.unciv.logic.automation.unit
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.automation.Automation
-import com.unciv.logic.automation.civilization.NextTurnAutomation
import com.unciv.logic.battle.Battle
import com.unciv.logic.battle.BattleDamage
import com.unciv.logic.battle.CityCombatant
@@ -11,15 +10,11 @@ import com.unciv.logic.battle.ICombatant
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.battle.TargetHelper
import com.unciv.logic.city.City
-import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
-import com.unciv.logic.civilization.managers.ReligionState
import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.logic.map.tile.Tile
-import com.unciv.models.UnitActionType
import com.unciv.models.ruleset.unique.UniqueType
-import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsPillage
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsUpgrade
@@ -155,7 +150,7 @@ object UnitAutomation {
if (unit.isCivilian()) {
- automateCivilianUnit(unit)
+ CivilianUnitAutomation.automateCivilianUnit(unit)
return
}
@@ -174,21 +169,21 @@ object UnitAutomation {
if ((unit.hasUnique(UniqueType.BuildImprovements) || unit.hasUnique(UniqueType.FoundCity) ||
unit.hasUnique(UniqueType.ReligiousUnit) || unit.hasUnique(UniqueType.CreateWaterImprovements))
&& !unit.civ.isAtWar()){
- automateCivilianUnit(unit)
+ CivilianUnitAutomation.automateCivilianUnit(unit)
return
}
if (unit.baseUnit.isAirUnit() && unit.canIntercept())
- return SpecificUnitAutomation.automateFighter(unit)
+ return AirUnitAutomation.automateFighter(unit)
if (unit.baseUnit.isAirUnit() && !unit.baseUnit.isNuclearWeapon())
- return SpecificUnitAutomation.automateBomber(unit)
+ return AirUnitAutomation.automateBomber(unit)
if (unit.baseUnit.isNuclearWeapon())
- return SpecificUnitAutomation.automateNukes(unit)
+ return AirUnitAutomation.automateNukes(unit)
- if (unit.hasUnique(UniqueType.SelfDestructs))
- return SpecificUnitAutomation.automateMissile(unit)
+ if (unit.baseUnit.isAirUnit() && unit.hasUnique(UniqueType.SelfDestructs))
+ return AirUnitAutomation.automateMissile(unit)
if (tryGoToRuinAndEncampment(unit) && unit.currentMovement == 0f) return
@@ -210,7 +205,7 @@ object UnitAutomation {
if (tryTakeBackCapturedCity(unit)) return
// Focus all units without a specific target on the enemy city closest to one of our cities
- if (tryHeadTowardsEnemyCity(unit)) return
+ if (HeadTowardsEnemyCityAutomation.tryHeadTowardsEnemyCity(unit)) return
if (tryGarrisoningRangedLandUnit(unit)) return
@@ -235,135 +230,6 @@ object UnitAutomation {
wander(unit, stayInTerritory = true)
}
- private fun automateCivilianUnit(unit: MapUnit) {
- if (tryRunAwayIfNeccessary(unit)) return
-
- if (unit.currentTile.isCityCenter() && unit.currentTile.getCity()!!.isCapital()
- && !unit.hasUnique(UniqueType.AddInCapital)
- && unit.civ.units.getCivUnits().any { unit.hasUnique(UniqueType.AddInCapital) }){
- // First off get out of the way, then decide if you actually want to do something else
- val tilesCanMoveTo = unit.movement.getDistanceToTiles()
- .filter { unit.movement.canMoveTo(it.key) }
- if (tilesCanMoveTo.isNotEmpty())
- unit.movement.moveToTile(tilesCanMoveTo.minByOrNull { it.value.totalDistance }!!.key)
- }
-
- val tilesWhereWeWillBeCaptured = unit.currentTile.getTilesInDistance(5)
- .mapNotNull { it.militaryUnit }
- .filter { it.civ.isAtWarWith(unit.civ) }
- .flatMap { it.movement.getReachableTilesInCurrentTurn() }
- .filter { it.militaryUnit?.civ != unit.civ }
- .toSet()
-
- if (unit.hasUnique(UniqueType.FoundCity))
- return SpecificUnitAutomation.automateSettlerActions(unit, tilesWhereWeWillBeCaptured)
-
- if (unit.cache.hasUniqueToBuildImprovements)
- return unit.civ.getWorkerAutomation().automateWorkerAction(unit, tilesWhereWeWillBeCaptured)
-
- if (unit.cache.hasUniqueToCreateWaterImprovements){
- if (!unit.civ.getWorkerAutomation().automateWorkBoats(unit))
- tryExplore(unit)
- return
- }
-
- if (unit.hasUnique(UniqueType.MayFoundReligion)
- && unit.civ.religionManager.religionState < ReligionState.Religion
- && unit.civ.religionManager.mayFoundReligionAtAll(unit)
- )
- return SpecificUnitAutomation.foundReligion(unit)
-
- if (unit.hasUnique(UniqueType.MayEnhanceReligion)
- && unit.civ.religionManager.religionState < ReligionState.EnhancedReligion
- && unit.civ.religionManager.mayEnhanceReligionAtAll(unit)
- )
- return SpecificUnitAutomation.enhanceReligion(unit)
-
- // We try to add any unit in the capital we can, though that might not always be desirable
- // For now its a simple option to allow AI to win a science victory again
- if (unit.hasUnique(UniqueType.AddInCapital))
- return SpecificUnitAutomation.automateAddInCapital(unit)
-
- //todo this now supports "Great General"-like mod units not combining 'aura' and citadel
- // abilities, but not additional capabilities if automation finds no use for those two
- if (unit.cache.hasStrengthBonusInRadiusUnique
- && SpecificUnitAutomation.automateGreatGeneral(unit))
- return
- if (unit.cache.hasCitadelPlacementUnique && SpecificUnitAutomation.automateCitadelPlacer(unit))
- return
- if (unit.cache.hasCitadelPlacementUnique || unit.cache.hasStrengthBonusInRadiusUnique)
- return SpecificUnitAutomation.automateGreatGeneralFallback(unit)
-
- if (unit.civ.religionManager.maySpreadReligionAtAll(unit))
- return SpecificUnitAutomation.automateMissionary(unit)
-
- if (unit.hasUnique(UniqueType.PreventSpreadingReligion) || unit.canDoLimitedAction(Constants.removeHeresy))
- return SpecificUnitAutomation.automateInquisitor(unit)
-
- val isLateGame = isLateGame(unit.civ)
- // Great scientist -> Hurry research if late game
- if (isLateGame) {
- val hurryResearch = UnitActions.getUnitActions(unit)
- .firstOrNull { it.type == UnitActionType.HurryResearch }?.action
- if (hurryResearch != null) {
- hurryResearch()
- return
- }
- }
-
- // Great merchant -> Conduct trade mission if late game and if not at war.
- // TODO: This could be more complex to walk to the city state that is most beneficial to
- // also have more influence.
- if (unit.hasUnique(UniqueType.CanTradeWithCityStateForGoldAndInfluence)
- // Don't wander around with the great merchant when at war. Barbs might also be a
- // problem, but hopefully by the time we have a great merchant, they're under control.
- && !unit.civ.isAtWar()
- && isLateGame
- ) {
- val tradeMissionCanBeConductedEventually =
- SpecificUnitAutomation.conductTradeMission(unit)
- if (tradeMissionCanBeConductedEventually)
- return
- }
-
- // Great engineer -> Try to speed up wonder construction if late game
- if (isLateGame &&
- (unit.hasUnique(UniqueType.CanSpeedupConstruction)
- || unit.hasUnique(UniqueType.CanSpeedupWonderConstruction))) {
- val wonderCanBeSpedUpEventually = SpecificUnitAutomation.speedupWonderConstruction(unit)
- if (wonderCanBeSpedUpEventually)
- return
- }
-
-
- // This has to come after the individual abilities for the great people that can also place
- // instant improvements (e.g. great scientist).
- if (unit.hasUnique(UniqueType.ConstructImprovementInstantly)
- ) {
- // catch great prophet for civs who can't found/enhance/spread religion
- // includes great people plus moddable units
- val improvementCanBePlacedEventually =
- SpecificUnitAutomation.automateImprovementPlacer(unit)
- if (!improvementCanBePlacedEventually)
- startGoldenAgeIfHasAbility(unit)
- }
-
- // TODO: The AI tends to have a lot of great generals. Maybe there should be a cutoff
- // (depending on number of cities) and after that they should just be used to start golden
- // ages?
-
- return // The AI doesn't know how to handle unknown civilian units
- }
-
- private fun isLateGame(civ: Civilization): Boolean {
- val researchCompletePercent =
- (civ.tech.researchedTechnologies.size * 1.0f) / civ.gameInfo.ruleset.technologies.size
- return researchCompletePercent >= 0.8f
- }
-
- private fun startGoldenAgeIfHasAbility(unit: MapUnit) {
- UnitActions.getUnitActions(unit).firstOrNull { it.type == UnitActionType.StartGoldenAge }?.action?.invoke()
- }
/** @return true only if the unit has 0 movement left */
private fun tryAttacking(unit: MapUnit): Boolean {
@@ -397,21 +263,7 @@ object UnitAutomation {
val currentUnitTile = unit.getTile()
- val nearbyRangedEnemyUnits = unit.currentTile.getTilesInDistance(3)
- .flatMap { tile -> tile.getUnits().filter { unit.civ.isAtWarWith(it.civ) } }
-
- val tilesInRangeOfAttack = nearbyRangedEnemyUnits
- .flatMap { it.getTile().getTilesInDistance(it.getRange()) }
-
- val tilesWithinBombardmentRange = unit.currentTile.getTilesInDistance(3)
- .filter { it.isCityCenter() && it.getCity()!!.civ.isAtWarWith(unit.civ) }
- .flatMap { it.getTilesInDistance(it.getCity()!!.range) }
-
- val tilesWithTerrainDamage = unit.currentTile.getTilesInDistance(3)
- .filter { unit.getDamageFromTerrain(it) > 0 }
-
- val dangerousTiles = (tilesInRangeOfAttack + tilesWithinBombardmentRange + tilesWithTerrainDamage).toHashSet()
-
+ val dangerousTiles = getDangerousTiles(unit)
val viableTilesForHealing = unitDistanceToTiles.keys
.filter { it !in dangerousTiles && unit.movement.canMoveTo(it) }
@@ -457,12 +309,29 @@ object UnitAutomation {
return true
}
+ private fun getDangerousTiles(unit: MapUnit): HashSet {
+ val nearbyRangedEnemyUnits = unit.currentTile.getTilesInDistance(3)
+ .flatMap { tile -> tile.getUnits().filter { unit.civ.isAtWarWith(it.civ) } }
+
+ val tilesInRangeOfAttack = nearbyRangedEnemyUnits
+ .flatMap { it.getTile().getTilesInDistance(it.getRange()) }
+
+ val tilesWithinBombardmentRange = unit.currentTile.getTilesInDistance(3)
+ .filter { it.isCityCenter() && it.getCity()!!.civ.isAtWarWith(unit.civ) }
+ .flatMap { it.getTilesInDistance(it.getCity()!!.range) }
+
+ val tilesWithTerrainDamage = unit.currentTile.getTilesInDistance(3)
+ .filter { unit.getDamageFromTerrain(it) > 0 }
+
+ return (tilesInRangeOfAttack + tilesWithinBombardmentRange + tilesWithTerrainDamage).toHashSet()
+ }
+
fun tryPillageImprovement(unit: MapUnit): Boolean {
if (unit.isCivilian()) return false
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
val tilesThatCanWalkToAndThenPillage = unitDistanceToTiles
.filter { it.value.totalDistance < unit.currentMovement }.keys
- .filter { unit.movement.canMoveTo(it) && UnitActions.canPillage(unit, it)
+ .filter { unit.movement.canMoveTo(it) && UnitActionsPillage.canPillage(unit, it)
&& (it.canPillageTileImprovement()
|| (it.canPillageRoad() && it.getRoadOwner() != null && unit.civ.isAtWarWith(it.getRoadOwner()!!)))}
@@ -548,114 +417,6 @@ object UnitAutomation {
return unit.currentMovement == 0f
}
- fun tryHeadTowardsEnemyCity(unit: MapUnit): Boolean {
- if (unit.civ.cities.isEmpty()) return false
-
- // only focus on *attacking* 1 enemy at a time otherwise you'll lose on both fronts
-
- val enemies = unit.civ.getKnownCivs()
- .filter { unit.civ.isAtWarWith(it) && it.cities.isNotEmpty() }
-
- val closestEnemyCity = enemies
- .mapNotNull { NextTurnAutomation.getClosestCities(unit.civ, it) }
- .minByOrNull { it.aerialDistance }?.city2
- ?: return false // no attackable cities found
-
- // Our main attack target is the closest city, but we're fine with deviating from that a bit
- var enemyCitiesByPriority = closestEnemyCity.civ.cities
- .associateWith { it.getCenterTile().aerialDistanceTo(closestEnemyCity.getCenterTile()) }
- .filterNot { it.value > 10 } // anything 10 tiles away from the target is irrelevant
- .asSequence().sortedBy { it.value }.map { it.key } // sort the list by closeness to target - least is best!
-
- if (unit.baseUnit.isRanged()) // ranged units don't harm capturable cities, waste of a turn
- enemyCitiesByPriority = enemyCitiesByPriority.filterNot { it.health == 1 }
-
- val closestReachableEnemyCity = enemyCitiesByPriority
- .firstOrNull { unit.movement.canReach(it.getCenterTile()) }
-
- if (closestReachableEnemyCity != null) {
- return headTowardsEnemyCity(
- unit,
- closestReachableEnemyCity.getCenterTile(),
- // This should be cached after the `canReach` call above.
- unit.movement.getShortestPath(closestReachableEnemyCity.getCenterTile())
- )
- }
- return false
- }
-
-
- private fun headTowardsEnemyCity(
- unit: MapUnit,
- closestReachableEnemyCity: Tile,
- shortestPath: List
- ): Boolean {
- val unitDistanceToTiles = unit.movement.getDistanceToTiles()
- val unitRange = unit.getRange()
-
- if (unitRange > 2) { // long-ranged unit, should never be in a bombardable position
- val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2).toSet()
- val tileToMoveTo =
- unitDistanceToTiles.asSequence()
- .filter {
- it.key.aerialDistanceTo(closestReachableEnemyCity) <=
- unitRange && it.key !in tilesInBombardRange
- && unit.getDamageFromTerrain(it.key) <= 0 // Don't set up on a mountain
- }
- .minByOrNull { it.value.totalDistance }?.key
-
- // move into position far away enough that the bombard doesn't hurt
- if (tileToMoveTo != null) {
- unit.movement.headTowards(tileToMoveTo)
- return true
- }
- return false
- }
-
- // None of the stuff below is relevant if we're still quite far away from the city, so we
- // short-circuit here for performance reasons.
- val minDistanceFromCityToConsiderForLandingArea = 3
- val maxDistanceFromCityToConsiderForLandingArea = 5
- if (unit.currentTile.aerialDistanceTo(closestReachableEnemyCity) > maxDistanceFromCityToConsiderForLandingArea
- // Even in the worst case of only being able to move 1 tile per turn, we would still
- // not overshoot.
- && shortestPath.size > minDistanceFromCityToConsiderForLandingArea ) {
- unit.movement.moveToTile(shortestPath[0])
- return true
- }
-
- val ourUnitsAroundEnemyCity = closestReachableEnemyCity.getTilesInDistance(6)
- .flatMap { it.getUnits() }
- .filter { it.isMilitary() && it.civ == unit.civ }
-
- val city = closestReachableEnemyCity.getCity()!!
- val cityCombatant = CityCombatant(city)
-
- val expectedDamagePerTurn = ourUnitsAroundEnemyCity
- .map { BattleDamage.calculateDamageToDefender(MapUnitCombatant(it), cityCombatant) }
- .sum() // City heals 20 per turn
-
- if (expectedDamagePerTurn < city.health && // If we can take immediately, go for it
- (expectedDamagePerTurn <= 20 || city.health / (expectedDamagePerTurn-20) > 5)){ // otherwise check if we can take within a couple of turns
-
- // We won't be able to take this even with 5 turns of continuous damage!
- // don't head straight to the city, try to head to landing grounds -
- // this is against tha AI's brilliant plan of having everyone embarked and attacking via sea when unnecessary.
- val tileToHeadTo = closestReachableEnemyCity.getTilesInDistanceRange(3..5)
- .filter { it.isLand && unit.getDamageFromTerrain(it) <= 0 } // Don't head for hurty terrain
- .sortedBy { it.aerialDistanceTo(unit.currentTile) }
- .firstOrNull { (unit.movement.canMoveTo(it) || it == unit.currentTile) && unit.movement.canReach(it) }
-
- if (tileToHeadTo != null) { // no need to worry, keep going as the movement alg. says
- unit.movement.headTowards(tileToHeadTo)
- }
- return true
- }
-
- unit.movement.moveToTile(shortestPath[0]) // go for it!
-
- return true
- }
fun tryEnterOwnClosestCity(unit: MapUnit): Boolean {
val closestCity = unit.civ.cities
@@ -720,7 +481,7 @@ object UnitAutomation {
.firstOrNull { unit.movement.canReach(it) }
if (closestReachableCapturedCity != null) {
- return headTowardsEnemyCity(
+ return HeadTowardsEnemyCityAutomation.headTowardsEnemyCity(
unit,
closestReachableCapturedCity,
// This should be cached after the `canReach` call above.
@@ -728,7 +489,6 @@ object UnitAutomation {
)
}
return false
-
}
private fun tryGarrisoningRangedLandUnit(unit: MapUnit): Boolean {
@@ -739,14 +499,6 @@ object UnitAutomation {
&& unit.movement.canMoveTo(centerTile)
}
- fun isCityThatNeedsDefendingInWartime(city: City): Boolean {
- if (city.health < city.getMaxHealth()) return true // this city is under attack!
- for (enemyCivCity in unit.civ.diplomacy.values
- .filter { it.diplomaticStatus == DiplomaticStatus.War }
- .map { it.otherCiv() }.flatMap { it.cities })
- if (city.getCenterTile().aerialDistanceTo(enemyCivCity.getCenterTile()) <= 5) return true // this is an edge city that needs defending
- return false
- }
val citiesToTry = if (!unit.civ.isAtWar()) {
if (unit.getTile().isCityCenter()) return true // It's always good to have a unit in the city center, so if you haven't found anyone around to attack, forget it.
@@ -768,6 +520,15 @@ object UnitAutomation {
return true
}
+ private fun isCityThatNeedsDefendingInWartime(city: City): Boolean {
+ if (city.health < city.getMaxHealth()) return true // this city is under attack!
+ for (enemyCivCity in city.civ.diplomacy.values
+ .filter { it.diplomaticStatus == DiplomaticStatus.War }
+ .map { it.otherCiv() }.flatMap { it.cities })
+ if (city.getCenterTile().aerialDistanceTo(enemyCivCity.getCenterTile()) <= 5) return true // this is an edge city that needs defending
+ return false
+ }
+
private fun tryStationingMeleeNavalUnit(unit: MapUnit): Boolean {
fun isMeleeNaval(mapUnit: MapUnit) = mapUnit.baseUnit.isMelee() && mapUnit.type.isWaterUnit()
@@ -807,54 +568,8 @@ object UnitAutomation {
unit.action = null
}
- /** Returns whether the civilian spends its turn hiding and not moving */
- private fun tryRunAwayIfNeccessary(unit: MapUnit): Boolean {
- // This is a little 'Bugblatter Beast of Traal': Run if we can attack an enemy
- // Cheaper than determining which enemies could attack us next turn
- val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
- .filter { containsEnemyMilitaryUnit(unit, it) }
-
- if (enemyUnitsInWalkingDistance.isNotEmpty() && !unit.baseUnit.isMilitary()
- && unit.getTile().militaryUnit == null && !unit.getTile().isCityCenter()) {
- runAway(unit)
- return true
- }
-
- return false
- }
-
- private fun runAway(unit: MapUnit) {
- val reachableTiles = unit.movement.getDistanceToTiles()
- val enterableCity = reachableTiles.keys
- .firstOrNull { it.isCityCenter() && unit.movement.canMoveTo(it) }
- if (enterableCity != null) {
- unit.movement.moveToTile(enterableCity)
- return
- }
- val defensiveUnit = reachableTiles.keys
- .firstOrNull {
- it.militaryUnit != null && it.militaryUnit!!.civ == unit.civ && it.civilianUnit == null
- }
- if (defensiveUnit != null) {
- unit.movement.moveToTile(defensiveUnit)
- return
- }
- val tileFurthestFromEnemy = reachableTiles.keys
- .filter { unit.movement.canMoveTo(it) && unit.getDamageFromTerrain(it) < unit.health }
- .maxByOrNull { countDistanceToClosestEnemy(unit, it) }
- ?: return // can't move anywhere!
- unit.movement.moveToTile(tileFurthestFromEnemy)
- }
-
-
- private fun countDistanceToClosestEnemy(unit: MapUnit, tile: Tile): Int {
- for (i in 1..3)
- if (tile.getTilesAtDistance(i).any { containsEnemyMilitaryUnit(unit, it) })
- return i
- return 4
- }
- private fun containsEnemyMilitaryUnit(unit: MapUnit, tile: Tile) =
+ internal fun containsEnemyMilitaryUnit(unit: MapUnit, tile: Tile) =
tile.militaryUnit != null
&& tile.militaryUnit!!.civ.isAtWarWith(unit.civ)
diff --git a/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt b/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt
index 3e60e8277cabb..a0a828a1e54ad 100644
--- a/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt
+++ b/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt
@@ -19,7 +19,7 @@ import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.UniqueType
-import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions
+import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsFromUniques
import com.unciv.utils.Log
import com.unciv.utils.debug
@@ -141,7 +141,7 @@ class WorkerAutomation(
if (unit.currentMovement > 0 && reachedTile == tileToWork) {
if (reachedTile.isPillaged()) {
debug("WorkerAutomation: ${unit.label()} -> repairs $reachedTile")
- UnitActions.getRepairAction(unit).invoke()
+ UnitActionsFromUniques.getRepairAction(unit)?.action?.invoke()
return
}
if (reachedTile.improvementInProgress == null && reachedTile.isLand
@@ -158,7 +158,7 @@ class WorkerAutomation(
if (currentTile.isPillaged()) {
debug("WorkerAutomation: ${unit.label()} -> repairs $currentTile")
- UnitActions.getRepairAction(unit).invoke()
+ UnitActionsFromUniques.getRepairAction(unit)?.action?.invoke()
return
}
@@ -587,7 +587,7 @@ class WorkerAutomation(
// all conditionals succeed with a current StateForConditionals(civ, unit)
// todo: Not necessarily the optimal flow: Be optimistic and head towards,
// then when arrived and the conditionals say "no" do something else instead?
- val action = UnitActions.getWaterImprovementAction(unit)
+ val action = UnitActionsFromUniques.getWaterImprovementAction(unit)
?: return false
// If action.action is null that means only transient reasons prevent the improvement -
diff --git a/core/src/com/unciv/logic/battle/AirInterception.kt b/core/src/com/unciv/logic/battle/AirInterception.kt
new file mode 100644
index 0000000000000..c982cf0442f68
--- /dev/null
+++ b/core/src/com/unciv/logic/battle/AirInterception.kt
@@ -0,0 +1,223 @@
+package com.unciv.logic.battle
+
+import com.unciv.UncivGame
+import com.unciv.logic.civilization.Civilization
+import com.unciv.logic.civilization.LocationAction
+import com.unciv.logic.civilization.NotificationCategory
+import com.unciv.logic.civilization.NotificationIcon
+import com.unciv.logic.map.mapunit.MapUnit
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.ruleset.unique.StateForConditionals
+import com.unciv.models.ruleset.unique.UniqueType
+import kotlin.random.Random
+
+object AirInterception {
+
+ // Should draw an Interception if available on the tile from any Civ
+ // Land Units deal 0 damage, and no XP for either party
+ // Air Interceptors do Air Combat as if Melee (mutual damage) but using Ranged Strength. 5XP to both
+ // But does not use the Interception mechanic bonuses/promotions
+ // Counts as an Attack for both units
+ // Will always draw out an Interceptor's attack (they cannot miss)
+ // This means the combat against Air Units will execute and always deal damage
+ // Random Civ at War will Intercept, prioritizing Air Units,
+ // sorted by highest Intercept chance (same as regular Intercept)
+ fun airSweep(attacker: MapUnitCombatant, attackedTile: Tile) {
+ // Air Sweep counts as an attack, even if nothing else happens
+ attacker.unit.attacksThisTurn++
+ // copied and modified from reduceAttackerMovementPointsAndAttacks()
+ // use up movement
+ if (attacker.unit.hasUnique(UniqueType.CanMoveAfterAttacking) || attacker.unit.maxAttacksPerTurn() > attacker.unit.attacksThisTurn) {
+ // if it was a melee attack and we won, then the unit ALREADY got movement points deducted,
+ // for the movement to the enemy's tile!
+ // and if it's an air unit, it only has 1 movement anyway, so...
+ if (!attacker.unit.baseUnit.movesLikeAirUnits())
+ attacker.unit.useMovementPoints(1f)
+ } else attacker.unit.currentMovement = 0f
+ val attackerName = attacker.getName()
+
+ // Make giant sequence of all potential Interceptors from all Civs isAtWarWith()
+ var potentialInterceptors = sequence { }
+ for (interceptingCiv in UncivGame.Current.gameInfo!!.civilizations
+ .filter {attacker.getCivInfo().isAtWarWith(it)}) {
+ potentialInterceptors += interceptingCiv.units.getCivUnits()
+ .filter { it.canIntercept(attackedTile) }
+ }
+
+ // first priority, only Air Units
+ if (potentialInterceptors.any { it.baseUnit.isAirUnit() })
+ potentialInterceptors = potentialInterceptors.filter { it.baseUnit.isAirUnit() }
+
+ // Pick highest chance interceptor
+ for (interceptor in potentialInterceptors
+ .shuffled() // randomize Civ
+ .sortedByDescending { it.interceptChance() }) {
+ // No chance of Interceptor to miss (unlike regular Interception). Always want to deal damage
+ // pairs of LocationAction for Notification
+ val locations = LocationAction(
+ interceptor.currentTile.position,
+ attacker.unit.currentTile.position
+ )
+ interceptor.attacksThisTurn++ // even if you miss, you took the shot
+ if (!interceptor.baseUnit.isAirUnit()) {
+ val interceptorName = interceptor.name
+ // Deal no damage (moddable in future?) and no XP
+ val attackerText =
+ "Our [$attackerName] ([-0] HP) was attacked by an intercepting [$interceptorName] ([-0] HP)"
+ val interceptorText =
+ "Our [$interceptorName] ([-0] HP) intercepted and attacked an enemy [$attackerName] ([-0] HP)"
+ attacker.getCivInfo().addNotification(
+ attackerText, locations, NotificationCategory.War,
+ attackerName, NotificationIcon.War, interceptorName
+ )
+ interceptor.civ.addNotification(
+ interceptorText, locations, NotificationCategory.War,
+ interceptorName, NotificationIcon.War, attackerName
+ )
+ attacker.unit.action = null
+ return
+ }
+
+ // Damage if Air v Air should work similar to Melee
+ val damageDealt: Battle.DamageDealt = Battle.takeDamage(attacker, MapUnitCombatant(interceptor))
+
+ // 5 XP to both
+ Battle.addXp(MapUnitCombatant(interceptor), 5, attacker)
+ Battle.addXp(attacker, 5, MapUnitCombatant(interceptor))
+
+ val locationsInterceptorUnknown =
+ LocationAction(attackedTile.position, attacker.unit.currentTile.position)
+
+ addAirSweepInterceptionNotifications(
+ attacker,
+ interceptor,
+ damageDealt,
+ locationsInterceptorUnknown,
+ locations
+ )
+ attacker.unit.action = null
+ return
+ }
+
+ // No Interceptions available
+ val attackerText = "Nothing tried to intercept our [$attackerName]"
+ attacker.getCivInfo().addNotification(attackerText, NotificationCategory.War, attackerName)
+ attacker.unit.action = null
+ }
+
+ // TODO: Check overlap with addInterceptionNotifications, and unify what we can
+ private fun addAirSweepInterceptionNotifications(
+ attacker: MapUnitCombatant,
+ interceptor: MapUnit,
+ damageDealt: Battle.DamageDealt,
+ locationsInterceptorUnknown: Sequence,
+ locations: Sequence
+ ) {
+ val attackerName = attacker.getName()
+ val interceptorName = interceptor.name
+
+ val attackerText =
+ if (attacker.isDefeated()) {
+ if (interceptor.getTile() in attacker.getCivInfo().viewableTiles)
+ "Our [$attackerName] ([-${damageDealt.defenderDealt}] HP) was destroyed by an intercepting [$interceptorName] ([-${damageDealt.attackerDealt}] HP)"
+ else "Our [$attackerName] ([-${damageDealt.defenderDealt}] HP) was destroyed by an unknown interceptor"
+ } else if (MapUnitCombatant(interceptor).isDefeated()) {
+ "Our [$attackerName] ([-${damageDealt.defenderDealt}] HP) destroyed an intercepting [$interceptorName] ([-${damageDealt.attackerDealt}] HP)"
+ } else "Our [$attackerName] ([-${damageDealt.defenderDealt}] HP) was attacked by an intercepting [$interceptorName] ([-${damageDealt.attackerDealt}] HP)"
+
+ attacker.getCivInfo().addNotification(
+ attackerText, locationsInterceptorUnknown, NotificationCategory.War,
+ attackerName, NotificationIcon.War, NotificationIcon.Question
+ )
+
+ val interceptorText =
+ if (attacker.isDefeated())
+ "Our [$interceptorName] ([-${damageDealt.attackerDealt}] HP) intercepted and destroyed an enemy [$attackerName] ([-${damageDealt.defenderDealt}] HP)"
+ else if (MapUnitCombatant(interceptor).isDefeated()) {
+ if (attacker.getTile() in interceptor.civ.viewableTiles) "Our [$interceptorName] ([-${damageDealt.attackerDealt}] HP) intercepted and was destroyed by an enemy [$attackerName] ([-${damageDealt.defenderDealt}] HP)"
+ else "Our [$interceptorName] ([-${damageDealt.attackerDealt}] HP) intercepted and was destroyed by an unknown enemy"
+ } else "Our [$interceptorName] ([-${damageDealt.attackerDealt}] HP) intercepted and attacked an enemy [$attackerName] ([-${damageDealt.defenderDealt}] HP)"
+
+ interceptor.civ.addNotification(
+ interceptorText, locations, NotificationCategory.War,
+ interceptorName, NotificationIcon.War, attackerName
+ )
+ }
+
+ internal fun tryInterceptAirAttack(
+ attacker: MapUnitCombatant,
+ attackedTile: Tile,
+ interceptingCiv: Civilization,
+ defender: ICombatant?
+ ): Battle.DamageDealt {
+ if (attacker.unit.hasUnique(UniqueType.CannotBeIntercepted, StateForConditionals(attacker.getCivInfo(), ourCombatant = attacker, theirCombatant = defender, attackedTile = attackedTile)))
+ return Battle.DamageDealt.None
+
+ // Pick highest chance interceptor
+ val interceptor = interceptingCiv.units.getCivUnits()
+ .filter { it.canIntercept(attackedTile) }
+ .sortedByDescending { it.interceptChance() }
+ .firstOrNull { unit ->
+ // Can't intercept if we have a unique preventing it
+ val conditionalState = StateForConditionals(interceptingCiv, ourCombatant = MapUnitCombatant(unit), theirCombatant = attacker, combatAction = CombatAction.Intercept, attackedTile = attackedTile)
+ unit.getMatchingUniques(UniqueType.CannotInterceptUnits, conditionalState)
+ .none { attacker.matchesCategory(it.params[0]) }
+ // Defender can't intercept either
+ && unit != (defender as? MapUnitCombatant)?.unit
+ }
+ ?: return Battle.DamageDealt.None
+
+ interceptor.attacksThisTurn++ // even if you miss, you took the shot
+ // Does Intercept happen? If not, exit
+ if (Random.Default.nextFloat() > interceptor.interceptChance() / 100f)
+ return Battle.DamageDealt.None
+
+ var damage = BattleDamage.calculateDamageToDefender(
+ MapUnitCombatant(interceptor),
+ attacker
+ )
+
+ var damageFactor = 1f + interceptor.interceptDamagePercentBonus().toFloat() / 100f
+ damageFactor *= attacker.unit.receivedInterceptDamageFactor()
+
+ damage = (damage.toFloat() * damageFactor).toInt().coerceAtMost(attacker.unit.health)
+
+ attacker.takeDamage(damage)
+ if (damage > 0)
+ Battle.addXp(MapUnitCombatant(interceptor), 2, attacker)
+
+ addInterceptionNotifications(attacker, interceptor, damage)
+
+ return Battle.DamageDealt(0, damage)
+ }
+
+ private fun addInterceptionNotifications(
+ attacker: MapUnitCombatant,
+ interceptor: MapUnit,
+ damage: Int
+ ) {
+ val attackerName = attacker.getName()
+ val interceptorName = interceptor.name
+
+ val locations = LocationAction(interceptor.currentTile.position, attacker.unit.currentTile.position)
+ val attackerText = if (!attacker.isDefeated())
+ "Our [$attackerName] ([-$damage] HP) was attacked by an intercepting [$interceptorName] ([-0] HP)"
+ else if (interceptor.getTile() in attacker.getCivInfo().viewableTiles)
+ "Our [$attackerName] ([-$damage] HP) was destroyed by an intercepting [$interceptorName] ([-0] HP)"
+ else "Our [$attackerName] ([-$damage] HP) was destroyed by an unknown interceptor"
+
+ attacker.getCivInfo().addNotification(
+ attackerText, interceptor.currentTile.position, NotificationCategory.War,
+ attackerName, NotificationIcon.War, interceptorName
+ )
+
+ val interceptorText = if (attacker.isDefeated())
+ "Our [$interceptorName] ([-0] HP) intercepted and destroyed an enemy [$attackerName] ([-$damage] HP)"
+ else "Our [$interceptorName] ([-0] HP) intercepted and attacked an enemy [$attackerName] ([-$damage] HP)"
+ interceptor.civ.addNotification(
+ interceptorText, locations, NotificationCategory.War,
+ interceptorName, NotificationIcon.War, attackerName
+ )
+ }
+
+}
diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt
index f766ac7298342..c928479f2a470 100644
--- a/core/src/com/unciv/logic/battle/Battle.kt
+++ b/core/src/com/unciv/logic/battle/Battle.kt
@@ -4,7 +4,6 @@ import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.automation.civilization.NextTurnAutomation
-import com.unciv.logic.automation.unit.SpecificUnitAutomation
import com.unciv.logic.city.City
import com.unciv.logic.civilization.AlertType
import com.unciv.logic.civilization.Civilization
@@ -12,28 +11,20 @@ import com.unciv.logic.civilization.LocationAction
import com.unciv.logic.civilization.MapUnitAction
import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.NotificationIcon
-import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.civilization.PopupAlert
import com.unciv.logic.civilization.PromoteUnitAction
-import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
-import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
-import com.unciv.logic.map.mapunit.MapUnit
-import com.unciv.logic.map.tile.RoadStatus
import com.unciv.logic.map.tile.Tile
import com.unciv.models.UnitActionType
-import com.unciv.models.helpers.UnitMovementMemoryType
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueTriggerActivation
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats
-import com.unciv.ui.components.extensions.toPercent
-import com.unciv.ui.screens.worldscreen.bottombar.BattleTable
+import com.unciv.ui.components.UnitMovementMemoryType
import com.unciv.utils.debug
import kotlin.math.max
import kotlin.math.min
-import kotlin.math.ulp
import kotlin.random.Random
/**
@@ -43,7 +34,7 @@ object Battle {
/**
* Moves [attacker] to [attackableTile], handles siege setup then attacks if still possible
- * (by calling [attack] or [NUKE]). Does _not_ play the attack sound!
+ * (by calling [attack] or [Nuke.NUKE]). Does _not_ play the attack sound!
*
* Currently not used by UI, only by automation via [BattleHelper.tryAttackNearbyEnemy][com.unciv.logic.automation.unit.BattleHelper.tryAttackNearbyEnemy]
*/
@@ -91,7 +82,7 @@ object Battle {
*/
fun attackOrNuke(attacker: ICombatant, attackableTile: AttackableTile): DamageDealt {
return if (attacker is MapUnitCombatant && attacker.unit.baseUnit.isNuclearWeapon()) {
- NUKE(attacker, attackableTile.tileToAttack)
+ Nuke.NUKE(attacker, attackableTile.tileToAttack)
DamageDealt.None
} else {
attack(attacker, getMapCombatantOfTile(attackableTile.tileToAttack)!!)
@@ -113,7 +104,7 @@ object Battle {
val interceptDamage: DamageDealt
if (attacker is MapUnitCombatant && attacker.unit.baseUnit.isAirUnit()) {
- interceptDamage = tryInterceptAirAttack(attacker, attackedTile, defender.getCivInfo(), defender)
+ interceptDamage = AirInterception.tryInterceptAirAttack(attacker, attackedTile, defender.getCivInfo(), defender)
if (attacker.isDefeated()) return interceptDamage
} else interceptDamage = DamageDealt.None
@@ -133,7 +124,7 @@ object Battle {
// check if unit is captured by the attacker (prize ships unique)
// As ravignir clarified in issue #4374, this only works for aggressor
- val captureMilitaryUnitSuccess = tryCaptureUnit(attacker, defender, attackedTile)
+ val captureMilitaryUnitSuccess = BattleUnitCapture.tryCaptureMilitaryUnit(attacker, defender, attackedTile)
if (!captureMilitaryUnitSuccess) // capture creates a new unit, but `defender` still is the original, so this function would still show a kill message
postBattleNotifications(attacker, defender, attackedTile, attacker.getTile(), damageDealt)
@@ -210,7 +201,7 @@ object Battle {
return damageDealt + interceptDamage
}
- private fun triggerDefeatUniques(ourUnit: MapUnitCombatant, enemy: ICombatant, attackedTile: Tile){
+ internal fun triggerDefeatUniques(ourUnit: MapUnitCombatant, enemy: ICombatant, attackedTile: Tile){
val stateForConditionals = StateForConditionals(civInfo = ourUnit.getCivInfo(),
ourCombatant = ourUnit, theirCombatant=enemy, tile = attackedTile)
for (unique in ourUnit.unit.getTriggeredUniques(UniqueType.TriggerUponDefeat, stateForConditionals))
@@ -266,90 +257,6 @@ object Battle {
}
}
- private fun tryCaptureUnit(attacker: ICombatant, defender: ICombatant, attackedTile: Tile): Boolean {
- // https://forums.civfanatics.com/threads/prize-ships-for-land-units.650196/
- // https://civilization.fandom.com/wiki/Module:Data/Civ5/GK/Defines\
- // There are 3 ways of capturing a unit, we separate them for cleaner code but we also need to ensure a unit isn't captured twice
-
- if (defender !is MapUnitCombatant || attacker !is MapUnitCombatant) return false
-
- if (!defender.isDefeated() || defender.unit.isCivilian()) return false
-
- fun unitCapturedPrizeShipsUnique(): Boolean {
- if (attacker.unit.getMatchingUniques(UniqueType.KillUnitCapture)
- .none { defender.matchesCategory(it.params[0]) }
- ) return false
-
- val captureChance = min(
- 0.8f,
- 0.1f + attacker.getAttackingStrength().toFloat() / defender.getDefendingStrength()
- .toFloat() * 0.4f
- )
- /** Between 0 and 1. Defaults to turn and location-based random to avoid save scumming */
- val random = Random((attacker.getCivInfo().gameInfo.turns * defender.getTile().position.hashCode()).toLong())
- return random.nextFloat() <= captureChance
- }
-
- fun unitGainFromEncampment(): Boolean {
- if (!defender.getCivInfo().isBarbarian()) return false
- if (attackedTile.improvement != Constants.barbarianEncampment) return false
-
- var unitCaptured = false
- // German unique - needs to be checked before we try to move to the enemy tile, since the encampment disappears after we move in
-
- for (unique in attacker.getCivInfo()
- .getMatchingUniques(UniqueType.GainFromEncampment)) {
- attacker.getCivInfo().addGold(unique.params[0].toInt())
- unitCaptured = true
- }
- return unitCaptured
- }
-
-
- fun unitGainFromDefeatingUnit(): Boolean {
- if (!attacker.isMelee()) return false
- var unitCaptured = false
- val state = StateForConditionals(attacker.getCivInfo(), ourCombatant = attacker, theirCombatant = defender)
- for (unique in attacker.getMatchingUniques(UniqueType.GainFromDefeatingUnit, state, true)) {
- if (defender.unit.matchesFilter(unique.params[0])) {
- attacker.getCivInfo().addGold(unique.params[1].toInt())
- unitCaptured = true
- }
- }
- return unitCaptured
- }
-
- // Due to the way OR operators short-circuit, calling just A() || B() means B isn't called if A is true.
- // Therefore we run all functions before checking if one is true.
- val wasUnitCaptured = listOf(
- unitCapturedPrizeShipsUnique(),
- unitGainFromEncampment(),
- unitGainFromDefeatingUnit()
- ).any { it }
-
- if (!wasUnitCaptured) return false
-
- // This is called after takeDamage and so the defeated defender is already destroyed and
- // thus removed from the tile - but MapUnit.destroy() will not clear the unit's currentTile.
- // Therefore placeUnitNearTile _will_ place the new unit exactly where the defender was
- return spawnCapturedUnit(defender.getName(), attacker, defender.getTile())
- }
-
- /** Places a [unitName] unit near [tile] after being attacked by [attacker].
- * Adds a notification to [attacker]'s civInfo and returns whether the captured unit could be placed */
- private fun spawnCapturedUnit(unitName: String, attacker: ICombatant, tile: Tile): Boolean {
- val addedUnit = attacker.getCivInfo().units.placeUnitNearTile(tile.position, unitName) ?: return false
- addedUnit.currentMovement = 0f
- addedUnit.health = 50
- attacker.getCivInfo().addNotification("An enemy [${unitName}] has joined us!", addedUnit.getTile().position, NotificationCategory.War, unitName)
-
- val civilianUnit = tile.civilianUnit
- // placeUnitNearTile might not have spawned the unit in exactly this tile, in which case no capture would have happened on this tile. So we need to do that here.
- if (addedUnit.getTile() != tile && civilianUnit != null) {
- captureCivilianUnit(attacker, MapUnitCombatant(civilianUnit))
- }
- return true
- }
/** Holder for battle result - actual damage.
* @param attackerDealt Damage done by attacker to defender
@@ -363,7 +270,7 @@ object Battle {
}
}
- private fun takeDamage(attacker: ICombatant, defender: ICombatant): DamageDealt {
+ internal fun takeDamage(attacker: ICombatant, defender: ICombatant): DamageDealt {
var potentialDamageToDefender = BattleDamage.calculateDamageToDefender(attacker, defender)
var potentialDamageToAttacker = BattleDamage.calculateDamageToAttacker(attacker, defender)
@@ -371,7 +278,7 @@ object Battle {
val defenderHealthBefore = defender.getHealth()
if (defender is MapUnitCombatant && defender.unit.isCivilian() && attacker.isMelee()) {
- captureCivilianUnit(attacker, defender)
+ BattleUnitCapture.captureCivilianUnit(attacker, defender)
} else if (attacker.isRanged() && !attacker.isAirUnit()) { // Air Units are Ranged, but take damage as well
defender.takeDamage(potentialDamageToDefender) // straight up
} else {
@@ -438,7 +345,7 @@ object Battle {
}
}
- private fun postBattleNotifications(
+ internal fun postBattleNotifications(
attacker: ICombatant,
defender: ICombatant,
attackedTile: Tile,
@@ -543,7 +450,7 @@ object Battle {
}
// XP!
- private fun addXp(thisCombatant: ICombatant, amount: Int, otherCombatant: ICombatant) {
+ internal fun addXp(thisCombatant: ICombatant, amount: Int, otherCombatant: ICombatant) {
if (thisCombatant !is MapUnitCombatant) return
val civ = thisCombatant.getCivInfo()
val otherIsBarbarian = otherCombatant.getCivInfo().isBarbarian()
@@ -600,7 +507,7 @@ object Battle {
city.hasJustBeenConquered = true
city.getCenterTile().apply {
if (militaryUnit != null) militaryUnit!!.destroy()
- if (civilianUnit != null) captureCivilianUnit(attacker, MapUnitCombatant(civilianUnit!!), checkDefeat = false)
+ if (civilianUnit != null) BattleUnitCapture.captureCivilianUnit(attacker, MapUnitCombatant(civilianUnit!!), checkDefeat = false)
for (airUnit in airUnits.toList()) airUnit.destroy()
}
@@ -625,9 +532,7 @@ object Battle {
} else if (attackerCiv.isHuman()) {
// we're not taking our former capital
attackerCiv.popupAlerts.add(PopupAlert(AlertType.CityConquered, city.id))
- } else {
- NextTurnAutomation.onConquerCity(attackerCiv, city)
- }
+ } else automateCityConquer(attackerCiv, city)
if (attackerCiv.isCurrentPlayer())
UncivGame.Current.settings.addCompletedTutorialTask("Conquer a city")
@@ -637,108 +542,36 @@ object Battle {
UniqueTriggerActivation.triggerCivwideUnique(unique, attackerCiv, city)
}
- fun getMapCombatantOfTile(tile: Tile): ICombatant? {
- if (tile.isCityCenter()) return CityCombatant(tile.getCity()!!)
- if (tile.militaryUnit != null) return MapUnitCombatant(tile.militaryUnit!!)
- if (tile.civilianUnit != null) return MapUnitCombatant(tile.civilianUnit!!)
- return null
- }
-
- /**
- * @throws IllegalArgumentException if the [attacker] and [defender] belong to the same civ.
- */
- fun captureCivilianUnit(attacker: ICombatant, defender: MapUnitCombatant, checkDefeat: Boolean = true) {
- require(attacker.getCivInfo() != defender.getCivInfo()) {
- "Can't capture our own unit!"
- }
-
- // need to save this because if the unit is captured its owner wil be overwritten
- val defenderCiv = defender.getCivInfo()
-
- val capturedUnit = defender.unit
- // Stop current action
- capturedUnit.action = null
-
- val capturedUnitTile = capturedUnit.getTile()
- val originalOwner = if (capturedUnit.originalOwner != null)
- capturedUnit.civ.gameInfo.getCivilization(capturedUnit.originalOwner!!)
- else null
-
- var wasDestroyedInstead = false
- when {
- // Uncapturable units are destroyed
- defender.unit.hasUnique(UniqueType.Uncapturable) -> {
- capturedUnit.destroy()
- wasDestroyedInstead = true
- }
- // City states can never capture settlers at all
- capturedUnit.hasUnique(UniqueType.FoundCity) && attacker.getCivInfo().isCityState() -> {
- capturedUnit.destroy()
- wasDestroyedInstead = true
- }
- // Is it our old unit?
- attacker.getCivInfo() == originalOwner -> {
- // Then it is recaptured without converting settlers to workers
- capturedUnit.capturedBy(attacker.getCivInfo())
- }
- // Return captured civilian to its original owner?
- defender.getCivInfo().isBarbarian()
- && originalOwner != null
- && !originalOwner.isBarbarian()
- && attacker.getCivInfo() != originalOwner
- && attacker.getCivInfo().knows(originalOwner)
- && originalOwner.isAlive()
- && !attacker.getCivInfo().isAtWarWith(originalOwner)
- && attacker.getCivInfo().playerType == PlayerType.Human // Only humans get the choice
- -> {
- capturedUnit.capturedBy(attacker.getCivInfo())
- attacker.getCivInfo().popupAlerts.add(
- PopupAlert(
- AlertType.RecapturedCivilian,
- capturedUnitTile.position.toString()
- )
- )
+ /** Handle decision making after city conquest, namely whether the AI should liberate, puppet,
+ * or raze a city */
+ private fun automateCityConquer(civInfo: Civilization, city: City) {
+ if (!city.hasDiplomaticMarriage()) {
+ val foundingCiv = civInfo.gameInfo.getCivilization(city.foundingCiv)
+ var valueAlliance = NextTurnAutomation.valueCityStateAlliance(civInfo, foundingCiv)
+ if (civInfo.getHappiness() < 0)
+ valueAlliance -= civInfo.getHappiness() // put extra weight on liberating if unhappy
+ if (foundingCiv.isCityState() && city.civ != civInfo && foundingCiv != civInfo
+ && !civInfo.isAtWarWith(foundingCiv)
+ && valueAlliance > 0) {
+ city.liberateCity(civInfo)
+ return
}
-
- else -> captureOrConvertToWorker(capturedUnit, attacker.getCivInfo())
}
- if (!wasDestroyedInstead)
- defenderCiv.addNotification(
- "An enemy [${attacker.getName()}] has captured our [${defender.getName()}]",
- defender.getTile().position, NotificationCategory.War, attacker.getName(),
- NotificationIcon.War, defender.getName()
- )
- else {
- defenderCiv.addNotification(
- "An enemy [${attacker.getName()}] has destroyed our [${defender.getName()}]",
- defender.getTile().position, NotificationCategory.War, attacker.getName(),
- NotificationIcon.War, defender.getName()
- )
- triggerDefeatUniques(defender, attacker, capturedUnitTile)
+ city.puppetCity(civInfo)
+ if ((city.population.population < 4 || civInfo.isCityState())
+ && city.foundingCiv != civInfo.civName && city.canBeDestroyed(justCaptured = true)) {
+ // raze if attacker is a city state
+ if (!civInfo.hasUnique(UniqueType.MayNotAnnexCities)) city.annexCity()
+ city.isBeingRazed = true
}
-
- if (checkDefeat)
- destroyIfDefeated(defenderCiv, attacker.getCivInfo())
- capturedUnit.updateVisibleTiles()
}
- fun captureOrConvertToWorker(capturedUnit: MapUnit, capturingCiv: Civilization){
- // Captured settlers are converted to workers unless captured by barbarians (so they can be returned later).
- if (capturedUnit.hasUnique(UniqueType.FoundCity) && !capturingCiv.isBarbarian()) {
- capturedUnit.destroy()
- // This is so that future checks which check if a unit has been captured are caught give the right answer
- // For example, in postBattleMoveToAttackedTile
- capturedUnit.civ = capturingCiv
-
- val workerTypeUnit = capturingCiv.gameInfo.ruleset.units.values
- .firstOrNull { it.isCivilian() && it.getMatchingUniques(UniqueType.BuildImprovements)
- .any { unique -> unique.params[0] == "Land" } }
-
- if (workerTypeUnit != null)
- capturingCiv.units.placeUnitNearTile(capturedUnit.currentTile.position, workerTypeUnit)
- }
- else capturedUnit.capturedBy(capturingCiv)
+ fun getMapCombatantOfTile(tile: Tile): ICombatant? {
+ if (tile.isCityCenter()) return CityCombatant(tile.getCity()!!)
+ if (tile.militaryUnit != null) return MapUnitCombatant(tile.militaryUnit!!)
+ if (tile.civilianUnit != null) return MapUnitCombatant(tile.civilianUnit!!)
+ return null
}
fun destroyIfDefeated(attackedCiv: Civilization, attacker: Civilization) {
@@ -750,397 +583,6 @@ object Battle {
}
}
- /**
- * Checks whether [nuke] is allowed to nuke [targetTile]
- * - Not if we would need to declare war on someone we can't.
- * - Disallow nuking the tile the nuke is in, as per Civ5 (but not nuking your own tiles/units otherwise)
- *
- * Both [BattleTable.simulateNuke] and [SpecificUnitAutomation.automateNukes] check range, so that check is omitted here.
- */
- fun mayUseNuke(nuke: MapUnitCombatant, targetTile: Tile): Boolean {
- if (nuke.getTile() == targetTile) return false
- // Can only nuke visible Tiles
- if (!targetTile.isVisible(nuke.getCivInfo())) return false
-
- var canNuke = true
- val attackerCiv = nuke.getCivInfo()
- fun checkDefenderCiv(defenderCiv: Civilization?) {
- if (defenderCiv == null) return
- // Allow nuking yourself! (Civ5 source: CvUnit::isNukeVictim)
- if (defenderCiv == attackerCiv || defenderCiv.isDefeated()) return
- // Gleaned from Civ5 source - this disallows nuking unknown civs even in invisible tiles
- // https://github.com/Gedemon/Civ5-DLL/blob/master/CvGameCoreDLL_Expansion1/CvUnit.cpp#L5056
- // https://github.com/Gedemon/Civ5-DLL/blob/master/CvGameCoreDLL_Expansion1/CvTeam.cpp#L986
- if (attackerCiv.knows(defenderCiv) && attackerCiv.getDiplomacyManager(defenderCiv).canAttack())
- return
- canNuke = false
- }
-
- val blastRadius = nuke.unit.getNukeBlastRadius()
- for (tile in targetTile.getTilesInDistance(blastRadius)) {
- checkDefenderCiv(tile.getOwner())
- checkDefenderCiv(getMapCombatantOfTile(tile)?.getCivInfo())
- }
- return canNuke
- }
-
- @Suppress("FunctionName") // Yes we want this name to stand out
- fun NUKE(attacker: MapUnitCombatant, targetTile: Tile) {
- val attackingCiv = attacker.getCivInfo()
- fun tryDeclareWar(civSuffered: Civilization) {
- if (civSuffered != attackingCiv
- && civSuffered.knows(attackingCiv)
- && civSuffered.getDiplomacyManager(attackingCiv).diplomaticStatus != DiplomaticStatus.War
- ) {
- attackingCiv.getDiplomacyManager(civSuffered).declareWar()
- attackingCiv.addNotification("After being hit by our [${attacker.getName()}], [${civSuffered}] has declared war on us!", targetTile.position, NotificationCategory.Diplomacy, NotificationIcon.War)
- }
- }
-
- val nukeStrength = attacker.unit.getMatchingUniques(UniqueType.NuclearWeapon)
- .firstOrNull()?.params?.get(0)?.toInt() ?: return
-
- val blastRadius = attacker.unit.getMatchingUniques(UniqueType.BlastRadius)
- .firstOrNull()?.params?.get(0)?.toInt() ?: 2
-
- // Calculate the tiles that are hit
- val hitTiles = targetTile.getTilesInDistance(blastRadius)
-
- // Declare war on the owners of all hit tiles
- for (hitCiv in hitTiles.mapNotNull { it.getOwner() }.distinct()) {
- hitCiv.addNotification("A(n) [${attacker.getName()}] exploded in our territory!", targetTile.position, NotificationCategory.War, NotificationIcon.War)
- tryDeclareWar(hitCiv)
- }
-
- // Declare war on all potentially hit units. They'll try to intercept the nuke before it drops
- for (civWhoseUnitWasAttacked in hitTiles
- .flatMap { it.getUnits() }
- .map { it.civ }.distinct()
- .filter { it != attackingCiv }) {
- tryDeclareWar(civWhoseUnitWasAttacked)
- if (attacker.unit.baseUnit.isAirUnit() && !attacker.isDefeated()) {
- tryInterceptAirAttack(attacker, targetTile, civWhoseUnitWasAttacked, null)
- }
- }
- if (attacker.isDefeated()) return
-
- attacker.unit.attacksSinceTurnStart.add(Vector2(targetTile.position))
-
- for (tile in hitTiles) {
- // Handle complicated effects
- doNukeExplosionForTile(attacker, tile, nukeStrength, targetTile == tile)
- }
-
- // Instead of postBattleAction() just destroy the unit, all other functions are not relevant
- if (attacker.unit.hasUnique(UniqueType.SelfDestructs)) attacker.unit.destroy()
-
- // It's unclear whether using nukes results in a penalty with all civs, or only affected civs.
- // For now I'll make it give a diplomatic penalty to all known civs, but some testing for this would be appreciated
- for (civ in attackingCiv.getKnownCivs()) {
- civ.getDiplomacyManager(attackingCiv).setModifier(DiplomaticModifiers.UsedNuclearWeapons, -50f)
- }
-
- if (!attacker.isDefeated()) {
- attacker.unit.attacksThisTurn += 1
- }
- }
-
- private fun doNukeExplosionForTile(
- attacker: MapUnitCombatant,
- tile: Tile,
- nukeStrength: Int,
- isGroundZero: Boolean
- ) {
- // https://forums.civfanatics.com/resources/unit-guide-modern-future-units-g-k.25628/
- // https://www.carlsguides.com/strategy/civilization5/units/aircraft-nukes.ph
- // Testing done by Ravignir
- // original source code: GenerateNuclearExplosionDamage(), ApplyNuclearExplosionDamage()
-
- var damageModifierFromMissingResource = 1f
- val civResources = attacker.getCivInfo().getCivResourcesByName()
- for (resource in attacker.unit.baseUnit.getResourceRequirementsPerTurn().keys) {
- if (civResources[resource]!! < 0 && !attacker.getCivInfo().isBarbarian())
- damageModifierFromMissingResource *= 0.5f // I could not find a source for this number, but this felt about right
- // - Original Civ5 does *not* reduce damage from missing resource, from source inspection
- }
-
- var buildingModifier = 1f // Strange, but in Civ5 a bunker mitigates damage to garrison, even if the city is destroyed by the nuke
-
- // Damage city and reduce its population
- val city = tile.getCity()
- if (city != null && tile.position == city.location) {
- buildingModifier = city.getAggregateModifier(UniqueType.GarrisonDamageFromNukes)
- doNukeExplosionDamageToCity(city, nukeStrength, damageModifierFromMissingResource)
- postBattleNotifications(attacker, CityCombatant(city), city.getCenterTile())
- destroyIfDefeated(city.civ, attacker.getCivInfo())
- }
-
- // Damage and/or destroy units on the tile
- for (unit in tile.getUnits().toList()) { // toList so if it's destroyed there's no concurrent modification
- val damage = (when {
- isGroundZero || nukeStrength >= 2 -> 100
- // The following constants are NUKE_UNIT_DAMAGE_BASE / NUKE_UNIT_DAMAGE_RAND_1 / NUKE_UNIT_DAMAGE_RAND_2 in Civ5
- nukeStrength == 1 -> 30 + Random.Default.nextInt(40) + Random.Default.nextInt(40)
- // Level 0 does not exist in Civ5 (it treats units same as level 2)
- else -> 20 + Random.Default.nextInt(30)
- } * buildingModifier * damageModifierFromMissingResource + 1f.ulp).toInt()
- val defender = MapUnitCombatant(unit)
- if (unit.isCivilian()) {
- if (unit.health - damage <= 40) unit.destroy() // Civ5: NUKE_NON_COMBAT_DEATH_THRESHOLD = 60
- } else {
- defender.takeDamage(damage)
- }
- postBattleNotifications(attacker, defender, defender.getTile())
- destroyIfDefeated(defender.getCivInfo(), attacker.getCivInfo())
- }
-
- // Pillage improvements, pillage roads, add fallout
- if (tile.isCityCenter()) return // Never touch city centers - if they survived
- fun applyPillageAndFallout() {
- if (tile.getUnpillagedImprovement() != null && !tile.getTileImprovement()!!.hasUnique(UniqueType.Irremovable)) {
- if (tile.getTileImprovement()!!.hasUnique(UniqueType.Unpillagable)) {
- tile.removeImprovement()
- } else {
- tile.setPillaged()
- }
- }
- if (tile.getUnpillagedRoad() != RoadStatus.None)
- tile.setPillaged()
- if (tile.isWater || tile.isImpassible() || tile.terrainFeatures.contains("Fallout")) return
- tile.addTerrainFeature("Fallout")
- }
-
- if (tile.terrainHasUnique(UniqueType.DestroyableByNukesChance)) {
- // Note: Safe from concurrent modification exceptions only because removeTerrainFeature
- // *replaces* terrainFeatureObjects and the loop will continue on the old one
- for (terrainFeature in tile.terrainFeatureObjects) {
- for (unique in terrainFeature.getMatchingUniques(UniqueType.DestroyableByNukesChance)) {
- val chance = unique.params[0].toFloat() / 100f
- if (!(chance > 0f && isGroundZero) && Random.Default.nextFloat() >= chance) continue
- tile.removeTerrainFeature(terrainFeature.name)
- applyPillageAndFallout()
- }
- }
- } else if (isGroundZero || Random.Default.nextFloat() < 0.5f) { // Civ5: NUKE_FALLOUT_PROB
- applyPillageAndFallout()
- }
- }
-
- /** @return the "protection" modifier from buildings (Bomb Shelter, UniqueType.PopulationLossFromNukes) */
- private fun doNukeExplosionDamageToCity(targetedCity: City, nukeStrength: Int, damageModifierFromMissingResource: Float) {
- // Original Capitals must be protected, `canBeDestroyed` is responsible for that check.
- // The `justCaptured = true` parameter is what allows other Capitals to suffer normally.
- if ((nukeStrength > 2 || nukeStrength > 1 && targetedCity.population.population < 5)
- && targetedCity.canBeDestroyed(true)) {
- targetedCity.destroyCity()
- return
- }
-
- val cityCombatant = CityCombatant(targetedCity)
- cityCombatant.takeDamage((cityCombatant.getHealth() * 0.5f * damageModifierFromMissingResource).toInt())
-
- // Difference to original: Civ5 rounds population loss down twice - before and after bomb shelters
- val populationLoss = (
- targetedCity.population.population *
- targetedCity.getAggregateModifier(UniqueType.PopulationLossFromNukes) *
- when (nukeStrength) {
- 0 -> 0f
- 1 -> (30 + Random.Default.nextInt(20) + Random.Default.nextInt(20)) / 100f
- 2 -> (60 + Random.Default.nextInt(10) + Random.Default.nextInt(10)) / 100f
- else -> 1f // hypothetical nukeStrength 3 -> always to 1 pop
- }
- ).toInt().coerceAtMost(targetedCity.population.population - 1)
- targetedCity.population.addPopulation(-populationLoss)
- }
-
- private fun City.getAggregateModifier(uniqueType: UniqueType): Float {
- var modifier = 1f
- for (unique in getMatchingUniques(uniqueType)) {
- if (!matchesFilter(unique.params[1])) continue
- modifier *= unique.params[0].toPercent()
- }
- return modifier
- }
-
- // Should draw an Interception if available on the tile from any Civ
- // Land Units deal 0 damage, and no XP for either party
- // Air Interceptors do Air Combat as if Melee (mutual damage) but using Ranged Strength. 5XP to both
- // But does not use the Interception mechanic bonuses/promotions
- // Counts as an Attack for both units
- // Will always draw out an Interceptor's attack (they cannot miss)
- // This means the combat against Air Units will execute and always deal damage
- // Random Civ at War will Intercept, prioritizing Air Units,
- // sorted by highest Intercept chance (same as regular Intercept)
- fun airSweep(attacker: MapUnitCombatant, attackedTile: Tile) {
- // Air Sweep counts as an attack, even if nothing else happens
- attacker.unit.attacksThisTurn++
- // copied and modified from reduceAttackerMovementPointsAndAttacks()
- // use up movement
- if (attacker.unit.hasUnique(UniqueType.CanMoveAfterAttacking) || attacker.unit.maxAttacksPerTurn() > attacker.unit.attacksThisTurn) {
- // if it was a melee attack and we won, then the unit ALREADY got movement points deducted,
- // for the movement to the enemy's tile!
- // and if it's an air unit, it only has 1 movement anyway, so...
- if (!attacker.unit.baseUnit.movesLikeAirUnits())
- attacker.unit.useMovementPoints(1f)
- } else attacker.unit.currentMovement = 0f
- val attackerName = attacker.getName()
-
- // Make giant sequence of all potential Interceptors from all Civs isAtWarWith()
- var potentialInterceptors = sequence { }
- for (interceptingCiv in UncivGame.Current.gameInfo!!.civilizations
- .filter {attacker.getCivInfo().isAtWarWith(it)}) {
- potentialInterceptors += interceptingCiv.units.getCivUnits()
- .filter { it.canIntercept(attackedTile) }
- }
-
- // first priority, only Air Units
- if (potentialInterceptors.any { it.baseUnit.isAirUnit() })
- potentialInterceptors = potentialInterceptors.filter { it.baseUnit.isAirUnit() }
-
- // Pick highest chance interceptor
- for (interceptor in potentialInterceptors
- .shuffled() // randomize Civ
- .sortedByDescending { it.interceptChance() }) {
- // No chance of Interceptor to miss (unlike regular Interception). Always want to deal damage
- val interceptingCiv = interceptor.civ
- val interceptorName = interceptor.name
- // pairs of LocationAction for Notification
- val locations = LocationAction(
- interceptor.currentTile.position,
- attacker.unit.currentTile.position
- )
- val locationsInterceptorUnknown =
- LocationAction(attackedTile.position, attacker.unit.currentTile.position)
-
- interceptor.attacksThisTurn++ // even if you miss, you took the shot
- val damageDealt: DamageDealt
- if (!interceptor.baseUnit.isAirUnit()) {
- // Deal no damage (moddable in future?) and no XP
- val attackerText =
- "Our [$attackerName] ([-0] HP) was attacked by an intercepting [$interceptorName] ([-0] HP)"
- val interceptorText =
- "Our [$interceptorName] ([-0] HP) intercepted and attacked an enemy [$attackerName] ([-0] HP)"
- attacker.getCivInfo().addNotification(
- attackerText, locations, NotificationCategory.War,
- attackerName, NotificationIcon.War, interceptorName
- )
- interceptingCiv.addNotification(
- interceptorText, locations, NotificationCategory.War,
- interceptorName, NotificationIcon.War, attackerName
- )
- attacker.unit.action = null
- return
- } else {
- // Damage if Air v Air should work similar to Melee
- damageDealt = takeDamage(attacker, MapUnitCombatant(interceptor))
-
- // 5 XP to both
- addXp(MapUnitCombatant(interceptor), 5, attacker)
- addXp(attacker, 5, MapUnitCombatant(interceptor))
- }
-
- val attackerText =
- if (attacker.isDefeated()) {
- if (interceptor.getTile() in attacker.getCivInfo().viewableTiles)
- "Our [$attackerName] ([-${damageDealt.defenderDealt}] HP) was destroyed by an intercepting [$interceptorName] ([-${damageDealt.attackerDealt}] HP)"
- else "Our [$attackerName] ([-${damageDealt.defenderDealt}] HP) was destroyed by an unknown interceptor"
- } else if (MapUnitCombatant(interceptor).isDefeated()) {
- "Our [$attackerName] ([-${damageDealt.defenderDealt}] HP) destroyed an intercepting [$interceptorName] ([-${damageDealt.attackerDealt}] HP)"
- } else "Our [$attackerName] ([-${damageDealt.defenderDealt}] HP) was attacked by an intercepting [$interceptorName] ([-${damageDealt.attackerDealt}] HP)"
-
- attacker.getCivInfo().addNotification(
- attackerText, locationsInterceptorUnknown, NotificationCategory.War,
- attackerName, NotificationIcon.War, NotificationIcon.Question
- )
-
- val interceptorText =
- if (attacker.isDefeated())
- "Our [$interceptorName] ([-${damageDealt.attackerDealt}] HP) intercepted and destroyed an enemy [$attackerName] ([-${damageDealt.defenderDealt}] HP)"
- else if (MapUnitCombatant(interceptor).isDefeated()) {
- if (attacker.getTile() in interceptingCiv.viewableTiles) "Our [$interceptorName] ([-${damageDealt.attackerDealt}] HP) intercepted and was destroyed by an enemy [$attackerName] ([-${damageDealt.defenderDealt}] HP)"
- else "Our [$interceptorName] ([-${damageDealt.attackerDealt}] HP) intercepted and was destroyed by an unknown enemy"
- } else "Our [$interceptorName] ([-${damageDealt.attackerDealt}] HP) intercepted and attacked an enemy [$attackerName] ([-${damageDealt.defenderDealt}] HP)"
-
- interceptingCiv.addNotification(
- interceptorText, locations, NotificationCategory.War,
- interceptorName, NotificationIcon.War, attackerName
- )
- attacker.unit.action = null
- return
- }
-
- // No Interceptions available
- val attackerText = "Nothing tried to intercept our [$attackerName]"
- attacker.getCivInfo().addNotification(attackerText, NotificationCategory.War, attackerName)
- attacker.unit.action = null
- }
-
- private fun tryInterceptAirAttack(
- attacker: MapUnitCombatant,
- attackedTile: Tile,
- interceptingCiv: Civilization,
- defender: ICombatant?
- ): DamageDealt {
- if (attacker.unit.hasUnique(UniqueType.CannotBeIntercepted, StateForConditionals(attacker.getCivInfo(), ourCombatant = attacker, theirCombatant = defender, attackedTile = attackedTile)))
- return DamageDealt.None
-
- // Pick highest chance interceptor
- val interceptor = interceptingCiv.units.getCivUnits()
- .filter { it.canIntercept(attackedTile) }
- .sortedByDescending { it.interceptChance() }
- .firstOrNull { unit ->
- // Can't intercept if we have a unique preventing it
- val conditionalState = StateForConditionals(interceptingCiv, ourCombatant = MapUnitCombatant(unit), theirCombatant = attacker, combatAction = CombatAction.Intercept, attackedTile = attackedTile)
- unit.getMatchingUniques(UniqueType.CannotInterceptUnits, conditionalState)
- .none { attacker.matchesCategory(it.params[0]) }
- // Defender can't intercept either
- && unit != (defender as? MapUnitCombatant)?.unit
- }
- ?: return DamageDealt.None
-
- interceptor.attacksThisTurn++ // even if you miss, you took the shot
- // Does Intercept happen? If not, exit
- if (Random.Default.nextFloat() > interceptor.interceptChance() / 100f)
- return DamageDealt.None
-
- var damage = BattleDamage.calculateDamageToDefender(
- MapUnitCombatant(interceptor),
- attacker
- )
-
- var damageFactor = 1f + interceptor.interceptDamagePercentBonus().toFloat() / 100f
- damageFactor *= attacker.unit.receivedInterceptDamageFactor()
-
- damage = (damage.toFloat() * damageFactor).toInt().coerceAtMost(attacker.unit.health)
-
- attacker.takeDamage(damage)
- if (damage > 0)
- addXp(MapUnitCombatant(interceptor), 2, attacker)
-
- val attackerName = attacker.getName()
- val interceptorName = interceptor.name
- val locations = LocationAction(interceptor.currentTile.position, attacker.unit.currentTile.position)
-
- val attackerText = if (!attacker.isDefeated())
- "Our [$attackerName] ([-$damage] HP) was attacked by an intercepting [$interceptorName] ([-0] HP)"
- else if (interceptor.getTile() in attacker.getCivInfo().viewableTiles)
- "Our [$attackerName] ([-$damage] HP) was destroyed by an intercepting [$interceptorName] ([-0] HP)"
- else "Our [$attackerName] ([-$damage] HP) was destroyed by an unknown interceptor"
-
- attacker.getCivInfo().addNotification(
- attackerText, interceptor.currentTile.position, NotificationCategory.War,
- attackerName, NotificationIcon.War, interceptorName
- )
-
- val interceptorText = if (attacker.isDefeated())
- "Our [$interceptorName] ([-0] HP) intercepted and destroyed an enemy [$attackerName] ([-$damage] HP)"
- else "Our [$interceptorName] ([-0] HP) intercepted and attacked an enemy [$attackerName] ([-$damage] HP)"
- interceptingCiv.addNotification(interceptorText, locations, NotificationCategory.War,
- interceptorName, NotificationIcon.War, attackerName)
-
- return DamageDealt(0, damage)
- }
-
private fun doWithdrawFromMeleeAbility(attacker: ICombatant, defender: ICombatant, baseWithdrawChance: Int): Boolean {
if (baseWithdrawChance == 0) return false
// Some notes...
diff --git a/core/src/com/unciv/logic/battle/BattleUnitCapture.kt b/core/src/com/unciv/logic/battle/BattleUnitCapture.kt
new file mode 100644
index 0000000000000..ebbfd464b801d
--- /dev/null
+++ b/core/src/com/unciv/logic/battle/BattleUnitCapture.kt
@@ -0,0 +1,204 @@
+package com.unciv.logic.battle
+
+import com.unciv.Constants
+import com.unciv.logic.civilization.AlertType
+import com.unciv.logic.civilization.Civilization
+import com.unciv.logic.civilization.NotificationCategory
+import com.unciv.logic.civilization.NotificationIcon
+import com.unciv.logic.civilization.PlayerType
+import com.unciv.logic.civilization.PopupAlert
+import com.unciv.logic.map.mapunit.MapUnit
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.ruleset.unique.StateForConditionals
+import com.unciv.models.ruleset.unique.UniqueType
+import kotlin.math.min
+import kotlin.random.Random
+
+object BattleUnitCapture {
+
+ fun tryCaptureMilitaryUnit(attacker: ICombatant, defender: ICombatant, attackedTile: Tile): Boolean {
+ // https://forums.civfanatics.com/threads/prize-ships-for-land-units.650196/
+ // https://civilization.fandom.com/wiki/Module:Data/Civ5/GK/Defines\
+ // There are 3 ways of capturing a unit, we separate them for cleaner code but we also need to ensure a unit isn't captured twice
+
+ if (defender !is MapUnitCombatant || attacker !is MapUnitCombatant) return false
+
+ if (!defender.isDefeated() || defender.unit.isCivilian()) return false
+
+ // Due to the way OR operators short-circuit, calling just A() || B() means B isn't called if A is true.
+ // Therefore we run all functions before checking if one is true.
+ val wasUnitCaptured = listOf(
+ unitCapturedPrizeShipsUnique(attacker, defender),
+ unitCapturedFromEncampment(attacker, defender, attackedTile),
+ unitGainFromDefeatingUnit(attacker, defender)
+ ).any { it }
+
+ if (!wasUnitCaptured) return false
+
+ // This is called after takeDamage and so the defeated defender is already destroyed and
+ // thus removed from the tile - but MapUnit.destroy() will not clear the unit's currentTile.
+ // Therefore placeUnitNearTile _will_ place the new unit exactly where the defender was
+ return spawnCapturedUnit(defender.getName(), attacker, defender.getTile())
+ }
+
+
+
+ private fun unitCapturedPrizeShipsUnique(attacker: MapUnitCombatant, defender: MapUnitCombatant): Boolean {
+ if (attacker.unit.getMatchingUniques(UniqueType.KillUnitCapture)
+ .none { defender.matchesCategory(it.params[0]) }
+ ) return false
+
+ val captureChance = min(
+ 0.8f,
+ 0.1f + attacker.getAttackingStrength().toFloat() / defender.getDefendingStrength()
+ .toFloat() * 0.4f
+ )
+ /** Between 0 and 1. Defaults to turn and location-based random to avoid save scumming */
+ val random = Random((attacker.getCivInfo().gameInfo.turns * defender.getTile().position.hashCode()).toLong())
+ return random.nextFloat() <= captureChance
+ }
+
+
+ private fun unitGainFromDefeatingUnit(attacker: MapUnitCombatant, defender: MapUnitCombatant): Boolean {
+ if (!attacker.isMelee()) return false
+ var unitCaptured = false
+ val state = StateForConditionals(attacker.getCivInfo(), ourCombatant = attacker, theirCombatant = defender)
+ for (unique in attacker.getMatchingUniques(UniqueType.GainFromDefeatingUnit, state, true)) {
+ if (defender.unit.matchesFilter(unique.params[0])) {
+ attacker.getCivInfo().addGold(unique.params[1].toInt())
+ unitCaptured = true
+ }
+ }
+ return unitCaptured
+ }
+
+ private fun unitCapturedFromEncampment(attacker: MapUnitCombatant, defender: MapUnitCombatant, attackedTile: Tile): Boolean {
+ if (!defender.getCivInfo().isBarbarian()) return false
+ if (attackedTile.improvement != Constants.barbarianEncampment) return false
+
+ var unitCaptured = false
+ // German unique - needs to be checked before we try to move to the enemy tile, since the encampment disappears after we move in
+
+ for (unique in attacker.getCivInfo()
+ .getMatchingUniques(UniqueType.GainFromEncampment)) {
+ attacker.getCivInfo().addGold(unique.params[0].toInt())
+ unitCaptured = true
+ }
+ return unitCaptured
+ }
+
+ /** Places a [unitName] unit near [tile] after being attacked by [attacker].
+ * Adds a notification to [attacker]'s civInfo and returns whether the captured unit could be placed */
+ private fun spawnCapturedUnit(unitName: String, attacker: ICombatant, tile: Tile): Boolean {
+ val addedUnit = attacker.getCivInfo().units.placeUnitNearTile(tile.position, unitName) ?: return false
+ addedUnit.currentMovement = 0f
+ addedUnit.health = 50
+ attacker.getCivInfo().addNotification("An enemy [${unitName}] has joined us!", addedUnit.getTile().position, NotificationCategory.War, unitName)
+
+ val civilianUnit = tile.civilianUnit
+ // placeUnitNearTile might not have spawned the unit in exactly this tile, in which case no capture would have happened on this tile. So we need to do that here.
+ if (addedUnit.getTile() != tile && civilianUnit != null) {
+ captureCivilianUnit(attacker, MapUnitCombatant(civilianUnit))
+ }
+ return true
+ }
+
+
+ /**
+ * @throws IllegalArgumentException if the [attacker] and [defender] belong to the same civ.
+ */
+ fun captureCivilianUnit(attacker: ICombatant, defender: MapUnitCombatant, checkDefeat: Boolean = true) {
+ require(attacker.getCivInfo() != defender.getCivInfo()) {
+ "Can't capture our own unit!"
+ }
+
+ // need to save this because if the unit is captured its owner wil be overwritten
+ val defenderCiv = defender.getCivInfo()
+
+ val capturedUnit = defender.unit
+ // Stop current action
+ capturedUnit.action = null
+
+ val capturedUnitTile = capturedUnit.getTile()
+ val originalOwner = if (capturedUnit.originalOwner != null)
+ capturedUnit.civ.gameInfo.getCivilization(capturedUnit.originalOwner!!)
+ else null
+
+ var wasDestroyedInstead = false
+ when {
+ // Uncapturable units are destroyed
+ defender.unit.hasUnique(UniqueType.Uncapturable) -> {
+ capturedUnit.destroy()
+ wasDestroyedInstead = true
+ }
+ // City states can never capture settlers at all
+ capturedUnit.hasUnique(UniqueType.FoundCity) && attacker.getCivInfo().isCityState() -> {
+ capturedUnit.destroy()
+ wasDestroyedInstead = true
+ }
+ // Is it our old unit?
+ attacker.getCivInfo() == originalOwner -> {
+ // Then it is recaptured without converting settlers to workers
+ capturedUnit.capturedBy(attacker.getCivInfo())
+ }
+ // Return captured civilian to its original owner?
+ defender.getCivInfo().isBarbarian()
+ && originalOwner != null
+ && !originalOwner.isBarbarian()
+ && attacker.getCivInfo() != originalOwner
+ && attacker.getCivInfo().knows(originalOwner)
+ && originalOwner.isAlive()
+ && !attacker.getCivInfo().isAtWarWith(originalOwner)
+ && attacker.getCivInfo().playerType == PlayerType.Human // Only humans get the choice
+ -> {
+ capturedUnit.capturedBy(attacker.getCivInfo())
+ attacker.getCivInfo().popupAlerts.add(
+ PopupAlert(
+ AlertType.RecapturedCivilian,
+ capturedUnitTile.position.toString()
+ )
+ )
+ }
+
+ else -> captureOrConvertToWorker(capturedUnit, attacker.getCivInfo())
+ }
+
+ if (!wasDestroyedInstead)
+ defenderCiv.addNotification(
+ "An enemy [${attacker.getName()}] has captured our [${defender.getName()}]",
+ defender.getTile().position, NotificationCategory.War, attacker.getName(),
+ NotificationIcon.War, defender.getName()
+ )
+ else {
+ defenderCiv.addNotification(
+ "An enemy [${attacker.getName()}] has destroyed our [${defender.getName()}]",
+ defender.getTile().position, NotificationCategory.War, attacker.getName(),
+ NotificationIcon.War, defender.getName()
+ )
+ Battle.triggerDefeatUniques(defender, attacker, capturedUnitTile)
+ }
+
+ if (checkDefeat)
+ Battle.destroyIfDefeated(defenderCiv, attacker.getCivInfo())
+ capturedUnit.updateVisibleTiles()
+ }
+
+ fun captureOrConvertToWorker(capturedUnit: MapUnit, capturingCiv: Civilization){
+ // Captured settlers are converted to workers unless captured by barbarians (so they can be returned later).
+ if (capturedUnit.hasUnique(UniqueType.FoundCity) && !capturingCiv.isBarbarian()) {
+ capturedUnit.destroy()
+ // This is so that future checks which check if a unit has been captured are caught give the right answer
+ // For example, in postBattleMoveToAttackedTile
+ capturedUnit.civ = capturingCiv
+
+ val workerTypeUnit = capturingCiv.gameInfo.ruleset.units.values
+ .firstOrNull { it.isCivilian() && it.getMatchingUniques(UniqueType.BuildImprovements)
+ .any { unique -> unique.params[0] == "Land" } }
+
+ if (workerTypeUnit != null)
+ capturingCiv.units.placeUnitNearTile(capturedUnit.currentTile.position, workerTypeUnit)
+ }
+ else capturedUnit.capturedBy(capturingCiv)
+ }
+
+}
diff --git a/core/src/com/unciv/logic/battle/Nuke.kt b/core/src/com/unciv/logic/battle/Nuke.kt
new file mode 100644
index 0000000000000..6983082ab1a9f
--- /dev/null
+++ b/core/src/com/unciv/logic/battle/Nuke.kt
@@ -0,0 +1,312 @@
+package com.unciv.logic.battle
+
+import com.badlogic.gdx.math.Vector2
+import com.unciv.logic.automation.unit.SpecificUnitAutomation
+import com.unciv.logic.city.City
+import com.unciv.logic.civilization.Civilization
+import com.unciv.logic.civilization.CivilopediaAction
+import com.unciv.logic.civilization.LocationAction
+import com.unciv.logic.civilization.NotificationCategory
+import com.unciv.logic.civilization.NotificationIcon
+import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
+import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
+import com.unciv.logic.map.tile.RoadStatus
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.ruleset.unique.UniqueType
+import com.unciv.ui.components.extensions.toPercent
+import com.unciv.ui.screens.worldscreen.bottombar.BattleTable
+import kotlin.math.ulp
+import kotlin.random.Random
+
+object Nuke {
+
+
+ /**
+ * Checks whether [nuke] is allowed to nuke [targetTile]
+ * - Not if we would need to declare war on someone we can't.
+ * - Disallow nuking the tile the nuke is in, as per Civ5 (but not nuking your own tiles/units otherwise)
+ *
+ * Both [BattleTable.simulateNuke] and [SpecificUnitAutomation.automateNukes] check range, so that check is omitted here.
+ */
+ fun mayUseNuke(nuke: MapUnitCombatant, targetTile: Tile): Boolean {
+ if (nuke.getTile() == targetTile) return false
+ // Can only nuke visible Tiles
+ if (!targetTile.isVisible(nuke.getCivInfo())) return false
+
+ var canNuke = true
+ val attackerCiv = nuke.getCivInfo()
+ fun checkDefenderCiv(defenderCiv: Civilization?) {
+ if (defenderCiv == null) return
+ // Allow nuking yourself! (Civ5 source: CvUnit::isNukeVictim)
+ if (defenderCiv == attackerCiv || defenderCiv.isDefeated()) return
+ // Gleaned from Civ5 source - this disallows nuking unknown civs even in invisible tiles
+ // https://github.com/Gedemon/Civ5-DLL/blob/master/CvGameCoreDLL_Expansion1/CvUnit.cpp#L5056
+ // https://github.com/Gedemon/Civ5-DLL/blob/master/CvGameCoreDLL_Expansion1/CvTeam.cpp#L986
+ if (attackerCiv.knows(defenderCiv) && attackerCiv.getDiplomacyManager(defenderCiv).canAttack())
+ return
+ canNuke = false
+ }
+
+ val blastRadius = nuke.unit.getNukeBlastRadius()
+ for (tile in targetTile.getTilesInDistance(blastRadius)) {
+ checkDefenderCiv(tile.getOwner())
+ checkDefenderCiv(Battle.getMapCombatantOfTile(tile)?.getCivInfo())
+ }
+ return canNuke
+ }
+
+ @Suppress("FunctionName") // Yes we want this name to stand out
+ fun NUKE(attacker: MapUnitCombatant, targetTile: Tile) {
+ val attackingCiv = attacker.getCivInfo()
+ val nukeStrength = attacker.unit.getMatchingUniques(UniqueType.NuclearWeapon)
+ .firstOrNull()?.params?.get(0)?.toInt() ?: return
+
+ val blastRadius = attacker.unit.getMatchingUniques(UniqueType.BlastRadius)
+ .firstOrNull()?.params?.get(0)?.toInt() ?: 2
+
+ val hitTiles = targetTile.getTilesInDistance(blastRadius)
+
+ val (hitCivsTerritory, notifyDeclaredWarCivs) =
+ declareWarOnHitCivs(attackingCiv, hitTiles, attacker, targetTile)
+
+ addNukeNotifications(targetTile, attacker, notifyDeclaredWarCivs, attackingCiv, hitCivsTerritory)
+
+ if (attacker.isDefeated()) return
+
+ attacker.unit.attacksSinceTurnStart.add(Vector2(targetTile.position))
+
+ for (tile in hitTiles) {
+ // Handle complicated effects
+ doNukeExplosionForTile(attacker, tile, nukeStrength, targetTile == tile)
+ }
+
+
+ // Instead of postBattleAction() just destroy the unit, all other functions are not relevant
+ if (attacker.unit.hasUnique(UniqueType.SelfDestructs)) attacker.unit.destroy()
+
+ // It's unclear whether using nukes results in a penalty with all civs, or only affected civs.
+ // For now I'll make it give a diplomatic penalty to all known civs, but some testing for this would be appreciated
+ for (civ in attackingCiv.getKnownCivs()) {
+ civ.getDiplomacyManager(attackingCiv).setModifier(DiplomaticModifiers.UsedNuclearWeapons, -50f)
+ }
+
+ if (!attacker.isDefeated()) {
+ attacker.unit.attacksThisTurn += 1
+ }
+ }
+
+ private fun addNukeNotifications(
+ targetTile: Tile,
+ attacker: MapUnitCombatant,
+ notifyDeclaredWarCivs: ArrayList,
+ attackingCiv: Civilization,
+ hitCivsTerritory: ArrayList
+ ) {
+ val nukeNotificationAction = sequenceOf(LocationAction(targetTile.position), CivilopediaAction("Units/" + attacker.getName()))
+
+ // If the nuke has been intercepted and destroyed then it fails to detonate
+ if (attacker.isDefeated()) {
+ // Notify attacker that they are now at war for the attempt
+ for (defendingCiv in notifyDeclaredWarCivs)
+ attackingCiv.addNotification(
+ "After an attempted attack by our [${attacker.getName()}], [${defendingCiv}] has declared war on us!",
+ nukeNotificationAction,
+ NotificationCategory.Diplomacy,
+ defendingCiv.civName,
+ NotificationIcon.War,
+ attacker.getName()
+ )
+ return
+ }
+
+ // Notify attacker that they are now at war
+ for (defendingCiv in notifyDeclaredWarCivs)
+ attackingCiv.addNotification(
+ "After being hit by our [${attacker.getName()}], [${defendingCiv}] has declared war on us!",
+ nukeNotificationAction, NotificationCategory.Diplomacy, defendingCiv.civName, NotificationIcon.War, attacker.getName()
+ )
+
+ // Message all other civs
+ for (otherCiv in attackingCiv.gameInfo.civilizations) {
+ if (!otherCiv.isAlive() || otherCiv == attackingCiv) continue
+ if (hitCivsTerritory.contains(otherCiv))
+ otherCiv.addNotification(
+ "A(n) [${attacker.getName()}] from [${attackingCiv.civName}] has exploded in our territory!",
+ nukeNotificationAction, NotificationCategory.War, attackingCiv.civName, NotificationIcon.War, attacker.getName()
+ )
+ else if (otherCiv.knows(attackingCiv))
+ otherCiv.addNotification(
+ "A(n) [${attacker.getName()}] has been detonated by [${attackingCiv.civName}]!",
+ nukeNotificationAction, NotificationCategory.War, attackingCiv.civName, NotificationIcon.War, attacker.getName()
+ )
+ else
+ otherCiv.addNotification(
+ "A(n) [${attacker.getName()}] has been detonated by an unkown civilization!",
+ nukeNotificationAction, NotificationCategory.War, NotificationIcon.War, attacker.getName()
+ )
+ }
+ }
+
+ private fun declareWarOnHitCivs(
+ attackingCiv: Civilization,
+ hitTiles: Sequence,
+ attacker: MapUnitCombatant,
+ targetTile: Tile
+ ): Pair, ArrayList> {
+ // Declare war on the owners of all hit tiles
+ val notifyDeclaredWarCivs = ArrayList()
+ fun tryDeclareWar(civSuffered: Civilization) {
+ if (civSuffered != attackingCiv
+ && civSuffered.knows(attackingCiv)
+ && civSuffered.getDiplomacyManager(attackingCiv).diplomaticStatus != DiplomaticStatus.War
+ ) {
+ attackingCiv.getDiplomacyManager(civSuffered).declareWar()
+ if (!notifyDeclaredWarCivs.contains(civSuffered)) notifyDeclaredWarCivs.add(
+ civSuffered
+ )
+ }
+ }
+
+ val hitCivsTerritory = ArrayList()
+ for (hitCiv in hitTiles.mapNotNull { it.getOwner() }.distinct()) {
+ hitCivsTerritory.add(hitCiv)
+ tryDeclareWar(hitCiv)
+ }
+
+ // Declare war on all potentially hit units. They'll try to intercept the nuke before it drops
+ for (civWhoseUnitWasAttacked in hitTiles
+ .flatMap { it.getUnits() }
+ .map { it.civ }.distinct()
+ .filter { it != attackingCiv }) {
+ tryDeclareWar(civWhoseUnitWasAttacked)
+ if (attacker.unit.baseUnit.isAirUnit() && !attacker.isDefeated()) {
+ AirInterception.tryInterceptAirAttack(
+ attacker,
+ targetTile,
+ civWhoseUnitWasAttacked,
+ null
+ )
+ }
+ }
+ return Pair(hitCivsTerritory, notifyDeclaredWarCivs)
+ }
+
+ private fun doNukeExplosionForTile(
+ attacker: MapUnitCombatant,
+ tile: Tile,
+ nukeStrength: Int,
+ isGroundZero: Boolean
+ ) {
+ // https://forums.civfanatics.com/resources/unit-guide-modern-future-units-g-k.25628/
+ // https://www.carlsguides.com/strategy/civilization5/units/aircraft-nukes.ph
+ // Testing done by Ravignir
+ // original source code: GenerateNuclearExplosionDamage(), ApplyNuclearExplosionDamage()
+
+ var damageModifierFromMissingResource = 1f
+ val civResources = attacker.getCivInfo().getCivResourcesByName()
+ for (resource in attacker.unit.baseUnit.getResourceRequirementsPerTurn().keys) {
+ if (civResources[resource]!! < 0 && !attacker.getCivInfo().isBarbarian())
+ damageModifierFromMissingResource *= 0.5f // I could not find a source for this number, but this felt about right
+ // - Original Civ5 does *not* reduce damage from missing resource, from source inspection
+ }
+
+ var buildingModifier = 1f // Strange, but in Civ5 a bunker mitigates damage to garrison, even if the city is destroyed by the nuke
+
+ // Damage city and reduce its population
+ val city = tile.getCity()
+ if (city != null && tile.position == city.location) {
+ buildingModifier = city.getAggregateModifier(UniqueType.GarrisonDamageFromNukes)
+ doNukeExplosionDamageToCity(city, nukeStrength, damageModifierFromMissingResource)
+ Battle.postBattleNotifications(attacker, CityCombatant(city), city.getCenterTile())
+ Battle.destroyIfDefeated(city.civ, attacker.getCivInfo())
+ }
+
+ // Damage and/or destroy units on the tile
+ for (unit in tile.getUnits().toList()) { // toList so if it's destroyed there's no concurrent modification
+ val damage = (when {
+ isGroundZero || nukeStrength >= 2 -> 100
+ // The following constants are NUKE_UNIT_DAMAGE_BASE / NUKE_UNIT_DAMAGE_RAND_1 / NUKE_UNIT_DAMAGE_RAND_2 in Civ5
+ nukeStrength == 1 -> 30 + Random.Default.nextInt(40) + Random.Default.nextInt(40)
+ // Level 0 does not exist in Civ5 (it treats units same as level 2)
+ else -> 20 + Random.Default.nextInt(30)
+ } * buildingModifier * damageModifierFromMissingResource + 1f.ulp).toInt()
+ val defender = MapUnitCombatant(unit)
+ if (unit.isCivilian()) {
+ if (unit.health - damage <= 40) unit.destroy() // Civ5: NUKE_NON_COMBAT_DEATH_THRESHOLD = 60
+ } else {
+ defender.takeDamage(damage)
+ }
+ Battle.postBattleNotifications(attacker, defender, defender.getTile())
+ Battle.destroyIfDefeated(defender.getCivInfo(), attacker.getCivInfo())
+ }
+
+ // Pillage improvements, pillage roads, add fallout
+ if (tile.isCityCenter()) return // Never touch city centers - if they survived
+
+ if (tile.terrainHasUnique(UniqueType.DestroyableByNukesChance)) {
+ // Note: Safe from concurrent modification exceptions only because removeTerrainFeature
+ // *replaces* terrainFeatureObjects and the loop will continue on the old one
+ for (terrainFeature in tile.terrainFeatureObjects) {
+ for (unique in terrainFeature.getMatchingUniques(UniqueType.DestroyableByNukesChance)) {
+ val chance = unique.params[0].toFloat() / 100f
+ if (!(chance > 0f && isGroundZero) && Random.Default.nextFloat() >= chance) continue
+ tile.removeTerrainFeature(terrainFeature.name)
+ applyPillageAndFallout(tile)
+ }
+ }
+ } else if (isGroundZero || Random.Default.nextFloat() < 0.5f) { // Civ5: NUKE_FALLOUT_PROB
+ applyPillageAndFallout(tile)
+ }
+ }
+
+ private fun applyPillageAndFallout(tile: Tile) {
+ if (tile.getUnpillagedImprovement() != null && !tile.getTileImprovement()!!.hasUnique(
+ UniqueType.Irremovable)) {
+ if (tile.getTileImprovement()!!.hasUnique(UniqueType.Unpillagable)) {
+ tile.removeImprovement()
+ } else {
+ tile.setPillaged()
+ }
+ }
+ if (tile.getUnpillagedRoad() != RoadStatus.None)
+ tile.setPillaged()
+ if (tile.isWater || tile.isImpassible() || tile.terrainFeatures.contains("Fallout")) return
+ tile.addTerrainFeature("Fallout")
+ }
+
+ /** @return the "protection" modifier from buildings (Bomb Shelter, UniqueType.PopulationLossFromNukes) */
+ private fun doNukeExplosionDamageToCity(targetedCity: City, nukeStrength: Int, damageModifierFromMissingResource: Float) {
+ // Original Capitals must be protected, `canBeDestroyed` is responsible for that check.
+ // The `justCaptured = true` parameter is what allows other Capitals to suffer normally.
+ if ((nukeStrength > 2 || nukeStrength > 1 && targetedCity.population.population < 5)
+ && targetedCity.canBeDestroyed(true)) {
+ targetedCity.destroyCity()
+ return
+ }
+
+ val cityCombatant = CityCombatant(targetedCity)
+ cityCombatant.takeDamage((cityCombatant.getHealth() * 0.5f * damageModifierFromMissingResource).toInt())
+
+ // Difference to original: Civ5 rounds population loss down twice - before and after bomb shelters
+ val populationLoss = (
+ targetedCity.population.population *
+ targetedCity.getAggregateModifier(UniqueType.PopulationLossFromNukes) *
+ when (nukeStrength) {
+ 0 -> 0f
+ 1 -> (30 + Random.Default.nextInt(20) + Random.Default.nextInt(20)) / 100f
+ 2 -> (60 + Random.Default.nextInt(10) + Random.Default.nextInt(10)) / 100f
+ else -> 1f // hypothetical nukeStrength 3 -> always to 1 pop
+ }
+ ).toInt().coerceAtMost(targetedCity.population.population - 1)
+ targetedCity.population.addPopulation(-populationLoss)
+ }
+
+ private fun City.getAggregateModifier(uniqueType: UniqueType): Float {
+ var modifier = 1f
+ for (unique in getMatchingUniques(uniqueType)) {
+ if (!matchesFilter(unique.params[1])) continue
+ modifier *= unique.params[0].toPercent()
+ }
+ return modifier
+ }
+}
diff --git a/core/src/com/unciv/logic/battle/TargetHelper.kt b/core/src/com/unciv/logic/battle/TargetHelper.kt
index 08eeab2fc9367..689e000bb3093 100644
--- a/core/src/com/unciv/logic/battle/TargetHelper.kt
+++ b/core/src/com/unciv/logic/battle/TargetHelper.kt
@@ -3,7 +3,7 @@ package com.unciv.logic.battle
import com.unciv.Constants
import com.unciv.logic.city.City
import com.unciv.logic.map.mapunit.MapUnit
-import com.unciv.logic.map.mapunit.PathsToTilesWithinTurn
+import com.unciv.logic.map.mapunit.movement.PathsToTilesWithinTurn
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.unique.UniqueType
diff --git a/core/src/com/unciv/logic/city/City.kt b/core/src/com/unciv/logic/city/City.kt
index 4aad98f03f890..f31c1c2ac7c40 100644
--- a/core/src/com/unciv/logic/city/City.kt
+++ b/core/src/com/unciv/logic/city/City.kt
@@ -631,6 +631,13 @@ class City : IsPartOfGameInfoSerialization {
}
}
+ // Uniques coming from only this city
+ fun getMatchingLocalOnlyUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals): Sequence {
+ val uniques = cityConstructions.builtBuildingUniqueMap.getUniques(uniqueType).filter { it.isLocalEffect } +
+ religion.getUniques().filter { it.isOfType(uniqueType) }
+ return if (uniques.any()) uniques.filter { it.conditionalsApply(stateForConditionals) }
+ else uniques
+ }
// Uniques coming from this city, but that should be provided globally
fun getMatchingUniquesWithNonLocalEffects(uniqueType: UniqueType, stateForConditionals: StateForConditionals): Sequence {
diff --git a/core/src/com/unciv/logic/city/CityConstructions.kt b/core/src/com/unciv/logic/city/CityConstructions.kt
index d033c8d648a61..c260297d6ce25 100644
--- a/core/src/com/unciv/logic/city/CityConstructions.kt
+++ b/core/src/com/unciv/logic/city/CityConstructions.kt
@@ -245,7 +245,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
throw NotBuildingOrUnitException("$constructionName is not a building or a unit!")
}
- internal fun getBuiltBuildings(): Sequence = builtBuildingObjects.asSequence()
+ fun getBuiltBuildings(): Sequence = builtBuildingObjects.asSequence()
fun containsBuildingOrEquivalent(buildingNameOrUnique: String): Boolean =
isBuilt(buildingNameOrUnique) || getBuiltBuildings().any { it.replaces == buildingNameOrUnique || it.hasUnique(buildingNameOrUnique) }
@@ -298,11 +298,11 @@ class CityConstructions : IsPartOfGameInfoSerialization {
return ceil((workLeft-productionOverflow) / production.toDouble()).toInt()
}
- fun hasBuildableStatBuildings(stat: Stat): Boolean {
+ fun cheapestStatBuilding(stat: Stat): Building? {
return getBasicStatBuildings(stat)
- .map { city.civ.getEquivalentBuilding(it.name) }
+ .map { city.civ.getEquivalentBuilding(it) }
.filter { it.isBuildable(this) || isBeingConstructedOrEnqueued(it.name) }
- .any()
+ .minByOrNull { it.cost }
}
//endregion
@@ -455,7 +455,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
}
/** Returns false if we tried to construct a unit but it has nowhere to go */
- private fun constructionComplete(construction: INonPerpetualConstruction): Boolean {
+ fun constructionComplete(construction: INonPerpetualConstruction): Boolean {
val managedToConstruct = construction.postBuildEvent(this)
if (!managedToConstruct) return false
@@ -524,6 +524,8 @@ class CityConstructions : IsPartOfGameInfoSerialization {
updateUniques()
+ validateConstructionQueue()
+
/** Support for [UniqueType.CreatesOneImprovement] */
applyCreateOneImprovement(building)
@@ -544,7 +546,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
}
else city.reassignPopulationDeferred()
- addFreeBuildings()
+ city.civ.civConstructions.tryAddFreeBuildings()
}
fun triggerNewBuildingUniques(building: Building) {
@@ -591,42 +593,6 @@ class CityConstructions : IsPartOfGameInfoSerialization {
}
}
- fun addFreeBuildings() {
- // "Gain a free [buildingName] [cityFilter]"
- val freeBuildingUniques = city.getMatchingUniques(UniqueType.GainFreeBuildings, StateForConditionals(city.civ, city))
-
- for (unique in freeBuildingUniques) {
- val freeBuilding = city.civ.getEquivalentBuilding(unique.params[0])
- val citiesThatApply =
- if (unique.isLocalEffect) listOf(city)
- else city.civ.cities.filter { it.matchesFilter(unique.params[1]) }
-
- for (city in citiesThatApply) {
- if (city.cityConstructions.containsBuildingOrEquivalent(freeBuilding.name)) continue
- city.cityConstructions.addBuilding(freeBuilding)
- freeBuildingsProvidedFromThisCity.addToMapOfSets(city.id, freeBuilding.name)
- }
- }
-
- // Civ-level uniques - for these only add free buildings from each city to itself to avoid weirdness on city conquest
- for (unique in city.civ.getMatchingUniques(UniqueType.GainFreeBuildings, stateForConditionals = StateForConditionals(city.civ, city))) {
- val freeBuilding = city.civ.getEquivalentBuilding(unique.params[0])
- if (city.matchesFilter(unique.params[1])) {
- freeBuildingsProvidedFromThisCity.addToMapOfSets(city.id, freeBuilding.name)
- if (!isBuilt(freeBuilding.name))
- addBuilding(freeBuilding)
- }
- }
-
-
- val autoGrantedBuildings = city.getRuleset().buildings.values
- .filter { it.hasUnique(UniqueType.GainBuildingWhereBuildable) }
-
- for (building in autoGrantedBuildings)
- if (building.isBuildable(city.cityConstructions))
- addBuilding(building)
- }
-
/**
* Purchase a construction for gold (or another stat)
* called from NextTurnAutomation and the City UI
@@ -719,18 +685,6 @@ class CityConstructions : IsPartOfGameInfoSerialization {
return true
}
- fun addCheapestBuildableStatBuilding(stat: Stat): String? {
- val cheapestBuildableStatBuilding = getBasicStatBuildings(stat)
- .map { city.civ.getEquivalentBuilding(it) }
- .filter { it.isBuildable(this) || isBeingConstructedOrEnqueued(it.name) }
- .minByOrNull { it.cost }
- ?: return null
-
- constructionComplete(cheapestBuildableStatBuilding)
-
- return cheapestBuildableStatBuilding.name
- }
-
private fun removeCurrentConstruction() = removeFromQueue(0, true)
fun chooseNextConstruction() {
diff --git a/core/src/com/unciv/logic/city/managers/CityConquestFunctions.kt b/core/src/com/unciv/logic/city/managers/CityConquestFunctions.kt
index a481aaf38c69e..e190df8fe229c 100644
--- a/core/src/com/unciv/logic/city/managers/CityConquestFunctions.kt
+++ b/core/src/com/unciv/logic/city/managers/CityConquestFunctions.kt
@@ -53,15 +53,6 @@ class CityConquestFunctions(val city: City){
for (building in city.civ.civConstructions.getFreeBuildingNames(city)) {
city.cityConstructions.removeBuilding(building)
}
-
- // Remove all buildings provided for free from here to other cities (e.g. CN Tower)
- for ((cityId, buildings) in city.cityConstructions.freeBuildingsProvidedFromThisCity) {
- val city = oldCiv.cities.firstOrNull { it.id == cityId } ?: continue
- debug("Removing buildings %s from city %s", buildings, city.name)
- for (building in buildings) {
- city.cityConstructions.removeBuilding(building)
- }
- }
city.cityConstructions.freeBuildingsProvidedFromThisCity.clear()
for (building in city.cityConstructions.getBuiltBuildings()) {
@@ -234,6 +225,9 @@ class CityConquestFunctions(val city: City){
if (foundingCiv.isMajorCiv()) {
foundingCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.CapturedOurCities, respectForLiberatingOurCity)
+ val openBordersTrade = TradeLogic(foundingCiv, conqueringCiv)
+ openBordersTrade.currentTrade.ourOffers.add(TradeOffer(Constants.openBorders, TradeType.Agreement))
+ openBordersTrade.acceptTrade()
} else {
//Liberating a city state gives a large amount of influence, and peace
foundingCiv.getDiplomacyManager(conqueringCiv).setInfluence(90f)
diff --git a/core/src/com/unciv/logic/city/managers/CityExpansionManager.kt b/core/src/com/unciv/logic/city/managers/CityExpansionManager.kt
index deb6ab21e7bf5..d6d9ffdbd85fa 100644
--- a/core/src/com/unciv/logic/city/managers/CityExpansionManager.kt
+++ b/core/src/com/unciv/logic/city/managers/CityExpansionManager.kt
@@ -68,13 +68,14 @@ class CityExpansionManager : IsPartOfGameInfoSerialization {
fun buyTile(tile: Tile) {
val goldCost = getGoldCostOfTile(tile)
- class TriedToBuyNonContiguousTileException:Exception()
+ class TriedToBuyNonContiguousTileException(msg: String) : Exception(msg)
if (tile.neighbors.none { it.getCity() == city })
- throw TriedToBuyNonContiguousTileException()
+ throw TriedToBuyNonContiguousTileException("$city tried to buy $tile, but it owns none of the neighbors")
- class NotEnoughGoldToBuyTileException : Exception()
+ class NotEnoughGoldToBuyTileException(msg: String) : Exception(msg)
if (city.civ.gold < goldCost && !city.civ.gameInfo.gameParameters.godMode)
- throw NotEnoughGoldToBuyTileException()
+ throw NotEnoughGoldToBuyTileException("$city tried to buy $tile, but lacks gold (cost $goldCost, has ${city.civ.gold})")
+
city.civ.addGold(-goldCost)
takeOwnership(tile)
diff --git a/core/src/com/unciv/logic/city/managers/CityTurnManager.kt b/core/src/com/unciv/logic/city/managers/CityTurnManager.kt
index 95c915e211486..0bba9cc717311 100644
--- a/core/src/com/unciv/logic/city/managers/CityTurnManager.kt
+++ b/core/src/com/unciv/logic/city/managers/CityTurnManager.kt
@@ -21,7 +21,6 @@ class CityTurnManager(val city: City) {
// Construct units at the beginning of the turn,
// so they won't be generated out in the open and vulnerable to enemy attacks before you can control them
city.cityConstructions.constructIfEnough()
- city.cityConstructions.addFreeBuildings()
city.tryUpdateRoadStatus()
city.attackedThisTurn = false
diff --git a/core/src/com/unciv/logic/civilization/CivConstructions.kt b/core/src/com/unciv/logic/civilization/CivConstructions.kt
index 7b7db0675f062..694bca2dc830a 100644
--- a/core/src/com/unciv/logic/civilization/CivConstructions.kt
+++ b/core/src/com/unciv/logic/civilization/CivConstructions.kt
@@ -5,6 +5,7 @@ import com.unciv.logic.city.City
import com.unciv.models.Counter
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.INonPerpetualConstruction
+import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat
@@ -64,6 +65,7 @@ class CivConstructions : IsPartOfGameInfoSerialization {
fun tryAddFreeBuildings() {
addFreeStatsBuildings()
addFreeSpecificBuildings()
+ addFreeBuildings()
}
/** Common to [hasFreeBuildingByName] and [getFreeBuildingNames] - 'has' doesn't need the whole set, one enumeration is enough.
@@ -107,13 +109,13 @@ class CivConstructions : IsPartOfGameInfoSerialization {
private fun addFreeStatBuildings(stat: Stat, amount: Int) {
for (city in civInfo.cities.take(amount)) {
if (freeStatBuildingsProvided.contains(stat.name, city.id)) continue
- if (!city.cityConstructions.hasBuildableStatBuildings(stat)) continue
+ val building = city.cityConstructions.cheapestStatBuilding(stat)
+ ?: continue
- val builtBuilding = city.cityConstructions.addCheapestBuildableStatBuilding(stat)
- if (builtBuilding != null) {
- freeStatBuildingsProvided.addToMapOfSets(stat.name, city.id)
- addFreeBuilding(city.id, builtBuilding)
- }
+ freeStatBuildingsProvided.addToMapOfSets(stat.name, city.id)
+ addFreeBuilding(city.id, building.name)
+ city.cityConstructions.constructionComplete(building)
+ building.postBuildEvent(city.cityConstructions)
}
}
@@ -129,15 +131,37 @@ class CivConstructions : IsPartOfGameInfoSerialization {
}
private fun addFreeBuildings(building: Building, amount: Int) {
-
for (city in civInfo.cities.take(amount)) {
if (freeSpecificBuildingsProvided.contains(building.name, city.id)
|| city.cityConstructions.containsBuildingOrEquivalent(building.name)) continue
- building.postBuildEvent(city.cityConstructions)
-
freeSpecificBuildingsProvided.addToMapOfSets(building.name, city.id)
addFreeBuilding(city.id, building.name)
+ city.cityConstructions.constructionComplete(building)
+ }
+ }
+
+ fun addFreeBuildings() {
+ val autoGrantedBuildings = civInfo.gameInfo.ruleset.buildings.values
+ .filter { it.hasUnique(UniqueType.GainBuildingWhereBuildable) }
+
+ // "Gain a free [buildingName] [cityFilter]"
+ val freeBuildingsFromCiv = civInfo.getMatchingUniques(UniqueType.GainFreeBuildings, StateForConditionals.IgnoreConditionals)
+ for (city in civInfo.cities) {
+ val freeBuildingsFromCity = city.getMatchingLocalOnlyUniques(UniqueType.GainFreeBuildings, StateForConditionals.IgnoreConditionals)
+ val freeBuildingUniques = (freeBuildingsFromCiv + freeBuildingsFromCity)
+ .filter { city.matchesFilter(it.params[1]) && it.conditionalsApply(StateForConditionals(city.civ, city)) }
+ for (unique in freeBuildingUniques){
+ val freeBuilding = city.civ.getEquivalentBuilding(unique.params[0])
+ city.cityConstructions.freeBuildingsProvidedFromThisCity.addToMapOfSets(city.id, freeBuilding.name)
+
+ if (city.cityConstructions.containsBuildingOrEquivalent(freeBuilding.name)) continue
+ city.cityConstructions.constructionComplete(freeBuilding)
+ }
+
+ for (building in autoGrantedBuildings)
+ if (building.isBuildable(city.cityConstructions))
+ city.cityConstructions.constructionComplete(building)
}
}
diff --git a/core/src/com/unciv/logic/civilization/Notification.kt b/core/src/com/unciv/logic/civilization/Notification.kt
index b1aea65b518bb..2b33f49a18b66 100644
--- a/core/src/com/unciv/logic/civilization/Notification.kt
+++ b/core/src/com/unciv/logic/civilization/Notification.kt
@@ -1,6 +1,5 @@
package com.unciv.logic.civilization
-import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Json
@@ -32,15 +31,13 @@ open class Notification() : IsPartOfGameInfoSerialization {
constructor(
text: String,
- notificationIcons: Array,
+ notificationIcons: Array, // `out` needed so we can pass a vararg directly
actions: Iterable?,
category: NotificationCategory = NotificationCategory.General
) : this() {
this.category = category
this.text = text
- if (notificationIcons.isNotEmpty()) {
- this.icons = notificationIcons.toCollection(ArrayList())
- }
+ notificationIcons.toCollection(this.icons)
actions?.toCollection(this.actions)
}
@@ -98,25 +95,23 @@ open class Notification() : IsPartOfGameInfoSerialization {
/**
* Custom [Gdx.Json][Json] serializer/deserializer for one [Notification].
*
- * Migration roadmap:
- *
- * 1.) Change internal structures but write old json format
- * 2.) Wait for good distribution in multiplayer user base
- * 3.) Switch to writing new format
- * 4.) Wait for Versions prior to Step 3 to fade out, keep switch for quick revert
- * 5.) Remove Switch, old format routines and this comment
- *
- * Caveats:
- *
- * * New format can express Notifications the old can't.
- * In that case, in Phase 1, reduce to first action and throw away the rest.
+ * Example of the serialized format:
+ * ```json
+ * "notifications":[
+ * {
+ * "category":"Production",
+ * "text":"[Nobel Foundation] has been built in [Stockholm]",
+ * "icons":["BuildingIcons/Nobel Foundation"],
+ * "actions":[
+ * {"LocationAction":{"location":{"x":9,"y":3}}},
+ * {"CivilopediaAction":{"link":"Wonder/Nobel Foundation"}},
+ * {"CityAction":{"city":{"x":9,"y":3}}}
+ * ]
+ * }
+ * ]
+ * ```
*/
class Serializer : Json.Serializer {
- companion object {
- /** The switch that starts Phase III and dies with Phase V
- * @see Serializer */
- private const val compatibilityMode = false
- }
override fun write(json: Json, notification: Notification, knownType: Class<*>?) {
json.writeObjectStart()
@@ -127,13 +122,12 @@ open class Notification() : IsPartOfGameInfoSerialization {
if (notification.icons.isNotEmpty())
json.writeValue("icons", notification.icons, null, String::class.java)
- if (compatibilityMode) writeOldFormatAction(json, notification)
- else writeNewFormatActions(json, notification)
+ writeActions(json, notification)
json.writeObjectEnd()
}
- private fun writeNewFormatActions(json: Json, notification: Notification) {
+ private fun writeActions(json: Json, notification: Notification) {
if (notification.actions.isEmpty()) return
json.writeArrayStart("actions")
for (action in notification.actions) {
@@ -146,35 +140,14 @@ open class Notification() : IsPartOfGameInfoSerialization {
json.writeArrayEnd()
}
- private fun writeOldFormatAction(json: Json, notification: Notification) {
- if (notification.actions.isEmpty()) return
- val firstAction = notification.actions.first()
- if (firstAction !is LocationAction) {
- json.writeValue("action", firstAction, null)
- return
- }
- val locations = notification.actions.filterIsInstance()
- .map { it.location }.toTypedArray()
- json.writeObjectStart("action")
- json.writeValue("class", "com.unciv.logic.civilization.LocationAction")
- json.writeValue("locations", locations, Array::class.java, Vector2::class.java)
- json.writeObjectEnd()
- }
-
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?) = Notification().apply {
- // Cannot be distinguished 100% certain by field names but if neither action / actions exist then both formats are compatible
json.readField(this, "category", jsonData)
json.readField(this, "text", jsonData)
- readOldFormatAction(json, jsonData)
- readNewFormatActions(json, jsonData)
+ readActions(json, jsonData)
json.readField(this, "icons", jsonData)
}
- private fun Notification.readNewFormatActions(json: Json, jsonData: JsonValue) {
- // New format looks like this: "notifications":[
- // {"category":"Cities","text":"[Stockholm] has expanded its borders!","icons":["StatIcons/Culture"],"actions":[{"LocationAction":{"location":{"x":7,"y":1}}},{"LocationAction":{"location":{"x":9,"y":3}}}]},
- // {"category":"Production","text":"[Nobel Foundation] has been built in [Stockholm]","icons":["BuildingIcons/Nobel Foundation"],"actions":[{"LocationAction":{"location":{"x":9,"y":3}}}]}
- // ]
+ private fun Notification.readActions(json: Json, jsonData: JsonValue) {
if (!jsonData.hasChild("actions")) return
var entry = jsonData.get("actions").child
while (entry != null) {
@@ -182,26 +155,5 @@ open class Notification() : IsPartOfGameInfoSerialization {
entry = entry.next
}
}
-
- private fun Notification.readOldFormatAction(json: Json, jsonData: JsonValue) {
- // Old format looks like: "notifications":[
- // {"text":"[Stockholm] has expanded its borders!","icons":["StatIcons/Culture"],"action":{"class":"com.unciv.logic.civilization.LocationAction","locations":[{"x":7,"y":1},{"x":9,"y":3}]},"category":"Cities"},
- // {"text":"[Nobel Foundation] has been built in [Stockholm]","icons":["BuildingIcons/Nobel Foundation"],"action":{"class":"com.unciv.logic.civilization.LocationAction","locations":[{"x":9,"y":3}]},"category":"Production"}
- // ]
- val actionData = jsonData.get("action") ?: return
- val actionClass = actionData.getString("class")
- when (actionClass.substring(actionClass.lastIndexOf('.') + 1)) {
- "LocationAction" -> actions += getOldFormatLocations(json, actionData)
- "TechAction" -> actions += json.readValue(TechAction::class.java, actionData)
- "CityAction" -> actions += json.readValue(CityAction::class.java, actionData)
- "DiplomacyAction" -> actions += json.readValue(DiplomacyAction::class.java, actionData)
- "MayaLongCountAction" -> actions += MayaLongCountAction()
- }
- }
-
- private fun getOldFormatLocations(json: Json, actionData: JsonValue): Sequence {
- val locations = json.readValue("locations", Array::class.java, actionData)
- return locations.asSequence().map { LocationAction(it) }
- }
}
}
diff --git a/core/src/com/unciv/logic/civilization/NotificationActions.kt b/core/src/com/unciv/logic/civilization/NotificationActions.kt
index db2568ea8f2ac..aa839d8b9c720 100644
--- a/core/src/com/unciv/logic/civilization/NotificationActions.kt
+++ b/core/src/com/unciv/logic/civilization/NotificationActions.kt
@@ -133,7 +133,7 @@ class OverviewAction(
internal class NotificationActionsDeserializer {
/* This exists as trick to leverage readFields for Json deserialization.
// The serializer writes each NotificationAction as json object (within the actions array),
- // containing the class simpleName as subfield name, which carries any (on none) subclass-
+ // containing the class simpleName as subfield name, which carries any (or none) subclass-
// specific data as its object value. So, reading this from json data will fill just one of the
// fields, and the listOfNotNull will output that field only.
// Even though we know there's only one result, no need to first() since it's no advantage to the caller.
diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt b/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt
new file mode 100644
index 0000000000000..b58da789be53d
--- /dev/null
+++ b/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt
@@ -0,0 +1,225 @@
+package com.unciv.logic.civilization.diplomacy
+
+import com.unciv.Constants
+import com.unciv.logic.civilization.AlertType
+import com.unciv.logic.civilization.NotificationCategory
+import com.unciv.logic.civilization.NotificationIcon
+import com.unciv.logic.civilization.PopupAlert
+import com.unciv.models.ruleset.unique.UniqueTriggerActivation
+import com.unciv.models.ruleset.unique.UniqueType
+
+object DeclareWar {
+
+ /** Declares war with the other civ in this diplomacy manager.
+ * Handles all war effects and diplomatic changes with other civs and such.
+ *
+ * @param indirectCityStateAttack Influence with city states should only be set to -60
+ * when they are attacked directly, not when their ally is attacked.
+ * When @indirectCityStateAttack is set to true, we thus don't reset the influence with this city state.
+ * Should only ever be set to true for calls originating from within this function.
+ */
+ internal fun declareWar(diplomacyManager: DiplomacyManager, indirectCityStateAttack: Boolean = false) {
+ val civInfo = diplomacyManager.civInfo
+ val otherCiv = diplomacyManager.otherCiv()
+ val otherCivDiplomacy = diplomacyManager.otherCivDiplomacy()
+
+ if (otherCiv.isCityState() && !indirectCityStateAttack) {
+ otherCivDiplomacy.setInfluence(-60f)
+ civInfo.numMinorCivsAttacked += 1
+ otherCiv.cityStateFunctions.cityStateAttacked(civInfo)
+
+ // You attacked your own ally, you're a right bastard
+ if (otherCiv.getAllyCiv() == civInfo.civName) {
+ otherCiv.cityStateFunctions.updateAllyCivForCityState()
+ otherCivDiplomacy.setInfluence(-120f)
+ for (knownCiv in civInfo.getKnownCivs()) {
+ knownCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.BetrayedDeclarationOfFriendship, -10f)
+ }
+ }
+ }
+
+ onWarDeclared(diplomacyManager, true)
+ onWarDeclared(otherCivDiplomacy, false)
+
+ otherCiv.addNotification("[${civInfo.civName}] has declared war on us!",
+ NotificationCategory.Diplomacy, NotificationIcon.War, civInfo.civName)
+ otherCiv.popupAlerts.add(PopupAlert(AlertType.WarDeclaration, civInfo.civName))
+
+ diplomacyManager.getCommonKnownCivs().forEach {
+ it.addNotification("[${civInfo.civName}] has declared war on [${diplomacyManager.otherCivName}]!",
+ NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.War, diplomacyManager.otherCivName)
+ }
+
+ otherCivDiplomacy.setModifier(DiplomaticModifiers.DeclaredWarOnUs, -20f)
+ otherCivDiplomacy.removeModifier(DiplomaticModifiers.ReturnedCapturedUnits)
+
+ for (thirdCiv in civInfo.getKnownCivs()) {
+ if (thirdCiv.isAtWarWith(otherCiv)) {
+ if (thirdCiv.isCityState()) thirdCiv.getDiplomacyManager(civInfo).addInfluence(10f)
+ else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.SharedEnemy, 5f)
+ } else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.WarMongerer, -5f)
+ }
+
+ breakTreaties(diplomacyManager)
+
+ if (otherCiv.isMajorCiv())
+ for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponDeclaringWar))
+ UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo)
+ }
+
+ private fun breakTreaties(diplomacyManager: DiplomacyManager) {
+ val otherCiv = diplomacyManager.otherCiv()
+ val otherCivDiplomacy = diplomacyManager.otherCivDiplomacy()
+
+ var betrayedFriendship = false
+ var betrayedDefensivePact = false
+ if (diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)) {
+ betrayedFriendship = true
+ diplomacyManager.removeFlag(DiplomacyFlags.DeclarationOfFriendship)
+ otherCivDiplomacy.removeModifier(DiplomaticModifiers.DeclarationOfFriendship)
+ }
+ otherCivDiplomacy.removeFlag(DiplomacyFlags.DeclarationOfFriendship)
+
+ if (diplomacyManager.hasFlag(DiplomacyFlags.DefensivePact)) {
+ betrayedDefensivePact = true
+ diplomacyManager.removeFlag(DiplomacyFlags.DefensivePact)
+ otherCivDiplomacy.removeModifier(DiplomaticModifiers.DefensivePact)
+ }
+ otherCivDiplomacy.removeFlag(DiplomacyFlags.DefensivePact)
+
+ if (betrayedFriendship || betrayedDefensivePact) {
+ for (knownCiv in diplomacyManager.civInfo.getKnownCivs()) {
+ val diploManager = knownCiv.getDiplomacyManager(diplomacyManager.civInfo)
+ if (betrayedFriendship) {
+ val amount = if (knownCiv == otherCiv) -40f else -20f
+ diploManager.addModifier(DiplomaticModifiers.BetrayedDeclarationOfFriendship, amount)
+ }
+ if (betrayedDefensivePact) {
+ //Note: this stacks with Declaration of Friendship
+ val amount = if (knownCiv == otherCiv) -20f else -10f
+ diploManager.addModifier(DiplomaticModifiers.BetrayedDefensivePact, amount)
+ }
+ diploManager.removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies) // obviously this guy's declarations of friendship aren't worth much.
+ diploManager.removeModifier(DiplomaticModifiers.SignedDefensivePactWithOurAllies)
+ }
+ }
+
+ if (diplomacyManager.hasFlag(DiplomacyFlags.ResearchAgreement)) {
+ diplomacyManager.removeFlag(DiplomacyFlags.ResearchAgreement)
+ diplomacyManager.totalOfScienceDuringRA = 0
+ otherCivDiplomacy.totalOfScienceDuringRA = 0
+ }
+ otherCivDiplomacy.removeFlag(DiplomacyFlags.ResearchAgreement)
+ }
+
+
+
+ /** Everything that happens to both sides equally when war is declared by one side on the other */
+ private fun onWarDeclared(diplomacyManager:DiplomacyManager, isOffensiveWar: Boolean) {
+ // Cancel all trades.
+ for (trade in diplomacyManager.trades)
+ for (offer in trade.theirOffers.filter { it.duration > 0 && it.name != Constants.defensivePact})
+ diplomacyManager.civInfo.addNotification("[${offer.name}] from [${diplomacyManager.otherCivName}] has ended",
+ NotificationCategory.Trade, diplomacyManager.otherCivName, NotificationIcon.Trade)
+ diplomacyManager.trades.clear()
+
+ val civAtWarWith = diplomacyManager.otherCiv()
+
+ // If we attacked, then we need to end all of our defensive pacts acording to Civ 5
+ if (isOffensiveWar) {
+ removeDefensivePacts(diplomacyManager)
+ }
+ diplomacyManager.diplomaticStatus = DiplomaticStatus.War
+
+ if (diplomacyManager.civInfo.isMajorCiv()) {
+ if (!isOffensiveWar && !civAtWarWith.isCityState()) callInDefensivePactAllies(diplomacyManager)
+ callInCityStateAllies(diplomacyManager)
+ }
+
+ if (diplomacyManager.civInfo.isCityState() &&
+ diplomacyManager.civInfo.cityStateFunctions.getProtectorCivs().contains(civAtWarWith)) {
+ diplomacyManager.civInfo.cityStateFunctions.removeProtectorCiv(civAtWarWith, forced = true)
+ }
+
+ diplomacyManager.updateHasOpenBorders()
+
+ diplomacyManager.removeModifier(DiplomaticModifiers.YearsOfPeace)
+ diplomacyManager.setFlag(DiplomacyFlags.DeclinedPeace, 10)/// AI won't propose peace for 10 turns
+ diplomacyManager.setFlag(DiplomacyFlags.DeclaredWar, 10) // AI won't agree to trade for 10 turns
+ diplomacyManager.removeFlag(DiplomacyFlags.BorderConflict)
+ }
+
+ /**
+ * Removes all defensive Pacts and trades. Notifies other civs.
+ * Note: Does not remove the flags and modifiers of the otherCiv if there is a defensive pact.
+ * This is so that we can apply more negative modifiers later.
+ */
+ private fun removeDefensivePacts(diplomacyManager: DiplomacyManager) {
+ val civAtWarWith = diplomacyManager.otherCiv()
+ for (thirdPartyDiploManager in diplomacyManager.civInfo.diplomacy.values) {
+ if (thirdPartyDiploManager.diplomaticStatus != DiplomaticStatus.DefensivePact) continue
+
+ // Cancel the defensive pact functionality
+ thirdPartyDiploManager.diplomaticStatus = DiplomaticStatus.Peace
+ thirdPartyDiploManager.otherCivDiplomacy().diplomaticStatus = DiplomaticStatus.Peace
+
+ // We already removed the trades and functionality
+ // But we don't want to remove the flags yet so we can process BetrayedDefensivePact later
+ if (thirdPartyDiploManager.otherCiv() != civAtWarWith) {
+ // Trades with defensive pact are now invalid
+ val defensivePactOffer = thirdPartyDiploManager.trades
+ .firstOrNull { trade -> trade.ourOffers.any { offer -> offer.name == Constants.defensivePact } }
+ thirdPartyDiploManager.trades.remove(defensivePactOffer)
+ val theirDefensivePactOffer = thirdPartyDiploManager.otherCivDiplomacy().trades
+ .firstOrNull { trade -> trade.ourOffers.any { offer -> offer.name == Constants.defensivePact } }
+ thirdPartyDiploManager.otherCivDiplomacy().trades.remove(theirDefensivePactOffer)
+
+ thirdPartyDiploManager.removeFlag(DiplomacyFlags.DefensivePact)
+ thirdPartyDiploManager.otherCivDiplomacy().removeFlag(DiplomacyFlags.DefensivePact)
+ }
+ for (civ in diplomacyManager.getCommonKnownCivs().filter { civ -> civ.isMajorCiv() || civ.isSpectator() }) {
+ civ.addNotification("[${diplomacyManager.civInfo.civName}] canceled their Defensive Pact with [${thirdPartyDiploManager.otherCivName}]!",
+ NotificationCategory.Diplomacy, diplomacyManager.civInfo.civName, NotificationIcon.Diplomacy, thirdPartyDiploManager.otherCivName)
+ }
+ diplomacyManager.civInfo.addNotification("We have canceled our Defensive Pact with [${thirdPartyDiploManager.otherCivName}]!",
+ NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, thirdPartyDiploManager.otherCivName)
+ }
+ }
+
+
+ /**
+ * Goes through each DiplomacyManager with a defensive pact that is not already in the war.
+ * The civ that we are calling them in against should no longer have a defensive pact with us.
+ */
+ private fun callInDefensivePactAllies(diplomacyManager: DiplomacyManager) {
+ val civAtWarWith = diplomacyManager.otherCiv()
+ for (ourDefensivePact in diplomacyManager.civInfo.diplomacy.values.filter { ourDipManager ->
+ ourDipManager.diplomaticStatus == DiplomaticStatus.DefensivePact
+ && !ourDipManager.otherCiv().isDefeated()
+ && !ourDipManager.otherCiv().isAtWarWith(civAtWarWith)
+ }) {
+ val ally = ourDefensivePact.otherCiv()
+ if (!civAtWarWith.knows(ally)) civAtWarWith.diplomacyFunctions.makeCivilizationsMeet(ally, true)
+ // Have the aggressor declare war on the ally.
+ civAtWarWith.getDiplomacyManager(ally).declareWar(true)
+ // Notify the aggressor
+ civAtWarWith.addNotification("[${ally.civName}] has joined the defensive war with [${diplomacyManager.civInfo.civName}]!",
+ NotificationCategory.Diplomacy, ally.civName, NotificationIcon.Diplomacy, diplomacyManager.civInfo.civName)
+ }
+ }
+
+ private fun callInCityStateAllies(diplomacyManager: DiplomacyManager) {
+ val civAtWarWith = diplomacyManager.otherCiv()
+ for (thirdCiv in diplomacyManager.civInfo.getKnownCivs()
+ .filter { it.isCityState() && it.getAllyCiv() == diplomacyManager.civInfo.civName }) {
+
+ if (thirdCiv.knows(civAtWarWith) && !thirdCiv.isAtWarWith(civAtWarWith))
+ thirdCiv.getDiplomacyManager(civAtWarWith).declareWar(true)
+ else if (!thirdCiv.knows(civAtWarWith)) {
+ // Our city state ally has not met them yet, so they have to meet first
+ thirdCiv.diplomacyFunctions.makeCivilizationsMeet(civAtWarWith, warOnContact = true)
+ thirdCiv.getDiplomacyManager(civAtWarWith).declareWar(true)
+ }
+ }
+ }
+}
diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt
index acecdbc65feef..55d4970d1d298 100644
--- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt
+++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt
@@ -6,7 +6,7 @@ import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.PopupAlert
-import com.unciv.logic.map.mapunit.UnitMovement
+import com.unciv.logic.map.mapunit.movement.UnitMovement
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
@@ -86,7 +86,7 @@ class DiplomacyFunctions(val civInfo: Civilization){
}
}
}
-
+
fun canSignDeclarationOfFriendshipWith(otherCiv: Civilization): Boolean {
return otherCiv.isMajorCiv() && !otherCiv.isAtWarWith(civInfo)
&& !civInfo.getDiplomacyManager(otherCiv).hasFlag(DiplomacyFlags.Denunciation)
@@ -131,7 +131,8 @@ class DiplomacyFunctions(val civInfo: Civilization){
fun canSignDefensivePactWith(otherCiv: Civilization): Boolean {
val diplomacyManager = civInfo.getDiplomacyManager(otherCiv)
return canSignDefensivePact() && otherCiv.diplomacyFunctions.canSignDefensivePact()
- && diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
+ && (diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
+ || diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DeclarationOfFriendship))
&& !diplomacyManager.hasFlag(DiplomacyFlags.DefensivePact)
&& !diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DefensivePact)
&& diplomacyManager.diplomaticStatus != DiplomaticStatus.DefensivePact
diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt
index df81ef34c0292..16523ff2cd009 100644
--- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt
+++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt
@@ -3,11 +3,9 @@ package com.unciv.logic.civilization.diplomacy
import com.badlogic.gdx.graphics.Color
import com.unciv.Constants
import com.unciv.logic.IsPartOfGameInfoSerialization
-import com.unciv.logic.civilization.AlertType
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.NotificationIcon
-import com.unciv.logic.civilization.PopupAlert
import com.unciv.logic.trade.Trade
import com.unciv.logic.trade.TradeOffer
import com.unciv.logic.trade.TradeType
@@ -144,7 +142,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
private var influence = 0f
/** Total of each turn Science during Research Agreement */
- private var totalOfScienceDuringRA = 0
+ internal var totalOfScienceDuringRA = 0
fun clone(): DiplomacyManager {
val toReturn = DiplomacyManager()
@@ -387,6 +385,8 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
fun canDeclareWar() = turnsToPeaceTreaty() == 0 && diplomaticStatus != DiplomaticStatus.War
+ fun declareWar(indirectCityStateAttack: Boolean = false) = DeclareWar.declareWar(this, indirectCityStateAttack)
+
//Used for nuke
fun canAttack() = turnsToPeaceTreaty() == 0
@@ -726,210 +726,6 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
}
}
- /** Everything that happens to both sides equally when war is declared by one side on the other */
- private fun onWarDeclared(isOffensiveWar: Boolean) {
- // Cancel all trades.
- for (trade in trades)
- for (offer in trade.theirOffers.filter { it.duration > 0 && it.name != Constants.defensivePact})
- civInfo.addNotification("[${offer.name}] from [$otherCivName] has ended",
- NotificationCategory.Trade, otherCivName, NotificationIcon.Trade)
- trades.clear()
-
- val civAtWarWith = otherCiv()
-
- // If we attacked, then we need to end all of our defensive pacts acording to Civ 5
- if (isOffensiveWar) {
- removeDefensivePacts()
- }
- diplomaticStatus = DiplomaticStatus.War
-
- if (civInfo.isMajorCiv()) {
- if (!isOffensiveWar) callInDefensivePactAllies()
- callInCityStateAllies()
- }
-
- if (civInfo.isCityState() && civInfo.cityStateFunctions.getProtectorCivs().contains(civAtWarWith)) {
- civInfo.cityStateFunctions.removeProtectorCiv(civAtWarWith, forced = true)
- }
-
- updateHasOpenBorders()
-
- removeModifier(DiplomaticModifiers.YearsOfPeace)
- setFlag(DiplomacyFlags.DeclinedPeace, 10)/// AI won't propose peace for 10 turns
- setFlag(DiplomacyFlags.DeclaredWar, 10) // AI won't agree to trade for 10 turns
- removeFlag(DiplomacyFlags.BorderConflict)
- }
-
- /**
- * Removes all defensive Pacts and trades. Notifies other civs.
- * Note: Does not remove the flags and modifiers of the otherCiv if there is a defensive pact.
- * This is so that we can apply more negative modifiers later.
- */
- private fun removeDefensivePacts() {
- val civAtWarWith = otherCiv()
- for (diploManager in civInfo.diplomacy.values) {
- if (diploManager.diplomaticStatus != DiplomaticStatus.DefensivePact) continue
-
- // We already removed the trades and we don't want to remove the flags yet.
- if (diploManager.otherCiv() != civAtWarWith) {
- // Trades with defensive pact are now invalid
- val defensivePactOffer = diploManager.trades
- .firstOrNull { trade -> trade.ourOffers.any { offer -> offer.name == Constants.defensivePact } }
- diploManager.trades.remove(defensivePactOffer)
- val theirDefensivePactOffer = diploManager.otherCivDiplomacy().trades
- .firstOrNull { trade -> trade.ourOffers.any { offer -> offer.name == Constants.defensivePact } }
- diploManager.otherCivDiplomacy().trades.remove(theirDefensivePactOffer)
- diploManager.removeFlag(DiplomacyFlags.DefensivePact)
- diploManager.otherCivDiplomacy().removeFlag(DiplomacyFlags.DefensivePact)
- diploManager.diplomaticStatus = DiplomaticStatus.Peace
- diploManager.otherCivDiplomacy().diplomaticStatus = DiplomaticStatus.Peace
- }
- for (civ in getCommonKnownCivs().filter { civ -> civ.isMajorCiv() || civ.isSpectator() }) {
- civ.addNotification("[${civInfo.civName}] canceled their Defensive Pact with [${diploManager.otherCivName}]!",
- NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy, diploManager.otherCivName)
- }
- civInfo.addNotification("We have canceled our Defensive Pact with [${diploManager.otherCivName}]!",
- NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, diploManager.otherCivName)
- }
- }
-
- /**
- * Goes through each DiplomacyManager with a defensive pact that is not already in the war.
- * The civ that we are calling them in against should no longer have a defensive pact with us.
- */
- private fun callInDefensivePactAllies() {
- val civAtWarWith = otherCiv()
- for (ourDefensivePact in civInfo.diplomacy.values.filter { ourDipManager ->
- ourDipManager.diplomaticStatus == DiplomaticStatus.DefensivePact
- && !ourDipManager.otherCiv().isDefeated()
- && !ourDipManager.otherCiv().isAtWarWith(civAtWarWith)
- }) {
- val ally = ourDefensivePact.otherCiv()
- if (!civAtWarWith.knows(ally)) civAtWarWith.diplomacyFunctions.makeCivilizationsMeet(ally, true)
- // Have the aggressor declare war on the ally.
- civAtWarWith.getDiplomacyManager(ally).declareWar(true)
- // Notify the aggressor
- civAtWarWith.addNotification("[${ally.civName}] has joined the defensive war with [${civInfo.civName}]!",
- NotificationCategory.Diplomacy, ally.civName, NotificationIcon.Diplomacy, civInfo.civName)
- }
- }
-
- private fun callInCityStateAllies() {
- val civAtWarWith = otherCiv()
- for (thirdCiv in civInfo.getKnownCivs()
- .filter { it.isCityState() && it.getAllyCiv() == civInfo.civName }) {
-
- if (thirdCiv.knows(civAtWarWith) && !thirdCiv.isAtWarWith(civAtWarWith))
- thirdCiv.getDiplomacyManager(civAtWarWith).declareWar(true)
- else if (!thirdCiv.knows(civAtWarWith)) {
- // Our city state ally has not met them yet, so they have to meet first
- thirdCiv.diplomacyFunctions.makeCivilizationsMeet(civAtWarWith, warOnContact = true)
- thirdCiv.getDiplomacyManager(civAtWarWith).declareWar(true)
- }
- }
- }
-
- /** Declares war with the other civ in this diplomacy manager.
- * Handles all war effects and diplomatic changes with other civs and such.
- *
- * @param indirectCityStateAttack Influence with city states should only be set to -60
- * when they are attacked directly, not when their ally is attacked.
- * When @indirectCityStateAttack is set to true, we thus don't reset the influence with this city state.
- * Should only ever be set to true for calls originating from within this function.
- */
- fun declareWar(indirectCityStateAttack: Boolean = false) {
- val otherCiv = otherCiv()
- val otherCivDiplomacy = otherCivDiplomacy()
-
- if (otherCiv.isCityState() && !indirectCityStateAttack) {
- otherCivDiplomacy.setInfluence(-60f)
- civInfo.numMinorCivsAttacked += 1
- otherCiv.cityStateFunctions.cityStateAttacked(civInfo)
-
- // You attacked your own ally, you're a right bastard
- if (otherCiv.getAllyCiv() == civInfo.civName) {
- otherCiv.cityStateFunctions.updateAllyCivForCityState()
- otherCivDiplomacy.setInfluence(-120f)
- for (knownCiv in civInfo.getKnownCivs()) {
- knownCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.BetrayedDeclarationOfFriendship, -10f)
- }
- }
- }
-
- onWarDeclared(true)
- otherCivDiplomacy.onWarDeclared(false)
-
- otherCiv.addNotification("[${civInfo.civName}] has declared war on us!",
- NotificationCategory.Diplomacy, NotificationIcon.War, civInfo.civName)
- otherCiv.popupAlerts.add(PopupAlert(AlertType.WarDeclaration, civInfo.civName))
-
- getCommonKnownCivs().forEach {
- it.addNotification("[${civInfo.civName}] has declared war on [$otherCivName]!",
- NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.War, otherCivName)
- }
-
- otherCivDiplomacy.setModifier(DiplomaticModifiers.DeclaredWarOnUs, -20f)
- otherCivDiplomacy.removeModifier(DiplomaticModifiers.ReturnedCapturedUnits)
-
- for (thirdCiv in civInfo.getKnownCivs()) {
- if (thirdCiv.isAtWarWith(otherCiv)) {
- if (thirdCiv.isCityState()) thirdCiv.getDiplomacyManager(civInfo).addInfluence(10f)
- else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.SharedEnemy, 5f)
- } else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.WarMongerer, -5f)
- }
-
- breakTreaties()
-
- if (otherCiv.isMajorCiv())
- for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponDeclaringWar))
- UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo)
- }
-
- private fun breakTreaties() {
- val otherCiv = otherCiv()
- val otherCivDiplomacy = otherCivDiplomacy()
-
- var betrayedFriendship = false
- var betrayedDefensivePact = false
- if (hasFlag(DiplomacyFlags.DeclarationOfFriendship)) {
- betrayedFriendship = true
- removeFlag(DiplomacyFlags.DeclarationOfFriendship)
- otherCivDiplomacy.removeModifier(DiplomaticModifiers.DeclarationOfFriendship)
- }
- otherCivDiplomacy.removeFlag(DiplomacyFlags.DeclarationOfFriendship)
-
- if (hasFlag(DiplomacyFlags.DefensivePact)) {
- betrayedDefensivePact = true
- removeFlag(DiplomacyFlags.DefensivePact)
- otherCivDiplomacy.removeModifier(DiplomaticModifiers.DefensivePact)
- }
- otherCivDiplomacy.removeFlag(DiplomacyFlags.DefensivePact)
-
- if (betrayedFriendship || betrayedDefensivePact) {
- for (knownCiv in civInfo.getKnownCivs()) {
- val diploManager = knownCiv.getDiplomacyManager(civInfo)
- if (betrayedFriendship) {
- val amount = if (knownCiv == otherCiv) -40f else -20f
- diploManager.addModifier(DiplomaticModifiers.BetrayedDeclarationOfFriendship, amount)
- }
- if (betrayedDefensivePact) {
- //Note: this stacks with Declaration of Friendship
- val amount = if (knownCiv == otherCiv) -20f else -10f
- diploManager.addModifier(DiplomaticModifiers.BetrayedDefensivePact, amount)
- }
- diploManager.removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies) // obviously this guy's declarations of friendship aren't worth much.
- diploManager.removeModifier(DiplomaticModifiers.SignedDefensivePactWithOurAllies)
- }
- }
-
- if (hasFlag(DiplomacyFlags.ResearchAgreement)) {
- removeFlag(DiplomacyFlags.ResearchAgreement)
- totalOfScienceDuringRA = 0
- otherCivDiplomacy.totalOfScienceDuringRA = 0
- }
- otherCivDiplomacy.removeFlag(DiplomacyFlags.ResearchAgreement)
- }
-
/** Should only be called from makePeace */
private fun makePeaceOneSide() {
diplomaticStatus = DiplomaticStatus.Peace
@@ -987,7 +783,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
return diplomaticModifiers[modifier.name]!!
}
- private fun removeModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.remove(modifier.name)
+ internal fun removeModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.remove(modifier.name)
fun hasModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.containsKey(modifier.name)
/** @param amount always positive, so you don't need to think about it */
diff --git a/core/src/com/unciv/logic/civilization/managers/GoldenAgeManager.kt b/core/src/com/unciv/logic/civilization/managers/GoldenAgeManager.kt
index c5bdb872f690e..40a01d80af764 100644
--- a/core/src/com/unciv/logic/civilization/managers/GoldenAgeManager.kt
+++ b/core/src/com/unciv/logic/civilization/managers/GoldenAgeManager.kt
@@ -9,6 +9,7 @@ import com.unciv.logic.civilization.PopupAlert
import com.unciv.models.ruleset.unique.UniqueTriggerActivation
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.components.extensions.toPercent
+import kotlin.math.max
class GoldenAgeManager : IsPartOfGameInfoSerialization {
@Transient
@@ -58,7 +59,8 @@ class GoldenAgeManager : IsPartOfGameInfoSerialization {
}
fun endTurn(happiness: Int) {
- if (happiness > 0 && !isGoldenAge()) storedHappiness += happiness
+ if (!isGoldenAge())
+ storedHappiness = (storedHappiness + happiness).coerceAtLeast(0)
if (isGoldenAge())
turnsLeftForCurrentGoldenAge--
diff --git a/core/src/com/unciv/logic/civilization/managers/ReligionManager.kt b/core/src/com/unciv/logic/civilization/managers/ReligionManager.kt
index 80f06a6769d5c..929c5912169a1 100644
--- a/core/src/com/unciv/logic/civilization/managers/ReligionManager.kt
+++ b/core/src/com/unciv/logic/civilization/managers/ReligionManager.kt
@@ -13,7 +13,7 @@ import com.unciv.models.ruleset.unique.UniqueTriggerActivation
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.ui.components.extensions.toPercent
-import java.lang.Integer.max
+import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionModifiers
import java.lang.Integer.min
import kotlin.random.Random
@@ -256,11 +256,9 @@ class ReligionManager : IsPartOfGameInfoSerialization {
}
- fun mayFoundReligionAtAll(prophet: MapUnit): Boolean {
+ fun mayFoundReligionAtAll(): Boolean {
if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion
if (religionState >= ReligionState.Religion) return false // Already created a major religion
- // Already used its power for other things
- if (prophet.abilityUsesLeft.any { it.value != prophet.maxAbilityUses[it.key] }) return false
if (!civInfo.isMajorCiv()) return false // Only major civs may use religion
@@ -271,7 +269,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
}
fun mayFoundReligionNow(prophet: MapUnit): Boolean {
- if (!mayFoundReligionAtAll(prophet)) return false
+ if (!mayFoundReligionAtAll()) return false
if (!prophet.getTile().isCityCenter()) return false
if (prophet.getTile().getCity()!!.isHolyCity()) return false
// No double holy cities. Not sure if these were allowed in the base game
@@ -289,11 +287,9 @@ class ReligionManager : IsPartOfGameInfoSerialization {
}
fun mayEnhanceReligionAtAll(prophet: MapUnit): Boolean {
- if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion, no enhancing
+ if (!civInfo.gameInfo.isReligionEnabled()) return false
if (religion == null) return false // First found a pantheon
if (religionState != ReligionState.Religion) return false // First found an actual religion
- // Already used its power for other things
- if (prophet.abilityUsesLeft.any { it.value != prophet.maxAbilityUses[it.key] }) return false
if (!civInfo.isMajorCiv()) return false // Only major civs
if (numberOfBeliefsAvailable(BeliefType.Follower) == 0)
@@ -445,7 +441,11 @@ class ReligionManager : IsPartOfGameInfoSerialization {
fun maySpreadReligionAtAll(missionary: MapUnit): Boolean {
if (!civInfo.isMajorCiv()) return false // Only major civs
if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion, no spreading
- if (!missionary.canDoLimitedAction(Constants.spreadReligion)) return false
+
+ val religion = missionary.civ.gameInfo.religions[missionary.religion] ?: return false
+ if (religion.isPantheon()) return false
+ if (!missionary.canDoLimitedAction(Constants.spreadReligion)
+ && UnitActionModifiers.getUsableUnitActionUniques(missionary, UniqueType.CanSpreadReligion).none()) return false
return true
}
diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerationRandomness.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerationRandomness.kt
new file mode 100644
index 0000000000000..05fe1a83d453b
--- /dev/null
+++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerationRandomness.kt
@@ -0,0 +1,90 @@
+package com.unciv.logic.map.mapgenerator
+
+import com.unciv.logic.map.HexMath
+import com.unciv.logic.map.tile.Tile
+import com.unciv.utils.Log
+import com.unciv.utils.debug
+import kotlin.math.max
+import kotlin.math.pow
+import kotlin.random.Random
+
+class MapGenerationRandomness {
+ var RNG = Random(42)
+
+ fun seedRNG(seed: Long = 42) {
+ RNG = Random(seed)
+ }
+
+ /**
+ * Generates a perlin noise channel combining multiple octaves
+ * Default settings generate mostly within [-0.55, 0.55], but clustered around 0.0
+ * About 28% are < -0.1 and 28% are > 0.1
+ *
+ * @param tile Source for x / x coordinates.
+ * @param seed Misnomer: actually the z value the Perlin cloud is 'cut' on.
+ * @param nOctaves is the number of octaves.
+ * @param persistence is the scaling factor of octave amplitudes.
+ * @param lacunarity is the scaling factor of octave frequencies.
+ * @param scale is the distance the noise is observed from.
+ */
+ fun getPerlinNoise(
+ tile: Tile,
+ seed: Double,
+ nOctaves: Int = 6,
+ persistence: Double = 0.5,
+ lacunarity: Double = 2.0,
+ scale: Double = 30.0
+ ): Double {
+ val worldCoords = HexMath.hex2WorldCoords(tile.position)
+ return Perlin.noise3d(worldCoords.x.toDouble(), worldCoords.y.toDouble(), seed, nOctaves, persistence, lacunarity, scale)
+ }
+
+
+ fun chooseSpreadOutLocations(number: Int, suitableTiles: List, mapRadius: Int): ArrayList {
+ if (number <= 0) return ArrayList(0)
+
+ // Determine sensible initial distance from number of desired placements and mapRadius
+ // empiric formula comes very close to eliminating retries for distance.
+ // The `if` means if we need to fill 60% or more of the available tiles, no sense starting with minimum distance 2.
+ val sparsityFactor = (HexMath.getHexagonalRadiusForArea(suitableTiles.size) / mapRadius).pow(0.333f)
+ val initialDistance = if (number == 1 || number * 5 >= suitableTiles.size * 3) 1
+ else max(1, (mapRadius * 0.666f / HexMath.getHexagonalRadiusForArea(number).pow(0.9f) * sparsityFactor + 0.5).toInt())
+
+ // If possible, we want to equalize the base terrains upon which
+ // the resources are found, so we save how many have been
+ // found for each base terrain and try to get one from the lowest
+ val baseTerrainsToChosenTiles = HashMap()
+ for (tileInfo in suitableTiles){
+ if (tileInfo.baseTerrain !in baseTerrainsToChosenTiles)
+ baseTerrainsToChosenTiles[tileInfo.baseTerrain] = 0
+ }
+
+ for (distanceBetweenResources in initialDistance downTo 1) {
+ var availableTiles = suitableTiles
+ val chosenTiles = ArrayList(number)
+
+ for (terrain in baseTerrainsToChosenTiles.keys)
+ baseTerrainsToChosenTiles[terrain] = 0
+
+ for (i in 1..number) {
+ if (availableTiles.isEmpty()) break
+ val orderedKeys = baseTerrainsToChosenTiles.entries
+ .sortedBy { it.value }.map { it.key }
+ val firstKeyWithTilesLeft = orderedKeys
+ .first { availableTiles.any { tile -> tile.baseTerrain == it} }
+ val chosenTile = availableTiles.filter { it.baseTerrain == firstKeyWithTilesLeft }.random(RNG)
+ availableTiles = availableTiles.filter { it.aerialDistanceTo(chosenTile) > distanceBetweenResources }
+ chosenTiles.add(chosenTile)
+ baseTerrainsToChosenTiles[firstKeyWithTilesLeft] = baseTerrainsToChosenTiles[firstKeyWithTilesLeft]!! + 1
+ }
+ if (chosenTiles.size == number || distanceBetweenResources == 1) {
+ // Either we got them all, or we're not going to get anything better
+ if (Log.shouldLog() && distanceBetweenResources < initialDistance)
+ debug("chooseSpreadOutLocations: distance $distanceBetweenResources < initial $initialDistance")
+ return chosenTiles
+ }
+ }
+ // unreachable due to last loop iteration always returning and initialDistance >= 1
+ throw Exception("Unreachable code reached!")
+ }
+}
diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt
index b0eaee60f3efe..c6c6a32d50636 100644
--- a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt
+++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt
@@ -3,11 +3,11 @@ package com.unciv.logic.map.mapgenerator
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.civilization.Civilization
-import com.unciv.logic.map.HexMath
import com.unciv.logic.map.MapParameters
import com.unciv.logic.map.MapShape
import com.unciv.logic.map.MapType
import com.unciv.logic.map.TileMap
+import com.unciv.logic.map.mapgenerator.mapregions.MapRegions
import com.unciv.logic.map.tile.Tile
import com.unciv.models.Counter
import com.unciv.models.metadata.GameParameters
@@ -19,7 +19,6 @@ import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.screens.mapeditorscreen.MapGeneratorSteps
import com.unciv.ui.screens.mapeditorscreen.TileInfoNormalizer
-import com.unciv.utils.Log
import com.unciv.utils.debug
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.isActive
@@ -30,7 +29,6 @@ import kotlin.math.roundToInt
import kotlin.math.sign
import kotlin.math.sqrt
import kotlin.math.ulp
-import kotlin.random.Random
/** Map generator, used by new game, map editor and main menu background
@@ -898,83 +896,3 @@ class MapGenerator(val ruleset: Ruleset, private val coroutineScope: CoroutineSc
}
}
-class MapGenerationRandomness {
- var RNG = Random(42)
-
- fun seedRNG(seed: Long = 42) {
- RNG = Random(seed)
- }
-
- /**
- * Generates a perlin noise channel combining multiple octaves
- * Default settings generate mostly within [-0.55, 0.55], but clustered around 0.0
- * About 28% are < -0.1 and 28% are > 0.1
- *
- * @param tile Source for x / x coordinates.
- * @param seed Misnomer: actually the z value the Perlin cloud is 'cut' on.
- * @param nOctaves is the number of octaves.
- * @param persistence is the scaling factor of octave amplitudes.
- * @param lacunarity is the scaling factor of octave frequencies.
- * @param scale is the distance the noise is observed from.
- */
- fun getPerlinNoise(
- tile: Tile,
- seed: Double,
- nOctaves: Int = 6,
- persistence: Double = 0.5,
- lacunarity: Double = 2.0,
- scale: Double = 30.0
- ): Double {
- val worldCoords = HexMath.hex2WorldCoords(tile.position)
- return Perlin.noise3d(worldCoords.x.toDouble(), worldCoords.y.toDouble(), seed, nOctaves, persistence, lacunarity, scale)
- }
-
-
- fun chooseSpreadOutLocations(number: Int, suitableTiles: List, mapRadius: Int): ArrayList {
- if (number <= 0) return ArrayList(0)
-
- // Determine sensible initial distance from number of desired placements and mapRadius
- // empiric formula comes very close to eliminating retries for distance.
- // The `if` means if we need to fill 60% or more of the available tiles, no sense starting with minimum distance 2.
- val sparsityFactor = (HexMath.getHexagonalRadiusForArea(suitableTiles.size) / mapRadius).pow(0.333f)
- val initialDistance = if (number == 1 || number * 5 >= suitableTiles.size * 3) 1
- else max(1, (mapRadius * 0.666f / HexMath.getHexagonalRadiusForArea(number).pow(0.9f) * sparsityFactor + 0.5).toInt())
-
- // If possible, we want to equalize the base terrains upon which
- // the resources are found, so we save how many have been
- // found for each base terrain and try to get one from the lowest
- val baseTerrainsToChosenTiles = HashMap()
- for (tileInfo in suitableTiles){
- if (tileInfo.baseTerrain !in baseTerrainsToChosenTiles)
- baseTerrainsToChosenTiles[tileInfo.baseTerrain] = 0
- }
-
- for (distanceBetweenResources in initialDistance downTo 1) {
- var availableTiles = suitableTiles
- val chosenTiles = ArrayList(number)
-
- for (terrain in baseTerrainsToChosenTiles.keys)
- baseTerrainsToChosenTiles[terrain] = 0
-
- for (i in 1..number) {
- if (availableTiles.isEmpty()) break
- val orderedKeys = baseTerrainsToChosenTiles.entries
- .sortedBy { it.value }.map { it.key }
- val firstKeyWithTilesLeft = orderedKeys
- .first { availableTiles.any { tile -> tile.baseTerrain == it} }
- val chosenTile = availableTiles.filter { it.baseTerrain == firstKeyWithTilesLeft }.random(RNG)
- availableTiles = availableTiles.filter { it.aerialDistanceTo(chosenTile) > distanceBetweenResources }
- chosenTiles.add(chosenTile)
- baseTerrainsToChosenTiles[firstKeyWithTilesLeft] = baseTerrainsToChosenTiles[firstKeyWithTilesLeft]!! + 1
- }
- if (chosenTiles.size == number || distanceBetweenResources == 1) {
- // Either we got them all, or we're not going to get anything better
- if (Log.shouldLog() && distanceBetweenResources < initialDistance)
- debug("chooseSpreadOutLocations: distance $distanceBetweenResources < initial $initialDistance")
- return chosenTiles
- }
- }
- // unreachable due to last loop iteration always returning and initialDistance >= 1
- throw Exception("Unreachable code reached!")
- }
-}
diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt b/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt
deleted file mode 100644
index a486a510f956d..0000000000000
--- a/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt
+++ /dev/null
@@ -1,1783 +0,0 @@
-package com.unciv.logic.map.mapgenerator
-
-import com.badlogic.gdx.math.Rectangle
-import com.badlogic.gdx.math.Vector2
-import com.unciv.Constants
-import com.unciv.logic.civilization.Civilization
-import com.unciv.logic.map.MapResources
-import com.unciv.logic.map.MapShape
-import com.unciv.logic.map.TileMap
-import com.unciv.logic.map.tile.Tile
-import com.unciv.models.metadata.GameParameters
-import com.unciv.models.ruleset.Ruleset
-import com.unciv.models.ruleset.tile.ResourceType
-import com.unciv.models.ruleset.tile.Terrain
-import com.unciv.models.ruleset.tile.TerrainType
-import com.unciv.models.ruleset.tile.TileResource
-import com.unciv.models.ruleset.unique.StateForConditionals
-import com.unciv.models.ruleset.unique.Unique
-import com.unciv.models.ruleset.unique.UniqueType
-import com.unciv.models.stats.Stat
-import com.unciv.models.translations.equalsPlaceholderText
-import com.unciv.models.translations.getPlaceholderParameters
-import com.unciv.ui.components.extensions.randomWeighted
-import com.unciv.utils.Log
-import com.unciv.utils.Tag
-import kotlin.math.abs
-import kotlin.math.max
-import kotlin.math.min
-import kotlin.math.pow
-import kotlin.math.roundToInt
-import kotlin.random.Random
-
-class MapRegions (val ruleset: Ruleset){
- companion object {
- val minimumFoodForRing = mapOf(1 to 1, 2 to 4, 3 to 4)
- val minimumProdForRing = mapOf(1 to 0, 2 to 0, 3 to 2)
- val minimumGoodForRing = mapOf(1 to 3, 2 to 6, 3 to 8)
- const val maximumJunk = 9
-
- val firstRingFoodScores = listOf(0, 8, 14, 19, 22, 24, 25)
- val firstRingProdScores = listOf(0, 10, 16, 20, 20, 12, 0)
- val secondRingFoodScores = listOf(0, 2, 5, 10, 20, 25, 28, 30, 32, 34, 35)
- val secondRingProdScores = listOf(0, 10, 20, 25, 30, 35)
-
- val closeStartPenaltyForRing = mapOf(
- 0 to 99, 1 to 97, 2 to 95,
- 3 to 92, 4 to 89, 5 to 69,
- 6 to 57, 7 to 24, 8 to 15 )
-
- val randomLuxuryRatios = mapOf(
- 1 to listOf(1f),
- 2 to listOf(0.55f, 0.44f),
- 3 to listOf(0.40f, 0.33f, 0.27f),
- 4 to listOf(0.35f, 0.25f, 0.25f, 0.15f),
- 5 to listOf(0.25f, 0.25f, 0.20f, 0.15f, 0.15f),
- 6 to listOf(0.20f, 0.20f, 0.20f, 0.15f, 0.15f, 0.10f),
- 7 to listOf(0.20f, 0.20f, 0.15f, 0.15f, 0.10f, 0.10f, 0.10f),
- 8 to listOf(0.20f, 0.15f, 0.15f, 0.10f, 0.10f, 0.10f, 0.10f, 0.10f)
- )
-
- // This number is 23 in G&K, but there's a bug where hills are exempt so this number brings
- // the result closer to the density and distribution that was probably intended.
- const val baseMinorDepositFrequency = 30
-
- }
-
- private val regions = ArrayList()
- private var usingArchipelagoRegions = false
- private val tileData = HashMap()
- private val cityStateLuxuries = ArrayList()
- private val randomLuxuries = ArrayList()
-
- /** Creates [numRegions] number of balanced regions for civ starting locations. */
- fun generateRegions(tileMap: TileMap, numRegions: Int) {
- if (numRegions <= 0) return // Don't bother about regions, probably map editor
- if (tileMap.continentSizes.isEmpty()) throw Exception("No Continents on this map!")
- val totalLand = tileMap.continentSizes.values.sum().toFloat()
- val largestContinent = tileMap.continentSizes.values.maxOf { it }.toFloat()
-
- val radius = if (tileMap.mapParameters.shape == MapShape.hexagonal || tileMap.mapParameters.shape == MapShape.flatEarth)
- tileMap.mapParameters.mapSize.radius.toFloat()
- else
- (max(tileMap.mapParameters.mapSize.width / 2, tileMap.mapParameters.mapSize.height / 2)).toFloat()
- // A huge box including the entire map.
- val mapRect = Rectangle(-radius, -radius, radius * 2 + 1, radius * 2 + 1)
-
- // Lots of small islands - just split ut the map in rectangles while ignoring Continents
- // 25% is chosen as limit so Four Corners maps don't fall in this category
- if (largestContinent / totalLand < 0.25f) {
- usingArchipelagoRegions = true
- // Make a huge rectangle covering the entire map
- val hugeRect = Region(tileMap, mapRect, -1) // -1 meaning ignore continent data
- hugeRect.affectedByWorldWrap = false // Might as well start at the seam
- hugeRect.updateTiles()
- divideRegion(hugeRect, numRegions)
- return
- }
- // Continents type - distribute civs according to total fertility, then split as needed
- val continents = tileMap.continentSizes.keys.toMutableList()
- val civsAddedToContinent = HashMap() // Continent ID, civs added
- val continentFertility = HashMap() // Continent ID, total fertility
- // Keep track of the even-q columns each continent is at, to figure out if they wrap
- val continentToColumnsItsIn = HashMap>()
-
- // Calculate continent fertilities and columns
- for (tile in tileMap.values) {
- val continent = tile.getContinent()
- if (continent != -1) {
- continentFertility[continent] = tile.getTileFertility(true) +
- (continentFertility[continent] ?: 0)
-
- if (continentToColumnsItsIn[continent] == null)
- continentToColumnsItsIn[continent] = HashSet()
-
- continentToColumnsItsIn[continent]!!.add(tile.getColumn())
- }
- }
-
- // Assign regions to the best continents, giving half value for region #2 etc
- repeat(numRegions) {
- val bestContinent = continents
- .maxByOrNull { continentFertility[it]!! / (1 + (civsAddedToContinent[it] ?: 0)) }!!
- civsAddedToContinent[bestContinent] = (civsAddedToContinent[bestContinent] ?: 0) + 1
- }
-
- // Split up the continents
- for (continent in civsAddedToContinent.keys) {
- val continentRegion = Region(tileMap, Rectangle(mapRect), continent)
- val cols = continentToColumnsItsIn[continent]!!
- // Set origin at the rightmost column which does not have a neighbor on the left
- continentRegion.rect.x = cols.filter { !cols.contains(it - 1) }.maxOf { it }.toFloat()
- continentRegion.rect.width = cols.size.toFloat()
- if (tileMap.mapParameters.worldWrap) {
- // Check if the continent is wrapping - if the leftmost col is not the one we set origin by
- if (cols.minOf { it } < continentRegion.rect.x)
- continentRegion.affectedByWorldWrap = true
- }
- continentRegion.updateTiles()
- divideRegion(continentRegion, civsAddedToContinent[continent]!!)
- }
- }
-
- /** Recursive function, divides a region into [numDivisions] pars of equal-ish fertility */
- private fun divideRegion(region: Region, numDivisions: Int) {
- if (numDivisions <= 1) {
- // We're all set, save the region and return
- regions.add(region)
- return
- }
-
- val firstDivisions = numDivisions / 2 // Since int division rounds down, works for all numbers
- val splitRegions = splitRegion(region, (100 * firstDivisions) / numDivisions)
- divideRegion(splitRegions.first, firstDivisions)
- divideRegion(splitRegions.second, numDivisions - firstDivisions)
- }
-
- /** Splits a region in 2, with the first having [firstPercent] of total fertility */
- private fun splitRegion(regionToSplit: Region, firstPercent: Int): Pair {
- val targetFertility = (regionToSplit.totalFertility * firstPercent) / 100
-
- val splitOffRegion = Region(regionToSplit.tileMap, Rectangle(regionToSplit.rect), regionToSplit.continentID)
-
- val widerThanTall = regionToSplit.rect.width > regionToSplit.rect.height
-
- var bestSplitPoint = 1 // will be the size of the split-off region
- var closestFertility = 0
- var cumulativeFertility = 0
-
- val highestPointToTry = if (widerThanTall) regionToSplit.rect.width.toInt()
- else regionToSplit.rect.height.toInt()
- val pointsToTry = 1..highestPointToTry
- val halfwayPoint = highestPointToTry/2
-
- for (splitPoint in pointsToTry) {
- val nextRect = if (widerThanTall)
- splitOffRegion.tileMap.getTilesInRectangle(Rectangle(
- splitOffRegion.rect.x + splitPoint - 1, splitOffRegion.rect.y,
- 1f, splitOffRegion.rect.height))
- else
- splitOffRegion.tileMap.getTilesInRectangle(Rectangle(
- splitOffRegion.rect.x, splitOffRegion.rect.y + splitPoint - 1,
- splitOffRegion.rect.width, 1f))
-
- cumulativeFertility += if (splitOffRegion.continentID == -1)
- nextRect.sumOf { it.getTileFertility(false) }
- else
- nextRect.sumOf { if (it.getContinent() == splitOffRegion.continentID) it.getTileFertility(true) else 0 }
-
- // Better than last try?
- val bestSplitPointFertilityDeltaFromTarget = abs(closestFertility - targetFertility)
- val currentSplitPointFertilityDeltaFromTarget = abs(cumulativeFertility - targetFertility)
- if (currentSplitPointFertilityDeltaFromTarget < bestSplitPointFertilityDeltaFromTarget
- || (currentSplitPointFertilityDeltaFromTarget == bestSplitPointFertilityDeltaFromTarget // same fertility split but better 'amount of tiles' split
- && abs(halfwayPoint- splitPoint) < abs(halfwayPoint- bestSplitPoint) )) { // current split point is closer to the halfway point
- bestSplitPoint = splitPoint
- closestFertility = cumulativeFertility
- }
- }
-
- if (widerThanTall) {
- splitOffRegion.rect.width = bestSplitPoint.toFloat()
- regionToSplit.rect.x = splitOffRegion.rect.x + splitOffRegion.rect.width
- regionToSplit.rect.width = regionToSplit.rect.width- bestSplitPoint
- } else {
- splitOffRegion.rect.height = bestSplitPoint.toFloat()
- regionToSplit.rect.y = splitOffRegion.rect.y + splitOffRegion.rect.height
- regionToSplit.rect.height = regionToSplit.rect.height - bestSplitPoint
- }
- splitOffRegion.updateTiles()
- regionToSplit.updateTiles()
-
- return Pair(splitOffRegion, regionToSplit)
- }
-
- /** Buckets for startBias to region assignments, used only in [assignRegions]. [PositiveFallback] is only for logging. */
- private enum class BiasTypes { Coastal, Positive, Negative, Random, PositiveFallback }
-
- fun assignRegions(tileMap: TileMap, civilizations: List, gameParameters: GameParameters) {
- if (civilizations.isEmpty()) return
-
- // first assign region types
- val regionTypes = ruleset.terrains.values.filter { getRegionPriority(it) != null }
- .sortedBy { getRegionPriority(it) }
-
- for (region in regions) {
- region.countTerrains()
-
- for (type in regionTypes) {
- // Test exclusion criteria first
- if (type.getMatchingUniques(UniqueType.RegionRequireFirstLessThanSecond).any {
- region.getTerrainAmount(it.params[0]) >= region.getTerrainAmount(it.params[1]) } ) {
- continue
- }
- // Test inclusion criteria
- if (type.getMatchingUniques(UniqueType.RegionRequirePercentSingleType).any {
- region.getTerrainAmount(it.params[1]) >= (it.params[0].toInt() * region.tiles.size) / 100 }
- || type.getMatchingUniques(UniqueType.RegionRequirePercentTwoTypes).any {
- region.getTerrainAmount(it.params[1]) + region.getTerrainAmount(it.params[2]) >= (it.params[0].toInt() * region.tiles.size) / 100 }
- ) {
- region.type = type.name
- break
- }
- }
- }
-
- // Generate tile data for all tiles
- for (tile in tileMap.values) {
- val newData = MapGenTileData(tile, regions.firstOrNull { it.tiles.contains(tile) })
- newData.evaluate(ruleset)
- tileData[tile.position] = newData
- }
-
- // Sort regions by fertility so the worse regions get to pick first
- val sortedRegions = regions.sortedBy { it.totalFertility }
- // Find a start for each region
- for (region in sortedRegions) {
- findStart(region)
- }
- // Normalize starts
- for (region in regions) {
- normalizeStart(tileMap[region.startPosition!!], tileMap, minorCiv = false)
- }
-
- val civBiases = civilizations.associateWith { ruleset.nations[it.civName]!!.startBias }
- // This ensures each civ can only be in one of the buckets
- val civsByBiasType = civBiases.entries.groupBy(
- keySelector = {
- (_, startBias) ->
- when {
- gameParameters.noStartBias -> BiasTypes.Random
- startBias.any { bias -> bias.equalsPlaceholderText("Avoid []") } -> BiasTypes.Negative
- "Coast" in startBias -> BiasTypes.Coastal
- startBias.isNotEmpty() -> BiasTypes.Positive
- else -> BiasTypes.Random
- }
- },
- valueTransform = { (civ, _) -> civ }
- )
-
- val coastBiasCivs = civsByBiasType[BiasTypes.Coastal]
- ?: emptyList()
- val positiveBiasCivs = civsByBiasType[BiasTypes.Positive]
- ?.sortedBy { civBiases[it]?.size } // civs with only one desired region go first
- ?: emptyList()
- val negativeBiasCivs = civsByBiasType[BiasTypes.Negative]
- ?.sortedByDescending { civBiases[it]?.size } // Civs with more complex avoids go first
- ?: emptyList()
- val randomCivs = civsByBiasType[BiasTypes.Random]
- ?.toMutableList() // We might fill this up as we go
- ?: mutableListOf()
- val positiveBiasFallbackCivs = mutableListOf() // Civs who couldn't get their desired region at first pass
- val unpickedRegions = regions.toMutableList()
-
- // First assign coast bias civs
- for (civ in coastBiasCivs) {
- // Try to find a coastal start, preferably a really coastal one
- var startRegion = unpickedRegions.filter { tileMap[it.startPosition!!].isCoastalTile() }
- .maxByOrNull { it.terrainCounts["Coastal"] ?: 0 }
- if (startRegion != null) {
- logAssignRegion(true, BiasTypes.Coastal, civ, startRegion)
- assignCivToRegion(civ, startRegion)
- unpickedRegions.remove(startRegion)
- continue
- }
- // Else adjacent to a lake
- startRegion = unpickedRegions.filter { tileMap[it.startPosition!!].neighbors.any { neighbor -> neighbor.getBaseTerrain().hasUnique(UniqueType.FreshWater) } }
- .maxByOrNull { it.terrainCounts["Coastal"] ?: 0 }
- if (startRegion != null) {
- logAssignRegion(true, BiasTypes.Coastal, civ, startRegion)
- assignCivToRegion(civ, startRegion)
- unpickedRegions.remove(startRegion)
- continue
- }
- // Else adjacent to a river
- startRegion = unpickedRegions.filter { tileMap[it.startPosition!!].isAdjacentToRiver() }
- .maxByOrNull { it.terrainCounts["Coastal"] ?: 0 }
- if (startRegion != null) {
- logAssignRegion(true, BiasTypes.Coastal, civ, startRegion)
- assignCivToRegion(civ, startRegion)
- unpickedRegions.remove(startRegion)
- continue
- }
- // Else at least close to a river ????
- startRegion = unpickedRegions.filter { tileMap[it.startPosition!!].neighbors.any { neighbor -> neighbor.isAdjacentToRiver() } }
- .maxByOrNull { it.terrainCounts["Coastal"] ?: 0 }
- if (startRegion != null) {
- logAssignRegion(true, BiasTypes.Coastal, civ, startRegion)
- assignCivToRegion(civ, startRegion)
- unpickedRegions.remove(startRegion)
- continue
- }
- // Else pick a random region at the end
- logAssignRegion(false, BiasTypes.Coastal, civ)
- randomCivs.add(civ)
- }
-
- // Next do positive bias civs
- for (civ in positiveBiasCivs) {
- // Try to find a start that matches any of the desired regions, ideally with lots of desired terrain
- val preferred = civBiases[civ]!!
- val startRegion = unpickedRegions.filter { it.type in preferred }
- .maxByOrNull { it.terrainCounts.filterKeys { terrain -> terrain in preferred }.values.sum() }
- if (startRegion != null) {
- logAssignRegion(true, BiasTypes.Positive, civ, startRegion)
- assignCivToRegion(civ, startRegion)
- unpickedRegions.remove(startRegion)
- continue
- } else if (preferred.size == 1) { // Civs with a single bias (only) get to look for a fallback region
- positiveBiasFallbackCivs.add(civ)
- } else { // Others get random starts
- logAssignRegion(false, BiasTypes.Positive, civ)
- randomCivs.add(civ)
- }
- }
-
- // Do a second pass for fallback civs, choosing the region most similar to the desired type
- for (civ in positiveBiasFallbackCivs) {
- val startRegion = getFallbackRegion(civBiases[civ]!!.first(), unpickedRegions)
- logAssignRegion(true, BiasTypes.PositiveFallback, civ, startRegion)
- assignCivToRegion(civ, startRegion)
- unpickedRegions.remove(startRegion)
- }
-
- // Next do negative bias ones (ie "Avoid []")
- for (civ in negativeBiasCivs) {
- val (avoidBias, preferred) = civBiases[civ]!!
- .partition { bias -> bias.equalsPlaceholderText("Avoid []") }
- val avoided = avoidBias.map { it.getPlaceholderParameters()[0] }
- // Try to find a region not of the avoided types, secondary sort by
- // least number of undesired terrains (weighed double) / most number of desired terrains
- val startRegion = unpickedRegions.filterNot { it.type in avoided }
- .minByOrNull {
- 2 * it.terrainCounts.filterKeys { terrain -> terrain in avoided }.values.sum()
- - it.terrainCounts.filterKeys { terrain -> terrain in preferred }.values.sum()
- }
- if (startRegion != null) {
- logAssignRegion(true, BiasTypes.Negative, civ, startRegion)
- assignCivToRegion(civ, startRegion)
- unpickedRegions.remove(startRegion)
- continue
- } else {
- logAssignRegion(false, BiasTypes.Negative, civ)
- randomCivs.add(civ) // else pick a random region at the end
- }
- }
-
- // Finally assign the remaining civs randomly
- for (civ in randomCivs) {
- // throws if regions.size < civilizations.size or if the assigning mismatched - leads to popup on newgame screen
- val startRegion = unpickedRegions.random()
- logAssignRegion(true, BiasTypes.Random, civ, startRegion)
- assignCivToRegion(civ, startRegion)
- unpickedRegions.remove(startRegion)
- }
- }
-
- private fun logAssignRegion(success: Boolean, startBiasType: BiasTypes, civ: Civilization, region: Region? = null) {
- if (Log.backend.isRelease()) return
-
- val logCiv = { civ.civName + " " + ruleset.nations[civ.civName]!!.startBias.joinToString(",", "(", ")") }
- val msg = if (success) "(%s): %s to %s"
- else "no region (%s) found for %s"
- Log.debug(Tag("assignRegions"), msg, startBiasType, logCiv, region)
- }
-
- private fun getRegionPriority(terrain: Terrain?): Int? {
- if (terrain == null) // ie "hybrid"
- return 99999 // a big number
- return if (!terrain.hasUnique(UniqueType.RegionRequirePercentSingleType)
- && !terrain.hasUnique(UniqueType.RegionRequirePercentTwoTypes))
- null
- else
- if (terrain.hasUnique(UniqueType.RegionRequirePercentSingleType))
- terrain.getMatchingUniques(UniqueType.RegionRequirePercentSingleType).first().params[2].toInt()
- else
- terrain.getMatchingUniques(UniqueType.RegionRequirePercentTwoTypes).first().params[3].toInt()
- }
-
- private fun assignCivToRegion(civ: Civilization, region: Region) {
- val tile = region.tileMap[region.startPosition!!]
- region.tileMap.addStartingLocation(civ.civName, tile)
-
- // Place impacts to keep city states etc at appropriate distance
- placeImpact(ImpactType.MinorCiv,tile, 6)
- placeImpact(ImpactType.Luxury, tile, 3)
- placeImpact(ImpactType.Strategic,tile, 0)
- placeImpact(ImpactType.Bonus, tile, 3)
- }
-
- /** Attempts to find a good start close to the center of [region]. Calls setRegionStart with the position*/
- private fun findStart(region: Region) {
- // Establish center bias rects
- val centerRect = getCentralRectangle(region.rect, 0.33f)
- val middleRect = getCentralRectangle(region.rect, 0.67f)
-
- // Priority: 1. Adjacent to river, 2. Adjacent to coast or fresh water, 3. Other.
- // First check center rect, then middle. Only check the outer area if no good sites found
- val riverTiles = HashSet()
- val wetTiles = HashSet()
- val dryTiles = HashSet()
- val fallbackTiles = HashSet()
-
- // First check center
- val centerTiles = region.tileMap.getTilesInRectangle(centerRect)
- for (tile in centerTiles) {
- if (tileData[tile.position]!!.isTwoFromCoast)
- continue // Don't even consider tiles two from coast
- if (region.continentID != -1 && region.continentID != tile.getContinent())
- continue // Wrong continent
- if (tile.isLand && !tile.isImpassible()) {
- evaluateTileForStart(tile)
- if (tile.isAdjacentToRiver())
- riverTiles.add(tile.position)
- else if (tile.isCoastalTile() || tile.isAdjacentTo(Constants.freshWater))
- wetTiles.add(tile.position)
- else
- dryTiles.add(tile.position)
- }
- }
- // Did we find a good start position?
- for (list in sequenceOf(riverTiles, wetTiles, dryTiles)) {
- if (list.any { tileData[it]!!.isGoodStart }) {
- setRegionStart(region, list
- .filter { tileData[it]!!.isGoodStart }.maxByOrNull { tileData[it]!!.startScore }!!)
- return
- }
- if (list.isNotEmpty()) // Save the best not-good-enough spots for later fallback
- fallbackTiles.add(list.maxByOrNull { tileData[it]!!.startScore }!!)
- }
-
- // Now check middle donut
- val middleDonut = region.tileMap.getTilesInRectangle(middleRect).filterNot { it in centerTiles }
- riverTiles.clear()
- wetTiles.clear()
- dryTiles.clear()
- for (tile in middleDonut) {
- if (tileData[tile.position]!!.isTwoFromCoast)
- continue // Don't even consider tiles two from coast
- if (region.continentID != -1 && region.continentID != tile.getContinent())
- continue // Wrong continent
- if (tile.isLand && !tile.isImpassible()) {
- evaluateTileForStart(tile)
- if (tile.isAdjacentToRiver())
- riverTiles.add(tile.position)
- else if (tile.isCoastalTile() || tile.isAdjacentTo(Constants.freshWater))
- wetTiles.add(tile.position)
- else
- dryTiles.add(tile.position)
- }
- }
- // Did we find a good start position?
- for (list in sequenceOf(riverTiles, wetTiles, dryTiles)) {
- if (list.any { tileData[it]!!.isGoodStart }) {
- setRegionStart(region, list
- .filter { tileData[it]!!.isGoodStart }.maxByOrNull { tileData[it]!!.startScore }!!)
- return
- }
- if (list.isNotEmpty()) // Save the best not-good-enough spots for later fallback
- fallbackTiles.add(list.maxByOrNull { tileData[it]!!.startScore }!!)
- }
-
- // Now check the outer tiles. For these we don't care about rivers, coasts etc
- val outerDonut = region.tileMap.getTilesInRectangle(region.rect).filterNot { it in centerTiles || it in middleDonut}
- dryTiles.clear()
- for (tile in outerDonut) {
- if (region.continentID != -1 && region.continentID != tile.getContinent())
- continue // Wrong continent
- if (tile.isLand && !tile.isImpassible()) {
- evaluateTileForStart(tile)
- dryTiles.add(tile.position)
- }
- }
- // Were any of them good?
- if (dryTiles.any { tileData[it]!!.isGoodStart }) {
- // Find the one closest to the center
- val center = region.rect.getCenter(Vector2())
- setRegionStart(region,
- dryTiles.filter { tileData[it]!!.isGoodStart }.minByOrNull {
- (region.tileMap.getIfTileExistsOrNull(center.x.roundToInt(), center.y.roundToInt()) ?: region.tileMap.values.first())
- .aerialDistanceTo(
- region.tileMap.getIfTileExistsOrNull(it.x.toInt(), it.y.toInt()) ?: region.tileMap.values.first()
- ) }!!)
- return
- }
- if (dryTiles.isNotEmpty())
- fallbackTiles.add(dryTiles.maxByOrNull { tileData[it]!!.startScore }!!)
-
- // Fallback time. Just pick the one with best score
- val fallbackPosition = fallbackTiles.maxByOrNull { tileData[it]!!.startScore }
- if (fallbackPosition != null) {
- setRegionStart(region, fallbackPosition)
- return
- }
-
- // Something went extremely wrong and there is somehow no place to start. Spawn some land and start there
- val panicPosition = region.rect.getPosition(Vector2())
- val panicTerrain = ruleset.terrains.values.first { it.type == TerrainType.Land }.name
- region.tileMap[panicPosition].baseTerrain = panicTerrain
- region.tileMap[panicPosition].setTerrainFeatures(listOf())
- setRegionStart(region, panicPosition)
- }
-
- /** Attempts to improve the start on [startTile] as needed to make it decent.
- * Relies on startPosition having been set previously.
- * Assumes unchanged baseline values ie citizens eat 2 food each, similar production costs
- * If [minorCiv] is true, different weightings will be used. */
- private fun normalizeStart(startTile: Tile, tileMap: TileMap, minorCiv: Boolean) {
- // Remove ice-like features adjacent to start
- for (tile in startTile.neighbors) {
- val lastTerrain = tile.terrainFeatureObjects.lastOrNull { it.impassable }
- if (lastTerrain != null) {
- tile.removeTerrainFeature(lastTerrain.name)
- }
- }
-
- // evaluate production potential
- val innerProduction = startTile.neighbors.sumOf { getPotentialYield(it, Stat.Production).toInt() }
- val outerProduction = startTile.getTilesAtDistance(2).sumOf { getPotentialYield(it, Stat.Production).toInt() }
- // for very early production we ideally want tiles that also give food
- val earlyProduction = startTile.getTilesInDistanceRange(1..2).sumOf {
- if (getPotentialYield(it, Stat.Food, unimproved = true) > 0f) getPotentialYield(it, Stat.Production, unimproved = true).toInt()
- else 0 }
-
- // If terrible, try adding a hill to a dry flat tile
- if (innerProduction == 0 || (innerProduction < 2 && outerProduction < 8) || (minorCiv && innerProduction < 4)) {
- val hillSpot = startTile.neighbors
- .filter { it.isLand && it.terrainFeatures.isEmpty() && !it.isAdjacentTo(Constants.freshWater) && !it.isImpassible() }
- .toList().randomOrNull()
- val hillEquivalent = ruleset.terrains.values
- .firstOrNull { it.type == TerrainType.TerrainFeature && it.production >= 2 && !it.hasUnique(UniqueType.RareFeature) }?.name
- if (hillSpot != null && hillEquivalent != null) {
- hillSpot.addTerrainFeature(hillEquivalent)
- }
- }
-
- // Place Strategic Balance Resources
- if (tileMap.mapParameters.mapResources == MapResources.strategicBalance) {
- val candidateTiles = startTile.getTilesInDistanceRange(1..2).shuffled() + startTile.getTilesAtDistance(3).shuffled()
- for (resource in ruleset.tileResources.values.filter { it.hasUnique(UniqueType.StrategicBalanceResource) }) {
- if (tryAddingResourceToTiles(resource, 1, candidateTiles, majorDeposit = true) == 0) {
- // Fallback mode - force placement, even on an otherwise inappropriate terrain. Do still respect water and impassible tiles!
- if (isWaterOnlyResource(resource))
- placeResourcesInTiles(999, candidateTiles.filter { it.isWater && !it.isImpassible() }.toList(), listOf(resource), majorDeposit = true, forcePlacement = true)
- else
- placeResourcesInTiles(999, candidateTiles.filter { it.isLand && !it.isImpassible() }.toList(), listOf(resource), majorDeposit = true, forcePlacement = true)
-
- }
- }
- }
-
- // If bad early production, add a small strategic resource to SECOND ring (not for minors)
- if (!minorCiv && innerProduction < 3 && earlyProduction < 6) {
- val lastEraNumber = ruleset.eras.values.maxOf { it.eraNumber }
- val earlyEras = ruleset.eras.filterValues { it.eraNumber <= lastEraNumber / 3 }
- val validResources = ruleset.tileResources.values.filter {
- it.resourceType == ResourceType.Strategic &&
- (it.revealedBy == null ||
- ruleset.technologies[it.revealedBy]!!.era() in earlyEras)
- }.shuffled()
- val candidateTiles = startTile.getTilesAtDistance(2).shuffled()
- for (resource in validResources) {
- if (tryAddingResourceToTiles(resource, 1, candidateTiles, majorDeposit = false) > 0)
- break
- }
- }
-
- // Now evaluate food situation
- // Food²/4 because excess food is really good and lets us work other tiles or run specialists!
- // 2F is worth 1, 3F is worth 2, 4F is worth 4, 5F is worth 6 and so on
- val innerFood = startTile.neighbors.sumOf { (getPotentialYield(it, Stat.Food).pow(2) / 4).toInt() }
- val outerFood = startTile.getTilesAtDistance(2).sumOf { (getPotentialYield(it, Stat.Food).pow(2) / 4).toInt() }
- val totalFood = innerFood + outerFood
- // we want at least some two-food tiles to keep growing
- val innerNativeTwoFood = startTile.neighbors.count { getPotentialYield(it, Stat.Food, unimproved = true) >= 2f }
- val outerNativeTwoFood = startTile.getTilesAtDistance(2).count { getPotentialYield(it, Stat.Food, unimproved = true) >= 2f }
- val totalNativeTwoFood = innerNativeTwoFood + outerNativeTwoFood
-
- // Determine number of needed bonuses. Different weightings for minor and major civs.
- var bonusesNeeded = if (minorCiv) {
- when { // From 2 to 0
- totalFood < 12 || innerFood < 4 -> 2
- totalFood < 16 || innerFood < 9 -> 1
- else -> 0
- }
- } else {
- when { // From 5 to 0
- innerFood == 0 && totalFood < 4 -> 5
- totalFood < 6 -> 4
- totalFood < 8 ||
- (totalFood < 12 && innerFood < 5) -> 3
- (totalFood < 17 && innerFood < 9) ||
- totalNativeTwoFood < 2 -> 2
- (totalFood < 24 && innerFood < 11) ||
- totalNativeTwoFood == 2 ||
- innerNativeTwoFood == 0 ||
- totalFood < 20 -> 1
- else -> 0
- }
- }
- if (tileMap.mapParameters.mapResources == MapResources.legendaryStart)
- bonusesNeeded += 2
-
- // Attempt to place one grassland at a plains-only spot (nor for minors)
- if (!minorCiv && bonusesNeeded < 3 && totalNativeTwoFood == 0) {
- val twoFoodTerrain = ruleset.terrains.values.firstOrNull { it.type == TerrainType.Land && it.food >= 2 }?.name
- val candidateInnerSpots = startTile.neighbors
- .filter { it.isLand && !it.isImpassible() && it.terrainFeatures.isEmpty() && it.resource == null }
- val candidateOuterSpots = startTile.getTilesAtDistance(2)
- .filter { it.isLand && !it.isImpassible() && it.terrainFeatures.isEmpty() && it.resource == null }
- val spot = candidateInnerSpots.shuffled().firstOrNull() ?: candidateOuterSpots.shuffled().firstOrNull()
- if (twoFoodTerrain != null && spot != null) {
- spot.baseTerrain = twoFoodTerrain
- } else
- bonusesNeeded = 3 // Irredeemable plains situation
- }
-
- val oasisEquivalent = ruleset.terrains.values.firstOrNull {
- it.type == TerrainType.TerrainFeature &&
- it.hasUnique(UniqueType.RareFeature) &&
- it.food >= 2 &&
- it.food + it.production + it.gold >= 3 &&
- it.occursOn.any { base -> ruleset.terrains[base]!!.type == TerrainType.Land }
- }
- var canPlaceOasis = oasisEquivalent != null // One oasis per start is enough. Don't bother finding a place if there is no good oasis equivalent
- var placedInFirst = 0 // Attempt to put first 2 in inner ring and next 3 in second ring
- var placedInSecond = 0
- val rangeForBonuses = if (minorCiv) 2 else 3
-
- // Start with list of candidate plots sorted in ring order 1,2,3
- val candidatePlots = startTile.getTilesInDistanceRange(1..rangeForBonuses)
- .filter { it.resource == null && oasisEquivalent !in it.terrainFeatureObjects }
- .shuffled().sortedBy { it.aerialDistanceTo(startTile) }.toMutableList()
-
- // Place food bonuses (and oases) as able
- while (bonusesNeeded > 0 && candidatePlots.isNotEmpty()) {
- val plot = candidatePlots.first()
- candidatePlots.remove(plot) // remove the plot as it has now been tried, whether successfully or not
- if (plot.getBaseTerrain().hasUnique(UniqueType.BlocksResources, StateForConditionals(attackedTile = plot)))
- continue // Don't put bonuses on snow hills
-
- val validBonuses = ruleset.tileResources.values.filter {
- it.resourceType == ResourceType.Bonus &&
- it.food >= 1 &&
- plot.lastTerrain.name in it.terrainsCanBeFoundOn
- }
- val goodPlotForOasis = canPlaceOasis && plot.lastTerrain.name in oasisEquivalent!!.occursOn
-
- if (validBonuses.isNotEmpty() || goodPlotForOasis) {
- if (goodPlotForOasis) {
- plot.addTerrainFeature(oasisEquivalent!!.name)
- canPlaceOasis = false
- } else {
- plot.setTileResource(validBonuses.random())
- }
-
- if (plot.aerialDistanceTo(startTile) == 1) {
- placedInFirst++
- if (placedInFirst == 2) // Resort the list in ring order 2,3,1
- candidatePlots.sortBy { abs(it.aerialDistanceTo(startTile) * 10 - 22 ) }
- } else if (plot.aerialDistanceTo(startTile) == 2) {
- placedInSecond++
- if (placedInSecond == 3) // Resort the list in ring order 3,1,2
- candidatePlots.sortByDescending { abs(it.aerialDistanceTo(startTile) * 10 - 17) }
- }
- bonusesNeeded--
- }
- }
-
- // Minor civs are done, go on with grassiness checks for major civs
- if (minorCiv) return
-
- // Check for very grass-heavy starts that might still need some stone to help with production
- val grassTypePlots = startTile.getTilesInDistanceRange(1..2).filter {
- it.isLand &&
- getPotentialYield(it, Stat.Food, unimproved = true) >= 2f && // Food neutral natively
- getPotentialYield(it, Stat.Production) == 0f // Production can't even be improved
- }.toMutableList()
- val plainsTypePlots = startTile.getTilesInDistanceRange(1..2).filter {
- it.isLand &&
- getPotentialYield(it, Stat.Food) >= 2f && // Something that can be improved to food neutral
- getPotentialYield(it, Stat.Production, unimproved = true) >= 1f // Some production natively
- }.toList()
- var stoneNeeded = when {
- grassTypePlots.size >= 9 && plainsTypePlots.isEmpty() -> 2
- grassTypePlots.size >= 6 && plainsTypePlots.size <= 4 -> 1
- else -> 0
- }
- val stoneTypeBonuses = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Bonus && it.production > 0 }
-
- if(stoneTypeBonuses.isNotEmpty()) {
- while (stoneNeeded > 0 && grassTypePlots.isNotEmpty()) {
- val plot = grassTypePlots.random()
- grassTypePlots.remove(plot)
-
- if (plot.resource != null) continue
-
- val bonusToPlace = stoneTypeBonuses.filter { plot.lastTerrain.name in it.terrainsCanBeFoundOn }.randomOrNull()
- if (bonusToPlace != null) {
- plot.resource = bonusToPlace.name
- stoneNeeded--
- }
- }
- }
- }
-
- private fun getPotentialYield(tile: Tile, stat: Stat, unimproved: Boolean = false): Float {
- val baseYield = tile.stats.getTileStats(null)[stat]
- if (unimproved) return baseYield
-
- val bestImprovementYield = tile.tileMap.ruleset!!.tileImprovements.values
- .filter { !it.hasUnique(UniqueType.GreatImprovement) &&
- it.uniqueTo == null &&
- tile.lastTerrain.name in it.terrainsCanBeBuiltOn }
- .maxOfOrNull { it[stat] }
- return baseYield + (bestImprovementYield ?: 0f)
- }
-
- /** @returns the region most similar to a region of [type] */
- private fun getFallbackRegion(type: String, candidates: List): Region {
- return candidates.maxByOrNull { it.terrainCounts[type] ?: 0 }!!
- }
-
- private fun setRegionStart(region: Region, position: Vector2) {
- region.startPosition = position
- setCloseStartPenalty(region.tileMap[position])
- }
-
- /** @returns a scaled according to [proportion] Rectangle centered over [originalRect] */
- private fun getCentralRectangle(originalRect: Rectangle, proportion: Float): Rectangle {
- val scaledRect = Rectangle(originalRect)
-
- scaledRect.width = (originalRect.width * proportion)
- scaledRect.height = (originalRect.height * proportion)
- scaledRect.x = originalRect.x + (originalRect.width - scaledRect.width) / 2
- scaledRect.y = originalRect.y + (originalRect.height - scaledRect.height) / 2
-
- // round values
- scaledRect.x = scaledRect.x.roundToInt().toFloat()
- scaledRect.y = scaledRect.y.roundToInt().toFloat()
- scaledRect.width = scaledRect.width.roundToInt().toFloat()
- scaledRect.height = scaledRect.height.roundToInt().toFloat()
-
- return scaledRect
- }
-
- private fun setCloseStartPenalty(tile: Tile) {
- for ((ring, penalty) in closeStartPenaltyForRing) {
- for (outerTile in tile.getTilesAtDistance(ring).map { it.position })
- tileData[outerTile]!!.addCloseStartPenalty(penalty)
- }
- }
-
- /** Evaluates a tile for starting position, setting isGoodStart and startScore in
- * MapGenTileData. Assumes that all tiles have corresponding MapGenTileData. */
- private fun evaluateTileForStart(tile: Tile) {
- val localData = tileData[tile.position]!!
-
- var totalFood = 0
- var totalProd = 0
- var totalGood = 0
- var totalJunk = 0
- var totalRivers = 0
- var totalScore = 0
-
- if (tile.isCoastalTile()) totalScore += 40
-
- // Go through all rings
- for (ring in 1..3) {
- // Sum up the values for this ring
- for (outerTile in tile.getTilesAtDistance(ring)) {
- val outerTileData = tileData[outerTile.position]!!
- if (outerTileData.isJunk)
- totalJunk++
- else {
- if (outerTileData.isFood) totalFood++
- if (outerTileData.isProd) totalProd++
- if (outerTileData.isGood) totalGood++
- if (outerTile.isAdjacentToRiver()) totalRivers++
- }
- }
- // Check for minimum levels. We still keep on calculating final score in case of failure
- if (totalFood < minimumFoodForRing[ring]!!
- || totalProd < minimumProdForRing[ring]!!
- || totalGood < minimumGoodForRing[ring]!!) {
- localData.isGoodStart = false
- }
-
- // Ring-specific scoring
- when (ring) {
- 1 -> {
- val foodScore = firstRingFoodScores[totalFood]
- val prodScore = firstRingProdScores[totalProd]
- totalScore += foodScore + prodScore + totalRivers
- + (totalGood * 2) - (totalJunk * 3)
- }
- 2 -> {
- val foodScore = if (totalFood > 10) secondRingFoodScores.last()
- else secondRingFoodScores[totalFood]
- val effectiveTotalProd = if (totalProd >= totalFood * 2) totalProd
- else (totalFood + 1) / 2 // Can't use all that production without food
- val prodScore = if (effectiveTotalProd > 5) secondRingProdScores.last()
- else secondRingProdScores[effectiveTotalProd]
- totalScore += foodScore + prodScore + totalRivers
- + (totalGood * 2) - (totalJunk * 3)
- }
- else -> {
- totalScore += totalFood + totalProd + totalGood + totalRivers - (totalJunk * 2)
- }
- }
- }
- // Too much junk?
- if (totalJunk > maximumJunk) {
- localData.isGoodStart = false
- }
-
- // Finally check if this is near another start
- if (localData.closeStartPenalty > 0) {
- localData.isGoodStart = false
- totalScore -= (totalScore * localData.closeStartPenalty) / 100
- }
- localData.startScore = totalScore
- }
-
- fun placeResourcesAndMinorCivs(tileMap: TileMap, minorCivs: List) {
- placeNaturalWonderImpacts(tileMap)
- assignLuxuries()
- placeMinorCivs(tileMap, minorCivs)
- placeLuxuries(tileMap)
- placeStrategicAndBonuses(tileMap)
- }
-
- /** Places impacts from NWs that have been generated just prior to this step. */
- private fun placeNaturalWonderImpacts(tileMap: TileMap) {
- for (tile in tileMap.values.filter { it.isNaturalWonder() }) {
- placeImpact(ImpactType.Bonus, tile, 1)
- placeImpact(ImpactType.Strategic, tile, 1)
- placeImpact(ImpactType.Luxury, tile, 1)
- placeImpact(ImpactType.MinorCiv, tile, 1)
- }
- }
-
- /** Assigns a luxury to each region. No luxury can be assigned to too many regions.
- * Some luxuries are earmarked for city states. The rest are randomly distributed or
- * don't occur att all in the map */
- private fun assignLuxuries() {
- // If there are any weightings defined in json, assume they are complete. If there are none, use flat weightings instead
- val fallbackWeightings = ruleset.tileResources.values.none {
- it.resourceType == ResourceType.Luxury &&
- (it.uniqueObjects.any { unique -> unique.isOfType(UniqueType.ResourceWeighting) } || it.hasUnique(UniqueType.LuxuryWeightingForCityStates)) }
-
- val maxRegionsWithLuxury = when {
- regions.size > 12 -> 3
- regions.size > 8 -> 2
- else -> 1
- }
- val targetCityStateLuxuries = 3 // was probably intended to be "if (tileData.size > 5000) 4 else 3"
- val disabledPercent = 100 - min(tileData.size.toFloat().pow(0.2f) * 16, 100f).toInt() // Approximately
- val targetDisabledLuxuries = (ruleset.tileResources.values
- .count { it.resourceType == ResourceType.Luxury } * disabledPercent) / 100
- val assignableLuxuries = ruleset.tileResources.values.filter {
- it.resourceType == ResourceType.Luxury &&
- !it.hasUnique(UniqueType.LuxurySpecialPlacement) &&
- !it.hasUnique(UniqueType.CityStateOnlyResource) }
- val amountRegionsWithLuxury = HashMap()
- // Init map
- ruleset.tileResources.values
- .forEach { amountRegionsWithLuxury[it.name] = 0 }
-
- for (region in regions.sortedBy { getRegionPriority(ruleset.terrains[it.type]) } ) {
- val regionConditional = StateForConditionals(region = region)
- var candidateLuxuries = assignableLuxuries.filter {
- amountRegionsWithLuxury[it.name]!! < maxRegionsWithLuxury &&
- // Check that it has a weight for this region type
- (fallbackWeightings ||
- it.hasUnique(UniqueType.ResourceWeighting, regionConditional)) &&
- // Check that there is enough coast if it is a water based resource
- ((region.terrainCounts["Coastal"] ?: 0) >= 12 ||
- it.terrainsCanBeFoundOn.any { terrain -> ruleset.terrains[terrain]!!.type != TerrainType.Water } )
- }
-
- // If we couldn't find any options, pick from all luxuries. First try to not pick water luxuries on land regions
- if (candidateLuxuries.isEmpty()) {
- candidateLuxuries = assignableLuxuries.filter {
- amountRegionsWithLuxury[it.name]!! < maxRegionsWithLuxury &&
- // Ignore weightings for this pass
- // Check that there is enough coast if it is a water based resource
- ((region.terrainCounts["Coastal"] ?: 0) >= 12 ||
- it.terrainsCanBeFoundOn.any { terrain -> ruleset.terrains[terrain]!!.type != TerrainType.Water })
- }
- }
- // If there are still no candidates, ignore water restrictions
- if (candidateLuxuries.isEmpty()) {
- candidateLuxuries = assignableLuxuries.filter {
- amountRegionsWithLuxury[it.name]!! < maxRegionsWithLuxury
- // Ignore weightings and water for this pass
- }
- }
- // If there are still no candidates (mad modders???) just skip this region
- if (candidateLuxuries.isEmpty()) continue
-
- // Pick a luxury at random. Weight is reduced if the luxury has been picked before
- val modifiedWeights = candidateLuxuries.map {
- val weightingUnique = it.getMatchingUniques(UniqueType.ResourceWeighting, regionConditional).firstOrNull()
- val relativeWeight = if (weightingUnique == null) 1f else weightingUnique.params[0].toFloat()
- relativeWeight / (1f + amountRegionsWithLuxury[it.name]!!)
- }.shuffled()
- region.luxury = candidateLuxuries.randomWeighted(modifiedWeights).name
- amountRegionsWithLuxury[region.luxury!!] = amountRegionsWithLuxury[region.luxury]!! + 1
- }
-
- // Assign luxuries to City States
- repeat(targetCityStateLuxuries) {
- val candidateLuxuries = assignableLuxuries.filter {
- amountRegionsWithLuxury[it.name] == 0 &&
- (fallbackWeightings || it.hasUnique(UniqueType.LuxuryWeightingForCityStates))
- }
- if (candidateLuxuries.isEmpty()) return@repeat
-
- val weights = candidateLuxuries.map {
- val weightingUnique = it.getMatchingUniques(UniqueType.LuxuryWeightingForCityStates).firstOrNull()
- if (weightingUnique == null)
- 1f
- else
- weightingUnique.params[0].toFloat()
- }
- val luxury = candidateLuxuries.randomWeighted(weights).name
- cityStateLuxuries.add(luxury)
- amountRegionsWithLuxury[luxury] = 1
- }
-
- // Assign some resources as random placement.
- val remainingLuxuries = assignableLuxuries.filter {
- amountRegionsWithLuxury[it.name] == 0
- }.map { it.name }.shuffled()
- randomLuxuries.addAll(remainingLuxuries.drop(targetDisabledLuxuries))
- }
-
- /** Assigns [civs] to regions or "uninhabited" land and places them. Depends on
- * assignLuxuries having been called previously.
- * Note: can silently fail to place all city states if there is too little room.
- * Currently our GameStarter fills out with random city states, Civ V behavior is to
- * forget about the discarded city states entirely. */
- private fun placeMinorCivs(tileMap: TileMap, civs: List) {
- if (civs.isEmpty()) return
-
- // Some but not all city states are assigned to regions directly. Determine the CS density.
- val minorCivRatio = civs.size.toFloat() / regions.size
- val minorCivPerRegion = when {
- minorCivRatio > 14f -> 10 // lol
- minorCivRatio > 11f -> 8
- minorCivRatio > 8f -> 6
- minorCivRatio > 5.7f -> 4
- minorCivRatio > 4.35f -> 3
- minorCivRatio > 2.7f -> 2
- minorCivRatio > 1.35f -> 1
- else -> 0
- }
- val unassignedCivs = civs.shuffled().toMutableList()
- if (minorCivPerRegion > 0) {
- regions.forEach {
- val civsToAssign = unassignedCivs.take(minorCivPerRegion)
- it.assignedMinorCivs.addAll(civsToAssign)
- unassignedCivs.removeAll(civsToAssign)
- }
- }
- // Some city states are assigned to "uninhabited" continents - unless it's an archipelago type map
- // (Because then every continent will have been assigned to a region anyway)
- val uninhabitedCoastal = ArrayList()
- val uninhabitedHinterland = ArrayList()
- val uninhabitedContinents = tileMap.continentSizes.filter {
- it.value >= 4 && // Don't bother with tiny islands
- regions.none { region -> region.continentID == it.key }
- }.keys
- val civAssignedToUninhabited = ArrayList()
- var numUninhabitedTiles = 0
- var numInhabitedTiles = 0
- if (!usingArchipelagoRegions) {
- // Go through the entire map to build the data
- for (tile in tileMap.values) {
- if (!canPlaceMinorCiv(tile)) continue
- val continent = tile.getContinent()
- if (continent in uninhabitedContinents) {
- if (tile.isCoastalTile())
- uninhabitedCoastal.add(tile)
- else
- uninhabitedHinterland.add(tile)
- numUninhabitedTiles++
- } else
- numInhabitedTiles++
- }
- // Determine how many minor civs to put on uninhabited continents.
- val maxByUninhabited = (3 * civs.size * numUninhabitedTiles) / (numInhabitedTiles + numUninhabitedTiles)
- val maxByRatio = (civs.size + 1) / 2
- val targetForUninhabited = min(maxByRatio, maxByUninhabited)
- val civsToAssign = unassignedCivs.take(targetForUninhabited)
- unassignedCivs.removeAll(civsToAssign)
- civAssignedToUninhabited.addAll(civsToAssign)
- }
-
- // If there are still unassigned minor civs, assign extra ones to regions that share their
- // luxury type with two others, as compensation. Because starting close to a city state is good??
- if (unassignedCivs.isNotEmpty()) {
- val regionsWithCommonLuxuries = regions.filter {
- regions.count { other -> other.luxury == it.luxury } >= 3
- }
- // assign one civ each to regions with common luxuries if there are enough to go around
- if (regionsWithCommonLuxuries.isNotEmpty() &&
- regionsWithCommonLuxuries.size <= unassignedCivs.size
- ) {
- regionsWithCommonLuxuries.forEach {
- val civToAssign = unassignedCivs.first()
- unassignedCivs.remove(civToAssign)
- it.assignedMinorCivs.add(civToAssign)
- }
- }
- }
- // Still unassigned civs??
- if (unassignedCivs.isNotEmpty()) {
- // Add one extra to each region as long as there are enough to go around
- while (unassignedCivs.size >= regions.size) {
- regions.forEach {
- val civToAssign = unassignedCivs.first()
- unassignedCivs.remove(civToAssign)
- it.assignedMinorCivs.add(civToAssign)
- }
- }
- }
-
- // STILL unassigned civs??
- if (unassignedCivs.isNotEmpty()) {
- // At this point there is at least for sure less remaining city states than regions
- // Sort regions by fertility and put extra city states in the worst ones.
- val worstRegions = regions.sortedBy { it.totalFertility }.take(unassignedCivs.size)
- worstRegions.forEach {
- val civToAssign = unassignedCivs.first()
- unassignedCivs.remove(civToAssign)
- it.assignedMinorCivs.add(civToAssign)
- }
- }
-
- // All minor civs are assigned - now place them
- // First place the "uninhabited continent" ones, preferring coastal starts
- tryPlaceMinorCivsInTiles(civAssignedToUninhabited, tileMap, uninhabitedCoastal)
- tryPlaceMinorCivsInTiles(civAssignedToUninhabited, tileMap, uninhabitedHinterland)
- // Fallback to a random region for civs that couldn't be placed in the wilderness
- for (unplacedCiv in civAssignedToUninhabited) {
- regions.random().assignedMinorCivs.add(unplacedCiv)
- }
-
- // Now place the ones assigned to specific regions.
- for (region in regions) {
- tryPlaceMinorCivsInTiles(region.assignedMinorCivs, tileMap, region.tiles.toMutableList())
- }
- }
-
- /** Attempts to randomly place civs from [civsToPlace] in tiles from [tileList]. Assumes that
- * [tileList] is pre-vetted and only contains habitable land tiles.
- * Will modify both [civsToPlace] and [tileList] as it goes! */
- private fun tryPlaceMinorCivsInTiles(civsToPlace: MutableList, tileMap: TileMap, tileList: MutableList) {
- while (tileList.isNotEmpty() && civsToPlace.isNotEmpty()) {
- val chosenTile = tileList.random()
- tileList.remove(chosenTile)
- val data = tileData[chosenTile.position]!!
- // If the randomly chosen tile is too close to a player or a city state, discard it
- if (data.impacts.containsKey(ImpactType.MinorCiv))
- continue
- // Otherwise, go ahead and place the minor civ
- val civToAdd = civsToPlace.first()
- civsToPlace.remove(civToAdd)
- placeMinorCiv(civToAdd, tileMap, chosenTile)
- }
- }
-
- private fun canPlaceMinorCiv(tile: Tile) = !tile.isWater && !tile.isImpassible() &&
- !tileData[tile.position]!!.isJunk &&
- tile.getBaseTerrain().getMatchingUniques(UniqueType.HasQuality).none { it.params[0] == "Undesirable" } && // So we don't get snow hills
- tile.neighbors.count() == 6 // Avoid map edges
-
- private fun placeMinorCiv(civ: Civilization, tileMap: TileMap, tile: Tile) {
- tileMap.addStartingLocation(civ.civName, tile)
- placeImpact(ImpactType.MinorCiv,tile, 4)
- placeImpact(ImpactType.Luxury, tile, 3)
- placeImpact(ImpactType.Strategic,tile, 0)
- placeImpact(ImpactType.Bonus, tile, 3)
-
- normalizeStart(tile, tileMap, minorCiv = true)
- }
-
- /** Places all Luxuries onto [tileMap]. Assumes that assignLuxuries and placeMinorCivs have been called. */
- private fun placeLuxuries(tileMap: TileMap) {
- // First place luxuries at major civ start locations
- val averageFertilityDensity = regions.sumOf { it.totalFertility } / regions.sumOf { it.tiles.size }.toFloat()
- for (region in regions) {
- var targetLuxuries = 1
- if (tileMap.mapParameters.mapResources == MapResources.legendaryStart)
- targetLuxuries++
- if (region.totalFertility / region.tiles.size.toFloat() < averageFertilityDensity) {
- targetLuxuries++
- }
-
- val luxuryToPlace = ruleset.tileResources[region.luxury] ?: continue
- // First check 2 inner rings
- val firstPass = tileMap[region.startPosition!!].getTilesInDistanceRange(1..2)
- .shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first
- targetLuxuries -= tryAddingResourceToTiles(luxuryToPlace, targetLuxuries, firstPass, 0.5f) // Skip every 2nd tile on first pass
-
- if (targetLuxuries > 0) {
- val secondPass = firstPass + tileMap[region.startPosition!!].getTilesAtDistance(3)
- .shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first
- targetLuxuries -= tryAddingResourceToTiles(luxuryToPlace, targetLuxuries, secondPass)
- }
- if (targetLuxuries > 0) {
- // Try adding in 1 luxury from the random rotation as compensation
- for (luxury in randomLuxuries) {
- if (tryAddingResourceToTiles(ruleset.tileResources[luxury]!!, 1, firstPass) > 0) break
- }
- }
- }
- // Second place one (1) luxury at minor civ start locations
- // Check only ones that got a start location
- for (startLocation in tileMap.startingLocationsByNation
- .filterKeys { ruleset.nations[it]!!.isCityState }.map { it.value.first() }) {
- val region = regions.firstOrNull { startLocation in it.tiles }
- val tilesToCheck = startLocation.getTilesInDistanceRange(1..2)
- // 75% probability that we first attempt to place a "city state" luxury, then a random or regional one
- // 25% probability of going the other way around
- val globalLuxuries = if (region?.luxury != null) randomLuxuries + listOf(region.luxury) else randomLuxuries
- val candidateLuxuries = if (Random.nextInt(100) >= 25)
- cityStateLuxuries.shuffled() + globalLuxuries.shuffled()
- else
- globalLuxuries.shuffled() + cityStateLuxuries.shuffled()
- // Now try adding one until we are successful
- for (luxury in candidateLuxuries) {
- if (tryAddingResourceToTiles(ruleset.tileResources[luxury]!!, 1, tilesToCheck) > 0) break
- }
- }
- // Third add regional luxuries
- // The target number depends on map size and how close we are to an "ideal number" of civs for the map
- val idealCivs = max(2, tileData.size / 500)
- var regionTargetNumber = (tileData.size / 600) - (0.3f * abs(regions.size - idealCivs)).toInt()
- regionTargetNumber += when (tileMap.mapParameters.mapResources) {
- MapResources.abundant -> 1
- MapResources.sparse -> -1
- else -> 0
- }
- regionTargetNumber = max(1, regionTargetNumber)
- for (region in regions) {
- val resource = ruleset.tileResources[region.luxury] ?: continue
- fun Tile.isShoreOfContinent(continent: Int) = isWater && neighbors.any { it.getContinent() == continent }
- val candidates = if (isWaterOnlyResource(resource))
- tileMap.getTilesInRectangle(region.rect).filter { it.isShoreOfContinent(region.continentID) }
- else region.tiles.asSequence()
- tryAddingResourceToTiles(resource, regionTargetNumber, candidates.shuffled(), 0.4f, true, 4, 2)
- }
- // Fourth add random luxuries
- if (randomLuxuries.isNotEmpty()) {
- var targetRandomLuxuries = tileData.size.toFloat().pow(0.45f).toInt() // Approximately
- targetRandomLuxuries *= when (tileMap.mapParameters.mapResources) {
- MapResources.sparse -> 80
- MapResources.abundant -> 133
- else -> 100
- }
- targetRandomLuxuries /= 100
- targetRandomLuxuries += Random.nextInt(regions.size) // Add random number based on number of civs
- val minimumRandomLuxuries = tileData.size.toFloat().pow(0.2f).toInt() // Approximately
- val worldTiles = tileMap.values.asSequence().shuffled()
- for ((index, luxury) in randomLuxuries.shuffled().withIndex()) {
- val targetForThisLuxury = if (randomLuxuries.size > 8) targetRandomLuxuries / 10
- else {
- val minimum = max(3, minimumRandomLuxuries - index)
- max(minimum, (targetRandomLuxuries * randomLuxuryRatios[randomLuxuries.size]!![index] + 0.5f).toInt())
- }
- tryAddingResourceToTiles(ruleset.tileResources[luxury]!!, targetForThisLuxury, worldTiles, 0.25f,
- true, 4, 2)
- }
- }
- val specialLuxuries = ruleset.tileResources.values.filter {
- it.resourceType == ResourceType.Luxury &&
- it.hasUnique(UniqueType.LuxurySpecialPlacement)
- }
- val placedSpecials = HashMap()
- specialLuxuries.forEach { placedSpecials[it.name] = 0 } // init map
-
- // Fifth, on resource settings other than sparse, add an extra luxury to starts
- if (tileMap.mapParameters.mapResources != MapResources.sparse) {
- for (region in regions) {
- val tilesToCheck = tileMap[region.startPosition!!].getTilesInDistanceRange(1..2)
- val candidateLuxuries = randomLuxuries.shuffled().toMutableList()
- if (tileMap.mapParameters.mapResources != MapResources.strategicBalance)
- candidateLuxuries += specialLuxuries.shuffled().map { it.name } // Include marble!
- candidateLuxuries += cityStateLuxuries.shuffled()
- candidateLuxuries += regions.mapNotNull { it.luxury }.shuffled()
- for (luxury in candidateLuxuries) {
- if (tryAddingResourceToTiles(ruleset.tileResources[luxury]!!, 1, tilesToCheck) > 0) {
- if (placedSpecials.containsKey(luxury)) // Keep track of marble-type specials as they may be placed now.
- placedSpecials[luxury] = placedSpecials[luxury]!! + 1
- break
- }
- }
- }
- }
- // Sixth, top up marble-type specials if needed
- for (special in specialLuxuries) {
- val targetNumber = when (tileMap.mapParameters.mapResources) {
- MapResources.sparse -> (regions.size * 0.5f).toInt()
- MapResources.abundant -> (regions.size * 0.9f).toInt()
- else -> (regions.size * 0.75f).toInt()
- }
- val numberToPlace = max(2, targetNumber - placedSpecials[special.name]!!)
- tryAddingResourceToTiles(special, numberToPlace, tileMap.values.asSequence().shuffled(), 1f,
- true, 6, 0)
- }
- }
-
- private fun placeStrategicAndBonuses(tileMap: TileMap) {
- val strategicResources = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Strategic }
- // As usual, if there are any relevant json definitions, assume they are complete
- val fallbackStrategic = ruleset.tileResources.values.none {
- it.resourceType == ResourceType.Strategic &&
- it.uniqueObjects.any { unique -> unique.isOfType(UniqueType.ResourceWeighting) } ||
- it.uniqueObjects.any { unique -> unique.isOfType(UniqueType.MinorDepositWeighting) }
- }
- /* There are a couple competing/complementary distribution systems at work here. First, major
- deposits are placed according to a frequency defined in the terrains themselves, for each
- tile that is eligible to get a major deposit, there is a weighted random choice between
- resource types.
- Minor deposits are placed by randomly picking a number of land tiles from anywhere on the
- map (so not stratified by terrain type) and assigning a weighted randomly picked resource.
- Bonuses are placed according to a frequency for a rule like "every 8 jungle hills", here
- implemented as a conditional.
-
- We need to build lists of all tiles following a given rule to place these, which is BY FAR
- the most expensive calculation in this entire class. To save some time we anonymize the
- uniques so we only have to make one list for each set of conditionals, so eg Wheat and
- Horses can share a list since they are both interested in Featureless Plains.
- We also save a list of all land tiles for minor deposit generation. */
-
- // Determines number tiles per resource
- val bonusMultiplier = when (tileMap.mapParameters.mapResources) {
- MapResources.sparse -> 1.5f
- MapResources.abundant -> 0.6667f
- else -> 1f
- }
- val landList = ArrayList() // For minor deposits
- val ruleLists = HashMap>() // For rule-based generation
-
- // Figure out which rules (sets of conditionals) need lists built
- for (resource in ruleset.tileResources.values.filter {
- it.resourceType == ResourceType.Strategic ||
- it.resourceType == ResourceType.Bonus }) {
- for (rule in resource.uniqueObjects.filter { unique ->
- unique.isOfType(UniqueType.ResourceFrequency) ||
- unique.isOfType(UniqueType.ResourceWeighting) ||
- unique.isOfType(UniqueType.MinorDepositWeighting) }) {
- // Weed out some clearly impossible rules straight away to save time later
- if (rule.conditionals.any { conditional ->
- (conditional.isOfType(UniqueType.ConditionalOnWaterMaps) && !usingArchipelagoRegions) ||
- (conditional.isOfType(UniqueType.ConditionalInRegionOfType) && regions.none { region -> region.type == conditional.params[0] }) ||
- (conditional.isOfType(UniqueType.ConditionalInRegionExceptOfType) && regions.all { region -> region.type == conditional.params[0] })
- } )
- continue
- val simpleRule = anonymizeUnique(rule)
- if (ruleLists.keys.none { it.text == simpleRule.text }) // Need to do text comparison since the uniques will not be equal otherwise
- ruleLists[simpleRule] = ArrayList()
- }
- }
- // Make up some rules for placing strategics in a fallback situation
- if (fallbackStrategic) {
- val interestingTerrains = strategicResources.flatMap { it.terrainsCanBeFoundOn }.map { ruleset.terrains[it]!! }.toSet()
- for (terrain in interestingTerrains) {
- val fallbackRule = if (terrain.type == TerrainType.TerrainFeature)
- Unique("RULE ")
- else
- Unique("RULE ")
- if (ruleLists.keys.none { it.text == fallbackRule.text }) // Need to do text comparison since the uniques will not be equal otherwise
- ruleLists[fallbackRule] = ArrayList()
- }
- }
- // Now go through the entire map to build lists
- for (tile in tileMap.values.asSequence().shuffled()) {
- val terrainCondition = StateForConditionals(attackedTile = tile, region = regions.firstOrNull { tile in it.tiles })
- if (tile.getBaseTerrain().hasUnique(UniqueType.BlocksResources, terrainCondition))
- continue // Don't count snow hills
- if (tile.isLand)
- landList.add(tile)
- for ((rule, list) in ruleLists) {
- if (rule.conditionalsApply(terrainCondition)) {
- list.add(tile)
- }
- }
- }
- // Keep track of total placed strategic resources in case we need to top them up later
- val totalPlaced = HashMap()
- strategicResources.forEach { totalPlaced[it] = 0 }
-
- // First place major deposits on land
- for (terrain in ruleset.terrains.values.filter { it.type != TerrainType.Water }) {
- // Figure out if we generated a list for this terrain
- val list = ruleLists.filterKeys { it.text == getTerrainRule(terrain).text }.values.firstOrNull()
- ?: continue // If not the terrain can be safely skipped
- totalPlaced += placeMajorDeposits(list, terrain, fallbackStrategic, 2, 2)
- }
-
- // Second add some small deposits of modern strategic resources to city states
- val lastEra = ruleset.eras.values.maxOf { it.eraNumber }
- val modernOptions = strategicResources.filter {
- it.revealedBy != null &&
- ruleset.eras[ruleset.technologies[it.revealedBy]!!.era()]!!.eraNumber >= lastEra / 2
- }
-
- if (modernOptions.any())
- for (cityStateLocation in tileMap.startingLocationsByNation
- .filterKeys { ruleset.nations[it]!!.isCityState }.values.map { it.first() }) {
- val resourceToPlace = modernOptions.random()
- totalPlaced[resourceToPlace] =
- totalPlaced[resourceToPlace]!! + tryAddingResourceToTiles(resourceToPlace, 1, cityStateLocation.getTilesInDistanceRange(1..3))
- }
-
- // Third add some minor deposits to land tiles
- // Note: In G&K there is a bug where minor deposits are never placed on hills. We're not replicating that.
- val frequency = (baseMinorDepositFrequency * bonusMultiplier).toInt()
- val minorDepositsToAdd = (landList.size / frequency) + 1 // I sometimes have division by zero errors on this line
- var minorDepositsAdded = 0
- for (tile in landList) {
- if (tile.resource != null || tileData[tile.position]!!.impacts.containsKey(ImpactType.Strategic))
- continue
- val conditionalTerrain = StateForConditionals(attackedTile = tile)
- if (tile.getBaseTerrain().hasUnique(UniqueType.BlocksResources, conditionalTerrain))
- continue
- val weightings = strategicResources.map {
- if (fallbackStrategic) {
- if (tile.lastTerrain.name in it.terrainsCanBeFoundOn) 1f else 0f
- } else {
- val uniques = it.getMatchingUniques(UniqueType.MinorDepositWeighting, conditionalTerrain).toList()
- uniques.sumOf { unique -> unique.params[0].toInt() }.toFloat()
- }
- }
- if (weightings.sum() <= 0) {
- continue
- }
- val resourceToPlace = strategicResources.randomWeighted(weightings)
- tile.setTileResource(resourceToPlace, majorDeposit = false)
- placeImpact(ImpactType.Strategic, tile, Random.nextInt(2) + Random.nextInt(2))
- totalPlaced[resourceToPlace] = totalPlaced[resourceToPlace]!! + 1
- minorDepositsAdded++
- if (minorDepositsAdded >= minorDepositsToAdd)
- break
- }
-
- // Fourth add water-based major deposits. Extra impact because we don't want them too clustered and there is usually lots to go around
- for (terrain in ruleset.terrains.values.filter { it.type == TerrainType.Water }) {
- // Figure out if we generated a list for this terrain
- val list = ruleLists.filterKeys { it.text == getTerrainRule(terrain).text }.values.firstOrNull()
- ?: continue // If not the terrain can be safely skipped
- totalPlaced += placeMajorDeposits(list, terrain, fallbackStrategic, 4, 3)
- }
-
- // Fifth place up to 2 extra deposits of each resource type if there is < 1 per civ
- for (resource in strategicResources) {
- val extraNeeded = min(2, regions.size - totalPlaced[resource]!!)
- if (extraNeeded > 0) {
- if (isWaterOnlyResource(resource))
- tryAddingResourceToTiles(resource, extraNeeded, tileMap.values.asSequence().filter { it.isWater }.shuffled(), respectImpacts = true)
- else
- tryAddingResourceToTiles(resource, extraNeeded, landList.asSequence(), respectImpacts = true)
- }
- }
-
- // Figure out if bonus generation rates are defined in json. Assume that if there are any, the definitions are complete.
- val fallbackBonuses = ruleset.tileResources.values.none { it.uniqueObjects.any { unique -> unique.type == UniqueType.ResourceFrequency } }
-
- // Sixth place bonus resources (and other resources that might have been assigned frequency-based generation).
- // Water-based bonuses go last and have extra impact, because coasts are very common and we don't want too much clustering
- val sortedResourceList = ruleset.tileResources.values.sortedBy { isWaterOnlyResource(it) }
- for (resource in sortedResourceList) {
- val extraImpact = if (isWaterOnlyResource(resource)) 1 else 0
- for (rule in resource.uniqueObjects.filter { it.type == UniqueType.ResourceFrequency }) {
- // Figure out which list applies, if any
- val simpleRule = anonymizeUnique(rule)
- val list = ruleLists.filterKeys { it.text == simpleRule.text }.values.firstOrNull()
- // If there is no matching list, it is because the rule was determined to be impossible and so can be safely skipped
- ?: continue
- // Place the resources
- placeResourcesInTiles((rule.params[0].toFloat() * bonusMultiplier).toInt(), list, listOf(resource), 0 + extraImpact, 2 + extraImpact, false)
- }
- if(fallbackBonuses && resource.resourceType == ResourceType.Bonus) {
- // Since we haven't been able to generate any rule-based lists, just generate new ones on the fly
- // Increase impact to avoid clustering since there is no terrain type stratification.
- val fallbackList = tileMap.values.filter { it.lastTerrain.name in resource.terrainsCanBeFoundOn }.shuffled()
- placeResourcesInTiles((20 * bonusMultiplier).toInt(), fallbackList, listOf(resource), 2 + extraImpact, 2 + extraImpact, false)
- }
- }
-
- // Seventh (and finally!) place an extra bonus in the THIRD ring of each start to make it slightly more attractive
- for (region in regions) {
- val terrain = if (region.type == "Hybrid") region.terrainCounts.filterNot { it.key == "Coastal" }.maxByOrNull { it.value }!!.key
- else region.type
- val resourceUnique = ruleset.terrains[terrain]!!.getMatchingUniques(UniqueType.RegionExtraResource).firstOrNull()
- // If this region has an explicit "this is the bonus" unique go with that, else random appropriate
- val resource = if (resourceUnique != null) ruleset.tileResources[resourceUnique.params[0]]!!
- else {
- val possibleResources =
- ruleset.tileResources.values.filter { it.resourceType == ResourceType.Bonus && terrain in it.terrainsCanBeFoundOn }
- if (possibleResources.isEmpty()) continue
- possibleResources.random()
- }
- val candidateTiles = tileMap[region.startPosition!!].getTilesAtDistance(3).shuffled()
- val amount = if (resourceUnique != null) 2 else 1 // Place an extra if the region type requests it
- if (tryAddingResourceToTiles(resource, amount, candidateTiles) == 0) {
- // We couldn't place any, try adding a fish instead
- val fishyBonus = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Bonus &&
- it.terrainsCanBeFoundOn.any { terrainName -> ruleset.terrains[terrainName]!!.type == TerrainType.Water } }
- .randomOrNull()
- if (fishyBonus != null)
- tryAddingResourceToTiles(fishyBonus, 1, candidateTiles)
- }
- }
- }
-
- /** Attempts to place [amount] [resource] on [tiles], checking tiles in order. A [ratio] below 1 means skipping
- * some tiles, ie ratio = 0.25 will put a resource on every 4th eligible tile. Can optionally respect impact flags,
- * and places impact if [baseImpact] >= 0. Returns number of placed resources. */
- private fun tryAddingResourceToTiles(resource: TileResource, amount: Int, tiles: Sequence, ratio: Float = 1f,
- respectImpacts: Boolean = false, baseImpact: Int = -1, randomImpact: Int = 0,
- majorDeposit: Boolean = false): Int {
- if (amount <= 0) return 0
- var amountAdded = 0
- var ratioProgress = 1f
- val impactType = when (resource.resourceType) {
- ResourceType.Luxury -> ImpactType.Luxury
- ResourceType.Strategic -> ImpactType.Strategic
- ResourceType.Bonus -> ImpactType.Bonus
- }
-
- for (tile in tiles) {
- val conditionalTerrain = StateForConditionals(attackedTile = tile)
- if (tile.resource == null &&
- tile.lastTerrain.name in resource.terrainsCanBeFoundOn &&
- !tile.getBaseTerrain().hasUnique(UniqueType.BlocksResources, conditionalTerrain) &&
- !resource.hasUnique(UniqueType.NoNaturalGeneration, conditionalTerrain) &&
- resource.getMatchingUniques(UniqueType.TileGenerationConditions).none {
- tile.temperature!! !in it.params[0].toDouble() .. it.params[1].toDouble()
- || tile.humidity!! !in it.params[2].toDouble() .. it.params[3].toDouble()
- }
- ) {
- if (ratioProgress >= 1f &&
- !(respectImpacts && tileData[tile.position]!!.impacts.containsKey(impactType))) {
- tile.setTileResource(resource, majorDeposit)
- ratioProgress -= 1f
- amountAdded++
- if (baseImpact + randomImpact >= 0)
- placeImpact(impactType, tile, baseImpact + Random.nextInt(randomImpact + 1))
- if (amountAdded >= amount) break
- }
- ratioProgress += ratio
- }
- }
- return amountAdded
- }
-
- /** Attempts to place major deposits in a [tileList] consisting exclusively of [terrain] tiles.
- * Lifted out of the main function to allow postponing water resources.
- * @return a map of resource types to placed deposits. */
- private fun placeMajorDeposits(tileList: List, terrain: Terrain, fallbackWeightings: Boolean, baseImpact: Int, randomImpact: Int): Map {
- if (tileList.isEmpty())
- return mapOf()
- val frequency = if (terrain.hasUnique(UniqueType.MajorStrategicFrequency))
- terrain.getMatchingUniques(UniqueType.MajorStrategicFrequency).first().params[0].toInt()
- else 25
- val resourceOptions = ruleset.tileResources.values.filter {
- it.resourceType == ResourceType.Strategic &&
- ((fallbackWeightings && terrain.name in it.terrainsCanBeFoundOn) ||
- it.uniqueObjects.any { unique -> anonymizeUnique(unique).text == getTerrainRule(terrain).text })
- }
- return if (resourceOptions.isNotEmpty())
- placeResourcesInTiles(frequency, tileList, resourceOptions, baseImpact, randomImpact, true)
- else
- mapOf()
- }
-
- /** Given a [tileList] and possible [resourceOptions], will place a resource on every [frequency] tiles.
- * Tries to avoid impacts, but falls back to lowest impact otherwise.
- * Goes through the list in order, so pre-shuffle it!
- * Assumes all tiles in the list are of the same terrain type when generating weightings, irrelevant if only one option.
- * Respects terrainsCanBeFoundOn when there is only one option, unless [forcePlacement] is true.
- * @return a map of the resources in the options list to number placed. */
- private fun placeResourcesInTiles(frequency: Int, tileList: List, resourceOptions: List,
- baseImpact: Int = 0, randomImpact: Int = 0, majorDeposit: Boolean = false, forcePlacement: Boolean = false): Map {
- if (tileList.isEmpty() || resourceOptions.isEmpty()) return mapOf()
- val impactType = when (resourceOptions.first().resourceType) {
- ResourceType.Strategic -> ImpactType.Strategic
- ResourceType.Bonus -> ImpactType.Bonus
- ResourceType.Luxury -> ImpactType.Luxury
- }
- val conditionalTerrain = StateForConditionals(attackedTile = tileList.firstOrNull())
- val weightings = resourceOptions.map {
- val unique = it.getMatchingUniques(UniqueType.ResourceWeighting, conditionalTerrain).firstOrNull()
- if (unique != null)
- unique.params[0].toFloat()
- else
- 1f
- }
- val testTerrains = (resourceOptions.size == 1) && !forcePlacement
- val amountToPlace = (tileList.size / frequency) + 1
- var amountPlaced = 0
- val detailedPlaced = HashMap()
- resourceOptions.forEach { detailedPlaced[it] = 0 }
- val fallbackTiles = ArrayList()
- // First pass - avoid impacts entirely
- for (tile in tileList) {
- if (tile.resource != null ||
- (testTerrains &&
- (tile.lastTerrain.name !in resourceOptions.first().terrainsCanBeFoundOn ||
- resourceOptions.first().hasUnique(UniqueType.NoNaturalGeneration, conditionalTerrain)) ) ||
- tile.getBaseTerrain().hasUnique(UniqueType.BlocksResources, conditionalTerrain))
- continue // Can't place here, can't be a fallback tile
- if (tileData[tile.position]!!.impacts.containsKey(impactType)) {
- fallbackTiles.add(tile) // Taken but might be a viable fallback tile
- } else {
- // Add a resource to the tile
- val resourceToPlace = resourceOptions.randomWeighted(weightings)
- tile.setTileResource(resourceToPlace, majorDeposit)
- placeImpact(impactType, tile, baseImpact + Random.nextInt(randomImpact + 1))
- amountPlaced++
- detailedPlaced[resourceToPlace] = detailedPlaced[resourceToPlace]!! + 1
- if (amountPlaced >= amountToPlace) {
- return detailedPlaced
- }
- }
- }
- // Second pass - place on least impacted tiles
- while (amountPlaced < amountToPlace && fallbackTiles.isNotEmpty()) {
- // Sorry, we do need to re-sort the list for every pass since new impacts are made with every placement
- val bestTile = fallbackTiles.minByOrNull { tileData[it.position]!!.impacts[impactType]!! }!!
- fallbackTiles.remove(bestTile)
- val resourceToPlace = resourceOptions.randomWeighted(weightings)
- bestTile.setTileResource(resourceToPlace, majorDeposit)
- placeImpact(impactType, bestTile, baseImpact + Random.nextInt(randomImpact + 1))
- amountPlaced++
- detailedPlaced[resourceToPlace] = detailedPlaced[resourceToPlace]!! + 1
- }
- return detailedPlaced
- }
-
- /** Adds numbers to tileData in a similar way to closeStartPenalty, but for different types */
- private fun placeImpact(type: ImpactType, tile: Tile, radius: Int) {
- // Epicenter
- tileData[tile.position]!!.impacts[type] = 99
- if (radius <= 0) return
-
- for (ring in 1..radius) {
- val ringValue = radius - ring + 1
- for (outerTile in tile.getTilesAtDistance(ring)) {
- val data = tileData[outerTile.position]!!
- if (data.impacts.containsKey(type))
- data.impacts[type] = min(50, max(ringValue, data.impacts[type]!!) + 2)
- else
- data.impacts[type] = ringValue
- }
- }
- }
-
- /** @return a fake unique with the same conditionals, but sorted alphabetically.
- * Used to save some memory and time when building resource lists. */
- private fun anonymizeUnique(unique: Unique) = Unique(
- "RULE" + unique.conditionals.sortedBy { it.text }.joinToString(prefix = " ", separator = " ") { "<" + it.text + ">" })
-
- /** @return a fake unique with conditionals that will satisfy the same conditions as terrainsCanBeFoundOn */
- private fun getTerrainRule(terrain: Terrain): Unique {
- return if (terrain.type == TerrainType.TerrainFeature) {
- if (terrain.hasUnique(UniqueType.VisibilityElevation))
- Unique("RULE ")
- else
- Unique("RULE " + ruleset.terrains.values
- .filter { it.type == TerrainType.TerrainFeature && it.hasUnique(UniqueType.VisibilityElevation) }
- .joinToString(separator = " ") { "" })
- } else
- Unique("RULE ")
- }
-
- private fun isWaterOnlyResource(resource: TileResource) = resource.terrainsCanBeFoundOn
- .all { terrainName -> ruleset.terrains[terrainName]!!.type == TerrainType.Water }
-
- enum class ImpactType {
- Strategic,
- Luxury,
- Bonus,
- MinorCiv,
- }
-
- // Holds a bunch of tile info that is only interesting during map gen
- class MapGenTileData(val tile: Tile, val region: Region?) {
- var closeStartPenalty = 0
- val impacts = HashMap()
- var isFood = false
- var isProd = false
- var isGood = false
- var isJunk = false
- var isTwoFromCoast = false
-
- var isGoodStart = true
- var startScore = 0
-
- fun addCloseStartPenalty(penalty: Int) {
- if (closeStartPenalty == 0)
- closeStartPenalty = penalty
- else {
- // Multiple overlapping values - take the higher one and add 20 %
- closeStartPenalty = max(closeStartPenalty, penalty)
- closeStartPenalty = min(97, (closeStartPenalty * 1.2f).toInt())
- }
- }
-
- fun evaluate(ruleset: Ruleset) {
- // Check if we are two tiles from coast (a bad starting site)
- if (!tile.isCoastalTile() && tile.neighbors.any { it.isCoastalTile() })
- isTwoFromCoast = true
-
- // Check first available out of unbuildable features, then other features, then base terrain
- val terrainToCheck = if (tile.terrainFeatures.isEmpty()) tile.getBaseTerrain()
- else tile.terrainFeatureObjects.firstOrNull { it.unbuildable }
- ?: tile.terrainFeatureObjects.first()
-
- // Add all applicable qualities
- for (unique in terrainToCheck.getMatchingUniques(UniqueType.HasQuality, StateForConditionals(region = region))) {
- when (unique.params[0]) {
- "Food" -> isFood = true
- "Desirable" -> isGood = true
- "Production" -> isProd = true
- "Undesirable" -> isJunk = true
- }
- }
-
- // Were there in fact no explicit qualities defined for any region at all? If so let's guess at qualities to preserve mod compatibility.
- if (terrainToCheck.uniqueObjects.none { it.type == UniqueType.HasQuality }) {
- if (tile.isWater) return // Most water type tiles have no qualities
-
- // is it junk???
- if (terrainToCheck.impassable) {
- isJunk = true
- return // Don't bother checking the rest, junk is junk
- }
-
- // Take possible improvements into account
- val improvements = ruleset.tileImprovements.values.filter {
- terrainToCheck.name in it.terrainsCanBeBuiltOn &&
- it.uniqueTo == null &&
- !it.hasUnique(UniqueType.GreatImprovement)
- }
-
- val maxFood = terrainToCheck.food + (improvements.maxOfOrNull { it.food } ?: 0f)
- val maxProd = terrainToCheck.production + (improvements.maxOfOrNull { it.production } ?: 0f)
- val bestImprovementValue = improvements.maxOfOrNull { it.food + it.production + it.gold + it.culture + it.science + it.faith } ?: 0f
- val maxOverall = terrainToCheck.food + terrainToCheck.production + terrainToCheck.gold +
- terrainToCheck.culture + terrainToCheck.science + terrainToCheck.faith + bestImprovementValue
-
- if (maxFood >= 2) isFood = true
- if (maxProd >= 2) isProd = true
- if (maxOverall >= 3) isGood = true
- }
- }
- }
-}
-
-class Region (val tileMap: TileMap, val rect: Rectangle, val continentID: Int = -1) {
- val tiles = HashSet()
- val terrainCounts = HashMap()
- var totalFertility = 0
- var type = "Hybrid" // being an undefined or indeterminate type
- var luxury: String? = null
- var startPosition: Vector2? = null
- val assignedMinorCivs = ArrayList()
-
- var affectedByWorldWrap = false
-
- /** Recalculates tiles and fertility */
- fun updateTiles(trim: Boolean = true) {
- totalFertility = 0
- var minColumn = 99999f
- var maxColumn = -99999f
- var minRow = 99999f
- var maxRow = -99999f
-
- val columnHasTile = HashSet()
-
- tiles.clear()
- for (tile in tileMap.getTilesInRectangle(rect).filter {
- continentID == -1 || it.getContinent() == continentID } ) {
- val fertility = tile.getTileFertility(continentID != -1)
- tiles.add(tile)
- totalFertility += fertility
-
- if (affectedByWorldWrap)
- columnHasTile.add(tile.getColumn())
-
- if (trim) {
- val row = tile.getRow().toFloat()
- val column = tile.getColumn().toFloat()
- minColumn = min(minColumn, column)
- maxColumn = max(maxColumn, column)
- minRow = min(minRow, row)
- maxRow = max(maxRow, row)
- }
- }
-
- if (trim) {
- if (affectedByWorldWrap) // Need to be more thorough with origin longitude
- rect.x = columnHasTile.filter { !columnHasTile.contains(it - 1) }.maxOf { it }.toFloat()
- else
- rect.x = minColumn // ez way for non-wrapping regions
- rect.y = minRow
- rect.height = maxRow - minRow + 1
- if (affectedByWorldWrap && minColumn < rect.x) { // Thorough way
- rect.width = columnHasTile.size.toFloat()
- } else {
- rect.width = maxColumn - minColumn + 1 // ez way
- affectedByWorldWrap = false // also we're not wrapping anymore
- }
- }
- }
-
- /** Counts the terrains in the Region for type and start determination */
- fun countTerrains() {
- // Count terrains in the region
- terrainCounts.clear()
- for (tile in tiles) {
- val terrainsToCount = if (tile.terrainHasUnique(UniqueType.IgnoreBaseTerrainForRegion))
- tile.terrainFeatureObjects.map { it.name }.asSequence()
- else
- tile.allTerrains.map { it.name }
- for (terrain in terrainsToCount) {
- terrainCounts[terrain] = (terrainCounts[terrain] ?: 0) + 1
- }
- if (tile.isCoastalTile())
- terrainCounts["Coastal"] = (terrainCounts["Coastal"] ?: 0) + 1
- }
- }
-
- /** Returns number terrains with [name] */
- fun getTerrainAmount(name: String) = terrainCounts[name] ?: 0
-
- override fun toString() = "Region($type, ${tiles.size} tiles, ${terrainCounts.entries.joinToString { "${it.value} ${it.key}" }})"
-}
diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/LuxuryResourcePlacementLogic.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/LuxuryResourcePlacementLogic.kt
new file mode 100644
index 0000000000000..f6e0dca1175d5
--- /dev/null
+++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/LuxuryResourcePlacementLogic.kt
@@ -0,0 +1,424 @@
+package com.unciv.logic.map.mapgenerator.mapregions
+
+import com.unciv.logic.map.MapResources
+import com.unciv.logic.map.TileMap
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.ruleset.Ruleset
+import com.unciv.models.ruleset.tile.ResourceType
+import com.unciv.models.ruleset.tile.TerrainType
+import com.unciv.models.ruleset.tile.TileResource
+import com.unciv.models.ruleset.unique.StateForConditionals
+import com.unciv.models.ruleset.unique.UniqueType
+import com.unciv.ui.components.extensions.randomWeighted
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.pow
+import kotlin.random.Random
+
+object LuxuryResourcePlacementLogic {
+
+ /** Assigns a luxury to each region. No luxury can be assigned to too many regions.
+ * Some luxuries are earmarked for city states. The rest are randomly distributed or
+ * don't occur att all in the map */
+ fun assignLuxuries(regions: ArrayList, tileData: TileDataMap, ruleset: Ruleset): Pair, List> {
+
+ // If there are any weightings defined in json, assume they are complete. If there are none, use flat weightings instead
+ val fallbackWeightings = ruleset.tileResources.values.none {
+ it.resourceType == ResourceType.Luxury &&
+ (it.uniqueObjects.any { unique -> unique.isOfType(UniqueType.ResourceWeighting) } || it.hasUnique(
+ UniqueType.LuxuryWeightingForCityStates)) }
+
+ val maxRegionsWithLuxury = when {
+ regions.size > 12 -> 3
+ regions.size > 8 -> 2
+ else -> 1
+ }
+ val targetCityStateLuxuries = 3 // was probably intended to be "if (tileData.size > 5000) 4 else 3"
+ val assignableLuxuries = ruleset.tileResources.values.filter {
+ it.resourceType == ResourceType.Luxury &&
+ !it.hasUnique(UniqueType.LuxurySpecialPlacement) &&
+ !it.hasUnique(UniqueType.CityStateOnlyResource) }
+ val amountRegionsWithLuxury = HashMap()
+ // Init map
+ ruleset.tileResources.values
+ .forEach { amountRegionsWithLuxury[it.name] = 0 }
+
+ for (region in regions.sortedBy { getRegionPriority(ruleset.terrains[it.type]) } ) {
+ val candidateLuxuries = getCandidateLuxuries(
+ assignableLuxuries,
+ amountRegionsWithLuxury,
+ maxRegionsWithLuxury,
+ fallbackWeightings,
+ region,
+ ruleset
+ )
+ // If there are no candidates (mad modders???) just skip this region
+ if (candidateLuxuries.isEmpty()) continue
+
+ // Pick a luxury at random. Weight is reduced if the luxury has been picked before
+ val regionConditional = StateForConditionals(region = region)
+ val modifiedWeights = candidateLuxuries.map {
+ val weightingUnique = it.getMatchingUniques(UniqueType.ResourceWeighting, regionConditional).firstOrNull()
+ val relativeWeight = if (weightingUnique == null) 1f else weightingUnique.params[0].toFloat()
+ relativeWeight / (1f + amountRegionsWithLuxury[it.name]!!)
+ }.shuffled()
+ region.luxury = candidateLuxuries.randomWeighted(modifiedWeights).name
+ amountRegionsWithLuxury[region.luxury!!] = amountRegionsWithLuxury[region.luxury]!! + 1
+ }
+
+
+ val cityStateLuxuries = assignCityStateLuxuries(
+ targetCityStateLuxuries,
+ assignableLuxuries,
+ amountRegionsWithLuxury,
+ fallbackWeightings
+ )
+
+ val randomLuxuries = getLuxuriesForRandomPlacement(assignableLuxuries, amountRegionsWithLuxury, tileData, ruleset)
+
+ return Pair(cityStateLuxuries, randomLuxuries)
+ }
+
+ private fun getLuxuriesForRandomPlacement(
+ assignableLuxuries: List,
+ amountRegionsWithLuxury: HashMap,
+ tileData: TileDataMap,
+ ruleset: Ruleset
+ ): List {
+ val remainingLuxuries = assignableLuxuries.filter {
+ amountRegionsWithLuxury[it.name] == 0
+ }.map { it.name }.shuffled()
+
+ val disabledPercent =
+ 100 - min(tileData.size.toFloat().pow(0.2f) * 16, 100f).toInt() // Approximately
+ val targetDisabledLuxuries = (ruleset.tileResources.values
+ .count { it.resourceType == ResourceType.Luxury } * disabledPercent) / 100
+ return remainingLuxuries.drop(targetDisabledLuxuries)
+ }
+
+ private fun getCandidateLuxuries(
+ assignableLuxuries: List,
+ amountRegionsWithLuxury: HashMap,
+ maxRegionsWithLuxury: Int,
+ fallbackWeightings: Boolean,
+ region: Region,
+ ruleset: Ruleset
+ ): List {
+ val regionConditional = StateForConditionals(region = region)
+
+ var candidateLuxuries = assignableLuxuries.filter {
+ amountRegionsWithLuxury[it.name]!! < maxRegionsWithLuxury &&
+ // Check that it has a weight for this region type
+ (fallbackWeightings ||
+ it.hasUnique(UniqueType.ResourceWeighting, regionConditional)) &&
+ // Check that there is enough coast if it is a water based resource
+ ((region.terrainCounts["Coastal"] ?: 0) >= 12 ||
+ it.terrainsCanBeFoundOn.any { terrain -> ruleset.terrains[terrain]!!.type != TerrainType.Water })
+ }
+
+ // If we couldn't find any options, pick from all luxuries. First try to not pick water luxuries on land regions
+ if (candidateLuxuries.isEmpty()) {
+ candidateLuxuries = assignableLuxuries.filter {
+ amountRegionsWithLuxury[it.name]!! < maxRegionsWithLuxury &&
+ // Ignore weightings for this pass
+ // Check that there is enough coast if it is a water based resource
+ ((region.terrainCounts["Coastal"] ?: 0) >= 12 ||
+ it.terrainsCanBeFoundOn.any { terrain -> ruleset.terrains[terrain]!!.type != TerrainType.Water })
+ }
+ }
+ // If there are still no candidates, ignore water restrictions
+ if (candidateLuxuries.isEmpty()) {
+ candidateLuxuries = assignableLuxuries.filter {
+ amountRegionsWithLuxury[it.name]!! < maxRegionsWithLuxury
+ // Ignore weightings and water for this pass
+ }
+ }
+ return candidateLuxuries
+ }
+
+ private fun assignCityStateLuxuries(
+ targetCityStateLuxuries: Int,
+ assignableLuxuries: List,
+ amountRegionsWithLuxury: HashMap,
+ fallbackWeightings: Boolean
+ ): ArrayList {
+ val cityStateLuxuries = ArrayList()
+ repeat(targetCityStateLuxuries) {
+ val candidateLuxuries = assignableLuxuries.filter {
+ amountRegionsWithLuxury[it.name] == 0 &&
+ (fallbackWeightings || it.hasUnique(UniqueType.LuxuryWeightingForCityStates))
+ }
+ if (candidateLuxuries.isEmpty()) return@repeat
+
+ val weights = candidateLuxuries.map {
+ val weightingUnique =
+ it.getMatchingUniques(UniqueType.LuxuryWeightingForCityStates).firstOrNull()
+ if (weightingUnique == null)
+ 1f
+ else
+ weightingUnique.params[0].toFloat()
+ }
+ val luxury = candidateLuxuries.randomWeighted(weights).name
+ cityStateLuxuries.add(luxury)
+ amountRegionsWithLuxury[luxury] = 1
+ }
+ return cityStateLuxuries
+ }
+
+
+ /** Places all Luxuries onto [tileMap]. Assumes that assignLuxuries and placeMinorCivs have been called. */
+ fun placeLuxuries(
+ regions: ArrayList,
+ tileMap: TileMap,
+ tileData: TileDataMap,
+ ruleset: Ruleset,
+ cityStateLuxuries: List,
+ randomLuxuries: List
+ ) {
+
+ placeLuxuriesAtMajorCivStartLocations(regions, tileMap, ruleset, tileData, randomLuxuries)
+ placeLuxuriesAtMinorCivStartLocations(tileMap, ruleset, regions, randomLuxuries, cityStateLuxuries, tileData)
+ addRegionalLuxuries(tileData, regions, tileMap, ruleset)
+ addRandomLuxuries(randomLuxuries, tileData, tileMap, regions, ruleset)
+
+
+ val specialLuxuries = ruleset.tileResources.values.filter {
+ it.resourceType == ResourceType.Luxury &&
+ it.hasUnique(UniqueType.LuxurySpecialPlacement)
+ }
+ val placedSpecials = HashMap()
+ specialLuxuries.forEach { placedSpecials[it.name] = 0 } // init map
+
+ addExtraLuxuryToStarts(
+ tileMap,
+ regions,
+ randomLuxuries,
+ specialLuxuries,
+ cityStateLuxuries,
+ tileData,
+ ruleset,
+ placedSpecials
+ )
+
+ fillSpecialLuxuries(specialLuxuries, tileMap, regions, placedSpecials, tileData)
+ }
+
+ /** top up marble-type specials if needed */
+ private fun fillSpecialLuxuries(
+ specialLuxuries: List,
+ tileMap: TileMap,
+ regions: ArrayList,
+ placedSpecials: HashMap,
+ tileData: TileDataMap
+ ) {
+ for (special in specialLuxuries) {
+ val targetNumber = when (tileMap.mapParameters.mapResources) {
+ MapResources.sparse -> (regions.size * 0.5f).toInt()
+ MapResources.abundant -> (regions.size * 0.9f).toInt()
+ else -> (regions.size * 0.75f).toInt()
+ }
+ val numberToPlace = max(2, targetNumber - placedSpecials[special.name]!!)
+ MapRegionResources.tryAddingResourceToTiles(
+ tileData, special, numberToPlace, tileMap.values.asSequence().shuffled(), 1f,
+ true, 6, 0
+ )
+ }
+ }
+
+ private fun addExtraLuxuryToStarts(
+ tileMap: TileMap,
+ regions: ArrayList,
+ randomLuxuries: List,
+ specialLuxuries: List,
+ cityStateLuxuries: List,
+ tileData: TileDataMap,
+ ruleset: Ruleset,
+ placedSpecials: HashMap
+ ) {
+ if (tileMap.mapParameters.mapResources == MapResources.sparse) return
+ for (region in regions) {
+ val tilesToCheck = tileMap[region.startPosition!!].getTilesInDistanceRange(1..2)
+ val candidateLuxuries = randomLuxuries.shuffled().toMutableList()
+ if (tileMap.mapParameters.mapResources != MapResources.strategicBalance)
+ candidateLuxuries += specialLuxuries.shuffled()
+ .map { it.name } // Include marble!
+ candidateLuxuries += cityStateLuxuries.shuffled()
+ candidateLuxuries += regions.mapNotNull { it.luxury }.shuffled()
+ for (luxury in candidateLuxuries) {
+ if (MapRegionResources.tryAddingResourceToTiles(
+ tileData,
+ ruleset.tileResources[luxury]!!,
+ 1,
+ tilesToCheck
+ ) > 0
+ ) {
+ if (placedSpecials.containsKey(luxury)) // Keep track of marble-type specials as they may be placed now.
+ placedSpecials[luxury] = placedSpecials[luxury]!! + 1
+ break
+ }
+ }
+ }
+ }
+
+ private fun addRandomLuxuries(
+ randomLuxuries: List,
+ tileData: TileDataMap,
+ tileMap: TileMap,
+ regions: ArrayList,
+ ruleset: Ruleset
+ ) {
+ if (randomLuxuries.isEmpty()) return
+ var targetRandomLuxuries = tileData.size.toFloat().pow(0.45f).toInt() // Approximately
+ targetRandomLuxuries *= when (tileMap.mapParameters.mapResources) {
+ MapResources.sparse -> 80
+ MapResources.abundant -> 133
+ else -> 100
+ }
+ targetRandomLuxuries /= 100
+ targetRandomLuxuries += Random.nextInt(regions.size) // Add random number based on number of civs
+ val minimumRandomLuxuries = tileData.size.toFloat().pow(0.2f).toInt() // Approximately
+ val worldTiles = tileMap.values.asSequence().shuffled()
+ for ((index, luxury) in randomLuxuries.shuffled().withIndex()) {
+ val targetForThisLuxury = if (randomLuxuries.size > 8) targetRandomLuxuries / 10
+ else {
+ val minimum = max(3, minimumRandomLuxuries - index)
+ max(
+ minimum,
+ (targetRandomLuxuries * MapRegions.randomLuxuryRatios[randomLuxuries.size]!![index] + 0.5f).toInt()
+ )
+ }
+ MapRegionResources.tryAddingResourceToTiles(
+ tileData,
+ ruleset.tileResources[luxury]!!,
+ targetForThisLuxury,
+ worldTiles,
+ 0.25f,
+ true,
+ 4,
+ 2
+ )
+ }
+ }
+
+ private fun addRegionalLuxuries(
+ tileData: TileDataMap,
+ regions: ArrayList,
+ tileMap: TileMap,
+ ruleset: Ruleset
+ ) {
+ val idealCivsForMapSize = max(2, tileData.size / 500)
+ var regionTargetNumber =
+ (tileData.size / 600) - (0.3f * abs(regions.size - idealCivsForMapSize)).toInt()
+ regionTargetNumber += when (tileMap.mapParameters.mapResources) {
+ MapResources.abundant -> 1
+ MapResources.sparse -> -1
+ else -> 0
+ }
+ regionTargetNumber = max(1, regionTargetNumber)
+ for (region in regions) {
+ val resource = ruleset.tileResources[region.luxury] ?: continue
+ fun Tile.isShoreOfContinent(continent: Int) =
+ isWater && neighbors.any { it.getContinent() == continent }
+
+ val candidates = if (isWaterOnlyResource(resource, ruleset))
+ tileMap.getTilesInRectangle(region.rect)
+ .filter { it.isShoreOfContinent(region.continentID) }
+ else region.tiles.asSequence()
+ MapRegionResources.tryAddingResourceToTiles(
+ tileData,
+ resource,
+ regionTargetNumber,
+ candidates.shuffled(),
+ 0.4f,
+ true,
+ 4,
+ 2
+ )
+ }
+ }
+
+ private fun placeLuxuriesAtMinorCivStartLocations(
+ tileMap: TileMap,
+ ruleset: Ruleset,
+ regions: ArrayList,
+ randomLuxuries: List,
+ cityStateLuxuries: List,
+ tileData: TileDataMap
+ ) {
+ for (startLocation in tileMap.startingLocationsByNation
+ .filterKeys { ruleset.nations[it]!!.isCityState }.map { it.value.first() }) {
+ val region = regions.firstOrNull { startLocation in it.tiles }
+ val tilesToCheck = startLocation.getTilesInDistanceRange(1..2)
+ // 75% probability that we first attempt to place a "city state" luxury, then a random or regional one
+ // 25% probability of going the other way around
+ val globalLuxuries =
+ if (region?.luxury != null) randomLuxuries + listOf(region.luxury) else randomLuxuries
+ val candidateLuxuries = if (Random.nextInt(100) >= 25)
+ cityStateLuxuries.shuffled() + globalLuxuries.shuffled()
+ else
+ globalLuxuries.shuffled() + cityStateLuxuries.shuffled()
+ // Now try adding one until we are successful
+ for (luxury in candidateLuxuries) {
+ if (MapRegionResources.tryAddingResourceToTiles(
+ tileData,
+ ruleset.tileResources[luxury]!!,
+ 1,
+ tilesToCheck
+ ) > 0
+ ) break
+ }
+ }
+ }
+
+ private fun placeLuxuriesAtMajorCivStartLocations(
+ regions: ArrayList,
+ tileMap: TileMap,
+ ruleset: Ruleset,
+ tileData: TileDataMap,
+ randomLuxuries: List
+ ) {
+ val averageFertilityDensity =
+ regions.sumOf { it.totalFertility } / regions.sumOf { it.tiles.size }.toFloat()
+ for (region in regions) {
+ var targetLuxuries = 1
+ if (tileMap.mapParameters.mapResources == MapResources.legendaryStart)
+ targetLuxuries++
+ if (region.totalFertility / region.tiles.size.toFloat() < averageFertilityDensity) {
+ targetLuxuries++
+ }
+
+ val luxuryToPlace = ruleset.tileResources[region.luxury] ?: continue
+ // First check 2 inner rings
+ val firstPass = tileMap[region.startPosition!!].getTilesInDistanceRange(1..2)
+ .shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first
+ targetLuxuries -= MapRegionResources.tryAddingResourceToTiles(
+ tileData,
+ luxuryToPlace,
+ targetLuxuries,
+ firstPass,
+ 0.5f
+ ) // Skip every 2nd tile on first pass
+
+ if (targetLuxuries > 0) {
+ val secondPass = firstPass + tileMap[region.startPosition!!].getTilesAtDistance(3)
+ .shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first
+ targetLuxuries -= MapRegionResources.tryAddingResourceToTiles(
+ tileData,
+ luxuryToPlace,
+ targetLuxuries,
+ secondPass
+ )
+ }
+ if (targetLuxuries > 0) {
+ // Try adding in 1 luxury from the random rotation as compensation
+ for (luxury in randomLuxuries) {
+ if (MapRegionResources.tryAddingResourceToTiles(
+ tileData, ruleset.tileResources[luxury]!!, 1, firstPass) > 0
+ ) break
+ }
+ }
+ }
+ }
+
+}
diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapGenTileData.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapGenTileData.kt
new file mode 100644
index 0000000000000..7a1a174d6cfe0
--- /dev/null
+++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapGenTileData.kt
@@ -0,0 +1,94 @@
+package com.unciv.logic.map.mapgenerator.mapregions
+
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.ruleset.Ruleset
+import com.unciv.models.ruleset.unique.StateForConditionals
+import com.unciv.models.ruleset.unique.UniqueType
+import kotlin.math.max
+import kotlin.math.min
+
+// Holds a bunch of tile info that is only interesting during map gen
+class MapGenTileData(val tile: Tile, val region: Region?, ruleset: Ruleset) {
+ var closeStartPenalty = 0
+ val impacts = HashMap()
+ var isFood = false
+ private set
+ var isProd = false
+ private set
+ var isGood = false
+ private set
+ var isJunk = false
+ private set
+ var isTwoFromCoast = false
+ private set
+
+ var isGoodStart = true
+ var startScore = 0
+
+ init {
+ evaluate(ruleset)
+ }
+
+ fun addCloseStartPenalty(penalty: Int) {
+ if (closeStartPenalty == 0)
+ closeStartPenalty = penalty
+ else {
+ // Multiple overlapping values - take the higher one and add 20 %
+ closeStartPenalty = max(closeStartPenalty, penalty)
+ closeStartPenalty = min(97, (closeStartPenalty * 1.2f).toInt())
+ }
+ }
+
+ /** Populates all private-set fields */
+ private fun evaluate(ruleset: Ruleset) {
+ // Check if we are two tiles from coast (a bad starting site)
+ if (!tile.isCoastalTile() && tile.neighbors.any { it.isCoastalTile() })
+ isTwoFromCoast = true
+
+ // Check first available out of unbuildable features, then other features, then base terrain
+ val terrainToCheck = if (tile.terrainFeatures.isEmpty()) tile.getBaseTerrain()
+ else tile.terrainFeatureObjects.firstOrNull { it.unbuildable }
+ ?: tile.terrainFeatureObjects.first()
+
+ // Add all applicable qualities
+ for (unique in terrainToCheck.getMatchingUniques(
+ UniqueType.HasQuality,
+ StateForConditionals(region = region)
+ )) {
+ when (unique.params[0]) {
+ "Food" -> isFood = true
+ "Desirable" -> isGood = true
+ "Production" -> isProd = true
+ "Undesirable" -> isJunk = true
+ }
+ }
+
+ // Were there in fact no explicit qualities defined for any region at all? If so let's guess at qualities to preserve mod compatibility.
+ if (terrainToCheck.uniqueObjects.none { it.type == UniqueType.HasQuality }) {
+ if (tile.isWater) return // Most water type tiles have no qualities
+
+ // is it junk???
+ if (terrainToCheck.impassable) {
+ isJunk = true
+ return // Don't bother checking the rest, junk is junk
+ }
+
+ // Take possible improvements into account
+ val improvements = ruleset.tileImprovements.values.filter {
+ terrainToCheck.name in it.terrainsCanBeBuiltOn &&
+ it.uniqueTo == null &&
+ !it.hasUnique(UniqueType.GreatImprovement)
+ }
+
+ val maxFood = terrainToCheck.food + (improvements.maxOfOrNull { it.food } ?: 0f)
+ val maxProd = terrainToCheck.production + (improvements.maxOfOrNull { it.production } ?: 0f)
+ val bestImprovementValue = improvements.maxOfOrNull { it.food + it.production + it.gold + it.culture + it.science + it.faith } ?: 0f
+ val maxOverall = terrainToCheck.food + terrainToCheck.production + terrainToCheck.gold +
+ terrainToCheck.culture + terrainToCheck.science + terrainToCheck.faith + bestImprovementValue
+
+ if (maxFood >= 2) isFood = true
+ if (maxProd >= 2) isProd = true
+ if (maxOverall >= 3) isGood = true
+ }
+ }
+}
diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegionResources.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegionResources.kt
new file mode 100644
index 0000000000000..b2e3a13d55a87
--- /dev/null
+++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegionResources.kt
@@ -0,0 +1,152 @@
+package com.unciv.logic.map.mapgenerator.mapregions
+
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.ruleset.Ruleset
+import com.unciv.models.ruleset.tile.ResourceType
+import com.unciv.models.ruleset.tile.Terrain
+import com.unciv.models.ruleset.tile.TileResource
+import com.unciv.models.ruleset.unique.StateForConditionals
+import com.unciv.models.ruleset.unique.UniqueType
+import com.unciv.ui.components.extensions.randomWeighted
+import kotlin.random.Random
+
+/** This class deals with the internals of *how* to place resources in tiles
+ * It does not contain the logic of *when* to do so */
+object MapRegionResources {
+
+ /** Given a [tileList] and possible [resourceOptions], will place a resource on every [frequency] tiles.
+ * Tries to avoid impacts, but falls back to lowest impact otherwise.
+ * Goes through the list in order, so pre-shuffle it!
+ * Assumes all tiles in the list are of the same terrain type when generating weightings, irrelevant if only one option.
+ * Respects terrainsCanBeFoundOn when there is only one option, unless [forcePlacement] is true.
+ * @return a map of the resources in the options list to number placed. */
+ fun placeResourcesInTiles(tileData: TileDataMap, frequency: Int, tileList: List, resourceOptions: List,
+ baseImpact: Int = 0, randomImpact: Int = 0, majorDeposit: Boolean = false,
+ forcePlacement: Boolean = false): Map {
+ if (tileList.none() || resourceOptions.isEmpty()) return mapOf()
+ val impactType = when (resourceOptions.first().resourceType) {
+ ResourceType.Strategic -> MapRegions.ImpactType.Strategic
+ ResourceType.Bonus -> MapRegions.ImpactType.Bonus
+ ResourceType.Luxury -> MapRegions.ImpactType.Luxury
+ }
+ val conditionalTerrain = StateForConditionals(attackedTile = tileList.firstOrNull())
+ val weightings = resourceOptions.map {
+ val unique = it.getMatchingUniques(UniqueType.ResourceWeighting, conditionalTerrain).firstOrNull()
+ if (unique != null)
+ unique.params[0].toFloat()
+ else
+ 1f
+ }
+ val testTerrains = (resourceOptions.size == 1) && !forcePlacement
+ val amountToPlace = (tileList.size / frequency) + 1
+ var amountPlaced = 0
+ val detailedPlaced = HashMap()
+ resourceOptions.forEach { detailedPlaced[it] = 0 }
+ val fallbackTiles = ArrayList()
+ // First pass - avoid impacts entirely
+ for (tile in tileList) {
+ if (tile.resource != null ||
+ (testTerrains &&
+ (tile.lastTerrain.name !in resourceOptions.first().terrainsCanBeFoundOn ||
+ resourceOptions.first().hasUnique(UniqueType.NoNaturalGeneration, conditionalTerrain)) ) ||
+ tile.getBaseTerrain().hasUnique(UniqueType.BlocksResources, conditionalTerrain))
+ continue // Can't place here, can't be a fallback tile
+ if (tileData[tile.position]!!.impacts.containsKey(impactType)) {
+ fallbackTiles.add(tile) // Taken but might be a viable fallback tile
+ } else {
+ // Add a resource to the tile
+ val resourceToPlace = resourceOptions.randomWeighted(weightings)
+ tile.setTileResource(resourceToPlace, majorDeposit)
+ tileData.placeImpact(impactType, tile, baseImpact + Random.nextInt(randomImpact + 1))
+ amountPlaced++
+ detailedPlaced[resourceToPlace] = detailedPlaced[resourceToPlace]!! + 1
+ if (amountPlaced >= amountToPlace) {
+ return detailedPlaced
+ }
+ }
+ }
+ // Second pass - place on least impacted tiles
+ while (amountPlaced < amountToPlace && fallbackTiles.isNotEmpty()) {
+ // Sorry, we do need to re-sort the list for every pass since new impacts are made with every placement
+ val bestTile = fallbackTiles.minByOrNull { tileData[it.position]!!.impacts[impactType]!! }!!
+ fallbackTiles.remove(bestTile)
+ val resourceToPlace = resourceOptions.randomWeighted(weightings)
+ bestTile.setTileResource(resourceToPlace, majorDeposit)
+ tileData.placeImpact(impactType, bestTile, baseImpact + Random.nextInt(randomImpact + 1))
+ amountPlaced++
+ detailedPlaced[resourceToPlace] = detailedPlaced[resourceToPlace]!! + 1
+ }
+ return detailedPlaced
+ }
+
+ /** Attempts to place [amount] [resource] on [tiles], checking tiles in order. A [ratio] below 1 means skipping
+ * some tiles, ie ratio = 0.25 will put a resource on every 4th eligible tile. Can optionally respect impact flags,
+ * and places impact if [baseImpact] >= 0. Returns number of placed resources. */
+ fun tryAddingResourceToTiles(tileData: TileDataMap, resource: TileResource, amount: Int, tiles: Sequence, ratio: Float = 1f,
+ respectImpacts: Boolean = false, baseImpact: Int = -1, randomImpact: Int = 0,
+ majorDeposit: Boolean = false): Int {
+ if (amount <= 0) return 0
+ var amountAdded = 0
+ var ratioProgress = 1f
+ val impactType = when (resource.resourceType) {
+ ResourceType.Luxury -> MapRegions.ImpactType.Luxury
+ ResourceType.Strategic -> MapRegions.ImpactType.Strategic
+ ResourceType.Bonus -> MapRegions.ImpactType.Bonus
+ }
+
+ for (tile in tiles) {
+ val conditionalTerrain = StateForConditionals(attackedTile = tile)
+ if (tile.resource == null &&
+ tile.lastTerrain.name in resource.terrainsCanBeFoundOn &&
+ !tile.getBaseTerrain().hasUnique(UniqueType.BlocksResources, conditionalTerrain) &&
+ !resource.hasUnique(UniqueType.NoNaturalGeneration, conditionalTerrain) &&
+
+ resource.getMatchingUniques(UniqueType.TileGenerationConditions).none {
+ tile.temperature!! !in it.params[0].toDouble() .. it.params[1].toDouble()
+ || tile.humidity!! !in it.params[2].toDouble() .. it.params[3].toDouble()
+ }
+ ) {
+ if (ratioProgress >= 1f &&
+ !(respectImpacts && tileData[tile.position]!!.impacts.containsKey(impactType))) {
+ tile.setTileResource(resource, majorDeposit)
+ ratioProgress -= 1f
+ amountAdded++
+ if (baseImpact + randomImpact >= 0)
+ tileData.placeImpact(impactType, tile, baseImpact + Random.nextInt(
+ randomImpact + 1
+ )
+ )
+ if (amountAdded >= amount) break
+ }
+ ratioProgress += ratio
+ }
+ }
+ return amountAdded
+ }
+
+ /** Attempts to place major deposits in a [tileList] consisting exclusively of [terrain] tiles.
+ * Lifted out of the main function to allow postponing water resources.
+ * @return a map of resource types to placed deposits. */
+ fun placeMajorDeposits(tileData: TileDataMap, ruleset: Ruleset, tileList: List, terrain: Terrain, fallbackWeightings: Boolean, baseImpact: Int, randomImpact: Int): Map {
+ if (tileList.isEmpty()) return mapOf()
+
+ val frequency = if (terrain.hasUnique(UniqueType.MajorStrategicFrequency))
+ terrain.getMatchingUniques(UniqueType.MajorStrategicFrequency).first().params[0].toInt()
+ else 25
+
+ val terrainRule = getTerrainRule(terrain, ruleset)
+ val resourceOptions = ruleset.tileResources.values.filter {
+ it.resourceType == ResourceType.Strategic &&
+ ((fallbackWeightings && terrain.name in it.terrainsCanBeFoundOn) ||
+ it.uniqueObjects.any { unique -> anonymizeUnique(unique).text == terrainRule.text })
+ }
+
+ return if (resourceOptions.isNotEmpty())
+ placeResourcesInTiles(tileData, frequency, tileList, resourceOptions, baseImpact, randomImpact, true)
+ else
+ mapOf()
+ }
+
+
+
+}
diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt
new file mode 100644
index 0000000000000..79b8dbad2118f
--- /dev/null
+++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt
@@ -0,0 +1,927 @@
+package com.unciv.logic.map.mapgenerator.mapregions
+
+import com.badlogic.gdx.math.Rectangle
+import com.badlogic.gdx.math.Vector2
+import com.unciv.Constants
+import com.unciv.logic.civilization.Civilization
+import com.unciv.logic.map.MapResources
+import com.unciv.logic.map.MapShape
+import com.unciv.logic.map.TileMap
+import com.unciv.logic.map.mapgenerator.mapregions.MapRegions.BiasTypes.PositiveFallback
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.metadata.GameParameters
+import com.unciv.models.ruleset.Ruleset
+import com.unciv.models.ruleset.tile.ResourceType
+import com.unciv.models.ruleset.tile.Terrain
+import com.unciv.models.ruleset.tile.TerrainType
+import com.unciv.models.ruleset.tile.TileResource
+import com.unciv.models.ruleset.unique.StateForConditionals
+import com.unciv.models.ruleset.unique.Unique
+import com.unciv.models.ruleset.unique.UniqueType
+import com.unciv.models.translations.equalsPlaceholderText
+import com.unciv.models.translations.getPlaceholderParameters
+import com.unciv.ui.components.extensions.randomWeighted
+import com.unciv.utils.Log
+import com.unciv.utils.Tag
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.roundToInt
+import kotlin.random.Random
+
+class TileDataMap:HashMap(){
+
+ /** Adds numbers to tileData in a similar way to closeStartPenalty, but for different types */
+ fun placeImpact(type: MapRegions.ImpactType, tile: Tile, radius: Int) {
+ // Epicenter
+ this[tile.position]!!.impacts[type] = 99
+ if (radius <= 0) return
+
+ for (ring in 1..radius) {
+ val ringValue = radius - ring + 1
+ for (outerTile in tile.getTilesAtDistance(ring)) {
+ val data = this[outerTile.position]!!
+ if (data.impacts.containsKey(type))
+ data.impacts[type] = min(50, max(ringValue, data.impacts[type]!!) + 2)
+ else
+ data.impacts[type] = ringValue
+ }
+ }
+ }
+}
+
+class MapRegions (val ruleset: Ruleset){
+ companion object {
+ val minimumFoodForRing = mapOf(1 to 1, 2 to 4, 3 to 4)
+ val minimumProdForRing = mapOf(1 to 0, 2 to 0, 3 to 2)
+ val minimumGoodForRing = mapOf(1 to 3, 2 to 6, 3 to 8)
+ const val maximumJunk = 9
+
+ val firstRingFoodScores = listOf(0, 8, 14, 19, 22, 24, 25)
+ val firstRingProdScores = listOf(0, 10, 16, 20, 20, 12, 0)
+ val secondRingFoodScores = listOf(0, 2, 5, 10, 20, 25, 28, 30, 32, 34, 35)
+ val secondRingProdScores = listOf(0, 10, 20, 25, 30, 35)
+
+ val closeStartPenaltyForRing = mapOf(
+ 0 to 99, 1 to 97, 2 to 95,
+ 3 to 92, 4 to 89, 5 to 69,
+ 6 to 57, 7 to 24, 8 to 15 )
+
+ val randomLuxuryRatios = mapOf(
+ 1 to listOf(1f),
+ 2 to listOf(0.55f, 0.44f),
+ 3 to listOf(0.40f, 0.33f, 0.27f),
+ 4 to listOf(0.35f, 0.25f, 0.25f, 0.15f),
+ 5 to listOf(0.25f, 0.25f, 0.20f, 0.15f, 0.15f),
+ 6 to listOf(0.20f, 0.20f, 0.20f, 0.15f, 0.15f, 0.10f),
+ 7 to listOf(0.20f, 0.20f, 0.15f, 0.15f, 0.10f, 0.10f, 0.10f),
+ 8 to listOf(0.20f, 0.15f, 0.15f, 0.10f, 0.10f, 0.10f, 0.10f, 0.10f)
+ )
+
+ // This number is 23 in G&K, but there's a bug where hills are exempt so this number brings
+ // the result closer to the density and distribution that was probably intended.
+ const val baseMinorDepositFrequency = 30
+
+ }
+
+ private val regions = ArrayList()
+ private var usingArchipelagoRegions = false
+ private val tileData = TileDataMap()
+
+ /** Creates [numRegions] number of balanced regions for civ starting locations. */
+ fun generateRegions(tileMap: TileMap, numRegions: Int) {
+ if (numRegions <= 0) return // Don't bother about regions, probably map editor
+ if (tileMap.continentSizes.isEmpty()) throw Exception("No Continents on this map!")
+ val totalLand = tileMap.continentSizes.values.sum().toFloat()
+ val largestContinent = tileMap.continentSizes.values.maxOf { it }.toFloat()
+
+ val radius = if (tileMap.mapParameters.shape == MapShape.hexagonal || tileMap.mapParameters.shape == MapShape.flatEarth)
+ tileMap.mapParameters.mapSize.radius.toFloat()
+ else
+ (max(tileMap.mapParameters.mapSize.width / 2, tileMap.mapParameters.mapSize.height / 2)).toFloat()
+ // A huge box including the entire map.
+ val mapRect = Rectangle(-radius, -radius, radius * 2 + 1, radius * 2 + 1)
+
+ // Lots of small islands - just split ut the map in rectangles while ignoring Continents
+ // 25% is chosen as limit so Four Corners maps don't fall in this category
+ if (largestContinent / totalLand < 0.25f) {
+ usingArchipelagoRegions = true
+ // Make a huge rectangle covering the entire map
+ val hugeRect = Region(tileMap, mapRect, -1) // -1 meaning ignore continent data
+ hugeRect.affectedByWorldWrap = false // Might as well start at the seam
+ hugeRect.updateTiles()
+ divideRegion(hugeRect, numRegions)
+ return
+ }
+ // Continents type - distribute civs according to total fertility, then split as needed
+ val continents = tileMap.continentSizes.keys.toMutableList()
+ val civsAddedToContinent = HashMap() // Continent ID, civs added
+ val continentFertility = HashMap() // Continent ID, total fertility
+ // Keep track of the even-q columns each continent is at, to figure out if they wrap
+ val continentToColumnsItsIn = HashMap>()
+
+ // Calculate continent fertilities and columns
+ for (tile in tileMap.values) {
+ val continent = tile.getContinent()
+ if (continent != -1) {
+ continentFertility[continent] = tile.getTileFertility(true) +
+ (continentFertility[continent] ?: 0)
+
+ if (continentToColumnsItsIn[continent] == null)
+ continentToColumnsItsIn[continent] = HashSet()
+
+ continentToColumnsItsIn[continent]!!.add(tile.getColumn())
+ }
+ }
+
+ // Assign regions to the best continents, giving half value for region #2 etc
+ repeat(numRegions) {
+ val bestContinent = continents
+ .maxByOrNull { continentFertility[it]!! / (1 + (civsAddedToContinent[it] ?: 0)) }!!
+ civsAddedToContinent[bestContinent] = (civsAddedToContinent[bestContinent] ?: 0) + 1
+ }
+
+ // Split up the continents
+ for (continent in civsAddedToContinent.keys) {
+ val continentRegion = Region(tileMap, Rectangle(mapRect), continent)
+ val cols = continentToColumnsItsIn[continent]!!
+ // Set origin at the rightmost column which does not have a neighbor on the left
+ continentRegion.rect.x = cols.filter { !cols.contains(it - 1) }.maxOf { it }.toFloat()
+ continentRegion.rect.width = cols.size.toFloat()
+ if (tileMap.mapParameters.worldWrap) {
+ // Check if the continent is wrapping - if the leftmost col is not the one we set origin by
+ if (cols.minOf { it } < continentRegion.rect.x)
+ continentRegion.affectedByWorldWrap = true
+ }
+ continentRegion.updateTiles()
+ divideRegion(continentRegion, civsAddedToContinent[continent]!!)
+ }
+ }
+
+ /** Recursive function, divides a region into [numDivisions] pars of equal-ish fertility */
+ private fun divideRegion(region: Region, numDivisions: Int) {
+ if (numDivisions <= 1) {
+ // We're all set, save the region and return
+ regions.add(region)
+ return
+ }
+
+ val firstDivisions = numDivisions / 2 // Since int division rounds down, works for all numbers
+ val splitRegions = splitRegion(region, (100 * firstDivisions) / numDivisions)
+ divideRegion(splitRegions.first, firstDivisions)
+ divideRegion(splitRegions.second, numDivisions - firstDivisions)
+ }
+
+ /** Splits a region in 2, with the first having [firstPercent] of total fertility */
+ private fun splitRegion(regionToSplit: Region, firstPercent: Int): Pair {
+ val targetFertility = (regionToSplit.totalFertility * firstPercent) / 100
+
+ val splitOffRegion = Region(regionToSplit.tileMap, Rectangle(regionToSplit.rect), regionToSplit.continentID)
+
+ val widerThanTall = regionToSplit.rect.width > regionToSplit.rect.height
+
+ var bestSplitPoint = 1 // will be the size of the split-off region
+ var closestFertility = 0
+ var cumulativeFertility = 0
+
+ val highestPointToTry = if (widerThanTall) regionToSplit.rect.width.toInt()
+ else regionToSplit.rect.height.toInt()
+ val pointsToTry = 1..highestPointToTry
+ val halfwayPoint = highestPointToTry/2
+
+ for (splitPoint in pointsToTry) {
+ val nextRect = if (widerThanTall)
+ splitOffRegion.tileMap.getTilesInRectangle(Rectangle(
+ splitOffRegion.rect.x + splitPoint - 1, splitOffRegion.rect.y,
+ 1f, splitOffRegion.rect.height))
+ else
+ splitOffRegion.tileMap.getTilesInRectangle(Rectangle(
+ splitOffRegion.rect.x, splitOffRegion.rect.y + splitPoint - 1,
+ splitOffRegion.rect.width, 1f))
+
+ cumulativeFertility += if (splitOffRegion.continentID == -1)
+ nextRect.sumOf { it.getTileFertility(false) }
+ else
+ nextRect.sumOf { if (it.getContinent() == splitOffRegion.continentID) it.getTileFertility(true) else 0 }
+
+ // Better than last try?
+ val bestSplitPointFertilityDeltaFromTarget = abs(closestFertility - targetFertility)
+ val currentSplitPointFertilityDeltaFromTarget = abs(cumulativeFertility - targetFertility)
+ if (currentSplitPointFertilityDeltaFromTarget < bestSplitPointFertilityDeltaFromTarget
+ || (currentSplitPointFertilityDeltaFromTarget == bestSplitPointFertilityDeltaFromTarget // same fertility split but better 'amount of tiles' split
+ && abs(halfwayPoint- splitPoint) < abs(halfwayPoint- bestSplitPoint) )) { // current split point is closer to the halfway point
+ bestSplitPoint = splitPoint
+ closestFertility = cumulativeFertility
+ }
+ }
+
+ if (widerThanTall) {
+ splitOffRegion.rect.width = bestSplitPoint.toFloat()
+ regionToSplit.rect.x = splitOffRegion.rect.x + splitOffRegion.rect.width
+ regionToSplit.rect.width = regionToSplit.rect.width- bestSplitPoint
+ } else {
+ splitOffRegion.rect.height = bestSplitPoint.toFloat()
+ regionToSplit.rect.y = splitOffRegion.rect.y + splitOffRegion.rect.height
+ regionToSplit.rect.height = regionToSplit.rect.height - bestSplitPoint
+ }
+ splitOffRegion.updateTiles()
+ regionToSplit.updateTiles()
+
+ return Pair(splitOffRegion, regionToSplit)
+ }
+
+ /** Buckets for startBias to region assignments, used only in [assignRegions]. [PositiveFallback] is only for logging. */
+ private enum class BiasTypes { Coastal, Positive, Negative, Random, PositiveFallback }
+
+ fun assignRegions(tileMap: TileMap, civilizations: List, gameParameters: GameParameters) {
+ if (civilizations.isEmpty()) return
+
+ assignRegionTypes()
+
+ // Generate tile data for all tiles
+ for (tile in tileMap.values) {
+ val newData = MapGenTileData(tile, regions.firstOrNull { it.tiles.contains(tile) }, ruleset)
+ tileData[tile.position] = newData
+ }
+
+ // Sort regions by fertility so the worse regions get to pick first
+ val sortedRegions = regions.sortedBy { it.totalFertility }
+ for (region in sortedRegions) findStart(region)
+ for (region in regions) {
+ StartNormalizer.normalizeStart(tileMap[region.startPosition!!], tileMap, tileData, ruleset, isMinorCiv = false)
+ }
+
+ val civBiases = civilizations.associateWith { ruleset.nations[it.civName]!!.startBias }
+ // This ensures each civ can only be in one of the buckets
+ val civsByBiasType = civBiases.entries.groupBy(
+ keySelector = {
+ (_, startBias) ->
+ when {
+ gameParameters.noStartBias -> BiasTypes.Random
+ startBias.any { bias -> bias.equalsPlaceholderText("Avoid []") } -> BiasTypes.Negative
+ "Coast" in startBias -> BiasTypes.Coastal
+ startBias.isNotEmpty() -> BiasTypes.Positive
+ else -> BiasTypes.Random
+ }
+ },
+ valueTransform = { (civ, _) -> civ }
+ )
+
+ val coastBiasCivs = civsByBiasType[BiasTypes.Coastal]
+ ?: emptyList()
+ val positiveBiasCivs = civsByBiasType[BiasTypes.Positive]
+ ?.sortedBy { civBiases[it]?.size } // civs with only one desired region go first
+ ?: emptyList()
+ val negativeBiasCivs = civsByBiasType[BiasTypes.Negative]
+ ?.sortedByDescending { civBiases[it]?.size } // Civs with more complex avoids go first
+ ?: emptyList()
+ val randomCivs = civsByBiasType[BiasTypes.Random]
+ ?.toMutableList() // We might fill this up as we go
+ ?: mutableListOf()
+ val positiveBiasFallbackCivs = mutableListOf() // Civs who couldn't get their desired region at first pass
+ val unpickedRegions = regions.toMutableList()
+
+ // First assign coast bias civs
+ for (civ in coastBiasCivs) {
+ // Try to find a coastal start, preferably a really coastal one
+ var startRegion = unpickedRegions.filter { tileMap[it.startPosition!!].isCoastalTile() }
+ .maxByOrNull { it.terrainCounts["Coastal"] ?: 0 }
+ if (startRegion != null) {
+ logAssignRegion(true, BiasTypes.Coastal, civ, startRegion)
+ assignCivToRegion(civ, startRegion)
+ unpickedRegions.remove(startRegion)
+ continue
+ }
+ // Else adjacent to a lake
+ startRegion = unpickedRegions.filter { tileMap[it.startPosition!!].neighbors.any { neighbor -> neighbor.getBaseTerrain().hasUnique(UniqueType.FreshWater) } }
+ .maxByOrNull { it.terrainCounts["Coastal"] ?: 0 }
+ if (startRegion != null) {
+ logAssignRegion(true, BiasTypes.Coastal, civ, startRegion)
+ assignCivToRegion(civ, startRegion)
+ unpickedRegions.remove(startRegion)
+ continue
+ }
+ // Else adjacent to a river
+ startRegion = unpickedRegions.filter { tileMap[it.startPosition!!].isAdjacentToRiver() }
+ .maxByOrNull { it.terrainCounts["Coastal"] ?: 0 }
+ if (startRegion != null) {
+ logAssignRegion(true, BiasTypes.Coastal, civ, startRegion)
+ assignCivToRegion(civ, startRegion)
+ unpickedRegions.remove(startRegion)
+ continue
+ }
+ // Else at least close to a river ????
+ startRegion = unpickedRegions.filter { tileMap[it.startPosition!!].neighbors.any { neighbor -> neighbor.isAdjacentToRiver() } }
+ .maxByOrNull { it.terrainCounts["Coastal"] ?: 0 }
+ if (startRegion != null) {
+ logAssignRegion(true, BiasTypes.Coastal, civ, startRegion)
+ assignCivToRegion(civ, startRegion)
+ unpickedRegions.remove(startRegion)
+ continue
+ }
+ // Else pick a random region at the end
+ logAssignRegion(false, BiasTypes.Coastal, civ)
+ randomCivs.add(civ)
+ }
+
+ // Next do positive bias civs
+ for (civ in positiveBiasCivs) {
+ // Try to find a start that matches any of the desired regions, ideally with lots of desired terrain
+ val preferred = civBiases[civ]!!
+ val startRegion = unpickedRegions.filter { it.type in preferred }
+ .maxByOrNull { it.terrainCounts.filterKeys { terrain -> terrain in preferred }.values.sum() }
+ if (startRegion != null) {
+ logAssignRegion(true, BiasTypes.Positive, civ, startRegion)
+ assignCivToRegion(civ, startRegion)
+ unpickedRegions.remove(startRegion)
+ continue
+ } else if (preferred.size == 1) { // Civs with a single bias (only) get to look for a fallback region
+ positiveBiasFallbackCivs.add(civ)
+ } else { // Others get random starts
+ logAssignRegion(false, BiasTypes.Positive, civ)
+ randomCivs.add(civ)
+ }
+ }
+
+ // Do a second pass for fallback civs, choosing the region most similar to the desired type
+ for (civ in positiveBiasFallbackCivs) {
+ val startRegion = getFallbackRegion(civBiases[civ]!!.first(), unpickedRegions)
+ logAssignRegion(true, BiasTypes.PositiveFallback, civ, startRegion)
+ assignCivToRegion(civ, startRegion)
+ unpickedRegions.remove(startRegion)
+ }
+
+ // Next do negative bias ones (ie "Avoid []")
+ for (civ in negativeBiasCivs) {
+ val (avoidBias, preferred) = civBiases[civ]!!
+ .partition { bias -> bias.equalsPlaceholderText("Avoid []") }
+ val avoided = avoidBias.map { it.getPlaceholderParameters()[0] }
+ // Try to find a region not of the avoided types, secondary sort by
+ // least number of undesired terrains (weighed double) / most number of desired terrains
+ val startRegion = unpickedRegions.filterNot { it.type in avoided }
+ .minByOrNull {
+ 2 * it.terrainCounts.filterKeys { terrain -> terrain in avoided }.values.sum()
+ - it.terrainCounts.filterKeys { terrain -> terrain in preferred }.values.sum()
+ }
+ if (startRegion != null) {
+ logAssignRegion(true, BiasTypes.Negative, civ, startRegion)
+ assignCivToRegion(civ, startRegion)
+ unpickedRegions.remove(startRegion)
+ continue
+ } else {
+ logAssignRegion(false, BiasTypes.Negative, civ)
+ randomCivs.add(civ) // else pick a random region at the end
+ }
+ }
+
+ // Finally assign the remaining civs randomly
+ for (civ in randomCivs) {
+ // throws if regions.size < civilizations.size or if the assigning mismatched - leads to popup on newgame screen
+ val startRegion = unpickedRegions.random()
+ logAssignRegion(true, BiasTypes.Random, civ, startRegion)
+ assignCivToRegion(civ, startRegion)
+ unpickedRegions.remove(startRegion)
+ }
+ }
+
+ /** Sets region.type */
+ private fun assignRegionTypes() {
+ val regionTypes = ruleset.terrains.values.filter { getRegionPriority(it) != null }
+ .sortedBy { getRegionPriority(it) }
+
+ for (region in regions) {
+ region.countTerrains()
+
+ for (type in regionTypes) {
+ // Test exclusion criteria first
+ if (type.getMatchingUniques(UniqueType.RegionRequireFirstLessThanSecond).any {
+ region.getTerrainAmount(it.params[0]) >= region.getTerrainAmount(it.params[1])
+ }) {
+ continue
+ }
+ // Test inclusion criteria
+ if (type.getMatchingUniques(UniqueType.RegionRequirePercentSingleType).any {
+ region.getTerrainAmount(it.params[1]) >= (it.params[0].toInt() * region.tiles.size) / 100
+ }
+ || type.getMatchingUniques(UniqueType.RegionRequirePercentTwoTypes).any {
+ region.getTerrainAmount(it.params[1]) + region.getTerrainAmount(it.params[2]) >= (it.params[0].toInt() * region.tiles.size) / 100
+ }
+ ) {
+ region.type = type.name
+ break
+ }
+ }
+ }
+ }
+
+ private fun logAssignRegion(success: Boolean, startBiasType: BiasTypes, civ: Civilization, region: Region? = null) {
+ if (Log.backend.isRelease()) return
+
+ val logCiv = { civ.civName + " " + ruleset.nations[civ.civName]!!.startBias.joinToString(",", "(", ")") }
+ val msg = if (success) "(%s): %s to %s"
+ else "no region (%s) found for %s"
+ Log.debug(Tag("assignRegions"), msg, startBiasType, logCiv, region)
+ }
+
+
+ private fun assignCivToRegion(civ: Civilization, region: Region) {
+ val tile = region.tileMap[region.startPosition!!]
+ region.tileMap.addStartingLocation(civ.civName, tile)
+
+ // Place impacts to keep city states etc at appropriate distance
+ tileData.placeImpact(ImpactType.MinorCiv,tile, 6)
+ tileData.placeImpact(ImpactType.Luxury, tile, 3)
+ tileData.placeImpact(ImpactType.Strategic,tile, 0)
+ tileData.placeImpact(ImpactType.Bonus, tile, 3)
+ }
+
+ /** Attempts to find a good start close to the center of [region]. Calls setRegionStart with the position*/
+ private fun findStart(region: Region) {
+ // Establish center bias rects
+ val centerRect = getCentralRectangle(region.rect, 0.33f)
+ val middleRect = getCentralRectangle(region.rect, 0.67f)
+
+ // Priority: 1. Adjacent to river, 2. Adjacent to coast or fresh water, 3. Other.
+ // First check center rect, then middle. Only check the outer area if no good sites found
+ val riverTiles = HashSet()
+ val wetTiles = HashSet()
+ val dryTiles = HashSet()
+ val fallbackTiles = HashSet()
+
+ // First check center
+ val centerTiles = region.tileMap.getTilesInRectangle(centerRect)
+ for (tile in centerTiles) {
+ if (tileData[tile.position]!!.isTwoFromCoast)
+ continue // Don't even consider tiles two from coast
+ if (region.continentID != -1 && region.continentID != tile.getContinent())
+ continue // Wrong continent
+ if (tile.isLand && !tile.isImpassible()) {
+ evaluateTileForStart(tile)
+ if (tile.isAdjacentToRiver())
+ riverTiles.add(tile.position)
+ else if (tile.isCoastalTile() || tile.isAdjacentTo(Constants.freshWater))
+ wetTiles.add(tile.position)
+ else
+ dryTiles.add(tile.position)
+ }
+ }
+ // Did we find a good start position?
+ for (list in sequenceOf(riverTiles, wetTiles, dryTiles)) {
+ if (list.any { tileData[it]!!.isGoodStart }) {
+ setRegionStart(region, list
+ .filter { tileData[it]!!.isGoodStart }.maxByOrNull { tileData[it]!!.startScore }!!)
+ return
+ }
+ if (list.isNotEmpty()) // Save the best not-good-enough spots for later fallback
+ fallbackTiles.add(list.maxByOrNull { tileData[it]!!.startScore }!!)
+ }
+
+ // Now check middle donut
+ val middleDonut = region.tileMap.getTilesInRectangle(middleRect).filterNot { it in centerTiles }
+ riverTiles.clear()
+ wetTiles.clear()
+ dryTiles.clear()
+ for (tile in middleDonut) {
+ if (tileData[tile.position]!!.isTwoFromCoast)
+ continue // Don't even consider tiles two from coast
+ if (region.continentID != -1 && region.continentID != tile.getContinent())
+ continue // Wrong continent
+ if (tile.isLand && !tile.isImpassible()) {
+ evaluateTileForStart(tile)
+ if (tile.isAdjacentToRiver())
+ riverTiles.add(tile.position)
+ else if (tile.isCoastalTile() || tile.isAdjacentTo(Constants.freshWater))
+ wetTiles.add(tile.position)
+ else
+ dryTiles.add(tile.position)
+ }
+ }
+ // Did we find a good start position?
+ for (list in sequenceOf(riverTiles, wetTiles, dryTiles)) {
+ if (list.any { tileData[it]!!.isGoodStart }) {
+ setRegionStart(region, list
+ .filter { tileData[it]!!.isGoodStart }.maxByOrNull { tileData[it]!!.startScore }!!)
+ return
+ }
+ if (list.isNotEmpty()) // Save the best not-good-enough spots for later fallback
+ fallbackTiles.add(list.maxByOrNull { tileData[it]!!.startScore }!!)
+ }
+
+ // Now check the outer tiles. For these we don't care about rivers, coasts etc
+ val outerDonut = region.tileMap.getTilesInRectangle(region.rect).filterNot { it in centerTiles || it in middleDonut}
+ dryTiles.clear()
+ for (tile in outerDonut) {
+ if (region.continentID != -1 && region.continentID != tile.getContinent())
+ continue // Wrong continent
+ if (tile.isLand && !tile.isImpassible()) {
+ evaluateTileForStart(tile)
+ dryTiles.add(tile.position)
+ }
+ }
+ // Were any of them good?
+ if (dryTiles.any { tileData[it]!!.isGoodStart }) {
+ // Find the one closest to the center
+ val center = region.rect.getCenter(Vector2())
+ setRegionStart(region,
+ dryTiles.filter { tileData[it]!!.isGoodStart }.minByOrNull {
+ (region.tileMap.getIfTileExistsOrNull(center.x.roundToInt(), center.y.roundToInt()) ?: region.tileMap.values.first())
+ .aerialDistanceTo(
+ region.tileMap.getIfTileExistsOrNull(it.x.toInt(), it.y.toInt()) ?: region.tileMap.values.first()
+ ) }!!)
+ return
+ }
+ if (dryTiles.isNotEmpty())
+ fallbackTiles.add(dryTiles.maxByOrNull { tileData[it]!!.startScore }!!)
+
+ // Fallback time. Just pick the one with best score
+ val fallbackPosition = fallbackTiles.maxByOrNull { tileData[it]!!.startScore }
+ if (fallbackPosition != null) {
+ setRegionStart(region, fallbackPosition)
+ return
+ }
+
+ // Something went extremely wrong and there is somehow no place to start. Spawn some land and start there
+ val panicPosition = region.rect.getPosition(Vector2())
+ val panicTerrain = ruleset.terrains.values.first { it.type == TerrainType.Land }.name
+ region.tileMap[panicPosition].baseTerrain = panicTerrain
+ region.tileMap[panicPosition].setTerrainFeatures(listOf())
+ setRegionStart(region, panicPosition)
+ }
+
+ /** @returns the region most similar to a region of [type] */
+ private fun getFallbackRegion(type: String, candidates: List): Region {
+ return candidates.maxByOrNull { it.terrainCounts[type] ?: 0 }!!
+ }
+
+ private fun setRegionStart(region: Region, position: Vector2) {
+ region.startPosition = position
+ setCloseStartPenalty(region.tileMap[position])
+ }
+
+ /** @returns a scaled according to [proportion] Rectangle centered over [originalRect] */
+ private fun getCentralRectangle(originalRect: Rectangle, proportion: Float): Rectangle {
+ val scaledRect = Rectangle(originalRect)
+
+ scaledRect.width = (originalRect.width * proportion)
+ scaledRect.height = (originalRect.height * proportion)
+ scaledRect.x = originalRect.x + (originalRect.width - scaledRect.width) / 2
+ scaledRect.y = originalRect.y + (originalRect.height - scaledRect.height) / 2
+
+ // round values
+ scaledRect.x = scaledRect.x.roundToInt().toFloat()
+ scaledRect.y = scaledRect.y.roundToInt().toFloat()
+ scaledRect.width = scaledRect.width.roundToInt().toFloat()
+ scaledRect.height = scaledRect.height.roundToInt().toFloat()
+
+ return scaledRect
+ }
+
+ private fun setCloseStartPenalty(tile: Tile) {
+ for ((ring, penalty) in closeStartPenaltyForRing) {
+ for (outerTile in tile.getTilesAtDistance(ring).map { it.position })
+ tileData[outerTile]!!.addCloseStartPenalty(penalty)
+ }
+ }
+
+ /** Evaluates a tile for starting position, setting isGoodStart and startScore in
+ * MapGenTileData. Assumes that all tiles have corresponding MapGenTileData. */
+ private fun evaluateTileForStart(tile: Tile) {
+ val localData = tileData[tile.position]!!
+
+ var totalFood = 0
+ var totalProd = 0
+ var totalGood = 0
+ var totalJunk = 0
+ var totalRivers = 0
+ var totalScore = 0
+
+ if (tile.isCoastalTile()) totalScore += 40
+
+ // Go through all rings
+ for (ring in 1..3) {
+ // Sum up the values for this ring
+ for (outerTile in tile.getTilesAtDistance(ring)) {
+ val outerTileData = tileData[outerTile.position]!!
+ if (outerTileData.isJunk)
+ totalJunk++
+ else {
+ if (outerTileData.isFood) totalFood++
+ if (outerTileData.isProd) totalProd++
+ if (outerTileData.isGood) totalGood++
+ if (outerTile.isAdjacentToRiver()) totalRivers++
+ }
+ }
+ // Check for minimum levels. We still keep on calculating final score in case of failure
+ if (totalFood < minimumFoodForRing[ring]!!
+ || totalProd < minimumProdForRing[ring]!!
+ || totalGood < minimumGoodForRing[ring]!!) {
+ localData.isGoodStart = false
+ }
+
+ // Ring-specific scoring
+ when (ring) {
+ 1 -> {
+ val foodScore = firstRingFoodScores[totalFood]
+ val prodScore = firstRingProdScores[totalProd]
+ totalScore += foodScore + prodScore + totalRivers
+ + (totalGood * 2) - (totalJunk * 3)
+ }
+ 2 -> {
+ val foodScore = if (totalFood > 10) secondRingFoodScores.last()
+ else secondRingFoodScores[totalFood]
+ val effectiveTotalProd = if (totalProd >= totalFood * 2) totalProd
+ else (totalFood + 1) / 2 // Can't use all that production without food
+ val prodScore = if (effectiveTotalProd > 5) secondRingProdScores.last()
+ else secondRingProdScores[effectiveTotalProd]
+ totalScore += foodScore + prodScore + totalRivers
+ + (totalGood * 2) - (totalJunk * 3)
+ }
+ else -> {
+ totalScore += totalFood + totalProd + totalGood + totalRivers - (totalJunk * 2)
+ }
+ }
+ }
+ // Too much junk?
+ if (totalJunk > maximumJunk) {
+ localData.isGoodStart = false
+ }
+
+ // Finally check if this is near another start
+ if (localData.closeStartPenalty > 0) {
+ localData.isGoodStart = false
+ totalScore -= (totalScore * localData.closeStartPenalty) / 100
+ }
+ localData.startScore = totalScore
+ }
+
+ fun placeResourcesAndMinorCivs(tileMap: TileMap, minorCivs: List) {
+ placeNaturalWonderImpacts(tileMap)
+
+ val (cityStateLuxuries, randomLuxuries) = LuxuryResourcePlacementLogic.assignLuxuries(regions, tileData, ruleset)
+ MinorCivPlacer.placeMinorCivs(regions, tileMap, minorCivs, usingArchipelagoRegions, tileData, ruleset)
+ LuxuryResourcePlacementLogic.placeLuxuries(regions, tileMap, tileData, ruleset, cityStateLuxuries, randomLuxuries)
+ placeStrategicAndBonuses(tileMap)
+ }
+
+ /** Places impacts from NWs that have been generated just prior to this step. */
+ private fun placeNaturalWonderImpacts(tileMap: TileMap) {
+ for (tile in tileMap.values.filter { it.isNaturalWonder() }) {
+ tileData.placeImpact(ImpactType.Bonus, tile, 1)
+ tileData.placeImpact(ImpactType.Strategic, tile, 1)
+ tileData.placeImpact(ImpactType.Luxury, tile, 1)
+ tileData.placeImpact(ImpactType.MinorCiv, tile, 1)
+ }
+ }
+
+
+ private fun placeStrategicAndBonuses(tileMap: TileMap) {
+ val strategicResources = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Strategic }
+ // As usual, if there are any relevant json definitions, assume they are complete
+ val fallbackStrategic = ruleset.tileResources.values.none {
+ it.resourceType == ResourceType.Strategic &&
+ it.uniqueObjects.any { unique -> unique.isOfType(UniqueType.ResourceWeighting) } ||
+ it.uniqueObjects.any { unique -> unique.isOfType(UniqueType.MinorDepositWeighting) }
+ }
+ /* There are a couple competing/complementary distribution systems at work here. First, major
+ deposits are placed according to a frequency defined in the terrains themselves, for each
+ tile that is eligible to get a major deposit, there is a weighted random choice between
+ resource types.
+ Minor deposits are placed by randomly picking a number of land tiles from anywhere on the
+ map (so not stratified by terrain type) and assigning a weighted randomly picked resource.
+ Bonuses are placed according to a frequency for a rule like "every 8 jungle hills", here
+ implemented as a conditional.
+
+ We need to build lists of all tiles following a given rule to place these, which is BY FAR
+ the most expensive calculation in this entire class. To save some time we anonymize the
+ uniques so we only have to make one list for each set of conditionals, so eg Wheat and
+ Horses can share a list since they are both interested in Featureless Plains.
+ We also save a list of all land tiles for minor deposit generation. */
+
+ // Determines number tiles per resource
+ val bonusMultiplier = when (tileMap.mapParameters.mapResources) {
+ MapResources.sparse -> 1.5f
+ MapResources.abundant -> 0.6667f
+ else -> 1f
+ }
+ val landList = ArrayList() // For minor deposits
+ val ruleLists = HashMap>() // For rule-based generation
+
+ // Figure out which rules (sets of conditionals) need lists built
+ for (resource in ruleset.tileResources.values.filter {
+ it.resourceType == ResourceType.Strategic ||
+ it.resourceType == ResourceType.Bonus }) {
+ for (rule in resource.uniqueObjects.filter { unique ->
+ unique.isOfType(UniqueType.ResourceFrequency) ||
+ unique.isOfType(UniqueType.ResourceWeighting) ||
+ unique.isOfType(UniqueType.MinorDepositWeighting) }) {
+ // Weed out some clearly impossible rules straight away to save time later
+ if (rule.conditionals.any { conditional ->
+ (conditional.isOfType(UniqueType.ConditionalOnWaterMaps) && !usingArchipelagoRegions) ||
+ (conditional.isOfType(UniqueType.ConditionalInRegionOfType) && regions.none { region -> region.type == conditional.params[0] }) ||
+ (conditional.isOfType(UniqueType.ConditionalInRegionExceptOfType) && regions.all { region -> region.type == conditional.params[0] })
+ } )
+ continue
+ val simpleRule = anonymizeUnique(rule)
+ if (ruleLists.keys.none { it.text == simpleRule.text }) // Need to do text comparison since the uniques will not be equal otherwise
+ ruleLists[simpleRule] = ArrayList()
+ }
+ }
+ // Make up some rules for placing strategics in a fallback situation
+ if (fallbackStrategic) {
+ val interestingTerrains = strategicResources.flatMap { it.terrainsCanBeFoundOn }.map { ruleset.terrains[it]!! }.toSet()
+ for (terrain in interestingTerrains) {
+ val fallbackRule = if (terrain.type == TerrainType.TerrainFeature)
+ Unique("RULE ")
+ else
+ Unique("RULE ")
+ if (ruleLists.keys.none { it.text == fallbackRule.text }) // Need to do text comparison since the uniques will not be equal otherwise
+ ruleLists[fallbackRule] = ArrayList()
+ }
+ }
+ // Now go through the entire map to build lists
+ for (tile in tileMap.values.asSequence().shuffled()) {
+ val terrainCondition = StateForConditionals(attackedTile = tile, region = regions.firstOrNull { tile in it.tiles })
+ if (tile.getBaseTerrain().hasUnique(UniqueType.BlocksResources, terrainCondition))
+ continue // Don't count snow hills
+ if (tile.isLand)
+ landList.add(tile)
+ for ((rule, list) in ruleLists) {
+ if (rule.conditionalsApply(terrainCondition)) {
+ list.add(tile)
+ }
+ }
+ }
+ // Keep track of total placed strategic resources in case we need to top them up later
+ val totalPlaced = HashMap()
+ strategicResources.forEach { totalPlaced[it] = 0 }
+
+ // First place major deposits on land
+ for (terrain in ruleset.terrains.values.filter { it.type != TerrainType.Water }) {
+ // Figure out if we generated a list for this terrain
+ val terrainRule = getTerrainRule(terrain, ruleset)
+ val list = ruleLists.filterKeys { it.text == terrainRule.text }.values.firstOrNull()
+ ?: continue // If not the terrain can be safely skipped
+ totalPlaced += MapRegionResources.placeMajorDeposits(tileData, ruleset, list, terrain, fallbackStrategic, 2, 2)
+ }
+
+ // Second add some small deposits of modern strategic resources to city states
+ val lastEra = ruleset.eras.values.maxOf { it.eraNumber }
+ val modernOptions = strategicResources.filter {
+ it.revealedBy != null &&
+ ruleset.eras[ruleset.technologies[it.revealedBy]!!.era()]!!.eraNumber >= lastEra / 2
+ }
+
+ if (modernOptions.any())
+ for (cityStateLocation in tileMap.startingLocationsByNation
+ .filterKeys { ruleset.nations[it]!!.isCityState }.values.map { it.first() }) {
+ val resourceToPlace = modernOptions.random()
+ totalPlaced[resourceToPlace] =
+ totalPlaced[resourceToPlace]!! + MapRegionResources.tryAddingResourceToTiles(tileData, resourceToPlace, 1, cityStateLocation.getTilesInDistanceRange(1..3))
+ }
+
+ // Third add some minor deposits to land tiles
+ // Note: In G&K there is a bug where minor deposits are never placed on hills. We're not replicating that.
+ val frequency = (baseMinorDepositFrequency * bonusMultiplier).toInt()
+ val minorDepositsToAdd = (landList.size / frequency) + 1 // I sometimes have division by zero errors on this line
+ var minorDepositsAdded = 0
+ for (tile in landList) {
+ if (tile.resource != null || tileData[tile.position]!!.impacts.containsKey(ImpactType.Strategic))
+ continue
+ val conditionalTerrain = StateForConditionals(attackedTile = tile)
+ if (tile.getBaseTerrain().hasUnique(UniqueType.BlocksResources, conditionalTerrain))
+ continue
+ val weightings = strategicResources.map {
+ if (fallbackStrategic) {
+ if (tile.lastTerrain.name in it.terrainsCanBeFoundOn) 1f else 0f
+ } else {
+ val uniques = it.getMatchingUniques(UniqueType.MinorDepositWeighting, conditionalTerrain).toList()
+ uniques.sumOf { unique -> unique.params[0].toInt() }.toFloat()
+ }
+ }
+ if (weightings.sum() <= 0) {
+ continue
+ }
+ val resourceToPlace = strategicResources.randomWeighted(weightings)
+ tile.setTileResource(resourceToPlace, majorDeposit = false)
+ tileData.placeImpact(ImpactType.Strategic, tile, Random.nextInt(2) + Random.nextInt(2))
+ totalPlaced[resourceToPlace] = totalPlaced[resourceToPlace]!! + 1
+ minorDepositsAdded++
+ if (minorDepositsAdded >= minorDepositsToAdd)
+ break
+ }
+
+ // Fourth add water-based major deposits. Extra impact because we don't want them too clustered and there is usually lots to go around
+ for (terrain in ruleset.terrains.values.filter { it.type == TerrainType.Water }) {
+ // Figure out if we generated a list for this terrain
+ val list = ruleLists.filterKeys { it.text == getTerrainRule(terrain, ruleset).text }.values.firstOrNull()
+ ?: continue // If not the terrain can be safely skipped
+ totalPlaced += MapRegionResources.placeMajorDeposits(tileData, ruleset, list, terrain, fallbackStrategic, 4, 3)
+ }
+
+ // Fifth place up to 2 extra deposits of each resource type if there is < 1 per civ
+ for (resource in strategicResources) {
+ val extraNeeded = min(2, regions.size - totalPlaced[resource]!!)
+ if (extraNeeded > 0) {
+ if (isWaterOnlyResource(resource, ruleset))
+ MapRegionResources.tryAddingResourceToTiles(tileData, resource, extraNeeded, tileMap.values.asSequence().filter { it.isWater }.shuffled(), respectImpacts = true)
+ else
+ MapRegionResources.tryAddingResourceToTiles(tileData, resource, extraNeeded, landList.asSequence(), respectImpacts = true)
+ }
+ }
+
+ // Figure out if bonus generation rates are defined in json. Assume that if there are any, the definitions are complete.
+ val fallbackBonuses = ruleset.tileResources.values.none { it.uniqueObjects.any { unique -> unique.type == UniqueType.ResourceFrequency } }
+
+ // Sixth place bonus resources (and other resources that might have been assigned frequency-based generation).
+ // Water-based bonuses go last and have extra impact, because coasts are very common and we don't want too much clustering
+ val sortedResourceList = ruleset.tileResources.values.sortedBy { isWaterOnlyResource(it, ruleset) }
+ for (resource in sortedResourceList) {
+ val extraImpact = if (isWaterOnlyResource(resource, ruleset)) 1 else 0
+ for (rule in resource.uniqueObjects.filter { it.type == UniqueType.ResourceFrequency }) {
+ // Figure out which list applies, if any
+ val simpleRule = anonymizeUnique(rule)
+ val list = ruleLists.filterKeys { it.text == simpleRule.text }.values.firstOrNull()
+ // If there is no matching list, it is because the rule was determined to be impossible and so can be safely skipped
+ ?: continue
+ // Place the resources
+ MapRegionResources.placeResourcesInTiles(tileData, (rule.params[0].toFloat() * bonusMultiplier).toInt(), list, listOf(resource), 0 + extraImpact, 2 + extraImpact, false)
+ }
+ if(fallbackBonuses && resource.resourceType == ResourceType.Bonus) {
+ // Since we haven't been able to generate any rule-based lists, just generate new ones on the fly
+ // Increase impact to avoid clustering since there is no terrain type stratification.
+ val fallbackList = tileMap.values.filter { it.lastTerrain.name in resource.terrainsCanBeFoundOn }.shuffled()
+ MapRegionResources.placeResourcesInTiles(tileData, (20 * bonusMultiplier).toInt(), fallbackList, listOf(resource), 2 + extraImpact, 2 + extraImpact, false)
+ }
+ }
+
+ // Seventh (and finally!) place an extra bonus in the THIRD ring of each start to make it slightly more attractive
+ for (region in regions) {
+ val terrain = if (region.type == "Hybrid") region.terrainCounts.filterNot { it.key == "Coastal" }.maxByOrNull { it.value }!!.key
+ else region.type
+ val resourceUnique = ruleset.terrains[terrain]!!.getMatchingUniques(UniqueType.RegionExtraResource).firstOrNull()
+ // If this region has an explicit "this is the bonus" unique go with that, else random appropriate
+ val resource = if (resourceUnique != null) ruleset.tileResources[resourceUnique.params[0]]!!
+ else {
+ val possibleResources =
+ ruleset.tileResources.values.filter { it.resourceType == ResourceType.Bonus && terrain in it.terrainsCanBeFoundOn }
+ if (possibleResources.isEmpty()) continue
+ possibleResources.random()
+ }
+ val candidateTiles = tileMap[region.startPosition!!].getTilesAtDistance(3).shuffled()
+ val amount = if (resourceUnique != null) 2 else 1 // Place an extra if the region type requests it
+ if (MapRegionResources.tryAddingResourceToTiles(tileData, resource, amount, candidateTiles) == 0) {
+ // We couldn't place any, try adding a fish instead
+ val fishyBonus = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Bonus &&
+ it.terrainsCanBeFoundOn.any { terrainName -> ruleset.terrains[terrainName]!!.type == TerrainType.Water } }
+ .randomOrNull()
+ if (fishyBonus != null)
+ MapRegionResources.tryAddingResourceToTiles(tileData, fishyBonus, 1, candidateTiles)
+ }
+ }
+ }
+
+ enum class ImpactType {
+ Strategic,
+ Luxury,
+ Bonus,
+ MinorCiv,
+ }
+
+}
+
+
+fun getRegionPriority(terrain: Terrain?): Int? {
+ if (terrain == null) // ie "hybrid"
+ return 99999 // a big number
+ return if (!terrain.hasUnique(UniqueType.RegionRequirePercentSingleType)
+ && !terrain.hasUnique(UniqueType.RegionRequirePercentTwoTypes))
+ null
+ else
+ if (terrain.hasUnique(UniqueType.RegionRequirePercentSingleType))
+ terrain.getMatchingUniques(UniqueType.RegionRequirePercentSingleType).first().params[2].toInt()
+ else
+ terrain.getMatchingUniques(UniqueType.RegionRequirePercentTwoTypes).first().params[3].toInt()
+}
+
+/** @return a fake unique with the same conditionals, but sorted alphabetically.
+ * Used to save some memory and time when building resource lists. */
+internal fun anonymizeUnique(unique: Unique) = Unique(
+ "RULE" + unique.conditionals.sortedBy { it.text }.joinToString(prefix = " ", separator = " ") { "<" + it.text + ">" })
+
+internal fun isWaterOnlyResource(resource: TileResource, ruleset: Ruleset) = resource.terrainsCanBeFoundOn
+ .all { terrainName -> ruleset.terrains[terrainName]!!.type == TerrainType.Water }
+
+
+/** @return a fake unique with conditionals that will satisfy the same conditions as terrainsCanBeFoundOn */
+internal fun getTerrainRule(terrain: Terrain, ruleset: Ruleset): Unique {
+ return if (terrain.type == TerrainType.TerrainFeature) {
+ if (terrain.hasUnique(UniqueType.VisibilityElevation))
+ Unique("RULE ")
+ else
+ Unique("RULE " + ruleset.terrains.values
+ .filter { it.type == TerrainType.TerrainFeature && it.hasUnique(UniqueType.VisibilityElevation) }
+ .joinToString(separator = " ") { "" })
+ } else
+ Unique("RULE ")
+}
+
diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/MinorCivPlacer.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MinorCivPlacer.kt
new file mode 100644
index 0000000000000..dd3a17a326657
--- /dev/null
+++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MinorCivPlacer.kt
@@ -0,0 +1,240 @@
+package com.unciv.logic.map.mapgenerator.mapregions
+
+import com.unciv.logic.civilization.Civilization
+import com.unciv.logic.map.TileMap
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.ruleset.Ruleset
+import com.unciv.models.ruleset.unique.UniqueType
+import kotlin.math.min
+
+object MinorCivPlacer {
+
+ /** Assigns [civs] to regions or "uninhabited" land and places them. Depends on
+ * assignLuxuries having been called previously.
+ * Note: can silently fail to place all city states if there is too little room.
+ * Currently our GameStarter fills out with random city states, Civ V behavior is to
+ * forget about the discarded city states entirely. */
+ fun placeMinorCivs(regions: List, tileMap: TileMap, civs: List, usingArchipelagoRegions:Boolean, tileData: TileDataMap, ruleset: Ruleset) {
+ if (civs.isEmpty()) return
+
+ // Some but not all city states are assigned to regions directly. Determine the CS density.
+ val unassignedCivs = assignMinorCivsDirectlyToRegions(civs, regions)
+
+ // Some city states are assigned to "uninhabited" continents - unless it's an archipelago type map
+ // (Because then every continent will have been assigned to a region anyway)
+ val uninhabitedCoastal = ArrayList()
+ val uninhabitedHinterland = ArrayList()
+ val civAssignedToUninhabited = ArrayList()
+ if (!usingArchipelagoRegions) {
+ spreadCityStatesBetweenHabitedAndUninhabited(
+ tileMap,
+ regions,
+ tileData,
+ uninhabitedCoastal,
+ uninhabitedHinterland,
+ civs,
+ unassignedCivs,
+ civAssignedToUninhabited
+ )
+ }
+
+ assignCityStatesToRegionsWithCommonLuxuries(regions, unassignedCivs)
+ spreadCityStatesEvenlyBetweenRegions(unassignedCivs, regions)
+ assignRemainingCityStatesToWorstFertileRegions(regions, unassignedCivs)
+
+ // After we've finished assigning, NOW we actually place them
+ placeAssignedMinorCivs(
+ civAssignedToUninhabited,
+ tileMap,
+ uninhabitedCoastal,
+ tileData,
+ ruleset,
+ uninhabitedHinterland,
+ regions
+ )
+ }
+
+ private fun spreadCityStatesBetweenHabitedAndUninhabited(
+ tileMap: TileMap,
+ regions: List,
+ tileData: TileDataMap,
+ uninhabitedCoastal: ArrayList,
+ uninhabitedHinterland: ArrayList,
+ civs: List,
+ unassignedCivs: MutableList,
+ civAssignedToUninhabited: ArrayList
+ ) {
+ val uninhabitedContinents = tileMap.continentSizes.filter {
+ it.value >= 4 && // Don't bother with tiny islands
+ regions.none { region -> region.continentID == it.key }
+ }.keys
+ var numInhabitedTiles = 0
+ var numUninhabitedTiles = 0
+ // Go through the entire map to build the data
+ for (tile in tileMap.values) {
+ if (!canPlaceMinorCiv(tile, tileData)) continue
+ val continent = tile.getContinent()
+ if (continent in uninhabitedContinents) {
+ if (tile.isCoastalTile())
+ uninhabitedCoastal.add(tile)
+ else
+ uninhabitedHinterland.add(tile)
+ numUninhabitedTiles++
+ } else
+ numInhabitedTiles++
+ }
+ // Determine how many minor civs to put on uninhabited continents.
+ val maxByUninhabited =
+ (3 * civs.size * numUninhabitedTiles) / (numInhabitedTiles + numUninhabitedTiles)
+ val maxByRatio = (civs.size + 1) / 2
+ val targetForUninhabited = min(maxByRatio, maxByUninhabited)
+ val civsToAssign = unassignedCivs.take(targetForUninhabited)
+ unassignedCivs.removeAll(civsToAssign)
+ civAssignedToUninhabited.addAll(civsToAssign)
+ }
+
+ /** If there are still unassigned minor civs, assign extra ones to regions that share their
+ * luxury type with two others, as compensation. Because starting close to a city state is good??
+ */
+ private fun assignCityStatesToRegionsWithCommonLuxuries(
+ regions: List,
+ unassignedCivs: MutableList
+ ) {
+ if (unassignedCivs.isEmpty()) return
+ val regionsWithCommonLuxuries = regions.filter {
+ regions.count { other -> other.luxury == it.luxury } >= 3
+ }
+ // assign one civ each to regions with common luxuries if there are enough to go around
+ if (regionsWithCommonLuxuries.isNotEmpty() &&
+ regionsWithCommonLuxuries.size <= unassignedCivs.size
+ ) {
+ regionsWithCommonLuxuries.forEach {
+ val civToAssign = unassignedCivs.first()
+ unassignedCivs.remove(civToAssign)
+ it.assignedMinorCivs.add(civToAssign)
+ }
+ }
+ }
+
+ /** Add one extra to each region as long as there are enough to go around */
+ private fun spreadCityStatesEvenlyBetweenRegions(
+ unassignedCivs: MutableList,
+ regions: List
+ ) {
+ if (unassignedCivs.isEmpty()) return
+ while (unassignedCivs.size >= regions.size) {
+ regions.forEach {
+ val civToAssign = unassignedCivs.first()
+ unassignedCivs.remove(civToAssign)
+ it.assignedMinorCivs.add(civToAssign)
+ }
+ }
+ }
+
+
+ /** At this point there is at least for sure less remaining city states than regions
+ Sort regions by fertility and put extra city states in the worst ones. */
+ private fun assignRemainingCityStatesToWorstFertileRegions(
+ regions: List,
+ unassignedCivs: MutableList
+ ) {
+ if (unassignedCivs.isEmpty()) return
+ val worstRegions = regions.sortedBy { it.totalFertility }.take(unassignedCivs.size)
+ worstRegions.forEach {
+ val civToAssign = unassignedCivs.first()
+ unassignedCivs.remove(civToAssign)
+ it.assignedMinorCivs.add(civToAssign)
+ }
+ }
+
+ /** Actually placee the minor civs, after they have been sorted into groups and assigned to regions */
+ private fun placeAssignedMinorCivs(
+ civAssignedToUninhabited: ArrayList,
+ tileMap: TileMap,
+ uninhabitedCoastal: ArrayList,
+ tileData: TileDataMap,
+ ruleset: Ruleset,
+ uninhabitedHinterland: ArrayList,
+ regions: List
+ ) {
+ // All minor civs are assigned - now place them
+ // First place the "uninhabited continent" ones, preferring coastal starts
+ tryPlaceMinorCivsInTiles(
+ civAssignedToUninhabited, tileMap, uninhabitedCoastal, tileData, ruleset
+ )
+ tryPlaceMinorCivsInTiles(
+ civAssignedToUninhabited, tileMap, uninhabitedHinterland, tileData, ruleset
+ )
+ // Fallback to a random region for civs that couldn't be placed in the wilderness
+ for (unplacedCiv in civAssignedToUninhabited) {
+ regions.random().assignedMinorCivs.add(unplacedCiv)
+ }
+
+ // Now place the ones assigned to specific regions.
+ for (region in regions) {
+ tryPlaceMinorCivsInTiles(
+ region.assignedMinorCivs, tileMap, region.tiles.toMutableList(), tileData, ruleset
+ )
+ }
+ }
+
+ private fun assignMinorCivsDirectlyToRegions(
+ civs: List,
+ regions: List
+ ): MutableList {
+ val minorCivRatio = civs.size.toFloat() / regions.size
+ val minorCivPerRegion = when {
+ minorCivRatio > 14f -> 10 // lol
+ minorCivRatio > 11f -> 8
+ minorCivRatio > 8f -> 6
+ minorCivRatio > 5.7f -> 4
+ minorCivRatio > 4.35f -> 3
+ minorCivRatio > 2.7f -> 2
+ minorCivRatio > 1.35f -> 1
+ else -> 0
+ }
+ val unassignedCivs = civs.shuffled().toMutableList()
+ if (minorCivPerRegion > 0) {
+ regions.forEach {
+ val civsToAssign = unassignedCivs.take(minorCivPerRegion)
+ it.assignedMinorCivs.addAll(civsToAssign)
+ unassignedCivs.removeAll(civsToAssign)
+ }
+ }
+ return unassignedCivs
+ }
+
+ /** Attempts to randomly place civs from [civsToPlace] in tiles from [tileList]. Assumes that
+ * [tileList] is pre-vetted and only contains habitable land tiles.
+ * Will modify both [civsToPlace] and [tileList] as it goes! */
+ private fun tryPlaceMinorCivsInTiles(civsToPlace: MutableList, tileMap: TileMap, tileList: MutableList, tileData: TileDataMap, ruleset: Ruleset) {
+ while (tileList.isNotEmpty() && civsToPlace.isNotEmpty()) {
+ val chosenTile = tileList.random()
+ tileList.remove(chosenTile)
+ val data = tileData[chosenTile.position]!!
+ // If the randomly chosen tile is too close to a player or a city state, discard it
+ if (data.impacts.containsKey(MapRegions.ImpactType.MinorCiv))
+ continue
+ // Otherwise, go ahead and place the minor civ
+ val civToAdd = civsToPlace.first()
+ civsToPlace.remove(civToAdd)
+ placeMinorCiv(civToAdd, tileMap, chosenTile, tileData, ruleset)
+ }
+ }
+
+ private fun canPlaceMinorCiv(tile: Tile, tileData: TileDataMap) = !tile.isWater && !tile.isImpassible() &&
+ !tileData[tile.position]!!.isJunk &&
+ tile.getBaseTerrain().getMatchingUniques(UniqueType.HasQuality).none { it.params[0] == "Undesirable" } && // So we don't get snow hills
+ tile.neighbors.count() == 6 // Avoid map edges
+
+ private fun placeMinorCiv(civ: Civilization, tileMap: TileMap, tile: Tile, tileData: TileDataMap, ruleset: Ruleset) {
+ tileMap.addStartingLocation(civ.civName, tile)
+ tileData.placeImpact(MapRegions.ImpactType.MinorCiv,tile, 4)
+ tileData.placeImpact(MapRegions.ImpactType.Luxury, tile, 3)
+ tileData.placeImpact(MapRegions.ImpactType.Strategic,tile, 0)
+ tileData.placeImpact(MapRegions.ImpactType.Bonus, tile, 3)
+
+ StartNormalizer.normalizeStart(tile, tileMap, tileData, ruleset, isMinorCiv = true)
+ }
+
+}
diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/Region.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/Region.kt
new file mode 100644
index 0000000000000..550e4eaeaf5dc
--- /dev/null
+++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/Region.kt
@@ -0,0 +1,90 @@
+package com.unciv.logic.map.mapgenerator.mapregions
+
+import com.badlogic.gdx.math.Rectangle
+import com.badlogic.gdx.math.Vector2
+import com.unciv.logic.civilization.Civilization
+import com.unciv.logic.map.TileMap
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.ruleset.unique.UniqueType
+import kotlin.math.max
+import kotlin.math.min
+
+class Region (val tileMap: TileMap, val rect: Rectangle, val continentID: Int = -1) {
+ val tiles = HashSet()
+ val terrainCounts = HashMap()
+ var totalFertility = 0
+ var type = "Hybrid" // being an undefined or indeterminate type
+ var luxury: String? = null
+ var startPosition: Vector2? = null
+ val assignedMinorCivs = ArrayList()
+
+ var affectedByWorldWrap = false
+
+ /** Recalculates tiles and fertility */
+ fun updateTiles(trim: Boolean = true) {
+ totalFertility = 0
+ var minColumn = 99999f
+ var maxColumn = -99999f
+ var minRow = 99999f
+ var maxRow = -99999f
+
+ val columnHasTile = HashSet()
+
+ tiles.clear()
+ for (tile in tileMap.getTilesInRectangle(rect).filter {
+ continentID == -1 || it.getContinent() == continentID } ) {
+ val fertility = tile.getTileFertility(continentID != -1)
+ tiles.add(tile)
+ totalFertility += fertility
+
+ if (affectedByWorldWrap)
+ columnHasTile.add(tile.getColumn())
+
+ if (trim) {
+ val row = tile.getRow().toFloat()
+ val column = tile.getColumn().toFloat()
+ minColumn = min(minColumn, column)
+ maxColumn = max(maxColumn, column)
+ minRow = min(minRow, row)
+ maxRow = max(maxRow, row)
+ }
+ }
+
+ if (trim) {
+ if (affectedByWorldWrap) // Need to be more thorough with origin longitude
+ rect.x = columnHasTile.filter { !columnHasTile.contains(it - 1) }.maxOf { it }.toFloat()
+ else
+ rect.x = minColumn // ez way for non-wrapping regions
+ rect.y = minRow
+ rect.height = maxRow - minRow + 1
+ if (affectedByWorldWrap && minColumn < rect.x) { // Thorough way
+ rect.width = columnHasTile.size.toFloat()
+ } else {
+ rect.width = maxColumn - minColumn + 1 // ez way
+ affectedByWorldWrap = false // also we're not wrapping anymore
+ }
+ }
+ }
+
+ /** Counts the terrains in the Region for type and start determination */
+ fun countTerrains() {
+ // Count terrains in the region
+ terrainCounts.clear()
+ for (tile in tiles) {
+ val terrainsToCount = if (tile.terrainHasUnique(UniqueType.IgnoreBaseTerrainForRegion))
+ tile.terrainFeatureObjects.map { it.name }.asSequence()
+ else
+ tile.allTerrains.map { it.name }
+ for (terrain in terrainsToCount) {
+ terrainCounts[terrain] = (terrainCounts[terrain] ?: 0) + 1
+ }
+ if (tile.isCoastalTile())
+ terrainCounts["Coastal"] = (terrainCounts["Coastal"] ?: 0) + 1
+ }
+ }
+
+ /** Returns number terrains with [name] */
+ fun getTerrainAmount(name: String) = terrainCounts[name] ?: 0
+
+ override fun toString() = "Region($type, ${tiles.size} tiles, ${terrainCounts.entries.joinToString { "${it.value} ${it.key}" }})"
+}
diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/StartNormalizer.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/StartNormalizer.kt
new file mode 100644
index 0000000000000..814b9b05b190e
--- /dev/null
+++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/StartNormalizer.kt
@@ -0,0 +1,322 @@
+package com.unciv.logic.map.mapgenerator.mapregions
+
+import com.unciv.Constants
+import com.unciv.logic.map.MapResources
+import com.unciv.logic.map.TileMap
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.ruleset.Ruleset
+import com.unciv.models.ruleset.tile.ResourceType
+import com.unciv.models.ruleset.tile.TerrainType
+import com.unciv.models.ruleset.unique.StateForConditionals
+import com.unciv.models.ruleset.unique.UniqueType
+import com.unciv.models.stats.Stat
+import kotlin.math.abs
+import kotlin.math.pow
+
+/** Ensures that starting positions of civs have enough yield that they aren't at a disadvantage */
+object StartNormalizer {
+
+ /** Attempts to improve the start on [startTile] as needed to make it decent.
+ * Relies on startPosition having been set previously.
+ * Assumes unchanged baseline values ie citizens eat 2 food each, similar production costs
+ * If [isMinorCiv] is true, different weightings will be used. */
+ fun normalizeStart(startTile: Tile, tileMap: TileMap, tileData: TileDataMap, ruleset: Ruleset, isMinorCiv: Boolean) {
+ // Remove ice-like features adjacent to start
+ for (tile in startTile.neighbors) {
+ val lastTerrain = tile.terrainFeatureObjects.lastOrNull { it.impassable }
+ if (lastTerrain != null) {
+ tile.removeTerrainFeature(lastTerrain.name)
+ }
+ }
+
+ if (tileMap.mapParameters.mapResources == MapResources.strategicBalance)
+ placeStrategicBalanceResources(startTile, ruleset, tileData)
+
+ normalizeProduction(startTile, isMinorCiv, ruleset, tileData)
+
+ val foodBonusesNeeded = calculateFoodBonusesNeeded(startTile, isMinorCiv, ruleset, tileMap)
+ placeFoodBonuses(isMinorCiv, startTile, ruleset, foodBonusesNeeded)
+
+ // Minor civs are done, go on with grassiness checks for major civs
+ if (isMinorCiv) return
+
+ addProductionBonuses(startTile, ruleset)
+ }
+
+ private fun normalizeProduction(
+ startTile: Tile,
+ isMinorCiv: Boolean,
+ ruleset: Ruleset,
+ tileData: TileDataMap
+ ) {
+ // evaluate production potential
+ val innerProduction =
+ startTile.neighbors.sumOf { getPotentialYield(it, Stat.Production).toInt() }
+ val outerProduction =
+ startTile.getTilesAtDistance(2).sumOf { getPotentialYield(it, Stat.Production).toInt() }
+ // for very early production we ideally want tiles that also give food
+ val earlyProduction = startTile.getTilesInDistanceRange(1..2).sumOf {
+ if (getPotentialYield(it, Stat.Food, unimproved = true) > 0f) getPotentialYield(
+ it,
+ Stat.Production,
+ unimproved = true
+ ).toInt()
+ else 0
+ }
+
+ // If terrible, try adding a hill to a dry flat tile
+ if (innerProduction == 0 || (innerProduction < 2 && outerProduction < 8) || (isMinorCiv && innerProduction < 4)) {
+ val hillSpot = startTile.neighbors
+ .filter { it.isLand && it.terrainFeatures.isEmpty() && !it.isAdjacentTo(Constants.freshWater) && !it.isImpassible() }
+ .toList().randomOrNull()
+ val hillEquivalent = ruleset.terrains.values
+ .firstOrNull {
+ it.type == TerrainType.TerrainFeature && it.production >= 2 && !it.hasUnique(
+ UniqueType.RareFeature
+ )
+ }?.name
+ if (hillSpot != null && hillEquivalent != null) {
+ hillSpot.addTerrainFeature(hillEquivalent)
+ }
+ }
+
+ // If bad early production, add a small strategic resource to SECOND ring (not for minors)
+ if (!isMinorCiv && innerProduction < 3 && earlyProduction < 6) {
+ val lastEraNumber = ruleset.eras.values.maxOf { it.eraNumber }
+ val earlyEras = ruleset.eras.filterValues { it.eraNumber <= lastEraNumber / 3 }
+ val validResources = ruleset.tileResources.values.filter {
+ it.resourceType == ResourceType.Strategic &&
+ (it.revealedBy == null ||
+ ruleset.technologies[it.revealedBy]!!.era() in earlyEras)
+ }.shuffled()
+ val candidateTiles = startTile.getTilesAtDistance(2).shuffled()
+ for (resource in validResources) {
+ val resourcesAdded = MapRegionResources.tryAddingResourceToTiles(
+ tileData, resource, 1, candidateTiles, majorDeposit = false)
+ if (resourcesAdded > 0) break
+ }
+ }
+ }
+
+ private fun placeStrategicBalanceResources(
+ startTile: Tile,
+ ruleset: Ruleset,
+ tileData: TileDataMap
+ ) {
+ val candidateTiles =
+ startTile.getTilesInDistanceRange(1..2).shuffled() + startTile.getTilesAtDistance(3)
+ .shuffled()
+ for (resource in ruleset.tileResources.values.filter { it.hasUnique(UniqueType.StrategicBalanceResource) }) {
+ if (MapRegionResources.tryAddingResourceToTiles(
+ tileData,
+ resource,
+ 1,
+ candidateTiles,
+ majorDeposit = true
+ ) == 0
+ ) {
+ // Fallback mode - force placement, even on an otherwise inappropriate terrain. Do still respect water and impassible tiles!
+ val resourceTiles =
+ if (isWaterOnlyResource(
+ resource,
+ ruleset
+ )
+ ) candidateTiles.filter { it.isWater && !it.isImpassible() }.toList()
+ else candidateTiles.filter { it.isLand && !it.isImpassible() }.toList()
+ MapRegionResources.placeResourcesInTiles(
+ tileData,
+ 999,
+ resourceTiles,
+ listOf(resource),
+ majorDeposit = true,
+ forcePlacement = true
+ )
+ }
+ }
+ }
+
+ /** Check for very food-heavy starts that might still need some stone to help with production */
+ private fun addProductionBonuses(startTile: Tile, ruleset: Ruleset) {
+ val grassTypePlots = startTile.getTilesInDistanceRange(1..2).filter {
+ it.isLand &&
+ getPotentialYield(it, Stat.Food, unimproved = true) >= 2f && // Food neutral natively
+ getPotentialYield(it, Stat.Production) == 0f // Production can't even be improved
+ }.toMutableList()
+ val plainsTypePlots = startTile.getTilesInDistanceRange(1..2).filter {
+ it.isLand &&
+ getPotentialYield(it, Stat.Food) >= 2f && // Something that can be improved to food neutral
+ getPotentialYield(it, Stat.Production, unimproved = true) >= 1f // Some production natively
+ }.toList()
+ var productionBonusesNeeded = when {
+ grassTypePlots.size >= 9 && plainsTypePlots.isEmpty() -> 2
+ grassTypePlots.size >= 6 && plainsTypePlots.size <= 4 -> 1
+ else -> 0
+ }
+ val productionBonuses =
+ ruleset.tileResources.values.filter { it.resourceType == ResourceType.Bonus && it.production > 0 }
+
+ if (productionBonuses.isNotEmpty()) {
+ while (productionBonusesNeeded > 0 && grassTypePlots.isNotEmpty()) {
+ val plot = grassTypePlots.random()
+ grassTypePlots.remove(plot)
+
+ if (plot.resource != null) continue
+
+ val bonusToPlace =
+ productionBonuses.filter { plot.lastTerrain.name in it.terrainsCanBeFoundOn }
+ .randomOrNull()
+ if (bonusToPlace != null) {
+ plot.resource = bonusToPlace.name
+ productionBonusesNeeded--
+ }
+ }
+ }
+ }
+
+ private fun calculateFoodBonusesNeeded(
+ startTile: Tile,
+ minorCiv: Boolean,
+ ruleset: Ruleset,
+ tileMap: TileMap
+ ): Int {
+ // evaluate food situation
+ // Food²/4 because excess food is really good and lets us work other tiles or run specialists!
+ // 2F is worth 1, 3F is worth 2, 4F is worth 4, 5F is worth 6 and so on
+ val innerFood =
+ startTile.neighbors.sumOf { (getPotentialYield(it, Stat.Food).pow(2) / 4).toInt() }
+ val outerFood = startTile.getTilesAtDistance(2)
+ .sumOf { (getPotentialYield(it, Stat.Food).pow(2) / 4).toInt() }
+ val totalFood = innerFood + outerFood
+ // we want at least some two-food tiles to keep growing
+ val innerNativeTwoFood =
+ startTile.neighbors.count { getPotentialYield(it, Stat.Food, unimproved = true) >= 2f }
+ val outerNativeTwoFood = startTile.getTilesAtDistance(2)
+ .count { getPotentialYield(it, Stat.Food, unimproved = true) >= 2f }
+ val totalNativeTwoFood = innerNativeTwoFood + outerNativeTwoFood
+
+ // Determine number of needed bonuses. Different weightings for minor and major civs.
+ var bonusesNeeded = if (minorCiv) {
+ when { // From 2 to 0
+ totalFood < 12 || innerFood < 4 -> 2
+ totalFood < 16 || innerFood < 9 -> 1
+ else -> 0
+ }
+ } else {
+ when { // From 5 to 0
+ innerFood == 0 && totalFood < 4 -> 5
+ totalFood < 6 -> 4
+ totalFood < 8 ||
+ (totalFood < 12 && innerFood < 5) -> 3
+
+ (totalFood < 17 && innerFood < 9) ||
+ totalNativeTwoFood < 2 -> 2
+
+ (totalFood < 24 && innerFood < 11) ||
+ totalNativeTwoFood == 2 ||
+ innerNativeTwoFood == 0 ||
+ totalFood < 20 -> 1
+
+ else -> 0
+ }
+ }
+ if (tileMap.mapParameters.mapResources == MapResources.legendaryStart)
+ bonusesNeeded += 2
+
+ // Attempt to place one grassland at a plains-only spot (nor for minors)
+ if (!minorCiv && bonusesNeeded < 3 && totalNativeTwoFood == 0) {
+ val twoFoodTerrain =
+ ruleset.terrains.values.firstOrNull { it.type == TerrainType.Land && it.food >= 2 }?.name
+ val candidateInnerSpots = startTile.neighbors
+ .filter { it.isLand && !it.isImpassible() && it.terrainFeatures.isEmpty() && it.resource == null }
+ val candidateOuterSpots = startTile.getTilesAtDistance(2)
+ .filter { it.isLand && !it.isImpassible() && it.terrainFeatures.isEmpty() && it.resource == null }
+ val spot =
+ candidateInnerSpots.shuffled().firstOrNull() ?: candidateOuterSpots.shuffled()
+ .firstOrNull()
+ if (twoFoodTerrain != null && spot != null) {
+ spot.baseTerrain = twoFoodTerrain
+ } else
+ bonusesNeeded = 3 // Irredeemable plains situation
+ }
+ return bonusesNeeded
+ }
+
+ private fun placeFoodBonuses(
+ minorCiv: Boolean,
+ startTile: Tile,
+ ruleset: Ruleset,
+ foodBonusesNeeded: Int
+ ) {
+ var bonusesStillNeeded = foodBonusesNeeded
+ val oasisEquivalent = ruleset.terrains.values.firstOrNull {
+ it.type == TerrainType.TerrainFeature &&
+ it.hasUnique(UniqueType.RareFeature) &&
+ it.food >= 2 &&
+ it.food + it.production + it.gold >= 3 &&
+ it.occursOn.any { base -> ruleset.terrains[base]!!.type == TerrainType.Land }
+ }
+ var canPlaceOasis =
+ oasisEquivalent != null // One oasis per start is enough. Don't bother finding a place if there is no good oasis equivalent
+ var placedInFirst = 0 // Attempt to put first 2 in inner ring and next 3 in second ring
+ var placedInSecond = 0
+ val rangeForBonuses = if (minorCiv) 2 else 3
+
+ // Start with list of candidate plots sorted in ring order 1,2,3
+ val candidatePlots = startTile.getTilesInDistanceRange(1..rangeForBonuses)
+ .filter { it.resource == null && oasisEquivalent !in it.terrainFeatureObjects }
+ .shuffled().sortedBy { it.aerialDistanceTo(startTile) }.toMutableList()
+
+ // Place food bonuses (and oases) as able
+ while (bonusesStillNeeded > 0 && candidatePlots.isNotEmpty()) {
+ val plot = candidatePlots.first()
+ candidatePlots.remove(plot) // remove the plot as it has now been tried, whether successfully or not
+ if (plot.getBaseTerrain().hasUnique(
+ UniqueType.BlocksResources,
+ StateForConditionals(attackedTile = plot)
+ )
+ )
+ continue // Don't put bonuses on snow hills
+
+ val validBonuses = ruleset.tileResources.values.filter {
+ it.resourceType == ResourceType.Bonus &&
+ it.food >= 1 &&
+ plot.lastTerrain.name in it.terrainsCanBeFoundOn
+ }
+ val goodPlotForOasis =
+ canPlaceOasis && plot.lastTerrain.name in oasisEquivalent!!.occursOn
+
+ if (validBonuses.isNotEmpty() || goodPlotForOasis) {
+ if (goodPlotForOasis) {
+ plot.addTerrainFeature(oasisEquivalent!!.name)
+ canPlaceOasis = false
+ } else {
+ plot.setTileResource(validBonuses.random())
+ }
+
+ if (plot.aerialDistanceTo(startTile) == 1) {
+ placedInFirst++
+ if (placedInFirst == 2) // Resort the list in ring order 2,3,1
+ candidatePlots.sortBy { abs(it.aerialDistanceTo(startTile) * 10 - 22) }
+ } else if (plot.aerialDistanceTo(startTile) == 2) {
+ placedInSecond++
+ if (placedInSecond == 3) // Resort the list in ring order 3,1,2
+ candidatePlots.sortByDescending { abs(it.aerialDistanceTo(startTile) * 10 - 17) }
+ }
+ bonusesStillNeeded--
+ }
+ }
+ }
+
+ private fun getPotentialYield(tile: Tile, stat: Stat, unimproved: Boolean = false): Float {
+ val baseYield = tile.stats.getTileStats(null)[stat]
+ if (unimproved) return baseYield
+
+ val bestImprovementYield = tile.tileMap.ruleset!!.tileImprovements.values
+ .filter { !it.hasUnique(UniqueType.GreatImprovement) &&
+ it.uniqueTo == null &&
+ tile.lastTerrain.name in it.terrainsCanBeBuiltOn }
+ .maxOfOrNull { it[stat] }
+ return baseYield + (bestImprovementYield ?: 0f)
+ }
+
+}
diff --git a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt
index f06aae4f5c6b0..89b07319a1701 100644
--- a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt
+++ b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt
@@ -4,15 +4,15 @@ import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.logic.IsPartOfGameInfoSerialization
import com.unciv.logic.automation.unit.UnitAutomation
-import com.unciv.logic.battle.Battle
+import com.unciv.logic.battle.BattleUnitCapture
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.city.City
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.NotificationIcon
+import com.unciv.logic.map.mapunit.movement.UnitMovement
import com.unciv.logic.map.tile.Tile
import com.unciv.models.UnitActionType
-import com.unciv.models.helpers.UnitMovementMemoryType
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.StateForConditionals
@@ -22,6 +22,7 @@ import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.ruleset.unit.UnitType
import com.unciv.models.stats.Stats
+import com.unciv.ui.components.UnitMovementMemoryType
import com.unciv.ui.components.extensions.filterAndLogic
import java.text.DecimalFormat
import kotlin.math.pow
@@ -549,7 +550,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
}
val maxAdjacentHealingBonus = currentTile.neighbors
- .flatMap { it.getUnits().asSequence() }.filter { it.civ == civ }
+ .flatMap { it.getUnits() }.filter { it.civ == civ }
.map { it.adjacentHealingBonus() }.maxOrNull()
if (maxAdjacentHealingBonus != null)
healing += maxAdjacentHealingBonus
@@ -635,7 +636,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
val unguardedCivilian = tile.getUnguardedCivilian(this)
// Capture Enemy Civilian Unit if you move on top of it
if (isMilitary() && unguardedCivilian != null && civ.isAtWarWith(unguardedCivilian.civ)) {
- Battle.captureCivilianUnit(MapUnitCombatant(this), MapUnitCombatant(tile.civilianUnit!!))
+ BattleUnitCapture.captureCivilianUnit(MapUnitCombatant(this), MapUnitCombatant(tile.civilianUnit!!))
}
val promotionUniques = tile.neighbors
@@ -654,7 +655,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
fun putInTile(tile: Tile) {
when {
!movement.canMoveTo(tile) ->
- throw Exception("Unit $name at $currentTile can't be put in tile ${tile.position}!")
+ throw Exception("Unit $name of ${civ.civName} at $currentTile can't be put in tile $tile!")
baseUnit.movesLikeAirUnits() -> tile.airUnits.add(this)
isCivilian() -> tile.civilianUnit = this
else -> tile.militaryUnit = this
diff --git a/core/src/com/unciv/logic/map/mapunit/movement/MovementCost.kt b/core/src/com/unciv/logic/map/mapunit/movement/MovementCost.kt
new file mode 100644
index 0000000000000..f4b7bea558940
--- /dev/null
+++ b/core/src/com/unciv/logic/map/mapunit/movement/MovementCost.kt
@@ -0,0 +1,179 @@
+package com.unciv.logic.map.mapunit.movement
+
+import com.unciv.Constants
+import com.unciv.logic.civilization.Civilization
+import com.unciv.logic.map.mapunit.MapUnit
+import com.unciv.logic.map.mapunit.MapUnitCache
+import com.unciv.logic.map.tile.RoadStatus
+import com.unciv.logic.map.tile.Tile
+import com.unciv.models.ruleset.unique.StateForConditionals
+import com.unciv.models.ruleset.unique.UniqueType
+
+object MovementCost {
+
+ // This function is called ALL THE TIME and should be as time-optimal as possible!
+ fun getMovementCostBetweenAdjacentTiles(
+ unit: MapUnit,
+ from: Tile,
+ to: Tile,
+ considerZoneOfControl: Boolean = true
+ ): Float {
+ val civ = unit.civ
+
+ if (unit.cache.cannotMove) return 100f
+
+ if (from.isLand != to.isLand && unit.baseUnit.isLandUnit() && !unit.cache.canMoveOnWater)
+ return if (from.isWater && to.isLand) unit.cache.costToDisembark ?: 100f
+ else unit.cache.costToEmbark ?: 100f
+
+ // If the movement is affected by a Zone of Control, all movement points are expended
+ if (considerZoneOfControl && isMovementAffectedByZoneOfControl(unit, from, to))
+ return 100f
+
+ // land units will still spend all movement points to embark even with this unique
+ if (unit.cache.allTilesCosts1)
+ return 1f
+
+ val toOwner = to.getOwner()
+
+ val extraCost = if (
+ toOwner != null &&
+ toOwner.hasActiveEnemyMovementPenalty &&
+ civ.isAtWarWith(toOwner)
+ ) getEnemyMovementPenalty(toOwner, unit) else 0f
+
+ if (from.getUnpillagedRoad() == RoadStatus.Railroad && to.getUnpillagedRoad() == RoadStatus.Railroad)
+ return RoadStatus.Railroad.movement + extraCost
+
+ // Each of these two function calls `hasUnique(UniqueType.CityStateTerritoryAlwaysFriendly)`
+ // when entering territory of a city state
+ val areConnectedByRoad = from.hasConnection(civ) && to.hasConnection(civ)
+
+ // You might think "wait doesn't isAdjacentToRiver() call isConnectedByRiver() anyway, why have those checks?"
+ // The answer is that the isAdjacentToRiver values are CACHED per tile, but the isConnectedByRiver are not - this is an efficiency optimization
+ val areConnectedByRiver =
+ from.isAdjacentToRiver() && to.isAdjacentToRiver() && from.isConnectedByRiver(to)
+
+ if (areConnectedByRoad && (!areConnectedByRiver || civ.tech.roadsConnectAcrossRivers))
+ return unit.civ.tech.movementSpeedOnRoads + extraCost
+
+ if (unit.cache.ignoresTerrainCost) return 1f + extraCost
+ if (areConnectedByRiver) return 100f // Rivers take the entire turn to cross
+
+ val terrainCost = to.lastTerrain.movementCost.toFloat()
+
+ if (unit.cache.noTerrainMovementUniques)
+ return terrainCost + extraCost
+
+ val stateForConditionals = StateForConditionals(unit.civ, unit = unit, tile = to)
+
+ if (to.terrainFeatures.any { hasDoubleMovement(unit, it, MapUnitCache.DoubleMovementTerrainTarget.Feature, stateForConditionals) })
+ return terrainCost * 0.5f + extraCost
+
+ if (unit.cache.roughTerrainPenalty && to.isRoughTerrain())
+ return 100f // units that have to spend all movement in rough terrain, have to spend all movement in rough terrain
+ // Placement of this 'if' based on testing, see #4232
+
+ if (civ.nation.ignoreHillMovementCost && to.isHill())
+ return 1f + extraCost // usually hills take 2 movements, so here it is 1
+
+ if (unit.cache.noBaseTerrainOrHillDoubleMovementUniques)
+ return terrainCost + extraCost
+
+ if (hasDoubleMovement(unit, to.baseTerrain, MapUnitCache.DoubleMovementTerrainTarget.Base, stateForConditionals))
+ return terrainCost * 0.5f + extraCost
+ if (hasDoubleMovement(unit, Constants.hill, MapUnitCache.DoubleMovementTerrainTarget.Hill, stateForConditionals)
+ && to.isHill())
+ return terrainCost * 0.5f + extraCost
+
+ if (unit.cache.noFilteredDoubleMovementUniques)
+ return terrainCost + extraCost
+ if (unit.cache.doubleMovementInTerrain.any {
+ hasDoubleMovement(it.value, MapUnitCache.DoubleMovementTerrainTarget.Filter, stateForConditionals)
+ && to.matchesFilter(it.key)
+ })
+ return terrainCost * 0.5f + extraCost
+
+ return terrainCost + extraCost // no road or other movement cost reduction
+ }
+
+
+ private fun hasDoubleMovement(
+ doubleMovement: MapUnitCache.DoubleMovement,
+ target: MapUnitCache.DoubleMovementTerrainTarget,
+ stateForConditionals: StateForConditionals
+ ): Boolean {
+ if (doubleMovement.terrainTarget != target) return false
+ if (doubleMovement.unique.conditionals.isNotEmpty()
+ && !doubleMovement.unique.conditionalsApply(stateForConditionals)) return false
+
+ return true
+ }
+
+ private fun hasDoubleMovement(
+ unit: MapUnit,
+ terrainName: String,
+ target: MapUnitCache.DoubleMovementTerrainTarget,
+ stateForConditionals: StateForConditionals
+ ): Boolean {
+ val doubleMovement = unit.cache.doubleMovementInTerrain[terrainName] ?: return false
+ return hasDoubleMovement(doubleMovement, target, stateForConditionals)
+ }
+
+ private fun getEnemyMovementPenalty(civInfo:Civilization, enemyUnit: MapUnit): Float {
+ if (civInfo.enemyMovementPenaltyUniques != null && civInfo.enemyMovementPenaltyUniques!!.any()) {
+ return civInfo.enemyMovementPenaltyUniques!!.sumOf {
+ if (it.type!! == UniqueType.EnemyUnitsSpendExtraMovement
+ && enemyUnit.matchesFilter(it.params[0]))
+ it.params[1].toInt()
+ else 0
+ }.toFloat()
+ }
+ return 0f // should not reach this point
+ }
+
+
+ /** Returns whether the movement between the adjacent tiles [from] and [to] is affected by Zone of Control */
+ private fun isMovementAffectedByZoneOfControl(unit: MapUnit, from: Tile, to: Tile): Boolean {
+ // Sources:
+ // - https://civilization.fandom.com/wiki/Zone_of_control_(Civ5)
+ // - https://forums.civfanatics.com/resources/understanding-the-zone-of-control-vanilla.25582/
+ //
+ // Enemy military units exert a Zone of Control over the tiles surrounding them. Moving from
+ // one tile in the ZoC of an enemy unit to another tile in the same unit's ZoC expends all
+ // movement points. Land units only exert a ZoC against land units. Sea units exert a ZoC
+ // against both land and sea units. Cities exert a ZoC as well, and it also affects both
+ // land and sea units. Embarked land units do not exert a ZoC. Finally, units that can move
+ // after attacking are not affected by zone of control if the movement is caused by killing
+ // a unit. This last case is handled in the movement-after-attacking code instead of here.
+
+ // We only need to check the two shared neighbors of [from] and [to]: the way of getting
+ // these two tiles can perhaps be optimized. Using a hex-math-based "commonAdjacentTiles"
+ // function is surprisingly less efficient than the current neighbor-intersection approach.
+ // See #4085 for more details.
+ val tilesExertingZoneOfControl = getTilesExertingZoneOfControl(unit, from)
+ if (tilesExertingZoneOfControl.none { to.neighbors.contains(it)})
+ return false
+
+ // Even though this is a very fast check, we perform it last. This is because very few units
+ // ignore zone of control, so the previous check has a much higher chance of yielding an
+ // early "false". If this function is going to return "true", the order doesn't matter
+ // anyway.
+ if (unit.cache.ignoresZoneOfControl)
+ return false
+ return true
+ }
+
+ private fun getTilesExertingZoneOfControl(unit: MapUnit, tile: Tile) = sequence {
+ for (neighbor in tile.neighbors) {
+ if (neighbor.isCityCenter() && unit.civ.isAtWarWith(neighbor.getOwner()!!)) {
+ yield(neighbor)
+ }
+ else if (neighbor.militaryUnit != null && unit.civ.isAtWarWith(neighbor.militaryUnit!!.civ)) {
+ if (neighbor.militaryUnit!!.type.isWaterUnit() || (unit.type.isLandUnit() && !neighbor.militaryUnit!!.isEmbarked()))
+ yield(neighbor)
+ }
+ }
+ }
+
+}
diff --git a/core/src/com/unciv/logic/map/mapunit/UnitMovement.kt b/core/src/com/unciv/logic/map/mapunit/movement/UnitMovement.kt
similarity index 81%
rename from core/src/com/unciv/logic/map/mapunit/UnitMovement.kt
rename to core/src/com/unciv/logic/map/mapunit/movement/UnitMovement.kt
index 28dba139796ee..967d14ced4a0f 100644
--- a/core/src/com/unciv/logic/map/mapunit/UnitMovement.kt
+++ b/core/src/com/unciv/logic/map/mapunit/movement/UnitMovement.kt
@@ -1,179 +1,19 @@
-package com.unciv.logic.map.mapunit
+package com.unciv.logic.map.mapunit.movement
import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
-import com.unciv.logic.civilization.Civilization
import com.unciv.logic.map.BFS
import com.unciv.logic.map.HexMath.getDistance
-import com.unciv.logic.map.tile.RoadStatus
+import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.logic.map.tile.Tile
import com.unciv.models.UnitActionType
-import com.unciv.models.helpers.UnitMovementMemoryType
-import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueType
+import com.unciv.ui.components.UnitMovementMemoryType
class UnitMovement(val unit: MapUnit) {
private val pathfindingCache = PathfindingCache(unit)
- fun getEnemyMovementPenalty(civInfo:Civilization, enemyUnit: MapUnit): Float {
- if (civInfo.enemyMovementPenaltyUniques != null && civInfo.enemyMovementPenaltyUniques!!.any()) {
- return civInfo.enemyMovementPenaltyUniques!!.sumOf {
- if (it.type!! == UniqueType.EnemyUnitsSpendExtraMovement
- && enemyUnit.matchesFilter(it.params[0]))
- it.params[1].toInt()
- else 0
- }.toFloat()
- }
- return 0f // should not reach this point
- }
-
- // This function is called ALL THE TIME and should be as time-optimal as possible!
- private fun getMovementCostBetweenAdjacentTiles(
- from: Tile,
- to: Tile,
- civInfo: Civilization,
- considerZoneOfControl: Boolean = true
- ): Float {
- if (unit.cache.cannotMove) return 100f
-
- if (from.isLand != to.isLand && unit.baseUnit.isLandUnit() && !unit.cache.canMoveOnWater)
- return if (from.isWater && to.isLand) unit.cache.costToDisembark ?: 100f
- else unit.cache.costToEmbark ?: 100f
-
- // If the movement is affected by a Zone of Control, all movement points are expended
- if (considerZoneOfControl && isMovementAffectedByZoneOfControl(from, to, civInfo))
- return 100f
-
- // land units will still spend all movement points to embark even with this unique
- if (unit.cache.allTilesCosts1)
- return 1f
-
- val toOwner = to.getOwner()
- val extraCost = if (
- toOwner != null &&
- toOwner.hasActiveEnemyMovementPenalty &&
- civInfo.isAtWarWith(toOwner)
- ) getEnemyMovementPenalty(toOwner, unit) else 0f
-
- if (from.getUnpillagedRoad() == RoadStatus.Railroad && to.getUnpillagedRoad() == RoadStatus.Railroad)
- return RoadStatus.Railroad.movement + extraCost
-
- // Each of these two function calls `hasUnique(UniqueType.CityStateTerritoryAlwaysFriendly)`
- // when entering territory of a city state
- val areConnectedByRoad = from.hasConnection(civInfo) && to.hasConnection(civInfo)
-
- // You might think "wait doesn't isAdjacentToRiver() call isConnectedByRiver() anyway, why have those checks?"
- // The answer is that the isAdjacentToRiver values are CACHED per tile, but the isConnectedByRiver are not - this is an efficiency optimization
- val areConnectedByRiver =
- from.isAdjacentToRiver() && to.isAdjacentToRiver() && from.isConnectedByRiver(to)
-
- if (areConnectedByRoad && (!areConnectedByRiver || civInfo.tech.roadsConnectAcrossRivers))
- return unit.civ.tech.movementSpeedOnRoads + extraCost
-
- if (unit.cache.ignoresTerrainCost) return 1f + extraCost
- if (areConnectedByRiver) return 100f // Rivers take the entire turn to cross
-
- val terrainCost = to.lastTerrain.movementCost.toFloat()
-
- if (unit.cache.noTerrainMovementUniques)
- return terrainCost + extraCost
-
- val stateForConditionals = StateForConditionals(unit.civ, unit = unit, tile = to)
- fun matchesTerrainTarget(
- doubleMovement: MapUnitCache.DoubleMovement,
- target: MapUnitCache.DoubleMovementTerrainTarget
- ): Boolean {
- if (doubleMovement.terrainTarget != target) return false
- if (doubleMovement.unique.conditionals.isNotEmpty()) {
- if (!doubleMovement.unique.conditionalsApply(stateForConditionals)) return false
- }
-
- return true
- }
-
- fun matchesTerrainTarget(
- terrainName: String,
- target: MapUnitCache.DoubleMovementTerrainTarget
- ): Boolean {
- val doubleMovement = unit.cache.doubleMovementInTerrain[terrainName] ?: return false
- return matchesTerrainTarget(doubleMovement, target)
- }
-
-
- if (to.terrainFeatures.any { matchesTerrainTarget(it, MapUnitCache.DoubleMovementTerrainTarget.Feature) })
- return terrainCost * 0.5f + extraCost
-
- if (unit.cache.roughTerrainPenalty && to.isRoughTerrain())
- return 100f // units that have to spend all movement in rough terrain, have to spend all movement in rough terrain
- // Placement of this 'if' based on testing, see #4232
-
- if (civInfo.nation.ignoreHillMovementCost && to.isHill())
- return 1f + extraCost // usually hills take 2 movements, so here it is 1
-
- if (unit.cache.noBaseTerrainOrHillDoubleMovementUniques)
- return terrainCost + extraCost
-
- if (matchesTerrainTarget(to.baseTerrain, MapUnitCache.DoubleMovementTerrainTarget.Base))
- return terrainCost * 0.5f + extraCost
- if (matchesTerrainTarget(Constants.hill, MapUnitCache.DoubleMovementTerrainTarget.Hill)
- && to.isHill())
- return terrainCost * 0.5f + extraCost
-
- if (unit.cache.noFilteredDoubleMovementUniques)
- return terrainCost + extraCost
- if (unit.cache.doubleMovementInTerrain.any {
- matchesTerrainTarget(it.value, MapUnitCache.DoubleMovementTerrainTarget.Filter)
- && to.matchesFilter(it.key)
- })
- return terrainCost * 0.5f + extraCost
-
- return terrainCost + extraCost // no road or other movement cost reduction
- }
-
- private fun getTilesExertingZoneOfControl(tile: Tile, civInfo: Civilization) = sequence {
- for (neighbor in tile.neighbors) {
- if (neighbor.isCityCenter() && civInfo.isAtWarWith(neighbor.getOwner()!!)) {
- yield(neighbor)
- }
- else if (neighbor.militaryUnit != null && civInfo.isAtWarWith(neighbor.militaryUnit!!.civ)) {
- if (neighbor.militaryUnit!!.type.isWaterUnit() || (unit.type.isLandUnit() && !neighbor.militaryUnit!!.isEmbarked()))
- yield(neighbor)
- }
- }
- }
-
- /** Returns whether the movement between the adjacent tiles [from] and [to] is affected by Zone of Control */
- private fun isMovementAffectedByZoneOfControl(from: Tile, to: Tile, civInfo: Civilization): Boolean {
- // Sources:
- // - https://civilization.fandom.com/wiki/Zone_of_control_(Civ5)
- // - https://forums.civfanatics.com/resources/understanding-the-zone-of-control-vanilla.25582/
- //
- // Enemy military units exert a Zone of Control over the tiles surrounding them. Moving from
- // one tile in the ZoC of an enemy unit to another tile in the same unit's ZoC expends all
- // movement points. Land units only exert a ZoC against land units. Sea units exert a ZoC
- // against both land and sea units. Cities exert a ZoC as well, and it also affects both
- // land and sea units. Embarked land units do not exert a ZoC. Finally, units that can move
- // after attacking are not affected by zone of control if the movement is caused by killing
- // a unit. This last case is handled in the movement-after-attacking code instead of here.
-
- // We only need to check the two shared neighbors of [from] and [to]: the way of getting
- // these two tiles can perhaps be optimized. Using a hex-math-based "commonAdjacentTiles"
- // function is surprisingly less efficient than the current neighbor-intersection approach.
- // See #4085 for more details.
- val tilesExertingZoneOfControl = getTilesExertingZoneOfControl(from, civInfo)
- if (tilesExertingZoneOfControl.none { to.neighbors.contains(it)})
- return false
-
- // Even though this is a very fast check, we perform it last. This is because very few units
- // ignore zone of control, so the previous check has a much higher chance of yielding an
- // early "false". If this function is going to return "true", the order doesn't matter
- // anyway.
- if (unit.cache.ignoresZoneOfControl)
- return false
- return true
- }
-
class ParentTileAndTotalDistance(val tile:Tile, val parentTile: Tile, val totalDistance: Float)
fun isUnknownTileWeShouldAssumeToBePassable(tile: Tile) = !unit.civ.hasExplored(tile)
@@ -218,7 +58,7 @@ class UnitMovement(val unit: MapUnit) {
val key = Pair(tileToCheck, neighbor)
val movementCost =
movementCostCache.getOrPut(key) {
- getMovementCostBetweenAdjacentTiles(tileToCheck, neighbor, unit.civ, considerZoneOfControl)
+ MovementCost.getMovementCostBetweenAdjacentTiles(unit, tileToCheck, neighbor, considerZoneOfControl)
}
distanceToTiles[tileToCheck]!!.totalDistance + movementCost
}
@@ -540,7 +380,9 @@ class UnitMovement(val unit: MapUnit) {
unit.currentMovement = 0f
unit.mostRecentMoveType = UnitMovementMemoryType.UnitTeleported
return
- } else if (unit.isPreparingParadrop()) { // paradropping units move differently
+ }
+
+ if (unit.isPreparingParadrop()) { // paradropping units move differently
unit.action = null
unit.removeFromTile()
unit.putInTile(destination)
@@ -594,7 +436,7 @@ class UnitMovement(val unit: MapUnit) {
// This fixes a bug where tiles in the fog of war would always only cost 1 mp
if (!unit.civ.gameInfo.gameParameters.godMode)
- passingMovementSpent += getMovementCostBetweenAdjacentTiles(previousTile, tile, unit.civ)
+ passingMovementSpent += MovementCost.getMovementCostBetweenAdjacentTiles(unit, previousTile, tile)
// In case something goes wrong, cache the last tile we were able to end on
// We can assume we can pass through this tile, as we would have broken earlier
@@ -741,7 +583,7 @@ class UnitMovement(val unit: MapUnit) {
}
// Can a paratrooper land at this tile?
- fun canParadropOn(destination: Tile): Boolean {
+ private fun canParadropOn(destination: Tile): Boolean {
if (unit.cache.cannotMove) return false
// Can only move to land tiles within range that are visible and not impassible
// Based on some testing done in the base game
diff --git a/core/src/com/unciv/logic/map/tile/Tile.kt b/core/src/com/unciv/logic/map/tile/Tile.kt
index d0d65d0efcb10..4c761d89b17ec 100644
--- a/core/src/com/unciv/logic/map/tile/Tile.kt
+++ b/core/src/com/unciv/logic/map/tile/Tile.kt
@@ -12,7 +12,7 @@ import com.unciv.logic.map.MapParameters
import com.unciv.logic.map.MapResources
import com.unciv.logic.map.TileMap
import com.unciv.logic.map.mapunit.MapUnit
-import com.unciv.logic.map.mapunit.UnitMovement
+import com.unciv.logic.map.mapunit.movement.UnitMovement
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.tile.Terrain
@@ -883,7 +883,7 @@ open class Tile : IsPartOfGameInfoSerialization {
if (resource != null && resource !in ruleset.tileResources)
resource = null
if (improvement != null && improvement !in ruleset.tileImprovements)
- removeImprovement()
+ improvement = null
}
/** If the unit isn't in the ruleset we can't even know what type of unit this is! So check each place
diff --git a/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt b/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt
index 9e16029da8285..674ccc9e9ac46 100644
--- a/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt
+++ b/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt
@@ -5,10 +5,10 @@ import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.LocationAction
import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.NotificationIcon
+import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueType
-import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions
enum class ImprovementBuildingProblem {
@@ -203,7 +203,7 @@ class TileInfoImprovementFunctions(val tile: Tile) {
if (civToActivateBroaderEffects != null && improvementObject != null
&& improvementObject.hasUnique(UniqueType.TakesOverAdjacentTiles)
)
- UnitActions.takeOverTilesAround(civToActivateBroaderEffects, tile)
+ takeOverTilesAround(civToActivateBroaderEffects, tile)
val city = tile.owningCity
if (city != null) {
@@ -261,6 +261,51 @@ class TileInfoImprovementFunctions(val tile: Tile) {
}
}
+ private fun takeOverTilesAround(civ: Civilization, tile: Tile) {
+ // This method should only be called for a citadel - therefore one of the neighbour tile
+ // must belong to unit's civ, so minByOrNull in the nearestCity formula should be never `null`.
+ // That is, unless a mod does not specify the proper unique - then fallbackNearestCity will take over.
+
+ fun priority(tile: Tile): Int { // helper calculates priority (lower is better): distance plus razing malus
+ val city = tile.getCity()!! // !! assertion is guaranteed by the outer filter selector.
+ return city.getCenterTile().aerialDistanceTo(tile) +
+ (if (city.isBeingRazed) 5 else 0)
+ }
+ fun fallbackNearestCity(civ: Civilization, tile: Tile) =
+ civ.cities.minByOrNull {
+ it.getCenterTile().aerialDistanceTo(tile) +
+ (if (it.isBeingRazed) 5 else 0)
+ }!!
+
+ // In the rare case more than one city owns tiles neighboring the citadel
+ // this will prioritize the nearest one not being razed
+ val nearestCity = tile.neighbors
+ .filter { it.getOwner() == civ }
+ .minByOrNull { priority(it) }?.getCity()
+ ?: fallbackNearestCity(civ, tile)
+
+ // capture all tiles which do not belong to unit's civ and are not enemy cities
+ // we use getTilesInDistance here, not neighbours to include the current tile as well
+ val tilesToTakeOver = tile.getTilesInDistance(1)
+ .filter { !it.isCityCenter() && it.getOwner() != civ }
+
+ val civsToNotify = mutableSetOf()
+ for (tileToTakeOver in tilesToTakeOver) {
+ val otherCiv = tileToTakeOver.getOwner()
+ if (otherCiv != null) {
+ // decrease relations for -10 pt/tile
+ if (!otherCiv.knows(civ)) otherCiv.diplomacyFunctions.makeCivilizationsMeet(civ)
+ otherCiv.getDiplomacyManager(civ).addModifier(DiplomaticModifiers.StealingTerritory, -10f)
+ civsToNotify.add(otherCiv)
+ }
+ nearestCity.expansion.takeOwnership(tileToTakeOver)
+ }
+
+ for (otherCiv in civsToNotify)
+ otherCiv.addNotification("Your territory has been stolen by [$civ]!",
+ tile.position, NotificationCategory.Cities, civ.civName, NotificationIcon.War)
+ }
+
/** Marks tile as target tile for a building with a [UniqueType.CreatesOneImprovement] unique */
diff --git a/core/src/com/unciv/models/simulation/MutableInt.kt b/core/src/com/unciv/logic/simulation/MutableInt.kt
similarity index 86%
rename from core/src/com/unciv/models/simulation/MutableInt.kt
rename to core/src/com/unciv/logic/simulation/MutableInt.kt
index f0009de378583..66cb23d9f5230 100644
--- a/core/src/com/unciv/models/simulation/MutableInt.kt
+++ b/core/src/com/unciv/logic/simulation/MutableInt.kt
@@ -1,4 +1,4 @@
-package com.unciv.models.simulation
+package com.unciv.logic.simulation
class MutableInt(var value: Int = 0) {
fun inc() { ++value }
diff --git a/core/src/com/unciv/models/simulation/Simulation.kt b/core/src/com/unciv/logic/simulation/Simulation.kt
similarity index 99%
rename from core/src/com/unciv/models/simulation/Simulation.kt
rename to core/src/com/unciv/logic/simulation/Simulation.kt
index 194f808bd22f3..cb1a34b1fc69f 100644
--- a/core/src/com/unciv/models/simulation/Simulation.kt
+++ b/core/src/com/unciv/logic/simulation/Simulation.kt
@@ -1,4 +1,4 @@
-package com.unciv.models.simulation
+package com.unciv.logic.simulation
import com.unciv.Constants
import com.unciv.UncivGame
diff --git a/core/src/com/unciv/models/simulation/SimulationStep.kt b/core/src/com/unciv/logic/simulation/SimulationStep.kt
similarity index 91%
rename from core/src/com/unciv/models/simulation/SimulationStep.kt
rename to core/src/com/unciv/logic/simulation/SimulationStep.kt
index 3428e6d09a7d4..1bb1782527ab0 100644
--- a/core/src/com/unciv/models/simulation/SimulationStep.kt
+++ b/core/src/com/unciv/logic/simulation/SimulationStep.kt
@@ -1,4 +1,4 @@
-package com.unciv.models.simulation
+package com.unciv.logic.simulation
import com.unciv.logic.GameInfo
diff --git a/core/src/com/unciv/logic/trade/Trade.kt b/core/src/com/unciv/logic/trade/Trade.kt
index f472de28ab642..05811798499ca 100644
--- a/core/src/com/unciv/logic/trade/Trade.kt
+++ b/core/src/com/unciv/logic/trade/Trade.kt
@@ -61,9 +61,9 @@ class TradeRequest : IsPartOfGameInfoSerialization {
if (trade.ourOffers.any { it.name == Constants.researchAgreement })
diplomacyManager.setFlag(DiplomacyFlags.DeclinedResearchAgreement,20)
if (trade.ourOffers.any { it.name == Constants.defensivePact })
- diplomacyManager.setFlag(DiplomacyFlags.DeclinedDefensivePact,10)
+ diplomacyManager.setFlag(DiplomacyFlags.DeclinedDefensivePact,20)
if (trade.ourOffers.any { it.name == Constants.openBorders })
- diplomacyManager.setFlag(DiplomacyFlags.DeclinedOpenBorders, 10)
+ diplomacyManager.setFlag(DiplomacyFlags.DeclinedOpenBorders, if (decliningCiv.isAI()) 10 else 20)
if (trade.isPeaceTreaty()) diplomacyManager.setFlag(DiplomacyFlags.DeclinedPeace, 5)
diff --git a/core/src/com/unciv/models/metadata/GameSettings.kt b/core/src/com/unciv/models/metadata/GameSettings.kt
index 0baf0704387e7..e3b9ab5a999c7 100644
--- a/core/src/com/unciv/models/metadata/GameSettings.kt
+++ b/core/src/com/unciv/models/metadata/GameSettings.kt
@@ -134,6 +134,9 @@ class GameSettings {
enum class NationPickerListMode { Icons, List }
var nationPickerListMode = NationPickerListMode.List
+ /** Size of automatic display of UnitSet art in Civilopedia - 0 to disable */
+ var pediaUnitArtSize = 0f
+
/** used to migrate from older versions of the settings */
var version: Int? = null
diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt
index 5a0cecad18b90..a39098b3d908f 100644
--- a/core/src/com/unciv/models/ruleset/Ruleset.kt
+++ b/core/src/com/unciv/models/ruleset/Ruleset.kt
@@ -21,6 +21,7 @@ import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.ruleset.unit.Promotion
import com.unciv.models.ruleset.unit.UnitType
import com.unciv.models.ruleset.validation.RulesetValidator
+import com.unciv.models.ruleset.validation.UniqueValidator
import com.unciv.models.stats.INamed
import com.unciv.models.translations.tr
import com.unciv.utils.Log
@@ -378,7 +379,7 @@ class Ruleset {
name = cityStateType.name
color = cityStateType.color
friendBonusUniques = ArrayList(cityStateType.friendBonusUniques.filter {
- RulesetValidator(this@Ruleset).checkUnique(
+ UniqueValidator(this@Ruleset).checkUnique(
Unique(it),
false,
cityStateType,
@@ -386,7 +387,7 @@ class Ruleset {
).isEmpty()
})
allyBonusUniques = ArrayList(cityStateType.allyBonusUniques.filter {
- RulesetValidator(this@Ruleset).checkUnique(
+ UniqueValidator(this@Ruleset).checkUnique(
Unique(it),
false,
cityStateType,
diff --git a/core/src/com/unciv/models/ruleset/RulesetCache.kt b/core/src/com/unciv/models/ruleset/RulesetCache.kt
index 72d43b33e5b32..9c998bfc22189 100644
--- a/core/src/com/unciv/models/ruleset/RulesetCache.kt
+++ b/core/src/com/unciv/models/ruleset/RulesetCache.kt
@@ -16,9 +16,6 @@ import com.unciv.utils.debug
* save all of the loaded rulesets somewhere for later use
* */
object RulesetCache : HashMap() {
- /** Whether mod checking allows untyped uniques - set to `false` once all vanilla uniques are converted! */
- var modCheckerAllowUntypedUniques = true
-
/** Similarity below which an untyped unique can be considered a potential misspelling.
* Roughly corresponds to the fraction of the Unique placeholder text that can be different/misspelled, but with some extra room for [getRelativeTextDistance] idiosyncrasies. */
var uniqueMisspellingThreshold = 0.15 // Tweak as needed. Simple misspellings seem to be around 0.025, so would mostly be caught by 0.05. IMO 0.1 would be good, but raising to 0.15 also seemed to catch what may be an outdated Unique.
diff --git a/core/src/com/unciv/models/ruleset/Tutorial.kt b/core/src/com/unciv/models/ruleset/Tutorial.kt
index c7a530eb3ef1f..a51d91ede42a5 100644
--- a/core/src/com/unciv/models/ruleset/Tutorial.kt
+++ b/core/src/com/unciv/models/ruleset/Tutorial.kt
@@ -1,6 +1,7 @@
package com.unciv.models.ruleset
import com.unciv.models.ruleset.unique.UniqueTarget
+import com.unciv.ui.screens.civilopediascreen.FormattedLine
/**
* Container for json-read "Tutorial" text, potentially decorated.
@@ -13,11 +14,12 @@ import com.unciv.models.ruleset.unique.UniqueTarget
* @see com.unciv.models.TutorialTrigger
*/
class Tutorial : RulesetObject() {
- // Why does this override RulesetObject()? The only unique it overrides is `Will not be displayed in Civilopedia`,
- // so allowing it access to the full power of uniques is completely unnecessary.
- // (Also, what even would it mean for this to have uniques like "[+10]% Production"? When should it even apply.)
- // imo just having a flag for this (and maybe one if religion is disabled, but even then, that should be a ruleset choice) should suffice.
- // -xlenstra
+ // Has access to the full power of uniques for:
+ // * Easier integration into Civilopedia
+ // * HiddenWithoutReligion, HiddenFromCivilopedia work _directly_
+ // * Future expansion - other meta tests to display or not are thinkable,
+ // e.g. modders may want to hide instructions until you discover the game element?
+ // -SomeTrog
override var name = "" // overridden only to have the name seen first by TranslationFileWriter
/** These lines will be displayed (when the Tutorial is _triggered_) one after another,
@@ -27,4 +29,10 @@ class Tutorial : RulesetObject() {
override fun getUniqueTarget() = UniqueTarget.Tutorial
override fun makeLink() = "Tutorial/$name"
+
+ override fun getCivilopediaTextLines(ruleset: Ruleset): List {
+ val imageLine = FormattedLine(extraImage = name.replace(' ', '_'))
+ if (steps == null) return listOf(imageLine)
+ return (sequenceOf(imageLine) + steps.asSequence().map { FormattedLine(it) }).toList()
+ }
}
diff --git a/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt b/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt
index 6704982c388a2..e4aaa3015a6af 100644
--- a/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt
+++ b/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt
@@ -4,7 +4,7 @@ import com.unciv.logic.battle.CombatAction
import com.unciv.logic.battle.ICombatant
import com.unciv.logic.city.City
import com.unciv.logic.civilization.Civilization
-import com.unciv.logic.map.mapgenerator.Region
+import com.unciv.logic.map.mapgenerator.mapregions.Region
import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.logic.map.tile.Tile
diff --git a/core/src/com/unciv/models/ruleset/unique/Unique.kt b/core/src/com/unciv/models/ruleset/unique/Unique.kt
index 04bb7dc3dfe95..abccb95f31e27 100644
--- a/core/src/com/unciv/models/ruleset/unique/Unique.kt
+++ b/core/src/com/unciv/models/ruleset/unique/Unique.kt
@@ -8,7 +8,7 @@ import com.unciv.logic.city.City
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.managers.ReligionState
import com.unciv.models.ruleset.Ruleset
-import com.unciv.models.ruleset.validation.RulesetValidator
+import com.unciv.models.ruleset.validation.UniqueValidator
import com.unciv.models.stats.Stats
import com.unciv.models.translations.getConditionals
import com.unciv.models.translations.getPlaceholderParameters
@@ -21,7 +21,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
* - for instance, in the city screen, we call every tile unique for every tile, which can lead to ANRs */
val placeholderText = text.getPlaceholderText()
val params = text.getPlaceholderParameters()
- val type = UniqueType.values().firstOrNull { it.placeholderText == placeholderText }
+ val type = UniqueType.uniqueTypeMap[placeholderText]
val stats: Stats by lazy {
val firstStatParam = params.firstOrNull { Stats.isStats(it) }
@@ -30,9 +30,11 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
}
val conditionals: List = text.getConditionals()
- val isTriggerable = type != null && type.targetTypes.contains(UniqueTarget.Triggerable)
- // in effect makes any unique become a triggerable unique
+ val isTriggerable = type != null && (
+ type.targetTypes.contains(UniqueTarget.Triggerable)
+ || type.targetTypes.contains(UniqueTarget.UnitTriggerable)
|| conditionals.any { it.type == UniqueType.ConditionalTimedUnique }
+ )
val allParams = params + conditionals.flatMap { it.params }
@@ -41,7 +43,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
fun hasFlag(flag: UniqueFlag) = type != null && type.flags.contains(flag)
fun hasTriggerConditional(): Boolean {
- if(conditionals.none()) return false
+ if (conditionals.none()) return false
return conditionals.any { conditional ->
conditional.type?.targetTypes?.any {
it.canAcceptUniqueTarget(UniqueTarget.TriggerCondition) || it.canAcceptUniqueTarget(UniqueTarget.UnitActionModifier)
@@ -117,7 +119,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
// filter out possible replacements that are obviously wrong
val uniquesWithNoErrors = finalPossibleUniques.filter {
val unique = Unique(it)
- val errors = RulesetValidator(ruleset).checkUnique(
+ val errors = UniqueValidator(ruleset).checkUnique(
unique, true, null,
UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
)
@@ -246,8 +248,11 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
UniqueType.ConditionalBelowHP ->
state.ourCombatant != null && state.ourCombatant.getHealth() < condition.params[0].toInt()
UniqueType.ConditionalHasNotUsedOtherActions ->
- state.unit != null &&
+ state.unit == null || // So we get the action as a valid action in BaseUnit.hasUnique()
+ ( // OLD format
state.unit.run { limitedActionsUnitCanDo().all { abilityUsesLeft[it] == maxAbilityUses[it] } }
+ // NEW format
+ && state.unit.abilityToTimesUsed.isEmpty())
UniqueType.ConditionalInTiles ->
relevantTile?.matchesFilter(condition.params[0], state.civInfo) == true
diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt
index 9e1612e04d48d..d42532288026b 100644
--- a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt
+++ b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt
@@ -499,6 +499,7 @@ enum class UniqueParameterType(
},
/** For untyped "Can [] [] times" unique */
+ @Deprecated("As of 4.8.9")
Action("action", Constants.spreadReligion, "An action that a unit can perform. Currently, there are only two actions part of this: 'Spread Religion' and 'Remove Foreign religions from your own cities'", "Religious Action Filters") {
private val knownValues = setOf(Constants.spreadReligion, Constants.removeHeresy)
override fun getErrorSeverity(
diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueTarget.kt b/core/src/com/unciv/models/ruleset/unique/UniqueTarget.kt
index befe26fb2954d..5580d8be3b877 100644
--- a/core/src/com/unciv/models/ruleset/unique/UniqueTarget.kt
+++ b/core/src/com/unciv/models/ruleset/unique/UniqueTarget.kt
@@ -41,7 +41,8 @@ enum class UniqueTarget(
Wonder(inheritsFrom = Building),
// Unit-specific
- Unit("Uniques that can be added to units, unit types, or promotions", inheritsFrom = UnitTriggerable),
+ UnitAction("Uniques that affect a unit's actions, and can be modified by UnitActionModifiers", inheritsFrom = UnitTriggerable),
+ Unit("Uniques that can be added to units, unit types, or promotions", inheritsFrom = UnitAction),
UnitType(inheritsFrom = Unit),
Promotion(inheritsFrom = Unit),
@@ -61,7 +62,7 @@ enum class UniqueTarget(
Conditional("Modifiers that can be added to other uniques to limit when they will be active", modifierType = ModifierType.Conditional),
TriggerCondition("Special conditionals that can be added to Triggerable uniques, to make them activate upon specific actions.", inheritsFrom = Global, modifierType = ModifierType.Other),
UnitTriggerCondition("Special conditionals that can be added to UnitTriggerable uniques, to make them activate upon specific actions.", inheritsFrom = TriggerCondition, modifierType = ModifierType.Other),
- UnitActionModifier("Modifiers that can be added to unit action uniques as conditionals", modifierType = ModifierType.Other),
+ UnitActionModifier("Modifiers that can be added to UnitAction uniques as conditionals", modifierType = ModifierType.Other),
;
/** Whether a UniqueType is allowed in the `` part - or not.
diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt
index 3bacb41b679d7..aa1cfcfaecebf 100644
--- a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt
+++ b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt
@@ -641,7 +641,8 @@ object UniqueTriggerActivation {
return true
}
- UniqueType.FreeStatBuildings, UniqueType.FreeSpecificBuildings -> {
+ UniqueType.FreeStatBuildings, UniqueType.FreeSpecificBuildings,
+ UniqueType.GainFreeBuildings -> {
civInfo.civConstructions.tryAddFreeBuildings()
return true // not fully correct
}
diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt
index 6e41f92cbb7ec..6fd213d27888c 100644
--- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt
+++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt
@@ -1,9 +1,8 @@
package com.unciv.models.ruleset.unique
import com.unciv.Constants
-import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.validation.RulesetErrorSeverity
-import com.unciv.models.ruleset.validation.RulesetValidator // Kdoc only
+import com.unciv.models.ruleset.validation.RulesetValidator
import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.models.translations.getPlaceholderText
@@ -129,7 +128,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
TileImprovementTime("[relativeAmount]% tile improvement construction time", UniqueTarget.Global, UniqueTarget.Unit),
/// Building Maintenance
- GainFreeBuildings("Gain a free [buildingName] [cityFilter]", UniqueTarget.Global),
+ GainFreeBuildings("Gain a free [buildingName] [cityFilter]", UniqueTarget.Global, UniqueTarget.Triggerable),
BuildingMaintenance("[relativeAmount]% maintenance cost for buildings [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
/// Border growth
@@ -219,6 +218,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
ReligionSpreadDistance("Religion naturally spreads to cities [amount] tiles away", UniqueTarget.Global, UniqueTarget.FollowerBelief),
MayNotGenerateGreatProphet("May not generate great prophet equivalents naturally", UniqueTarget.Global),
FaithCostOfGreatProphetChange("[relativeAmount]% Faith cost of generating Great Prophet equivalents", UniqueTarget.Global),
+ @Deprecated("As of 4.8.9", ReplaceWith("All newly-trained [baseUnitFilter] units [cityFilter] receive the [Devout] promotion"))
UnitStartingActions("[baseUnitFilter] units built [cityFilter] can [action] [amount] extra times", UniqueTarget.Global, UniqueTarget.FollowerBelief),
/// Things you get at the start of the game
@@ -311,22 +311,26 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
// Unit action uniques
// Unit actions should look like: "Can {action description}, to allow them to be combined with modifiers
- FoundCity("Founds a new city", UniqueTarget.Unit),
+ FoundCity("Founds a new city", UniqueTarget.UnitAction),
+ ConstructImprovementInstantly("Can instantly construct a [improvementFilter] improvement", UniqueTarget.UnitAction),
+ CanSpreadReligion("Can Spread Religion", UniqueTarget.UnitAction),
+ CanRemoveHeresy("Can remove other religions from cities", UniqueTarget.UnitAction),
+ MayFoundReligion("May found a religion", UniqueTarget.UnitAction),
+ MayEnhanceReligion("May enhance a religion", UniqueTarget.UnitAction),
- ConstructImprovementInstantly("Can instantly construct a [improvementFilter] improvement", UniqueTarget.Unit),
BuildImprovements("Can build [improvementFilter/terrainFilter] improvements on tiles", UniqueTarget.Unit),
CreateWaterImprovements("May create improvements on water resources", UniqueTarget.Unit),
- MayFoundReligion("May found a religion", UniqueTarget.Unit),
- MayEnhanceReligion("May enhance a religion", UniqueTarget.Unit),
-
AddInCapital("Can be added to [comment] in the Capital", UniqueTarget.Unit),
PreventSpreadingReligion("Prevents spreading of religion to the city it is next to", UniqueTarget.Unit),
RemoveOtherReligions("Removes other religions when spreading religion", UniqueTarget.Unit),
MayParadrop("May Paradrop up to [amount] tiles from inside friendly territory", UniqueTarget.Unit),
CanAirsweep("Can perform Air Sweep", UniqueTarget.Unit),
+
+ @Deprecated("As of 4.8.9", ReplaceWith("Can Spread Religion <[amount] times> \" OR \"Can remove other religions from cities "))
CanActionSeveralTimes("Can [action] [amount] times", UniqueTarget.Unit),
+
CanSpeedupConstruction("Can speed up construction of a building", UniqueTarget.Unit),
CanSpeedupWonderConstruction("Can speed up the construction of a wonder", UniqueTarget.Unit),
CanHurryResearch("Can hurry technology research", UniqueTarget.Unit),
@@ -756,12 +760,15 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
//endregion
///////////////////////////////////////////// region 90 META /////////////////////////////////////////////
- HiddenWithoutReligion("Hidden when religion is disabled", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Ruins, flags = UniqueFlag.setOfHiddenToUsers),
+ HiddenWithoutReligion("Hidden when religion is disabled",
+ UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Ruins, UniqueTarget.Tutorial,
+ flags = UniqueFlag.setOfHiddenToUsers),
HiddenAfterGreatProphet("Hidden after generating a Great Prophet", UniqueTarget.Ruins),
HiddenWithoutVictoryType("Hidden when [victoryType] Victory is disabled", UniqueTarget.Building, UniqueTarget.Unit, flags = UniqueFlag.setOfHiddenToUsers),
- HiddenFromCivilopedia("Will not be displayed in Civilopedia", UniqueTarget.Building, UniqueTarget.Unit, UniqueTarget.UnitType, UniqueTarget.Improvement,
- UniqueTarget.Tech, UniqueTarget.Terrain, UniqueTarget.Resource, UniqueTarget.Policy, UniqueTarget.Promotion,
+ HiddenFromCivilopedia("Will not be displayed in Civilopedia", UniqueTarget.Building,
+ UniqueTarget.Unit, UniqueTarget.UnitType, UniqueTarget.Improvement, UniqueTarget.Tech,
+ UniqueTarget.Terrain, UniqueTarget.Resource, UniqueTarget.Policy, UniqueTarget.Promotion,
UniqueTarget.Nation, UniqueTarget.Ruins, flags = UniqueFlag.setOfHiddenToUsers),
// Declarative Mod compatibility (so far rudimentary):
@@ -1186,28 +1193,14 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
abstract fun getRulesetErrorSeverity(severityToReport: UniqueComplianceErrorSeverity): RulesetErrorSeverity
}
- /** Maps uncompliant parameters to their required types */
- fun getComplianceErrors(
- unique: Unique,
- ruleset: Ruleset
- ): List {
- val errorList = ArrayList()
- for ((index, param) in unique.params.withIndex()) {
- val acceptableParamTypes = parameterTypeMap[index]
- val errorTypesForAcceptableParameters =
- acceptableParamTypes.map { it.getErrorSeverity(param, ruleset) }
- if (errorTypesForAcceptableParameters.any { it == null }) continue // This matches one of the types!
- val leastSevereWarning =
- errorTypesForAcceptableParameters.minByOrNull { it!!.ordinal }!!
- errorList += UniqueComplianceError(param, acceptableParamTypes, leastSevereWarning)
- }
- return errorList
- }
-
fun getDeprecationAnnotation(): Deprecated? = declaringJavaClass.getField(name)
.getAnnotation(Deprecated::class.java)
/** Checks whether a specific [uniqueTarget] as e.g. given by [IHasUniques.getUniqueTarget] works with `this` UniqueType */
fun canAcceptUniqueTarget(uniqueTarget: UniqueTarget) =
targetTypes.any { uniqueTarget.canAcceptUniqueTarget(it) }
+
+ companion object {
+ val uniqueTypeMap: Map = UniqueType.values().associateBy { it.placeholderText }
+ }
}
diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt
index a445af16e806f..9f0ecf4bb2bee 100644
--- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt
+++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt
@@ -216,8 +216,6 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
if (boughtWith != null && !civInfo.gameInfo.gameParameters.godMode && !unit.hasUnique(UniqueType.MoveImmediatelyOnceBought))
unit.currentMovement = 0f
- if (this.isCivilian()) return true // tiny optimization makes save files a few bytes smaller
-
addConstructionBonuses(unit, cityConstructions)
return true
diff --git a/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt b/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt
index 7ba14f03339fa..4004d18d51ea2 100644
--- a/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt
+++ b/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt
@@ -8,203 +8,338 @@ import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.logic.map.tile.RoadStatus
import com.unciv.models.metadata.BaseRuleset
-import com.unciv.models.ruleset.IRulesetObject
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.nation.getContrastRatio
import com.unciv.models.ruleset.nation.getRelativeLuminance
import com.unciv.models.ruleset.tile.TerrainType
-import com.unciv.models.ruleset.unique.IHasUniques
import com.unciv.models.ruleset.unique.StateForConditionals
-import com.unciv.models.ruleset.unique.Unique
-import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.ruleset.unit.Promotion
-import com.unciv.models.stats.INamed
import com.unciv.models.stats.Stats
import com.unciv.models.tilesets.TileSetCache
import com.unciv.models.tilesets.TileSetConfig
class RulesetValidator(val ruleset: Ruleset) {
+ val uniqueValidator = UniqueValidator(ruleset)
+
fun getErrorList(tryFixUnknownUniques: Boolean = false): RulesetErrorList {
val lines = RulesetErrorList()
/********************** Ruleset Invariant Part **********************/
- // Checks for all mods - only those that can succeed without loading a base ruleset
+ // Checks for ALL MODS - only those that can succeed without loading a base ruleset
// When not checking the entire ruleset, we can only really detect ruleset-invariant errors in uniques
val rulesetInvariant = UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant
val rulesetSpecific = UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
- checkUniques(ruleset.globalUniques, lines, rulesetInvariant, tryFixUnknownUniques)
-
- for (unit in ruleset.units.values) {
- if (unit.upgradesTo == unit.name || (unit.upgradesTo != null && unit.upgradesTo == unit.replaces))
- lines += "${unit.name} upgrades to itself!"
- if (!unit.isCivilian() && unit.strength == 0)
- lines += "${unit.name} is a military unit but has no assigned strength!"
- if (unit.isRanged() && unit.rangedStrength == 0 && !unit.hasUnique(UniqueType.CannotAttack))
- lines += "${unit.name} is a ranged unit but has no assigned rangedStrength!"
+ uniqueValidator.checkUniques(ruleset.globalUniques, lines, rulesetInvariant, tryFixUnknownUniques)
+ addUnitErrorsRulesetInvariant(lines, rulesetInvariant, tryFixUnknownUniques)
+ addTechErrorsRulesetInvariant(lines, rulesetInvariant, tryFixUnknownUniques)
+ addTechColumnErrorsRulesetInvariant(lines)
+ addBuildingErrorsRulesetInvariant(lines, rulesetInvariant, tryFixUnknownUniques)
+ addNationErrorsRulesetInvariant(lines, rulesetInvariant, tryFixUnknownUniques)
+ addPromotionErrorsRulesetInvariant(lines, rulesetInvariant, tryFixUnknownUniques)
+ addResourceErrorsRulesetInvariant(lines, rulesetInvariant, tryFixUnknownUniques)
- checkUniques(unit, lines, rulesetInvariant, tryFixUnknownUniques)
+ /********************** Tileset tests **********************/
+ // e.g. json configs complete and parseable
+ // Check for mod or Civ_V_GnK to avoid running the same test twice (~200ms for the builtin assets)
+ if (ruleset.folderLocation != null || ruleset.name == BaseRuleset.Civ_V_GnK.fullName) {
+ checkTilesetSanity(lines)
}
- for (tech in ruleset.technologies.values) {
- for (otherTech in ruleset.technologies.values) {
- if (tech != otherTech && otherTech.column?.columnNumber == tech.column?.columnNumber && otherTech.row == tech.row)
- lines += "${tech.name} is in the same row and column as ${otherTech.name}!"
- }
+ // Quit here when no base ruleset is loaded - references cannot be checked
+ if (!ruleset.modOptions.isBaseRuleset) return lines
- checkUniques(tech, lines, rulesetInvariant, tryFixUnknownUniques)
- }
+ /********************** Ruleset Specific Part **********************/
- for (techColumn in ruleset.techColumns){
- if (techColumn.columnNumber < 0)
- lines += "Tech Column number ${techColumn.columnNumber} is negative"
- if (techColumn.buildingCost == -1)
- lines.add("Tech Column number ${techColumn.columnNumber} has no explicit building cost",
- RulesetErrorSeverity.Warning)
- if (techColumn.wonderCost == -1)
- lines.add("Tech Column number ${techColumn.columnNumber} has no explicit wonder cost",
- RulesetErrorSeverity.Warning)
- }
+ uniqueValidator.checkUniques(ruleset.globalUniques, lines, rulesetSpecific, tryFixUnknownUniques)
+
+ addUnitErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addBuildingErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addSpecialistErrors(lines)
+ addResourceErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addImprovementErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addTerrainErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addTechErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addEraErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addSpeedErrors(lines)
+ addBeliefErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addNationErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addPolicyErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addRuinsErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addPromotionErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addUnitTypeErrors(lines, rulesetSpecific, tryFixUnknownUniques)
+ addVictoryTypeErrors(lines)
+ addDifficutlyErrors(lines)
+ addCityStateTypeErrors(tryFixUnknownUniques, rulesetSpecific, lines)
- for (building in ruleset.buildings.values) {
- if (building.requiredTech == null && building.cost == -1 && !building.hasUnique(
- UniqueType.Unbuildable))
- lines.add("${building.name} is buildable and therefore should either have an explicit cost or reference an existing tech!",
- RulesetErrorSeverity.Warning)
+ return lines
+ }
- for (gpp in building.greatPersonPoints)
- if (gpp.key !in ruleset.units)
- lines.add("Building ${building.name} has greatPersonPoints for ${gpp.key}, which is not a unit in the ruleset!",
- RulesetErrorSeverity.Warning)
+ private fun addCityStateTypeErrors(
+ tryFixUnknownUniques: Boolean,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ lines: RulesetErrorList
+ ) {
+ for (cityStateType in ruleset.cityStateTypes.values) {
+ for (unique in cityStateType.allyBonusUniqueMap.getAllUniques() + cityStateType.friendBonusUniqueMap.getAllUniques()) {
+ val errors = uniqueValidator.checkUnique(
+ unique,
+ tryFixUnknownUniques,
+ cityStateType,
+ rulesetSpecific
+ )
+ lines.addAll(errors)
+ }
+ }
+ }
- checkUniques(building, lines, rulesetInvariant, tryFixUnknownUniques)
+ private fun addDifficutlyErrors(lines: RulesetErrorList) {
+ for (difficulty in ruleset.difficulties.values) {
+ for (unitName in difficulty.aiCityStateBonusStartingUnits + difficulty.aiMajorCivBonusStartingUnits + difficulty.playerBonusStartingUnits)
+ if (unitName != Constants.eraSpecificUnit && !ruleset.units.containsKey(unitName))
+ lines += "Difficulty ${difficulty.name} contains starting unit $unitName which does not exist!"
+ }
+ }
+ private fun addVictoryTypeErrors(lines: RulesetErrorList) {
+ for (victoryType in ruleset.victories.values) {
+ for (requiredUnit in victoryType.requiredSpaceshipParts)
+ if (!ruleset.units.contains(requiredUnit))
+ lines.add(
+ "Victory type ${victoryType.name} requires adding the non-existant unit $requiredUnit to the capital to win!",
+ RulesetErrorSeverity.Warning
+ )
+ for (milestone in victoryType.milestoneObjects)
+ if (milestone.type == null)
+ lines.add(
+ "Victory type ${victoryType.name} has milestone ${milestone.uniqueDescription} that is of an unknown type!",
+ RulesetErrorSeverity.Error
+ )
+ for (victory in ruleset.victories.values)
+ if (victory.name != victoryType.name && victory.milestones == victoryType.milestones)
+ lines.add(
+ "Victory types ${victoryType.name} and ${victory.name} have the same requirements!",
+ RulesetErrorSeverity.Warning
+ )
}
+ }
- for (nation in ruleset.nations.values) {
- if (nation.cities.isEmpty() && !nation.isSpectator && !nation.isBarbarian) {
- lines += "${nation.name} can settle cities, but has no city names!"
+ private fun addUnitTypeErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (unitType in ruleset.unitTypes.values) {
+ uniqueValidator.checkUniques(unitType, lines, rulesetSpecific, tryFixUnknownUniques)
+ }
+ }
+
+ private fun addPromotionErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (promotion in ruleset.unitPromotions.values) {
+ // These are warning as of 3.17.5 to not break existing mods and give them time to correct, should be upgraded to error in the future
+ for (prereq in promotion.prerequisites)
+ if (!ruleset.unitPromotions.containsKey(prereq))
+ lines.add(
+ "${promotion.name} requires promotion $prereq which does not exist!",
+ RulesetErrorSeverity.Warning
+ )
+ for (unitType in promotion.unitTypes) checkUnitType(unitType) {
+ lines.add(
+ "${promotion.name} references unit type $unitType, which does not exist!",
+ RulesetErrorSeverity.Warning
+ )
}
+ uniqueValidator.checkUniques(promotion, lines, rulesetSpecific, tryFixUnknownUniques)
+ }
+ checkPromotionCircularReferences(lines)
+ }
- // https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast
- val constrastRatio = nation.getContrastRatio()
- if (constrastRatio < 3) {
- val innerColorLuminance = getRelativeLuminance(nation.getInnerColor())
- val outerColorLuminance = getRelativeLuminance(nation.getOuterColor())
+ private fun addRuinsErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (reward in ruleset.ruinRewards.values) {
+ for (difficulty in reward.excludedDifficulties)
+ if (!ruleset.difficulties.containsKey(difficulty))
+ lines += "${reward.name} references difficulty ${difficulty}, which does not exist!"
+ uniqueValidator.checkUniques(reward, lines, rulesetSpecific, tryFixUnknownUniques)
+ }
+ }
- val innerLerpColor:Color
- val outerLerpColor:Color
+ private fun addPolicyErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (policy in ruleset.policies.values) {
+ if (policy.requires != null)
+ for (prereq in policy.requires!!)
+ if (!ruleset.policies.containsKey(prereq))
+ lines += "${policy.name} requires policy $prereq which does not exist!"
+ uniqueValidator.checkUniques(policy, lines, rulesetSpecific, tryFixUnknownUniques)
+ }
- if (innerColorLuminance > outerColorLuminance) { // inner is brighter
- innerLerpColor = Color.WHITE
- outerLerpColor = Color.BLACK
- }
- else {
- innerLerpColor = Color.BLACK
- outerLerpColor = Color.WHITE
- }
+ for (branch in ruleset.policyBranches.values)
+ if (branch.era !in ruleset.eras)
+ lines += "${branch.name} requires era ${branch.era} which does not exist!"
- var text = "${nation.name}'s colors do not contrast enough - it is unreadable!"
- for (i in 1..10){
- val newInnerColor = nation.getInnerColor().cpy().lerp(innerLerpColor, 0.05f *i)
- val newOuterColor = nation.getOuterColor().cpy().lerp(outerLerpColor, 0.05f *i)
+ for (policy in ruleset.policyBranches.values.flatMap { it.policies + it })
+ if (policy != ruleset.policies[policy.name])
+ lines += "More than one policy with the name ${policy.name} exists!"
- if (getContrastRatio(newInnerColor, newOuterColor) > 3){
- text += "\nSuggested colors: "
- text += "\n\t\t\"outerColor\": [${(newOuterColor.r*255).toInt()}, ${(newOuterColor.g*255).toInt()}, ${(newOuterColor.b*255).toInt()}],"
- text += "\n\t\t\"innerColor\": [${(newInnerColor.r*255).toInt()}, ${(newInnerColor.g*255).toInt()}, ${(newInnerColor.b*255).toInt()}],"
- break
- }
- }
+ }
- lines.add(
- text, RulesetErrorSeverity.WarningOptionsOnly
- )
- }
+ private fun addNationErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (nation in ruleset.nations.values) {
+ uniqueValidator.checkUniques(nation, lines, rulesetSpecific, tryFixUnknownUniques)
- checkUniques(nation, lines, rulesetInvariant, tryFixUnknownUniques)
+ if (nation.cityStateType != null && nation.cityStateType !in ruleset.cityStateTypes)
+ lines += "${nation.name} is of city-state type ${nation.cityStateType} which does not exist!"
+ if (nation.favoredReligion != null && nation.favoredReligion !in ruleset.religions)
+ lines += "${nation.name} has ${nation.favoredReligion} as their favored religion, which does not exist!"
}
+ }
- for (promotion in ruleset.unitPromotions.values) {
- checkUniques(promotion, lines, rulesetInvariant, tryFixUnknownUniques)
- if (promotion.row < -1) lines += "Promotion ${promotion.name} has invalid row value: ${promotion.row}"
- if (promotion.column < 0) lines += "Promotion ${promotion.name} has invalid column value: ${promotion.column}"
- if (promotion.row == -1) continue
- for (otherPromotion in ruleset.unitPromotions.values)
- if (promotion != otherPromotion && promotion.column == otherPromotion.column && promotion.row == otherPromotion.row)
- lines += "Promotions ${promotion.name} and ${otherPromotion.name} have the same position: ${promotion.row}/${promotion.column}"
+ private fun addBeliefErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (belief in ruleset.beliefs.values) {
+ uniqueValidator.checkUniques(belief, lines, rulesetSpecific, tryFixUnknownUniques)
}
+ }
- for (resource in ruleset.tileResources.values) {
- checkUniques(resource, lines, rulesetInvariant, tryFixUnknownUniques)
+ private fun addSpeedErrors(lines: RulesetErrorList) {
+ for (speed in ruleset.speeds.values) {
+ if (speed.modifier < 0f)
+ lines += "Negative speed modifier for game speed ${speed.name}"
+ if (speed.yearsPerTurn.isEmpty())
+ lines += "Empty turn increment list for game speed ${speed.name}"
}
+ }
- /********************** Tileset tests **********************/
- // e.g. json configs complete and parseable
- // Check for mod or Civ_V_GnK to avoid running the same test twice (~200ms for the builtin assets)
- if (ruleset.folderLocation != null || ruleset.name == BaseRuleset.Civ_V_GnK.fullName) {
- checkTilesetSanity(lines)
+ private fun addEraErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ if (ruleset.eras.isEmpty()) {
+ lines += "Eras file is empty! This will likely lead to crashes. Ask the mod maker to update this mod!"
}
- // Quit here when no base ruleset is loaded - references cannot be checked
- if (!ruleset.modOptions.isBaseRuleset) return lines
+ val allDifficultiesStartingUnits = hashSetOf()
+ for (difficulty in ruleset.difficulties.values) {
+ allDifficultiesStartingUnits.addAll(difficulty.aiCityStateBonusStartingUnits)
+ allDifficultiesStartingUnits.addAll(difficulty.aiMajorCivBonusStartingUnits)
+ allDifficultiesStartingUnits.addAll(difficulty.playerBonusStartingUnits)
+ }
- /********************** Ruleset Specific Part **********************/
+ for (era in ruleset.eras.values) {
+ for (wonder in era.startingObsoleteWonders)
+ if (wonder !in ruleset.buildings)
+ lines += "Nonexistent wonder $wonder obsoleted when starting in ${era.name}!"
+ for (building in era.settlerBuildings)
+ if (building !in ruleset.buildings)
+ lines += "Nonexistent building $building built by settlers when starting in ${era.name}"
+ // todo the whole 'starting unit' thing needs to be redone, there's no reason we can't have a single list containing all the starting units.
+ if (era.startingSettlerUnit !in ruleset.units
+ && ruleset.units.values.none { it.isCityFounder() }
+ )
+ lines += "Nonexistent unit ${era.startingSettlerUnit} marked as starting unit when starting in ${era.name}"
+ if (era.startingWorkerCount != 0 && era.startingWorkerUnit !in ruleset.units
+ && ruleset.units.values.none { it.hasUnique(UniqueType.BuildImprovements) }
+ )
+ lines += "Nonexistent unit ${era.startingWorkerUnit} marked as starting unit when starting in ${era.name}"
- checkUniques(ruleset.globalUniques, lines, rulesetSpecific, tryFixUnknownUniques)
+ if ((era.startingMilitaryUnitCount != 0 || allDifficultiesStartingUnits.contains(
+ Constants.eraSpecificUnit
+ )) && era.startingMilitaryUnit !in ruleset.units
+ )
+ lines += "Nonexistent unit ${era.startingMilitaryUnit} marked as starting unit when starting in ${era.name}"
+ if (era.researchAgreementCost < 0 || era.startingSettlerCount < 0 || era.startingWorkerCount < 0 || era.startingMilitaryUnitCount < 0 || era.startingGold < 0 || era.startingCulture < 0)
+ lines += "Unexpected negative number found while parsing era ${era.name}"
+ if (era.settlerPopulation <= 0)
+ lines += "Population in cities from settlers must be strictly positive! Found value ${era.settlerPopulation} for era ${era.name}"
- if (ruleset.units.values.none { it.isCityFounder() })
- lines += "No city-founding units in ruleset!"
+ if (era.allyBonus.isNotEmpty())
+ lines.add(
+ "Era ${era.name} contains city-state bonuses. City-state bonuses are now defined in CityStateType.json",
+ RulesetErrorSeverity.WarningOptionsOnly
+ )
+ if (era.friendBonus.isNotEmpty())
+ lines.add(
+ "Era ${era.name} contains city-state bonuses. City-state bonuses are now defined in CityStateType.json",
+ RulesetErrorSeverity.WarningOptionsOnly
+ )
- for (unit in ruleset.units.values) {
- checkUnitRulesetSpecific(unit, lines)
- checkUniques(unit, lines, rulesetSpecific, tryFixUnknownUniques)
+ uniqueValidator.checkUniques(era, lines, rulesetSpecific, tryFixUnknownUniques)
}
+ }
- for (building in ruleset.buildings.values) {
- if (building.requiredTech != null && !ruleset.technologies.containsKey(building.requiredTech!!))
- lines += "${building.name} requires tech ${building.requiredTech} which does not exist!"
+ private fun addTechErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (tech in ruleset.technologies.values) {
+ for (prereq in tech.prerequisites) {
+ if (!ruleset.technologies.containsKey(prereq))
+ lines += "${tech.name} requires tech $prereq which does not exist!"
- for (specialistName in building.specialistSlots.keys)
- if (!ruleset.specialists.containsKey(specialistName))
- lines += "${building.name} provides specialist $specialistName which does not exist!"
- for (resource in building.getResourceRequirementsPerTurn().keys)
- if (!ruleset.tileResources.containsKey(resource))
- lines += "${building.name} requires resource $resource which does not exist!"
- if (building.replaces != null && !ruleset.buildings.containsKey(building.replaces!!))
- lines += "${building.name} replaces ${building.replaces} which does not exist!"
- if (building.requiredBuilding != null && !ruleset.buildings.containsKey(building.requiredBuilding!!))
- lines += "${building.name} requires ${building.requiredBuilding} which does not exist!"
- checkUniques(building, lines, rulesetSpecific, tryFixUnknownUniques)
- }
- for (specialist in ruleset.specialists.values){
- for (gpp in specialist.greatPersonPoints)
- if (gpp.key !in ruleset.units)
- lines.add("Specialist ${specialist.name} has greatPersonPoints for ${gpp.key}, which is not a unit in the ruleset!",
- RulesetErrorSeverity.Warning)
+ if (tech.prerequisites.asSequence().filterNot { it == prereq }
+ .any { getPrereqTree(it).contains(prereq) }) {
+ lines.add(
+ "No need to add $prereq as a prerequisite of ${tech.name} - it is already implicit from the other prerequisites!",
+ RulesetErrorSeverity.Warning
+ )
+ }
+
+ if (getPrereqTree(prereq).contains(tech.name))
+ lines += "Techs ${tech.name} and $prereq require each other!"
+ }
+ if (tech.era() !in ruleset.eras)
+ lines += "Unknown era ${tech.era()} referenced in column of tech ${tech.name}"
+ uniqueValidator.checkUniques(tech, lines, rulesetSpecific, tryFixUnknownUniques)
}
+ }
- for (resource in ruleset.tileResources.values) {
- if (resource.revealedBy != null && !ruleset.technologies.containsKey(resource.revealedBy!!))
- lines += "${resource.name} revealed by tech ${resource.revealedBy} which does not exist!"
- if (resource.improvement != null && !ruleset.tileImprovements.containsKey(resource.improvement!!))
- lines += "${resource.name} improved by improvement ${resource.improvement} which does not exist!"
- for (improvement in resource.improvedBy)
- if (!ruleset.tileImprovements.containsKey(improvement))
- lines += "${resource.name} improved by improvement $improvement which does not exist!"
- for (terrain in resource.terrainsCanBeFoundOn)
- if (!ruleset.terrains.containsKey(terrain))
- lines += "${resource.name} can be found on terrain $terrain which does not exist!"
- checkUniques(resource, lines, rulesetSpecific, tryFixUnknownUniques)
+ private fun addTerrainErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ if (ruleset.terrains.values.none { it.type == TerrainType.Land && !it.impassable })
+ lines += "No passable land terrains exist!"
+ for (terrain in ruleset.terrains.values) {
+ for (baseTerrain in terrain.occursOn)
+ if (!ruleset.terrains.containsKey(baseTerrain))
+ lines += "${terrain.name} occurs on terrain $baseTerrain which does not exist!"
+ uniqueValidator.checkUniques(terrain, lines, rulesetSpecific, tryFixUnknownUniques)
}
+ }
+ private fun addImprovementErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
for (improvement in ruleset.tileImprovements.values) {
if (improvement.techRequired != null && !ruleset.technologies.containsKey(improvement.techRequired!!))
lines += "${improvement.name} requires tech ${improvement.techRequired} which does not exist!"
@@ -212,11 +347,11 @@ class RulesetValidator(val ruleset: Ruleset) {
if (!ruleset.terrains.containsKey(terrain) && terrain != "Land" && terrain != "Water")
lines += "${improvement.name} can be built on terrain $terrain which does not exist!"
if (improvement.terrainsCanBeBuiltOn.isEmpty()
- && !improvement.hasUnique(UniqueType.CanOnlyImproveResource)
- && !improvement.hasUnique(UniqueType.Unbuildable)
- && !improvement.name.startsWith(Constants.remove)
- && improvement.name !in RoadStatus.values().map { it.removeAction }
- && improvement.name != Constants.cancelImprovementOrder
+ && !improvement.hasUnique(UniqueType.CanOnlyImproveResource)
+ && !improvement.hasUnique(UniqueType.Unbuildable)
+ && !improvement.name.startsWith(Constants.remove)
+ && improvement.name !in RoadStatus.values().map { it.removeAction }
+ && improvement.name != Constants.cancelImprovementOrder
) {
lines.add(
"${improvement.name} has an empty `terrainsCanBeBuiltOn`, isn't allowed to only improve resources and isn't unbuildable! Support for this will soon end. Either give this the unique \"Unbuildable\", \"Can only be built to improve a resource\" or add \"Land\", \"Water\" or any other value to `terrainsCanBeBuiltOn`.",
@@ -233,186 +368,246 @@ class RulesetValidator(val ruleset: Ruleset) {
)
}
}
- if ((improvement.hasUnique(UniqueType.PillageYieldRandom, StateForConditionals.IgnoreConditionals)
- || improvement.hasUnique(UniqueType.PillageYieldFixed, StateForConditionals.IgnoreConditionals))
- && improvement.hasUnique(UniqueType.Unpillagable, StateForConditionals.IgnoreConditionals)) {
+ if ((improvement.hasUnique(
+ UniqueType.PillageYieldRandom,
+ StateForConditionals.IgnoreConditionals
+ )
+ || improvement.hasUnique(
+ UniqueType.PillageYieldFixed,
+ StateForConditionals.IgnoreConditionals
+ ))
+ && improvement.hasUnique(
+ UniqueType.Unpillagable,
+ StateForConditionals.IgnoreConditionals
+ )
+ ) {
lines.add(
"${improvement.name} has both an `Unpillagable` unique type and a `PillageYieldRandom` or `PillageYieldFixed` unique type!",
RulesetErrorSeverity.Warning
)
}
- checkUniques(improvement, lines, rulesetSpecific, tryFixUnknownUniques)
+ uniqueValidator.checkUniques(improvement, lines, rulesetSpecific, tryFixUnknownUniques)
}
+ }
- if (ruleset.terrains.values.none { it.type == TerrainType.Land && !it.impassable })
- lines += "No passable land terrains exist!"
- for (terrain in ruleset.terrains.values) {
- for (baseTerrain in terrain.occursOn)
- if (!ruleset.terrains.containsKey(baseTerrain))
- lines += "${terrain.name} occurs on terrain $baseTerrain which does not exist!"
- checkUniques(terrain, lines, rulesetSpecific, tryFixUnknownUniques)
+ private fun addResourceErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (resource in ruleset.tileResources.values) {
+ if (resource.revealedBy != null && !ruleset.technologies.containsKey(resource.revealedBy!!))
+ lines += "${resource.name} revealed by tech ${resource.revealedBy} which does not exist!"
+ if (resource.improvement != null && !ruleset.tileImprovements.containsKey(resource.improvement!!))
+ lines += "${resource.name} improved by improvement ${resource.improvement} which does not exist!"
+ for (improvement in resource.improvedBy)
+ if (!ruleset.tileImprovements.containsKey(improvement))
+ lines += "${resource.name} improved by improvement $improvement which does not exist!"
+ for (terrain in resource.terrainsCanBeFoundOn)
+ if (!ruleset.terrains.containsKey(terrain))
+ lines += "${resource.name} can be found on terrain $terrain which does not exist!"
+ uniqueValidator.checkUniques(resource, lines, rulesetSpecific, tryFixUnknownUniques)
+ }
+ }
+
+ private fun addSpecialistErrors(lines: RulesetErrorList) {
+ for (specialist in ruleset.specialists.values) {
+ for (gpp in specialist.greatPersonPoints)
+ if (gpp.key !in ruleset.units)
+ lines.add(
+ "Specialist ${specialist.name} has greatPersonPoints for ${gpp.key}, which is not a unit in the ruleset!",
+ RulesetErrorSeverity.Warning
+ )
}
+ }
- for (tech in ruleset.technologies.values) {
- for (prereq in tech.prerequisites) {
- if (!ruleset.technologies.containsKey(prereq))
- lines += "${tech.name} requires tech $prereq which does not exist!"
+ private fun addBuildingErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (building in ruleset.buildings.values) {
+ if (building.requiredTech != null && !ruleset.technologies.containsKey(building.requiredTech!!))
+ lines += "${building.name} requires tech ${building.requiredTech} which does not exist!"
+ for (specialistName in building.specialistSlots.keys)
+ if (!ruleset.specialists.containsKey(specialistName))
+ lines += "${building.name} provides specialist $specialistName which does not exist!"
+ for (resource in building.getResourceRequirementsPerTurn().keys)
+ if (!ruleset.tileResources.containsKey(resource))
+ lines += "${building.name} requires resource $resource which does not exist!"
+ if (building.replaces != null && !ruleset.buildings.containsKey(building.replaces!!))
+ lines += "${building.name} replaces ${building.replaces} which does not exist!"
+ if (building.requiredBuilding != null && !ruleset.buildings.containsKey(building.requiredBuilding!!))
+ lines += "${building.name} requires ${building.requiredBuilding} which does not exist!"
+ uniqueValidator.checkUniques(building, lines, rulesetSpecific, tryFixUnknownUniques)
+ }
+ }
- if (tech.prerequisites.asSequence().filterNot { it == prereq }
- .any { getPrereqTree(it).contains(prereq) }){
- lines.add("No need to add $prereq as a prerequisite of ${tech.name} - it is already implicit from the other prerequisites!",
- RulesetErrorSeverity.Warning)
- }
+ private fun addUnitErrors(
+ lines: RulesetErrorList,
+ rulesetSpecific: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ if (ruleset.units.values.none { it.isCityFounder() })
+ lines += "No city-founding units in ruleset!"
- if (getPrereqTree(prereq).contains(tech.name))
- lines += "Techs ${tech.name} and $prereq require each other!"
- }
- if (tech.era() !in ruleset.eras)
- lines += "Unknown era ${tech.era()} referenced in column of tech ${tech.name}"
- checkUniques(tech, lines, rulesetSpecific, tryFixUnknownUniques)
+ for (unit in ruleset.units.values) {
+ checkUnitRulesetSpecific(unit, lines)
+ uniqueValidator.checkUniques(unit, lines, rulesetSpecific, tryFixUnknownUniques)
}
+ }
- if (ruleset.eras.isEmpty()) {
- lines += "Eras file is empty! This will likely lead to crashes. Ask the mod maker to update this mod!"
+ private fun addResourceErrorsRulesetInvariant(
+ lines: RulesetErrorList,
+ rulesetInvariant: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (resource in ruleset.tileResources.values) {
+ uniqueValidator.checkUniques(resource, lines, rulesetInvariant, tryFixUnknownUniques)
}
+ }
- val allDifficultiesStartingUnits = hashSetOf()
- for (difficulty in ruleset.difficulties.values){
- allDifficultiesStartingUnits.addAll(difficulty.aiCityStateBonusStartingUnits)
- allDifficultiesStartingUnits.addAll(difficulty.aiMajorCivBonusStartingUnits)
- allDifficultiesStartingUnits.addAll(difficulty.playerBonusStartingUnits)
+ private fun addPromotionErrorsRulesetInvariant(
+ lines: RulesetErrorList,
+ rulesetInvariant: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (promotion in ruleset.unitPromotions.values) {
+ uniqueValidator.checkUniques(promotion, lines, rulesetInvariant, tryFixUnknownUniques)
+ if (promotion.row < -1) lines += "Promotion ${promotion.name} has invalid row value: ${promotion.row}"
+ if (promotion.column < 0) lines += "Promotion ${promotion.name} has invalid column value: ${promotion.column}"
+ if (promotion.row == -1) continue
+ for (otherPromotion in ruleset.unitPromotions.values)
+ if (promotion != otherPromotion && promotion.column == otherPromotion.column && promotion.row == otherPromotion.row)
+ lines += "Promotions ${promotion.name} and ${otherPromotion.name} have the same position: ${promotion.row}/${promotion.column}"
}
+ }
- for (era in ruleset.eras.values) {
- for (wonder in era.startingObsoleteWonders)
- if (wonder !in ruleset.buildings)
- lines += "Nonexistent wonder $wonder obsoleted when starting in ${era.name}!"
- for (building in era.settlerBuildings)
- if (building !in ruleset.buildings)
- lines += "Nonexistent building $building built by settlers when starting in ${era.name}"
- // todo the whole 'starting unit' thing needs to be redone, there's no reason we can't have a single list containing all the starting units.
- if (era.startingSettlerUnit !in ruleset.units
- && ruleset.units.values.none { it.isCityFounder() })
- lines += "Nonexistent unit ${era.startingSettlerUnit} marked as starting unit when starting in ${era.name}"
- if (era.startingWorkerCount != 0 && era.startingWorkerUnit !in ruleset.units
- && ruleset.units.values.none { it.hasUnique(UniqueType.BuildImprovements) })
- lines += "Nonexistent unit ${era.startingWorkerUnit} marked as starting unit when starting in ${era.name}"
+ private fun addNationErrorsRulesetInvariant(
+ lines: RulesetErrorList,
+ rulesetInvariant: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (nation in ruleset.nations.values) {
+ if (nation.cities.isEmpty() && !nation.isSpectator && !nation.isBarbarian) {
+ lines += "${nation.name} can settle cities, but has no city names!"
+ }
- if ((era.startingMilitaryUnitCount != 0 || allDifficultiesStartingUnits.contains(
- Constants.eraSpecificUnit)) && era.startingMilitaryUnit !in ruleset.units)
- lines += "Nonexistent unit ${era.startingMilitaryUnit} marked as starting unit when starting in ${era.name}"
- if (era.researchAgreementCost < 0 || era.startingSettlerCount < 0 || era.startingWorkerCount < 0 || era.startingMilitaryUnitCount < 0 || era.startingGold < 0 || era.startingCulture < 0)
- lines += "Unexpected negative number found while parsing era ${era.name}"
- if (era.settlerPopulation <= 0)
- lines += "Population in cities from settlers must be strictly positive! Found value ${era.settlerPopulation} for era ${era.name}"
+ // https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast
+ val constrastRatio = nation.getContrastRatio()
+ if (constrastRatio < 3) {
+ val innerColorLuminance = getRelativeLuminance(nation.getInnerColor())
+ val outerColorLuminance = getRelativeLuminance(nation.getOuterColor())
- if (era.allyBonus.isNotEmpty())
- lines.add("Era ${era.name} contains city-state bonuses. City-state bonuses are now defined in CityStateType.json",
- RulesetErrorSeverity.WarningOptionsOnly)
- if (era.friendBonus.isNotEmpty())
- lines.add("Era ${era.name} contains city-state bonuses. City-state bonuses are now defined in CityStateType.json",
- RulesetErrorSeverity.WarningOptionsOnly)
+ val innerLerpColor: Color
+ val outerLerpColor: Color
- checkUniques(era, lines, rulesetSpecific, tryFixUnknownUniques)
- }
+ if (innerColorLuminance > outerColorLuminance) { // inner is brighter
+ innerLerpColor = Color.WHITE
+ outerLerpColor = Color.BLACK
+ } else {
+ innerLerpColor = Color.BLACK
+ outerLerpColor = Color.WHITE
+ }
- for (speed in ruleset.speeds.values) {
- if (speed.modifier < 0f)
- lines += "Negative speed modifier for game speed ${speed.name}"
- if (speed.yearsPerTurn.isEmpty())
- lines += "Empty turn increment list for game speed ${speed.name}"
- }
+ var text = "${nation.name}'s colors do not contrast enough - it is unreadable!"
- for (belief in ruleset.beliefs.values) {
- checkUniques(belief, lines, rulesetSpecific, tryFixUnknownUniques)
- }
+ for (i in 1..10) {
+ val newInnerColor = nation.getInnerColor().cpy().lerp(innerLerpColor, 0.05f * i)
+ val newOuterColor = nation.getOuterColor().cpy().lerp(outerLerpColor, 0.05f * i)
- for (nation in ruleset.nations.values) {
- checkUniques(nation, lines, rulesetSpecific, tryFixUnknownUniques)
+ if (getContrastRatio(newInnerColor, newOuterColor) > 3) {
+ text += "\nSuggested colors: "
+ text += "\n\t\t\"outerColor\": [${(newOuterColor.r * 255).toInt()}, ${(newOuterColor.g * 255).toInt()}, ${(newOuterColor.b * 255).toInt()}],"
+ text += "\n\t\t\"innerColor\": [${(newInnerColor.r * 255).toInt()}, ${(newInnerColor.g * 255).toInt()}, ${(newInnerColor.b * 255).toInt()}],"
+ break
+ }
+ }
- if (nation.cityStateType!=null && nation.cityStateType !in ruleset.cityStateTypes)
- lines += "${nation.name} is of city-state type ${nation.cityStateType} which does not exist!"
- if (nation.favoredReligion != null && nation.favoredReligion !in ruleset.religions)
- lines += "${nation.name} has ${nation.favoredReligion} as their favored religion, which does not exist!"
- }
+ lines.add(
+ text, RulesetErrorSeverity.WarningOptionsOnly
+ )
+ }
- for (policy in ruleset.policies.values) {
- if (policy.requires != null)
- for (prereq in policy.requires!!)
- if (!ruleset.policies.containsKey(prereq))
- lines += "${policy.name} requires policy $prereq which does not exist!"
- checkUniques(policy, lines, rulesetSpecific, tryFixUnknownUniques)
+ uniqueValidator.checkUniques(nation, lines, rulesetInvariant, tryFixUnknownUniques)
}
+ }
- for (branch in ruleset.policyBranches.values)
- if (branch.era !in ruleset.eras)
- lines += "${branch.name} requires era ${branch.era} which does not exist!"
+ private fun addBuildingErrorsRulesetInvariant(
+ lines: RulesetErrorList,
+ rulesetInvariant: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (building in ruleset.buildings.values) {
+ if (building.requiredTech == null && building.cost == -1 && !building.hasUnique(
+ UniqueType.Unbuildable
+ )
+ )
+ lines.add(
+ "${building.name} is buildable and therefore should either have an explicit cost or reference an existing tech!",
+ RulesetErrorSeverity.Warning
+ )
+ for (gpp in building.greatPersonPoints)
+ if (gpp.key !in ruleset.units)
+ lines.add(
+ "Building ${building.name} has greatPersonPoints for ${gpp.key}, which is not a unit in the ruleset!",
+ RulesetErrorSeverity.Warning
+ )
- for (policy in ruleset.policyBranches.values.flatMap { it.policies + it })
- if (policy != ruleset.policies[policy.name])
- lines += "More than one policy with the name ${policy.name} exists!"
+ uniqueValidator.checkUniques(building, lines, rulesetInvariant, tryFixUnknownUniques)
- for (reward in ruleset.ruinRewards.values) {
- for (difficulty in reward.excludedDifficulties)
- if (!ruleset.difficulties.containsKey(difficulty))
- lines += "${reward.name} references difficulty ${difficulty}, which does not exist!"
- checkUniques(reward, lines, rulesetSpecific, tryFixUnknownUniques)
}
+ }
- for (promotion in ruleset.unitPromotions.values) {
- // These are warning as of 3.17.5 to not break existing mods and give them time to correct, should be upgraded to error in the future
- for (prereq in promotion.prerequisites)
- if (!ruleset.unitPromotions.containsKey(prereq))
- lines.add("${promotion.name} requires promotion $prereq which does not exist!",
- RulesetErrorSeverity.Warning)
- for (unitType in promotion.unitTypes) checkUnitType(unitType) {
- lines.add("${promotion.name} references unit type $unitType, which does not exist!",
- RulesetErrorSeverity.Warning)
- }
- checkUniques(promotion, lines, rulesetSpecific, tryFixUnknownUniques)
- checkPromotionCircularReferences(lines)
+ private fun addTechColumnErrorsRulesetInvariant(lines: RulesetErrorList) {
+ for (techColumn in ruleset.techColumns) {
+ if (techColumn.columnNumber < 0)
+ lines += "Tech Column number ${techColumn.columnNumber} is negative"
+ if (techColumn.buildingCost == -1)
+ lines.add(
+ "Tech Column number ${techColumn.columnNumber} has no explicit building cost",
+ RulesetErrorSeverity.Warning
+ )
+ if (techColumn.wonderCost == -1)
+ lines.add(
+ "Tech Column number ${techColumn.columnNumber} has no explicit wonder cost",
+ RulesetErrorSeverity.Warning
+ )
}
+ }
- for (unitType in ruleset.unitTypes.values) {
- checkUniques(unitType, lines, rulesetSpecific, tryFixUnknownUniques)
- }
+ private fun addTechErrorsRulesetInvariant(
+ lines: RulesetErrorList,
+ rulesetInvariant: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (tech in ruleset.technologies.values) {
+ for (otherTech in ruleset.technologies.values) {
+ if (tech != otherTech && otherTech.column?.columnNumber == tech.column?.columnNumber && otherTech.row == tech.row)
+ lines += "${tech.name} is in the same row and column as ${otherTech.name}!"
+ }
- for (victoryType in ruleset.victories.values) {
- for (requiredUnit in victoryType.requiredSpaceshipParts)
- if (!ruleset.units.contains(requiredUnit))
- lines.add("Victory type ${victoryType.name} requires adding the non-existant unit $requiredUnit to the capital to win!",
- RulesetErrorSeverity.Warning)
- for (milestone in victoryType.milestoneObjects)
- if (milestone.type == null)
- lines.add("Victory type ${victoryType.name} has milestone ${milestone.uniqueDescription} that is of an unknown type!",
- RulesetErrorSeverity.Error)
- for (victory in ruleset.victories.values)
- if (victory.name != victoryType.name && victory.milestones == victoryType.milestones)
- lines.add("Victory types ${victoryType.name} and ${victory.name} have the same requirements!",
- RulesetErrorSeverity.Warning)
+ uniqueValidator.checkUniques(tech, lines, rulesetInvariant, tryFixUnknownUniques)
}
+ }
- for (difficulty in ruleset.difficulties.values) {
- for (unitName in difficulty.aiCityStateBonusStartingUnits + difficulty.aiMajorCivBonusStartingUnits + difficulty.playerBonusStartingUnits)
- if (unitName != Constants.eraSpecificUnit && !ruleset.units.containsKey(unitName))
- lines += "Difficulty ${difficulty.name} contains starting unit $unitName which does not exist!"
- }
+ private fun addUnitErrorsRulesetInvariant(
+ lines: RulesetErrorList,
+ rulesetInvariant: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (unit in ruleset.units.values) {
+ if (unit.upgradesTo == unit.name || (unit.upgradesTo != null && unit.upgradesTo == unit.replaces))
+ lines += "${unit.name} upgrades to itself!"
+ if (!unit.isCivilian() && unit.strength == 0)
+ lines += "${unit.name} is a military unit but has no assigned strength!"
+ if (unit.isRanged() && unit.rangedStrength == 0 && !unit.hasUnique(UniqueType.CannotAttack))
+ lines += "${unit.name} is a ranged unit but has no assigned rangedStrength!"
- for (cityStateType in ruleset.cityStateTypes.values) {
- for (unique in cityStateType.allyBonusUniqueMap.getAllUniques() + cityStateType.friendBonusUniqueMap.getAllUniques()){
- val errors = checkUnique(
- unique,
- tryFixUnknownUniques,
- cityStateType,
- rulesetSpecific
- )
- lines.addAll(errors)
- }
+ uniqueValidator.checkUniques(unit, lines, rulesetInvariant, tryFixUnknownUniques)
}
-
- return lines
}
/** Collects known technology prerequisite paths: key is the technology name,
@@ -467,12 +662,12 @@ class RulesetValidator(val ruleset: Ruleset) {
}
for (unique in unit.getMatchingUniques(UniqueType.ConstructImprovementInstantly)) {
val improvementName = unique.params[0]
- if (ruleset.tileImprovements[improvementName]==null) continue // this will be caught in the checkUniques
+ if (ruleset.tileImprovements[improvementName]==null) continue // this will be caught in the uniqueValidator.checkUniques
if ((ruleset.tileImprovements[improvementName] as Stats).none() &&
unit.isCivilian() &&
!unit.isGreatPersonOfType("War")) {
lines.add("${unit.name} can place improvement $improvementName which has no stats, preventing unit automation!",
- RulesetErrorSeverity.Warning)
+ RulesetErrorSeverity.WarningOptionsOnly)
}
}
@@ -593,7 +788,7 @@ class RulesetValidator(val ruleset: Ruleset) {
}
private fun checkPromotionCircularReferences(lines: RulesetErrorList) {
- fun recursiveCheck(history: LinkedHashSet, promotion: Promotion, level: Int) {
+ fun recursiveCheck(history: HashSet, promotion: Promotion, level: Int) {
if (promotion in history) {
lines.add("Circular Reference in Promotions: ${history.joinToString("→") { it.name }}→${promotion.name}",
RulesetErrorSeverity.Warning)
@@ -603,163 +798,18 @@ class RulesetValidator(val ruleset: Ruleset) {
history.add(promotion)
for (prerequisiteName in promotion.prerequisites) {
val prerequisite = ruleset.unitPromotions[prerequisiteName] ?: continue
- recursiveCheck(history.toCollection(linkedSetOf()), prerequisite, level + 1)
+ // Performance - if there's only one prerequisite, we can send this linked set as-is, since no one else will be using it
+ val linkedSetToPass =
+ if (promotion.prerequisites.size == 1) history
+ else history.toCollection(hashSetOf())
+ recursiveCheck(linkedSetToPass, prerequisite, level + 1)
}
}
for (promotion in ruleset.unitPromotions.values) {
- recursiveCheck(linkedSetOf(), promotion, 0)
- }
- }
-
-
- private fun checkUniques(
- uniqueContainer: IHasUniques,
- lines: RulesetErrorList,
- severityToReport: UniqueType.UniqueComplianceErrorSeverity,
- tryFixUnknownUniques: Boolean
- ) {
- for (unique in uniqueContainer.uniqueObjects) {
- val errors = checkUnique(
- unique,
- tryFixUnknownUniques,
- uniqueContainer as? INamed,
- severityToReport
- )
- lines.addAll(errors)
- }
- }
-
- fun checkUnique(
- unique: Unique,
- tryFixUnknownUniques: Boolean,
- namedObj: INamed?,
- severityToReport: UniqueType.UniqueComplianceErrorSeverity
- ): List {
- val prefix = (if (namedObj is IRulesetObject) "${namedObj.originRuleset}: " else "") +
- (if (namedObj == null) "The" else "${namedObj.name}'s")
- if (unique.type == null) return checkUntypedUnique(unique, tryFixUnknownUniques, prefix)
-
- val rulesetErrors = RulesetErrorList()
-
- if (namedObj is IHasUniques && !unique.type.canAcceptUniqueTarget(namedObj.getUniqueTarget()))
- rulesetErrors.add(RulesetError("$prefix unique \"${unique.text}\" is not allowed on its target type", RulesetErrorSeverity.Warning))
-
- val typeComplianceErrors = unique.type.getComplianceErrors(unique, ruleset)
- for (complianceError in typeComplianceErrors) {
- if (complianceError.errorSeverity <= severityToReport)
- rulesetErrors.add(RulesetError("$prefix unique \"${unique.text}\" contains parameter ${complianceError.parameterName}," +
- " which does not fit parameter type" +
- " ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !",
- complianceError.errorSeverity.getRulesetErrorSeverity(severityToReport)
- ))
- }
-
- for (conditional in unique.conditionals) {
- if (conditional.type == null) {
- rulesetErrors.add(
- "$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
- " which is of an unknown type!",
- RulesetErrorSeverity.Warning
- )
- } else {
- if (conditional.type.targetTypes.none { it.modifierType != UniqueTarget.ModifierType.None })
- rulesetErrors.add("$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
- " which is a Unique type not allowed as conditional or trigger.",
- RulesetErrorSeverity.Warning)
-
- val conditionalComplianceErrors =
- conditional.type.getComplianceErrors(conditional, ruleset)
- for (complianceError in conditionalComplianceErrors) {
- if (complianceError.errorSeverity == severityToReport)
- rulesetErrors.add(RulesetError( "$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"." +
- " This contains the parameter ${complianceError.parameterName} which does not fit parameter type" +
- " ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !",
- complianceError.errorSeverity.getRulesetErrorSeverity(severityToReport)
- ))
- }
- }
- }
-
-
- if (severityToReport != UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific)
- // If we don't filter these messages will be listed twice as this function is called twice on most objects
- // The tests are RulesetInvariant in nature, but RulesetSpecific is called for _all_ objects, invariant is not.
- return rulesetErrors
-
-
- val deprecationAnnotation = unique.getDeprecationAnnotation()
- if (deprecationAnnotation != null) {
- val replacementUniqueText = unique.getReplacementText(ruleset)
- val deprecationText =
- "$prefix unique \"${unique.text}\" is deprecated ${deprecationAnnotation.message}," +
- if (deprecationAnnotation.replaceWith.expression != "") " replace with \"${replacementUniqueText}\"" else ""
- val severity = if (deprecationAnnotation.level == DeprecationLevel.WARNING)
- RulesetErrorSeverity.WarningOptionsOnly // Not user-visible
- else RulesetErrorSeverity.Warning // User visible
-
- rulesetErrors.add(deprecationText, severity)
- }
-
- return rulesetErrors
- }
-
- private fun checkUntypedUnique(unique: Unique, tryFixUnknownUniques: Boolean, prefix: String ): List {
- // Malformed conditional is always bad
- if (unique.text.count { it == '<' } != unique.text.count { it == '>' })
- return listOf(RulesetError(
- "$prefix unique \"${unique.text}\" contains mismatched conditional braces!",
- RulesetErrorSeverity.Warning))
-
- // Support purely filtering Uniques without actual implementation
- if (isFilteringUniqueAllowed(unique)) return emptyList()
- if (tryFixUnknownUniques) {
- val fixes = tryFixUnknownUnique(unique, prefix)
- if (fixes.isNotEmpty()) return fixes
+ if (promotion.prerequisites.isEmpty()) continue
+ recursiveCheck(hashSetOf(), promotion, 0)
}
-
- if (RulesetCache.modCheckerAllowUntypedUniques) return emptyList()
-
- return listOf(RulesetError(
- "$prefix unique \"${unique.text}\" not found in Unciv's unique types.",
- RulesetErrorSeverity.WarningOptionsOnly))
- }
-
- private fun isFilteringUniqueAllowed(unique: Unique): Boolean {
- // Isolate this decision, to allow easy change of approach
- // This says: Must have no conditionals or parameters, and is contained in GlobalUniques
- if (unique.conditionals.isNotEmpty() || unique.params.isNotEmpty()) return false
- return unique.text in ruleset.globalUniques.uniqueMap
}
- private fun tryFixUnknownUnique(unique: Unique, prefix: String): List {
- val similarUniques = UniqueType.values().filter {
- getRelativeTextDistance(
- it.placeholderText,
- unique.placeholderText
- ) <= RulesetCache.uniqueMisspellingThreshold
- }
- val equalUniques =
- similarUniques.filter { it.placeholderText == unique.placeholderText }
- return when {
- // This should only ever happen if a bug is or has been introduced that prevents Unique.type from being set for a valid UniqueType, I think.\
- equalUniques.isNotEmpty() -> listOf(RulesetError(
- "$prefix unique \"${unique.text}\" looks like it should be fine, but for some reason isn't recognized.",
- RulesetErrorSeverity.OK))
- similarUniques.isNotEmpty() -> {
- val text =
- "$prefix unique \"${unique.text}\" looks like it may be a misspelling of:\n" +
- similarUniques.joinToString("\n") { uniqueType ->
- var text = "\"${uniqueType.text}"
- if (unique.conditionals.isNotEmpty())
- text += " " + unique.conditionals.joinToString(" ") { "<${it.text}>" }
- text += "\""
- if (uniqueType.getDeprecationAnnotation() != null) text += " (Deprecated)"
- return@joinToString text
- }.prependIndent("\t")
- listOf(RulesetError(text, RulesetErrorSeverity.OK))
- }
- else -> emptyList()
- }
- }
}
diff --git a/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt b/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt
new file mode 100644
index 0000000000000..89a6b3397a251
--- /dev/null
+++ b/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt
@@ -0,0 +1,227 @@
+package com.unciv.models.ruleset.validation
+
+import com.unciv.models.ruleset.IRulesetObject
+import com.unciv.models.ruleset.Ruleset
+import com.unciv.models.ruleset.RulesetCache
+import com.unciv.models.ruleset.unique.IHasUniques
+import com.unciv.models.ruleset.unique.Unique
+import com.unciv.models.ruleset.unique.UniqueComplianceError
+import com.unciv.models.ruleset.unique.UniqueParameterType
+import com.unciv.models.ruleset.unique.UniqueTarget
+import com.unciv.models.ruleset.unique.UniqueType
+import com.unciv.models.stats.INamed
+
+class UniqueValidator(val ruleset: Ruleset) {
+
+ fun checkUniques(
+ uniqueContainer: IHasUniques,
+ lines: RulesetErrorList,
+ severityToReport: UniqueType.UniqueComplianceErrorSeverity,
+ tryFixUnknownUniques: Boolean
+ ) {
+ for (unique in uniqueContainer.uniqueObjects) {
+ val errors = checkUnique(
+ unique,
+ tryFixUnknownUniques,
+ uniqueContainer as? INamed,
+ severityToReport
+ )
+ lines.addAll(errors)
+ }
+ }
+
+ fun checkUnique(
+ unique: Unique,
+ tryFixUnknownUniques: Boolean,
+ namedObj: INamed?,
+ severityToReport: UniqueType.UniqueComplianceErrorSeverity
+ ): List {
+ val prefix by lazy { (if (namedObj is IRulesetObject) "${namedObj.originRuleset}: " else "") +
+ (if (namedObj == null) "The" else "${namedObj.name}'s") }
+ if (unique.type == null) return checkUntypedUnique(unique, tryFixUnknownUniques, prefix)
+
+ val rulesetErrors = RulesetErrorList()
+
+ if (namedObj is IHasUniques && !unique.type.canAcceptUniqueTarget(namedObj.getUniqueTarget()))
+ rulesetErrors.add(RulesetError("$prefix unique \"${unique.text}\" is not allowed on its target type", RulesetErrorSeverity.Warning))
+
+ val typeComplianceErrors = getComplianceErrors(unique)
+ for (complianceError in typeComplianceErrors) {
+ if (complianceError.errorSeverity <= severityToReport)
+ rulesetErrors.add(RulesetError("$prefix unique \"${unique.text}\" contains parameter ${complianceError.parameterName}," +
+ " which does not fit parameter type" +
+ " ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !",
+ complianceError.errorSeverity.getRulesetErrorSeverity(severityToReport)
+ ))
+ }
+
+ for (conditional in unique.conditionals) {
+ addConditionalErrors(conditional, rulesetErrors, prefix, unique, severityToReport)
+ }
+
+
+ if (severityToReport != UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific)
+ // If we don't filter these messages will be listed twice as this function is called twice on most objects
+ // The tests are RulesetInvariant in nature, but RulesetSpecific is called for _all_ objects, invariant is not.
+ return rulesetErrors
+
+ addDeprecationAnnotationErrors(unique, prefix, rulesetErrors)
+
+ return rulesetErrors
+ }
+
+ private fun addConditionalErrors(
+ conditional: Unique,
+ rulesetErrors: RulesetErrorList,
+ prefix: String,
+ unique: Unique,
+ severityToReport: UniqueType.UniqueComplianceErrorSeverity
+ ) {
+ if (conditional.type == null) {
+ rulesetErrors.add(
+ "$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
+ " which is of an unknown type!",
+ RulesetErrorSeverity.Warning
+ )
+ return
+ }
+
+ if (conditional.type.targetTypes.none { it.modifierType != UniqueTarget.ModifierType.None })
+ rulesetErrors.add(
+ "$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
+ " which is a Unique type not allowed as conditional or trigger.",
+ RulesetErrorSeverity.Warning
+ )
+
+ if (conditional.type.targetTypes.contains(UniqueTarget.UnitActionModifier)
+ && unique.type!!.targetTypes.none { UniqueTarget.UnitAction.canAcceptUniqueTarget(it) }
+ )
+ rulesetErrors.add(
+ "$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
+ " which as a UnitActionModifier is only allowed on UnitAction uniques.",
+ RulesetErrorSeverity.Warning
+ )
+
+ val conditionalComplianceErrors =
+ getComplianceErrors(conditional)
+ for (complianceError in conditionalComplianceErrors) {
+ if (complianceError.errorSeverity == severityToReport)
+ rulesetErrors.add(
+ RulesetError(
+ "$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"." +
+ " This contains the parameter ${complianceError.parameterName} which does not fit parameter type" +
+ " ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !",
+ complianceError.errorSeverity.getRulesetErrorSeverity(severityToReport)
+ )
+ )
+ }
+ }
+
+ private fun addDeprecationAnnotationErrors(
+ unique: Unique,
+ prefix: String,
+ rulesetErrors: RulesetErrorList
+ ) {
+ val deprecationAnnotation = unique.getDeprecationAnnotation()
+ if (deprecationAnnotation != null) {
+ val replacementUniqueText = unique.getReplacementText(ruleset)
+ val deprecationText =
+ "$prefix unique \"${unique.text}\" is deprecated ${deprecationAnnotation.message}," +
+ if (deprecationAnnotation.replaceWith.expression != "") " replace with \"${replacementUniqueText}\"" else ""
+ val severity = if (deprecationAnnotation.level == DeprecationLevel.WARNING)
+ RulesetErrorSeverity.WarningOptionsOnly // Not user-visible
+ else RulesetErrorSeverity.Warning // User visible
+
+ rulesetErrors.add(deprecationText, severity)
+ }
+ }
+
+ /** Maps uncompliant parameters to their required types */
+ private fun getComplianceErrors(
+ unique: Unique,
+ ): List {
+ if (unique.type==null) return emptyList()
+ val errorList = ArrayList()
+ for ((index, param) in unique.params.withIndex()) {
+ val acceptableParamTypes = unique.type.parameterTypeMap[index]
+ val errorTypesForAcceptableParameters =
+ acceptableParamTypes.map { getParamTypeErrorSeverityCached(it, param) }
+ if (errorTypesForAcceptableParameters.any { it == null }) continue // This matches one of the types!
+ val leastSevereWarning =
+ errorTypesForAcceptableParameters.minByOrNull { it!!.ordinal }!!
+ errorList += UniqueComplianceError(param, acceptableParamTypes, leastSevereWarning)
+ }
+ return errorList
+ }
+
+ private val paramTypeErrorSeverityCache = HashMap>()
+ private fun getParamTypeErrorSeverityCached(uniqueParameterType: UniqueParameterType, param:String): UniqueType.UniqueComplianceErrorSeverity? {
+ if (!paramTypeErrorSeverityCache.containsKey(uniqueParameterType))
+ paramTypeErrorSeverityCache[uniqueParameterType] = hashMapOf()
+ val uniqueParamCache = paramTypeErrorSeverityCache[uniqueParameterType]!!
+
+ if (uniqueParamCache.containsKey(param)) return uniqueParamCache[param]
+
+ val severity = uniqueParameterType.getErrorSeverity(param, ruleset)
+ uniqueParamCache[param] = severity
+ return severity
+ }
+
+ private fun checkUntypedUnique(unique: Unique, tryFixUnknownUniques: Boolean, prefix: String ): List {
+ // Malformed conditional is always bad
+ if (unique.text.count { it == '<' } != unique.text.count { it == '>' })
+ return listOf(RulesetError(
+ "$prefix unique \"${unique.text}\" contains mismatched conditional braces!",
+ RulesetErrorSeverity.Warning))
+
+ // Support purely filtering Uniques without actual implementation
+ if (isFilteringUniqueAllowed(unique)) return emptyList()
+ if (tryFixUnknownUniques) {
+ val fixes = tryFixUnknownUnique(unique, prefix)
+ if (fixes.isNotEmpty()) return fixes
+ }
+
+ return listOf(RulesetError(
+ "$prefix unique \"${unique.text}\" not found in Unciv's unique types.",
+ RulesetErrorSeverity.OK))
+ }
+
+ private fun isFilteringUniqueAllowed(unique: Unique): Boolean {
+ // Isolate this decision, to allow easy change of approach
+ // This says: Must have no conditionals or parameters, and is contained in GlobalUniques
+ if (unique.conditionals.isNotEmpty() || unique.params.isNotEmpty()) return false
+ return unique.text in ruleset.globalUniques.uniqueMap
+ }
+
+ private fun tryFixUnknownUnique(unique: Unique, prefix: String): List {
+ val similarUniques = UniqueType.values().filter {
+ getRelativeTextDistance(
+ it.placeholderText,
+ unique.placeholderText
+ ) <= RulesetCache.uniqueMisspellingThreshold
+ }
+ val equalUniques =
+ similarUniques.filter { it.placeholderText == unique.placeholderText }
+ return when {
+ // This should only ever happen if a bug is or has been introduced that prevents Unique.type from being set for a valid UniqueType, I think.\
+ equalUniques.isNotEmpty() -> listOf(RulesetError(
+ "$prefix unique \"${unique.text}\" looks like it should be fine, but for some reason isn't recognized.",
+ RulesetErrorSeverity.OK))
+
+ similarUniques.isNotEmpty() -> {
+ val text =
+ "$prefix unique \"${unique.text}\" looks like it may be a misspelling of:\n" +
+ similarUniques.joinToString("\n") { uniqueType ->
+ var text = "\"${uniqueType.text}"
+ if (unique.conditionals.isNotEmpty())
+ text += " " + unique.conditionals.joinToString(" ") { "<${it.text}>" }
+ text += "\""
+ if (uniqueType.getDeprecationAnnotation() != null) text += " (Deprecated)"
+ return@joinToString text
+ }.prependIndent("\t")
+ listOf(RulesetError(text, RulesetErrorSeverity.OK))
+ }
+ else -> emptyList()
+ }
+ }
+}
diff --git a/core/src/com/unciv/ui/audio/CityAmbiencePlayer.kt b/core/src/com/unciv/ui/audio/CityAmbiencePlayer.kt
index b80853855b659..d78311b46929e 100644
--- a/core/src/com/unciv/ui/audio/CityAmbiencePlayer.kt
+++ b/core/src/com/unciv/ui/audio/CityAmbiencePlayer.kt
@@ -1,72 +1,24 @@
package com.unciv.ui.audio
-import com.badlogic.gdx.Gdx
-import com.badlogic.gdx.audio.Music
-import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.Disposable
import com.unciv.UncivGame
import com.unciv.logic.city.City
-import com.unciv.utils.Log
-/** Must be [disposed][dispose]. Starts playing an ambience sound for the city when created. Stops playing the ambience sound when [disposed][dispose]. */
+/** Must be [disposed][dispose].
+ * Starts playing an ambience sound for the city when created.
+ * Stops playing the ambience sound when [disposed][dispose]. */
class CityAmbiencePlayer(
city: City
) : Disposable {
- private var playingCitySound: Music? = null
-
init {
- play(city)
- }
-
- private fun getModsFolder(): FileHandle {
- val path = "mods"
- val internal = Gdx.files.internal(path)
- if (internal.exists()) return internal
- return Gdx.files.local(path)
- }
-
- private fun getModSoundFolders(): Sequence {
- val visualMods = UncivGame.Current.settings.visualMods
- val mods = UncivGame.Current.gameInfo!!.gameParameters.getModsAndBaseRuleset()
- return (visualMods + mods).asSequence()
- .map { modName ->
- getModsFolder()
- .child(modName)
- .child("sounds")
- }
- }
-
- private fun getSoundFile(fileName: String): FileHandle {
- val fileFromMods = getModSoundFolders()
- .filter { it.isDirectory }
- .flatMap { it.list().asSequence() }
- .filter { !it.isDirectory && it.extension() in MusicController.gdxSupportedFileExtensions }
- .firstOrNull { it.nameWithoutExtension() == fileName }
-
- return fileFromMods ?: Gdx.files.internal("sounds/$fileName.ogg")
- }
-
- private fun play(city: City) {
- if (UncivGame.Current.settings.citySoundsVolume == 0f) return
-
- if (playingCitySound != null)
- stop()
- try {
- playingCitySound = Gdx.audio.newMusic(getSoundFile(city.civ.getEra().citySound))
- playingCitySound?.volume = UncivGame.Current.settings.citySoundsVolume
- playingCitySound?.isLooping = true
- playingCitySound?.play()
- } catch (ex: Throwable) {
- playingCitySound?.dispose()
- Log.error("Error while playing city sound: ", ex)
+ val volume = UncivGame.Current.settings.citySoundsVolume
+ if (volume > 0f) {
+ UncivGame.Current.musicController
+ .playOverlay("sounds", city.civ.getEra().citySound, volume)
}
}
- private fun stop() {
- playingCitySound?.dispose()
- }
-
override fun dispose() {
- stop()
+ UncivGame.Current.musicController.stopOverlay()
}
}
diff --git a/core/src/com/unciv/ui/audio/MusicController.kt b/core/src/com/unciv/ui/audio/MusicController.kt
index 9dc1b35e97f5b..05fd7c07d102d 100644
--- a/core/src/com/unciv/ui/audio/MusicController.kt
+++ b/core/src/com/unciv/ui/audio/MusicController.kt
@@ -9,6 +9,7 @@ import com.unciv.logic.multiplayer.storage.DropBox
import com.unciv.models.metadata.GameSettings
import com.unciv.utils.Concurrency
import com.unciv.utils.Log
+import java.io.File
import java.util.EnumSet
import java.util.Timer
import kotlin.concurrent.thread
@@ -20,6 +21,10 @@ import kotlin.math.roundToInt
* Play, choose, fade-in/out and generally manage music track playback.
*
* Main methods: [chooseTrack], [pause], [resume], [setModList], [isPlaying], [gracefulShutdown]
+ *
+ * City ambience feature: [playOverlay], [stopOverlay]
+ * * This plays entirely independent of all other functionality as linked above.
+ * * Can load from internal (jar,apk) - music is always local, nothing is packaged into a release.
*/
class MusicController {
companion object {
@@ -27,16 +32,24 @@ class MusicController {
private val musicLocation = FileType.Local
private const val musicPath = "music"
private const val modPath = "mods"
- private const val musicFallbackLocation = "/music/thatched-villagers.mp3" // Dropbox path
- private const val musicFallbackLocalName = "music/Thatched Villagers - Ambient.mp3" // Name we save it to
- private const val maxVolume = 0.6f // baseVolume has range 0.0-1.0, which is multiplied by this for the API
- private const val ticksPerSecondGdx = 58.3f // *Observed* frequency of Gdx app loop
- private const val ticksPerSecondOwn = 20f // Timer frequency when we use our own
- private const val defaultFadeDuration = 0.9f // in seconds
+ /** Dropbox path of default download offer */
+ private const val musicFallbackLocation = "/music/thatched-villagers.mp3"
+ /** Name we save the default download offer to */
+ private const val musicFallbackLocalName = "music/Thatched Villagers - Ambient.mp3"
+ /** baseVolume has range 0.0-1.0, which is multiplied by this for the API */
+ private const val maxVolume = 0.6f
+ /** *Observed* frequency of Gdx app loop - theoretically this should reach 60fps */
+ private const val ticksPerSecondGdx = 58.3f
+ /** Timer frequency when we use our own */
+ private const val ticksPerSecondOwn = 20f
+ /** Default fade duration in seconds used to calculate the step per tick */
+ private const val defaultFadeDuration = 0.9f
private const val defaultFadingStepGdx = 1f / (defaultFadeDuration * ticksPerSecondGdx)
private const val defaultFadingStepOwn = 1f / (defaultFadeDuration * ticksPerSecondOwn)
- private const val musicHistorySize = 8 // number of names to keep to avoid playing the same in short succession
- val gdxSupportedFileExtensions = listOf("mp3", "ogg", "wav") // All Gdx formats
+ /** Number of names to keep, to avoid playing the same in short succession */
+ private const val musicHistorySize = 8
+ /** All Gdx-supported sound formats (file extensions) */
+ val gdxSupportedFileExtensions = listOf("mp3", "ogg", "wav")
private fun getFile(path: String) =
if (musicLocation == FileType.External && Gdx.files.isExternalStorageAvailable)
@@ -75,9 +88,13 @@ class MusicController {
if (fileName.isEmpty())
return MusicTrackInfo("", "", "")
val fileNameParts = fileName.split('/')
- val modName = if (fileNameParts.size > 1 && fileNameParts[0] == "mods") fileNameParts[1] else ""
- var trackName = fileNameParts[if (fileNameParts.size > 3 && fileNameParts[2] == "music") 3 else 1]
- val type = gdxSupportedFileExtensions.firstOrNull {trackName.endsWith(".$it") } ?: ""
+ val modName = if (fileNameParts.size > 1 && fileNameParts[0] == modPath)
+ fileNameParts[1] else ""
+ var trackName = fileNameParts[
+ if (fileNameParts.size > 3 && fileNameParts[2] == musicPath) 3 else 1
+ ]
+ val type = gdxSupportedFileExtensions
+ .firstOrNull {trackName.endsWith(".$it") } ?: ""
trackName = trackName.removeSuffix(".$type")
return MusicTrackInfo(modName, trackName, type)
}
@@ -93,7 +110,8 @@ class MusicController {
get() = silenceLengthInTicks.toFloat() / ticksPerSecond
set(value) { silenceLengthInTicks = (ticksPerSecond * value).toInt() }
- private var silenceLengthInTicks = (UncivGame.Current.settings.pauseBetweenTracks * ticksPerSecond).roundToInt()
+ private var silenceLengthInTicks =
+ (UncivGame.Current.settings.pauseBetweenTracks * ticksPerSecond).roundToInt()
private var mods = HashSet()
@@ -106,7 +124,7 @@ class MusicController {
private enum class ControllerState {
/** Own timer stopped, if using the HardenedGdxAudio callback just do nothing */
Idle,
- /** As the name says. Loop will release everything and go [Idle] if it encounters this state. */
+ /** Loop will release everything and go [Idle] if it encounters this state. */
Cleanup,
/** Play a track to its end, then silence for a while, then choose another track */
Playing,
@@ -124,6 +142,9 @@ class MusicController {
private var current: MusicTrackController? = null
private var next: MusicTrackController? = null
+ /** One entry only for 'overlay' tracks in addition to and independent of normal music */
+ private var overlay: MusicTrackController? = null
+
/** Keeps paths of recently played track to reduce repetition */
private val musicHistory = ArrayDeque(musicHistorySize)
@@ -198,7 +219,8 @@ class MusicController {
private fun startTimer() {
if (!needOwnTimer || musicTimer != null) return
- // Start background TimerTask which manages track changes - on desktop, we get callbacks from the app.loop instead
+ // Start background TimerTask which manages track changes and fades -
+ // on desktop, we get callbacks from the app.loop instead
val timerPeriod = (1000f / ticksPerSecond).roundToInt().toLong()
musicTimer = timer("MusicTimer", daemon = true, period = timerPeriod ) {
musicTimerTask()
@@ -213,6 +235,9 @@ class MusicController {
private fun musicTimerTask() {
// This ticks [ticksPerSecond] times per second. Runs on Gdx main thread in desktop only
+
+ overlay?.overlayTick()
+
when (state) {
ControllerState.Idle -> return
@@ -221,10 +246,12 @@ class MusicController {
if (next == null) {
// no music to play - begin silence or shut down
ticksOfSilence = 0
- state = if (state == ControllerState.PlaySingle) ControllerState.Shutdown else ControllerState.Silence
+ state = if (state == ControllerState.PlaySingle) ControllerState.Shutdown
+ else ControllerState.Silence
fireOnChange()
} else if (next!!.state.canPlay) {
- // Next track - if top slot empty and a next exists, move it to top and start
+ // Next track -
+ // if top slot empty and a next exists, move it to top and start
current = next
next = null
if (!current!!.play()) {
@@ -269,13 +296,14 @@ class MusicController {
stopTimer()
clearNext()
clearCurrent()
+ clearOverlay()
musicHistory.clear()
Log.debug("MusicController shut down.")
}
private fun audioExceptionHandler(ex: Throwable, music: Music) {
- // Should run only in exceptional cases when the Gdx codecs actually have trouble with a file.
- // Most playback problems are caught by the similar handler in MusicTrackController
+ // Should run only in exceptional cases when the Gdx codecs actually have trouble with a
+ // file. Most playback problems are caught by the similar handler in MusicTrackController.
// Gdx _will_ try to read more data from file in Lwjgl3Application.loop even for
// Music instances that already have thrown an exception.
@@ -283,6 +311,7 @@ class MusicController {
music.dispose()
if (music == next?.music) clearNext()
if (music == current?.music) clearCurrent()
+ if (music == overlay?.music) clearOverlay()
Log.error("Error playing music", ex)
@@ -295,35 +324,49 @@ class MusicController {
}
}
- /** Get sequence of potential music locations */
- private fun getMusicFolders() = sequence {
+ /** Get sequence of potential music locations when called without parameters.
+ * @param folder a folder name relative to mod/assets/local root
+ * @param getDefault builds the default (not modded) `FileHandle`,
+ * allows fallback to internal assets
+ * @return a Sequence of `FileHandle`s describing potential existing directories
+ */
+ private fun getMusicFolders(
+ folder: String = musicPath,
+ getDefault: () -> FileHandle = { getFile(folder) }
+ ) = sequence {
yieldAll(
(UncivGame.Current.settings.visualMods + mods).asSequence()
- .map { getFile(modPath).child(it).child(musicPath) }
+ .map { getFile(modPath).child(it).child(folder) }
)
- yield(getFile(musicPath))
- }
+ yield(getDefault())
+ }.filter { it.exists() && it.isDirectory }
/** Get a sequence of all existing music files */
private fun getAllMusicFiles() = getMusicFolders()
- .filter { it.exists() && it.isDirectory }
.flatMap { it.list().asSequence() }
// ensure only normal files with common sound extension
.filter { it.exists() && !it.isDirectory && it.extension() in gdxSupportedFileExtensions }
/** Choose adequate entry from [getAllMusicFiles] */
- private fun chooseFile(prefix: String, suffix: String, flags: EnumSet): FileHandle? {
+ private fun chooseFile(
+ prefix: String,
+ suffix: String,
+ flags: EnumSet
+ ): FileHandle? {
if (flags.contains(MusicTrackChooserFlags.PlayDefaultFile)) {
val defaultFile = getFile(musicFallbackLocalName)
- // Test so if someone never downloaded Thatched Villagers, their volume slider will still play music
+ // Test so if someone never downloaded Thatched Villagers,
+ // their volume slider will still play music
if (defaultFile.exists()) return defaultFile
}
// Scan whole music folder and mods to find best match for desired prefix and/or suffix
// get a path list (as strings) of music folder candidates - existence unchecked
+ val prefixMustMatch = flags.contains(MusicTrackChooserFlags.PrefixMustMatch)
+ val suffixMustMatch = flags.contains(MusicTrackChooserFlags.SuffixMustMatch)
return getAllMusicFiles()
.filter {
- (!flags.contains(MusicTrackChooserFlags.PrefixMustMatch) || it.nameWithoutExtension().startsWith(prefix))
- && (!flags.contains(MusicTrackChooserFlags.SuffixMustMatch) || it.nameWithoutExtension().endsWith(suffix))
+ (!prefixMustMatch || it.nameWithoutExtension().startsWith(prefix))
+ && (!suffixMustMatch || it.nameWithoutExtension().endsWith(suffix))
}
// randomize
.shuffled()
@@ -332,10 +375,12 @@ class MusicController {
{ if (it.nameWithoutExtension().startsWith(prefix)) 0 else 1 }
, { if (it.nameWithoutExtension().endsWith(suffix)) 0 else 1 }
, { if (it.path() in musicHistory) 1 else 0 }
- // Then just pick the first one. Not as wasteful as it looks - need to check all names anyway
+ // Then just pick the first one.
+ // Not as wasteful as it looks - need to check all names anyway
)).firstOrNull()
// Note: shuffled().sortedWith(), ***not*** .sortedWith(.., Random)
- // the latter worked with older JVM's, current ones *crash* you when a compare is not transitive.
+ // the latter worked with older JVM's,
+ // current ones *crash* you when a compare is not transitive.
}
private fun fireOnChange() {
@@ -368,12 +413,14 @@ class MusicController {
/**
* Chooses and plays a music track using an adaptable approach - for details see the wiki.
- * Called without parameters it will choose a new ambient music track and start playing it with fade-in/out.
+ * Called without parameters it will choose a new ambient music track
+ * and start playing it with fade-in/out.
* Will do nothing when no music files exist or the master volume is zero.
*
* @param prefix file name prefix, meant to represent **Context** - in most cases a Civ name
- * @param suffix file name suffix, meant to represent **Mood** - e.g. Peace, War, Theme, Defeat, Ambient
- * (Ambient is the default when a track ends and exists so War Peace and the others are not chosen in that case)
+ * @param suffix file name suffix, meant to represent **Mood** -
+ * e.g. Peace, War, Theme, Defeat, Ambient (Ambient is the default when
+ * a track ends and exists so War Peace and the others are not chosen in that case)
* @param flags a set of optional flags to tune the choice and playback.
* @return `true` = success, `false` = no match, no playback change
*/
@@ -388,10 +435,12 @@ class MusicController {
if (musicFile == null) {
// MustMatch flags at work or Music folder empty
- Log.debug("No music found for prefix=%s, suffix=%s, flags=%s", prefix, suffix, flags)
+ Log.debug("No music found for prefix=%s, suffix=%s, flags=%s",
+ prefix, suffix, flags)
return false
}
- Log.debug("Track chosen: %s for prefix=%s, suffix=%s, flags=%s", musicFile.path(), prefix, suffix, flags)
+ Log.debug("Track chosen: %s for prefix=%s, suffix=%s, flags=%s",
+ musicFile.path(), prefix, suffix, flags)
return startTrack(musicFile, flags)
}
@@ -420,10 +469,12 @@ class MusicController {
if (musicHistory.size >= musicHistorySize) musicHistory.removeFirst()
musicHistory.addLast(musicFile.path())
- // This is what makes a track change fade _over_ current fading out and next fading in at the same time.
+ // This is what makes a track change fade _over_:
+ // current fading out and next fading in at the same time.
it.play()
- val fadingStep = defaultFadingStep / (if (flags.contains(MusicTrackChooserFlags.SlowFade)) 5 else 1)
+ val fadingStep = defaultFadingStep /
+ (if (flags.contains(MusicTrackChooserFlags.SlowFade)) 5 else 1)
it.startFade(MusicTrackController.State.FadeIn, fadingStep)
when (state) {
@@ -436,7 +487,8 @@ class MusicController {
})
// Yes while the loader is doing its thing we wait for it in a Playing state
- state = if (flags.contains(MusicTrackChooserFlags.PlaySingle)) ControllerState.PlaySingle else ControllerState.Playing
+ state = if (flags.contains(MusicTrackChooserFlags.PlaySingle)) ControllerState.PlaySingle
+ else ControllerState.Playing
startTimer()
return true
}
@@ -470,8 +522,8 @@ class MusicController {
*/
fun pause(speedFactor: Float = 1f) {
Log.debug("MusicTrackController.pause called")
- val controller = current
- if ((state != ControllerState.Playing && state != ControllerState.PlaySingle) || controller == null) return
+ val controller = current ?: return
+ if (state != ControllerState.Playing && state != ControllerState.PlaySingle) return
val fadingStep = defaultFadingStep * speedFactor.coerceIn(0.001f..1000f)
controller.startFade(MusicTrackController.State.FadeOut, fadingStep)
if (next?.state == MusicTrackController.State.FadeIn)
@@ -490,14 +542,17 @@ class MusicController {
if (state == ControllerState.Pause && current != null) {
val fadingStep = defaultFadingStep * speedFactor.coerceIn(0.001f..1000f)
current!!.startFade(MusicTrackController.State.FadeIn, fadingStep)
- state = ControllerState.Playing // this may circumvent a PlaySingle, but, currently only the main menu resumes, and then it's perfect
+ // this may circumvent a PlaySingle, but -
+ // currently only the main menu resumes, and then it's perfect:
+ state = ControllerState.Playing
current!!.play()
} else if (state == ControllerState.Cleanup) {
chooseTrack()
}
}
- /** Fade out then shutdown with a given [duration] in seconds, defaults to a 'slow' fade (4.5s) */
+ /** Fade out then shutdown with a given [duration] in seconds,
+ * defaults to a 'slow' fade (4.5s) */
@Suppress("unused") // might be useful instead of gracefulShutdown
fun fadeoutToSilence(duration: Float = defaultFadeDuration * 5) {
val fadingStep = 1f / ticksPerSecond / duration
@@ -529,5 +584,53 @@ class MusicController {
fun isDefaultFileAvailable() =
getFile(musicFallbackLocalName).exists()
+ //endregion
+ //region Overlay track
+
+ /** Scans all mods [folder]s for [name] with a supported sound extension,
+ * with fallback to _internal_ assets [folder] */
+ private fun getMatchingFiles(folder: String, name: String) =
+ getMusicFolders(folder) { Gdx.files.internal(folder) }
+ .flatMap {
+ it.list { file: File ->
+ file.nameWithoutExtension == name && file.exists() && !file.isDirectory &&
+ file.extension in gdxSupportedFileExtensions
+ }.asSequence()
+ }
+
+ /** Play [name] from any mod's [folder] or internal assets,
+ * fading in to [volume] then looping */
+ fun playOverlay(folder: String, name: String, volume: Float) {
+ val file = getMatchingFiles(folder, name).firstOrNull() ?: return
+ playOverlay(file, volume)
+ }
+
+ /** Play [file], fading in to [volume] then looping */
+ @Suppress("MemberVisibilityCanBePrivate") // open to future use
+ fun playOverlay(file: FileHandle, volume: Float) {
+ clearOverlay()
+ MusicTrackController(volume, initialFadeVolume = 0f).load(file) {
+ it.music?.isLooping = true
+ it.play()
+ it.startFade(MusicTrackController.State.FadeIn)
+ overlay = it
+ }
+ }
+
+ /** Fade out any playing overlay then clean up */
+ fun stopOverlay() {
+ overlay?.startFade(MusicTrackController.State.FadeOut)
+ }
+
+ private fun MusicTrackController.overlayTick() {
+ if (timerTick() == MusicTrackController.State.Idle)
+ clearOverlay() // means FadeOut finished
+ }
+
+ private fun clearOverlay() {
+ overlay?.clear()
+ overlay = null
+ }
+
//endregion
}
diff --git a/core/src/com/unciv/ui/audio/MusicTrackController.kt b/core/src/com/unciv/ui/audio/MusicTrackController.kt
index 2fad3ec84781b..72c788037ac6e 100644
--- a/core/src/com/unciv/ui/audio/MusicTrackController.kt
+++ b/core/src/com/unciv/ui/audio/MusicTrackController.kt
@@ -7,7 +7,7 @@ import com.unciv.utils.Log
import com.unciv.utils.debug
/** Wraps one Gdx Music instance and manages loading, playback, fading and cleanup */
-internal class MusicTrackController(private var volume: Float) {
+internal class MusicTrackController(private var volume: Float, initialFadeVolume: Float = 1f) {
/** Internal state of this Music track */
enum class State(val canPlay: Boolean) {
@@ -25,7 +25,7 @@ internal class MusicTrackController(private var volume: Float) {
var music: Music? = null
private set
private var fadeStep = MusicController.defaultFadingStep
- private var fadeVolume: Float = 1f
+ private var fadeVolume: Float = initialFadeVolume
//region Functions for MusicController
@@ -47,7 +47,9 @@ internal class MusicTrackController(private var volume: Float) {
onError: ((MusicTrackController)->Unit)? = null,
onSuccess: ((MusicTrackController)->Unit)? = null
) {
- check(state == State.None && music == null) { "MusicTrackController.load should only be called once" }
+ check(state == State.None && music == null) {
+ "MusicTrackController.load should only be called once"
+ }
state = State.Loading
try {
@@ -75,12 +77,13 @@ internal class MusicTrackController(private var volume: Float) {
/** Starts fadeIn or fadeOut.
*
- * Note this does _not_ set the current fade "percentage" to allow smoothly changing direction mid-fade
+ * Note this does _not_ set the current fade "percentage" to allow smoothly
+ * changing direction mid-fade
* @param step Overrides current fade step only if >0
*/
fun startFade(fade: State, step: Float = 0f) {
if (!state.canPlay) return
- if (fadeStep > 0f) fadeStep = step
+ if (step > 0f) fadeStep = step
state = fade
}
@@ -97,7 +100,8 @@ internal class MusicTrackController(private var volume: Float) {
return timerTick() == State.Idle
}
- /** @return [Music.isPlaying] (Gdx music stream is playing) unless [state] says it won't make sense */
+ /** @return [Music.isPlaying] (Gdx music stream is playing)
+ * unless [state] says it won't make sense */
fun isPlaying() = state.canPlay && music?.isPlaying == true
/** Calls play() on the wrapped Gdx Music, catching exceptions to console.
@@ -105,11 +109,14 @@ internal class MusicTrackController(private var volume: Float) {
* @throws IllegalStateException if called on uninitialized instance
*/
fun play(): Boolean {
- check(state.canPlay && music != null) { "MusicTrackController.play called on uninitialized instance" }
+ check(state.canPlay && music != null) {
+ "MusicTrackController.play called on uninitialized instance"
+ }
// Unexplained observed exception: Gdx.Music.play fails with
// "Unable to allocate audio buffers. AL Error: 40964" (AL_INVALID_OPERATION)
- // Approach: This track dies, parent controller will enter state Silence thus retry after a while.
+ // Approach: This track dies, parent controller will enter state Silence thus
+ // retry after a while.
if (tryPlay(music!!)) return true
state = State.Error
return false
@@ -136,7 +143,8 @@ internal class MusicTrackController(private var volume: Float) {
state = State.Playing
}
private fun fadeOutStep() {
- // fade-out: linearly ramp fadeVolume to 0.0, then act according to Status (Playing->Silence/Pause/Shutdown)
+ // fade-out: linearly ramp fadeVolume to 0.0, then act according to Status
+ // (Playing->Silence/Pause/Shutdown)
// This needs to guard against the music backend breaking mid-fade away during game shutdown
fadeVolume -= fadeStep
try {
@@ -153,9 +161,9 @@ internal class MusicTrackController(private var volume: Float) {
private fun tryPlay(music: Music): Boolean {
return try {
- music.volume = volume
- if (!music.isPlaying) // for fade-over this could be called by the end of the previous track
- music.play()
+ music.volume = volume * fadeVolume
+ // for fade-over this could be called by the end of the previous track:
+ if (!music.isPlaying) music.play()
true
} catch (ex: Throwable) {
audioExceptionHandler(ex)
diff --git a/core/src/com/unciv/models/helpers/MapArrowType.kt b/core/src/com/unciv/ui/components/MapArrowType.kt
similarity index 97%
rename from core/src/com/unciv/models/helpers/MapArrowType.kt
rename to core/src/com/unciv/ui/components/MapArrowType.kt
index 3fe06934afd70..5ff57366a5ef2 100644
--- a/core/src/com/unciv/models/helpers/MapArrowType.kt
+++ b/core/src/com/unciv/ui/components/MapArrowType.kt
@@ -1,4 +1,4 @@
-package com.unciv.models.helpers
+package com.unciv.ui.components
import com.badlogic.gdx.graphics.Color
import com.unciv.logic.IsPartOfGameInfoSerialization
diff --git a/core/src/com/unciv/ui/components/SortableGrid.kt b/core/src/com/unciv/ui/components/SortableGrid.kt
index 4f0c99db40d8c..b9a2932c5747d 100644
--- a/core/src/com/unciv/ui/components/SortableGrid.kt
+++ b/core/src/com/unciv/ui/components/SortableGrid.kt
@@ -75,7 +75,7 @@ class SortableGrid> (
*/
fun getHeader(): Table {
if (!separateHeader)
- throw IllegalStateException("You can't call SortableGrid.getHeader unless you override separateHeader to true")
+ throw IllegalStateException("You can't call SortableGrid.getHeader unless you set separateHeader to true")
return headerRow
}
@@ -87,6 +87,10 @@ class SortableGrid> (
private val totalsRow = Table(skin)
init {
+ require (!separateHeader || columns.none { it.expandX }) {
+ "SortableGrid currently does not support separateHeader combined with expanding columns"
+ }
+
headerRow.defaults().pad(paddingVert, paddingHorz).minWidth(iconSize)
details.defaults().pad(paddingVert, paddingHorz).minWidth(iconSize)
totalsRow.defaults().pad(paddingVert, paddingHorz).minWidth(iconSize)
@@ -116,8 +120,13 @@ class SortableGrid> (
}
private fun initHeader() {
- sortSymbols[false] = "↑".toLabel()
- sortSymbols[true] = "↓".toLabel()
+ // These are possibly the highest codepoints in use in Unciv -
+ // Take into account when limiting Fonts.nextUnusedCharacterNumber
+ // Alternatives: "↑" U+2191, "↓" U+2193 - much wider and weird spacing in some fonts (e.g. Verdana)
+ // Note: These will scale with GameSettings.fontSizeMultiplier - could be *partly* countered
+ // with `toLabel(fontSize = (Constants.defaultFontSize / GUI.getSettings().fontSizeMultiplier).toInt())`
+ sortSymbols[false] = "↑".toLabel() // U+FFEA
+ sortSymbols[true] = "↓".toLabel() // U+FFEC
for (column in columns) {
val group = HeaderGroup(column)
@@ -208,6 +217,7 @@ class SortableGrid> (
// Using Group to overlay an optional sort symbol on top of the icon - we could also
// do HorizontalGroup to have them side by side. Also, note this is not a WidgetGroup
// so all layout details are left to the container - in this case, a Table.Cell
+ // This will knowingly place the arrow partly outside the Group bounds.
/** Wrap icon and sort symbol for a header cell */
inner class HeaderGroup(column: CT) : Group() {
private val icon = column.getHeaderIcon(iconSize)
diff --git a/core/src/com/unciv/ui/components/TabbedPager.kt b/core/src/com/unciv/ui/components/TabbedPager.kt
index 1c882d2c27ebe..bbef19d270574 100644
--- a/core/src/com/unciv/ui/components/TabbedPager.kt
+++ b/core/src/com/unciv/ui/components/TabbedPager.kt
@@ -9,7 +9,6 @@ import com.badlogic.gdx.scenes.scene2d.InputListener
import com.badlogic.gdx.scenes.scene2d.ui.Button
import com.badlogic.gdx.scenes.scene2d.ui.Cell
import com.badlogic.gdx.scenes.scene2d.ui.Image
-import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup
@@ -21,12 +20,11 @@ import com.unciv.ui.components.UncivTooltip.Companion.addTooltip
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.darken
import com.unciv.ui.components.extensions.isEnabled
-import com.unciv.ui.components.input.keyShortcuts
-import com.unciv.ui.components.input.onActivation
import com.unciv.ui.components.extensions.packIfNeeded
import com.unciv.ui.components.extensions.pad
-import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.input.KeyCharAndCode
+import com.unciv.ui.components.input.keyShortcuts
+import com.unciv.ui.components.input.onActivation
import com.unciv.ui.images.IconTextButton
import com.unciv.ui.popups.Popup
import com.unciv.ui.screens.basescreen.BaseScreen
@@ -225,8 +223,8 @@ open class TabbedPager(
}
}
- class SyncedScrollListener(val linkedScrollPane: LinkedScrollPane):InputListener(){
- val oldScrollListener = linkedScrollPane.listeners.removeIndex(linkedScrollPane.listeners.size-1) as InputListener
+ class SyncedScrollListener(val linkedScrollPane: LinkedScrollPane) : InputListener() {
+ val oldScrollListener = linkedScrollPane.listeners.removeIndex(linkedScrollPane.listeners.size - 1) as InputListener
override fun scrolled(event: InputEvent?, x: Float, y: Float, amountX: Float, amountY: Float): Boolean {
val toReturn = oldScrollListener.scrolled(event, x, y, amountX, amountY)
linkedScrollPane.sync(false)
@@ -239,7 +237,7 @@ open class TabbedPager(
addListener(SyncedScrollListener(this))
}
- class LinkedCaptureListener(val linkedScrollPane: LinkedScrollPane):InputListener(){
+ class LinkedCaptureListener(val linkedScrollPane: LinkedScrollPane) : InputListener() {
val oldListener = linkedScrollPane.captureListeners.removeIndex(0) as InputListener
override fun touchDown(event: InputEvent?, x: Float, y: Float, pointer: Int, button: Int): Boolean {
val toReturn = oldListener.touchDown(event, x, y, pointer, button)
@@ -265,7 +263,7 @@ open class TabbedPager(
addCaptureListener(LinkedCaptureListener(this))
}
- class LinkedFlickScrollListener(val stdFlickListener: ActorGestureListener, val linkedScrollPane: LinkedScrollPane):ActorGestureListener(){
+ class LinkedFlickScrollListener(val stdFlickListener: ActorGestureListener, val linkedScrollPane: LinkedScrollPane) : ActorGestureListener() {
override fun pan(event: InputEvent?, x: Float, y: Float, deltaX: Float, deltaY: Float) {
stdFlickListener.pan(event, x, y, deltaX, deltaY)
linkedScrollPane.sync()
diff --git a/core/src/com/unciv/ui/components/extensions/FormattingExtensions.kt b/core/src/com/unciv/ui/components/extensions/FormattingExtensions.kt
index c9988bb31a5de..96495455326bb 100644
--- a/core/src/com/unciv/ui/components/extensions/FormattingExtensions.kt
+++ b/core/src/com/unciv/ui/components/extensions/FormattingExtensions.kt
@@ -28,6 +28,7 @@ fun String.getConsumesAmountString(amount: Int, isStockpiled:Boolean): String {
/** Convert a [resource name][this] into "Need [amount] more $resource" string (untranslated) */
fun String.getNeedMoreAmountString(amount: Int) = "Need [$amount] more [$this]"
+// todo: There's a few other `if (>0) "+" else ""` around, and a DecimalFormat solution in DetailedStatsPopup: unify
fun Int.toStringSigned() = if (this > 0) "+$this" else this.toString()
/** Formats the [Duration] into a translated string */
diff --git a/core/src/com/unciv/ui/components/tilegroups/layers/TileLayerMisc.kt b/core/src/com/unciv/ui/components/tilegroups/layers/TileLayerMisc.kt
index 02da9779f6a8f..c075e1aa696ae 100644
--- a/core/src/com/unciv/ui/components/tilegroups/layers/TileLayerMisc.kt
+++ b/core/src/com/unciv/ui/components/tilegroups/layers/TileLayerMisc.kt
@@ -9,10 +9,10 @@ import com.unciv.UncivGame
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.map.HexMath
import com.unciv.logic.map.tile.Tile
-import com.unciv.models.helpers.MapArrowType
-import com.unciv.models.helpers.MiscArrowTypes
-import com.unciv.models.helpers.TintedMapArrow
-import com.unciv.models.helpers.UnitMovementMemoryType
+import com.unciv.ui.components.MapArrowType
+import com.unciv.ui.components.MiscArrowTypes
+import com.unciv.ui.components.TintedMapArrow
+import com.unciv.ui.components.UnitMovementMemoryType
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.ui.components.extensions.center
import com.unciv.ui.components.extensions.centerX
diff --git a/core/src/com/unciv/ui/images/ImageGetter.kt b/core/src/com/unciv/ui/images/ImageGetter.kt
index 104121566eda4..7a87d8191a93e 100644
--- a/core/src/com/unciv/ui/images/ImageGetter.kt
+++ b/core/src/com/unciv/ui/images/ImageGetter.kt
@@ -62,6 +62,8 @@ object ImageGetter {
atlases["game"] = atlas
}
+ fun reloadImages() = setNewRuleset(ruleset)
+
/** Required every time the ruleset changes, in order to load mod-specific images */
fun setNewRuleset(ruleset: Ruleset) {
ImageGetter.ruleset = ruleset
diff --git a/core/src/com/unciv/ui/objectdescriptions/BaseUnitDescriptions.kt b/core/src/com/unciv/ui/objectdescriptions/BaseUnitDescriptions.kt
index 12aaedb267e79..b82f9cb9a40ee 100644
--- a/core/src/com/unciv/ui/objectdescriptions/BaseUnitDescriptions.kt
+++ b/core/src/com/unciv/ui/objectdescriptions/BaseUnitDescriptions.kt
@@ -2,7 +2,10 @@ package com.unciv.ui.objectdescriptions
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table
+import com.unciv.GUI
import com.unciv.logic.city.City
+import com.unciv.models.metadata.GameSettings
+import com.unciv.models.ruleset.IRulesetObject
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueFlag
@@ -14,11 +17,10 @@ import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
import com.unciv.ui.components.Fonts
import com.unciv.ui.components.extensions.getConsumesAmountString
-import com.unciv.ui.components.extensions.toPercent
+import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.civilopediascreen.FormattedLine
import com.unciv.ui.screens.civilopediascreen.MarkupRenderer
-import kotlin.math.pow
object BaseUnitDescriptions {
@@ -74,6 +76,9 @@ object BaseUnitDescriptions {
fun getCivilopediaTextLines(baseUnit: BaseUnit, ruleset: Ruleset): List {
val textList = ArrayList()
+ // Potentially show pixel unit on top (other civilopediaText is handled by the caller)
+ textList.addPixelUnitImage(baseUnit)
+
// Don't call baseUnit.getType() here - coming from the main menu baseUnit isn't fully initialized
val unitTypeLink = ruleset.unitTypes[baseUnit.unitType]?.makeLink() ?: ""
textList += FormattedLine("{Unit type}: ${baseUnit.unitType.tr()}", unitTypeLink)
@@ -199,6 +204,22 @@ object BaseUnitDescriptions {
return textList
}
+ /** Show Pixel Unit Art for the unit.
+ * * _Unless_ the mod already uses [extraImage][FormattedLine.extraImage] in the unit's [civilopediaText][IRulesetObject.civilopediaText]
+ * * _Unless_ user has selected no [unitSet][GameSettings.unitSet]
+ * * For units with era or style variants, only the default is shown (todo: extend FormattedLine with slideshow capability)
+ */
+ // Note: By popular request (this is a simple variant of one of the ideas in #10175)
+ private fun ArrayList.addPixelUnitImage(baseUnit: BaseUnit) {
+ if (baseUnit.civilopediaText.any { it.extraImage.isNotEmpty() }) return
+ val settings = GUI.getSettings()
+ if (settings.unitSet.isNullOrEmpty() || settings.pediaUnitArtSize < 1f) return
+ val imageName = "TileSets/${settings.unitSet}/Units/${baseUnit.name}"
+ if (!ImageGetter.imageExists(imageName)) return // Some units don't have Unit art (e.g. nukes)
+ add(FormattedLine(extraImage = imageName, imageSize = settings.pediaUnitArtSize, centered = true))
+ add(FormattedLine(separator = true, color = "#7f7f7f"))
+ }
+
@Suppress("RemoveExplicitTypeArguments") // for faster IDE - inferring sequence types can be slow
fun UnitType.getUnitTypeCivilopediaTextLines(ruleset: Ruleset): List {
fun getDomainLines() = sequence {
diff --git a/core/src/com/unciv/ui/popups/CityScreenConstructionMenu.kt b/core/src/com/unciv/ui/popups/CityScreenConstructionMenu.kt
index 029d598958336..2d246c2926707 100644
--- a/core/src/com/unciv/ui/popups/CityScreenConstructionMenu.kt
+++ b/core/src/com/unciv/ui/popups/CityScreenConstructionMenu.kt
@@ -37,13 +37,18 @@ class CityScreenConstructionMenu(
cityConstructions.constructionQueue
.count { it !in PerpetualConstruction.perpetualConstructionsMap }
private val myIndex = cityConstructions.constructionQueue.indexOf(constructionName)
+ /** Cities (including this one) where changing the construction queue makes sense
+ * (excludes isBeingRazed even though technically that would be allowed) */
+ // Can't use CityScreen.canChangeState for other cities
+ private fun candidateCities() = city.civ.cities.asSequence()
+ .filterNot { it.isPuppet || it.isInResistance() || it.isBeingRazed }
/** Check whether an "All cities" menu makes sense: `true` if there's more than one city, it's not a Wonder, and any city's queue matches [predicate]. */
private fun allCitiesEntryValid(predicate: (CityConstructions) -> Boolean) =
- city.civ.cities.size > 1 &&
+ city.civ.cities.size > 1 && // Yes any 2 cities, not candidateCities.drop(1).any()
(construction as? Building)?.isAnyWonder() != true &&
- city.civ.cities.map { it.cityConstructions }.any(predicate)
+ candidateCities().map { it.cityConstructions }.any(predicate)
private fun forAllCities(action: (CityConstructions) -> Unit) =
- city.civ.cities.map { it.cityConstructions }.forEach(action)
+ candidateCities().map { it.cityConstructions }.forEach(action)
private val settings = GUI.getSettings()
private val disabledAutoAssignConstructions = settings.disabledAutoAssignConstructions
diff --git a/core/src/com/unciv/ui/popups/options/DebugTab.kt b/core/src/com/unciv/ui/popups/options/DebugTab.kt
index 193bdefb00eeb..1071d119718bf 100644
--- a/core/src/com/unciv/ui/popups/options/DebugTab.kt
+++ b/core/src/com/unciv/ui/popups/options/DebugTab.kt
@@ -3,17 +3,17 @@ package com.unciv.ui.popups.options
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.GUI
import com.unciv.UncivGame
-import com.unciv.logic.files.UncivFiles
import com.unciv.logic.files.MapSaver
+import com.unciv.logic.files.UncivFiles
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.tile.ResourceType
-import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.components.UncivSlider
import com.unciv.ui.components.UncivTextField
-import com.unciv.ui.components.input.onClick
import com.unciv.ui.components.extensions.toCheckBox
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.extensions.toTextButton
+import com.unciv.ui.components.input.onClick
+import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.utils.DebugUtils
fun debugTab(
@@ -80,10 +80,6 @@ fun debugTab(
BaseScreen.enableSceneDebug = it
}).colspan(2).row()
- add("Allow untyped Uniques in mod checker".toCheckBox(RulesetCache.modCheckerAllowUntypedUniques) {
- RulesetCache.modCheckerAllowUntypedUniques = it
- }).colspan(2).row()
-
add(Table().apply {
add("Unique misspelling threshold".toLabel()).left().fillX()
add(
diff --git a/core/src/com/unciv/ui/popups/options/DisplayTab.kt b/core/src/com/unciv/ui/popups/options/DisplayTab.kt
index 7622379df0be9..9747d64af243c 100644
--- a/core/src/com/unciv/ui/popups/options/DisplayTab.kt
+++ b/core/src/com/unciv/ui/popups/options/DisplayTab.kt
@@ -12,18 +12,18 @@ import com.unciv.models.metadata.ScreenSize
import com.unciv.models.skins.SkinCache
import com.unciv.models.tilesets.TileSetCache
import com.unciv.models.translations.tr
+import com.unciv.ui.components.TranslatedSelectBox
import com.unciv.ui.components.UncivSlider
import com.unciv.ui.components.WrappableLabel
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.brighten
-import com.unciv.ui.components.input.onChange
-import com.unciv.ui.components.input.onClick
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.extensions.toTextButton
+import com.unciv.ui.components.input.onChange
+import com.unciv.ui.components.input.onClick
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.popups.ConfirmPopup
import com.unciv.ui.screens.basescreen.BaseScreen
-import com.unciv.ui.components.TranslatedSelectBox
import com.unciv.ui.screens.worldscreen.NotificationsScroll
import com.unciv.utils.Display
import com.unciv.utils.ScreenMode
@@ -71,6 +71,7 @@ fun displayTab(
addResetTutorials(this, settings)
optionsPopup.addCheckbox(this, "Show zoom buttons in world screen", settings.showZoomButtons, true) { settings.showZoomButtons = it }
optionsPopup.addCheckbox(this, "Experimental Demographics scoreboard", settings.useDemographics, true) { settings.useDemographics = it }
+ addPediaUnitArtSizeSlider(this, settings, optionsPopup.selectBoxMinWidth)
addSeparator()
add("Visual Hints".toLabel(fontSize = 24)).colspan(2).row()
@@ -159,6 +160,19 @@ private fun addUnitIconAlphaSlider(table: Table, settings: GameSettings, selectB
table.add(unitIconAlphaSlider).minWidth(selectBoxMinWidth).pad(10f).row()
}
+private fun addPediaUnitArtSizeSlider(table: Table, settings: GameSettings, selectBoxMinWidth: Float) {
+ table.add("Size of Unitset art in Civilopedia".toLabel()).left().fillX()
+
+ val unitArtSizeSlider = UncivSlider(
+ 0f, 360f, 1f, initial = settings.pediaUnitArtSize
+ ) {
+ settings.pediaUnitArtSize = it
+ GUI.setUpdateWorldOnNextRender()
+ }
+ unitArtSizeSlider.setSnapToValues(floatArrayOf(0f, 32f, 48f, 64f, 96f, 120f, 180f, 240f, 360f), 60f)
+ table.add(unitArtSizeSlider).minWidth(selectBoxMinWidth).pad(10f).row()
+}
+
private fun addScreenModeSelectBox(table: Table, settings: GameSettings, selectBoxMinWidth: Float) {
table.add("Screen Mode".toLabel()).left().fillX()
diff --git a/core/src/com/unciv/ui/popups/options/ModCheckTab.kt b/core/src/com/unciv/ui/popups/options/ModCheckTab.kt
index 03630b8bfc69e..600161c2aebfc 100644
--- a/core/src/com/unciv/ui/popups/options/ModCheckTab.kt
+++ b/core/src/com/unciv/ui/popups/options/ModCheckTab.kt
@@ -7,14 +7,15 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
-import com.unciv.models.ruleset.validation.RulesetError
-import com.unciv.models.ruleset.validation.RulesetErrorSeverity
-import com.unciv.models.ruleset.validation.RulesetValidator
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType
+import com.unciv.models.ruleset.validation.RulesetError
+import com.unciv.models.ruleset.validation.RulesetErrorSeverity
+import com.unciv.models.ruleset.validation.UniqueValidator
import com.unciv.models.translations.tr
import com.unciv.ui.components.ExpanderTab
import com.unciv.ui.components.TabbedPager
+import com.unciv.ui.components.TranslatedSelectBox
import com.unciv.ui.components.extensions.surroundWithCircle
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.extensions.toTextButton
@@ -23,7 +24,6 @@ import com.unciv.ui.components.input.onClick
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.popups.ToastPopup
import com.unciv.ui.screens.basescreen.BaseScreen
-import com.unciv.ui.components.TranslatedSelectBox
import com.unciv.utils.Concurrency
import com.unciv.utils.Log
import com.unciv.utils.debug
@@ -200,7 +200,7 @@ class ModCheckTab(
uniqueReplacementText += " <${conditional.text}>"
val replacementUnique = Unique(uniqueReplacementText)
- val modInvariantErrors = RulesetValidator(mod).checkUnique(
+ val modInvariantErrors = UniqueValidator(mod).checkUnique(
replacementUnique,
false,
null,
@@ -211,7 +211,7 @@ class ModCheckTab(
if (modInvariantErrors.isNotEmpty()) continue // errors means no autoreplace
if (mod.modOptions.isBaseRuleset) {
- val modSpecificErrors = RulesetValidator(mod).checkUnique(
+ val modSpecificErrors = UniqueValidator(mod).checkUnique(
replacementUnique,
false,
null,
diff --git a/core/src/com/unciv/ui/screens/basescreen/TutorialController.kt b/core/src/com/unciv/ui/screens/basescreen/TutorialController.kt
index 2ec70c6dc17e7..9fb529bcf1e83 100644
--- a/core/src/com/unciv/ui/screens/basescreen/TutorialController.kt
+++ b/core/src/com/unciv/ui/screens/basescreen/TutorialController.kt
@@ -1,19 +1,17 @@
package com.unciv.ui.screens.basescreen
import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.files.FileHandle
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.models.TutorialTrigger
import com.unciv.models.ruleset.Tutorial
-import com.unciv.models.ruleset.unique.UniqueType
-import com.unciv.models.stats.INamed
import com.unciv.ui.components.input.KeyCharAndCode
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.popups.Popup
-import com.unciv.ui.screens.civilopediascreen.FormattedLine
-import com.unciv.ui.screens.civilopediascreen.SimpleCivilopediaText
+import com.unciv.ui.screens.civilopediascreen.ICivilopediaText
class TutorialController(screen: BaseScreen) {
@@ -29,18 +27,21 @@ class TutorialController(screen: BaseScreen) {
// static to allow use from TutorialTranslationTests
fun loadTutorialsFromJson(includeMods: Boolean = true): LinkedHashMap {
val result = linkedMapOf()
- for (path in tutorialFiles(includeMods)) {
- json().fromJsonFile(Array::class.java, path)
+ for (file in tutorialFiles(includeMods)) {
+ json().fromJsonFile(Array::class.java, file)
.associateByTo(result) { it.name }
}
return result
}
- private fun tutorialFiles(includeMods: Boolean) = sequence {
- yield("jsons/Tutorials.json")
+
+ private fun tutorialFiles(includeMods: Boolean) = sequence {
+ yield(Gdx.files.internal("jsons/Tutorials.json"))
if (!includeMods) return@sequence
- val mods = UncivGame.Current.gameInfo?.ruleset?.mods ?: return@sequence
- val names = mods.asSequence().map { "mods/$it/jsons/Tutorials.json" }
- yieldAll(names.filter { Gdx.files.local(it).exists() })
+ val mods = UncivGame.Current.gameInfo?.ruleset?.mods
+ ?: return@sequence
+ val files = mods.asSequence()
+ .map { Gdx.files.local("mods/$it/jsons/Tutorials.json") }
+ yieldAll(files.filter { it.exists() })
}
}
@@ -80,32 +81,12 @@ class TutorialController(screen: BaseScreen) {
return tutorials[name]?.steps ?: emptyList()
}
- /** Wrapper for a Tutorial, supports INamed and ICivilopediaText,
- * and already provisions for the display of an ExtraImage on top.
- * @param name from Tutorial.name, also used for ExtraImage (with spaces replaced by underscores)
- * @param tutorial provides [Tutorial.civilopediaText] and [Tutorial.steps] for display
- */
- //todo Replace - Civilopedia should display Tutorials directly as the RulesetObjects they are
- class CivilopediaTutorial(
- override var name: String,
- tutorial: Tutorial
- ) : INamed, SimpleCivilopediaText(
- sequenceOf(FormattedLine(extraImage = name.replace(' ', '_'))) + tutorial.civilopediaText.asSequence(),
- tutorial.steps?.asSequence() ?: emptySequence()
- ) {
- override fun makeLink() = "Tutorial/$name"
- }
-
- /** Get all Tutorials intended to be displayed in the Civilopedia
- * as a List of wrappers supporting INamed and ICivilopediaText
- */
- fun getCivilopediaTutorials(): List {
- val civilopediaTutorials = tutorials.filter {
- !it.value.hasUnique(UniqueType.HiddenFromCivilopedia)
- }.map {
- tutorial -> CivilopediaTutorial(tutorial.key, tutorial.value)
- }
- return civilopediaTutorials
+ /** Get all Tutorials to be displayed in the Civilopedia */
+ fun getCivilopediaTutorials(): Collection {
+ // Todo This is essentially an 'un-private' kludge and the accessor
+ // in CivilopediaCategories desperately needs independence from TutorialController:
+ // Move storage to RuleSet someday?
+ return tutorials.values
}
}
diff --git a/core/src/com/unciv/ui/screens/civilopediascreen/FormattedLine.kt b/core/src/com/unciv/ui/screens/civilopediascreen/FormattedLine.kt
index 4be65ff1c12b4..ffc7f4faa878a 100644
--- a/core/src/com/unciv/ui/screens/civilopediascreen/FormattedLine.kt
+++ b/core/src/com/unciv/ui/screens/civilopediascreen/FormattedLine.kt
@@ -2,8 +2,15 @@ package com.unciv.ui.screens.civilopediascreen
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color
+import com.badlogic.gdx.graphics.Pixmap
+import com.badlogic.gdx.graphics.TextureData
+import com.badlogic.gdx.graphics.g2d.TextureRegion
+import com.badlogic.gdx.graphics.glutils.FileTextureData
+import com.badlogic.gdx.graphics.glutils.PixmapTextureData
import com.badlogic.gdx.scenes.scene2d.Actor
+import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Table
+import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.UncivGame
@@ -238,15 +245,19 @@ class FormattedLine (
try {
val image = when {
ImageGetter.imageExists(extraImage) ->
- ImageGetter.getImage(extraImage)
+ if (centered) ImageGetter.getDrawable(extraImage).cropToContent()
+ else ImageGetter.getImage(extraImage)
Gdx.files.internal("ExtraImages/$extraImage.png").exists() ->
ImageGetter.getExternalImage("$extraImage.png")
Gdx.files.internal("ExtraImages/$extraImage.jpg").exists() ->
ImageGetter.getExternalImage("$extraImage.jpg")
else -> return table
}
- val width = if (imageSize.isNaN()) labelWidth else imageSize
- val height = width * image.height / image.width
+ // limit larger cordinate to a given max size
+ val maxSize = if (imageSize.isNaN()) labelWidth else imageSize
+ val (width, height) = if (image.width > image.height)
+ maxSize to maxSize * image.height / image.width
+ else maxSize * image.width / image.height to maxSize
table.add(image).size(width, height)
} catch (exception: Exception) {
Log.error("Exception while rendering civilopedia text", exception)
@@ -337,4 +348,98 @@ class FormattedLine (
else -> "'$text'->$link"
}
}
+
+ // region Helpers to crop an image to content
+ private fun TextureRegionDrawable.cropToContent(): Image {
+ val rect = getContentSize()
+ val newRegion = TextureRegion(region.texture, rect.x, rect.y, rect.width, rect.height)
+ return Image(TextureRegionDrawable(newRegion))
+ }
+
+ private fun TextureRegionDrawable.getContentSize(): IntRectangle {
+ val pixMap = region.texture.textureData.getReadonlyPixmap()
+ val result = IntRectangle(region.regionX, region.regionY, region.regionWidth, region.regionHeight) // Not Gdx: integers!
+ val original = result.copy()
+
+ while (result.height > 0 && pixMap.isRowEmpty(result, result.height - 1)) {
+ result.height -= 1
+ }
+ while (result.height > 0 && pixMap.isRowEmpty(result, 0)) {
+ result.y += 1
+ result.height -= 1
+ }
+ while (result.width > 0 && pixMap.isColumnEmpty(result, result.width - 1)) {
+ result.width -= 1
+ }
+ while (result.width > 0 && pixMap.isColumnEmpty(result, 0)) {
+ result.x += 1
+ result.width -= 1
+ }
+
+ result.grow((original.width / 40).coerceAtLeast(1), (original.height / 40).coerceAtLeast(1))
+ return result.intersection(original)
+ }
+
+ private fun Pixmap.isRowEmpty(bounds: IntRectangle, relativeY: Int): Boolean {
+ val y = bounds.y + relativeY
+ return (bounds.x until bounds.x + bounds.width).all {
+ getPixel(it, y) and 255 == 0
+ }
+ }
+
+ private fun Pixmap.isColumnEmpty(bounds: IntRectangle, relativeX: Int): Boolean {
+ val x = bounds.x + relativeX
+ return (bounds.y until bounds.y + bounds.height).all {
+ getPixel(x, it) and 255 == 0
+ }
+ }
+
+ /** Retrieve a texture Pixmap without reload or ownership transfer, useable for read operations only.
+ *
+ * (FileTextureData.consumePixmap forces a reload of the entire file - inefficient if we only want to look at pixel values) */
+ private fun TextureData.getReadonlyPixmap(): Pixmap {
+ if (!isPrepared) prepare()
+ if (this is PixmapTextureData) return consumePixmap()
+ if (this !is FileTextureData) throw TypeCastException("getReadonlyPixmap only works on file or pixmap based textures")
+ val field = FileTextureData::class.java.getDeclaredField("pixmap")
+ field.isAccessible = true
+ return field.get(this) as Pixmap
+ }
+ // endregion
+
+ // region Integer Rectangle class
+ /** Partial rewrite of java.awt.Rectangle which is not available on Android. */
+ private data class IntRectangle(
+ var x: Int,
+ var y: Int,
+ var width: Int,
+ var height: Int
+ ) {
+ // Note: Gdx *has* an Integer equivalent of Vector2: GridPoint2 - but not of Rectangle (all in com.badlogic.gdx.math)
+
+ /** Grow both left and right edges horizontally by [h] and correspondingly top, bottom by [v]
+ *
+ * Unlike java.awt.Rectangle this will not check for integer overflow or negative size.
+ */
+ fun grow(h: Int, v: Int) {
+ x -= h
+ width += h + h
+ y -= v
+ height += y + y
+ }
+
+ /** Returns a new IntRectangle that represents the intersection of the two rectangles: `this` and [r].
+ * If the two rectangles do not intersect, the result will be an empty rectangle.
+ *
+ * Unlike java.awt.Rectangle this will not check for integer overflow or negative size.
+ */
+ fun intersection(r: IntRectangle): IntRectangle {
+ val tx1 = x.coerceAtLeast(r.x)
+ val ty1 = y.coerceAtLeast(r.y)
+ val tx2 = (x + width).coerceAtMost(r.x + r.width)
+ val ty2 = (y + height).coerceAtMost(r.y + r.height)
+ return IntRectangle(tx1, ty1, tx2 - tx1, ty2 - ty1)
+ }
+ }
+ // endregion
}
diff --git a/core/src/com/unciv/ui/screens/modmanager/ModManagementScreen.kt b/core/src/com/unciv/ui/screens/modmanager/ModManagementScreen.kt
index 7e190099b0aee..d7cc4bf446419 100644
--- a/core/src/com/unciv/ui/screens/modmanager/ModManagementScreen.kt
+++ b/core/src/com/unciv/ui/screens/modmanager/ModManagementScreen.kt
@@ -447,8 +447,7 @@ class ModManagementScreen private constructor(
launchOnGLThread {
val repoName = modFolder.name() // repo.name still has the replaced "-"'s
ToastPopup("[$repoName] Downloaded!", this@ModManagementScreen)
- RulesetCache.loadRulesets()
- TileSetCache.loadTileSetConfigs()
+ reloadCachesAfterModChange()
UncivGame.Current.translations.tryReadTranslationForCurrentLanguage()
RulesetCache[repoName]?.let {
installedModInfo[repoName] = ModUIData(it, false)
@@ -513,7 +512,7 @@ class ModManagementScreen private constructor(
else
game.settings.visualMods.remove(mod.name)
game.settings.save()
- ImageGetter.setNewRuleset(ImageGetter.ruleset)
+ ImageGetter.reloadImages()
refreshInstalledModActions(mod)
if (optionsManager.sortInstalled == SortType.Status)
refreshInstalledModTable()
@@ -583,12 +582,17 @@ class ModManagementScreen private constructor(
/** Delete a Mod, refresh ruleset cache and update installed mod table */
private fun deleteMod(mod: Ruleset) {
mod.folderLocation!!.deleteDirectory()
- RulesetCache.loadRulesets()
- TileSetCache.loadTileSetConfigs()
+ reloadCachesAfterModChange()
installedModInfo.remove(mod.name)
refreshInstalledModTable()
}
+ private fun reloadCachesAfterModChange(){
+ RulesetCache.loadRulesets()
+ ImageGetter.reloadImages()
+ TileSetCache.loadTileSetConfigs()
+ }
+
internal fun refreshOnlineModTable() {
if (runningSearchJob != null) {
ToastPopup("Sorting and filtering needs to wait until the online query finishes", this)
diff --git a/core/src/com/unciv/ui/screens/newgamescreen/MapFileSelectTable.kt b/core/src/com/unciv/ui/screens/newgamescreen/MapFileSelectTable.kt
index 136d6f97b5816..218cd0c166ca9 100644
--- a/core/src/com/unciv/ui/screens/newgamescreen/MapFileSelectTable.kt
+++ b/core/src/com/unciv/ui/screens/newgamescreen/MapFileSelectTable.kt
@@ -18,7 +18,6 @@ import com.unciv.ui.components.input.onChange
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.victoryscreen.LoadMapPreview
import com.unciv.utils.Concurrency
-import io.ktor.util.collections.ConcurrentSet
import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive
import com.badlogic.gdx.utils.Array as GdxArray
@@ -37,7 +36,7 @@ class MapFileSelectTable(
private class MapWrapper(val fileHandle: FileHandle, val mapParameters: MapParameters) {
override fun toString(): String = mapParameters.baseRuleset + " | " + fileHandle.name()
}
- private val mapWrappers = ConcurrentSet()
+ private val mapWrappers = ArrayList()
private val columnWidth = newGameScreen.getColumnWidth()
@@ -56,45 +55,50 @@ class MapFileSelectTable(
mapFileSelectBox.onChange { onSelectBoxChange() }
- addMapWrappersSemiAsync()
+ addMapWrappersAsync()
}
- private fun addMapWrappersSemiAsync(){
- val mapFilesSequence = sequence