mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-25 07:09:16 +07:00
Sound upgrade - enable BaseUnit.attackSound, a few extras (#4013)
This commit is contained in:
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
Reference in New Issue
Block a user