| | | @@ -0,0 +1,250 @@ |
| 1 | + | local CODE = 108 |
| 2 | + | |
| 3 | + | -- Configura��o |
| 4 | + | local SOUNDS_CONFIG = { |
| 5 | + | areaSoundFolder = 'sounds/', -- Pasta onde ficam as m�sicas de �rea |
| 6 | + | effectSoundFolder= 'sounds/', -- Pasta onde ficam efeitos sonoros |
| 7 | + | checkInterval = 500, -- Intervalo (ms) para verificar se o player est� numa �rea |
| 8 | + | noSound = 'No sound file for this area.' -- Texto exibido na interface quando n�o h� som |
| 9 | + | } |
| 10 | + | |
| 11 | + | -- �reas que t�m m�sica de fundo |
| 12 | + | local AREAS = { |
| 13 | + | { |
| 14 | + | fromPos = {x = 3967, y = 936, z = 8}, |
| 15 | + | toPos = {x = 4038, y = 1000, z = 8}, |
| 16 | + | sound = "cave.ogg" |
| 17 | + | }, |
| 18 | + | { |
| 19 | + | fromPos = {x = 3921, y = 904, z = 7}, |
| 20 | + | toPos = {x = 4086, y = 1006, z = 7}, |
| 21 | + | sound = "gaivotas.ogg" |
| 22 | + | } |
| 23 | + | } |
| 24 | + | |
| 25 | + | -- Vari�veis globais |
| 26 | + | local areaChannel -- Canal para m�sica de �rea (background) |
| 27 | + | local effectChannel -- Canal para efeitos sonoros pontuais |
| 28 | + | local playingAreaSound = nil -- Qual m�sica de �rea est� tocando agora |
| 29 | + | local toggleSoundEvent = nil -- Evento agendado para checar posi��o periodicamente |
| 30 | + | |
| 31 | + | --[[ |
| 32 | + | FUN��O PRINCIPAL DE INICIALIZA��O |
| 33 | + | Chamada quando o script do OTClient � carregado. |
| 34 | + | --]] |
| 35 | + | function init() |
| 36 | + | -- Conecta eventos do jogo |
| 37 | + | connect(g_game, { onGameStart = onGameStart, onGameEnd = onGameEnd }) |
| 38 | + | |
| 39 | + | -- Registra opcode para sons de efeito vindos do servidor |
| 40 | + | ProtocolGame.registerExtendedOpcode(CODE, onExtendedOpcode) |
| 41 | + | |
| 42 | + | -- Cria canais de som |
| 43 | + | areaChannel = g_sounds.getChannel(SoundChannels.Music) -- Canal de m�sica de fundo |
| 44 | + | effectChannel = g_sounds.getChannel(SoundChannels.Effects) -- Canal de efeitos |
| 45 | + | |
| 46 | + | -- Bot�o na interface |
| 47 | + | soundButton = modules.client_topmenu.addRightGameToggleButton( |
| 48 | + | 'soundButton', 'Som', '/images/audio', toggleSoundWindow |
| 49 | + | ) |
| 50 | + | soundButton:setOn(true) |
| 51 | + | |
| 52 | + | -- Carrega a janela "rcsound.otui" |
| 53 | + | soundWindow = g_ui.loadUI('rcsound', modules.game_interface.getRightPanel()) |
| 54 | + | soundWindow:disableResize() |
| 55 | + | soundWindow:setup() |
| 56 | + | |
| 57 | + | if g_game.isOnline() then |
| 58 | + | onGameStart() |
| 59 | + | end |
| 60 | + | end |
| 61 | + | |
| 62 | + | --[[ |
| 63 | + | FUN��O DE FINALIZA��O |
| 64 | + | Chamada quando o script � descarregado do OTClient. |
| 65 | + | --]] |
| 66 | + | function terminate() |
| 67 | + | disconnect(g_game, { onGameStart = onGameStart, onGameEnd = onGameEnd }) |
| 68 | + | ProtocolGame.unregisterExtendedOpcode(CODE) |
| 69 | + | onGameEnd() |
| 70 | + | |
| 71 | + | soundWindow:destroy() |
| 72 | + | soundButton:destroy() |
| 73 | + | end |
| 74 | + | |
| 75 | + | --[[ |
| 76 | + | Chamado quando o jogo fica online (player loga) |
| 77 | + | --]] |
| 78 | + | function onGameStart() |
| 79 | + | stopAreaSound() |
| 80 | + | toggleSoundEvent = addEvent(checkAreaSound, SOUNDS_CONFIG.checkInterval) |
| 81 | + | end |
| 82 | + | |
| 83 | + | --[[ |
| 84 | + | Chamado quando o jogo fica offline (player desloga) |
| 85 | + | --]] |
| 86 | + | function onGameEnd() |
| 87 | + | stopAreaSound() |
| 88 | + | if toggleSoundEvent then |
| 89 | + | removeEvent(toggleSoundEvent) |
| 90 | + | toggleSoundEvent = nil |
| 91 | + | end |
| 92 | + | end |
| 93 | + | |
| 94 | + | --[[ |
| 95 | + | Abrir/fechar janela de som |
| 96 | + | --]] |
| 97 | + | function toggleSoundWindow() |
| 98 | + | if soundButton:isOn() then |
| 99 | + | soundWindow:close() |
| 100 | + | soundButton:setOn(false) |
| 101 | + | else |
| 102 | + | soundWindow:open() |
| 103 | + | soundButton:setOn(true) |
| 104 | + | end |
| 105 | + | end |
| 106 | + | |
| 107 | + | function onMiniWindowClose() |
| 108 | + | if soundButton then |
| 109 | + | soundButton:setOn(false) |
| 110 | + | end |
| 111 | + | end |
| 112 | + | |
| 113 | + | --[[ |
| 114 | + | Checa se a posi��o "pos" est� dentro do ret�ngulo 3D [fromPos -> toPos] |
| 115 | + | --]] |
| 116 | + | function isInPos(pos, fromPos, toPos) |
| 117 | + | return (pos.x >= fromPos.x and pos.y >= fromPos.y and pos.z >= fromPos.z) |
| 118 | + | and (pos.x <= toPos.x and pos.y <= toPos.y and pos.z <= toPos.z) |
| 119 | + | end |
| 120 | + | |
| 121 | + | --[[ |
| 122 | + | Fun��o principal que verifica a �rea do player a cada "checkInterval" |
| 123 | + | e decide se deve tocar/parar a m�sica de �rea. |
| 124 | + | --]] |
| 125 | + | function checkAreaSound() |
| 126 | + | local player = g_game.getLocalPlayer() |
| 127 | + | if not player then return end |
| 128 | + | |
| 129 | + | local pos = player:getPosition() |
| 130 | + | local areaSoundData = nil |
| 131 | + | |
| 132 | + | for _, area in ipairs(AREAS) do |
| 133 | + | if isInPos(pos, area.fromPos, area.toPos) then |
| 134 | + | areaSoundData = area |
| 135 | + | break |
| 136 | + | end |
| 137 | + | end |
| 138 | + | |
| 139 | + | if areaSoundData then |
| 140 | + | -- Se � outra m�sica, troca |
| 141 | + | if playingAreaSound ~= areaSoundData.sound then |
| 142 | + | stopAreaSound() |
| 143 | + | local fullPath = SOUNDS_CONFIG.areaSoundFolder .. areaSoundData.sound |
| 144 | + | if g_resources.fileExists(fullPath) then |
| 145 | + | g_logger.info("Tocando m�sica de �rea: " .. fullPath) |
| 146 | + | playAreaSound(fullPath) |
| 147 | + | playingAreaSound = areaSoundData.sound |
| 148 | + | else |
| 149 | + | g_logger.warning("Arquivo de m�sica n�o encontrado: " .. fullPath) |
| 150 | + | end |
| 151 | + | end |
| 152 | + | else |
| 153 | + | -- Se n�o est� em �rea, parar se estava tocando algo |
| 154 | + | if playingAreaSound then |
| 155 | + | g_logger.info("Parando m�sica de �rea.") |
| 156 | + | stopAreaSound() |
| 157 | + | end |
| 158 | + | end |
| 159 | + | |
| 160 | + | toggleSoundEvent = scheduleEvent(checkAreaSound, SOUNDS_CONFIG.checkInterval) |
| 161 | + | end |
| 162 | + | |
| 163 | + | --[[ |
| 164 | + | Toca a m�sica de �rea no canal "areaChannel" |
| 165 | + | Ajuste o volume inicial se quiser mais alto/baixo (0.1 = 10%, 0.4 = 40% etc.). |
| 166 | + | --]] |
| 167 | + | function playAreaSound(soundPath) |
| 168 | + | if not areaChannel then |
| 169 | + | print("Canal de �rea n�o dispon�vel!") |
| 170 | + | return |
| 171 | + | end |
| 172 | + | areaChannel:play(soundPath, 0, 0.1) |
| 173 | + | areaChannel:setGain(0.1) |
| 174 | + | |
| 175 | + | setLabel(soundPath) |
| 176 | + | end |
| 177 | + | |
| 178 | + | --[[ |
| 179 | + | Para a m�sica de �rea |
| 180 | + | --]] |
| 181 | + | function stopAreaSound() |
| 182 | + | if areaChannel then |
| 183 | + | areaChannel:stop() |
| 184 | + | end |
| 185 | + | playingAreaSound = nil |
| 186 | + | setLabel(SOUNDS_CONFIG.noSound) |
| 187 | + | end |
| 188 | + | |
| 189 | + | --[[ |
| 190 | + | Recebe opcode do servidor com algum som extra (efeito) para tocar |
| 191 | + | --]] |
| 192 | + | function onExtendedOpcode(protocol, code, buffer) |
| 193 | + | if code ~= CODE then return end |
| 194 | + | |
| 195 | + | local ok, data = pcall(function() return json.decode(buffer) end) |
| 196 | + | if not ok then |
| 197 | + | print("Erro ao decodificar JSON: " .. data) |
| 198 | + | return |
| 199 | + | end |
| 200 | + | |
| 201 | + | local soundFile = data.data |
| 202 | + | local fullPath = SOUNDS_CONFIG.effectSoundFolder .. soundFile |
| 203 | + | if g_resources.fileExists(fullPath) then |
| 204 | + | playEffectSound(fullPath) |
| 205 | + | else |
| 206 | + | print("Som n�o encontrado: " .. fullPath) |
| 207 | + | end |
| 208 | + | end |
| 209 | + | |
| 210 | + | --[[ |
| 211 | + | Toca um efeito sonoro e reduz momentaneamente o volume da m�sica de �rea |
| 212 | + | para destacar o efeito. |
| 213 | + | --]] |
| 214 | + | function playEffectSound(soundPath) |
| 215 | + | if not effectChannel then |
| 216 | + | print("Canal de efeito n�o dispon�vel!") |
| 217 | + | return |
| 218 | + | end |
| 219 | + | |
| 220 | + | -- Se a m�sica de �rea est� tocando, diminui volume |
| 221 | + | if playingAreaSound and areaChannel then |
| 222 | + | areaChannel:setGain(0.1) -- Ajuste esse valor se quiser mais baixo durante o efeito |
| 223 | + | end |
| 224 | + | |
| 225 | + | effectChannel:play(soundPath, 0, 1.0) |
| 226 | + | effectChannel:setGain(1.0) |
| 227 | + | |
| 228 | + | -- Precisamos descobrir a dura��o do efeito para restaurar o volume depois |
| 229 | + | local duration = getSoundDuration(soundPath) or 3000 |
| 230 | + | scheduleEvent(function() |
| 231 | + | -- Restaura volume da m�sica de �rea |
| 232 | + | if playingAreaSound and areaChannel then |
| 233 | + | areaChannel:setGain(0.1) -- volta ao volume anterior |
| 234 | + | end |
| 235 | + | end, duration) |
| 236 | + | end |
| 237 | + | |
| 238 | + | --[[ |
| 239 | + | Se n�o tem leitura de metadados, definimos 3 segundos como padr�o |
| 240 | + | --]] |
| 241 | + | function getSoundDuration(soundPath) |
| 242 | + | return 3000 |
| 243 | + | end |
| 244 | + | |
| 245 | + | --[[ |
| 246 | + | Atualiza o texto na janela "rcsound" com o nome do som atual |
| 247 | + | --]] |
| 248 | + | function setLabel(str) |
| 249 | + | soundWindow:recursiveGetChildById('currentSound'):getChildById('value'):setText(str) |
| 250 | + | end |