434 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			434 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| -- qb-shootingrange client.lua
 | |
| local QBCore = exports['qb-core']:GetCoreObject()
 | |
| 
 | |
| -- Variablen
 | |
| local menuActive = false
 | |
| local menuIndex = 1
 | |
| local shooting = false
 | |
| local score = 0
 | |
| local targets = {}
 | |
| local timerEnabled = false
 | |
| local timeLeft = 0
 | |
| local isTrainer = false
 | |
| local currentRange = nil
 | |
| local isInCompetition = false
 | |
| local competitionHost = false
 | |
| 
 | |
| -- Gültige Target Models
 | |
| local validTargetModels = {
 | |
|     `prop_target_backboard_b`,
 | |
|     `gr_prop_gr_target_05c`,
 | |
|     `gr_prop_gr_target_04c`
 | |
| }
 | |
| 
 | |
| -- Menüoptionen
 | |
| local menuOptions = {
 | |
|     "⏱️ Starte Schießstand mit Timer",
 | |
|     "🔫 Starte Schießstand ohne Timer",
 | |
|     "🏁 Starte Wettkampfmodus",
 | |
|     "👥 Trainingsmodus",
 | |
|     "🛑 Beende Schießstand",
 | |
|     "📋 Bestenliste"
 | |
| }
 | |
| 
 | |
| -- Hilfsfunktionen
 | |
| function DrawTxt(text, x, y, scale)
 | |
|     SetTextFont(4)
 | |
|     SetTextProportional(0)
 | |
|     SetTextScale(scale, scale)
 | |
|     SetTextColour(255, 255, 255, 255)
 | |
|     SetTextDropShadow()
 | |
|     SetTextCentre(true)
 | |
|     SetTextEntry("STRING")
 | |
|     AddTextComponentString(text)
 | |
|     DrawText(x, y)
 | |
| end
 | |
| 
 | |
| function PlaySound(name)
 | |
|     SendNUIMessage({ action = "play", sound = name })
 | |
| end
 | |
| 
 | |
| function GetNearbyPlayers()
 | |
|     local players = {}
 | |
|     local playerPed = PlayerPedId()
 | |
|     local playerCoords = GetEntityCoords(playerPed)
 | |
|     
 | |
|     for _, player in ipairs(GetActivePlayers()) do
 | |
|         if player ~= PlayerId() then
 | |
|             local targetPed = GetPlayerPed(player)
 | |
|             local targetCoords = GetEntityCoords(targetPed)
 | |
|             local distance = #(playerCoords - targetCoords)
 | |
|             
 | |
|             if distance <= 20.0 then
 | |
|                 local playerName = GetPlayerName(player)
 | |
|                 local serverId = GetPlayerServerId(player)
 | |
|                 table.insert(players, {
 | |
|                     label = playerName,
 | |
|                     serverId = serverId,
 | |
|                     distance = math.floor(distance)
 | |
|                 })
 | |
|             end
 | |
|         end
 | |
|     end
 | |
|     return players
 | |
| end
 | |
| 
 | |
| function ShowPlayerSelectionMenu()
 | |
|     local nearbyPlayers = GetNearbyPlayers()
 | |
|     
 | |
|     if #nearbyPlayers == 0 then
 | |
|         QBCore.Functions.Notify("Keine Spieler in der Nähe!", "error")
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     local elements = {
 | |
|         {
 | |
|             header = "Spieler zum Wettkampf einladen",
 | |
|             isMenuHeader = true
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     for _, player in ipairs(nearbyPlayers) do
 | |
|         table.insert(elements, {
 | |
|             header = player.label,
 | |
|             txt = string.format("Entfernung: %dm", player.distance),
 | |
|             params = {
 | |
|                 event = "qb-shootingrange:invitePlayer",
 | |
|                 args = {
 | |
|                     playerId = player.serverId
 | |
|                 }
 | |
|             }
 | |
|         })
 | |
|     end
 | |
| 
 | |
|     exports['qb-menu']:openMenu(elements)
 | |
| end
 | |
| 
 | |
| function ShowStartCompetitionMenu()
 | |
|     local elements = {
 | |
|         {
 | |
|             header = "Wettkampf Steuerung",
 | |
|             isMenuHeader = true
 | |
|         },
 | |
|         {
 | |
|             header = "🏁 Wettkampf starten",
 | |
|             txt = "Startet den Wettkampf für alle Teilnehmer",
 | |
|             params = {
 | |
|                 event = "qb-shootingrange:hostStartCompetition",
 | |
|                 args = {}
 | |
|             }
 | |
|         },
 | |
|         {
 | |
|             header = "❌ Abbrechen",
 | |
|             txt = "Bricht den Wettkampf ab",
 | |
|             params = {
 | |
|                 event = "qb-shootingrange:cancelCompetition",
 | |
|                 args = {}
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     exports['qb-menu']:openMenu(elements)
 | |
| end
 | |
| 
 | |
| function FindNearbyTargets()
 | |
|     local foundTargets = {}
 | |
|     local playerCoords = GetEntityCoords(PlayerPedId())
 | |
|     local radius = 200.0
 | |
| 
 | |
|     for _, model in ipairs(validTargetModels) do
 | |
|         local handle, object = FindFirstObject()
 | |
|         local success
 | |
|         repeat
 | |
|             if IsEntityAnObject(object) and GetEntityModel(object) == model then
 | |
|                 local objCoords = GetEntityCoords(object)
 | |
|                 if #(playerCoords - objCoords) < radius then
 | |
|                     table.insert(foundTargets, object)
 | |
|                 end
 | |
|             end
 | |
|             success, object = FindNextObject(handle)
 | |
|         until not success
 | |
|         EndFindObject(handle)
 | |
|     end
 | |
|     return foundTargets
 | |
| end
 | |
| 
 | |
| function PromptPlayerName()
 | |
|     AddTextEntry('SHOOTING_NAME', 'Gib deinen Namen ein:')
 | |
|     DisplayOnscreenKeyboard(1, "SHOOTING_NAME", "", "", "", "", "", 20)
 | |
|     while UpdateOnscreenKeyboard() ~= 1 and UpdateOnscreenKeyboard() ~= 2 do Wait(0) end
 | |
|     if UpdateOnscreenKeyboard() ~= 2 then
 | |
|         return GetOnscreenKeyboardResult()
 | |
|     end
 | |
|     return "Unbekannt"
 | |
| end
 | |
| 
 | |
| function DrawMenu()
 | |
|     DrawTxt("~b~" .. currentRange.label .. "~s~", 0.5, 0.3, 0.6)
 | |
|     
 | |
|     for i, option in ipairs(menuOptions) do
 | |
|         local color = i == menuIndex and "~y~" or "~w~"
 | |
|         DrawTxt(color .. option, 0.5, 0.35 + (i * 0.05), 0.4)
 | |
|     end
 | |
|     
 | |
|     DrawTxt("~c~↑/↓ Auswählen  |  ENTER bestätigen  |  BACKSPACE zurück", 0.5, 0.7, 0.3)
 | |
| end
 | |
| 
 | |
| function StartShooting(useTimer, isComp)
 | |
|     if shooting then return end
 | |
| 
 | |
|     if isComp then
 | |
|         competitionHost = true
 | |
|         ShowPlayerSelectionMenu()
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     targets = FindNearbyTargets()
 | |
|     if #targets == 0 then
 | |
|         QBCore.Functions.Notify("Keine Ziele im Umkreis gefunden!", "error")
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     shooting = true
 | |
|     score = 0
 | |
|     timerEnabled = useTimer
 | |
|     timeLeft = useTimer and 30 or 0
 | |
|     PlaySound("start")
 | |
|     QBCore.Functions.Notify("Schießstand gestartet!", "success")
 | |
| 
 | |
|     if timerEnabled then
 | |
|         CreateThread(function()
 | |
|             while timeLeft > 0 and shooting do
 | |
|                 Wait(1000)
 | |
|                 timeLeft = timeLeft - 1
 | |
|             end
 | |
|             if shooting then StopShooting() end
 | |
|         end)
 | |
|     end
 | |
| 
 | |
|     CreateThread(function()
 | |
|         while shooting do
 | |
|             Wait(0)
 | |
|             local hit, endCoords = GetPedLastWeaponImpactCoord(PlayerPedId())
 | |
|             if hit then
 | |
|                 for _, target in ipairs(targets) do
 | |
|                     if DoesEntityExist(target) then
 | |
|                         local targetCoords = GetEntityCoords(target)
 | |
|                         if #(endCoords - targetCoords) < 1.2 then
 | |
|                             score = score + 10
 | |
|                             QBCore.Functions.Notify("🎯 Treffer! Punkte: " .. score, "success")
 | |
|                             if isInCompetition then
 | |
|                                 TriggerServerEvent("qb-shootingrange:updateCompetitionScore", score)
 | |
|                             end
 | |
|                             Wait(300)
 | |
|                             break
 | |
|                         end
 | |
|                     end
 | |
|                 end
 | |
|             end
 | |
|         end
 | |
|     end)
 | |
| 
 | |
|     CreateThread(function()
 | |
|         while shooting do
 | |
|             Wait(0)
 | |
|             DrawTxt("Punkte: " .. score, 0.5, 0.05, 0.5)
 | |
|             if timerEnabled then
 | |
|                 DrawTxt("Zeit: " .. timeLeft .. "s", 0.5, 0.09, 0.4)
 | |
|             end
 | |
|         end
 | |
|     end)
 | |
| end
 | |
| 
 | |
| function StopShooting()
 | |
|     if not shooting then return end
 | |
|     shooting = false
 | |
|     targets = {}
 | |
|     QBCore.Functions.Notify("Beendet! Gesamtpunkte: " .. score, "success")
 | |
|     PlaySound("timeout")
 | |
| 
 | |
|     if not isInCompetition then
 | |
|         local name = PromptPlayerName()
 | |
|         TriggerServerEvent("qb-shootingrange:saveScore", name, score)
 | |
|     end
 | |
|     
 | |
|     isInCompetition = false
 | |
|     competitionHost = false
 | |
| end
 | |
| 
 | |
| function ShowHighscores()
 | |
|     QBCore.Functions.TriggerCallback('qb-shootingrange:getHighscores', function(scores)
 | |
|         if scores and #scores > 0 then
 | |
|             local text = "~y~🏆 Bestenliste~s~\n\n"
 | |
|             for i, entry in ipairs(scores) do
 | |
|                 text = text .. string.format("%d. %s - %d Punkte\n~c~%s~s~\n\n", 
 | |
|                     i, 
 | |
|                     entry.name, 
 | |
|                     entry.score,
 | |
|                     entry.timestamp
 | |
|                 )
 | |
|             end
 | |
|             
 | |
|             SetNotificationTextEntry("STRING")
 | |
|             AddTextComponentString(text)
 | |
|             DrawNotification(false, true)
 | |
|         else
 | |
|             QBCore.Functions.Notify("Keine Einträge in der Bestenliste.", "error")
 | |
|         end
 | |
|     end)
 | |
| end
 | |
| 
 | |
| -- Event Handler
 | |
| RegisterNetEvent("qb-shootingrange:invitePlayer")
 | |
| AddEventHandler("qb-shootingrange:invitePlayer", function(data)
 | |
|     TriggerServerEvent("qb-shootingrange:sendInvite", data.playerId)
 | |
|     QBCore.Functions.Notify("Einladung gesendet!", "success")
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent("qb-shootingrange:receiveInvite")
 | |
| AddEventHandler("qb-shootingrange:receiveInvite", function(inviterName, inviterId)
 | |
|     local elements = {
 | |
|         {
 | |
|             header = "Schießstand Einladung",
 | |
|             txt = string.format("Von: %s", inviterName),
 | |
|             isMenuHeader = true
 | |
|         },
 | |
|         {
 | |
|             header = "✅ Annehmen",
 | |
|             txt = "Der Einladung folgen",
 | |
|             params = {
 | |
|                 event = "qb-shootingrange:acceptInvite",
 | |
|                 args = {
 | |
|                     inviterId = inviterId
 | |
|                 }
 | |
|             }
 | |
|         },
 | |
|         {
 | |
|             header = "❌ Ablehnen",
 | |
|             txt = "Einladung ablehnen",
 | |
|             params = {
 | |
|                 event = "qb-shootingrange:declineInvite",
 | |
|                 args = {
 | |
|                     inviterId = inviterId
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     exports['qb-menu']:openMenu(elements)
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent("qb-shootingrange:acceptInvite")
 | |
| AddEventHandler("qb-shootingrange:acceptInvite", function(data)
 | |
|     TriggerServerEvent("qb-shootingrange:acceptInvite", data.inviterId)
 | |
|     isInCompetition = true
 | |
|     QBCore.Functions.Notify("Du hast die Einladung angenommen!", "success")
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent("qb-shootingrange:declineInvite")
 | |
| AddEventHandler("qb-shootingrange:declineInvite", function(data)
 | |
|     TriggerServerEvent("qb-shootingrange:declineInvite", data.inviterId)
 | |
|     QBCore.Functions.Notify("Du hast die Einladung abgelehnt.", "error")
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent("qb-shootingrange:hostStartCompetition")
 | |
| AddEventHandler("qb-shootingrange:hostStartCompetition", function()
 | |
|     TriggerServerEvent("qb-shootingrange:startCompetition")
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent("qb-shootingrange:cancelCompetition")
 | |
| AddEventHandler("qb-shootingrange:cancelCompetition", function()
 | |
|     TriggerServerEvent("qb-shootingrange:cancelCompetition")
 | |
|     isInCompetition = false
 | |
|     competitionHost = false
 | |
|     QBCore.Functions.Notify("Wettkampf abgebrochen", "error")
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent("qb-shootingrange:competitionStarted")
 | |
| AddEventHandler("qb-shootingrange:competitionStarted", function()
 | |
|     StartShooting(true, false)
 | |
|     QBCore.Functions.Notify("Der Wettkampf beginnt!", "success")
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent("qb-shootingrange:playerJoinedCompetition")
 | |
| AddEventHandler("qb-shootingrange:playerJoinedCompetition", function(playerName)
 | |
|     QBCore.Functions.Notify(playerName .. " ist dem Wettkampf beigetreten!", "success")
 | |
|     if competitionHost then
 | |
|         ShowStartCompetitionMenu()
 | |
|     end
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent("qb-shootingrange:updateCompetition")
 | |
| AddEventHandler("qb-shootingrange:updateCompetition", function(scores)
 | |
|     if not isInCompetition then return end
 | |
|     
 | |
|     local scoreText = "🏁 Wettkampf Punktestand:\n"
 | |
|     for name, playerScore in pairs(scores) do
 | |
|         scoreText = scoreText .. string.format("%s: %d\n", name, playerScore)
 | |
|     end
 | |
|     
 | |
|     DrawTxt(scoreText, 0.5, 0.15, 0.4)
 | |
| end)
 | |
| 
 | |
| -- Hauptthread
 | |
| CreateThread(function()
 | |
|     while true do
 | |
|         Wait(0)
 | |
|         local playerPed = PlayerPedId()
 | |
|         local playerCoords = GetEntityCoords(playerPed)
 | |
|         local closestRange = nil
 | |
|         local closestDist = 1000
 | |
| 
 | |
|         for _, range in pairs(Config.ShootingRanges) do
 | |
|             local dist = #(playerCoords - range.coords)
 | |
|             if dist < closestDist then
 | |
|                 closestDist = dist
 | |
|                 closestRange = range
 | |
|             end
 | |
|         end
 | |
| 
 | |
|         if closestRange and closestDist < 2.0 then
 | |
|             currentRange = closestRange
 | |
|             if not menuActive then
 | |
|                 DrawTxt("Drücke ~g~E~s~ für den " .. closestRange.label, 0.5, 0.9, 0.4)
 | |
|                 if IsControlJustPressed(0, 38) then -- E
 | |
|                     menuActive = true
 | |
|                     menuIndex = 1
 | |
|                 end
 | |
|             else
 | |
|                 DrawMenu()
 | |
|                 
 | |
|                 if IsControlJustPressed(0, 172) then -- Hoch
 | |
|                     menuIndex = menuIndex - 1
 | |
|                     if menuIndex < 1 then menuIndex = #menuOptions end
 | |
|                     
 | |
|                 elseif IsControlJustPressed(0, 173) then -- Runter
 | |
|                     menuIndex = menuIndex + 1
 | |
|                     if menuIndex > #menuOptions then menuIndex = 1 end
 | |
|                     
 | |
|                 elseif IsControlJustPressed(0, 201) then -- Enter
 | |
|                     if menuIndex == 1 then
 | |
|                         StartShooting(true, false)
 | |
|                     elseif menuIndex == 2 then
 | |
|                         StartShooting(false, false)
 | |
|                     elseif menuIndex == 3 then
 | |
|                         StartShooting(true, true)
 | |
|                     elseif menuIndex == 4 then
 | |
|                         QBCore.Functions.Notify("Trainingsmodus wird bald verfügbar!", "primary")
 | |
|                     elseif menuIndex == 5 then
 | |
|                         StopShooting()
 | |
|                     elseif menuIndex == 6 then
 | |
|                         ShowHighscores()
 | |
|                     end
 | |
|                     menuActive = false
 | |
|                     
 | |
|                 elseif IsControlJustPressed(0, 202) then -- Backspace
 | |
|                     menuActive = false
 | |
|                 end
 | |
|             end
 | |
|         else
 | |
|             menuActive = false
 | |
|             currentRange = nil
 | |
|         end
 | |
|     end
 | |
| end)
 | 
