Skip to content

Commit

Permalink
Implement 3 new sound triggers
Browse files Browse the repository at this point in the history
Wisdom runes spawning.
Game is paused.
Roshan's minimum respawn time is reached.
  • Loading branch information
MrBean355 committed Apr 29, 2023
1 parent 013177b commit d53d31d
Show file tree
Hide file tree
Showing 20 changed files with 560 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ const val DEFAULT_MAX_PERIOD = 15
const val MIN_BOUNTY_RUNE_TIMER = 0
const val MAX_BOUNTY_RUNE_TIMER = 60
const val BOUNTY_RUNE_TIMER_STEP = 5
const val DEFAULT_BOUNTY_RUNE_TIMER = 15
const val DEFAULT_BOUNTY_RUNE_TIMER = 15
const val DEFAULT_WISDOM_RUNE_TIMER = 45
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.github.mrbean355.admiralbulldog.game

import com.github.mrbean355.dota2.event.DotaEvent
import com.github.mrbean355.dota2.hero.Hero
import com.github.mrbean355.dota2.item.Items
import com.github.mrbean355.dota2.map.DotaMap
Expand All @@ -26,4 +27,5 @@ class GameState(
val player: Player?,
val hero: Hero?,
val items: Items?,
val events: List<DotaEvent>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,16 @@ fun monitorGameStateUpdates(onNewGameState: () -> Unit) {
}
.setPlayingListener { state ->
val map = state.map
if (map != null) {
processGameState(GameState(map, state.player, state.hero, state.items))
val events = state.events
if (map != null && events != null) {
processGameState(GameState(map, state.player, state.hero, state.items, events))
}
}
.setSpectatingListener { state ->
val map = state.map
if (map != null) {
processGameState(GameState(map, null, null, null))
val events = state.events
if (map != null && events != null) {
processGameState(GameState(map, null, null, null, events))
}
}
.setErrorHandler { t, _ ->
Expand All @@ -67,11 +69,12 @@ private fun processGameState(currentState: GameState) = synchronized(soundTrigge
previousState = null
soundTriggers.clear()
soundTriggers.addAll(SOUND_TRIGGER_TYPES.map { it.createInstance() })
Roshan.reset()
}

// Play sound bites that want to be played:
val localPreviousState = previousState
if (localPreviousState != null && !currentState.map.isPaused) {
if (localPreviousState != null && (!localPreviousState.map.isPaused || !currentState.map.isPaused)) {
soundTriggers
.filter { ConfigPersistence.isSoundTriggerEnabled(it::class) }
.filter { it.shouldPlay(localPreviousState, currentState) }
Expand Down
43 changes: 43 additions & 0 deletions src/main/kotlin/com/github/mrbean355/admiralbulldog/game/Roshan.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2023 Michael Johnston
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.mrbean355.admiralbulldog.game

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

object Roshan {
const val AEGIS_DURATION: Int = 5 * 60
const val MIN_RESPAWN_TIME: Int = 8 * 60
const val MAX_RESPAWN_TIME: Int = 11 * 60

const val AEGIS_DURATION_TURBO: Int = 4 * 60
const val MIN_RESPAWN_TIME_TURBO: Int = MIN_RESPAWN_TIME / 2
const val MAX_RESPAWN_TIME_TURBO: Int = MAX_RESPAWN_TIME / 2

private val _deathTime: MutableStateFlow<Int?> = MutableStateFlow(null)

val deathTime: StateFlow<Int?> = _deathTime.asStateFlow()

fun setDeathTime(deathTime: Int) {
_deathTime.value = deathTime
}

fun reset() {
_deathTime.value = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.github.mrbean355.admiralbulldog.common.DEFAULT_MAX_PERIOD
import com.github.mrbean355.admiralbulldog.common.DEFAULT_MIN_PERIOD
import com.github.mrbean355.admiralbulldog.common.DEFAULT_RATE
import com.github.mrbean355.admiralbulldog.common.DEFAULT_VOLUME
import com.github.mrbean355.admiralbulldog.common.DEFAULT_WISDOM_RUNE_TIMER
import com.github.mrbean355.admiralbulldog.common.MAX_VOLUME
import com.github.mrbean355.admiralbulldog.common.MIN_VOLUME
import com.github.mrbean355.admiralbulldog.persistence.migration.ConfigMigration
Expand All @@ -38,7 +39,7 @@ import java.io.File
import java.util.concurrent.TimeUnit

/** Version of the config file that this app supports. */
const val CONFIG_VERSION = 3
const val CONFIG_VERSION = 4

private const val FILE_NAME = "config.json"
private const val DEFAULT_PORT = 12345
Expand Down Expand Up @@ -194,6 +195,15 @@ object ConfigPersistence {
save()
}

fun getWisdomRuneTimer(): Int {
return loadedConfig.special.wisdomRuneTimer
}

fun setWisdomRuneTimer(timer: Int) {
loadedConfig.special.wisdomRuneTimer = timer
save()
}

/** @return the current volume, in the range `[0.0, 100.0]`. */
fun getVolume(): Int = loadedConfig.volume

Expand Down Expand Up @@ -517,7 +527,8 @@ object ConfigPersistence {
var useHealSmartChance: Boolean = true,
var minPeriod: Int = DEFAULT_MIN_PERIOD,
var maxPeriod: Int = DEFAULT_MAX_PERIOD,
var bountyRuneTimer: Int = DEFAULT_BOUNTY_RUNE_TIMER
var bountyRuneTimer: Int = DEFAULT_BOUNTY_RUNE_TIMER,
var wisdomRuneTimer: Int = DEFAULT_WISDOM_RUNE_TIMER,
)

private data class Toggle(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ object GameStateIntegration {
"buildings" "0"
"draft" "0"
"wearables" "0"
"events" "1"
}
}
""".trimIndent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class ConfigMigration {
From0To1Migration(),
From1To2Migration(),
From2To3Migration(),
From3To4Migration(),
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2023 Michael Johnston
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.mrbean355.admiralbulldog.persistence.migration

import com.google.gson.JsonArray
import com.google.gson.JsonObject

class From3To4Migration : Migration(from = 3, to = 4) {

override fun migrate(config: JsonObject) {
// New property: wisdom rune timer.
config.getAsJsonObject("special")["wisdomRuneTimer"] = 45

// New triggers: OnWisdomRunesSpawn, OnPause, RoshanTimer.
config.getAsJsonObject("sounds").apply {
add("OnWisdomRunesSpawn", triggerConfig())
add("OnPause", triggerConfig())
add("RoshanTimer", triggerConfig())
}
}

private fun triggerConfig() = JsonObject().apply {
addProperty("enabled", false)
addProperty("chance", 100)
addProperty("minRate", 100)
addProperty("maxRate", 100)
addProperty("playThroughDiscord", false)
add("sounds", JsonArray())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,20 @@ class ConfigureSoundTriggerScreen : Fragment() {
field(getString("label_enable_sound_trigger")) {
checkbox(property = viewModel.enabled)
}
field(getString("label_bounty_rune_timer")) {
field(getString("label_rune_timer")) {
visibleWhen(viewModel.showBountyRuneTimer)
managedWhen(visibleProperty())
bountyRuneSpinner(viewModel.bountyRuneTimer)
button(graphic = ImageView(HelpIcon())) {
action { showInformation(getString("header_about_bounty_rune_timer"), getString("content_about_bounty_rune_timer")) }
action { showInformation(getString("header_about_rune_timer"), getString("content_about_rune_timer")) }
}
}
field(getString("label_rune_timer")) {
visibleWhen(viewModel.showWisdomRuneTimer)
managedWhen(visibleProperty())
bountyRuneSpinner(viewModel.wisdomRuneTimer)
button(graphic = ImageView(HelpIcon())) {
action { showInformation(getString("header_about_rune_timer"), getString("content_about_rune_timer")) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.github.mrbean355.admiralbulldog.arch.AppViewModel
import com.github.mrbean355.admiralbulldog.persistence.ConfigPersistence
import com.github.mrbean355.admiralbulldog.triggers.OnBountyRunesSpawn
import com.github.mrbean355.admiralbulldog.triggers.OnHeal
import com.github.mrbean355.admiralbulldog.triggers.OnWisdomRunesSpawn
import com.github.mrbean355.admiralbulldog.triggers.Periodically
import com.github.mrbean355.admiralbulldog.triggers.SoundTriggerType
import javafx.beans.binding.BooleanBinding
Expand All @@ -40,6 +41,8 @@ class ConfigureSoundTriggerViewModel : AppViewModel() {
val enabled: BooleanProperty = booleanProperty(ConfigPersistence.isSoundTriggerEnabled(type))
val bountyRuneTimer: IntegerProperty = intProperty(ConfigPersistence.getBountyRuneTimer())
val showBountyRuneTimer: BooleanProperty = booleanProperty(type == OnBountyRunesSpawn::class)
val wisdomRuneTimer: IntegerProperty = intProperty(ConfigPersistence.getWisdomRuneTimer())
val showWisdomRuneTimer: BooleanProperty = booleanProperty(type == OnWisdomRunesSpawn::class)
val soundBiteCount = stringProperty(ConfigPersistence.getSoundsForType(type).size.toString())

/* Chance to play */
Expand All @@ -61,6 +64,7 @@ class ConfigureSoundTriggerViewModel : AppViewModel() {
init {
enabled.onChange { ConfigPersistence.toggleSoundTrigger(type, it) }
bountyRuneTimer.onChange { ConfigPersistence.setBountyRuneTimer(it) }
wisdomRuneTimer.onChange { ConfigPersistence.setWisdomRuneTimer(it) }
useSmartChance.onChange { ConfigPersistence.setIsUsingHealSmartChance(it) }
chance.onChange { ConfigPersistence.setSoundTriggerChance(type, it) }
minPeriod.onChange {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,56 +24,68 @@ import com.github.mrbean355.admiralbulldog.triggers.OnHeal
import com.github.mrbean355.admiralbulldog.triggers.OnKill
import com.github.mrbean355.admiralbulldog.triggers.OnMatchStart
import com.github.mrbean355.admiralbulldog.triggers.OnMidasReady
import com.github.mrbean355.admiralbulldog.triggers.OnPause
import com.github.mrbean355.admiralbulldog.triggers.OnRespawn
import com.github.mrbean355.admiralbulldog.triggers.OnSmoked
import com.github.mrbean355.admiralbulldog.triggers.OnVictory
import com.github.mrbean355.admiralbulldog.triggers.OnWisdomRunesSpawn
import com.github.mrbean355.admiralbulldog.triggers.Periodically
import com.github.mrbean355.admiralbulldog.triggers.RoshanTimer
import com.github.mrbean355.admiralbulldog.triggers.SoundTriggerType

val SoundTriggerType.friendlyName: String
get() = when (this) {
OnBountyRunesSpawn::class -> getString("trigger_name_bounty_runes")
OnWisdomRunesSpawn::class -> getString("trigger_name_wisdom_runes")
OnDeath::class -> getString("trigger_name_death")
OnDefeat::class -> getString("trigger_name_defeat")
OnHeal::class -> getString("trigger_name_heal")
OnKill::class -> getString("trigger_name_kill")
OnMatchStart::class -> getString("trigger_name_match_start")
OnMidasReady::class -> getString("trigger_name_midas_ready")
OnPause::class -> getString("trigger_name_paused")
OnRespawn::class -> getString("trigger_name_respawn")
OnSmoked::class -> getString("trigger_name_smoked")
OnVictory::class -> getString("trigger_name_victory")
Periodically::class -> getString("trigger_name_periodically")
RoshanTimer::class -> getString("trigger_name_roshan")
else -> throw IllegalArgumentException("Unexpected type: $this")
}

val SoundTriggerType.description: String
get() = when (this) {
OnBountyRunesSpawn::class -> getString("trigger_desc_bounty_runes")
OnWisdomRunesSpawn::class -> getString("trigger_desc_wisdom_runes")
OnDeath::class -> getString("trigger_desc_death")
OnDefeat::class -> getString("trigger_desc_defeat")
OnHeal::class -> getString("trigger_desc_heal")
OnKill::class -> getString("trigger_desc_kill")
OnMatchStart::class -> getString("trigger_desc_match_start")
OnMidasReady::class -> getString("trigger_desc_midas_ready")
OnPause::class -> getString("trigger_desc_paused")
OnRespawn::class -> getString("trigger_desc_respawn")
OnSmoked::class -> getString("trigger_desc_smoked")
OnVictory::class -> getString("trigger_desc_victory")
Periodically::class -> getString("trigger_desc_periodically")
RoshanTimer::class -> getString("trigger_desc_roshan")
else -> throw IllegalArgumentException("Unexpected type: $this")
}

val SoundTriggerType.tableHeader: String
get() = when (this) {
OnBountyRunesSpawn::class -> getString("trigger_header_bounty_runes")
OnWisdomRunesSpawn::class -> getString("trigger_header_wisdom_runes")
OnDeath::class -> getString("trigger_header_death")
OnDefeat::class -> getString("trigger_header_defeat")
OnHeal::class -> getString("trigger_header_heal")
OnKill::class -> getString("trigger_header_kill")
OnMatchStart::class -> getString("trigger_header_match_start")
OnMidasReady::class -> getString("trigger_header_midas_ready")
OnPause::class -> getString("trigger_header_paused")
OnRespawn::class -> getString("trigger_header_respawn")
OnSmoked::class -> getString("trigger_header_smoked")
OnVictory::class -> getString("trigger_header_victory")
Periodically::class -> getString("trigger_header_periodically")
RoshanTimer::class -> getString("trigger_header_roshan")
else -> throw IllegalArgumentException("Unexpected type: $this")
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,11 @@

package com.github.mrbean355.admiralbulldog.triggers

import com.github.mrbean355.admiralbulldog.game.GameState
import com.github.mrbean355.admiralbulldog.persistence.ConfigPersistence
import com.github.mrbean355.dota2.map.MatchState
import kotlin.math.ceil

/** How often (in seconds) the runes spawn. */
private const val HOW_OFTEN = 3 * 60

/** Plays a sound shortly before the bounty runes spawn. */
class OnBountyRunesSpawn : SoundTrigger {
private var nextPlayTime = UNINITIALISED

override fun shouldPlay(previous: GameState, current: GameState): Boolean {
val gameState = current.map.matchState
if (gameState != MatchState.PreGame && gameState != MatchState.GameInProgress) {
// Don't play during the picking phase.
return false
}
val currentTime = current.map.clockTime
val warningPeriod = ConfigPersistence.getBountyRuneTimer()
if (nextPlayTime == UNINITIALISED) {
nextPlayTime = findNextPlayTime(currentTime, warningPeriod)
}
if (currentTime >= nextPlayTime) {
val diff = currentTime - nextPlayTime
nextPlayTime = findNextPlayTime(currentTime + 10, warningPeriod)
if (diff <= warningPeriod) {
return true
}
}
return false
}

private fun findNextPlayTime(clockTime: Int, warningPeriod: Int): Int {
val iteration = ceil((clockTime + warningPeriod) / HOW_OFTEN.toFloat()).toInt()
val nextPlayTime = iteration * HOW_OFTEN - warningPeriod
return nextPlayTime.coerceAtLeast(-warningPeriod)
}
}
class OnBountyRunesSpawn : RunesSpawnTrigger(
frequencyMinutes = 3,
spawnsAtStart = true,
provideWarningPeriod = { ConfigPersistence.getBountyRuneTimer() },
)
Loading

0 comments on commit d53d31d

Please sign in to comment.