mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-27 13:55:54 -04:00
Regions part 1 - subdivide generated maps into regions, and use to place civs (#5556)
* json definitions * create regions, define region types * count terrains * terrain qualities * tilesInRectangle * use even q coords * major civ start locations * move to separate file * remove printlns * unused imports * strings * strings * reviews * conditionalize qualities * guess qualities of terrain types without explicit definitions * guess qualities of terrain types without explicit definitions * Update template.properties * Update template.properties * add HideInCivilopedia to technical uniques * reviews
This commit is contained in:
parent
6e6192c369
commit
09c40002f0
@ -13,7 +13,9 @@
|
|||||||
"type": "Water",
|
"type": "Water",
|
||||||
"food": 1,
|
"food": 1,
|
||||||
"movementCost": 1,
|
"movementCost": 1,
|
||||||
"RGB": [107,167,193]
|
"RGB": [107,167,193],
|
||||||
|
"uniques": ["[+2] to Fertility for Map Generation",
|
||||||
|
"Considered [Desirable] when determining start locations <on water maps>"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Grassland",
|
"name": "Grassland",
|
||||||
@ -28,7 +30,16 @@
|
|||||||
"Occurs at temperature between [0.9] and [1] and humidity between [0.2] and [0.9]",
|
"Occurs at temperature between [0.9] and [1] and humidity between [0.2] and [0.9]",
|
||||||
"Occurs at temperature between [0.8] and [0.9] and humidity between [0.6] and [0.9]",
|
"Occurs at temperature between [0.8] and [0.9] and humidity between [0.6] and [0.9]",
|
||||||
"Occurs at temperature between [0.7] and [0.8] and humidity between [0.7] and [0.9]",
|
"Occurs at temperature between [0.7] and [0.8] and humidity between [0.7] and [0.9]",
|
||||||
"Occurs at temperature between [0.6] and [0.8] and humidity between [0.4] and [0.6]"]
|
"Occurs at temperature between [0.6] and [0.8] and humidity between [0.4] and [0.6]",
|
||||||
|
"[+3] to Fertility for Map Generation",
|
||||||
|
"A Region is formed with at least [30]% [Grassland] tiles, with priority [7]",
|
||||||
|
"A Region can not contain more [Plains] tiles than [Grassland] tiles",
|
||||||
|
"Considered [Desirable] when determining start locations",
|
||||||
|
"Considered [Food] when determining start locations <in [Jungle] Regions>",
|
||||||
|
"Considered [Food] when determining start locations <in [Forest] Regions>",
|
||||||
|
"Considered [Food] when determining start locations <in [Hill] Regions>",
|
||||||
|
"Considered [Food] when determining start locations <in [Grassland] Regions>",
|
||||||
|
"Considered [Food] when determining start locations <in [Hybrid] Regions>"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Plains",
|
"name": "Plains",
|
||||||
@ -47,7 +58,16 @@
|
|||||||
"Occurs at temperature between [0.8] and [0.9] and humidity between [0.2] and [0.6]",
|
"Occurs at temperature between [0.8] and [0.9] and humidity between [0.2] and [0.6]",
|
||||||
"Occurs at temperature between [0.7] and [0.8] and humidity between [0.3] and [0.4]",
|
"Occurs at temperature between [0.7] and [0.8] and humidity between [0.3] and [0.4]",
|
||||||
"Occurs at temperature between [0.6] and [0.8] and humidity between [0.6] and [0.7]",
|
"Occurs at temperature between [0.6] and [0.8] and humidity between [0.6] and [0.7]",
|
||||||
"Occurs at temperature between [0.5] and [0.7] and humidity between [0.7] and [0.8]"]
|
"Occurs at temperature between [0.5] and [0.7] and humidity between [0.7] and [0.8]",
|
||||||
|
"[+4] to Fertility for Map Generation",
|
||||||
|
"A Region is formed with at least [30]% [Plains] tiles, with priority [6]",
|
||||||
|
"A Region can not contain more [Grassland] tiles than [Plains] tiles",
|
||||||
|
"Considered [Desirable] when determining start locations",
|
||||||
|
"Considered [Food] when determining start locations <in [Tundra] Regions>",
|
||||||
|
"Considered [Food] when determining start locations <in [Desert] Regions>",
|
||||||
|
"Considered [Food] when determining start locations <in [Hill] Regions>",
|
||||||
|
"Considered [Food] when determining start locations <in [Plains] Regions>",
|
||||||
|
"Considered [Food] when determining start locations <in [Hybrid] Regions>"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Tundra",
|
"name": "Tundra",
|
||||||
@ -59,7 +79,11 @@
|
|||||||
"Occurs at temperature between [-0.8] and [-0.5] and humidity between [0.6] and [0.8]",
|
"Occurs at temperature between [-0.8] and [-0.5] and humidity between [0.6] and [0.8]",
|
||||||
"Occurs at temperature between [-0.7] and [-0.4] and humidity between [0.4] and [0.6]",
|
"Occurs at temperature between [-0.7] and [-0.4] and humidity between [0.4] and [0.6]",
|
||||||
"Occurs at temperature between [-0.6] and [-0.4] and humidity between [0.2] and [0.4]",
|
"Occurs at temperature between [-0.6] and [-0.4] and humidity between [0.2] and [0.4]",
|
||||||
"Occurs at temperature between [-0.5] and [-0.4] and humidity between [0] and [0.2]"]
|
"Occurs at temperature between [-0.5] and [-0.4] and humidity between [0] and [0.2]",
|
||||||
|
"[+2] to Fertility for Map Generation",
|
||||||
|
"A Region is formed with at least [30]% [Tundra] tiles and [Snow] tiles, with priority [1]",
|
||||||
|
"Considered [Food] when determining start locations <in [Tundra] Regions>",
|
||||||
|
"Considered [Desirable] when determining start locations <in [Tundra] Regions>"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Desert",
|
"name": "Desert",
|
||||||
@ -70,7 +94,10 @@
|
|||||||
"Occurs at temperature between [0.1] and [0.8] and humidity between [0.2] and [0.3]",
|
"Occurs at temperature between [0.1] and [0.8] and humidity between [0.2] and [0.3]",
|
||||||
"Occurs at temperature between [0.2] and [0.7] and humidity between [0.3] and [0.4]",
|
"Occurs at temperature between [0.2] and [0.7] and humidity between [0.3] and [0.4]",
|
||||||
"Occurs at temperature between [0.4] and [0.6] and humidity between [0.4] and [0.5]",
|
"Occurs at temperature between [0.4] and [0.6] and humidity between [0.4] and [0.5]",
|
||||||
"Occurs at temperature between [0.5] and [0.6] and humidity between [0.5] and [0.7]"]
|
"Occurs at temperature between [0.5] and [0.6] and humidity between [0.5] and [0.7]",
|
||||||
|
"[+1] to Fertility for Map Generation",
|
||||||
|
"A Region is formed with at least [25]% [Desert] tiles, with priority [4]",
|
||||||
|
"Considered [Undesirable] when determining start locations <in all except [Desert] Regions>"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Lakes",
|
"name": "Lakes",
|
||||||
@ -78,7 +105,9 @@
|
|||||||
"food": 2,
|
"food": 2,
|
||||||
"gold": 1,
|
"gold": 1,
|
||||||
"RGB": [ 123, 202, 226],
|
"RGB": [ 123, 202, 226],
|
||||||
"uniques": ["Fresh water"]
|
"uniques": ["Fresh water",
|
||||||
|
"Considered [Food] when determining start locations",
|
||||||
|
"Considered [Desirable] when determining start locations"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Mountain",
|
"name": "Mountain",
|
||||||
@ -86,7 +115,12 @@
|
|||||||
"impassable": true,
|
"impassable": true,
|
||||||
"defenceBonus": 0.25,
|
"defenceBonus": 0.25,
|
||||||
"RGB": [120, 120, 120],
|
"RGB": [120, 120, 120],
|
||||||
"uniques":["Rough terrain", "Has an elevation of [4] for visibility calculations", "Occurs in chains at high elevations", "Units ending their turn on this terrain take [50] damage"]
|
"uniques": ["Rough terrain",
|
||||||
|
"Has an elevation of [4] for visibility calculations",
|
||||||
|
"Occurs in chains at high elevations",
|
||||||
|
"Units ending their turn on this terrain take [50] damage",
|
||||||
|
"Always Fertility [-2] for Map Generation",
|
||||||
|
"Considered [Undesirable] when determining start locations"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Snow",
|
"name": "Snow",
|
||||||
@ -97,7 +131,9 @@
|
|||||||
"Occurs at temperature between [-0.9] and [-0.8] and humidity between [0] and [0.8]",
|
"Occurs at temperature between [-0.9] and [-0.8] and humidity between [0] and [0.8]",
|
||||||
"Occurs at temperature between [-0.8] and [-0.7] and humidity between [0] and [0.6]",
|
"Occurs at temperature between [-0.8] and [-0.7] and humidity between [0] and [0.6]",
|
||||||
"Occurs at temperature between [-0.7] and [-0.6] and humidity between [0] and [0.4]",
|
"Occurs at temperature between [-0.7] and [-0.6] and humidity between [0] and [0.4]",
|
||||||
"Occurs at temperature between [-0.6] and [-0.5] and humidity between [0] and [0.2]"]
|
"Occurs at temperature between [-0.6] and [-0.5] and humidity between [0] and [0.2]",
|
||||||
|
"Always Fertility [-1] for Map Generation",
|
||||||
|
"Considered [Undesirable] when determining start locations"]
|
||||||
},
|
},
|
||||||
|
|
||||||
// Terrain features
|
// Terrain features
|
||||||
@ -110,8 +146,15 @@
|
|||||||
"defenceBonus": 0.25,
|
"defenceBonus": 0.25,
|
||||||
"RGB": [105,125,72],
|
"RGB": [105,125,72],
|
||||||
"occursOn": ["Tundra","Plains","Grassland","Desert","Snow"],
|
"occursOn": ["Tundra","Plains","Grassland","Desert","Snow"],
|
||||||
"uniques": ["Rough terrain", "[+5] Strength for cities built on this terrain",
|
"uniques": ["Rough terrain",
|
||||||
"Has an elevation of [2] for visibility calculations", "Occurs in groups around high elevations"]
|
"[+5] Strength for cities built on this terrain",
|
||||||
|
"Has an elevation of [2] for visibility calculations",
|
||||||
|
"Occurs in groups around high elevations",
|
||||||
|
"[+1] to Fertility for Map Generation",
|
||||||
|
"A Region is formed with at least [40]% [Hill] tiles, with priority [5]",
|
||||||
|
"Base Terrain on this tile is not counted for Region determination",
|
||||||
|
"Considered [Desirable] when determining start locations",
|
||||||
|
"Considered [Production] when determining start locations"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Forest",
|
"name": "Forest",
|
||||||
@ -123,8 +166,17 @@
|
|||||||
"unbuildable": true,
|
"unbuildable": true,
|
||||||
"defenceBonus": 0.25,
|
"defenceBonus": 0.25,
|
||||||
"occursOn": ["Tundra","Plains","Grassland","Hill"],
|
"occursOn": ["Tundra","Plains","Grassland","Hill"],
|
||||||
"uniques": ["Rough terrain", "Provides a one-time Production bonus to the closest city when cut down",
|
"uniques": ["Rough terrain",
|
||||||
"Blocks line-of-sight from tiles at same elevation", "Resistant to nukes", "Can be destroyed by nukes"],
|
"Provides a one-time Production bonus to the closest city when cut down",
|
||||||
|
"Blocks line-of-sight from tiles at same elevation",
|
||||||
|
"Resistant to nukes", "Can be destroyed by nukes",
|
||||||
|
"A Region is formed with at least [30]% [Forest] tiles, with priority [3]",
|
||||||
|
"A Region is formed with at least [35]% [Forest] tiles and [Jungle] tiles, with priority [3]",
|
||||||
|
"A Region can not contain more [Jungle] tiles than [Forest] tiles",
|
||||||
|
"Considered [Desirable] when determining start locations",
|
||||||
|
"Considered [Production] when determining start locations",
|
||||||
|
"Considered [Food] when determining start locations <in [Forest] Regions>",
|
||||||
|
"Considered [Food] when determining start locations <in [Tundra] Regions>"],
|
||||||
"civilopediaText": [{"text":"A Camp can be built here without cutting it down", "link":"Improvement/Camp"}]
|
"civilopediaText": [{"text":"A Camp can be built here without cutting it down", "link":"Improvement/Camp"}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -136,7 +188,15 @@
|
|||||||
"unbuildable": true,
|
"unbuildable": true,
|
||||||
"defenceBonus": 0.25,
|
"defenceBonus": 0.25,
|
||||||
"occursOn": ["Plains","Grassland"],
|
"occursOn": ["Plains","Grassland"],
|
||||||
"uniques": ["Rough terrain", "Blocks line-of-sight from tiles at same elevation", "Resistant to nukes", "Can be destroyed by nukes"]
|
"uniques": ["Rough terrain",
|
||||||
|
"Blocks line-of-sight from tiles at same elevation",
|
||||||
|
"Resistant to nukes", "Can be destroyed by nukes",
|
||||||
|
"[-1] to Fertility for Map Generation",
|
||||||
|
"A Region is formed with at least [30]% [Jungle] tiles, with priority [2]",
|
||||||
|
"A Region is formed with at least [35]% [Jungle] tiles and [Forest] tiles, with priority [2]",
|
||||||
|
"A Region can not contain more [Forest] tiles than [Jungle] tiles",
|
||||||
|
"Considered [Food] when determining start locations <in all except [Grassland] Regions>",
|
||||||
|
"Considered [Desirable] when determining start locations <in all except [Grassland] Regions>"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Marsh",
|
"name": "Marsh",
|
||||||
@ -146,7 +206,8 @@
|
|||||||
"unbuildable": true,
|
"unbuildable": true,
|
||||||
"defenceBonus": -0.15,
|
"defenceBonus": -0.15,
|
||||||
"occursOn": ["Grassland"],
|
"occursOn": ["Grassland"],
|
||||||
"uniques": ["Rare feature"],
|
"uniques": ["Rare feature",
|
||||||
|
"[-2] to Fertility for Map Generation"],
|
||||||
"civilopediaText": [{"text":"Only Polders can be built here", "link":"Improvement/Polder"}]
|
"civilopediaText": [{"text":"Only Polders can be built here", "link":"Improvement/Polder"}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -169,7 +230,11 @@
|
|||||||
"unbuildable": true,
|
"unbuildable": true,
|
||||||
"defenceBonus": -0.1,
|
"defenceBonus": -0.1,
|
||||||
"occursOn": ["Desert"],
|
"occursOn": ["Desert"],
|
||||||
"uniques": ["Fresh water", "Rare feature", "Only [All Road] improvements may be built on this tile"]
|
"uniques": ["Fresh water", "Rare feature",
|
||||||
|
"Only [All Road] improvements may be built on this tile",
|
||||||
|
"Always Fertility [4] for Map Generation",
|
||||||
|
"Considered [Food] when determining start locations",
|
||||||
|
"Considered [Desirable] when determining start locations"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Flood plains",
|
"name": "Flood plains",
|
||||||
@ -177,14 +242,19 @@
|
|||||||
"food": 2,
|
"food": 2,
|
||||||
"movementCost": 1,
|
"movementCost": 1,
|
||||||
"defenceBonus": -0.1,
|
"defenceBonus": -0.1,
|
||||||
"occursOn": ["Desert"]
|
"occursOn": ["Desert"],
|
||||||
|
"uniques": ["Always Fertility [5] for Map Generation",
|
||||||
|
"Considered [Food] when determining start locations",
|
||||||
|
"Considered [Desirable] when determining start locations"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Ice",
|
"name": "Ice",
|
||||||
"type": "TerrainFeature",
|
"type": "TerrainFeature",
|
||||||
"impassable": true,
|
"impassable": true,
|
||||||
"overrideStats": true,
|
"overrideStats": true,
|
||||||
"occursOn": ["Ocean", "Coast"]
|
"occursOn": ["Ocean", "Coast"],
|
||||||
|
"uniques": ["[-1] to Fertility for Map Generation",
|
||||||
|
"Considered [Undesirable] when determining start locations"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Atoll",
|
"name": "Atoll",
|
||||||
|
@ -143,7 +143,10 @@
|
|||||||
"terrainsCanBeFoundOn": ["Forest","Tundra"],
|
"terrainsCanBeFoundOn": ["Forest","Tundra"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Camp",
|
"improvement": "Camp",
|
||||||
"improvementStats": {"gold": 1}
|
"improvementStats": {"gold": 1},
|
||||||
|
"uniques": ["Appears in [Tundra] regions with weight [40]",
|
||||||
|
"Appears in [Forest] regions with weight [10]",
|
||||||
|
"Appears near City States with weight [15]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Cotton",
|
"name": "Cotton",
|
||||||
@ -151,7 +154,11 @@
|
|||||||
"terrainsCanBeFoundOn": ["Grassland","Plains","Desert"],
|
"terrainsCanBeFoundOn": ["Grassland","Plains","Desert"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Plantation",
|
"improvement": "Plantation",
|
||||||
"improvementStats": {"gold": 1}
|
"improvementStats": {"gold": 1},
|
||||||
|
"uniques": ["Appears in [Desert] regions with weight [15]",
|
||||||
|
"Appears in [Grassland] regions with weight [30]",
|
||||||
|
"Appears in [Hybrid] regions with weight [15]",
|
||||||
|
"Appears near City States with weight [10]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Dyes",
|
"name": "Dyes",
|
||||||
@ -159,7 +166,11 @@
|
|||||||
"terrainsCanBeFoundOn": ["Jungle","Forest"],
|
"terrainsCanBeFoundOn": ["Jungle","Forest"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Plantation",
|
"improvement": "Plantation",
|
||||||
"improvementStats": {"gold": 1}
|
"improvementStats": {"gold": 1},
|
||||||
|
"uniques": ["Appears in [Tundra] regions with weight [5]",
|
||||||
|
"Appears in [Jungle] regions with weight [5]",
|
||||||
|
"Appears in [Forest] regions with weight [30]",
|
||||||
|
"Appears near City States with weight [10]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Gems",
|
"name": "Gems",
|
||||||
@ -167,7 +178,13 @@
|
|||||||
"terrainsCanBeFoundOn": ["Jungle","Grassland","Plains","Desert","Tundra","Hill"],
|
"terrainsCanBeFoundOn": ["Jungle","Grassland","Plains","Desert","Tundra","Hill"],
|
||||||
"gold": 3,
|
"gold": 3,
|
||||||
"improvement": "Mine",
|
"improvement": "Mine",
|
||||||
"improvementStats": {"production": 1}
|
"improvementStats": {"production": 1},
|
||||||
|
"uniques": ["Appears in [Tundra] regions with weight [5]",
|
||||||
|
"Appears in [Jungle] regions with weight [20]",
|
||||||
|
"Appears in [Hill] regions with weight [15]",
|
||||||
|
"Appears in [Grassland] regions with weight [5]",
|
||||||
|
"Appears in [Hybrid] regions with weight [5]",
|
||||||
|
"Appears near City States with weight [10]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Gold Ore", // Not called "Gold" in order to not conflict with siege type units for translations
|
"name": "Gold Ore", // Not called "Gold" in order to not conflict with siege type units for translations
|
||||||
@ -175,7 +192,12 @@
|
|||||||
"terrainsCanBeFoundOn": ["Grassland","Plains","Desert","Hill"],
|
"terrainsCanBeFoundOn": ["Grassland","Plains","Desert","Hill"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Mine",
|
"improvement": "Mine",
|
||||||
"improvementStats": {"production": 1}
|
"improvementStats": {"production": 1},
|
||||||
|
"uniques": ["Appears in [Desert] regions with weight [25]",
|
||||||
|
"Appears in [Hill] regions with weight [30]",
|
||||||
|
"Appears in [Plains] regions with weight [5]",
|
||||||
|
"Appears in [Hybrid] regions with weight [5]",
|
||||||
|
"Appears near City States with weight [10]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Silver",
|
"name": "Silver",
|
||||||
@ -183,7 +205,12 @@
|
|||||||
"terrainsCanBeFoundOn": ["Desert","Tundra","Hill"],
|
"terrainsCanBeFoundOn": ["Desert","Tundra","Hill"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Mine",
|
"improvement": "Mine",
|
||||||
"improvementStats": {"production": 1}
|
"improvementStats": {"production": 1},
|
||||||
|
"uniques": ["Appears in [Tundra] regions with weight [25]",
|
||||||
|
"Appears in [Hill] regions with weight [30]",
|
||||||
|
"Appears in [Grassland] regions with weight [20]",
|
||||||
|
"Appears in [Hybrid] regions with weight [10]",
|
||||||
|
"Appears near City States with weight [10]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Incense",
|
"name": "Incense",
|
||||||
@ -191,7 +218,11 @@
|
|||||||
"terrainsCanBeFoundOn": ["Plains","Desert"],
|
"terrainsCanBeFoundOn": ["Plains","Desert"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Plantation",
|
"improvement": "Plantation",
|
||||||
"improvementStats": {"gold": 1}
|
"improvementStats": {"gold": 1},
|
||||||
|
"uniques": ["Appears in [Desert] regions with weight [35]",
|
||||||
|
"Appears in [Plains] regions with weight [10]",
|
||||||
|
"Appears in [Hybrid] regions with weight [5]",
|
||||||
|
"Appears near City States with weight [15]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Ivory",
|
"name": "Ivory",
|
||||||
@ -199,7 +230,10 @@
|
|||||||
"terrainsCanBeFoundOn": ["Plains"],
|
"terrainsCanBeFoundOn": ["Plains"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Camp",
|
"improvement": "Camp",
|
||||||
"improvementStats": {"gold": 1}
|
"improvementStats": {"gold": 1},
|
||||||
|
"uniques": ["Appears in [Plains] regions with weight [35]",
|
||||||
|
"Appears in [Hybrid] regions with weight [15]",
|
||||||
|
"Appears near City States with weight [10]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Silk",
|
"name": "Silk",
|
||||||
@ -207,7 +241,11 @@
|
|||||||
"terrainsCanBeFoundOn": ["Forest"],
|
"terrainsCanBeFoundOn": ["Forest"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Plantation",
|
"improvement": "Plantation",
|
||||||
"improvementStats": {"gold": 1}
|
"improvementStats": {"gold": 1},
|
||||||
|
"uniques": ["Appears in [Jungle] regions with weight [5]",
|
||||||
|
"Appears in [Forest] regions with weight [30]",
|
||||||
|
"Appears in [Hybrid] regions with weight [5]",
|
||||||
|
"Appears near City States with weight [15]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Spices",
|
"name": "Spices",
|
||||||
@ -215,7 +253,13 @@
|
|||||||
"terrainsCanBeFoundOn": ["Jungle","Forest"],
|
"terrainsCanBeFoundOn": ["Jungle","Forest"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Plantation",
|
"improvement": "Plantation",
|
||||||
"improvementStats": {"gold": 1}
|
"improvementStats": {"gold": 1},
|
||||||
|
"uniques": ["Appears in [Jungle] regions with weight [30]",
|
||||||
|
"Appears in [Forest] regions with weight [10]",
|
||||||
|
"Appears in [Plains] regions with weight [5]",
|
||||||
|
"Appears in [Grassland] regions with weight [5]",
|
||||||
|
"Appears in [Hybrid] regions with weight [5]",
|
||||||
|
"Appears near City States with weight [15]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Wine",
|
"name": "Wine",
|
||||||
@ -223,7 +267,10 @@
|
|||||||
"terrainsCanBeFoundOn": ["Grassland","Plains"],
|
"terrainsCanBeFoundOn": ["Grassland","Plains"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Plantation",
|
"improvement": "Plantation",
|
||||||
"improvementStats": {"gold": 1}
|
"improvementStats": {"gold": 1},
|
||||||
|
"uniques": ["Appears in [Plains] regions with weight [35]",
|
||||||
|
"Appears in [Hybrid] regions with weight [15]",
|
||||||
|
"Appears near City States with weight [10]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Sugar",
|
"name": "Sugar",
|
||||||
@ -231,7 +278,12 @@
|
|||||||
"terrainsCanBeFoundOn": ["Plains","Flood plains","Grassland","Marsh"],
|
"terrainsCanBeFoundOn": ["Plains","Flood plains","Grassland","Marsh"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Plantation",
|
"improvement": "Plantation",
|
||||||
"improvementStats": {"gold": 1}
|
"improvementStats": {"gold": 1},
|
||||||
|
"uniques": ["Appears in [Jungle] regions with weight [20]",
|
||||||
|
"Appears in [Desert] regions with weight [15]",
|
||||||
|
"Appears in [Grassland] regions with weight [20]",
|
||||||
|
"Appears in [Hybrid] regions with weight [5]",
|
||||||
|
"Appears near City States with weight [10]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Marble",
|
"name": "Marble",
|
||||||
@ -249,7 +301,14 @@
|
|||||||
"food": 1,
|
"food": 1,
|
||||||
"gold": 1,
|
"gold": 1,
|
||||||
"improvement": "Fishing Boats",
|
"improvement": "Fishing Boats",
|
||||||
"improvementStats": {"food": 1}
|
"improvementStats": {"food": 1},
|
||||||
|
"uniques": ["Appears in [Tundra] regions with weight [35]",
|
||||||
|
"Appears in [Forest] regions with weight [10]",
|
||||||
|
"Appears in [Hill] regions with weight [10]",
|
||||||
|
"Appears in [Plains] regions with weight [5]",
|
||||||
|
"Appears in [Grassland] regions with weight [10]",
|
||||||
|
"Appears in [Hybrid] regions with weight [20]",
|
||||||
|
"Appears near City States with weight [10]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Pearls",
|
"name": "Pearls",
|
||||||
@ -257,7 +316,15 @@
|
|||||||
"terrainsCanBeFoundOn": ["Coast"],
|
"terrainsCanBeFoundOn": ["Coast"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Fishing Boats",
|
"improvement": "Fishing Boats",
|
||||||
"improvementStats": {"food": 1}
|
"improvementStats": {"food": 1},
|
||||||
|
"uniques": ["Appears in [Jungle] regions with weight [20]",
|
||||||
|
"Appears in [Forest] regions with weight [10]",
|
||||||
|
"Appears in [Desert] regions with weight [5]",
|
||||||
|
"Appears in [Hill] regions with weight [15]",
|
||||||
|
"Appears in [Plains] regions with weight [5]",
|
||||||
|
"Appears in [Grassland] regions with weight [10]",
|
||||||
|
"Appears in [Hybrid] regions with weight [20]",
|
||||||
|
"Appears near City States with weight [15]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Jewelry",
|
"name": "Jewelry",
|
||||||
@ -279,6 +346,11 @@
|
|||||||
"gold": 1,
|
"gold": 1,
|
||||||
"improvement": "Plantation",
|
"improvement": "Plantation",
|
||||||
"improvementStats": {"gold": 1},
|
"improvementStats": {"gold": 1},
|
||||||
|
"uniques": ["Appears in [Jungle] regions with weight [35]",
|
||||||
|
"Appears in [Forest] regions with weight [5]",
|
||||||
|
"Appears in [Desert] regions with weight [5]",
|
||||||
|
"Appears in [Hybrid] regions with weight [5]",
|
||||||
|
"Appears near City States with weight [15]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Copper",
|
"name": "Copper",
|
||||||
@ -286,7 +358,15 @@
|
|||||||
"terrainsCanBeFoundOn": ["Plains","Grassland","Desert","Tundra","Snow"],
|
"terrainsCanBeFoundOn": ["Plains","Grassland","Desert","Tundra","Snow"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Mine",
|
"improvement": "Mine",
|
||||||
"improvementStats": {"production": 2}
|
"improvementStats": {"production": 2},
|
||||||
|
"uniques": ["Appears in [Tundra] regions with weight [15]",
|
||||||
|
"Appears in [Jungle] regions with weight [5]",
|
||||||
|
"Appears in [Forest] regions with weight [5]",
|
||||||
|
"Appears in [Desert] regions with weight [10]",
|
||||||
|
"Appears in [Hill] regions with weight [30]",
|
||||||
|
"Appears in [Grassland] regions with weight [20]",
|
||||||
|
"Appears in [Hybrid] regions with weight [20]",
|
||||||
|
"Appears near City States with weight [10]"]
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
@ -307,6 +387,14 @@
|
|||||||
"gold": 1,
|
"gold": 1,
|
||||||
"improvement": "Fishing Boats",
|
"improvement": "Fishing Boats",
|
||||||
"improvementStats": {"food": 1},
|
"improvementStats": {"food": 1},
|
||||||
|
"uniques": ["Appears in [Tundra] regions with weight [30]",
|
||||||
|
"Appears in [Jungle] regions with weight [5]",
|
||||||
|
"Appears in [Forest] regions with weight [10]",
|
||||||
|
"Appears in [Hill] regions with weight [10]",
|
||||||
|
"Appears in [Plains] regions with weight [5]",
|
||||||
|
"Appears in [Grassland] regions with weight [20]",
|
||||||
|
"Appears in [Hybrid] regions with weight [20]",
|
||||||
|
"Appears near City States with weight [15]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Salt",
|
"name": "Salt",
|
||||||
@ -315,7 +403,14 @@
|
|||||||
"gold": 1,
|
"gold": 1,
|
||||||
"food": 1,
|
"food": 1,
|
||||||
"improvement": "Mine",
|
"improvement": "Mine",
|
||||||
"improvementStats": {"food": 1,"production":1}
|
"improvementStats": {"food": 1,"production":1},
|
||||||
|
"uniques": ["Appears in [Tundra] regions with weight [15]",
|
||||||
|
"Appears in [Forest] regions with weight [5]",
|
||||||
|
"Appears in [Desert] regions with weight [15]",
|
||||||
|
"Appears in [Hill] regions with weight [10]",
|
||||||
|
"Appears in [Plains] regions with weight [25]",
|
||||||
|
"Appears in [Hybrid] regions with weight [15]",
|
||||||
|
"Appears near City States with weight [10]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Truffles",
|
"name": "Truffles",
|
||||||
@ -323,6 +418,12 @@
|
|||||||
"terrainsCanBeFoundOn": ["Forest","Marsh","Jungle"],
|
"terrainsCanBeFoundOn": ["Forest","Marsh","Jungle"],
|
||||||
"gold": 2,
|
"gold": 2,
|
||||||
"improvement": "Camp",
|
"improvement": "Camp",
|
||||||
"improvementStats": {"gold": 1}
|
"improvementStats": {"gold": 1},
|
||||||
|
"uniques": ["Appears in [Jungle] regions with weight [5]",
|
||||||
|
"Appears in [Forest] regions with weight [30]",
|
||||||
|
"Appears in [Plains] regions with weight [5]",
|
||||||
|
"Appears in [Grassland] regions with weight [5]",
|
||||||
|
"Appears in [Hybrid] regions with weight [10]",
|
||||||
|
"Appears near City States with weight [15]"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1181,6 +1181,9 @@ Strategic resource =
|
|||||||
Fresh water =
|
Fresh water =
|
||||||
non-fresh water =
|
non-fresh water =
|
||||||
Natural Wonder =
|
Natural Wonder =
|
||||||
|
Hybrid =
|
||||||
|
Undesirable =
|
||||||
|
Desirable =
|
||||||
|
|
||||||
# improvementFilters
|
# improvementFilters
|
||||||
|
|
||||||
|
@ -41,8 +41,12 @@ object GameStarter {
|
|||||||
tileMap = MapSaver.loadMap(gameSetupInfo.mapFile!!)
|
tileMap = MapSaver.loadMap(gameSetupInfo.mapFile!!)
|
||||||
// Don't override the map parameters - this can include if we world wrap or not!
|
// Don't override the map parameters - this can include if we world wrap or not!
|
||||||
} else runAndMeasure("generateMap") {
|
} else runAndMeasure("generateMap") {
|
||||||
tileMap = mapGen.generateMap(gameSetupInfo.mapParameters)
|
// The mapgen needs to know what civs are in the game to generate regions, starts and resources
|
||||||
|
addCivilizations(gameSetupInfo.gameParameters, gameInfo, ruleset, existingMap = false)
|
||||||
|
tileMap = mapGen.generateMap(gameSetupInfo.mapParameters, gameInfo.civilizations)
|
||||||
tileMap.mapParameters = gameSetupInfo.mapParameters
|
tileMap.mapParameters = gameSetupInfo.mapParameters
|
||||||
|
// Now forget them for a moment! MapGen can silently fail to place some city states, so then we'll use the old fallback method to place those.
|
||||||
|
gameInfo.civilizations.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
runAndMeasure("addCivilizations") {
|
runAndMeasure("addCivilizations") {
|
||||||
@ -52,7 +56,8 @@ object GameStarter {
|
|||||||
addCivilizations(
|
addCivilizations(
|
||||||
gameSetupInfo.gameParameters,
|
gameSetupInfo.gameParameters,
|
||||||
gameInfo,
|
gameInfo,
|
||||||
ruleset
|
ruleset,
|
||||||
|
existingMap = true
|
||||||
) // this is before gameInfo.setTransients, so gameInfo doesn't yet have the gameBasics
|
) // this is before gameInfo.setTransients, so gameInfo doesn't yet have the gameBasics
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +174,7 @@ object GameStarter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addCivilizations(newGameParameters: GameParameters, gameInfo: GameInfo, ruleset: Ruleset) {
|
private fun addCivilizations(newGameParameters: GameParameters, gameInfo: GameInfo, ruleset: Ruleset, existingMap: Boolean) {
|
||||||
val availableCivNames = Stack<String>()
|
val availableCivNames = Stack<String>()
|
||||||
// CityState or Spectator civs are not available for Random pick
|
// CityState or Spectator civs are not available for Random pick
|
||||||
availableCivNames.addAll(ruleset.nations.filter { it.value.isMajorCiv() }.keys.shuffled())
|
availableCivNames.addAll(ruleset.nations.filter { it.value.isMajorCiv() }.keys.shuffled())
|
||||||
@ -183,9 +188,16 @@ object GameStarter {
|
|||||||
gameInfo.civilizations.add(barbarianCivilization)
|
gameInfo.civilizations.add(barbarianCivilization)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val civNamesWithStartingLocations = if(existingMap) gameInfo.tileMap.startingLocationsByNation.keys
|
||||||
|
else emptySet()
|
||||||
|
val presetMajors = Stack<String>()
|
||||||
|
presetMajors.addAll(availableCivNames.filter { it in civNamesWithStartingLocations })
|
||||||
|
|
||||||
for (player in newGameParameters.players.sortedBy { it.chosenCiv == "Random" }) {
|
for (player in newGameParameters.players.sortedBy { it.chosenCiv == "Random" }) {
|
||||||
val nationName = if (player.chosenCiv != "Random") player.chosenCiv
|
val nationName = if (player.chosenCiv != "Random") player.chosenCiv
|
||||||
|
else if (presetMajors.isNotEmpty()) presetMajors.pop()
|
||||||
else availableCivNames.pop()
|
else availableCivNames.pop()
|
||||||
|
availableCivNames.remove(nationName) // In case we got it from a map preset
|
||||||
|
|
||||||
val playerCiv = CivilizationInfo(nationName)
|
val playerCiv = CivilizationInfo(nationName)
|
||||||
for (tech in startingTechs)
|
for (tech in startingTechs)
|
||||||
@ -195,8 +207,6 @@ object GameStarter {
|
|||||||
gameInfo.civilizations.add(playerCiv)
|
gameInfo.civilizations.add(playerCiv)
|
||||||
}
|
}
|
||||||
|
|
||||||
val civNamesWithStartingLocations = gameInfo.tileMap.startingLocationsByNation.keys
|
|
||||||
|
|
||||||
val availableCityStatesNames = Stack<String>()
|
val availableCityStatesNames = Stack<String>()
|
||||||
// since we shuffle and then order by, we end up with all the City-States with starting tiles first in a random order,
|
// since we shuffle and then order by, we end up with all the City-States with starting tiles first in a random order,
|
||||||
// and then all the other City-States in a random order! Because the sortedBy function is stable!
|
// and then all the other City-States in a random order! Because the sortedBy function is stable!
|
||||||
|
@ -117,6 +117,13 @@ object HexMath {
|
|||||||
cubic2HexCoords(evenQ2CubicCoords(evenQCoord))
|
cubic2HexCoords(evenQ2CubicCoords(evenQCoord))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hex2EvenQCoords(hexCoord: Vector2): Vector2 {
|
||||||
|
return if (hexCoord == Vector2.Zero)
|
||||||
|
Vector2.Zero
|
||||||
|
else
|
||||||
|
cubic2EvenQCoords(hex2CubicCoords(hexCoord))
|
||||||
|
}
|
||||||
|
|
||||||
fun roundCubicCoords(cubicCoords: Vector3): Vector3 {
|
fun roundCubicCoords(cubicCoords: Vector3): Vector3 {
|
||||||
var rx = round(cubicCoords.x)
|
var rx = round(cubicCoords.x)
|
||||||
var ry = round(cubicCoords.y)
|
var ry = round(cubicCoords.y)
|
||||||
|
@ -354,6 +354,23 @@ open class TileInfo {
|
|||||||
return stats.food + stats.production + stats.gold
|
return stats.food + stats.production + stats.gold
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For dividing the map into Regions to determine start locations
|
||||||
|
fun getTileFertility(checkCoasts: Boolean): Int {
|
||||||
|
val terrains = getAllTerrains()
|
||||||
|
var fertility = 0
|
||||||
|
for (terrain in terrains) {
|
||||||
|
if (terrain.hasUnique(UniqueType.OverrideFertility))
|
||||||
|
return terrain.getMatchingUniques(UniqueType.OverrideFertility).first().params[0].toInt()
|
||||||
|
else
|
||||||
|
fertility += terrain.getMatchingUniques(UniqueType.AddFertility)
|
||||||
|
.sumBy { it.params[0].toInt() }
|
||||||
|
}
|
||||||
|
if (isAdjacentToRiver()) fertility += 1
|
||||||
|
if (isAdjacentToFreshwater) fertility += 1 // meaning total +2 for river
|
||||||
|
if (checkCoasts && isCoastalTile()) fertility += 2
|
||||||
|
return fertility
|
||||||
|
}
|
||||||
|
|
||||||
fun getImprovementStats(improvement: TileImprovement, observingCiv: CivilizationInfo, city: CityInfo?): Stats {
|
fun getImprovementStats(improvement: TileImprovement, observingCiv: CivilizationInfo, city: CityInfo?): Stats {
|
||||||
val stats = improvement.cloneStats()
|
val stats = improvement.cloneStats()
|
||||||
if (hasViewableResource(observingCiv) && tileResource.improvement == improvement.name)
|
if (hasViewableResource(observingCiv) && tileResource.improvement == improvement.name)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.unciv.logic.map
|
package com.unciv.logic.map
|
||||||
|
|
||||||
|
import com.badlogic.gdx.math.Rectangle
|
||||||
import com.badlogic.gdx.math.Vector2
|
import com.badlogic.gdx.math.Vector2
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.logic.GameInfo
|
import com.unciv.logic.GameInfo
|
||||||
@ -198,6 +199,27 @@ class TileMap {
|
|||||||
}
|
}
|
||||||
}.filterNotNull()
|
}.filterNotNull()
|
||||||
|
|
||||||
|
/** @return all tiles within [rectangle], respecting world edges and wrap.
|
||||||
|
* If using even Q coordinates the rectangle will be "straight" ie parallel with rectangular map edges. */
|
||||||
|
fun getTilesInRectangle(rectangle: Rectangle, evenQ: Boolean = false): Sequence<TileInfo> =
|
||||||
|
if (rectangle.width <= 0 || rectangle.height <= 0)
|
||||||
|
sequenceOf(get(rectangle.x.toInt(), rectangle.y.toInt()))
|
||||||
|
else
|
||||||
|
sequence {
|
||||||
|
for (x in 0 until rectangle.width.toInt()) {
|
||||||
|
for (y in 0 until rectangle.height.toInt()) {
|
||||||
|
val currentX = rectangle.x + x
|
||||||
|
val currentY = rectangle.y + y
|
||||||
|
if (evenQ) {
|
||||||
|
val hexCoords = HexMath.evenQ2HexCoords(Vector2(currentX, currentY))
|
||||||
|
yield(getIfTileExistsOrNull(hexCoords.x.toInt(), hexCoords.y.toInt()))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
yield(getIfTileExistsOrNull(currentX.toInt(), currentY.toInt()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.filterNotNull()
|
||||||
|
|
||||||
/** @return tile at hex coordinates ([x],[y]) or null if they are outside the map. Respects map edges and world wrap. */
|
/** @return tile at hex coordinates ([x],[y]) or null if they are outside the map. Respects map edges and world wrap. */
|
||||||
fun getIfTileExistsOrNull(x: Int, y: Int): TileInfo? {
|
fun getIfTileExistsOrNull(x: Int, y: Int): TileInfo? {
|
||||||
if (contains(x, y))
|
if (contains(x, y))
|
||||||
|
@ -3,17 +3,15 @@ package com.unciv.logic.map.mapgenerator
|
|||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
import com.unciv.logic.HexMath
|
import com.unciv.logic.HexMath
|
||||||
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
import com.unciv.logic.map.*
|
import com.unciv.logic.map.*
|
||||||
import com.unciv.models.Counter
|
import com.unciv.models.Counter
|
||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
import com.unciv.models.ruleset.tile.ResourceType
|
import com.unciv.models.ruleset.tile.ResourceType
|
||||||
import com.unciv.models.ruleset.tile.Terrain
|
import com.unciv.models.ruleset.tile.Terrain
|
||||||
import com.unciv.models.ruleset.tile.TerrainType
|
import com.unciv.models.ruleset.tile.TerrainType
|
||||||
|
import kotlin.math.*
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.pow
|
|
||||||
import kotlin.math.sign
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +24,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
|||||||
|
|
||||||
private var randomness = MapGenerationRandomness()
|
private var randomness = MapGenerationRandomness()
|
||||||
|
|
||||||
fun generateMap(mapParameters: MapParameters): TileMap {
|
fun generateMap(mapParameters: MapParameters, civilizations: List<CivilizationInfo> = emptyList()): TileMap {
|
||||||
val mapSize = mapParameters.mapSize
|
val mapSize = mapParameters.mapSize
|
||||||
val mapType = mapParameters.type
|
val mapType = mapParameters.type
|
||||||
|
|
||||||
@ -77,12 +75,19 @@ class MapGenerator(val ruleset: Ruleset) {
|
|||||||
runAndMeasure("assignContinents") {
|
runAndMeasure("assignContinents") {
|
||||||
map.assignContinents(TileMap.AssignContinentsMode.Assign)
|
map.assignContinents(TileMap.AssignContinentsMode.Assign)
|
||||||
}
|
}
|
||||||
runAndMeasure("NaturalWonderGenerator") {
|
|
||||||
NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map)
|
|
||||||
}
|
|
||||||
runAndMeasure("RiverGenerator") {
|
runAndMeasure("RiverGenerator") {
|
||||||
RiverGenerator(map, randomness).spawnRivers()
|
RiverGenerator(map, randomness).spawnRivers()
|
||||||
}
|
}
|
||||||
|
val regions = MapRegions(ruleset)
|
||||||
|
runAndMeasure("generateRegions") {
|
||||||
|
regions.generateRegions(map, civilizations.count { ruleset.nations[it.civName]!!.isMajorCiv() })
|
||||||
|
}
|
||||||
|
runAndMeasure("assignRegions") {
|
||||||
|
regions.assignRegions(map, civilizations.filter { ruleset.nations[it.civName]!!.isMajorCiv() })
|
||||||
|
}
|
||||||
|
runAndMeasure("NaturalWonderGenerator") {
|
||||||
|
NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map)
|
||||||
|
}
|
||||||
runAndMeasure("spreadResources") {
|
runAndMeasure("spreadResources") {
|
||||||
spreadResources(map)
|
spreadResources(map)
|
||||||
}
|
}
|
||||||
|
679
core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt
Normal file
679
core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt
Normal file
@ -0,0 +1,679 @@
|
|||||||
|
package com.unciv.logic.map.mapgenerator
|
||||||
|
|
||||||
|
import com.badlogic.gdx.math.Rectangle
|
||||||
|
import com.badlogic.gdx.math.Vector2
|
||||||
|
import com.unciv.logic.HexMath
|
||||||
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
|
import com.unciv.logic.map.MapShape
|
||||||
|
import com.unciv.logic.map.TileInfo
|
||||||
|
import com.unciv.logic.map.TileMap
|
||||||
|
import com.unciv.models.ruleset.Ruleset
|
||||||
|
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.translations.equalsPlaceholderText
|
||||||
|
import com.unciv.models.translations.getPlaceholderParameters
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
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 )
|
||||||
|
}
|
||||||
|
|
||||||
|
private val regions = ArrayList<Region>()
|
||||||
|
private val tileData = HashMap<Vector2, MapGenTileData>()
|
||||||
|
|
||||||
|
/** 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.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) {
|
||||||
|
// 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<Int, Int>() // Continent ID, civs added
|
||||||
|
val continentFertility = HashMap<Int, Int>() // Continent ID, total fertility
|
||||||
|
// Keep track of the even-q columns each continent is at, to figure out if they wrap
|
||||||
|
val continentIsAtCol = HashMap<Int, HashSet<Int>>()
|
||||||
|
|
||||||
|
// 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 (continentIsAtCol[continent] == null)
|
||||||
|
continentIsAtCol[continent] = HashSet()
|
||||||
|
continentIsAtCol[continent]!!.add(HexMath.hex2EvenQCoords(tile.position).x.toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign regions to the best continents, giving half value for region #2 etc
|
||||||
|
for (regionToAssign in 1..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 = continentIsAtCol[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.count().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<Region, Region> {
|
||||||
|
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 pointsToTry = if (widerThanTall) 1..regionToSplit.rect.width.toInt()
|
||||||
|
else 1..regionToSplit.rect.height.toInt()
|
||||||
|
|
||||||
|
for (splitPoint in pointsToTry) {
|
||||||
|
val nextRect = if (widerThanTall)
|
||||||
|
splitOffRegion.tileMap.getTilesInRectangle(Rectangle(
|
||||||
|
splitOffRegion.rect.x + splitPoint - 1, splitOffRegion.rect.y,
|
||||||
|
1f, splitOffRegion.rect.height),
|
||||||
|
evenQ = true)
|
||||||
|
else
|
||||||
|
splitOffRegion.tileMap.getTilesInRectangle(Rectangle(
|
||||||
|
splitOffRegion.rect.x, splitOffRegion.rect.y + splitPoint - 1,
|
||||||
|
splitOffRegion.rect.width, 1f),
|
||||||
|
evenQ = true)
|
||||||
|
|
||||||
|
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?
|
||||||
|
if (abs(cumulativeFertility - targetFertility) <= abs(closestFertility - targetFertility)) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assignRegions(tileMap: TileMap, civilizations: List<CivilizationInfo>) {
|
||||||
|
if (civilizations.isEmpty()) return
|
||||||
|
|
||||||
|
// first assign region types
|
||||||
|
val regionTypes = ruleset.terrains.values.filter { it.hasUnique(UniqueType.RegionRequirePercentSingleType) ||
|
||||||
|
it.hasUnique(UniqueType.RegionRequirePercentTwoTypes) }
|
||||||
|
.sortedBy { if (it.hasUnique(UniqueType.RegionRequirePercentSingleType))
|
||||||
|
it.getMatchingUniques(UniqueType.RegionRequirePercentSingleType).first().params[2].toInt()
|
||||||
|
else
|
||||||
|
it.getMatchingUniques(UniqueType.RegionRequirePercentTwoTypes).first().params[3].toInt() }
|
||||||
|
|
||||||
|
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.count()) / 100 }
|
||||||
|
|| type.getMatchingUniques(UniqueType.RegionRequirePercentTwoTypes).any {
|
||||||
|
region.getTerrainAmount(it.params[1]) + region.getTerrainAmount(it.params[2]) >= (it.params[0].toInt() * region.tiles.count()) / 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
val coastBiasCivs = civilizations.filter { ruleset.nations[it.civName]!!.startBias.contains("Coast") }
|
||||||
|
val negativeBiasCivs = civilizations.filter { ruleset.nations[it.civName]!!.startBias.any { bias -> bias.equalsPlaceholderText("Avoid []") } }
|
||||||
|
.sortedByDescending { ruleset.nations[it.civName]!!.startBias.count() } // Civs with more complex avoids go first
|
||||||
|
val randomCivs = civilizations.filter { ruleset.nations[it.civName]!!.startBias.isEmpty() }.toMutableList() // We might fill this up as we go
|
||||||
|
// The rest are positive bias
|
||||||
|
val positiveBiasCivs = civilizations.filterNot { it in coastBiasCivs || it in negativeBiasCivs || it in randomCivs }
|
||||||
|
.sortedBy { ruleset.nations[it.civName]!!.startBias.count() } // civs with only one desired region go first
|
||||||
|
val positiveBiasFallbackCivs = ArrayList<CivilizationInfo>() // Civs who couln't get their desired region at first pass
|
||||||
|
|
||||||
|
// First assign coast bias civs
|
||||||
|
for (civ in coastBiasCivs) {
|
||||||
|
// Try to find a coastal start, preferably a really coastal one
|
||||||
|
var startRegion = regions.filter { tileMap[it.startPosition!!].isCoastalTile() }
|
||||||
|
.maxByOrNull { it.terrainCounts["Coastal"] ?: 0 }
|
||||||
|
if (startRegion != null) {
|
||||||
|
assignCivToRegion(civ, startRegion)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Else adjacent to a lake
|
||||||
|
startRegion = regions.filter { tileMap[it.startPosition!!].neighbors.any { neighbor -> neighbor.getBaseTerrain().hasUnique(UniqueType.FreshWater) } }
|
||||||
|
.maxByOrNull { it.terrainCounts["Coastal"] ?: 0 }
|
||||||
|
if (startRegion != null) {
|
||||||
|
assignCivToRegion(civ, startRegion)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Else adjacent to a river
|
||||||
|
startRegion = regions.filter { tileMap[it.startPosition!!].isAdjacentToRiver() }
|
||||||
|
.maxByOrNull { it.terrainCounts["Coastal"] ?: 0 }
|
||||||
|
if (startRegion != null) {
|
||||||
|
assignCivToRegion(civ, startRegion)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Else at least close to a river ????
|
||||||
|
startRegion = regions.filter { tileMap[it.startPosition!!].neighbors.any { neighbor -> neighbor.isAdjacentToRiver() } }
|
||||||
|
.maxByOrNull { it.terrainCounts["Coastal"] ?: 0 }
|
||||||
|
if (startRegion != null) {
|
||||||
|
assignCivToRegion(civ, startRegion)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Else pick a random region at the end
|
||||||
|
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 = ruleset.nations[civ.civName]!!.startBias
|
||||||
|
val startRegion = regions.filter { it.type in preferred }
|
||||||
|
.maxByOrNull { it.terrainCounts.filterKeys { terrain -> terrain in preferred }.values.sum() }
|
||||||
|
if (startRegion != null) {
|
||||||
|
assignCivToRegion(civ, startRegion)
|
||||||
|
continue
|
||||||
|
} else if (ruleset.nations[civ.civName]!!.startBias.count() == 1) { // Civs with a single bias (only) get to look for a fallback region
|
||||||
|
positiveBiasFallbackCivs.add(civ)
|
||||||
|
} else { // Others get random starts
|
||||||
|
randomCivs.add(civ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a second pass for fallback civs, choosing the region most similar to the desired type
|
||||||
|
for (civ in positiveBiasFallbackCivs) {
|
||||||
|
assignCivToRegion(civ, getFallbackRegion(ruleset.nations[civ.civName]!!.startBias.first()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next do negative bias ones (ie "Avoid []")
|
||||||
|
for (civ in negativeBiasCivs) {
|
||||||
|
val avoided = ruleset.nations[civ.civName]!!.startBias.map { it.getPlaceholderParameters()[0] }
|
||||||
|
// Try to find a region not of the avoided types, secondary sort by least number of undesired terrains
|
||||||
|
val startRegion = regions.filterNot { it.type in avoided }
|
||||||
|
.minByOrNull { it.terrainCounts.filterKeys { terrain -> terrain in avoided }.values.sum() }
|
||||||
|
if (startRegion != null) {
|
||||||
|
assignCivToRegion(civ, startRegion)
|
||||||
|
continue
|
||||||
|
} else
|
||||||
|
randomCivs.add(civ) // else pick a random region at the end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally assign the remaining civs randomly
|
||||||
|
for (civ in randomCivs) {
|
||||||
|
val startRegion = regions.random()
|
||||||
|
assignCivToRegion(civ, startRegion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignCivToRegion(civInfo: CivilizationInfo, region: Region) {
|
||||||
|
region.tileMap.addStartingLocation(civInfo.civName, region.tileMap[region.startPosition!!])
|
||||||
|
regions.remove(region) // This region can no longer be picked
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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<Vector2>()
|
||||||
|
val wetTiles = HashSet<Vector2>()
|
||||||
|
val dryTiles = HashSet<Vector2>()
|
||||||
|
val fallbackTiles = HashSet<Vector2>()
|
||||||
|
|
||||||
|
// First check center
|
||||||
|
val centerTiles = region.tileMap.getTilesInRectangle(centerRect, evenQ = true)
|
||||||
|
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.isAdjacentToFreshwater)
|
||||||
|
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, evenQ = true).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.isAdjacentToFreshwater)
|
||||||
|
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, evenQ = true).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].terrainFeatures.clear()
|
||||||
|
setRegionStart(region, panicPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns the region most similar to a region of [type] */
|
||||||
|
private fun getFallbackRegion(type: String): Region {
|
||||||
|
return regions.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: TileInfo) {
|
||||||
|
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: TileInfo) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holds a bunch of tile info that is only interesting during map gen
|
||||||
|
class MapGenTileData(val tile: TileInfo, val region: Region?) {
|
||||||
|
var closeStartPenalty = 0
|
||||||
|
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.getTerrainFeatures().firstOrNull { it.unbuildable }
|
||||||
|
?: tile.getTerrainFeatures().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<TileInfo>()
|
||||||
|
val terrainCounts = HashMap<String, Int>()
|
||||||
|
var totalFertility = 0
|
||||||
|
var type = "Hybrid" // being an undefined or indeterminate type
|
||||||
|
var startPosition: Vector2? = null
|
||||||
|
|
||||||
|
var affectedByWorldWrap = false
|
||||||
|
|
||||||
|
/** Recalculates tiles and fertility */
|
||||||
|
fun updateTiles(trim: Boolean = true) {
|
||||||
|
totalFertility = 0
|
||||||
|
var minX = 99999f
|
||||||
|
var maxX = -99999f
|
||||||
|
var minY = 99999f
|
||||||
|
var maxY = -99999f
|
||||||
|
|
||||||
|
val columnHasTile = HashSet<Int>()
|
||||||
|
|
||||||
|
tiles.clear()
|
||||||
|
for (tile in tileMap.getTilesInRectangle(rect, evenQ = true).filter {
|
||||||
|
continentID == -1 || it.getContinent() == continentID } ) {
|
||||||
|
val fertility = tile.getTileFertility(continentID != -1)
|
||||||
|
if (fertility != 0) { // If fertility is 0 this is candidate for trimming
|
||||||
|
tiles.add(tile)
|
||||||
|
totalFertility += fertility
|
||||||
|
}
|
||||||
|
|
||||||
|
if (affectedByWorldWrap)
|
||||||
|
columnHasTile.add(HexMath.hex2EvenQCoords(tile.position).x.toInt())
|
||||||
|
|
||||||
|
if (trim) {
|
||||||
|
val evenQCoords = HexMath.hex2EvenQCoords(tile.position)
|
||||||
|
minX = min(minX, evenQCoords.x)
|
||||||
|
maxX = max(maxX, evenQCoords.x)
|
||||||
|
minY = min(minY, evenQCoords.y)
|
||||||
|
maxY = max(maxY, evenQCoords.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = minX // ez way for non-wrapping regions
|
||||||
|
rect.y = minY
|
||||||
|
rect.height = maxY - minY + 1
|
||||||
|
if (affectedByWorldWrap && minX < rect.x) { // Thorough way
|
||||||
|
rect.width = columnHasTile.count().toFloat()
|
||||||
|
} else {
|
||||||
|
rect.width = maxX - minX + 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.getAllTerrains().any { it.hasUnique(UniqueType.IgnoreBaseTerrainForRegion) })
|
||||||
|
tile.getTerrainFeatures().map { it.name }.asSequence()
|
||||||
|
else
|
||||||
|
tile.getAllTerrains().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
|
||||||
|
}
|
@ -6,6 +6,7 @@ import com.unciv.logic.city.CityInfo
|
|||||||
import com.unciv.logic.civilization.CivilizationInfo
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
import com.unciv.logic.map.MapUnit
|
import com.unciv.logic.map.MapUnit
|
||||||
import com.unciv.logic.map.TileInfo
|
import com.unciv.logic.map.TileInfo
|
||||||
|
import com.unciv.logic.map.mapgenerator.Region
|
||||||
|
|
||||||
data class StateForConditionals(
|
data class StateForConditionals(
|
||||||
val civInfo: CivilizationInfo? = null,
|
val civInfo: CivilizationInfo? = null,
|
||||||
@ -16,4 +17,6 @@ data class StateForConditionals(
|
|||||||
val theirCombatant: ICombatant? = null,
|
val theirCombatant: ICombatant? = null,
|
||||||
val attackedTile: TileInfo? = null,
|
val attackedTile: TileInfo? = null,
|
||||||
val combatAction: CombatAction? = null,
|
val combatAction: CombatAction? = null,
|
||||||
|
|
||||||
|
val region: Region? = null,
|
||||||
)
|
)
|
@ -105,6 +105,11 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
|
|||||||
it.matchesFilter(condition.params[2], state.civInfo) &&
|
it.matchesFilter(condition.params[2], state.civInfo) &&
|
||||||
it.matchesFilter(condition.params[3], state.civInfo)
|
it.matchesFilter(condition.params[3], state.civInfo)
|
||||||
} in (condition.params[0].toInt())..(condition.params[1].toInt())
|
} in (condition.params[0].toInt())..(condition.params[1].toInt())
|
||||||
|
|
||||||
|
UniqueType.ConditionalOnWaterMaps -> state.region?.continentID == -1
|
||||||
|
UniqueType.ConditionalInRegionOfType -> state.region?.type == condition.params[0]
|
||||||
|
UniqueType.ConditionalInRegionExceptOfType -> state.region != null && state.region.type != condition.params[0]
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,6 +155,27 @@ enum class UniqueParameterType(val parameterName:String) {
|
|||||||
return UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
|
return UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/** Used for region definitions, can be a terrain type with region unique, or "Hybrid" */
|
||||||
|
RegionType("regionType") {
|
||||||
|
private val knownValues = setOf("Hybrid")
|
||||||
|
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
|
||||||
|
UniqueType.UniqueComplianceErrorSeverity? {
|
||||||
|
if (parameterText in knownValues) return null
|
||||||
|
if (ruleset.terrains[parameterText]?.hasUnique(UniqueType.RegionRequirePercentSingleType) == true ||
|
||||||
|
ruleset.terrains[parameterText]?.hasUnique(UniqueType.RegionRequirePercentTwoTypes) == true)
|
||||||
|
return null
|
||||||
|
return UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** Used for start placements */
|
||||||
|
TerrainQuality("terrainQuality") {
|
||||||
|
private val knownValues = setOf("Undesirable", "Food", "Desirable", "Production")
|
||||||
|
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
|
||||||
|
UniqueType.UniqueComplianceErrorSeverity? {
|
||||||
|
if (parameterText in knownValues) return null
|
||||||
|
return UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant
|
||||||
|
}
|
||||||
|
},
|
||||||
Promotion("promotion") {
|
Promotion("promotion") {
|
||||||
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
|
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
|
||||||
UniqueType.UniqueComplianceErrorSeverity? = when (parameterText) {
|
UniqueType.UniqueComplianceErrorSeverity? = when (parameterText) {
|
||||||
|
@ -307,10 +307,27 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget, val flags:
|
|||||||
BlocksLineOfSightAtSameElevation("Blocks line-of-sight from tiles at same elevation", UniqueTarget.Terrain),
|
BlocksLineOfSightAtSameElevation("Blocks line-of-sight from tiles at same elevation", UniqueTarget.Terrain),
|
||||||
VisibilityElevation("Has an elevation of [amount] for visibility calculations", UniqueTarget.Terrain),
|
VisibilityElevation("Has an elevation of [amount] for visibility calculations", UniqueTarget.Terrain),
|
||||||
|
|
||||||
|
OverrideFertility("Always Fertility [amount] for Map Generation", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
|
AddFertility("[amount] to Fertility for Map Generation", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
|
|
||||||
|
RegionRequirePercentSingleType("A Region is formed with at least [amount]% [simpleTerrain] tiles, with priority [amount]", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
|
RegionRequirePercentTwoTypes("A Region is formed with at least [amount]% [simpleTerrain] tiles and [simpleTerrain] tiles, with priority [amount]",
|
||||||
|
UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
|
RegionRequireFirstLessThanSecond("A Region can not contain more [simpleTerrain] tiles than [simpleTerrain] tiles", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
|
IgnoreBaseTerrainForRegion("Base Terrain on this tile is not counted for Region determination", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
|
|
||||||
|
HasQuality("Considered [terrainQuality] when determining start locations", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
|
|
||||||
|
LuxuryWeighting("Appears in [regionType] regions with weight [amount]", UniqueTarget.Resource, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
|
LuxuryWeightingForCityStates("Appears near City States with weight [amount]", UniqueTarget.Resource, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
|
|
||||||
|
OverrideDepositAmountOnTileFilter("Deposits in [tileFilter] tiles always provide [amount] resources", UniqueTarget.Resource),
|
||||||
|
|
||||||
NoNaturalGeneration("Doesn't generate naturally", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
NoNaturalGeneration("Doesn't generate naturally", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
TileGenerationConditions("Occurs at temperature between [amount] and [amount] and humidity between [amount] and [amount]", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
TileGenerationConditions("Occurs at temperature between [amount] and [amount] and humidity between [amount] and [amount]", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
OccursInChains("Occurs in chains at high elevations", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
OccursInChains("Occurs in chains at high elevations", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
OccursInGroups("Occurs in groups around high elevations", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
OccursInGroups("Occurs in groups around high elevations", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HideInCivilopedia)),
|
||||||
|
|
||||||
RareFeature("Rare feature", UniqueTarget.Terrain),
|
RareFeature("Rare feature", UniqueTarget.Terrain),
|
||||||
|
|
||||||
ResistsNukes("Resistant to nukes", UniqueTarget.Terrain),
|
ResistsNukes("Resistant to nukes", UniqueTarget.Terrain),
|
||||||
@ -348,8 +365,8 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget, val flags:
|
|||||||
IsAncientRuinsEquivalent("Provides a random bonus when entered", UniqueTarget.Improvement),
|
IsAncientRuinsEquivalent("Provides a random bonus when entered", UniqueTarget.Improvement),
|
||||||
|
|
||||||
Unpillagable("Unpillagable", UniqueTarget.Improvement),
|
Unpillagable("Unpillagable", UniqueTarget.Improvement),
|
||||||
Indestructible("Indestructible", UniqueTarget.Improvement),
|
|
||||||
|
|
||||||
|
Indestructible("Indestructible", UniqueTarget.Improvement),
|
||||||
///////////////////////////////////////// CONDITIONALS /////////////////////////////////////////
|
///////////////////////////////////////// CONDITIONALS /////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
@ -385,6 +402,11 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget, val flags:
|
|||||||
ConditionalNeighborTiles("with [amount] to [amount] neighboring [tileFilter] tiles", UniqueTarget.Conditional),
|
ConditionalNeighborTiles("with [amount] to [amount] neighboring [tileFilter] tiles", UniqueTarget.Conditional),
|
||||||
ConditionalNeighborTilesAnd("with [amount] to [amount] neighboring [tileFilter] [tileFilter] tiles", UniqueTarget.Conditional),
|
ConditionalNeighborTilesAnd("with [amount] to [amount] neighboring [tileFilter] [tileFilter] tiles", UniqueTarget.Conditional),
|
||||||
|
|
||||||
|
/////// region conditionals
|
||||||
|
ConditionalOnWaterMaps("on water maps", UniqueTarget.Conditional),
|
||||||
|
ConditionalInRegionOfType("in [regionType] Regions", UniqueTarget.Conditional),
|
||||||
|
ConditionalInRegionExceptOfType("in all except [regionType] Regions", UniqueTarget.Conditional),
|
||||||
|
|
||||||
///////////////////////////////////////// TRIGGERED ONE-TIME /////////////////////////////////////////
|
///////////////////////////////////////// TRIGGERED ONE-TIME /////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user