From b79d97db17590025f080abb3834514c39de60abc Mon Sep 17 00:00:00 2001 From: kokekanon <114332266+kokekanon@users.noreply.github.com> Date: Sat, 8 Feb 2025 23:55:30 -0300 Subject: [PATCH 1/4] 001-UI-ClientOptions --- modules/client_options/data_options.lua | 119 ++++++++++++++ modules/client_options/options.lua | 19 ++- .../client_options/styles/sound/audio.otui | 36 ----- .../styles/sound/battleSounds.otui | 146 ++++++++++++++++++ .../client_options/styles/sound/sound.otui | 119 ++++++++++++++ .../client_options/styles/sound/uiSounds.otui | 120 ++++++++++++++ 6 files changed, 516 insertions(+), 43 deletions(-) delete mode 100644 modules/client_options/styles/sound/audio.otui create mode 100644 modules/client_options/styles/sound/battleSounds.otui create mode 100644 modules/client_options/styles/sound/sound.otui create mode 100644 modules/client_options/styles/sound/uiSounds.otui diff --git a/modules/client_options/data_options.lua b/modules/client_options/data_options.lua index 2c8335991a..b529303878 100644 --- a/modules/client_options/data_options.lua +++ b/modules/client_options/data_options.lua @@ -470,4 +470,123 @@ return { listKeybindsComboBox(value) end }, + battleSoundOwnBattlesubChannelsSpells = true, + battleSoundOwnBattleSubChannelsAttack = true, + battleSoundOwnBattleSoundSubChannelsHealing = true, + battleSoundOwnBattleSoundSubChannelsSupport = true, + battleSoundOwnBattleSoundSubChannelsWeapons = true, + battleSoundOtherPlayersSubChannelsSpells = true, + battleSoundOtherPlayersSubChannelsAttack = true, + battleSoundOtherPlayersSubChannelsHealing = true, + battleSoundOtherPlayersSubChannelsSupport = true, + battleSoundOtherPlayersSubChannelsWeapons = true, + battleSoundCreatureSubChannelsNoises = true, + battleSoundCreatureSubChannelsNoisesDeath = true, + battleSoundCreatureSubChannelsAttacksAndSpells = true, + soundAnthem = true, + soundFoodAndBeverages = true, + soundMoveItem = true, + soundUIsubChannelsInteractions = true, + soundUIsubChannelsJoinLeaveParty = true, + soundUIsubChannelsVipLoginLogout = true, + soundNotificationUIInteractions = true, + soundNotificationsubChannelsParty = true, + soundNotificationsubChannelsGuild = true, + soundNotificationsubChannelsLocalChat = true, + soundNotificationsubChannelsPrivateMessages = true, + soundNotificationsubChannelsNPC = true, + soundNotificationsubChannelsGlobal = true, + soundNotificationsubChannelsTeamFinder = true, + soundNotificationsubChannelsRaidAnnuncements = true, + soundNotificationsubChannelsSystemAnnouncements = true, + battleSoundOwnBattle = { + value = 100, + action = function(value, options, controller, panels, extraWidgets) + panels.battleSoundsPanel:recursiveGetChildById('battleSoundOwnBattle'):setText(tr( + 'Own Battle Sounds: %d %%', value)) + end + }, + battleSoundOtherPlayers = { + value = 100, + action = function(value, options, controller, panels, extraWidgets) + panels.battleSoundsPanel:recursiveGetChildById('battleSoundOtherPlayers'):setText(tr( + 'Others Players: %d %%', value)) + end + }, + battleSoundCreature = { + value = 100, + action = function(value, options, controller, panels, extraWidgets) + panels.battleSoundsPanel:recursiveGetChildById('battleSoundCreature'):setText(tr('Creature: %d %%', value)) + end + }, + soundUI = { + value = 100, + action = function(value, options, controller, panels, extraWidgets) + panels.iuSoundPanel:recursiveGetChildById('soundUI'):setText(tr('UI Volumen: %d %%', value)) + end + }, + soundMaster = { + value = 100, + aux = true, + action = function(value, options, controller, panels, extraWidgets) + if not g_sounds then + return + end + local soundMasterWidget = panels.soundPanel:recursiveGetChildById('soundMaster') + soundMasterWidget:setText(string.format('Master Volume: %d %%', value)) + -- TODO CHECK CHANNEL + if g_sounds then + g_sounds.getChannel(SoundChannels.Music):setGain(value / 100) + end + local shouldDisable = value <= 1 + local hasChanged = shouldDisable ~= (options.soundMaster.aux or false) + if not hasChanged then + return + end + options.soundMaster.aux = shouldDisable + if shouldDisable then + modules.game_sound.unregisterEvents() + extraWidgets.audioButton:setIcon('/images/topbuttons/button_mute_pressed') + else + modules.game_sound.registerEvents() + extraWidgets.audioButton:setIcon('/images/topbuttons/button_mute_up') + end + local function togglePanel(panel) + if not panel then + return + end + local infoPanel = panel:recursiveGetChildById('info') + local children = panel:getChildren() + for _, widget in ipairs(children) do + if widget:getStyle().__class ~= "UILabel" then + widget:setEnabled(not shouldDisable) + end + end + infoPanel:setVisible(shouldDisable) + infoPanel:setHeight(shouldDisable and 30 or 0) + end + + togglePanel(panels.battleSoundsPanel) + togglePanel(panels.iuSoundPanel) + end + }, + soundMusic = { + value = 100, + action = function(value, options, controller, panels, extraWidgets) + panels.soundPanel:recursiveGetChildById('soundMusic'):setText(tr('Music Volume: %d %%', value)) + end + }, + soundAmbience = { + value = 100, + action = function(value, options, controller, panels, extraWidgets) + panels.soundPanel:recursiveGetChildById('soundAmbience'):setText(tr('Ambience Volumen: %d %%', value)) + end + }, + soundItems = { + value = 100, + action = function(value, options, controller, panels, extraWidgets) + panels.soundPanel:recursiveGetChildById('soundItems'):setText(tr('Item Volume: %d %%', value)) + end + } + } diff --git a/modules/client_options/options.lua b/modules/client_options/options.lua index 892994214f..d98d175c68 100644 --- a/modules/client_options/options.lua +++ b/modules/client_options/options.lua @@ -9,7 +9,10 @@ panels = { interface = nil, misc = nil, miscHelp = nil, - keybindsPanel = nil + keybindsPanel = nil, + battleSoundsPanel = nil, + iuSoundPanel = nil, + } -- LuaFormatter off local buttons = { { @@ -43,14 +46,14 @@ local buttons = { { }, { text = "Sound", icon = "/images/icons/icon_sound", - open = "soundPanel" - --[[ subCategories = {{ + open = "soundPanel", + subCategories = {{ text = "Battle Sounds", - open = "Battle_Sounds" + open = "battleSoundsPanel" }, { text = "UI Sounds", - open = "UI_Sounds" - }} ]] + open = "iuSoundPanel" + }} }, { text = "Misc.", icon = "/images/icons/icon_misc", @@ -207,7 +210,9 @@ function controller:onInit() panels.interfaceConsole = g_ui.loadUI('styles/interface/console', controller.ui.optionsTabContent) panels.interfaceHUD = g_ui.loadUI('styles/interface/HUD', controller.ui.optionsTabContent) - panels.soundPanel = g_ui.loadUI('styles/sound/audio', controller.ui.optionsTabContent) + panels.soundPanel = g_ui.loadUI('styles/sound/sound', controller.ui.optionsTabContent) + panels.battleSoundsPanel = g_ui.loadUI('styles/sound/battleSounds', controller.ui.optionsTabContent) + panels.iuSoundPanel = g_ui.loadUI('styles/sound/uiSounds', controller.ui.optionsTabContent) panels.misc = g_ui.loadUI('styles/misc/misc', controller.ui.optionsTabContent) panels.miscHelp = g_ui.loadUI('styles/misc/help', controller.ui.optionsTabContent) diff --git a/modules/client_options/styles/sound/audio.otui b/modules/client_options/styles/sound/audio.otui deleted file mode 100644 index 5af79771e1..0000000000 --- a/modules/client_options/styles/sound/audio.otui +++ /dev/null @@ -1,36 +0,0 @@ -UIWidget - anchors.fill: parent - visible: false - SmallReversedQtPanel - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - height: 22 - - OptionCheckBox - id: enableAudio - !text: tr('Enable audio') - - SmallReversedQtPanel - anchors.left: parent.left - anchors.right: parent.right - anchors.top: prev.bottom - margin-top: 7 - height: 44 - - OptionCheckBox - id: enableMusicSound - !text: tr('Enable music sound') - - OptionScaleScroll - id: musicSoundVolume - margin-top: 5 - anchors.top: prev.bottom - anchors.left: parent.left - anchors.right: parent.right - &minimumScrollValue: 1 - &maximumScrollValue: 100 - &scrollSize: 21 - @onSetup: | - local value = modules.client_options.getOption('musicSoundVolume') - self:setText(tr('Music volume: %d', value)) diff --git a/modules/client_options/styles/sound/battleSounds.otui b/modules/client_options/styles/sound/battleSounds.otui new file mode 100644 index 0000000000..403e5af411 --- /dev/null +++ b/modules/client_options/styles/sound/battleSounds.otui @@ -0,0 +1,146 @@ +UIWidget + anchors.fill: parent + visible: false + Label + id: info + text: "The sound is currently desactivated. \nincrease the master volume in the Options menu 'Sound'." + text-vertical-auto-resize: true + color: #FF9854 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + + SmallReversedQtPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + height: 100 + margin-top: 7 + + OptionScaleScroll + id: battleSoundOwnBattle + anchors.left: parent.left + anchors.right: parent.right + &minimumScrollValue: 1 + &maximumScrollValue: 100 + &scrollSize: 21 + margin-left: -15 + @onSetup: | + local value = modules.client_options.getOption('battleSoundOwnBattle') + self:setText(tr('Own Battle Sounds: %d %%', value)) + + OptionCheckBox + id: battleSoundOwnBattlesubChannelsSpells + !text: tr('Spells') + anchors.top: prev.bottom + margin-top:5 + + MiniWindowContents + padding-left: 10 + padding-right: 5 + padding-top: 10 + + layout: verticalBox + QtCheckBox + id: battleSoundOwnBattleSubChannelsAttack + !text: tr('Attack') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + QtCheckBox + id: battleSoundOwnBattleSoundSubChannelsHealing + !text: tr('Healing') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + QtCheckBox + id: battleSoundOwnBattleSoundSubChannelsSupport + !text: tr('Support') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + + OptionCheckBox + id: battleSoundOwnBattleSoundSubChannelsWeapons + anchors.top: prev.bottom + !text: tr('Weapons') + margin-top: -10 + + SmallReversedQtPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 7 + height: 100 + OptionScaleScroll + id: battleSoundOtherPlayers + anchors.left: parent.left + anchors.right: parent.right + &minimumScrollValue: 1 + &maximumScrollValue: 100 + &scrollSize: 21 + margin-left: -15 + @onSetup: | + local value = modules.client_options.getOption('battleSoundOtherPlayers') or 0 + self:setText(tr('Others Players: %d %%', value)) + + OptionCheckBox + id: battleSoundOtherPlayersSubChannelsSpells + !text: tr('Spells') + anchors.top: prev.bottom + margin-top:5 + + + MiniWindowContents + padding-left: 10 + padding-right: 5 + padding-top: 10 + + layout: verticalBox + QtCheckBox + id: battleSoundOtherPlayersSubChannelsAttack + !text: tr('Attack') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + QtCheckBox + id: battleSoundOtherPlayersSubChannelsHealing + !text: tr('Healing') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + QtCheckBox + id: battleSoundOtherPlayersSubChannelsSupport + !text: tr('Support') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + + OptionCheckBox + id: battleSoundOtherPlayersSubChannelsWeapons + anchors.top: prev.bottom + !text: tr('Weapons') + margin-top: -10 + + SmallReversedQtPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 7 + height: 75 + OptionScaleScroll + id: battleSoundCreature + anchors.left: parent.left + anchors.right: parent.right + &minimumScrollValue: 1 + &maximumScrollValue: 100 + &scrollSize: 21 + margin-left: -15 + @onSetup: | + local value = modules.client_options.getOption('battleSoundCreature') + self:setText(tr('Creature: %d %%', value)) + + OptionCheckBox + id: battleSoundCreatureSubChannelsNoises + !text: tr('Creature Noises') + anchors.top: prev.bottom + margin-top:5 + + OptionCheckBox + id: battleSoundCreatureSubChannelsNoisesDeath + !text: tr('Death') + anchors.top: prev.bottom + OptionCheckBox + id: battleSoundCreatureSubChannelsAttacksAndSpells + !text: tr('Attacks And Spells') + anchors.top: prev.bottom + + diff --git a/modules/client_options/styles/sound/sound.otui b/modules/client_options/styles/sound/sound.otui new file mode 100644 index 0000000000..0f3abfe0fc --- /dev/null +++ b/modules/client_options/styles/sound/sound.otui @@ -0,0 +1,119 @@ +UIWidget + anchors.fill: parent + visible: false + + SmallReversedQtPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: 22 + + OptionScaleScroll + id: soundMaster + anchors.left: parent.left + anchors.right: parent.right + &minimumScrollValue: 1 + &maximumScrollValue: 100 + &scrollSize: 21 + margin-left: -15 + @onSetup: | + local value = modules.client_options.getOption('soundMaster') + self:setText(tr('Master Volumen: %d %%', value)) + + SmallReversedQtPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 7 + height: 44 + + OptionScaleScroll + id: soundMusic + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + &minimumScrollValue: 1 + &maximumScrollValue: 100 + &scrollSize: 21 + margin-left: -15 + @onSetup: | + local value = modules.client_options.getOption('soundMusic') + self:setText(tr('Music Volume: %d %%', value)) + + OptionCheckBox + id: soundAnthem + !text: tr('Anthem') + margin-top: 5 + anchors.top: prev.bottom + + SmallReversedQtPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 7 + height: 25 + OptionScaleScroll + id: soundAmbience + margin-top: 2 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + &minimumScrollValue: 1 + &maximumScrollValue: 100 + &scrollSize: 21 + margin-left: -15 + @onSetup: | + local value = modules.client_options.getOption('soundAmbience') + self:setText(tr('Ambience Volumen: %d %%', value)) + + SmallReversedQtPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 7 + height: 55 + + OptionScaleScroll + id: soundItems + margin-top: 1 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + &minimumScrollValue: 1 + &maximumScrollValue: 100 + &scrollSize: 21 + margin-left: -15 + @onSetup: | + local value = modules.client_options.getOption('soundItems') + self:setText(tr('Item Volume: %d %%', value)) + + OptionCheckBox + id: soundFoodAndBeverages + !text: tr('Food and Beverages') + anchors.top: prev.bottom + + OptionCheckBox + id: soundMoveItem + !text: tr('Move Item') + anchors.top: prev.bottom + + SmallReversedQtPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 7 + height: 33 + + OptionScaleScroll + id: musicSoundVolume + margin-top: 5 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + &minimumScrollValue: 1 + &maximumScrollValue: 100 + &scrollSize: 21 + margin-left: -15 + @onSetup: | + local value = modules.client_options.getOption('musicSoundVolume') + self:setText(tr('Event Volume: %d %%', value)) \ No newline at end of file diff --git a/modules/client_options/styles/sound/uiSounds.otui b/modules/client_options/styles/sound/uiSounds.otui new file mode 100644 index 0000000000..52b9c4f2d1 --- /dev/null +++ b/modules/client_options/styles/sound/uiSounds.otui @@ -0,0 +1,120 @@ +UIWidget + anchors.fill: parent + visible: false + Label + id: info + text: "The sound is currently desactivated. \nincrease the master volume in the Options menu 'Sound'." + color: #FF9854 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + + + SmallReversedQtPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 7 + height: 20 + + OptionScaleScroll + id: soundUI + anchors.left: parent.left + anchors.right: parent.right + &minimumScrollValue: 1 + &maximumScrollValue: 100 + &scrollSize: 21 + margin-left: -15 + @onSetup: | + local value = modules.client_options.getOption('soundUI') + self:setText(tr('UI Volumen: %d %%', value)) + + SmallReversedQtPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 7 + height: 22 + + OptionCheckBox + id: soundUIsubChannelsInteractions + !text: tr('UI Interactions') + + SmallReversedQtPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 7 + height: 40 + + OptionCheckBox + id: soundUIsubChannelsJoinLeaveParty + !text: tr('Join/Leave Party') + + OptionCheckBox + id: soundUIsubChannelsVipLoginLogout + !text: tr('VIP login/logout') + anchors.top: prev.bottom + + SmallReversedQtPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 7 + height: 160 + + OptionCheckBox + id: soundNotificationUIInteractions + !text: tr('UI Interactions') + + MiniWindowContents + id: soundNotification + padding-left: 10 + padding-right: 5 + layout: verticalBox + QtCheckBox + id: soundNotificationsubChannelsParty + !text: tr('Party') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + QtCheckBox + id: soundNotificationsubChannelsGuild + !text: tr('Guild') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + QtCheckBox + id: soundNotificationsubChannelsLocalChat + !text: tr('Private Messages in Local Chat') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + QtCheckBox + id: soundNotificationsubChannelsPrivateMessages + !text: tr('Private Messages') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + QtCheckBox + id: soundNotificationsubChannelsNPC + !text: tr('NPC') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + QtCheckBox + id: soundNotificationsubChannelsGlobal + !text: tr('Global') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + QtCheckBox + id: soundNotificationsubChannelsTeamFinder + !text: tr('Team Finder') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + QtCheckBox + id: soundNotificationsubChannelsRaidAnnuncements + !text: tr('Raid Annuncements') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + QtCheckBox + id: soundNotificationsubChannelsSystemAnnouncements + !text: tr('System Announcements') + !tooltip: tr('Check this box to see how much time or how many charges are left \non your equipped items') + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + UIWidget + id: toolTipWidget + image-source: /images/icons/show_gui_help_grey + size: 12 12 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + margin-right: 3 + + From 1eb4e36818dfde02718bbe9fdcd966bcd8438d70 Mon Sep 17 00:00:00 2001 From: kokekanon <114332266+kokekanon@users.noreply.github.com> Date: Sun, 9 Feb 2025 01:39:57 -0300 Subject: [PATCH 2/4] 002-proposed module --- modules/client_options/data_options.lua | 101 ++- .../styles/sound/battleSounds.otui | 3 +- .../client_options/styles/sound/sound.otui | 4 +- modules/corelib/const.lua | 11 +- modules/game_interface/interface.otmod | 3 +- modules/game_screenshot/game_screenshot.lua | 2 + modules/game_sound/game_sound.lua | 724 ++++++++++++++++++ modules/game_sound/game_sound.otmod | 9 + 8 files changed, 816 insertions(+), 41 deletions(-) create mode 100644 modules/game_sound/game_sound.lua create mode 100644 modules/game_sound/game_sound.otmod diff --git a/modules/client_options/data_options.lua b/modules/client_options/data_options.lua index b529303878..d835b8a46a 100644 --- a/modules/client_options/data_options.lua +++ b/modules/client_options/data_options.lua @@ -58,32 +58,11 @@ return { enableAudio = { value = true, action = function(value, options, controller, panels, extraWidgets) - if g_sounds then - g_sounds.setAudioEnabled(value) - end - if value then - extraWidgets.audioButton:setIcon('/images/topbuttons/button_mute_up') + setOption("soundMaster", 100) else - extraWidgets.audioButton:setIcon('/images/topbuttons/button_mute_pressed') - end - end - }, - enableMusicSound = { - value = true, - action = function(value, options, controller, panels, extraWidgets) - if g_sounds then - g_sounds.getChannel(SoundChannels.Music):setEnabled(value) - end - end - }, - musicSoundVolume = { - value = 100, - action = function(value, options, controller, panels, extraWidgets) - if g_sounds then - g_sounds.getChannel(SoundChannels.Music):setGain(value / 100) + setOption("soundMaster", 1) end - panels.soundPanel:recursiveGetChildById('musicSoundVolume'):setText(tr('Music volume: %d', value)) end }, enableLights = { @@ -381,16 +360,20 @@ return { value = 0, action = function(value, options, controller, panels, extraWidgets) local bar = modules.game_healthcircle.optionPanel:recursiveGetChildById('distFromCenScrollbar') - bar:setText(tr('Distance: %s', bar:recursiveGetChildById('valueBar'):getValue())) - modules.game_healthcircle.setDistanceFromCenter(bar:recursiveGetChildById('valueBar'):getValue()) + if bar then + bar:setText(tr('Distance: %s', bar:recursiveGetChildById('valueBar'):getValue())) + modules.game_healthcircle.setDistanceFromCenter(bar:recursiveGetChildById('valueBar'):getValue()) + end end }, opacityScrollbar = { value = 0, action = function(value, options, controller, panels, extraWidgets) local bar = modules.game_healthcircle.optionPanel:recursiveGetChildById('opacityScrollbar') - bar:setText(tr('Opacity: %s', bar:recursiveGetChildById('valueBar'):getValue() / 100)) - modules.game_healthcircle.setCircleOpacity(bar:recursiveGetChildById('valueBar'):getValue() / 100) + if bar then + bar:setText(tr('Opacity: %s', bar:recursiveGetChildById('valueBar'):getValue() / 100)) + modules.game_healthcircle.setCircleOpacity(bar:recursiveGetChildById('valueBar'):getValue() / 100) + end end }, profile = { @@ -470,12 +453,35 @@ return { listKeybindsComboBox(value) end }, - battleSoundOwnBattlesubChannelsSpells = true, + -- TODO move to \modules\game_sound\game_sound.lua + battleSoundOwnBattlesubChannelsSpells = { + value = true, + action = function(value, options, controller, panels, extraWidgets) + if value then + panels.battleSoundsPanel:recursiveGetChildById("panelOwnBattleSubChannels"):enable() + + else + panels.battleSoundsPanel:recursiveGetChildById("panelOwnBattleSubChannels"):disable() + + end + end + }, battleSoundOwnBattleSubChannelsAttack = true, battleSoundOwnBattleSoundSubChannelsHealing = true, battleSoundOwnBattleSoundSubChannelsSupport = true, battleSoundOwnBattleSoundSubChannelsWeapons = true, - battleSoundOtherPlayersSubChannelsSpells = true, + battleSoundOtherPlayersSubChannelsSpells = { + value = true, + action = function(value, options, controller, panels, extraWidgets) + if value then + panels.battleSoundsPanel:recursiveGetChildById("panelOtherPlayersSubChannels"):enable() + + else + panels.battleSoundsPanel:recursiveGetChildById("panelOtherPlayersSubChannels"):disable() + + end + end + }, battleSoundOtherPlayersSubChannelsAttack = true, battleSoundOtherPlayersSubChannelsHealing = true, battleSoundOtherPlayersSubChannelsSupport = true, @@ -534,9 +540,8 @@ return { end local soundMasterWidget = panels.soundPanel:recursiveGetChildById('soundMaster') soundMasterWidget:setText(string.format('Master Volume: %d %%', value)) - -- TODO CHECK CHANNEL - if g_sounds then - g_sounds.getChannel(SoundChannels.Music):setGain(value / 100) + for channelName, channelId in pairs(SoundChannels) do + g_sounds.getChannel(channelId):setGain(value / 100) end local shouldDisable = value <= 1 local hasChanged = shouldDisable ~= (options.soundMaster.aux or false) @@ -545,10 +550,10 @@ return { end options.soundMaster.aux = shouldDisable if shouldDisable then - modules.game_sound.unregisterEvents() + g_sounds.setAudioEnabled(false) extraWidgets.audioButton:setIcon('/images/topbuttons/button_mute_pressed') else - modules.game_sound.registerEvents() + g_sounds.setAudioEnabled(true) extraWidgets.audioButton:setIcon('/images/topbuttons/button_mute_up') end local function togglePanel(panel) @@ -565,15 +570,34 @@ return { infoPanel:setVisible(shouldDisable) infoPanel:setHeight(shouldDisable and 30 or 0) end - togglePanel(panels.battleSoundsPanel) togglePanel(panels.iuSoundPanel) end }, soundMusic = { value = 100, + aux = true, + event = nil, action = function(value, options, controller, panels, extraWidgets) panels.soundPanel:recursiveGetChildById('soundMusic'):setText(tr('Music Volume: %d %%', value)) + if not g_sounds then + return + end + g_sounds.getChannel(SoundChannels.Music):setGain(value / 100) + local shouldBeDisabled = value <= 1 + if shouldBeDisabled ~= (not options.soundMusic.aux) then + options.soundMusic.aux = not shouldBeDisabled + if options.soundMusic.event ~= nil then + removeEvent(options.soundMusic.event) + end + options.soundMusic.event = scheduleEvent(function() + if shouldBeDisabled then + g_sounds.getChannel(SoundChannels.Music):setEnabled(false) + else + g_sounds.getChannel(SoundChannels.Music):setEnabled(true) + end + end, 100) + end end }, soundAmbience = { @@ -587,6 +611,11 @@ return { action = function(value, options, controller, panels, extraWidgets) panels.soundPanel:recursiveGetChildById('soundItems'):setText(tr('Item Volume: %d %%', value)) end + }, + soundEventVolume = { + value = 100, + action = function(value, options, controller, panels, extraWidgets) + panels.soundPanel:recursiveGetChildById('soundEventVolume'):setText(tr('Event Volume: %d %%', value)) + end } - } diff --git a/modules/client_options/styles/sound/battleSounds.otui b/modules/client_options/styles/sound/battleSounds.otui index 403e5af411..42d78fc1fa 100644 --- a/modules/client_options/styles/sound/battleSounds.otui +++ b/modules/client_options/styles/sound/battleSounds.otui @@ -36,10 +36,10 @@ UIWidget margin-top:5 MiniWindowContents + id: panelOwnBattleSubChannels padding-left: 10 padding-right: 5 padding-top: 10 - layout: verticalBox QtCheckBox id: battleSoundOwnBattleSubChannelsAttack @@ -86,6 +86,7 @@ UIWidget MiniWindowContents + id: panelOtherPlayersSubChannels padding-left: 10 padding-right: 5 padding-top: 10 diff --git a/modules/client_options/styles/sound/sound.otui b/modules/client_options/styles/sound/sound.otui index 0f3abfe0fc..3895637db6 100644 --- a/modules/client_options/styles/sound/sound.otui +++ b/modules/client_options/styles/sound/sound.otui @@ -105,7 +105,7 @@ UIWidget height: 33 OptionScaleScroll - id: musicSoundVolume + id: soundEventVolume margin-top: 5 anchors.top: parent.top anchors.left: parent.left @@ -115,5 +115,5 @@ UIWidget &scrollSize: 21 margin-left: -15 @onSetup: | - local value = modules.client_options.getOption('musicSoundVolume') + local value = modules.client_options.getOption('soundEventVolume') self:setText(tr('Event Volume: %d %%', value)) \ No newline at end of file diff --git a/modules/corelib/const.lua b/modules/corelib/const.lua index 086f6880d2..71f031d7aa 100644 --- a/modules/corelib/const.lua +++ b/modules/corelib/const.lua @@ -329,5 +329,14 @@ NetworkMessageTypes = { SoundChannels = { Music = 1, Ambient = 2, - Effect = 3 + Effect = 3, + Spells = 4, + Item = 5, + Event = 6, + OwnBattles = 7, + OthersPlayers = 8, + Creature = 9, + SoundUI = 10, + Bot = 11, + secundaryChannel = 12 } diff --git a/modules/game_interface/interface.otmod b/modules/game_interface/interface.otmod index 34c78c9c4c..3b4f3b4573 100644 --- a/modules/game_interface/interface.otmod +++ b/modules/game_interface/interface.otmod @@ -50,6 +50,7 @@ Module - game_quickloot - game_cyclopedia - game_creatureinformation - + - game_sound + @onLoad: init() @onUnload: terminate() \ No newline at end of file diff --git a/modules/game_screenshot/game_screenshot.lua b/modules/game_screenshot/game_screenshot.lua index 09536df3ff..034e24c813 100644 --- a/modules/game_screenshot/game_screenshot.lua +++ b/modules/game_screenshot/game_screenshot.lua @@ -70,6 +70,7 @@ function screenshotController:onGameStart() if g_game.getClientVersion() < 1180 then return end + if optionPanel then return end optionPanel = g_ui.loadUI('game_screenshot',modules.client_options:getPanel()) for _, screenshotEvent in ipairs(AutoScreenshotEvents) do @@ -98,6 +99,7 @@ function screenshotController:onGameStart() end function screenshotController:onGameEnd() + if not optionPanel then return end if g_game.getClientVersion() >= 1180 then g_settings.set("onlyCaptureGameWindow",optionPanel:recursiveGetChildById("onlyCaptureGameWindow"):isChecked()) g_settings.set("enableScreenshots",optionPanel:recursiveGetChildById("enableScreenshots"):isChecked()) diff --git a/modules/game_sound/game_sound.lua b/modules/game_sound/game_sound.lua new file mode 100644 index 0000000000..e06afd756c --- /dev/null +++ b/modules/game_sound/game_sound.lua @@ -0,0 +1,724 @@ +---------------------------------------------------------------- +---------------------To-do---------------------------------- +---------------------------------------------------------------- +-- Multi Channels +-- 8-D +-- randomPitch, randomVolume +-- move options/sounds to here +-- cache +-- ambienceObjectStream with onAddThing and onPositionChange ? +-- uiSound with onTalk ? UIWidget-onClick ? +---------------------------------------------------------------- +---------------------Variables---------------------------------- +---------------------------------------------------------------- +local debugMode = false + +function toggleDebugMode() + -- modules.game_sound.toggleDebugMode() + debugMode = not debugMode + if debugMode then + print("Debug mode enabled") + else + print("Debug mode disabled") + end +end + +local function createEffectAtPosition(pos, effectId) + local effect = Effect.create() + effect:setId(effectId) + g_map.addThing(effect, pos) +end + +local function createMissileBetweenPositions(fromPos, toPos, missileId) + local missile = Missile.create() + missile:setId(missileId) + missile:setPath(fromPos, toPos) + g_map.addThing(missile, fromPos) +end + +local function debugTablePrint(title, data) + local function createSeparator(length) + return string.rep("=", length) + end + + local function createRow(cells, separator) + local row = "" + for i, cell in ipairs(cells) do + row = row .. tostring(cell) + if i < #cells then + row = row .. (separator or " ") + end + end + return row + end + + local title_fill = createSeparator(title:len()) + local header_str = '\n' .. createSeparator(title:len() + 8) .. '\n' + header_str = header_str .. '=== ' .. title .. ' ===\n' + header_str = header_str .. createSeparator(title:len() + 8) .. '\n' + + local output = {} + table.insert(output, "Missile created from") + table.insert(output, createSeparator(80)) + + -- Positions + table.insert(output, "Positions") + table.insert(output, createRow( + {"Position Sonido", "x= " .. data.soundPos.x, "y= " .. data.soundPos.y, "z= " .. data.soundPos.z})) + table.insert(output, createRow( + {"Posicion del player", "x= " .. data.playerPos.x, "y= " .. data.playerPos.y, "z= " .. data.playerPos.z})) + + table.insert(output, createRow({"distanceCalculate", data.distanceCalculate})) + table.insert(output, createRow({"gainCalculate", data.gainCalculate})) + table.insert(output, createSeparator(80)) + + -- Sound Info + table.insert(output, "Info del sonido") + table.insert(output, createRow({"g_sounds.soundIds(soundID)", data.soundEffectType})) + table.insert(output, createRow({"g_sounds.getSoundEffectType(soundID)", data.getSoundEffectType})) + table.insert(output, createRow({"channelId", data.channelId})) + + local footer_str = '\n' .. createSeparator(title:len() + 8) .. '\n' + if data.color == "#FFFF00" then + pwarning(header_str .. table.concat(output, "\n") .. footer_str) + else + perror(header_str .. table.concat(output, "\n") .. footer_str) + + end + -- parseColoredText(header_str .. table.concat(output, "\n") .. footer_str) +end + +---------------------------------------------------------------- +---------------------Variables---------------------------------- +---------------------------------------------------------------- + +local soundType = { + NUMERIC_SOUND_TYPE_UNKNOWN = 0, + NUMERIC_SOUND_TYPE_SPELL_ATTACK = 1, + NUMERIC_SOUND_TYPE_SPELL_HEALING = 2, + NUMERIC_SOUND_TYPE_SPELL_SUPPORT = 3, + NUMERIC_SOUND_TYPE_WEAPON_ATTACK = 4, + NUMERIC_SOUND_TYPE_CREATURE_NOISE = 5, + NUMERIC_SOUND_TYPE_CREATURE_DEATH = 6, + NUMERIC_SOUND_TYPE_CREATURE_ATTACK = 7, + NUMERIC_SOUND_TYPE_AMBIENCE_STREAM = 8, + NUMERIC_SOUND_TYPE_FOOD_AND_DRINK = 9, + NUMERIC_SOUND_TYPE_ITEM_MOVEMENT = 10, + NUMERIC_SOUND_TYPE_EVENT = 11, + NUMERIC_SOUND_TYPE_UI = 12, + NUMERIC_SOUND_TYPE_WHISPER_WITHOUT_OPEN_CHAT = 13, + NUMERIC_SOUND_TYPE_CHAT_MESSAGE = 14, + NUMERIC_SOUND_TYPE_PARTY = 15, + NUMERIC_SOUND_TYPE_VIP_LIST = 16, + NUMERIC_SOUND_TYPE_RAID_ANNOUNCEMENT = 17, + NUMERIC_SOUND_TYPE_SERVER_MESSAGE = 18, + NUMERIC_SOUND_TYPE_SPELL_GENERIC = 19 +} + +local MusicType = { + MUSIC_TYPE_UNKNOWN = 0, + MUSIC_TYPE_MUSIC = 1, + MUSIC_TYPE_MUSIC_IMMEDIATE = 2, + MUSIC_TYPE_MUSIC_TITLE = 3 +} + +local soundSource = { + GLOBAL = 0, + OWN = 1, + OTHERS = 2, + CREATURES = 3 +} + +---------------------------------------------------------------- +---------------------getConfig---------------------------------- +---------------------------------------------------------------- +-- LuaFormatter off +local function getSoundConfig() + return { + panelSound = { + masterVolume = modules.client_options.getOption('soundMaster'), + musicVolume = modules.client_options.getOption('soundMusic'), + anthem = modules.client_options.getOption('soundAnthem'), + ambienceVolume = modules.client_options.getOption('soundAmbience'), + items = modules.client_options.getOption('soundItems'), + foodAndBeverages = modules.client_options.getOption('soundFoodAndBeverages'), + moveItem = modules.client_options.getOption('soundMoveItem'), + eventVolume = modules.client_options.getOption('soundEventVolume') + }, + battleSound = { + [soundSource.GLOBAL] = { + volume = modules.client_options.getOption('battleSoundOwnBattle'), + subChannels = { + } + }, + [soundSource.OWN] = { + volume = modules.client_options.getOption('battleSoundOwnBattle'), + subChannels = { + [-1] = modules.client_options.getOption('battleSoundOwnBattlesubChannelsSpells'), -- UI disable? + [soundType.NUMERIC_SOUND_TYPE_SPELL_ATTACK] = modules.client_options.getOption('battleSoundOwnBattleSubChannelsAttack'), + [soundType.NUMERIC_SOUND_TYPE_SPELL_HEALING] = modules.client_options.getOption('battleSoundOwnBattleSoundSubChannelsHealing'), + [soundType.NUMERIC_SOUND_TYPE_SPELL_SUPPORT] = modules.client_options.getOption('battleSoundOwnBattleSoundSubChannelsSupport'), + [soundType.NUMERIC_SOUND_TYPE_WEAPON_ATTACK] = modules.client_options.getOption('battleSoundOwnBattleSoundSubChannelsWeapons') + } + }, + [soundSource.OTHERS] = { + volume = modules.client_options.getOption('battleSoundOtherPlayers'), + subChannels = { + [-1] = modules.client_options.getOption('battleSoundOtherPlayersSubChannelsSpells'), -- UI disable? + [soundType.NUMERIC_SOUND_TYPE_SPELL_ATTACK] = modules.client_options.getOption('battleSoundOtherPlayersSubChannelsAttack'), + [soundType.NUMERIC_SOUND_TYPE_SPELL_HEALING] = modules.client_options.getOption('battleSoundOtherPlayersSubChannelsHealing'), + [soundType.NUMERIC_SOUND_TYPE_SPELL_SUPPORT] = modules.client_options.getOption('battleSoundOtherPlayersSubChannelsSupport'), + [soundType.NUMERIC_SOUND_TYPE_WEAPON_ATTACK] = modules.client_options.getOption('battleSoundOtherPlayersSubChannelsWeapons'), + [soundType.NUMERIC_SOUND_TYPE_CREATURE_NOISE] = modules.client_options.getOption('battleSoundCreatureSubChannelsNoises'), + + } + }, + [soundSource.CREATURES] = { + volume = modules.client_options.getOption('battleSoundCreature'), + subChannels = { + [soundType.NUMERIC_SOUND_TYPE_CREATURE_NOISE] = modules.client_options.getOption('battleSoundCreatureSubChannelsNoises'), + [soundType.NUMERIC_SOUND_TYPE_CREATURE_DEATH] = modules.client_options.getOption('battleSoundCreatureSubChannelsNoisesDeath'), + [soundType.NUMERIC_SOUND_TYPE_CREATURE_ATTACK] = modules.client_options.getOption('battleSoundCreatureSubChannelsAttacksAndSpells'), + [soundType.NUMERIC_SOUND_TYPE_SPELL_ATTACK] = modules.client_options.getOption('battleSoundCreatureSubChannelsAttacksAndSpells'), + [soundType.NUMERIC_SOUND_TYPE_WEAPON_ATTACK] = modules.client_options.getOption('battleSoundCreatureSubChannelsAttacksAndSpells') + } + } + }, + uiSound = { + volume = modules.client_options.getOption('soundUI'), + subChannels = { + interactions = modules.client_options.getOption('soundUIsubChannelsInteractions'), + joinLeaveParty = modules.client_options.getOption('soundUIsubChannelsJoinLeaveParty'), + vipLoginLogout = modules.client_options.getOption('soundUIsubChannelsVipLoginLogout') + } + }, + notifications = { + subChannels = { + party = modules.client_options.getOption('soundNotificationsubChannelsParty'), + guild = modules.client_options.getOption('soundNotificationsubChannelsGuild'), + localChat = modules.client_options.getOption('soundNotificationsubChannelsLocalChat'), + privateMessages = modules.client_options.getOption('soundNotificationsubChannelsPrivateMessages'), + npc = modules.client_options.getOption('soundNotificationsubChannelsNPC'), + global = modules.client_options.getOption('soundNotificationsubChannelsGlobal'), + teamFinder = modules.client_options.getOption('soundNotificationsubChannelsTeamFinder'), + raidAnnouncements = modules.client_options.getOption('soundNotificationsubChannelsRaidAnnuncements'), + systemAnnouncements = modules.client_options.getOption('soundNotificationsubChannelsSystemAnnouncements') + } + } + } +end +-- LuaFormatter on +---------------------------------------------------------------- +---------------------Play-------------------------------------- +---------------------------------------------------------------- + +local function playSoundBasedOnPosition(soundID, pos, max, channel) + + if type(soundID) == "number" then + soundID = g_sounds.getAudioFileNameById(soundID) + end + local playerPos = g_game.getLocalPlayer():getPosition() + local distance = math.sqrt((pos.x - playerPos.x) ^ 2 + (pos.y - playerPos.y) ^ 2) + + local soundChannel = g_sounds.getChannel(channel) + if not soundChannel then + return + end + + local maxDistance = max or 8 -- extendedview ? + local gain = math.max(0.1, 1 - (distance / maxDistance)) + + local stereoBalance = (pos.x - playerPos.x) / maxDistance + + local active_source = soundChannel:play("/sounds/" .. g_game.getProtocolVersion() .. "/" .. soundID, 0, gain, 1.0) + if active_source then + active_source:setPosition({ + x = stereoBalance, + y = 0 + }) + end + if debugMode then + createEffectAtPosition(pos, 4) + createMissileBetweenPositions(pos, playerPos, 6) + end +end + +---------------------------------------------------------------- +---------------------Protocol----------------------------------- +---------------------------------------------------------------- + +local lastPlayedSounds = {} +local function cleanOldEntries() + -- TODO improve + local currentTime = g_clock.millis() / 1000 + for soundID, timestamp in pairs(lastPlayedSounds) do + if currentTime - timestamp > 10 then + lastPlayedSounds[soundID] = nil + end + end +end + +local function handleSoundPlayback(soundSource2, soundID, pos, channel, debugColor) + cleanOldEntries() + local currentTime = g_clock.millis() / 1000 + local cooldownTime = 1 -- in seconds + if lastPlayedSounds[soundID] and (currentTime - lastPlayedSounds[soundID] < cooldownTime) then + return + end + + lastPlayedSounds[soundID] = currentTime + + local soundConfig = getSoundConfig() + local sourceConfig = soundConfig.battleSound[soundSource2] + if not sourceConfig or not sourceConfig.volume or sourceConfig.volume == 1 then + return + end + if soundSource2 >= 1 then + local soundType = g_sounds.getSoundEffectType(soundID) + local subChannelConfig = sourceConfig.subChannels[soundType] + if not subChannelConfig then + return + end + end + local soundIds = g_sounds.getRandomSoundIds(soundID) + if type(soundIds) == "table" and #soundIds > 0 then + soundIds = soundIds[math.random(1, #soundIds)] + playSoundBasedOnPosition(soundIds, pos, 8, channel) + elseif type(soundIds) == "number" then + playSoundBasedOnPosition(soundIds, pos, 8, channel) + end + if debugMode then + local playerPos = g_game.getLocalPlayer():getPosition() + local distance = math.sqrt((pos.x - playerPos.x) ^ 2 + (pos.y - playerPos.y) ^ 2) + local debugData = { + color = debugColor, + soundPos = pos, + playerPos = playerPos, + distanceCalculate = distance, + gainCalculate = math.max(0, 1 - (distance / 8)), + soundIds = soundIds, + getSoundEffectType = g_sounds.getSoundEffectType(soundID), + channelId = channel + } + debugTablePrint("Debug Sound Playback", debugData) + end +end + +local function soundMain(soundSource2, soundID, pos) + handleSoundPlayback(soundSource2, soundID, pos, SoundChannels.Effect, "#FFFF00") +end + +local function soundSecondary(SoundENUM, soundSource2, soundID, pos) + print("what is 'SoundENUM' : ?" .. SoundENUM) + handleSoundPlayback(soundSource2, soundID, pos, SoundChannels.secundaryChannel, "#FF00003") +end + +---------------------------------------------------------------- +---------------------Terminal----------------------------------- +---------------------------------------------------------------- +local function clearAudios(gameEnd) + if gameEnd then + for channelName, channelId in pairs(SoundChannels) do + if channelId ~= SoundChannels.Music then + g_sounds.getChannel(channelId):stop() + end + end + else + g_sounds.stopAll() + end +end + +---------------------------------------------------------------- +---------------------Controller----------------------------------- +---------------------------------------------------------------- +local function testSound(bool) + if bool then + connect(g_sounds, { + soundMain = soundMain, + soundSecondary = soundSecondary + }) + else + disconnect(g_sounds, { + soundMain = soundMain, + soundSecondary = soundSecondary + }) + end +end + +controllerSound = Controller:new() +function controllerSound:onInit() + if not g_sounds then + return + end + if g_modules.isAutoReloadEnabled() then + -- registerEvents() + end + controllerSound:registerEvents(g_sounds, { + testSound = testSound + }) + +end + +function controllerSound:onGameStart() +end + +function controllerSound:onGameEnd() + if not g_sounds then + return + end + clearAudios(true) +end + +function controllerSound:onTerminate() + if g_modules.isAutoReloadEnabled() then + if not g_sounds then + return + end + clearAudios(false) + -- unregisterEvents() + end +end + +---------------------------------------------------------------- +---------------------Bibliografia----------------------------------- +---------------------------------------------------------------- +--[[ + ---* FRAMEWORK_SOUND +---@class g_sounds +g_sounds = {} + +---@param fileName string +function g_sounds.preload(fileName) end + +---@param fileName string +---@param fadeTime? number 0.0 +---@param gain? number 0.0 +---@param pitch? number 0.0 +---@return SoundSource +function g_sounds.play(fileName, fadeTime, gain, pitch) end + +---@param channelId integer +---@return SoundChannel +function g_sounds.getChannel(channelId) end + +function g_sounds.stopAll() end + +function g_sounds.enableAudio() end + +function g_sounds.disableAudio() end + +---@return boolean +function g_sounds.isAudioEnabled() end + +---@param pos Point | string +function g_sounds.setPosition(pos) end + +---@return SoundSource +function g_sounds.createSoundEffect() end + +---@return boolean +function g_sounds.isEaxEnabled() end + +---@param file string +---@return boolean +function g_sounds.loadClientFiles(directory) end + +---@param audioFileId string +---@return string +function g_sounds.getAudioFileNameById(audioFileId) end + +-------------------------------- +--------- SoundSource ---------- +-------------------------------- + +---* FRAMEWORK_SOUND +---@class SoundSource +SoundSource = {} + +---@return SoundSource +function SoundSource.create(fileName) end + +---@param name string +function SoundSource:setName(name) end + +function SoundSource:play() end + +function SoundSource:stop() end + +---@return boolean +function SoundSource:isPlaying() end + +---@param gain number +function SoundSource:setGain(gain) end + +---@param pos Point | string +function SoundSource:setPosition(pos) end + +---@param velocity Point | string +function SoundSource:setVelocity(velocity) end + +---@param state number +---@param fadeTime number +function SoundSource:setFading(state, fadeTime) end + +---@param looping boolean +function SoundSource:setLooping(looping) end + +---@param relative boolean +function SoundSource:setRelative(relative) end + +---@param distance number +function SoundSource:setReferenceDistance(distance) end + +---@param soundEffect SoundEffect +function SoundSource:setEffect(soundEffect) end + +function SoundSource:removeEffect() end + +-------------------------------- +------ CombinedSoundSource ----- +-------------------------------- + +---* FRAMEWORK_SOUND +---@class CombinedSoundSource : SoundSource +CombinedSoundSource = {} + +-------------------------------- +------ StreamSoundSource ------- +-------------------------------- + +---* FRAMEWORK_SOUND +---@class StreamSoundSource : SoundSource +StreamSoundSource = {} + +-------------------------------- +--------- SoundEffect ---------- +-------------------------------- + +---* FRAMEWORK_SOUND +---@class SoundEffect +SoundEffect = {} + +---@param presetName string +function SoundEffect:setPreset(presetName) end + +-------------------------------- +--------- SoundChannel --------- +-------------------------------- + +---* FRAMEWORK_SOUND +---@class SoundChannel +SoundChannel = {} + +---@param fileName string +---@param fadeTime? number 0.0 +---@param gain? number 1.0 +---@param pitch? number 1.0 +---@return SoundSource +function SoundChannel:play(fileName, fadeTime, gain, pitch) end + +---@param fadeTime? number 0.0 +function SoundChannel:stop(fadeTime) end + +---@param fileName string +---@param fadeTime? number 0.0 +---@param gain? number 1.0 +---@param pitch? number 1.0 +function SoundChannel:enqueue(fileName, fadeTime, gain, pitch) end + +function SoundChannel:enable() end + +function SoundChannel:disable() end + +---@param gain number +function SoundChannel:setGain(gain) end + +---@return number +function SoundChannel:getGain() end + +---@param enabled boolean +function SoundChannel:setEnabled(enabled) end + +---@return boolean +function SoundChannel:isEnabled() end + +---@return integer +function SoundChannel:getId() end +local availablePresets = { + "none", + "generic", + "paddedCell", + "room", + "bathroom", + "stoneroom", + "sewerPipe", + "underWater", + "dizzy", + "psychotic", + "none", + "generic", + "paddedcell", + "room", + "bathroom", + "livingroom", + "stoneroom", + "auditorium", + "concerthall", + "cave", + "arena", + "hangar", + "carpetedhallway", + "hallway", + "stonecorridor", + "alley", + "forest", + "city", + "mountains", + "quarry", + "plain", + "parkinglot", + "sewerpipe", + "underwater", + "drugged", + "dizzy", + "psychotic", + "castleSmallroom", + "castleShortpassage", + "castleMediumroom", + "castleLargeroom", + "castleLongpassage", + "castleHall", + "castleCupboard", + "castleCourtyard", + "castleAlcove", + "factorySmallroom", + "factoryShortpassage", + "factoryMediumroom", + "factoryLargeroom", + "factoryLongpassage", + "factoryHall", + "factoryCupboard", + "factoryCourtyard", + "factoryAlcove", + "icepalaceSmallroom", + "icepalaceShortpassage", + "icepalaceMediumroom", + "icepalaceLargeroom", + "icepalaceLongpassage", + "icepalaceHall", + "icepalaceCupboard", + "icepalaceCourtyard", + "icepalaceAlcove", + "spacestationSmallroom", + "spacestationShortpassage", + "spacestationMediumroom", + "spacestationLargeroom", + "spacestationLongpassage", + "spacestationHall", + "spacestationCupboard", + "spacestationAlcove", + "woodenSmallroom", + "woodenShortpassage", + "woodenMediumroom", + "woodenLargeroom", + "woodenLongpassage", + "woodenHall", + "woodenCupboard", + "woodenCourtyard", + "woodenAlcove", + "sportEmptystadium", + "sportSquashcourt", + "sportSmallswimmingpool", + "sportLargeswimmingpool", + "sportGymnasium", + "sportFullstadium", + "sportStadiumtannoy", + "prefabWorkshop", + "prefabSchoolroom", + "prefabPractiseroom", + "prefabOuthouse", + "prefabCaravan", + "domeTomb", + "pipeSmall", + "domeSaintpauls", + "pipeLongthin", + "pipeLarge", + "pipeResonant", + "outdoorsBackyard", + "outdoorsRollingplains", + "outdoorsDeepcanyon", + "outdoorsCreek", + "outdoorsValley", + "moodHeaven", + "moodHell", + "moodMemory", + "drivingCommentator", + "drivingPitgarage", + "drivingIncarRacer", + "drivingIncarSports", + "drivingIncarLuxury", + "drivingFullgrandstand", + "drivingEmptygrandstand", + "drivingTunnel", + "cityStreets", + "citySubway", + "cityMuseum", + "cityLibrary", + "cityUnderpass", + "cityAbandoned", + "dustyroom", + "chapel", + "smallwaterroom", +} + + ]] + +local function testThings(tile, thing) + print(1) + if not thing:isItem() then + return + end + print(2) + --[[ + { + "id": 13, + "countedAppearanceTypes": [ + 1922, + ], + "soundEffects": [ + { + "count": 1, + "loopingSoundId": 631 + } + ], + "maxSoundDistance": 3 + } + ]] + if thing:getId() == 1922 then + print(3) + local ogg = g_sounds.getAudioFileNameById(631) + playSoundBasedOnPosition(ogg, thing:getPosition(), 3) + end + +end + +local function onPositionChange(newPos, oldPos) + +end + +local function test() + connect(Tile, { + onAddThing = testThings + }) + connect(LocalPlayer, { + onPositionChange = onPositionChange + }) +end +local function test2() + disconnect(Tile, { + onAddThing = testThings + }) + disconnect(LocalPlayer, { + onPositionChange = onPositionChange + }) +end diff --git a/modules/game_sound/game_sound.otmod b/modules/game_sound/game_sound.otmod new file mode 100644 index 0000000000..6c863fc157 --- /dev/null +++ b/modules/game_sound/game_sound.otmod @@ -0,0 +1,9 @@ +Module + name: game_sound + description: sound + author: OTClient team + website: https://github.com/mehah/otclient + scripts: [ game_sound ] + sandbox: true + @onLoad: controllerSound:init() + @onUnload: controllerSound:terminate() From cf2b379a65fc87ad0940aea3aa252b38bb5dd9fa Mon Sep 17 00:00:00 2001 From: kokekanon <114332266+kokekanon@users.noreply.github.com> Date: Sun, 9 Feb 2025 01:40:02 -0300 Subject: [PATCH 3/4] 003-cpp --- src/client/luavaluecasts_client.cpp | 36 ++++++++++++++++++++++++++++ src/client/luavaluecasts_client.h | 4 ++++ src/client/protocolgameparse.cpp | 12 ++++++---- src/framework/core/modulemanager.h | 2 +- src/framework/luafunctions.cpp | 3 +++ src/framework/sound/soundmanager.cpp | 27 +++++++++++++++++++++ src/framework/sound/soundmanager.h | 2 ++ 7 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/client/luavaluecasts_client.cpp b/src/client/luavaluecasts_client.cpp index 4ab801f041..1d9a01a9e5 100644 --- a/src/client/luavaluecasts_client.cpp +++ b/src/client/luavaluecasts_client.cpp @@ -1157,3 +1157,39 @@ int push_luavalue(const RaceType& raceData) g_lua.setField("boss"); return 1; } + +int push_luavalue(const ClientSoundEffect& soundEffect) { + g_lua.createTable(0, 7); + + g_lua.pushInteger(soundEffect.clientId); + g_lua.setField("clientId"); + + g_lua.pushInteger(static_cast(soundEffect.type)); + g_lua.setField("type"); + + g_lua.createTable(0, 2); + g_lua.pushNumber(soundEffect.pitchMin); + g_lua.setField("min"); + g_lua.pushNumber(soundEffect.pitchMax); + g_lua.setField("max"); + g_lua.setField("randomPitch"); + + g_lua.createTable(0, 2); + g_lua.pushNumber(soundEffect.volumeMin); + g_lua.setField("min"); + g_lua.pushNumber(soundEffect.volumeMax); + g_lua.setField("max"); + g_lua.setField("randomVolume"); + + g_lua.pushInteger(soundEffect.soundId); + g_lua.setField("soundId"); + + g_lua.createTable(soundEffect.randomSoundId.size(), 0); + for (size_t i = 0; i < soundEffect.randomSoundId.size(); ++i) { + g_lua.pushInteger(soundEffect.randomSoundId[i]); + g_lua.rawSeti(i + 1); + } + g_lua.setField("randomSoundId"); + + return 1; +} diff --git a/src/client/luavaluecasts_client.h b/src/client/luavaluecasts_client.h index 6d9cf0d60a..1c18c77da4 100644 --- a/src/client/luavaluecasts_client.h +++ b/src/client/luavaluecasts_client.h @@ -24,6 +24,7 @@ #include "game.h" #include "outfit.h" +#include // outfit int push_luavalue(const Outfit& outfit); @@ -92,3 +93,6 @@ int push_luavalue(const CharacterInfoFamiliar& familiar); // bestiary int push_luavalue(const RaceType& raceData); + +// sound +int push_luavalue(const ClientSoundEffect& soundEffect); diff --git a/src/client/protocolgameparse.cpp b/src/client/protocolgameparse.cpp index 82d25ca715..722d53533d 100644 --- a/src/client/protocolgameparse.cpp +++ b/src/client/protocolgameparse.cpp @@ -1677,15 +1677,17 @@ void ProtocolGame::parseMagicEffect(const InputMessagePtr& msg) } case Otc::MAGIC_EFFECTS_CREATE_SOUND_MAIN_EFFECT: { - msg->getU8(); // Source - msg->getU16(); // Sound ID + const uint8_t soundSource = msg->getU8(); // Source + const uint16_t SoundID = msg->getU16(); // Sound ID + g_lua.callGlobalField("g_sounds", "soundMain", soundSource, SoundID, pos); break; } case Otc::MAGIC_EFFECTS_CREATE_SOUND_SECONDARY_EFFECT: { - msg->getU8(); // ENUM - msg->getU8(); // Source - msg->getU16(); // Sound ID + const uint8_t SoundENUM = msg->getU8(); // ENUM + const uint8_t soundSource = msg->getU8(); // Source + const uint16_t soundID = msg->getU16(); // Sound ID + g_lua.callGlobalField("g_sounds", "soundSecondary", SoundENUM, soundSource, soundID, pos); break; } default: diff --git a/src/framework/core/modulemanager.h b/src/framework/core/modulemanager.h index c5b4867a97..23dc92a308 100644 --- a/src/framework/core/modulemanager.h +++ b/src/framework/core/modulemanager.h @@ -41,7 +41,7 @@ class ModuleManager std::deque getModules() { return m_modules; } ModulePtr getCurrentModule() { return m_currentModule; } void enableAutoReload(); - bool isAutoReloadEnabled() const { return m_reloadEnable; } + bool isAutoReloadEnabled() { return m_reloadEnable; } protected: void updateModuleLoadOrder(const ModulePtr& module); diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index 78a0b813aa..ffebbc80f3 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -219,6 +219,7 @@ void Application::registerLuaFunctions() g_lua.bindSingletonFunction("g_modules", "getModules", &ModuleManager::getModules, &g_modules); g_lua.bindSingletonFunction("g_modules", "getCurrentModule", &ModuleManager::getCurrentModule, &g_modules); g_lua.bindSingletonFunction("g_modules", "enableAutoReload", &ModuleManager::enableAutoReload, &g_modules); + g_lua.bindSingletonFunction("g_modules", "isAutoReloadEnabled", &ModuleManager::isAutoReloadEnabled, &g_modules); // EventDispatcher g_lua.registerSingletonClass("g_dispatcher"); @@ -1006,6 +1007,8 @@ void Application::registerLuaFunctions() g_lua.bindSingletonFunction("g_sounds", "isEaxEnabled", &SoundManager::isEaxEnabled, &g_sounds); g_lua.bindSingletonFunction("g_sounds", "loadClientFiles", &SoundManager::loadClientFiles, &g_sounds); g_lua.bindSingletonFunction("g_sounds", "getAudioFileNameById", &SoundManager::getAudioFileNameById, &g_sounds); + g_lua.bindSingletonFunction("g_sounds", "getRandomSoundIds", &SoundManager::getRandomSoundIds, &g_sounds); + g_lua.bindSingletonFunction("g_sounds", "getSoundEffectType", &SoundManager::getSoundEffectType, &g_sounds); g_lua.registerClass(); g_lua.bindClassStaticFunction("create", [] { return std::make_shared(); }); diff --git a/src/framework/sound/soundmanager.cpp b/src/framework/sound/soundmanager.cpp index dfbf32efe6..c5361f35d9 100644 --- a/src/framework/sound/soundmanager.cpp +++ b/src/framework/sound/soundmanager.cpp @@ -159,6 +159,7 @@ void SoundManager::poll() void SoundManager::setAudioEnabled(const bool enable) { + g_lua.callGlobalField("g_sounds", "testSound", enable); // for test if (m_audioEnabled == enable) return; @@ -536,3 +537,29 @@ std::string SoundManager::getAudioFileNameById(int32_t audioFileId) return ""; } + +std::vector SoundManager::getRandomSoundIds(uint32_t id) +{ + // test + if (m_clientSoundEffects.find(id) == m_clientSoundEffects.end()) { + return {}; + } + const auto& soundEffect = m_clientSoundEffects[id]; + if (!soundEffect.randomSoundId.empty()) { + return soundEffect.randomSoundId; + } + if (soundEffect.soundId != 0) { + return { soundEffect.soundId }; + } + return {}; +} + +ClientSoundType SoundManager::getSoundEffectType(uint32_t id) +{ + if (m_clientSoundEffects.find(id) == m_clientSoundEffects.end()) { + return NUMERIC_SOUND_TYPE_UNKNOWN; + } + + return m_clientSoundEffects[id].type; +} + diff --git a/src/framework/sound/soundmanager.h b/src/framework/sound/soundmanager.h index fbc753f85c..25abf466e5 100644 --- a/src/framework/sound/soundmanager.h +++ b/src/framework/sound/soundmanager.h @@ -135,6 +135,8 @@ class SoundManager bool isEaxEnabled(); bool loadClientFiles(const std::string& directory); std::string getAudioFileNameById(int32_t audioFileId); + std::vector getRandomSoundIds(uint32_t id); + ClientSoundType getSoundEffectType(uint32_t id); void preload(std::string filename); SoundSourcePtr play(const std::string& filename, float fadetime = 0, float gain = 0, float pitch = 0); From 108321c4c00d6a358aa00cb93ad31670fba0439e Mon Sep 17 00:00:00 2001 From: kokekanon <114332266+kokekanon@users.noreply.github.com> Date: Mon, 10 Feb 2025 18:03:19 -0300 Subject: [PATCH 4/4] 004-fix-withoutFRAMEWORK_SOUND --- modules/game_things/things.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/game_things/things.lua b/modules/game_things/things.lua index a042e87fe8..9afa92e0b2 100644 --- a/modules/game_things/things.lua +++ b/modules/game_things/things.lua @@ -63,7 +63,9 @@ function load(version) -- loading client files was successful, try to load sounds now -- sound files are optional, this means that failing to load them -- will not block logging into game - g_sounds.loadClientFiles(resolvepath(string.format('/sounds/%d/', version))) + if g_sounds then + g_sounds.loadClientFiles(resolvepath(string.format('/sounds/%d/', version))) + end return end