Added missionairy units, which can spread religion and bought with faith (#4568)

* Added missionairy units, which can spread religion and bought with faith

* Forgot an include, minor additions

* Forgot credit for the missionary image

* Large refactor, enabling buying with almost all stats, split IConstruction into IConstruction & INonPerpetualConstruction

* Does this fix the tests

* Fixed accidentally removing all trailing spaces in template.properties

* Thanks to someTroglodyte for paying more attention than I do :)

* Implemented requested changes

* Fixed large amount of question marks

* Missing space, of course

* Fixed function name change

* Fixed merge problems

Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>
This commit is contained in:
Xander Lenstra 2021-08-06 13:40:48 +02:00 committed by GitHub
parent ee32392ecd
commit 547f5a57e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 524 additions and 325 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -494,275 +494,282 @@ Missile Cruiser
orig: 100, 100
offset: 0, 0
index: -1
Mobile SAM
Missionary
rotate: false
xy: 652, 214
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Modern Armor
Mobile SAM
rotate: false
xy: 760, 322
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Mohawk Warrior
Modern Armor
rotate: false
xy: 868, 430
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Musketeer
Mohawk Warrior
rotate: false
xy: 976, 538
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Musketeer
rotate: false
xy: 1084, 646
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Musketman
rotate: false
xy: 1084, 647
xy: 1192, 755
size: 100, 99
orig: 100, 99
offset: 0, 0
index: -1
Naresuan's Elephant
rotate: false
xy: 1192, 754
xy: 1300, 862
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Norwegian Ski Infantry
rotate: false
xy: 1300, 862
xy: 652, 106
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Nuclear Missile
rotate: false
xy: 652, 106
xy: 760, 214
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Nuclear Submarine
rotate: false
xy: 760, 214
xy: 868, 322
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Panzer
rotate: false
xy: 868, 322
xy: 976, 430
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Paratrooper
rotate: false
xy: 976, 430
xy: 1084, 538
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Persian Immortal
rotate: false
xy: 1084, 539
xy: 1192, 647
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Pikeman
rotate: false
xy: 1192, 646
xy: 1300, 754
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Privateer
rotate: false
xy: 1300, 754
xy: 1408, 862
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Rifleman
rotate: false
xy: 1408, 862
xy: 760, 106
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Rocket Artillery
rotate: false
xy: 760, 106
xy: 868, 214
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Samurai
rotate: false
xy: 868, 214
xy: 976, 322
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Scout
rotate: false
xy: 976, 322
xy: 1084, 430
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Sea Beggar
rotate: false
xy: 1084, 431
xy: 1192, 539
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Settler
rotate: false
xy: 1192, 538
xy: 1300, 646
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Ship of the Line
rotate: false
xy: 1300, 646
xy: 1408, 754
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Sipahi
rotate: false
xy: 1408, 754
xy: 1516, 862
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Slinger
rotate: false
xy: 1516, 862
xy: 868, 106
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Spearman
rotate: false
xy: 868, 106
xy: 976, 214
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Stealth Bomber
rotate: false
xy: 976, 214
xy: 1084, 322
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Submarine
rotate: false
xy: 1084, 323
xy: 1192, 431
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Swordsman
rotate: false
xy: 1192, 430
xy: 1300, 538
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Tank
rotate: false
xy: 1300, 538
xy: 1408, 646
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Tercio
rotate: false
xy: 1408, 646
xy: 1516, 754
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Trebuchet
rotate: false
xy: 1516, 754
xy: 1624, 862
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Triplane
rotate: false
xy: 1624, 862
xy: 976, 106
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Trireme
rotate: false
xy: 1084, 214
xy: 1084, 213
size: 100, 101
orig: 100, 101
offset: 0, 0
index: -1
Turtle Ship
rotate: false
xy: 976, 106
xy: 1192, 323
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
War Chariot
rotate: false
xy: 1192, 322
xy: 1300, 430
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
War Elephant
rotate: false
xy: 1300, 430
xy: 1408, 538
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Warrior
rotate: false
xy: 1408, 538
xy: 1516, 646
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Work Boats
rotate: false
xy: 1516, 646
xy: 1624, 754
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Worker
rotate: false
xy: 1624, 754
xy: 1732, 862
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Zero
rotate: false
xy: 1732, 862
xy: 1192, 215
size: 100, 100
orig: 100, 100
offset: 0, 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

After

Width:  |  Height:  |  Size: 344 KiB

View File

@ -847,7 +847,7 @@
"gold": 4,
"greatPersonPoints": {"Great Merchant": 2},
"isWonder": true,
"uniques": ["Cost of purchasing items in cities reduced by [15]%"],
"uniques": ["[Gold] cost of purchasing items in cities [-15]%"],
"requiredTech": "Industrialization",
"quote": "'To achieve great things, two things are needed: a plan, and not quite enough time.' - Leonard Bernstein"
},

View File

@ -10,6 +10,7 @@
"startingMilitaryUnitCount": 1,
"startingMilitaryUnit": "Warrior",
"settlerPopulation": 1,
"baseUnitBuyCost": 200,
"iconRGB": [255, 87, 35]
},
{
@ -22,6 +23,7 @@
"startingGold": 10,
"startingCulture": 100,
"settlerPopulation": 1,
"baseUnitBuyCost": 200,
"iconRGB": [233, 31, 99]
},
{
@ -36,6 +38,7 @@
"settlerPopulation": 1,
"settlerBuildings": ["Shrine","Monument"],
"startingObsoleteWonders": ["Temple of Artemis", "Stonehenge", "The Great Library", "Mausoleum of Halicarnassus", "The Pyramids", "Statue of Zeus"],
"baseUnitBuyCost": 200,
"iconRGB": [157, 39, 176]
},
{
@ -51,6 +54,7 @@
"settlerBuildings": ["Shrine","Monument","Granary","Lighthouse"],
"startingObsoleteWonders": ["Temple of Artemis", "Stonehenge", "The Great Library", "Mausoleum of Halicarnassus", "The Pyramids", "Statue of Zeus",
"The Great Lighthouse", "Hanging Gardens", "Terracotta Army", "The Oracle", "Petra", "Great Wall", "Colossus"],
"baseUnitBuyCost": 300,
"iconRGB": [104, 58, 183]
},
{
@ -67,6 +71,7 @@
"startingObsoleteWonders": ["Temple of Artemis", "Stonehenge", "The Great Library", "Mausoleum of Halicarnassus", "The Pyramids", "Statue of Zeus",
"The Great Lighthouse", "Hanging Gardens", "Terracotta Army", "The Oracle", "Petra", "Great Wall", "Colossus",
"Hagia Sophia", "Chichen Itza", "Machu Picchu", "Angkor Wat", "Alhambra", "Notre Dame"],
"baseUnitBuyCost": 400,
"iconRGB": [63, 81, 182]
},
{
@ -84,6 +89,7 @@
"The Great Lighthouse", "Hanging Gardens", "Terracotta Army", "The Oracle", "Petra", "Great Wall", "Colossus",
"Hagia Sophia", "Chichen Itza", "Machu Picchu", "Angkor Wat", "Alhambra", "Notre Dame",
"Sistine Chapel", "Forbidden Palace", "Leaning Tower of Pisa", "Himeji Castle", "Taj Mahal", "Porcelain Tower", "Kremlin"],
"baseUnitBuyCost": 600,
"iconRGB": [33, 150, 243]
},
{
@ -102,6 +108,7 @@
"Hagia Sophia", "Chichen Itza", "Machu Picchu", "Angkor Wat", "Alhambra", "Notre Dame",
"Sistine Chapel", "Forbidden Palace", "Leaning Tower of Pisa", "Himeji Castle", "Taj Mahal", "Porcelain Tower", "Kremlin",
"The Louvre", "Big Ben", "Brandenburg Gate"],
"baseUnitBuyCost": 800,
"iconRGB": [0, 150, 136]
},
{
@ -121,6 +128,7 @@
"Sistine Chapel", "Forbidden Palace", "Leaning Tower of Pisa", "Himeji Castle", "Taj Mahal", "Porcelain Tower", "Kremlin",
"The Louvre", "Big Ben", "Brandenburg Gate",
"Eiffel Tower", "Statue of Liberty", "Neuschwanstein", "Cristo Redentor"],
"baseUnitBuyCost": 1000,
// So theoretically this is always just all the wonders at least 2 eras old. So we could just use that.
// But where is the modularity? The excluding of very specific wonders? That is no fun.
// So we just write down the entire long list (sorted by era!) instead.
@ -145,6 +153,7 @@
"Sistine Chapel", "Forbidden Palace", "Leaning Tower of Pisa", "Himeji Castle", "Taj Mahal", "Porcelain Tower", "Kremlin",
"The Louvre", "Big Ben", "Brandenburg Gate",
"Eiffel Tower", "Statue of Liberty", "Neuschwanstein", "Cristo Redentor"],
"baseUnitBuyCost": 1000,
"iconRGB": [76, 176, 81]
}
]

View File

@ -712,7 +712,7 @@
"innerColor": [255, 255, 255],
"uniqueName": "Dutch East India Company",
"uniques": ["Retain [50]% of the happiness from a luxury after the last copy has been traded away"],
"cities": ["Amsterdam", "Rotterdam", "Utrecht", "Groningen", "Breda", "Nijmegen", "Den Haag", "Haarlem", "Arnhem", "Zutphen", "Maastricht", "Tilburg", "Eindhoven", "Dordrecht", "Leiden", "Hertogenbosch", "Almere", "Alkmaar", "Brielle", "Vlissingen", "Apeldoorn", "Enschede", "Amersfoort", "Zwolle", "Venlo", "Uden", "Grave", "Delft", "Gouda", "Nieuwstadt", "Weesp", "Coevorden", "Kerkrade"]
"cities": ["Amsterdam", "Rotterdam", "Utrecht", "Groningen", "Breda", "Nijmegen", "Den Haag", "Haarlem", "Arnhem", "Zutphen", "Maastricht", "Tilburg", "Eindhoven", "Dordrecht", "Leiden", "'s Hertogenbosch", "Almere", "Alkmaar", "Brielle", "Vlissingen", "Apeldoorn", "Enschede", "Amersfoort", "Zwolle", "Venlo", "Uden", "Grave", "Delft", "Gouda", "Nieuwstadt", "Weesp", "Coevorden", "Kerkrade"]
},
{

View File

@ -260,7 +260,7 @@
},
{
"name": "Mercantilism",
"uniques": ["Cost of purchasing items in cities reduced by [25]%", "[+1 Science] from every [Mint]", "[+1 Science] from every [Market]",
"uniques": ["[Gold] cost of purchasing items in cities [-25]%", "[+1 Science] from every [Mint]", "[+1 Science] from every [Market]",
"[+1 Science] from every [Bank]", "[+1 Science] from every [Stock Exchange]"],
"requires": ["Trade Unions"],
"row": 2,
@ -381,7 +381,7 @@
},
{
"name": "Militarism",
"uniques": ["Gold cost of purchasing [All] units -[33]%"],
"uniques": ["[Gold] cost of purchasing [All] units [-33]%"],
"row": 1,
"column": 5
},

View File

@ -1176,9 +1176,9 @@
"May withdraw before melee ([80]%)", "+[100]% Strength vs [submarine units]"],
"attackSound": "shipguns"
},
// Atomic Era
{
"name": "Marine",
"unitType": "Gunpowder",
@ -1375,7 +1375,7 @@
"cost": 425,
"requiredTech": "Computers",
"requiredResource": "Aluminum",
"uniques": ["+[100]% Strength vs [Armored]", "No defensive terrain bonus", "Can move after attacking",
"uniques": ["+[100]% Strength vs [Armored]", "No defensive terrain bonus", "Can move after attacking",
"All tiles cost 1 movement", "Unable to capture cities"],
"attackSound": "machinegun"
},
@ -1390,7 +1390,7 @@
"rangedStrength": 85,
"cost": 425,
"requiredTech": "Telecommunications",
"uniques": ["+[75]% Strength when attacking", "Can only attack [Water] tiles", "Can attack submarines",
"uniques": ["+[75]% Strength when attacking", "Can only attack [Water] tiles", "Can attack submarines",
"[+1] Visibility Range", "Can carry [2] [Missile] units"],
"attackSound": "torpedo"
},
@ -1500,7 +1500,7 @@
"name": "Great Prophet",
"unitType": "Civilian",
"uniques": ["Can construct [Holy site] if it hasn't spread religion yet", "Can spread religion [4] times",
"May found a religion", "Great Person - [Faith]", "Unbuildable", "Hidden when religion is disabled"],
"May found a religion", "Great Person - [Faith]", "Unbuildable", "Religious Unit", "Hidden when religion is disabled"],
"movement": 2
},
{
@ -1518,6 +1518,16 @@
"uniques": ["Can start an [8]-turn golden age","Bonus for units in 2 tile radius 15%",
"Heal adjacent units for an additional 15 HP per turn", "Can construct [Citadel]", "Great Person - [War]", "Unbuildable"],
"movement": 5
},
/* Religious units */
{
"name": "Missionary",
"unitType": "Civilian",
"uniques": ["Can spread religion [2] times", "Can be purchased with [Faith] [in all cities in which the majority religion is a major religion]",
"Unbuildable", "Religious Unit"],
"movement": 4
}
/* Spaceship Parts */

View File

@ -1,6 +1,6 @@
# Tutorial tasks
# Tutorial tasks
Move a unit!\nClick on a unit > Click on a destination > Click the arrow popup =
Found a city!\nSelect the Settler (flag unit) > Click on 'Found city' (bottom-left corner) =
Enter the city screen!\nClick the city button twice =
@ -15,24 +15,24 @@ Create a trade route!\nConstruct roads between your capital and another city\nOr
Conquer a city!\nBring an enemy city down to low health > \nEnter the city with a melee unit =
Move an air unit!\nSelect an air unit > select another city within range > \nMove the unit to the other city =
See your stats breakdown!\nEnter the Overview screen (top right corner) >\nClick on 'Stats' =
Oh no! It looks like something went DISASTROUSLY wrong! This is ABSOLUTELY not supposed to happen! Please send me (yairm210@hotmail.com) an email with the game information (menu -> save game -> copy game info -> paste into email) and I'll try to fix it as fast as I can! =
Oh no! It looks like something went DISASTROUSLY wrong! This is ABSOLUTELY not supposed to happen! Please send us an report and we'll try to fix it as fast as we can! =
# Buildings
# Buildings
Unsellable =
Not displayed as an available construction unless [building] is built =
Not displayed as an available construction without [resource] =
Choose a free great person =
Get [unitName] =
Hydro Plant =
[buildingName] obsoleted =
# Diplomacy,Trade,Nations
# Diplomacy,Trade,Nations
Requires [buildingName] to be built in the city =
Requires [buildingName] to be built in all cities =
Provides a free [buildingName] in the city =
@ -46,14 +46,16 @@ Cannot be built with [buildingName] =
Consumes 1 [resource] =
Consumes [amount] [resource] =
Required tech: [requiredTech] =
Requires [PolicyOrNationalWonder] =
Requires [PolicyOrNationalWonder] =
Cannot be purchased =
Can only be purchased =
See also =
Requires at least one of the following: =
Requires all of the following: =
Leads to [techName] =
Leads to: =
Current construction =
Construction queue =
Pick a construction =
@ -64,7 +66,7 @@ Show stats drilldown =
Show construction queue =
Save =
Cancel =
Diplomacy =
War =
Peace =
@ -92,7 +94,7 @@ Indeed! =
Denounce [civName]? =
Denounce ([numberOfTurns] turns) =
We will remember this. =
[civName] has declared war on [targetCivName]! =
[civName] and [targetCivName] have signed a Peace Treaty! =
[civName] and [targetCivName] have signed the Declaration of Friendship! =
@ -100,7 +102,7 @@ We will remember this. =
Do you want to break your promise to [leaderName]? =
We promised not to settle near them ([count] turns remaining) =
They promised not to settle near us ([count] turns remaining) =
Unforgivable =
Enemy =
Competitor =
@ -108,12 +110,12 @@ Neutral =
Favorable =
Friend =
Ally =
[questName] (+[influenceAmount] influence) =
[remainingTurns] turns remaining =
## Diplomatic modifiers
## Diplomatic modifiers
You declared war on us! =
Your warmongering ways are unacceptable to us. =
You have captured our cities! =
@ -136,15 +138,15 @@ Your arrogant demands are in bad taste =
Your use of nuclear weapons is disgusting! =
You have stolen our lands! =
You gave us units! =
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 noticed your new city near our borders, despite your promise. This will have....implications. =
# City-States
# City-States
Provides [amountOfCulture] culture at 30 Influence =
Provides 3 food in capital and 1 food in other cities at 30 Influence =
Provides 3 happiness at 30 Influence =
@ -156,7 +158,7 @@ Protected by =
Revoke Protection =
Pledge to protect =
Declare Protection of [cityStateName]? =
Cultured =
Maritime =
Mercantile =
@ -170,9 +172,9 @@ Personality =
Influence =
Reach 30 for friendship. =
Reach highest influence above 60 for alliance. =
# Trades
Trade =
Offer trade =
Retract offer =
@ -199,17 +201,17 @@ Declare war on [nation] =
Luxury resources =
Strategic resources =
Owned: [amountOwned] =
# Nation picker
[resourceName] not required =
Lost ability =
National ability =
[firstValue] vs [secondValue] =
# New game screen
# New game screen
Uniques =
Promotions =
Load copied data =
@ -240,7 +242,7 @@ Victory Conditions =
Scientific =
Domination =
Cultural =
Map Shape =
Hexagonal =
Rectangular =
@ -248,8 +250,9 @@ Height =
Width =
Radius =
Enable Religion =
Advanced Settings =
Show advanced settings =
Hide advanced settings =
RNG Seed =
Map Height =
Temperature extremeness =
@ -260,9 +263,9 @@ Max Coast extension =
Biome areas extension =
Water level =
Reset to default =
Online Multiplayer =
World Size =
Tiny =
Small =
@ -273,9 +276,9 @@ World wrap requires a minimum width of 32 tiles =
The provided map dimensions were too small =
The provided map dimensions were too big =
The provided map dimensions had an unacceptable aspect ratio =
Difficulty =
AI =
Remove =
Random =
@ -283,14 +286,14 @@ Human =
Hotseat =
User ID =
Click to copy =
Game Speed =
Quick =
Standard =
Epic =
Marathon =
Starting Era =
It looks like we can't make a map with the parameters you requested! =
Maybe you put too many players into too small a map? =
@ -298,14 +301,14 @@ No human players selected! =
Mods: =
Base ruleset mods: =
Extension mods: =
World Wrap =
World wrap maps are very memory intensive - creating large world wrap maps on Android can lead to crashes! =
Anything above 80 by 50 may work very slowly on Android! =
Anything above 40 may work very slowly on Android! =
# Multiplayer
# Multiplayer
Username =
Multiplayer =
Could not download game! =
@ -341,9 +344,9 @@ Resign =
Are you sure you want to resign? =
You can only resign if it's your turn =
[civName] resigned and is now controlled by AI =
# Save game menu
# Save game menu
Current saves =
Show autosaves =
Saved game name =
@ -370,9 +373,9 @@ Load from custom location =
Could not load game from custom location! =
Save to custom location =
Could not save game to custom location! =
# Options
# Options
Options =
Display options =
Gameplay options =
@ -410,9 +413,9 @@ Enable portrait orientation =
Generate translation files =
Translation files are generated successfully. =
Locate mod errors =
# Notifications
# Notifications
Research of [technologyName] has completed! =
[construction] has become obsolete and was removed from the queue in [cityName]! =
[construction] has become obsolete and was removed from the queue in [amount] cities! =
@ -516,9 +519,9 @@ Our [name] took [tileDamage] tile damage =
[civName] has adopted the [policyName] policy =
An unknown civilization has adopted the [policyName] policy =
Our influence with City-States has started dropping faster! =
# World Screen UI
# World Screen UI
Working... =
Waiting for other players... =
in =
@ -571,7 +574,7 @@ Yes =
No =
Acquire =
Under construction =
Food =
Production =
Gold =
@ -579,7 +582,7 @@ Happiness =
Culture =
Science =
Faith =
Crop Yield =
Territory =
Force =
@ -588,7 +591,7 @@ Golden Age =
[year] BC =
[year] AD =
Civilopedia =
Start new game =
Save game =
Load game =
@ -604,9 +607,9 @@ Close =
Do you want to exit the game? =
Start bias: =
Avoid [terrain] =
# City screen
# City screen
Exit city =
Raze city =
Stop razing city =
@ -654,9 +657,9 @@ Worked by [cityName] =
Lock =
Unlock =
Move to city =
# Technology UI
# Technology UI
Pick a tech =
Pick a free tech =
Research [technology] =
@ -679,9 +682,9 @@ Attack =
Bombard =
NUKE =
Captured! =
# Battle modifier categories
# Battle modifier categories
defence vs ranged =
[percentage] to unit defence =
Attacker Bonus =
@ -703,11 +706,11 @@ defence vs [unitType] =
[tileFilter] defence =
Defensive Bonus =
Stacked with [unitType] =
The following improvements [stats]: =
The following improvements on [tileType] tiles [stats]: =
Hurry Research =
Conduct Trade Mission =
Your trade mission to [civName] has earned you [goldAmount] gold and [influenceAmount] influence! =
@ -726,9 +729,9 @@ Policies =
Base happiness =
Occupied City =
Buildings =
# terrainFilters (so for uniques like: "[stats] from [terrainFilter] tiles")
# terrainFilters (so for uniques like: "[stats] from [terrainFilter] tiles")
All =
Water =
Land =
@ -747,15 +750,15 @@ Strategic resource =
Fresh water =
non-fresh water =
Natural Wonder =
# improvementFilters
# improvementFilters
All =
All Road =
Great Improvement =
Great =
Wonders =
Base values =
Bonuses =
@ -781,9 +784,9 @@ Known and defeated ([numberOfCivs]) =
Tiles =
Natural Wonders =
Treasury deficit =
# Victory
# Victory
Science victory =
Cultural victory =
Conquest victory =
@ -802,7 +805,7 @@ The world has been convulsed by war. Many great and powerful civilizations have
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! =
Your civilization stands above all others! The exploits of your people shall be remembered until the end of civilization itself! =
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! =
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! =
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! =
One more turn...! =
Built Apollo Program =
Destroy [civName] =
@ -812,8 +815,8 @@ Rankings =
Spaceship parts remaining =
Branches completed =
Undefeated civs =
# The \n here means: put a newline (enter) here. If this is omitted, the sidebox in the diplomacy overview will become _really_ wide.
# Feel free to replace it with a space and put it somewhere else in your translation
# The \n here means: put a newline (enter) here. If this is omitted, the sidebox in the diplomacy overview will become _really_ wide.
# Feel free to replace it with a space and put it somewhere else in your translation
Turns until the next\ndiplomacy victory vote: [amount] =
Choose a civ to vote for =
Choose who should become the world leader and win a diplomatic victory! =
@ -822,9 +825,9 @@ Vote for [civilizationName] =
Continue =
Abstained =
Vote for World Leader =
# Capturing a city
# Capturing a city
What would you like to do with the city? =
Annex =
Annexed cities become part of your regular empire. =
@ -844,14 +847,14 @@ Destroying the city instantly razes the city to the ground. =
Remove your troops in our border immediately! =
Sorry. =
Never! =
Offer Declaration of Friendship ([30] turns) =
My friend, shall we declare our friendship to the world? =
Sign Declaration of Friendship ([30] turns) =
We are not interested. =
We have signed a Declaration of Friendship with [otherCiv]! =
[otherCiv] has denied our Declaration of Friendship! =
Basics =
Resources =
Terrains =
@ -920,8 +923,8 @@ Terrain feature [feature] does not exist in ruleset! =
Resource [resource] does not exist in ruleset! =
Improvement [improvement] does not exist in ruleset! =
Change map to fit selected ruleset? =
# Civilopedia difficulty levels
# Civilopedia difficulty levels
Player settings =
Base Happiness =
Happiness per luxury =
@ -931,7 +934,7 @@ Building cost modifier =
Policy cost modifier =
Unhappiness modifier =
Bonus vs. Barbarians =
AI settings =
AI city growth modifier =
AI unit cost modifier =
@ -940,14 +943,14 @@ AI wonder cost modifier =
AI building maintenance modifier =
AI unit maintenance modifier =
AI unhappiness modifier =
Turns until barbarians enter player tiles =
Gold reward for clearing barbarian camps =
# Other civilopedia things
# Other civilopedia things
Nations =
Available for [unitTypes] =
Available for: =
Available for: =
Free promotion: =
Free promotions: =
Free for [units] =
@ -956,17 +959,17 @@ Granted by [param] =
Granted by: =
[bonus] with [tech] =
Difficulty levels =
# Policies
# Policies
Adopt policy =
Adopt free policy =
Unlocked at =
Gain 2 free technologies =
All policies adopted =
# Religions
# Religions
Choose an Icon and name for your Religion =
Choose a [beliefType] belief! =
Found [religionName] =
@ -975,14 +978,14 @@ Found Religion =
Found Pantheon =
Follow [belief] =
Religions and Beliefs =
# Terrains
# Terrains
Impassable =
Rare feature =
# Resources
# Resources
Bison =
Copper =
Cocoa =
@ -992,9 +995,9 @@ Truffles =
Strategic =
Bonus =
Luxury =
# Unit types
# Unit types
City =
Civilian =
Melee =
@ -1003,21 +1006,21 @@ Scout =
Mounted =
Armor =
Siege =
WaterCivilian =
WaterMelee =
WaterRanged =
WaterSubmarine =
WaterAircraftCarrier =
Fighter =
Bomber =
AtomicBomber =
Missile =
# Unit filters and other unit related things
Air =
air units =
All =
@ -1038,13 +1041,13 @@ Water =
water units =
wounded units =
Wounded =
# For the All "newly-trained [relevant] units in this city receive the [] promotion" translation. Relevant as in 'units that can receive'
# For the All "newly-trained [relevant] units in this city receive the [] promotion" translation. Relevant as in 'units that can receive'
relevant =
# Promotions
# Promotions
Pick promotion =
OR =
units in open terrain =
@ -1058,9 +1061,9 @@ Dogfighting II =
Dogfighting III =
Choose name for [unitName] =
[unitFilter] units gain the [promotion] promotion =
# Multiplayer Turn Checker Service
# Multiplayer Turn Checker Service
Multiplayer options =
Enable out-of-game turn notifications =
Time between turn checks out-of-game (in minutes) =
@ -1069,10 +1072,10 @@ Take user ID from clipboard =
Doing this will reset your current user ID to the clipboard contents - are you sure? =
ID successfully set! =
Invalid ID! =
# Mods
# Mods
Mods =
Download [modName] =
Update [modName] =
@ -1100,9 +1103,9 @@ No description provided =
Author: [author] =
Size: [size] kB =
The mod you selected is incompatible with the defined ruleset! =
# Uniques that are relevant to more than one type of game object
# Uniques that are relevant to more than one type of game object
[stats] from every [param] =
[stats] from [param] tiles in this city =
[stats] from every [param] on [tileFilter] tiles =
@ -1116,8 +1119,8 @@ Can only be built on [tileFilter] tiles =
Cannot be built on [tileFilter] tiles =
Does not need removal of [feature] =
Gain a free [building] [cityFilter] =
# City filters
# City filters
in this city =
in all cities =
in all coastal cities =
@ -1127,3 +1130,4 @@ in all cities with a world wonder =
in all cities connected to capital =
in all cities with a garrison =

View File

@ -2,6 +2,9 @@ package com.unciv.logic.automation
import com.unciv.Constants
import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.IConstruction
import com.unciv.logic.city.INonPerpetualConstruction
import com.unciv.logic.city.PerpetualConstruction
import com.unciv.logic.civilization.*
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
@ -15,6 +18,7 @@ import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.VictoryType
import com.unciv.models.ruleset.tech.Technology
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
import kotlin.math.min
@ -136,8 +140,9 @@ object NextTurnAutomation {
for (city in civInfo.cities.sortedByDescending { it.population.population }) {
val construction = city.cityConstructions.getCurrentConstruction()
if (construction.canBePurchased()
&& city.civInfo.gold / 3 >= construction.getGoldCost(civInfo)) {
if (construction is PerpetualConstruction) continue
if ((construction as INonPerpetualConstruction).canBePurchasedWithStat(city, Stat.Gold)
&& city.civInfo.gold / 3 >= construction.getStatBuyCost(city, Stat.Gold)!!) {
city.cityConstructions.purchaseConstruction(construction.name, 0, true)
}
}

View File

@ -8,6 +8,7 @@ import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.UniqueMap
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats
import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.CivilopediaCategories
@ -152,15 +153,19 @@ class CityConstructions {
}
/** @constructionName needs to be a non-perpetual construction, else a cost of -1 is inferred */
internal fun getTurnsToConstructionString(constructionName: String, useStoredProduction:Boolean = true): String {
val construction = getConstruction(constructionName)
val cost = construction.getProductionCost(cityInfo.civInfo)
val cost =
if (construction is INonPerpetualConstruction) construction.getProductionCost(cityInfo.civInfo)
else -1 // This could _should_ never be reached
val turnsToConstruction = turnsToConstruction(constructionName, useStoredProduction)
val currentProgress = if (useStoredProduction) getWorkDone(constructionName) else 0
if (currentProgress == 0) return "\n$cost${Fonts.production} $turnsToConstruction${Fonts.turn}"
else return "\n$currentProgress/$cost${Fonts.production}\n$turnsToConstruction${Fonts.turn}"
}
// This function appears unused, can it be removed?
fun getProductionForTileInfo(): String {
/* this is because there were rare errors that I assume were caused because
currentConstruction changed on another thread */
@ -246,8 +251,8 @@ class CityConstructions {
val constr = getConstruction(constructionName)
return when {
constr is PerpetualConstruction -> 0
useStoredProduction -> constr.getProductionCost(cityInfo.civInfo) - getWorkDone(constructionName)
else -> constr.getProductionCost(cityInfo.civInfo)
useStoredProduction -> (constr as INonPerpetualConstruction).getProductionCost(cityInfo.civInfo) - getWorkDone(constructionName)
else -> (constr as INonPerpetualConstruction).getProductionCost(cityInfo.civInfo)
}
}
@ -315,7 +320,7 @@ class CityConstructions {
val construction = getConstruction(currentConstructionFromQueue)
if (construction is PerpetualConstruction) chooseNextConstruction() // check every turn if we could be doing something better, because this doesn't end by itself
else {
val productionCost = construction.getProductionCost(cityInfo.civInfo)
val productionCost = (construction as INonPerpetualConstruction).getProductionCost(cityInfo.civInfo)
if (inProgressConstructions.containsKey(currentConstructionFromQueue)
&& inProgressConstructions[currentConstructionFromQueue]!! >= productionCost) {
productionOverflow = inProgressConstructions[currentConstructionFromQueue]!! - productionCost
@ -481,14 +486,25 @@ class CityConstructions {
* Note: -1 does not guarantee queue will remain unchanged (validation)
* @param automatic Flag whether automation should try to choose what next to build (not coming from UI)
* Note: settings.autoAssignCityProduction is handled later
* @param stat Stat object of the stat with which was paid for the construction
* @return Success (false e.g. unit cannot be placed
*/
fun purchaseConstruction(constructionName: String, queuePosition: Int, automatic: Boolean): Boolean {
fun purchaseConstruction(
constructionName: String,
queuePosition: Int,
automatic: Boolean,
stat: Stat = Stat.Gold
): Boolean {
if (!getConstruction(constructionName).postBuildEvent(this, true))
return false // nothing built - no pay
if (!cityInfo.civInfo.gameInfo.gameParameters.godMode)
cityInfo.civInfo.addGold(-getConstruction(constructionName).getGoldCost(cityInfo.civInfo))
if (!cityInfo.civInfo.gameInfo.gameParameters.godMode) {
val construction = getConstruction(constructionName)
if (construction is PerpetualConstruction) return false
val constructionCost = (construction as INonPerpetualConstruction).getStatBuyCost(cityInfo, stat)
if (constructionCost == null) return false // We should never end up here anyway, so things have already gone _way_ wrong
cityInfo.addStat(stat, -1 * constructionCost)
}
if (queuePosition in 0 until constructionQueue.size)
removeFromQueue(queuePosition, automatic)

View File

@ -12,6 +12,7 @@ import com.unciv.models.ruleset.Unique
import com.unciv.models.ruleset.tile.ResourceSupplyList
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
@ -398,6 +399,21 @@ class CityInfo {
gppCounter.add(entry)
return gppCounter
}
fun addStat(stat: Stat, amount: Int) {
when (stat) {
Stat.Production -> cityConstructions.addProductionPoints(amount)
Stat.Food -> population.foodStored += amount
else -> civInfo.addStat(stat, amount)
}
}
fun getStatReserve(stat: Stat): Int {
return when (stat) {
Stat.Food -> population.foodStored
else -> civInfo.getStatReserve(stat)
}
}
internal fun getMaxHealth() = 200 + cityConstructions.getBuiltBuildings().sumBy { it.cityHealth }
@ -554,7 +570,7 @@ class CityInfo {
otherCiv.getDiplomacyManager(civInfo).setFlag(DiplomacyFlags.SettledCitiesNearUs, 30)
}
fun canPurchase(construction: IConstruction): Boolean {
fun canPurchase(construction: INonPerpetualConstruction): Boolean {
if (construction is BaseUnit) {
val tile = getCenterTile()
if (construction.isCivilian())
@ -576,6 +592,9 @@ class CityInfo {
"in all cities with a world wonder" -> cityConstructions.getBuiltBuildings().any { it.isWonder }
"in all cities connected to capital" -> isConnectedToCapital()
"in all cities with a garrison" -> getCenterTile().militaryUnit != null
"in all cities in which the majority religion is a major religion" ->
religion.getMajorityReligion() != null
&& civInfo.gameInfo.religions[religion.getMajorityReligion()]!!.isMajorReligion()
else -> false
}
}

View File

@ -43,8 +43,8 @@ class CityInfoReligionManager: Counter<String>() {
val followersPerReligion = getNumberOfFollowers()
if (followersPerReligion.isEmpty()) return null
val religionWithMaxFollowers = followersPerReligion.maxByOrNull { it.value }!!
if (religionWithMaxFollowers.value >= cityInfo.population.population) return religionWithMaxFollowers.key
else return null
return if (religionWithMaxFollowers.value >= cityInfo.population.population) religionWithMaxFollowers.key
else null
}
fun getAffectedBySurroundingCities() {

View File

@ -1,23 +1,78 @@
package com.unciv.logic.city
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.ruleset.Unique
import com.unciv.models.stats.INamed
import com.unciv.models.stats.Stat
import com.unciv.ui.utils.Fonts
import kotlin.math.pow
import kotlin.math.roundToInt
interface IConstruction : INamed {
fun getProductionCost(civInfo: CivilizationInfo): Int
fun getGoldCost(civInfo: CivilizationInfo): Int
fun isBuildable(cityConstructions: CityConstructions): Boolean
fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean
fun postBuildEvent(cityConstructions: CityConstructions, wasBought: Boolean = false): Boolean // Yes I'm hilarious.
fun getResourceRequirements(): HashMap<String,Int>
fun canBePurchased(): Boolean
}
interface INonPerpetualConstruction : IConstruction, INamed {
val hurryCostModifier: Int
val uniqueObjects: List<Unique>
val uniques: List<String>
fun getProductionCost(civInfo: CivilizationInfo): Int
fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int?
private fun getMatchingUniques(uniqueTemplate: String): Sequence<Unique> {
return uniqueObjects.asSequence().filter { it.placeholderText == uniqueTemplate }
}
fun canBePurchasedWithStat(cityInfo: CityInfo, stat: Stat, ignoreCityRequirements: Boolean = false): Boolean {
if (stat in listOf(Stat.Production, Stat.Happiness)) return false
if ("Cannot be purchased" in uniques) return false
if (stat == Stat.Gold) return !uniques.contains("Unbuildable")
// Can be purchased with [Stat] [cityFilter]
if (getMatchingUniques("Can be purchased with [] []")
.any { it.params[0] == stat.name && (ignoreCityRequirements || cityInfo.matchesFilter(it.params[1])) }
) return true
// Can be purchased for [amount] [Stat] [cityFilter]
if (getMatchingUniques("Can be purchased for [] [] []")
.any { it.params[1] == stat.name && ( ignoreCityRequirements || cityInfo.matchesFilter(it.params[2])) }
) return true
return false
}
fun canBePurchasedWithAnyStat(cityInfo: CityInfo): Boolean {
return Stat.values().any { canBePurchasedWithStat(cityInfo, it) }
}
fun getBaseGoldCost(civInfo: CivilizationInfo): Double {
// https://forums.civfanatics.com/threads/rush-buying-formula.393892/
return (30.0 * getProductionCost(civInfo)).pow(0.75) * (1 + hurryCostModifier / 100f)
}
// I can't make this function protected or private :(
fun getBaseBuyCost(cityInfo: CityInfo, stat: Stat): Int? {
if (stat == Stat.Gold) return getBaseGoldCost(cityInfo.civInfo).toInt()
// Can be purchased for [amount] [Stat] [cityFilter]
val lowestCostUnique = getMatchingUniques("Can be purchased for [] [] []")
.filter { it.params[1] == stat.name && cityInfo.matchesFilter(it.params[2]) }
.minByOrNull { it.params[0].toInt() }
if (lowestCostUnique != null) return lowestCostUnique.params[0].toInt()
// Can be purchased with [Stat] [cityFilter]
if (getMatchingUniques("Can be purchased with [] []")
.any { it.params[0] == stat.name && cityInfo.matchesFilter(it.params[1])}
) return cityInfo.civInfo.gameInfo.ruleSet.eras[cityInfo.civInfo.getEra()]!!.baseUnitBuyCost
return null
}
}
open class PerpetualConstruction(override var name: String, val description: String) : IConstruction {
override fun shouldBeDisplayed(cityConstructions: CityConstructions) = isBuildable(cityConstructions)
open fun getProductionTooltip(cityInfo: CityInfo) : String
= "\r\n${(cityInfo.cityStats.currentCityStats.production / CONVERSION_RATE).roundToInt()}/${Fonts.turn}"
@ -50,12 +105,6 @@ open class PerpetualConstruction(override var name: String, val description: Str
= mapOf(science.name to science, gold.name to gold, idle.name to idle)
}
override fun canBePurchased() = false
override fun getProductionCost(civInfo: CivilizationInfo) = throw Exception("Impossible!")
override fun getGoldCost(civInfo: CivilizationInfo) = throw Exception("Impossible!")
override fun isBuildable(cityConstructions: CityConstructions): Boolean =
throw Exception("Impossible!")

View File

@ -705,6 +705,19 @@ class CivilizationInfo {
// Happiness cannot be added as it is recalculated again, use a unique instead
}
}
fun getStatReserve(stat: Stat): Int {
return when (stat) {
Stat.Culture -> policies.storedCulture
Stat.Science -> {
if (tech.currentTechnology() == null) 0
else tech.remainingScienceToTech(tech.currentTechnology()!!.name)
}
Stat.Gold -> gold
Stat.Faith -> religionManager.storedFaith
else -> 0
}
}
fun getGreatPersonPointsForNextTurn(): Counter<String> {
val greatPersonPoints = Counter<String>()

View File

@ -1,8 +1,9 @@
package com.unciv.models.ruleset
import com.unciv.UncivGame
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.IConstruction
import com.unciv.logic.city.INonPerpetualConstruction
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.Counter
import com.unciv.models.ruleset.tile.TileImprovement
@ -17,10 +18,9 @@ import com.unciv.ui.utils.Fonts
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.pow
class Building : NamedStats(), IConstruction, ICivilopediaText {
class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
var requiredTech: String? = null
@ -45,7 +45,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText {
var greatPersonPoints= Counter<String>()
/** Extra cost percentage when purchasing */
private var hurryCostModifier = 0
override var hurryCostModifier = 0
var isWonder = false
var isNationalWonder = false
fun isAnyWonder() = isWonder || isNationalWonder
@ -68,9 +68,9 @@ class Building : NamedStats(), IConstruction, ICivilopediaText {
var quote: String = ""
@Deprecated("As of 3.15.16 - replaced with 'Provides a free [buildingName] [cityFilter]'")
var providesFreeBuilding: String? = null
var uniques = ArrayList<String>()
override var uniques = ArrayList<String>()
var replacementTextForUniques = ""
val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } }
override val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } }
override var civilopediaText = listOf<FormattedLine>()
@ -101,7 +101,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText {
if (!tileBonusHashmap.containsKey(stats)) tileBonusHashmap[stats] = ArrayList()
tileBonusHashmap[stats]!!.add(unique.params[1])
}
unique.placeholderText == "Consumes [] []" -> Unit // skip these,
unique.placeholderText == "Consumes [] []" -> Unit // skip these,
else -> yield(unique.text)
}
for ((key, value) in tileBonusHashmap)
@ -173,7 +173,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText {
if (!isWonder)
for (unique in city.getMatchingUniques("[] from all [] buildings")) {
if (isStatRelated(Stat.valueOf(unique.params[1])))
if (matchesFilter(unique.params[1]))
stats.add(unique.stats)
}
else
@ -199,13 +199,19 @@ class Building : NamedStats(), IConstruction, ICivilopediaText {
return stats
}
override fun canBePurchasedWithStat(cityInfo: CityInfo, stat: Stat, ignoreCityRequirements: Boolean): Boolean {
if (stat == Stat.Gold && isAnyWonder()) return false
return super.canBePurchasedWithStat(cityInfo, stat, ignoreCityRequirements)
}
override fun getCivilopediaTextHeader() = FormattedLine(name, header=2, icon=makeLink())
override fun makeLink() = if (isAnyWonder()) "Wonder/$name" else "Building/$name"
override fun hasCivilopediaTextLines() = true
override fun replacesCivilopediaDescription() = true
override fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> {
fun Float.formatSignedInt() = (if (this > 0f) "+" else "") + this.toInt().toString()
val textList = ArrayList<FormattedLine>()
if (isAnyWonder()) {
@ -223,7 +229,9 @@ class Building : NamedStats(), IConstruction, ICivilopediaText {
if (cost > 0) {
val stats = mutableListOf("$cost${Fonts.production}")
if (canBePurchased()) stats += "${(getBaseGoldCost()*0.1).toInt()*10}${Fonts.gold}"
if (canBePurchasedWithStat(CityInfo(), Stat.Gold, true)) {
stats += "${getBaseGoldCost(UncivGame.Current.gameInfo.currentPlayerCiv).toInt() / 10 * 10}${Fonts.gold}"
}
textList += FormattedLine(stats.joinToString(", ", "{Cost}: "))
}
@ -323,12 +331,6 @@ class Building : NamedStats(), IConstruction, ICivilopediaText {
return textList
}
override fun canBePurchased(): Boolean {
return !isAnyWonder() && "Cannot be purchased" !in uniques
}
override fun getProductionCost(civInfo: CivilizationInfo): Int {
var productionCost = cost.toFloat()
@ -351,24 +353,34 @@ class Building : NamedStats(), IConstruction, ICivilopediaText {
return productionCost.toInt()
}
private fun getBaseGoldCost() = (30.0 * cost).pow(0.75) * (1 + hurryCostModifier / 100.0)
override fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int? {
var cost = getBaseBuyCost(cityInfo, stat)?.toDouble()
if (cost == null) return null
override fun getGoldCost(civInfo: CivilizationInfo): Int {
// https://forums.civfanatics.com/threads/rush-buying-formula.393892/
var cost = (30 * getProductionCost(civInfo)).toDouble().pow(0.75) * (1 + hurryCostModifier / 100f)
// Deprecated since 3.15.15
if (stat == Stat.Gold) {
for (unique in cityInfo.getMatchingUniques("Cost of purchasing items in cities reduced by []%"))
cost *= 1 - (unique.params[0].toFloat() / 100)
for (unique in civInfo.getMatchingUniques("Cost of purchasing items in cities reduced by []%"))
cost *= 1 - (unique.params[0].toFloat() / 100)
for (unique in cityInfo.getMatchingUniques("Cost of purchasing [] buildings reduced by []%")) {
if (matchesFilter(unique.params[0]))
cost *= 1 - (unique.params[1].toFloat() / 100)
}
}
//
for (unique in civInfo.getMatchingUniques("Cost of purchasing [] buildings reduced by []%")) {
if (isStatRelated(Stat.valueOf(unique.params[0])))
cost *= 1 - (unique.params[1].toFloat() / 100)
for (unique in cityInfo.getMatchingUniques("[] cost of purchasing items in cities []%"))
if (stat.name == unique.params[0])
cost *= 1 + (unique.params[1].toFloat() / 100)
for (unique in cityInfo.getMatchingUniques("[] cost of purchasing [] buildings []%")) {
if (stat.name == unique.params[0] && matchesFilter(unique.params[1]))
cost *= 1 + (unique.params[2].toFloat() / 100)
}
return (cost / 10).toInt() * 10
return (cost / 10f).toInt() * 10
}
override fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean {
if (cityConstructions.isBeingConstructedOrEnqueued(name))
return false
@ -377,22 +389,29 @@ class Building : NamedStats(), IConstruction, ICivilopediaText {
|| rejectionReason.startsWith("Requires")
|| rejectionReason.startsWith("Consumes")
|| rejectionReason.endsWith("Wonder is being built elsewhere")
|| rejectionReason == "Can only be purchased"
}
fun getRejectionReason(construction: CityConstructions): String {
if (construction.isBuilt(name)) return "Already built"
// for buildings that are created as side effects of other things, and not directly built
if (uniques.contains("Unbuildable")) return "Unbuildable"
// unless they can be bought with faith
if (uniques.contains("Unbuildable")) {
if (canBePurchasedWithAnyStat(construction.cityInfo))
return "Can only be purchased"
return "Unbuildable"
}
val cityCenter = construction.cityInfo.getCenterTile()
val civInfo = construction.cityInfo.civInfo
// This overrides the others
if (uniqueObjects.any {
it.placeholderText == "Not displayed as an available construction unless [] is built"
&& !construction.containsBuildingOrEquivalent(it.params[0])
})
return "Should not be displayed"
if (uniqueObjects
.any {
it.placeholderText == "Not displayed as an available construction unless [] is built"
&& !construction.containsBuildingOrEquivalent(it.params[0])
}
) return "Should not be displayed"
for (unique in uniqueObjects.filter { it.placeholderText == "Not displayed as an available construction without []" }) {
val filter = unique.params[0]
@ -437,7 +456,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText {
if (civInfo.isCityState())
return "No world wonders for city-states"
val ruleSet = civInfo.gameInfo.ruleSet
val startingEra = civInfo.gameInfo.gameParameters.startingEra
if (startingEra in ruleSet.eras && name in ruleSet.eras[startingEra]!!.startingObsoleteWonders)
@ -602,7 +621,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText {
return true
}
fun matchesFilter(filter: String): Boolean {
return when (filter) {
"All" -> true
@ -612,6 +631,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText {
replaces -> true
else -> {
if (uniques.contains(filter)) return true
if (isStats(filter) && isStatRelated(Stat.valueOf(filter))) return true
return false
}
}

View File

@ -18,6 +18,7 @@ class Era : INamed {
var settlerPopulation = 1
var settlerBuildings = ArrayList<String>()
var startingObsoleteWonders = ArrayList<String>()
var baseUnitBuyCost = 200
var iconRGB: List<Int>? = null
fun getStartingUnits(): List<String> {

View File

@ -461,7 +461,7 @@ object RulesetCache : HashMap<String,Ruleset>() {
}
fun getBaseRuleset() = this[BaseRuleset.Civ_V_Vanilla.fullName]!!.clone() // safeguard, o no-one edits the base ruleset by mistake
fun getBaseRuleset() = this[BaseRuleset.Civ_V_Vanilla.fullName]!!.clone() // safeguard, so no-one edits the base ruleset by mistake
fun getComplexRuleset(mods: LinkedHashSet<String>): Ruleset {
val newRuleset = Ruleset()

View File

@ -1,15 +1,18 @@
package com.unciv.models.ruleset.unit
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.IConstruction
import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.INonPerpetualConstruction
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.Unique
import com.unciv.models.stats.INamed
import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.ICivilopediaText
import com.unciv.ui.civilopedia.CivilopediaText
import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.utils.Fonts
import java.util.*
@ -22,11 +25,11 @@ import kotlin.math.pow
/** This is the basic info of the units, as specified in Units.json,
in contrast to MapUnit, which is a specific unit of a certain type that appears on the map */
class BaseUnit : INamed, IConstruction, ICivilopediaText {
class BaseUnit : INamed, INonPerpetualConstruction, CivilopediaText() {
override lateinit var name: String
var cost: Int = 0
var hurryCostModifier: Int = 0
override var hurryCostModifier: Int = 0
var movement: Int = 0
var strength: Int = 0
var rangedStrength: Int = 0
@ -36,8 +39,8 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
fun getType() = ruleset.unitTypes[unitType]!!
var requiredTech: String? = null
var requiredResource: String? = null
var uniques = HashSet<String>()
val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } }
override var uniques = ArrayList<String>() // Can not be a hashset as that would remove doubles
override val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } }
var replacementTextForUniques = ""
var promotions = HashSet<String>()
var obsoleteTech: String? = null
@ -45,7 +48,7 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
var replaces: String? = null
var uniqueTo: String? = null
var attackSound: String? = null
lateinit var ruleset: Ruleset
override var civilopediaText = listOf<FormattedLine>()
@ -110,7 +113,8 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
if (cost > 0) {
stats.clear()
stats += "$cost${Fonts.production}"
if (canBePurchased()) stats += "${(getBaseGoldCost()*0.1).toInt()*10}${Fonts.gold}"
if (canBePurchasedWithStat(CityInfo(), Stat.Gold, true))
stats += "${getBaseGoldCost(UncivGame.Current.gameInfo.currentPlayerCiv).toInt() / 10 * 10}${Fonts.gold}"
textList += FormattedLine(stats.joinToString(", ", "{Cost}: "))
}
@ -174,7 +178,7 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
return textList
}
fun getMapUnit(ruleset: Ruleset): MapUnit {
val unit = MapUnit()
unit.name = name
@ -184,8 +188,6 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
return unit
}
override fun canBePurchased() = "Cannot be purchased" !in uniques
override fun getProductionCost(civInfo: CivilizationInfo): Int {
var productionCost = cost.toFloat()
if (civInfo.isCityState())
@ -198,33 +200,44 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
return productionCost.toInt()
}
private fun getBaseGoldCost() = (30.0 * cost).pow(0.75) * (1 + hurryCostModifier / 100.0)
private fun getGoldCostWithGameSpeed(civInfo: CivilizationInfo) =
getBaseGoldCost() * civInfo.gameInfo.gameParameters.gameSpeed.modifier
override fun getGoldCost(civInfo: CivilizationInfo): Int {
var cost = getGoldCostWithGameSpeed(civInfo)
for (unique in civInfo.getMatchingUniques("Gold cost of purchasing [] units -[]%")) {
if (matchesFilter(unique.params[0]))
cost *= 1f - unique.params[1].toFloat() / 100f
}
override fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int? {
var cost = getBaseBuyCost(cityInfo, stat)?.toDouble()
if (cost == null) return null
// Deprecated since 3.15
if (civInfo.hasUnique("Gold cost of purchasing units -33%")) cost *= 0.67f
if (stat == Stat.Gold && cityInfo.civInfo.hasUnique("Gold cost of purchasing units -33%")) cost *= 0.67f
//
for (unique in civInfo.getMatchingUniques("Cost of purchasing items in cities reduced by []%"))
cost *= 1f - (unique.params[0].toFloat() / 100f)
return (cost / 10).toInt() * 10 // rounded down to nearest ten
// Deprecated since 3.15.15
if (stat == Stat.Gold) {
for (unique in cityInfo.getMatchingUniques("Gold cost of purchasing [] units -[]%")) {
if (matchesFilter(unique.params[0]))
cost *= 1f - unique.params[1].toFloat() / 100f
}
for (unique in cityInfo.getMatchingUniques("Cost of purchasing items in cities reduced by []%"))
cost *= 1f - (unique.params[0].toFloat() / 100f)
}
//
for (unique in cityInfo.getMatchingUniques("[] cost of purchasing [] units []%")) {
if (stat.name == unique.params[0] && matchesFilter(unique.params[1]))
cost *= 1f + unique.params[2].toFloat() / 100f
}
for (unique in cityInfo.getMatchingUniques("[] cost of purchasing items in cities []%"))
if (stat.name == unique.params[0])
cost *= 1f + (unique.params[1].toFloat() / 100f)
return (cost / 10f).toInt() * 10
}
fun getDisbandGold(civInfo: CivilizationInfo) = getGoldCostWithGameSpeed(civInfo).toInt() / 20
fun getDisbandGold(civInfo: CivilizationInfo) = getBaseGoldCost(civInfo).toInt() / 20
override fun shouldBeDisplayed(construction: CityConstructions): Boolean {
val rejectionReason = getRejectionReason(construction)
override fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean {
val rejectionReason = getRejectionReason(cityConstructions)
return rejectionReason == ""
|| rejectionReason.startsWith("Requires")
|| rejectionReason.startsWith("Consumes")
|| rejectionReason == "Can only be purchased"
}
fun getRejectionReason(cityConstructions: CityConstructions): String {
@ -238,7 +251,11 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
return "Should not be displayed"
}
val civRejectionReason = getRejectionReason(civInfo)
if (civRejectionReason != "") return civRejectionReason
if (civRejectionReason != "") {
if (civRejectionReason == "Unbuildable" && canBePurchasedWithAnyStat(cityConstructions.cityInfo))
return "Can only be purchased"
return civRejectionReason
}
for (unique in uniqueObjects.filter { it.placeholderText == "Requires at least [] population" })
if (unique.params[0].toInt() > cityConstructions.cityInfo.population.population)
return unique.text
@ -293,6 +310,10 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
if (wasBought && !civInfo.gameInfo.gameParameters.godMode && !unit.hasUnique("Can move immediately once bought"))
unit.currentMovement = 0f
if (unit.hasUnique("Religious Unit")) {
unit.religion = cityConstructions.cityInfo.religion.getMajorityReligion()
}
if (this.isCivilian()) return true // tiny optimization makes save files a few bytes smaller
var XP = cityConstructions.getBuiltBuildings().sumBy { it.xpForNewUnits }
@ -313,7 +334,7 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
}
unit.promotions.XP = XP
for (unique in
for (unique in
cityConstructions.cityInfo.getMatchingUniques("All newly-trained [] units [] receive the [] promotion")
.filter { cityConstructions.cityInfo.matchesFilter(it.params[1]) } +
// Deprecated since 3.15.9
@ -323,13 +344,13 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
val filter = unique.params[0]
val promotion = unique.params.last()
if (unit.matchesFilter(filter) ||
if (unit.matchesFilter(filter) ||
(
filter == "relevant" &&
filter == "relevant" &&
civInfo.gameInfo.ruleSet.unitPromotions.values
.any {
it.name == promotion
&& unit.type.name in it.unitTypes
it.name == promotion
&& unit.type.name in it.unitTypes
}
)
) {
@ -353,12 +374,12 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
}
fun matchesFilter(filter: String): Boolean {
return when (filter) {
unitType -> true
name -> true
"All" -> true
"Melee" -> isMelee()
"Ranged" -> isRanged()
"Civilian" -> isCivilian()
@ -367,7 +388,7 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
"Water" -> isWaterUnit()
"Air" -> isAirUnit()
"non-air" -> !movesLikeAirUnits()
"Nuclear Weapon" -> isNuclearWeapon()
// Deprecated as of 3.15.2
"military water" -> isMilitary() && isWaterUnit()
@ -378,8 +399,8 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
// "military units" --> "Military"
&& matchesFilter(filter.removeSuffix(" units").toLowerCase(Locale.ENGLISH).capitalize(Locale.ENGLISH))
) return true
return uniques.contains(filter)
}
return uniques.contains(filter)
}
}
}
@ -403,12 +424,12 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText {
fun isMelee() = !isRanged() && strength > 0
fun isMilitary() = isRanged() || isMelee()
fun isCivilian() = !isMilitary()
fun isLandUnit() = getType().isLandUnit()
fun isWaterUnit() = getType().isWaterUnit()
fun isAirUnit() = getType().isAirUnit()
fun isProbablySiegeUnit() =
fun isProbablySiegeUnit() =
(
isRanged()
&& (uniqueObjects + getType().uniqueObjects)

View File

@ -1,11 +1,13 @@
package com.unciv.models.stats
enum class Stat{
Production,
Food,
Gold,
Science,
Culture,
Happiness,
Faith
import com.unciv.models.UncivSound
enum class Stat (val sound: UncivSound) {
Production(UncivSound.Click),
Food(UncivSound.Click),
Gold(UncivSound.Coin),
Science(UncivSound.Chimes),
Culture(UncivSound.Paper),
Happiness(UncivSound.Click),
Faith(UncivSound.Choir),
}

View File

@ -224,7 +224,8 @@ object TranslationFileWriter {
"in all non-occupied cities",
"in all cities with a world wonder",
"in all cities connected to capital",
"in all cities with a garrison"
"in all cities with a garrison",
"in all cities in which the majority religion is a major religion"
)
val startMillis = System.currentTimeMillis()

View File

@ -7,10 +7,7 @@ import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.utils.Align
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.IConstruction
import com.unciv.logic.city.PerpetualConstruction
import com.unciv.logic.city.*
import com.unciv.models.UncivSound
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.unit.BaseUnit
@ -70,7 +67,9 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
private fun updateButtons(construction: IConstruction?) {
buttons.clear()
buttons.add(getQueueButton(construction)).padRight(5f)
buttons.add(getBuyButton(construction))
if (construction != null && construction !is PerpetualConstruction)
for (button in getBuyButtons(construction as INonPerpetualConstruction))
buttons.add(button).padRight(5f)
}
private fun updateConstructionQueue() {
@ -262,7 +261,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
if (cityConstructions.getWorkDone(constructionName) == 0) return Table()
val constructionPercentage = cityConstructions.getWorkDone(constructionName) /
construction.getProductionCost(cityConstructions.cityInfo.civInfo).toFloat()
(construction as INonPerpetualConstruction).getProductionCost(cityConstructions.cityInfo.civInfo).toFloat()
return ImageGetter.getProgressBarVertical(2f, 30f, constructionPercentage,
Color.BROWN.cpy().lerp(Color.WHITE, 0.5f), Color.WHITE)
}
@ -377,9 +376,9 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
}
}
fun purchaseConstruction(construction: IConstruction) {
fun purchaseConstruction(construction: INonPerpetualConstruction, stat: Stat = Stat.Gold) {
val city = cityScreen.city
if (!city.cityConstructions.purchaseConstruction(construction.name, selectedQueueEntry, false)) {
if (!city.cityConstructions.purchaseConstruction(construction.name, selectedQueueEntry, false, stat)) {
Popup(cityScreen).apply {
add("No space available to place [${construction.name}] near [${city.name}]".tr()).row()
addCloseButton()
@ -393,38 +392,47 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
}
cityScreen.update()
}
private fun getBuyButtons(construction: INonPerpetualConstruction?): List<TextButton> {
return listOf(Stat.Gold, Stat.Faith, Stat.Culture, Stat.Science, Stat.Food)
.mapNotNull { getBuyButton(construction, it) }
}
private fun getBuyButton(construction: IConstruction?): TextButton {
private fun getBuyButton(construction: INonPerpetualConstruction?, stat: Stat = Stat.Gold): TextButton? {
if (stat !in listOf(Stat.Gold, Stat.Faith, Stat.Culture, Stat.Science, Stat.Food))
return null
val city = cityScreen.city
val cityConstructions = city.cityConstructions
val button = "".toTextButton()
if (construction == null || construction is PerpetualConstruction ||
(!construction.canBePurchased() && !city.civInfo.gameInfo.gameParameters.godMode)) {
(!construction.canBePurchasedWithStat(city, stat) && !city.civInfo.gameInfo.gameParameters.godMode)) {
// fully disable a "buy" button only for "priceless" buildings such as wonders
// for all other cases, the price should be displayed
button.setText("Buy".tr())
button.disable()
if (stat == Stat.Gold) {
button.setText("Buy".tr())
button.disable()
}
else return null
} else {
val constructionGoldCost = construction.getGoldCost(city.civInfo)
button.setText("Buy".tr() + " " + constructionGoldCost)
button.add(ImageGetter.getStatIcon(Stat.Gold.name)).size(20f).padBottom(2f)
val constructionBuyCost = construction.getStatBuyCost(city, stat)!!
button.setText("Buy".tr() + " " + constructionBuyCost)
button.add(ImageGetter.getStatIcon(stat.name)).size(20f).padBottom(2f)
button.onClick(UncivSound.Coin) {
button.onClick(stat.sound) {
button.disable()
cityScreen.closeAllPopups()
val purchasePrompt = "Currently you have [${city.civInfo.gold}] gold.".tr() + "\n" +
"Would you like to purchase [${construction.name}] for [$constructionGoldCost] gold?".tr()
YesNoPopup(purchasePrompt, { purchaseConstruction(construction) }, cityScreen, { cityScreen.update() }).open()
val purchasePrompt = "Currently you have [${city.getStatReserve(stat)}] [${stat.name}].".tr() + "\n" +
"Would you like to purchase [${construction.name}] for [$constructionBuyCost] [${stat.name}]?".tr()
YesNoPopup(purchasePrompt, { purchaseConstruction(construction, stat) }, cityScreen, { cityScreen.update() }).open()
}
if (!construction.isBuildable(cityConstructions)
|| !cityScreen.canChangeState
|| city.isPuppet || city.isInResistance()
if (!cityScreen.canChangeState
|| city.isPuppet
|| city.isInResistance()
|| !city.canPurchase(construction)
|| (constructionGoldCost > city.civInfo.gold && !city.civInfo.gameInfo.gameParameters.godMode))
|| (constructionBuyCost > city.getStatReserve(stat) && !city.civInfo.gameInfo.gameParameters.godMode))
button.disable()
}
@ -432,7 +440,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
return button
}
private fun getRaisePriorityButton(constructionQueueIndex: Int, name: String, city: CityInfo): Table {
val tab = Table()
tab.add(ImageGetter.getImage("OtherIcons/Up").surroundWithCircle(40f))

View File

@ -13,6 +13,7 @@ import com.badlogic.gdx.utils.Align
import com.unciv.logic.battle.CityCombatant
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.INonPerpetualConstruction
import com.unciv.logic.city.PerpetualConstruction
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.ui.cityscreen.CityScreen
@ -386,7 +387,7 @@ class CityButton(val city: CityInfo, private val tileGroup: WorldTileGroup): Tab
group.addActor(label)
val constructionPercentage = cityConstructions.getWorkDone(cityCurrentConstruction.name) /
cityCurrentConstruction.getProductionCost(cityConstructions.cityInfo.civInfo).toFloat()
(cityCurrentConstruction as INonPerpetualConstruction).getProductionCost(cityConstructions.cityInfo.civInfo).toFloat()
val productionBar = ImageGetter.getProgressBarVertical(2f, groupHeight, constructionPercentage,
Color.BROWN.cpy().lerp(Color.WHITE, 0.5f), Color.BLACK)
productionBar.x = 10f

View File

@ -12,6 +12,7 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.utils.Array
import com.badlogic.gdx.utils.Disposable
import com.unciv.UncivGame
import com.unciv.models.stats.Stat
interface NativeFontImplementation {
fun getFontSize(): Int
@ -151,7 +152,6 @@ object Fonts {
return pixmap
}
const val turn = '⏳' // U+23F3 'hourglass'
const val strength = '†' // U+2020 'dagger'
const val rangedStrength = '‡' // U+2021 'double dagger'
@ -164,4 +164,16 @@ object Fonts {
const val culture = '♪' // U+266A 'eighth note' (🎵 U+1F3B5 'musical note')
const val happiness = '⌣' // U+2323 'smile' (😀 U+1F600 'grinning face')
const val faith = '☮' // U+262E 'peace symbol' (🕊 U+1F54A 'dove of peace')
fun statToChar(stat: Stat): Char {
return when (stat) {
Stat.Food -> food
Stat.Production -> production
Stat.Gold -> gold
Stat.Happiness -> happiness
Stat.Culture -> culture
Stat.Science -> science
Stat.Faith -> faith
}
}
}

View File

@ -125,13 +125,14 @@ Unless otherwise specified, all the following are from [the Noun Project](https:
* [Robot](https://thenounproject.com/term/robot/1182459/) by Lluisa Iborra, ES for Giant Death Robot
### Great People
### All Eras
* [Pallet](https://thenounproject.com/search/?q=Pallet&i=6862) By James Keuning for Great Artist
* [Gear](https://thenounproject.com/search/?q=Gear&i=17369) By Melvin Salas for Great Engineer
* [Beaker](https://thenounproject.com/search/?q=Beaker&i=621510) By Delwar Hossain for Great Scientist
* [Dove](https://thenounproject.com/search/?q=dove&i=1344088) by sandra for Great Prophet*
* [Dove](https://thenounproject.com/search/?q=dove&i=1344088) by sandra for Great Prophet
* [General](https://thenounproject.com/search/?q=general&i=933566) By anbileru adaleru for Great General
* [Religion](https://thenounproject.com/search/?q=preach&i=53064) by Bruno Gätjens González adapted for Missionary
## Resources