--[[ Skate Mod - Client Side Convertido de CLEO (skateboard.cs) para MTA:SA Lua Comportamento fiel ao original: - Substitui modelo do veículo ID 74 por sktbd.dff/.txd - Substitui modelo da arma (slot 15 / model 326) por gun_cane.dff/.txd - Carrega animações choppa.ifp - Ao pressionar ENTER (key 4 no CLEO = Enter/Num5) segurando a arma skate → pede ao server spawnar skate - Toca skate.mp3 em loop, volume baseado em distância da câmera + velocidade do veículo - Esconde arma enquanto no skate - Ao sair do veículo → notifica server, para o som --]] local CONFIG = { weaponType = 15, weaponModel = 326, vehicleID = 74, volume = 0.3, } local localPlayer = getLocalPlayer() local skateVehicle = nil local skateSound = nil local inSkate = false local modelsReplaced = false local animsLoaded = false local waitingForSpawn = false local checkTick = 0 -- ============================================================ -- SUBSTITUIÇÃO DE MODELOS (equivalente ao .dff/.txd do CLEO) -- ============================================================ local function replaceModels() if modelsReplaced then return end -- Substitui modelo do veículo ID 74 (BMX → Skateboard) local txd_veh = engineLoadTXD("models/sktbd.txd") if txd_veh then engineImportTXD(txd_veh, CONFIG.vehicleID) else outputDebugString("[SkateMod] ERRO: não foi possível carregar sktbd.txd") end local dff_veh = engineLoadDFF("models/sktbd.dff") if dff_veh then engineReplaceModel(dff_veh, CONFIG.vehicleID) else outputDebugString("[SkateMod] ERRO: não foi possível carregar sktbd.dff") end -- Substitui modelo da arma (model 326 = GUN_CANE → modelo do skate como item) local txd_wpn = engineLoadTXD("models/gun_cane.txd") if txd_wpn then engineImportTXD(txd_wpn, CONFIG.weaponModel) else outputDebugString("[SkateMod] ERRO: não foi possível carregar gun_cane.txd") end local dff_wpn = engineLoadDFF("models/gun_cane.dff") if dff_wpn then engineReplaceModel(dff_wpn, CONFIG.weaponModel) else outputDebugString("[SkateMod] ERRO: não foi possível carregar gun_cane.dff") end modelsReplaced = true outputDebugString("[SkateMod] Modelos substituídos com sucesso.") end -- ============================================================ -- CARREGAMENTO DE ANIMAÇÕES IFP -- (equivalente ao CHOPPA.ifp do CLEO - animação de corrida no skate) -- ============================================================ local function loadAnimations() if animsLoaded then return end -- engineLoadIFP retorna um element de animação customizada local ifp = engineLoadIFP("models/choppa.ifp", "CHOPPA") if ifp then animsLoaded = true outputDebugString("[SkateMod] Animações CHOPPA carregadas.") else outputDebugString("[SkateMod] AVISO: não foi possível carregar choppa.ifp.") end end -- ============================================================ -- SOM (equivalente ao Audiostream do CLEO) -- ============================================================ local function startSkateSound() if skateSound then stopSound(skateSound) skateSound = nil end skateSound = playSound("skate.mp3", true) -- true = loop if skateSound then setSoundVolume(skateSound, 0) outputDebugString("[SkateMod] Som iniciado.") end end local function stopSkateSound() if skateSound then stopSound(skateSound) skateSound = nil end end -- Atualiza volume do som baseado em distância da câmera e velocidade -- (fiel ao algoritmo do CLEO original) local function updateSoundVolume() if not skateSound or not skateVehicle then return end if not isElement(skateVehicle) then return end local vx, vy, vz = getElementPosition(skateVehicle) local cx, cy, cz = getCameraMatrix() -- Distância entre câmera e skate local dist = getDistanceBetweenPoints3D(vx, vy, vz, cx, cy, cz) dist = dist / 2.0 if dist < 1.0 then dist = 1.0 end local volFactor = 1.0 / dist -- Velocidade do veículo (normalizada 0~1, máx 20 u/s) local speed = getElementSpeed(skateVehicle, "km/h") / 72.0 -- 72 km/h ≈ 20 u/s if speed > 1.0 then speed = 1.0 end -- Volume final = fator_distância × velocidade × volume_config local finalVol = volFactor * speed * CONFIG.volume if finalVol > 1.0 then finalVol = 1.0 end -- Só toca se não estiver no ar e estiver se movendo local onGround = not getVehicleNitroLevel and true if isVehicleOnGround ~= nil then -- função disponível em algumas versões onGround = true end setSoundVolume(skateSound, finalVol) end -- Helper: velocidade do elemento em km/h ou m/s function getElementSpeed(element, unit) local vx, vy, vz = getElementVelocity(element) local speed = math.sqrt(vx*vx + vy*vy + vz*vz) * 50 -- converter p/ km/h approx if unit == "km/h" then return speed end return speed / 3.6 end -- ============================================================ -- ANIMAÇÃO NO SKATE (equivalente ao CHOPPA_sprint) -- ============================================================ local function applySkateAnimation() -- Aplica animação "CHOPPA_sprint" do IFP CHOPPA (corrida no skate) setPedAnimation(localPlayer, "CHOPPA", "CHOPPA_sprint", -1, true, false, false, false) end -- ============================================================ -- INICIAR SKATE (ao receber confirmação do server) -- ============================================================ addEvent("skatemod:skateStarted", true) -- true = remotely triggerable (server pode disparar) addEventHandler("skatemod:skateStarted", root, function(skate) skateVehicle = skate inSkate = true waitingForSpawn = false -- Esconde armas e remove arma ativa (como no CLEO) -- MTA não tem setPedWeaponVisible direto, mas podemos forçar slot 0 setPedWeaponSlot(localPlayer, 0) -- Inicia o som startSkateSound() -- Aplica animação setTimer(function() if inSkate and isElement(localPlayer) then applySkateAnimation() end end, 200, 1) outputChatBox("[Skate] Andando de skate! Saia do veículo para parar.", 255, 200, 0) outputDebugString("[SkateMod] Skate iniciado no client.") end) -- ============================================================ -- SAIR DO SKATE -- ============================================================ local function exitSkate() if not inSkate then return end inSkate = false stopSkateSound() -- Para animação setPedAnimation(localPlayer, false) -- Notifica server para destruir o veículo e devolver a arma triggerServerEvent("skatemod:exitSkate", localPlayer) skateVehicle = nil outputDebugString("[SkateMod] Saiu do skate.") end -- ============================================================ -- DETECÇÃO DE INPUT - ENTER (key 4 do CLEO = Enter / Num Enter) -- "00E1: player 0 pressed_key 4" no CLEO = tecla ENTER -- No MTA, mapeamos para "vehicle_enter_exit" ou tecla direta -- ============================================================ local function onKeyPress(key, press) if not press then return end -- Tecla ENTER: spawn do skate (se não estiver em veículo e tiver arma skate) if key == "num_5" or key == "return" then if inSkate then return end if waitingForSpawn then return end -- Verifica se está a pé if isPedInVehicle(localPlayer) then return end -- Verifica se está segurando a arma skate (slot 15 / weapon type 15) local currentWeapon = getPedWeapon(localPlayer) if currentWeapon ~= CONFIG.weaponType then -- Tenta verificar se tem a arma no inventário local ammo = getPedAmmo(localPlayer, CONFIG.weaponType) if ammo <= 0 then outputChatBox("[Skate] Você precisa ter o skateboard no inventário!", 255, 100, 100) return end -- Força selecionar a arma skate setPedWeaponSlot(localPlayer, getPedWeaponSlot(localPlayer)) return end waitingForSpawn = true triggerServerEvent("skatemod:spawnSkate", localPlayer) outputDebugString("[SkateMod] Solicitando spawn do skate ao server...") end end addEventHandler("onClientKey", root, onKeyPress) -- ============================================================ -- DETECÇÃO DE SAÍDA DO VEÍCULO -- ============================================================ addEventHandler("onClientVehicleExit", root, function(vehicle, seat) if source == localPlayer then if inSkate and vehicle == skateVehicle then exitSkate() end end end) -- ============================================================ -- LOOP PRINCIPAL (equivalente ao "while true / wait 0" do CLEO) -- ============================================================ addEventHandler("onClientRender", root, function() local tick = getTickCount() if tick - checkTick < 100 then return end -- roda a ~10fps para economizar checkTick = tick -- Atualiza volume do som if inSkate then updateSoundVolume() -- Verifica se o veículo ainda existe if not skateVehicle or not isElement(skateVehicle) then exitSkate() return end -- Verifica se player ainda está no skate if not isPedInVehicle(localPlayer) then exitSkate() return end -- Impede uso de armas dentro do skate (como no CLEO) local currentWeapon = getPedWeapon(localPlayer) if currentWeapon ~= 0 and currentWeapon ~= CONFIG.weaponType then setPedWeaponSlot(localPlayer, 0) end end end) -- ============================================================ -- INICIALIZAÇÃO -- ============================================================ addEventHandler("onClientResourceStart", resourceRoot, function() -- Solicita config do server (não obrigatório, já hardcoded, mas bom para expansão) triggerServerEvent("skatemod:requestConfig", localPlayer) -- Substitui modelos replaceModels() -- Carrega animações loadAnimations() outputChatBox("[Skate Mod] Carregado! Pegue um skateboard no mapa e pressione ENTER para usar.", 100, 255, 100) outputDebugString("[SkateMod] Resource client iniciado.") end) -- Recebe config do server (opcional, para config dinâmica futura) addEvent("skatemod:receiveConfig", true) -- true = remotely triggerable (server pode disparar) addEventHandler("skatemod:receiveConfig", root, function(cfg) if cfg then CONFIG.weaponType = cfg.weaponType or CONFIG.weaponType CONFIG.weaponModel = cfg.weaponModel or CONFIG.weaponModel CONFIG.vehicleID = cfg.vehicleID or CONFIG.vehicleID CONFIG.volume = cfg.volume or CONFIG.volume outputDebugString("[SkateMod] Config recebida do server.") end end) addEventHandler("onClientResourceStop", resourceRoot, function() stopSkateSound() setPedAnimation(localPlayer, false) end)