mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-11 00:08:58 +07:00
Sound upgrade - enable BaseUnit.attackSound, a few extras (#4013)
This commit is contained in:
@ -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",
|
||||||
|
BIN
android/assets/sounds/bombard.mp3
Normal file
BIN
android/assets/sounds/bombard.mp3
Normal file
Binary file not shown.
BIN
android/assets/sounds/nuke.mp3
Normal file
BIN
android/assets/sounds/nuke.mp3
Normal file
Binary file not shown.
BIN
android/assets/sounds/slider.mp3
Normal file
BIN
android/assets/sounds/slider.mp3
Normal file
Binary file not shown.
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user