This commit is contained in:
Nordi98 2025-07-26 08:56:55 +02:00
parent 2fd4906414
commit 9d4f625a84
962 changed files with 36 additions and 36 deletions

View file

@ -0,0 +1,434 @@
-- 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)

View file

@ -0,0 +1,19 @@
Config = {}
Config.ShootingRanges = {
{
coords = vector3(434.6526, -976.7142, 35.9675),
label = "LSPD Schießstand"
},
{
coords = vector3(17.9420, -1100.7803, 29.7972),
label = "Ammunation Schießstand"
},
{
coords = vector3(542.0584, 6.6291, 76.6281),
label = "Acedemy Schießstand"
}
}
Config.MaxHighscores = 10
Config.MaxTrainingParticipants = 4

View file

@ -0,0 +1,33 @@
fx_version 'cerulean'
game 'gta5'
description 'QB-Shootingrange mit Bestenliste, Timer und Wettkampf-Modus'
author 'DeinDevName'
shared_scripts {
'config.lua'
}
client_scripts {
'config.lua',
'client.lua'
}
server_scripts {
'config.lua',
'server.lua'
}
files {
'html/index.html',
'html/start.ogg',
'html/timeout.ogg'
}
ui_page 'html/index.html'

View file

@ -0,0 +1,49 @@
[
{
"timestamp": "02.06.2025 - 16:15",
"name": "kim",
"score": 460
},
{
"timestamp": "24.06.2025 - 04:09",
"name": "Unbekannt",
"score": 370
},
{
"timestamp": "24.06.2025 - 04:10",
"name": "Unbekannt",
"score": 360
},
{
"timestamp": "23.05.2025 - 08:25",
"name": "Unbekannt",
"score": 330
},
{
"score": 210,
"name": "kim"
},
{
"score": 160,
"name": "kim"
},
{
"timestamp": "23.05.2025 - 08:40",
"name": "kim",
"score": 130
},
{
"timestamp": "23.05.2025 - 08:37",
"name": "kim",
"score": 110
},
{
"timestamp": "23.05.2025 - 08:37",
"name": "kim",
"score": 90
},
{
"score": 0,
"name": "kim"
}
]

View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener('message', function (event) {
if (event.data.action === 'play') {
let audio = new Audio(`./${event.data.sound}.ogg`);
audio.play();
}
});
</script>
</head>
<body></body>
</html>

View file

@ -0,0 +1,110 @@
local QBCore = exports['qb-core']:GetCoreObject()
local activeCompetitions = {}
QBCore.Functions.CreateCallback('qb-shootingrange:getHighscores', function(source, cb)
local scores = json.decode(LoadResourceFile(GetCurrentResourceName(), "highscores.json") or "[]")
cb(scores)
end)
RegisterNetEvent('qb-shootingrange:saveScore', function(name, score)
local scores = json.decode(LoadResourceFile(GetCurrentResourceName(), "highscores.json") or "[]")
local timestamp = os.date("%d.%m.%Y - %H:%M")
table.insert(scores, {
name = name,
score = score,
timestamp = timestamp
})
table.sort(scores, function(a, b)
return tonumber(a.score) > tonumber(b.score)
end)
while #scores > Config.MaxHighscores do
table.remove(scores, #scores)
end
SaveResourceFile(GetCurrentResourceName(), "highscores.json", json.encode(scores, {indent = true}), -1)
end)
RegisterNetEvent("qb-shootingrange:sendInvite")
AddEventHandler("qb-shootingrange:sendInvite", function(targetId)
local src = source
local inviterName = GetPlayerName(src)
if not activeCompetitions[src] then
activeCompetitions[src] = {
host = src,
players = {},
scores = {}
}
end
TriggerClientEvent("qb-shootingrange:receiveInvite", targetId, inviterName, src)
end)
RegisterNetEvent("qb-shootingrange:acceptInvite")
AddEventHandler("qb-shootingrange:acceptInvite", function(inviterId)
local src = source
local playerName = GetPlayerName(src)
if activeCompetitions[inviterId] then
activeCompetitions[inviterId].players[src] = true
activeCompetitions[inviterId].scores[playerName] = 0
TriggerClientEvent("qb-shootingrange:playerJoinedCompetition", inviterId, playerName)
end
end)
RegisterNetEvent("qb-shootingrange:declineInvite")
AddEventHandler("qb-shootingrange:declineInvite", function(inviterId)
local src = source
local playerName = GetPlayerName(src)
TriggerClientEvent("qb-shootingrange:inviteDeclined", inviterId, playerName)
end)
RegisterNetEvent("qb-shootingrange:startCompetition")
AddEventHandler("qb-shootingrange:startCompetition", function()
local src = source
if activeCompetitions[src] then
for playerId, _ in pairs(activeCompetitions[src].players) do
TriggerClientEvent("qb-shootingrange:competitionStarted", playerId)
end
TriggerClientEvent("qb-shootingrange:competitionStarted", src)
end
end)
RegisterNetEvent("qb-shootingrange:cancelCompetition")
AddEventHandler("qb-shootingrange:cancelCompetition", function()
local src = source
if activeCompetitions[src] then
for playerId, _ in pairs(activeCompetitions[src].players) do
TriggerClientEvent("qb-shootingrange:competitionCancelled", playerId)
end
activeCompetitions[src] = nil
end
end)
RegisterNetEvent("qb-shootingrange:updateCompetitionScore")
AddEventHandler("qb-shootingrange:updateCompetitionScore", function(score)
local src = source
local playerName = GetPlayerName(src)
for _, competition in pairs(activeCompetitions) do
if competition.players[src] or competition.host == src then
competition.scores[playerName] = score
for playerId, _ in pairs(competition.players) do
TriggerClientEvent("qb-shootingrange:updateCompetition", playerId, competition.scores)
end
TriggerClientEvent("qb-shootingrange:updateCompetition", competition.host, competition.scores)
break
end
end
end)