Added Privateer unit; updated Coastal Raider promotion (#4301)

* Added privateer unit

* Privateers can now capture other naval units

* Updated Coastal Raider promotion to include the gold gained from damaging cities

* Added missing translatable notification

* Implemented requested changes

* Implemented requested changes _but better_

* Forgot to update a variable name
This commit is contained in:
Xander Lenstra 2021-07-02 09:38:45 +02:00 committed by GitHub
parent 4e36773cf3
commit 70882b4e8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 132 additions and 62 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -564,170 +564,177 @@ Pikeman
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Rifleman Privateer
rotate: false rotate: false
xy: 1300, 868 xy: 1300, 868
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Rocket Artillery Rifleman
rotate: false rotate: false
xy: 544, 4 xy: 544, 4
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Samurai Rocket Artillery
rotate: false rotate: false
xy: 652, 112 xy: 652, 112
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Scout Samurai
rotate: false rotate: false
xy: 760, 226 xy: 760, 226
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Settler Scout
rotate: false rotate: false
xy: 868, 328 xy: 868, 328
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Ship of the Line Settler
rotate: false rotate: false
xy: 976, 436 xy: 976, 436
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Sipahi Ship of the Line
rotate: false rotate: false
xy: 1084, 544 xy: 1084, 544
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Slinger Sipahi
rotate: false rotate: false
xy: 1192, 653 xy: 1192, 653
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Spearman Slinger
rotate: false rotate: false
xy: 1300, 760 xy: 1300, 760
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Stealth Bomber Spearman
rotate: false rotate: false
xy: 1408, 868 xy: 1408, 868
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Submarine Stealth Bomber
rotate: false rotate: false
xy: 652, 4 xy: 652, 4
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Swordsman Submarine
rotate: false rotate: false
xy: 760, 118 xy: 760, 118
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Tank Swordsman
rotate: false rotate: false
xy: 868, 220 xy: 868, 220
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Tercio Tank
rotate: false rotate: false
xy: 976, 328 xy: 976, 328
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Trebuchet Tercio
rotate: false rotate: false
xy: 1084, 436 xy: 1084, 436
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Triplane Trebuchet
rotate: false rotate: false
xy: 1192, 545 xy: 1192, 545
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Triplane
rotate: false
xy: 1300, 652
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Trireme Trireme
rotate: false rotate: false
xy: 1300, 651 xy: 1408, 759
size: 100, 101 size: 100, 101
orig: 100, 101 orig: 100, 101
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Turtle Ship Turtle Ship
rotate: false rotate: false
xy: 1408, 760 xy: 1516, 868
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
War Chariot War Chariot
rotate: false rotate: false
xy: 1516, 868 xy: 760, 10
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
War Elephant War Elephant
rotate: false rotate: false
xy: 760, 10 xy: 868, 112
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Warrior Warrior
rotate: false rotate: false
xy: 868, 112 xy: 976, 220
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Work Boats Work Boats
rotate: false rotate: false
xy: 976, 220 xy: 1084, 328
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Worker Worker
rotate: false rotate: false
xy: 1084, 328 xy: 1192, 437
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0
index: -1 index: -1
Zero Zero
rotate: false rotate: false
xy: 1192, 437 xy: 1300, 544
size: 100, 100 size: 100, 100
orig: 100, 100 orig: 100, 100
offset: 0, 0 offset: 0, 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 KiB

After

Width:  |  Height:  |  Size: 330 KiB

View File

@ -213,19 +213,19 @@
{ {
"name": "Coastal Raider I", "name": "Coastal Raider I",
"effect": "+[15]% Strength vs [City]", "uniques": ["+[20]% Strength vs [City]", "Earn [33]% of the damage done to [City] units as [Gold]"],
"unitTypes": ["WaterMelee"] "unitTypes": ["WaterMelee"]
}, },
{ {
"name": "Coastal Raider II", "name": "Coastal Raider II",
"prerequisites": ["Coastal Raider I"], "prerequisites": ["Coastal Raider I"],
"effect": "+[15]% Strength vs [City]", "uniques": ["+[20]% Strength vs [City]", "Earn [33]% of the damage done to [City] units as [Gold]"],
"unitTypes": ["WaterMelee"] "unitTypes": ["WaterMelee"]
}, },
{ {
"name": "Coastal Raider III", "name": "Coastal Raider III",
"prerequisites": ["Coastal Raider II"], "prerequisites": ["Coastal Raider II"],
"effect": "+[15]% Strength vs [City]", "uniques": ["+[20]% Strength vs [City]", "Earn [33]% of the damage done to [City] units as [Gold]"],
"unitTypes": ["WaterMelee"] "unitTypes": ["WaterMelee"]
}, },

View File

@ -151,7 +151,7 @@
"strength": 10, "strength": 10,
"cost": 45, "cost": 45,
"requiredTech": "Sailing", "requiredTech": "Sailing",
"uniques": ["Cannot enter ocean tiles",], "uniques": ["Cannot enter ocean tiles"],
"upgradesTo": "Caravel", "upgradesTo": "Caravel",
"obsoleteTech": "Astronomy", "obsoleteTech": "Astronomy",
"attackSound": "nonmetalhit" "attackSound": "nonmetalhit"
@ -790,6 +790,17 @@
"uniques": ["+[50]% Strength vs [Mounted]"], "uniques": ["+[50]% Strength vs [Mounted]"],
"attackSound": "shot" "attackSound": "shot"
}, },
{
"name": "Privateer",
"unitType": "WaterMelee",
"movement": 5,
"strength": 25,
"cost": 150,
"requiredTech": "Navigation",
"upgradesTo": "Destroyer",
"promotions": ["Coastal Raider I"],
"uniques": ["May capture killed [Water] units"]
},
{ {
"name": "Frigate", "name": "Frigate",
"unitType": "WaterRanged", "unitType": "WaterRanged",
@ -1057,7 +1068,7 @@
"cost": 375, "cost": 375,
"requiredResource": "Oil", "requiredResource": "Oil",
"requiredTech": "Electronics", "requiredTech": "Electronics",
"uniques": ["Ranged attacks may be performed over obstacles"], "uniques": ["Ranged attacks may be performed over obstacles"]
// Does not actually upgrade to Missile Cruisers // Does not actually upgrade to Missile Cruisers
}, },
{ {

View File

@ -446,6 +446,8 @@ A(n) [nukeType] exploded in our territory! =
After being hit by our [nukeType], [civName] has declared war on us! After being hit by our [nukeType], [civName] has declared war on us!
The civilization of [civName] has been destroyed! = The civilization of [civName] has been destroyed! =
The City-State of [name] has been destroyed! = The City-State of [name] has been destroyed! =
Your [ourUnit] captured an enemy [theirUnit]! =
Your [ourUnit] plundered [amount] [Stat] from [theirUnit] =
We have captured a barbarian encampment and recovered [goldAmount] gold! = We have captured a barbarian encampment and recovered [goldAmount] gold! =
A barbarian [unitType] has joined us! = A barbarian [unitType] has joined us! =
We have found survivors in the ruins - population added to [cityName] = We have found survivors in the ruins - population added to [cityName] =

View File

@ -12,7 +12,6 @@ import com.unciv.models.AttackableTile
import com.unciv.models.ruleset.Unique import com.unciv.models.ruleset.Unique
import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.ruleset.unit.UnitType
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
import java.util.* import java.util.*
import kotlin.math.max import kotlin.math.max
@ -72,20 +71,16 @@ object Battle {
if (!defender.isDefeated() && defender is MapUnitCombatant && defender.unit.action == Constants.unitActionExplore) if (!defender.isDefeated() && defender is MapUnitCombatant && defender.unit.action == Constants.unitActionExplore)
defender.unit.action = null defender.unit.action = null
// we're a melee unit and we destroyed\captured an enemy unit
postBattleMoveToAttackedTile(attacker, defender, attackedTile)
reduceAttackerMovementPointsAndAttacks(attacker, defender)
if (!isAlreadyDefeatedCity) postBattleAddXp(attacker, defender)
// Add culture when defeating a barbarian when Honor policy is adopted, gold from enemy killed when honor is complete // Add culture when defeating a barbarian when Honor policy is adopted, gold from enemy killed when honor is complete
// or any enemy military unit with Sacrificial captives unique (can be either attacker or defender!) // or any enemy military unit with Sacrificial captives unique (can be either attacker or defender!)
// or check if unit is captured by the attacker (prize ships unique)
if (defender.isDefeated() && defender is MapUnitCombatant && !defender.getUnitType().isCivilian()) { if (defender.isDefeated() && defender is MapUnitCombatant && !defender.getUnitType().isCivilian()) {
tryEarnFromKilling(attacker, defender) tryEarnFromKilling(attacker, defender)
tryCaptureUnit(attacker, defender)
tryHealAfterKilling(attacker) tryHealAfterKilling(attacker)
} else if (attacker.isDefeated() && attacker is MapUnitCombatant && !attacker.getUnitType().isCivilian()) { } else if (attacker.isDefeated() && attacker is MapUnitCombatant && !attacker.getUnitType().isCivilian()) {
tryEarnFromKilling(defender, attacker) tryEarnFromKilling(defender, attacker)
tryCaptureUnit(defender, attacker)
tryHealAfterKilling(defender) tryHealAfterKilling(defender)
} }
@ -95,6 +90,14 @@ object Battle {
else if (attacker.unit.isMoving()) else if (attacker.unit.isMoving())
attacker.unit.action = null attacker.unit.action = null
} }
// we're a melee unit and we destroyed\captured an enemy unit
// Should be called after tryCaptureUnit(), as that might spawn a unit on the tile we go to
postBattleMoveToAttackedTile(attacker, defender, attackedTile)
reduceAttackerMovementPointsAndAttacks(attacker, defender)
if (!isAlreadyDefeatedCity) postBattleAddXp(attacker, defender)
} }
private fun tryEarnFromKilling(civUnit: ICombatant, defeatedUnit: MapUnitCombatant) { private fun tryEarnFromKilling(civUnit: ICombatant, defeatedUnit: MapUnitCombatant) {
@ -125,33 +128,79 @@ object Battle {
} catch (ex: Exception) { } catch (ex: Exception) {
} // parameter is not a stat } // parameter is not a stat
} }
}
private fun tryCaptureUnit(attacker: ICombatant, defender: ICombatant) {
// https://forums.civfanatics.com/threads/prize-ships-for-land-units.650196/
// https://civilization.fandom.com/wiki/Module:Data/Civ5/GK/Defines
if (!defender.isDefeated()) return
if (attacker !is MapUnitCombatant) return
if (defender is MapUnitCombatant && !defender.getUnitType().isMilitary()) return
if (attacker.unit.getMatchingUniques("May capture killed [] units").none { defender.matchesCategory(it.params[0]) }) return
var captureChance = 10 + attacker.getAttackingStrength().toFloat() / defender.getDefendingStrength().toFloat() * 40
if (captureChance > 80) captureChance = 80f
if (100 * Random().nextFloat() > captureChance) return
val newUnit = attacker.getCivInfo().placeUnitNearTile(defender.getTile().position, defender.getName())
if (newUnit == null) return // silently fail
attacker.getCivInfo().addNotification("Your [${attacker.getName()}] captured an enemy [${defender.getName()}]", newUnit.getTile().position, NotificationIcon.War)
newUnit.currentMovement = 0f
newUnit.health = 50
} }
private fun takeDamage(attacker: ICombatant, defender: ICombatant) { private fun takeDamage(attacker: ICombatant, defender: ICombatant) {
var damageToDefender = BattleDamage.calculateDamageToDefender(attacker, attacker.getTile(), defender) var potentialDamageToDefender = BattleDamage.calculateDamageToDefender(attacker, attacker.getTile(), defender)
var damageToAttacker = BattleDamage.calculateDamageToAttacker(attacker, attacker.getTile(), defender) var potentialDamageToAttacker = BattleDamage.calculateDamageToAttacker(attacker, attacker.getTile(), defender)
var damageToAttacker = attacker.getHealth() // These variables names don't make any sense as of yet ...
var damageToDefender = defender.getHealth()
if (defender.getUnitType().isCivilian() && attacker.isMelee()) { if (defender.getUnitType().isCivilian() && attacker.isMelee()) {
captureCivilianUnit(attacker, defender as MapUnitCombatant) captureCivilianUnit(attacker, defender as MapUnitCombatant)
} else if (attacker.isRanged()) { } else if (attacker.isRanged()) {
defender.takeDamage(damageToDefender) // straight up defender.takeDamage(potentialDamageToDefender) // straight up
} else { } else {
//melee attack is complicated, because either side may defeat the other midway //melee attack is complicated, because either side may defeat the other midway
//so...for each round, we randomize who gets the attack in. Seems to be a good way to work for now. //so...for each round, we randomize who gets the attack in. Seems to be a good way to work for now.
while (damageToDefender + damageToAttacker > 0) { while (potentialDamageToDefender + potentialDamageToAttacker > 0) {
if (Random().nextInt(damageToDefender + damageToAttacker) < damageToDefender) { if (Random().nextInt(potentialDamageToDefender + potentialDamageToAttacker) < potentialDamageToDefender) {
damageToDefender-- potentialDamageToDefender--
defender.takeDamage(1) defender.takeDamage(1)
if (defender.isDefeated()) break if (defender.isDefeated()) break
} else { } else {
damageToAttacker-- potentialDamageToAttacker--
attacker.takeDamage(1) attacker.takeDamage(1)
if (attacker.isDefeated()) break if (attacker.isDefeated()) break
} }
} }
} }
damageToAttacker -= attacker.getHealth() // ... but from here on they are accurate
damageToDefender -= defender.getHealth()
if (attacker is MapUnitCombatant) {
for (unique in attacker.unit.getMatchingUniques("Earn []% of the damage done to [] units as []"))
if (defender.matchesCategory(unique.params[1])) {
val resourcesPlundered =
(unique.params[0].toFloat() / 100f * damageToDefender).toInt()
attacker.getCivInfo().addStat(Stat.valueOf(unique.params[2]), resourcesPlundered)
attacker.getCivInfo()
.addNotification(
"Your [${attacker.getName()}] plundered [${resourcesPlundered}] [${unique.params[2]}] from [${defender.getName()}]",
defender.getTile().position,
NotificationIcon.War
)
}
}
if (defender is MapUnitCombatant) {
for (unique in defender.unit.getMatchingUniques("Earn []% of the damage done to [] units as []"))
if (attacker.matchesCategory(unique.params[1]))
defender.getCivInfo().addStat(Stat.valueOf(unique.params[2]), (unique.params[0].toFloat() / 100f * damageToAttacker).toInt())
}
} }
@ -411,7 +460,7 @@ object Battle {
// Declare war on the owners of all hit tiles // Declare war on the owners of all hit tiles
for (hitCiv in hitTiles.mapNotNull { it.getOwner() }.distinct()) { for (hitCiv in hitTiles.mapNotNull { it.getOwner() }.distinct()) {
hitCiv.addNotification("A(n) [${attacker.getName()}] exploded in our territory!".tr(), targetTile.position, NotificationIcon.War) hitCiv.addNotification("A(n) [${attacker.getName()}] exploded in our territory!", targetTile.position, NotificationIcon.War)
tryDeclareWar(hitCiv) tryDeclareWar(hitCiv)
} }

View File

@ -74,6 +74,7 @@ Unless otherwise specified, all the following are from [the Noun Project](https:
* [Black powder musket](https://thenounproject.com/term/black-powder-musket/1202078/) By Jarem Fyre for Minuteman * [Black powder musket](https://thenounproject.com/term/black-powder-musket/1202078/) By Jarem Fyre for Minuteman
* [Rapier](https://thenounproject.com/search/?q=musketeer&i=819822) By Hamish for Musketeer * [Rapier](https://thenounproject.com/search/?q=musketeer&i=819822) By Hamish for Musketeer
* [Ship](https://thenounproject.com/term/ship/1293899/) By Orin Zuu for Frigate * [Ship](https://thenounproject.com/term/ship/1293899/) By Orin Zuu for Frigate
* [Pirate](https://thenounproject.com/search/?q=pirate&i=2349496) by Berkah Icon for Privateer
* [Ship](https://thenounproject.com/search/?q=ship&i=800131) By Mungang Kim for Ship of the Line * [Ship](https://thenounproject.com/search/?q=ship&i=800131) By Mungang Kim for Ship of the Line
* [Lance](https://thenounproject.com/search/?q=Lance&i=440122) By parkjisun for Lancer * [Lance](https://thenounproject.com/search/?q=Lance&i=440122) By parkjisun for Lancer
* [Sword](https://thenounproject.com/search/?q=saber&i=1174742) By Daniela Baptista for Sipahi * [Sword](https://thenounproject.com/search/?q=saber&i=1174742) By Daniela Baptista for Sipahi