diff --git a/android/Images.Construction/UnitIcons/African Forest Elephant.png b/android/Images.Construction/UnitIcons/African Forest Elephant.png new file mode 100644 index 0000000000..f9f72bad44 Binary files /dev/null and b/android/Images.Construction/UnitIcons/African Forest Elephant.png differ diff --git a/android/Images.Construction/UnitIcons/Quinquereme.png b/android/Images.Construction/UnitIcons/Quinquereme.png new file mode 100644 index 0000000000..cb0a30285a Binary files /dev/null and b/android/Images.Construction/UnitIcons/Quinquereme.png differ diff --git a/android/Images/NationIcons/Carthage.png b/android/Images/NationIcons/Carthage.png new file mode 100644 index 0000000000..445bc6d205 Binary files /dev/null and b/android/Images/NationIcons/Carthage.png differ diff --git a/android/assets/jsons/Civ V - Vanilla/Nations.json b/android/assets/jsons/Civ V - Vanilla/Nations.json index 50283ba95e..06dfd2328a 100644 --- a/android/assets/jsons/Civ V - Vanilla/Nations.json +++ b/android/assets/jsons/Civ V - Vanilla/Nations.json @@ -773,6 +773,33 @@ "Kapfenberg", "Hallein", "Bischofshofen", "Waidhofen", "Saalbach", "Lienz", "Steyr" ] }, + { + "name": "Carthage", + "leaderName": "Dido", + "adjective": ["Carthaginian"], + "startBias": ["Coast"], + "preferredVictoryType": "Domination", + + "startIntroPart1": "Blessings and salutations to you, revered Queen Dido, founder of the legendary kingdom of Carthage. Chronicled by the words of the great poet Virgil, your husband Acerbas was murdered at the hands of your own brother, King Pygmalion of Tyre, who subsequently claimed the treasures of Acerbas that were now rightfully yours. Fearing the lengths from which your brother would pursue this vast wealth, you and your compatriots sailed for new lands. Arriving on the shores of North Africa, you tricked the local king with the simple manipulation of an ox hide, laying out a vast expanse of territory for your new home, the future kingdom of Carthage.", + "startIntroPart2": "Clever and inquisitive Dido, the world longs for a leader who can provide a shelter from the coming storm, guided by brilliant intuition and cunning. Can you lead the people in the creation of a new kingdom to rival that of once mighty Carthage? Can you build a civilization that will stand the test of time?", + + "declaringWar": "Tell me, do you all know how numerous my armies, elephants and the gdadons are? No? Today, you shall find out!", + "attacked": "Fate is against you. You earned the animosity of Carthage in your exploration. Your days are numbered.", + "defeated": "The fates became to hate me. This is it? You wouldn't destroy us so without their help.", + "introduction": "The Phoenicians welcome you to this most pleasant kingdom. I am Dido, the queen of Carthage and all that belongs to it.", + "neutralHello": "It is done.", + "hateHello": "What is it now?", + "tradeRequest": "I had an idea and I realized I should tell it to you!", + "outerColor": [205,205,205], + "innerColor": [81,0,137], + "uniqueName": "Phoenician Heritage", + "uniques": ["Gain a free [Harbor] [in all coastal cities]","Land units may cross [Mountain] tiles after the first [Great General] is earned", + "Units ending their turn on [Mountain] tiles take [50] damage"], + "cities": ["Carthage","Utique","Hippo Regius","Gades","Saguntum","Carthago Nova","Panormus","Lilybaeum","Hadrumetum","Zama Regia", + "Karalis","Malaca","Leptis Magna","Hippo Diarrhytus","Motya","Sulci","Leptis Parva","Tharros","Soluntum","Lixus", + "Oea","Theveste","Ibossim","Thapsus","Aleria","Tingis","Abyla","Sabratha","Rusadir","Baecula", + "Saldae"] + }, diff --git a/android/assets/jsons/Civ V - Vanilla/Terrains.json b/android/assets/jsons/Civ V - Vanilla/Terrains.json index f7383dd6da..a7a6fb169e 100644 --- a/android/assets/jsons/Civ V - Vanilla/Terrains.json +++ b/android/assets/jsons/Civ V - Vanilla/Terrains.json @@ -86,7 +86,7 @@ "impassable": true, "defenceBonus": 0.25, "RGB": [120, 120, 120], - "uniques":["Rough terrain", "Has an elevation of [4] for visibility calculations", "Occurs in chains at high elevations"] + "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"] }, { "name": "Snow", diff --git a/android/assets/jsons/Civ V - Vanilla/Units.json b/android/assets/jsons/Civ V - Vanilla/Units.json index 2e80c1894c..405731ae7f 100644 --- a/android/assets/jsons/Civ V - Vanilla/Units.json +++ b/android/assets/jsons/Civ V - Vanilla/Units.json @@ -161,6 +161,20 @@ "obsoleteTech": "Astronomy", "attackSound": "nonmetalhit" }, + { + "name": "Quinquereme", + "unitType": "Melee Water", + "uniqueTo": "Carthage", + "replaces": "Trireme", + "movement": 4, + "strength": 13, + "cost": 45, + "requiredTech": "Sailing", + "uniques": ["Cannot enter ocean tiles"], + "upgradesTo": "Caravel", + "obsoleteTech": "Astronomy", + "attackSound": "nonmetalhit" + }, /* { "name": "Galley", @@ -339,6 +353,22 @@ "hurryCostModifier": 20, "attackSound": "horse" }, + { + "name": "African Forest Elephant", + "unitType": "Mounted", + "uniqueTo": "Carthage", + "replaces": "Horseman", + "movement": 3, + "strength": 14, + "cost": 100, + "requiredTech": "Horseback Riding", + "upgradesTo": "Knight", + "obsoleteTech": "Metallurgy", + "promotions": ["Great Generals II"], + "uniques": ["Can move after attacking", "No defensive terrain bonus", "-[33]% Strength vs [City]","[-10]% Strength for enemy [Military] units in adjacent [All] tiles"], + "hurryCostModifier": 20, + "attackSound": "elephant" + }, { "name": "Catapult", "unitType": "Siege", diff --git a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt index 951216c532..3dbe984677 100644 --- a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt @@ -156,7 +156,8 @@ object SpecificUnitAutomation { } if (unit.getTile().militaryUnit == null // Don't move until you're accompanied by a military unit - && !unit.civInfo.isCityState()) return // ..unless you're a city state that was unable to settle its city on turn 1 + && !unit.civInfo.isCityState() // ..unless you're a city state that was unable to settle its city on turn 1 + && unit.getDamageFromTerrain() < unit.health) return // Also make sure we won't die waiting val tilesNearCities = unit.civInfo.gameInfo.getCities().asSequence() .flatMap { @@ -177,7 +178,7 @@ object SpecificUnitAutomation { val possibleCityLocations = unit.getTile().getTilesInDistance(5) .filter { val tileOwner = it.getOwner() - it.isLand && (tileOwner == null || tileOwner == unit.civInfo) // don't allow settler to settle inside other civ's territory + it.isLand && !it.isImpassible() && (tileOwner == null || tileOwner == unit.civInfo) // don't allow settler to settle inside other civ's territory && (unit.currentTile == it || unit.movement.canMoveTo(it)) && it !in tilesNearCities }.toList() diff --git a/core/src/com/unciv/logic/automation/UnitAutomation.kt b/core/src/com/unciv/logic/automation/UnitAutomation.kt index d81318ad8f..14ef3748dc 100644 --- a/core/src/com/unciv/logic/automation/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/UnitAutomation.kt @@ -19,7 +19,8 @@ object UnitAutomation { && (tile.getOwner() == null || !tile.getOwner()!!.isCityState()) && tile.neighbors.any { it.position !in unit.civInfo.exploredTiles } && unit.movement.canReach(tile) - && (!unit.civInfo.isCityState() || tile.neighbors.any { it.getOwner() == unit.civInfo }) // Don't want city-states exploring far outside their borders + && (!unit.civInfo.isCityState() || tile.neighbors.any { it.getOwner() == unit.civInfo } // Don't want city-states exploring far outside their borders + && unit.getDamageFromTerrain(tile) <= 0) // Don't take unnecessary damage } internal fun tryExplore(unit: MapUnit): Boolean { @@ -66,7 +67,8 @@ object UnitAutomation { .filter { unit.movement.canMoveTo(it.key) && unit.movement.canReach(it.key) } val reachableTilesMaxWalkingDistance = reachableTiles - .filter { it.value.totalDistance == unit.currentMovement } + .filter { it.value.totalDistance == unit.currentMovement + && unit.getDamageFromTerrain(it.key) <= 0 } // Don't end turn on damaging terrain for no good reason if (reachableTilesMaxWalkingDistance.any()) unit.movement.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first) else if (reachableTiles.any()) unit.movement.moveToTile(reachableTiles.keys.random()) } @@ -87,6 +89,9 @@ object UnitAutomation { if (unit.civInfo.isBarbarian()) throw IllegalStateException("Barbarians is not allowed here.") + // Might die next turn - move! + if (unit.health <= unit.getDamageFromTerrain() && tryHealUnit(unit)) return + if (unit.isCivilian()) { if (tryRunAwayIfNeccessary(unit)) return @@ -209,7 +214,10 @@ object UnitAutomation { .filter { it.isCityCenter() && it.getCity()!!.civInfo.isAtWarWith(unit.civInfo) } .flatMap { it.getTilesInDistance(it.getCity()!!.range) } - val dangerousTiles = (tilesInRangeOfAttack + tilesWithinBombardmentRange).toHashSet() + val tilesWithTerrainDamage = unit.currentTile.getTilesInDistance(3) + .filter { unit.getDamageFromTerrain(it) > 0 } + + val dangerousTiles = (tilesInRangeOfAttack + tilesWithinBombardmentRange + tilesWithTerrainDamage).toHashSet() val viableTilesForHealing = unitDistanceToTiles.keys @@ -230,7 +238,7 @@ object UnitAutomation { val bestTileForHealingRank = unit.rankTileForHealing(bestTileForHealing) if (currentUnitTile != bestTileForHealing - && bestTileForHealingRank > unit.rankTileForHealing(currentUnitTile)) + && bestTileForHealingRank > unit.rankTileForHealing(currentUnitTile) - unit.getDamageFromTerrain()) unit.movement.moveToTile(bestTileForHealing) unit.fortifyIfCan() @@ -272,16 +280,18 @@ object UnitAutomation { unitDistanceToTiles, tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT).toList() ).filter { - // Ignore units that would 1-shot you if you attacked + // Ignore units that would 1-shot you if you attacked. Account for taking terrain damage after the fact. BattleDamage.calculateDamageToAttacker(MapUnitCombatant(unit), it.tileToAttackFrom, - Battle.getMapCombatantOfTile(it.tileToAttack)!!) < unit.health + Battle.getMapCombatantOfTile(it.tileToAttack)!!) + + unit.getDamageFromTerrain(it.tileToAttackFrom) < unit.health } if (unit.baseUnit.isRanged()) closeEnemies = closeEnemies.filterNot { it.tileToAttack.isCityCenter() && it.tileToAttack.getCity()!!.health == 1 } - val closestEnemy = closeEnemies.minByOrNull { it.tileToAttack.aerialDistanceTo(unit.getTile()) } + val closestEnemy = closeEnemies.filter { unit.getDamageFromTerrain(it.tileToAttackFrom) <= 0 } // Don't attack from a mountain + .minByOrNull { it.tileToAttack.aerialDistanceTo(unit.getTile()) } if (closestEnemy != null) { unit.movement.headTowards(closestEnemy.tileToAttackFrom) @@ -313,7 +323,8 @@ object UnitAutomation { val reachableTileNearSiegedCity = siegedCities .flatMap { it.getCenterTile().getTilesAtDistance(2) } .sortedBy { it.aerialDistanceTo(unit.currentTile) } - .firstOrNull { unit.movement.canMoveTo(it) && unit.movement.canReach(it) } + .firstOrNull { unit.movement.canMoveTo(it) && unit.movement.canReach(it) + && unit.getDamageFromTerrain(it) <= 0 } // Avoid ending up on damaging terrain if (reachableTileNearSiegedCity != null) { unit.movement.headTowards(reachableTileNearSiegedCity) @@ -359,6 +370,7 @@ object UnitAutomation { .filter { it.key.aerialDistanceTo(closestReachableEnemyCity) <= unitRange && it.key !in tilesInBombardRange + && unit.getDamageFromTerrain(it.key) <= 0 // Don't set up on a mountain } .minByOrNull { it.value.totalDistance }?.key @@ -377,7 +389,7 @@ object UnitAutomation { // don't head straight to the city, try to head to landing grounds - // this is against tha AI's brilliant plan of having everyone embarked and attacking via sea when unnecessary. val tileToHeadTo = closestReachableEnemyCity.getTilesInDistanceRange(3..4) - .filter { it.isLand } + .filter { it.isLand && unit.getDamageFromTerrain(it) <= 0 } // Don't head for hurty terrain .sortedBy { it.aerialDistanceTo(unit.currentTile) } .firstOrNull { unit.movement.canReach(it) } @@ -500,7 +512,7 @@ object UnitAutomation { } /** Returns whether the civilian spends its turn hiding and not moving */ - fun tryRunAwayIfNeccessary(unit: MapUnit): Boolean { + fun tryRunAwayIfNeccessary(unit: MapUnit): Boolean { // This is a little 'Bugblatter Beast of Traal': Run if we can attack an enemy // Cheaper than determining which enemies could attack us next turn //todo - stay when we're stacked with a good military unit??? @@ -534,7 +546,7 @@ object UnitAutomation { return } val tileFurthestFromEnemy = reachableTiles.keys - .filter { unit.movement.canMoveTo(it) } + .filter { unit.movement.canMoveTo(it) && unit.getDamageFromTerrain(it) < unit.health } .maxByOrNull { countDistanceToClosestEnemy(unit, it) } ?: return // can't move anywhere! unit.movement.moveToTile(tileFurthestFromEnemy) diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 636e659bc7..e46babc1c0 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -1,6 +1,7 @@ package com.unciv.logic.civilization import com.badlogic.gdx.math.Vector2 +import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.UncivShowableException @@ -97,6 +98,12 @@ class CivilizationInfo { @Transient private var cachedMilitaryMight = -1 + @Transient + var passThroughImpassableUnlocked = false // Cached Boolean equal to passableImpassables.isNotEmpty() + + @Transient + var nonStandardTerrainDamage = false + var playerType = PlayerType.AI /** Used in online multiplayer for human players */ @@ -149,6 +156,8 @@ class CivilizationInfo { // default false once we no longer want legacy save-game compatibility var hasEverOwnedOriginalCapital: Boolean? = null + val passableImpassables = HashSet() // For Carthage-like uniques + // For Aggressor, Warmonger status private var numMinorCivsAttacked = 0 @@ -197,6 +206,7 @@ class CivilizationInfo { toReturn.boughtConstructionsWithGloballyIncreasingPrice.putAll(boughtConstructionsWithGloballyIncreasingPrice) // toReturn.hasEverOwnedOriginalCapital = hasEverOwnedOriginalCapital + toReturn.passableImpassables.addAll(passableImpassables) toReturn.numMinorCivsAttacked = numMinorCivsAttacked return toReturn } @@ -643,6 +653,11 @@ class CivilizationInfo { cityInfo.civInfo = this // must be before the city's setTransients because it depends on the tilemap, that comes from the currentPlayerCivInfo cityInfo.setTransients() } + + passThroughImpassableUnlocked = passableImpassables.isNotEmpty() + // Cache whether this civ gets nonstandard terrain damage for performance reasons. + nonStandardTerrainDamage = getMatchingUniques("Units ending their turn on [] tiles take [] damage") + .any { gameInfo.ruleSet.terrains[it.params[0]]!!.damagePerTurn != it.params[1].toInt() } } fun updateSightAndResources() { @@ -913,6 +928,13 @@ class CivilizationInfo { } placedUnit.setupAbilityUses() } + + for (unique in getMatchingUniques("Land units may cross [] tiles after the first [] is earned")) { + if (unit.matchesFilter(unique.params[1])) { + passThroughImpassableUnlocked = true // Update the cached Boolean + passableImpassables.add(unique.params[0]) // Add to list of passable impassables + } + } return placedUnit } diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index 0df7532e70..bcc3161956 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -672,8 +672,8 @@ class MapUnit { } } - getCitadelDamage() - getTerrainDamage() + doCitadelDamage() + doTerrainDamage() } fun startTurn() { @@ -882,30 +882,38 @@ class MapUnit { return damageFactor } - private fun getTerrainDamage() { - // hard coded mountain damage for now - if (getTile().baseTerrain == Constants.mountain) { - val tileDamage = 50 - health -= tileDamage + private fun doTerrainDamage() { + val tileDamage = getDamageFromTerrain() + health -= tileDamage - if (health <= 0) { - civInfo.addNotification( - "Our [$name] took [$tileDamage] tile damage and was destroyed", - currentTile.position, - name, - NotificationIcon.Death - ) - destroy() - } else civInfo.addNotification( - "Our [$name] took [$tileDamage] tile damage", + if (health <= 0) { + civInfo.addNotification( + "Our [$name] took [$tileDamage] tile damage and was destroyed", currentTile.position, - name + name, + NotificationIcon.Death ) - } - + destroy() + } else if (tileDamage > 0) civInfo.addNotification( + "Our [$name] took [$tileDamage] tile damage", + currentTile.position, + name + ) } - private fun getCitadelDamage() { + fun getDamageFromTerrain(tile: TileInfo = currentTile): Int { + if (civInfo.nonStandardTerrainDamage) { + for (unique in getMatchingUniques("Units ending their turn on [] tiles take [] damage")) { + if (unique.params[0] in tile.getAllTerrains().map { it.name }) { + return unique.params[1].toInt() // Use the damage from the unique + } + } + } + // Otherwise fall back to the defined standard damage + return tile.getAllTerrains().sumBy { it.damagePerTurn } + } + + private fun doCitadelDamage() { // Check for Citadel damage - note: 'Damage does not stack with other Citadels' val citadelTile = currentTile.neighbors .firstOrNull { diff --git a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt index a3fdfe4f3d..8a0160228a 100644 --- a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt +++ b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt @@ -167,7 +167,13 @@ class UnitMovementAlgorithms(val unit:MapUnit) { * Does not consider if the [destination] tile can actually be entered, use [canMoveTo] for that. * Returns an empty list if there's no way to get to the destination. */ - fun getShortestPath(destination: TileInfo): List { + fun getShortestPath(destination: TileInfo, avoidDamagingTerrain: Boolean = false): List { + // First try and find a path without damaging terrain + if (!avoidDamagingTerrain && unit.civInfo.passThroughImpassableUnlocked && unit.baseUnit.isLandUnit()) { + val damageFreePath = getShortestPath(destination, true) + if (damageFreePath.isNotEmpty()) return damageFreePath + } + val currentTile = unit.getTile() if (currentTile.position == destination) return listOf(currentTile) // edge case that's needed, so that workers will know that they can reach their own tile. *sigh* @@ -187,6 +193,9 @@ class UnitMovementAlgorithms(val unit:MapUnit) { for (tileToCheck in tilesToCheck) { val distanceToTilesThisTurn = getDistanceToTilesWithinTurn(tileToCheck.position, movementThisTurn) for (reachableTile in distanceToTilesThisTurn.keys) { + // Avoid damaging terrain on first pass + if (avoidDamagingTerrain && unit.getDamageFromTerrain(reachableTile) > 0) + continue if (reachableTile == destination) distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!!.totalDistance else { @@ -546,7 +555,9 @@ class UnitMovementAlgorithms(val unit:MapUnit) { if (tile.isImpassible()) { // special exception - ice tiles are technically impassible, but some units can move through them anyway // helicopters can pass through impassable tiles like mountains - if (!(tile.terrainFeatures.contains(Constants.ice) && unit.canEnterIceTiles) && !unit.canPassThroughImpassableTiles) + if (!(tile.terrainFeatures.contains(Constants.ice) && unit.canEnterIceTiles) && !unit.canPassThroughImpassableTiles + // carthage-like uniques sometimes allow passage through impassible tiles + && !(unit.civInfo.passThroughImpassableUnlocked && unit.civInfo.passableImpassables.contains(tile.getLastTerrain().name))) return false } if (tile.isLand diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index c4aad507a7..ee57e69285 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -170,7 +170,10 @@ class Ruleset { if (buildingsFile.exists()) buildings += createHashmap(jsonParser.getFromJson(Array::class.java, buildingsFile)) val terrainsFile = folderHandle.child("Terrains.json") - if (terrainsFile.exists()) terrains += createHashmap(jsonParser.getFromJson(Array::class.java, terrainsFile)) + if (terrainsFile.exists()) { + terrains += createHashmap(jsonParser.getFromJson(Array::class.java, terrainsFile)) + for (terrain in terrains.values) terrain.setTransients() + } val resourcesFile = folderHandle.child("TileResources.json") if (resourcesFile.exists()) tileResources += createHashmap(jsonParser.getFromJson(Array::class.java, resourcesFile)) diff --git a/core/src/com/unciv/models/ruleset/tile/Terrain.kt b/core/src/com/unciv/models/ruleset/tile/Terrain.kt index 8712e6aad8..76c7305c1e 100644 --- a/core/src/com/unciv/models/ruleset/tile/Terrain.kt +++ b/core/src/com/unciv/models/ruleset/tile/Terrain.kt @@ -40,6 +40,9 @@ class Terrain : NamedStats(), ICivilopediaText, IHasUniques { var defenceBonus:Float = 0f var impassable = false + @Transient + var damagePerTurn = 0 + override var civilopediaText = listOf() fun isRough(): Boolean = uniques.contains("Rough terrain") @@ -141,4 +144,10 @@ class Terrain : NamedStats(), ICivilopediaText, IHasUniques { return textList } + + fun setTransients() { + damagePerTurn = uniqueObjects.sumBy { + if (it.placeholderText == "Units ending their turn on this terrain take [] damage") it.params[0].toInt() else 0 + } + } } diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index a763ca4900..a15361bff2 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -161,7 +161,7 @@ object UnitActions { * (no movement left, too close to another city). */ fun getFoundCityAction(unit: MapUnit, tile: TileInfo): UnitAction? { - if (!unit.hasUnique("Founds a new city") || tile.isWater) return null + if (!unit.hasUnique("Founds a new city") || tile.isWater || tile.isImpassible()) return null if (unit.currentMovement <= 0 || tile.getTilesInDistance(3).any { it.isCityCenter() }) return UnitAction(UnitActionType.FoundCity, action = null) diff --git a/docs/Credits.md b/docs/Credits.md index 8b99c8006c..40f273258a 100644 --- a/docs/Credits.md +++ b/docs/Credits.md @@ -42,6 +42,7 @@ Unless otherwise specified, all the following are from [the Noun Project](https: * [Bow](https://thenounproject.com/search/?q=bow&i=101736) By Arthur Shlain for Bowman * [Fishing Vessel](https://thenounproject.com/term/fishing-vessel/23815/) By Luis Prado for Work Boats * [Greek Trireme](https://thenounproject.com/search/?q=ancient%20boat&i=1626303) By Zachary McCune for Trireme +* [Greek Trireme](https://thenounproject.com/search/?q=ancient%20boat&i=1626303) By Zachary McCune for Quinquereme * [Chariot](https://thenounproject.com/search/?q=Chariot&i=1189930) By Andrew Doane for Chariot Archer * [Elephant](https://thenounproject.com/Luis/uploads/?i=14048) By Luis Prado for War Elephant * [Centaur](https://thenounproject.com/search/?q=horse+archer&i=1791296) by Michael Wohlwend for Horse Archer @@ -62,6 +63,7 @@ Unless otherwise specified, all the following are from [the Noun Project](https: * [Roman Helmet](https://thenounproject.com/search/?q=legion&i=440134) By parkjisun for Legion * [Horse](https://thenounproject.com/search/?q=Horse&i=1373793) By AFY Studio for Horseman * [Horse Head](https://thenounproject.com/search/?q=Cavalry&i=374037) By Juan Pablo Bravo for Companion Cavalry +* [Elephant](https://thenounproject.com/term/elephant/1302749) By Angriawan Ditya Zulkarnain for African Forest Elephant * [Judge](https://thenounproject.com/search/?q=judge&i=1076388) By Krisztián Mátyás for Courthouse * [Petra](https://thenounproject.com/search/?q=petra&i=2855893) By Ranah Pixel Studio for Petra @@ -553,6 +555,7 @@ Unless otherwise specified, all the following are from [the Noun Project](https: * [Lion](https://thenounproject.com/search/?q=lion&i=76154) by Nikki Rodriguez for The Netherlands * [Three Crowns](https://thenounproject.com/search/?q=three+crowns&i=1155972) by Daniel Falk for Sweden * [Flag of Austria](https://thenounproject.com/term/flag-of-austria/3292053/) by Olena Panasovska, UA for Austria +* [Elephant](https://thenounproject.com/term/elephant/564421/) by Hea Poh Lin for Carthage ## Promotions