Sound upgrade - enable BaseUnit.attackSound, a few extras (#4013)

This commit is contained in:
SomeTroglodyte
2021-05-30 09:18:42 +02:00
committed by GitHub
parent 432e937474
commit 589ed4e423
12 changed files with 126 additions and 15 deletions

View File

@ -1076,7 +1076,8 @@
"cost": 1000, "cost": 1000,
"requiredTech": "Rocketry", "requiredTech": "Rocketry",
"requiredResource": "Uranium", "requiredResource": "Uranium",
"uniques": ["Self-destructs when attacking", "Nuclear weapon", "Requires [Manhattan Project]"] "uniques": ["Self-destructs when attacking", "Nuclear weapon", "Requires [Manhattan Project]"],
"attackSound": "nuke"
}, },
{ {
"name": "Landship", "name": "Landship",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3,6 +3,7 @@ package com.unciv.logic.battle
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.UncivSound
import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.ruleset.unit.UnitType
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -20,6 +21,7 @@ class CityCombatant(val city: CityInfo) : ICombatant {
override fun isInvisible(): Boolean = false override fun isInvisible(): Boolean = false
override fun canAttack(): Boolean = (!city.attackedThisTurn) override fun canAttack(): Boolean = (!city.attackedThisTurn)
override fun matchesCategory(category: String) = category == "City" override fun matchesCategory(category: String) = category == "City"
override fun getAttackSound() = UncivSound.Bombard
override fun takeDamage(damage: Int) { override fun takeDamage(damage: Int) {
city.health -= damage city.health -= damage

View File

@ -2,6 +2,7 @@ package com.unciv.logic.battle
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.UncivSound
import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.ruleset.unit.UnitType
interface ICombatant{ interface ICombatant{
@ -18,6 +19,7 @@ interface ICombatant{
fun isInvisible(): Boolean fun isInvisible(): Boolean
fun canAttack(): Boolean fun canAttack(): Boolean
fun matchesCategory(category:String): Boolean fun matchesCategory(category:String): Boolean
fun getAttackSound(): UncivSound
fun isMelee(): Boolean { fun isMelee(): Boolean {
return getUnitType().isMelee() return getUnitType().isMelee()

View File

@ -3,6 +3,7 @@ package com.unciv.logic.battle
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.models.UncivSound
import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.ruleset.unit.UnitType
class MapUnitCombatant(val unit: MapUnit) : ICombatant { class MapUnitCombatant(val unit: MapUnit) : ICombatant {
@ -15,6 +16,9 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant {
override fun isInvisible(): Boolean = unit.isInvisible() override fun isInvisible(): Boolean = unit.isInvisible()
override fun canAttack(): Boolean = unit.canAttack() override fun canAttack(): Boolean = unit.canAttack()
override fun matchesCategory(category:String) = unit.matchesFilter(category) override fun matchesCategory(category:String) = unit.matchesFilter(category)
override fun getAttackSound() = unit.baseUnit.attackSound.let {
if (it==null) UncivSound.Click else UncivSound.custom(it)
}
override fun takeDamage(damage: Int) { override fun takeDamage(damage: Int) {
unit.health -= damage unit.health -= damage

View File

@ -1,6 +1,6 @@
package com.unciv.models package com.unciv.models
enum class UncivSound(val value: String) { private enum class UncivSoundConstants (val value: String) {
Click("click"), Click("click"),
Fortify("fortify"), Fortify("fortify"),
Promote("promote"), Promote("promote"),
@ -12,5 +12,62 @@ enum class UncivSound(val value: String) {
Policy("policy"), Policy("policy"),
Paper("paper"), Paper("paper"),
Whoosh("whoosh"), Whoosh("whoosh"),
Silent("") Bombard("bombard"),
Slider("slider"),
Silent(""),
Custom("")
}
/**
* Represents an Unciv Sound, either from a predefined set or custom with a specified filename.
*/
class UncivSound private constructor (
private val type: UncivSoundConstants,
filename: String? = null
) {
/** The base filename without extension. */
val value: String = filename ?: type.value
/*
init {
// Checking contract "use non-custom *w/o* filename OR custom *with* one
// Removed due to private constructor
if ((type == UncivSoundConstants.Custom) == filename.isNullOrEmpty()) {
throw IllegalArgumentException("Invalid UncivSound constructor arguments")
}
}
*/
companion object {
val Click = UncivSound(UncivSoundConstants.Click)
val Fortify = UncivSound(UncivSoundConstants.Fortify)
val Promote = UncivSound(UncivSoundConstants.Promote)
val Upgrade = UncivSound(UncivSoundConstants.Upgrade)
val Setup = UncivSound(UncivSoundConstants.Setup)
val Chimes = UncivSound(UncivSoundConstants.Chimes)
val Coin = UncivSound(UncivSoundConstants.Coin)
val Choir = UncivSound(UncivSoundConstants.Choir)
val Policy = UncivSound(UncivSoundConstants.Policy)
val Paper = UncivSound(UncivSoundConstants.Paper)
val Whoosh = UncivSound(UncivSoundConstants.Whoosh)
val Bombard = UncivSound(UncivSoundConstants.Bombard)
val Slider = UncivSound(UncivSoundConstants.Slider)
val Silent = UncivSound(UncivSoundConstants.Silent)
/** Creates an UncivSound instance for a custom sound.
* @param filename The base filename without extension.
*/
fun custom(filename: String) = UncivSound(UncivSoundConstants.Custom, filename)
}
// overrides ensure usability as hash key
override fun hashCode(): Int {
return type.hashCode() xor value.hashCode()
}
override fun equals(other: Any?): Boolean {
if (other == null || other !is UncivSound) return false
if (type != other.type) return false
return type != UncivSoundConstants.Custom || value == other.value
}
override fun toString(): String = value
} }

View File

@ -2,22 +2,64 @@ package com.unciv.ui.utils
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.audio.Sound import com.badlogic.gdx.audio.Sound
import com.badlogic.gdx.files.FileHandle
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.models.UncivSound import com.unciv.models.UncivSound
import java.io.File
/**
* Generates Gdx [Sound] objects from [UncivSound] ones on demand, only once per key
* (two UncivSound custom instances with the same filename are considered equal).
*
* Gdx asks Sound usage to respect the Disposable contract, but since we're only caching
* a handful of them in memory we should be able to get away with keeping them alive for the
* app lifetime.
*/
object Sounds { object Sounds {
private val soundMap = HashMap<UncivSound, Sound>() private val soundMap = HashMap<UncivSound, Sound?>()
fun get(sound: UncivSound): Sound { private val separator = File.separator // just a shorthand for readability
if (!soundMap.containsKey(sound)) {
soundMap[sound] = Gdx.audio.newSound(Gdx.files.internal("sounds/${sound.value}.mp3")) private var modListHash = Int.MIN_VALUE
/** Ensure cache is not outdated _and_ build list of folders to look for sounds */
private fun getFolders(): Sequence<String> {
if (!UncivGame.isCurrentInitialized() || !UncivGame.Current.isGameInfoInitialized()) // Allow sounds from main menu
return sequenceOf("")
// Allow mod sounds - preferentially so they can override built-in sounds
val modList = UncivGame.Current.gameInfo.ruleSet.mods
val newHash = modList.hashCode()
if (modListHash == Int.MIN_VALUE || modListHash != newHash) {
// Seems the mod list has changed - start over
for (sound in soundMap.values) sound?.dispose()
soundMap.clear()
modListHash = newHash
} }
return soundMap[sound]!! // Should we also look in UncivGame.Current.settings.visualMods?
return modList.asSequence()
.map { "mods$separator$it$separator" } +
sequenceOf("")
}
fun get(sound: UncivSound): Sound? {
if (sound in soundMap) return soundMap[sound]
val fileName = sound.value
var file: FileHandle? = null
for (modFolder in getFolders()) {
val path = "${modFolder}sounds$separator$fileName.mp3"
file = Gdx.files.internal(path)
if (file.exists()) break
}
val newSound =
if (file == null || !file.exists()) null
else Gdx.audio.newSound(file)
// Store Sound for reuse or remember that the actual file is missing
soundMap[sound] = newSound
return newSound
} }
fun play(sound: UncivSound) { fun play(sound: UncivSound) {
val volume = UncivGame.Current.settings.soundEffectsVolume val volume = UncivGame.Current.settings.soundEffectsVolume
if (sound == UncivSound.Silent || volume < 0.01) return if (sound == UncivSound.Silent || volume < 0.01) return
get(sound).play(volume) get(sound)?.play(volume)
} }
} }

View File

@ -207,7 +207,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
} }
else { else {
attackButton.onClick { attackButton.onClick(attacker.getAttackSound()) {
Battle.moveAndAttack(attacker, attackableTile) Battle.moveAndAttack(attacker, attackableTile)
worldScreen.mapHolder.removeUnitActionOverlay() // the overlay was one of attacking worldScreen.mapHolder.removeUnitActionOverlay() // the overlay was one of attacking
worldScreen.shouldUpdate = true worldScreen.shouldUpdate = true
@ -278,7 +278,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
attackButton.label.color = Color.GRAY attackButton.label.color = Color.GRAY
} }
else { else {
attackButton.onClick { attackButton.onClick(attacker.getAttackSound()) {
Battle.nuke(attacker, targetTile) Battle.nuke(attacker, targetTile)
worldScreen.mapHolder.removeUnitActionOverlay() // the overlay was one of attacking worldScreen.mapHolder.removeUnitActionOverlay() // the overlay was one of attacking
worldScreen.shouldUpdate = true worldScreen.shouldUpdate = true

View File

@ -181,7 +181,7 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
settings.minimapSize = size settings.minimapSize = size
} }
settings.save() settings.save()
Sounds.play(UncivSound.Click) Sounds.play(UncivSound.Slider)
if (previousScreen is WorldScreen) if (previousScreen is WorldScreen)
previousScreen.shouldUpdate = true previousScreen.shouldUpdate = true
} }
@ -276,7 +276,7 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
soundEffectsVolumeSlider.onChange { soundEffectsVolumeSlider.onChange {
settings.soundEffectsVolume = soundEffectsVolumeSlider.value settings.soundEffectsVolume = soundEffectsVolumeSlider.value
settings.save() settings.save()
Sounds.play(UncivSound.Click) Sounds.play(UncivSound.Slider)
} }
optionsTable.add(soundEffectsVolumeSlider).pad(5f).row() optionsTable.add(soundEffectsVolumeSlider).pad(5f).row()
} }

View File

@ -562,7 +562,10 @@ Sounds are from FreeSound.org and are either Creative Commons or Public Domain
* [Horse Neigh 2](https://freesound.org/people/GoodListener/sounds/322450/) By GoodListener as 'horse' for cavalry attack sounds * [Horse Neigh 2](https://freesound.org/people/GoodListener/sounds/322450/) By GoodListener as 'horse' for cavalry attack sounds
* [machine gun 001 - loop](https://freesound.org/people/pgi/sounds/212602/) By pgi as 'machinegun' for machine gun attack sound * [machine gun 001 - loop](https://freesound.org/people/pgi/sounds/212602/) By pgi as 'machinegun' for machine gun attack sound
* [uzzi_full_single](https://freesound.org/people/Deganoth/sounds/348685/) By Deganoth as 'shot' for bullet attacks * [uzzi_full_single](https://freesound.org/people/Deganoth/sounds/348685/) By Deganoth as 'shot' for bullet attacks
* [Grenade Launcher 2](https://soundbible.com/2140-Grenade-Launcher-2.html) By Daniel Simon as city bombard sound (CC Attribution 3.0 license)
* [Woosh](https://soundbible.com/2068-Woosh.html) by Mark DiAngelo as 'slider' sound (CC Attribution 3.0 license)
* [Tornado-Siren-II](https://soundbible.com/1937-Tornado-Siren-II.html) by Delilah as part of 'nuke' sound (CC Attribution 3.0 license)
* [Explosion-Ultra-Bass](https://soundbible.com/1807-Explosion-Ultra-Bass.html) by Mark DiAngelo as part of 'nuke' sound (CC Attribution 3.0 license)
# Music # Music
The following music is from https://filmmusic.io The following music is from https://filmmusic.io