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

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

View File

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

View File

@ -3,6 +3,7 @@ package com.unciv.logic.battle
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.models.UncivSound
import com.unciv.models.ruleset.unit.UnitType
class MapUnitCombatant(val unit: MapUnit) : ICombatant {
@ -15,6 +16,9 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant {
override fun isInvisible(): Boolean = unit.isInvisible()
override fun canAttack(): Boolean = unit.canAttack()
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) {
unit.health -= damage

View File

@ -1,6 +1,6 @@
package com.unciv.models
enum class UncivSound(val value: String) {
private enum class UncivSoundConstants (val value: String) {
Click("click"),
Fortify("fortify"),
Promote("promote"),
@ -12,5 +12,62 @@ enum class UncivSound(val value: String) {
Policy("policy"),
Paper("paper"),
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.audio.Sound
import com.badlogic.gdx.files.FileHandle
import com.unciv.UncivGame
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 {
private val soundMap = HashMap<UncivSound, Sound>()
fun get(sound: UncivSound): Sound {
if (!soundMap.containsKey(sound)) {
soundMap[sound] = Gdx.audio.newSound(Gdx.files.internal("sounds/${sound.value}.mp3"))
private val soundMap = HashMap<UncivSound, Sound?>()
private val separator = File.separator // just a shorthand for readability
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) {
val volume = UncivGame.Current.settings.soundEffectsVolume
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 {
attackButton.onClick {
attackButton.onClick(attacker.getAttackSound()) {
Battle.moveAndAttack(attacker, attackableTile)
worldScreen.mapHolder.removeUnitActionOverlay() // the overlay was one of attacking
worldScreen.shouldUpdate = true
@ -278,7 +278,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
attackButton.label.color = Color.GRAY
}
else {
attackButton.onClick {
attackButton.onClick(attacker.getAttackSound()) {
Battle.nuke(attacker, targetTile)
worldScreen.mapHolder.removeUnitActionOverlay() // the overlay was one of attacking
worldScreen.shouldUpdate = true

View File

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