diff --git a/resources/[inventory]/nordi_alctester/client/client.lua b/resources/[inventory]/nordi_alctester/client/client.lua new file mode 100644 index 000000000..9511c7ead --- /dev/null +++ b/resources/[inventory]/nordi_alctester/client/client.lua @@ -0,0 +1,107 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local bac = nil +local display = false + +function SetDisplay(bool) + display = bool + SetNuiFocus(bool, bool) + SendNUIMessage({ + type = "ui", + status = bool, + }) +end + +RegisterNUICallback("exit", function(data) + SetDisplay(false) + SendNUIMessage({ + type = "data", + bac = '0.00', + textColor = '--color-black' + }) +end) + +RegisterNUICallback("startBac", function(data) + local target = GetClosestPlayerRadius(2.0) + if target == nil then + QBCore.Functions.Notify("Keine Person in der Nähe gefunden!", "error") + return + end + TriggerServerEvent('qb-alcoholtest:server:doBacTest', GetPlayerServerId(target)) + QBCore.Functions.Notify('Anfrage wurde an ' .. GetPlayerName(target) .. ' gesendet') +end) + +-- Kontrollen deaktivieren, wenn UI geöffnet ist +Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + if display then + DisableControlAction(0, 1, display) -- LookLeftRight + DisableControlAction(0, 2, display) -- LookUpDown + DisableControlAction(0, 142, display) -- MeleeAttackAlternate + DisableControlAction(0, 18, display) -- Enter + DisableControlAction(0, 322, display) -- ESC + DisableControlAction(0, 106, display) -- VehicleMouseControlOverride + else + Citizen.Wait(500) + end + end +end) + +-- Alkoholtester Item verwenden +RegisterNetEvent('qb-alcoholtest:client:useBreathalyzer') +AddEventHandler('qb-alcoholtest:client:useBreathalyzer', function() + SetDisplay(true) +end) + +-- BAC-Test Anfrage erhalten +RegisterNetEvent('qb-alcoholtest:client:requestBac') +AddEventHandler('qb-alcoholtest:client:requestBac', function(leo, target) + local accepted = nil + QBCore.Functions.Notify(GetPlayerName(GetPlayerFromServerId(leo)) .. " möchte deinen Alkoholwert testen.") + QBCore.Functions.Notify("Akzeptieren [Y] Ablehnen [N]") + + Citizen.CreateThread(function() + while accepted == nil do + Citizen.Wait(0) + if IsControlJustReleased(1, 246) then -- Y Taste + accepted = true + TriggerServerEvent('qb-alcoholtest:server:acceptedBac', leo, target) + + -- Tatsächlichen BAC-Wert vom System abrufen + TriggerServerEvent('qb-alcoholtest:server:getActualBAC', leo, target) + end + if IsControlJustReleased(1, 249) then -- N Taste + accepted = false + TriggerServerEvent('qb-alcoholtest:server:refusedBac', leo, target) + end + end + end) +end) + +-- BAC-Wert anzeigen +RegisterNetEvent('qb-alcoholtest:client:displayBac') +AddEventHandler('qb-alcoholtest:client:displayBac', function(bac, color) + SendNUIMessage({ + type = "data", + bac = bac, + textColor = color + }) +end) + +-- BAC-Test abgelehnt +RegisterNetEvent('qb-alcoholtest:client:bacRefused') +AddEventHandler('qb-alcoholtest:client:bacRefused', function(target) + SetDisplay(false) + SendNUIMessage({ + type = "data", + bac = '0.00', + textColor = '--color-black' + }) + QBCore.Functions.Notify(GetPlayerName(GetPlayerFromServerId(target)) .. " hat den Alkoholtest abgelehnt!", "error") +end) + +-- BAC-Test akzeptiert +RegisterNetEvent('qb-alcoholtest:client:acceptedBac') +AddEventHandler('qb-alcoholtest:client:acceptedBac', function(target) + QBCore.Functions.Notify("Teste " .. GetPlayerName(GetPlayerFromServerId(target)) .. "'s Blutalkoholwert...") +end) diff --git a/resources/[inventory]/nordi_alctester/client/client_functions.lua b/resources/[inventory]/nordi_alctester/client/client_functions.lua new file mode 100644 index 000000000..7d4b4e24f --- /dev/null +++ b/resources/[inventory]/nordi_alctester/client/client_functions.lua @@ -0,0 +1,35 @@ +function GetClosestPlayerRadius(radius) + local players = GetPlayers() + local closestDistance = -1 + local closestPlayer = -1 + local ply = PlayerPedId() + local plyCoords = GetEntityCoords(ply) + + for index,value in ipairs(players) do + local target = GetPlayerPed(value) + if(target ~= ply) then + local targetCoords = GetEntityCoords(GetPlayerPed(value), 0) + local distance = #(targetCoords - plyCoords) + if(closestDistance == -1 or closestDistance > distance) then + closestPlayer = value + closestDistance = distance + end + end + end + if closestDistance ~= -1 and closestDistance <= radius then + return closestPlayer + else + return nil + end +end + +function GetPlayers() + local players = {} + for _, player in ipairs(GetActivePlayers()) do + local ped = GetPlayerPed(player) + if DoesEntityExist(ped) then + table.insert(players, player) + end + end + return players +end diff --git a/resources/[inventory]/nordi_alctester/config.lua b/resources/[inventory]/nordi_alctester/config.lua new file mode 100644 index 000000000..e74ab6df9 --- /dev/null +++ b/resources/[inventory]/nordi_alctester/config.lua @@ -0,0 +1,37 @@ +Config = {} + +-- Debug-Modus (zeigt zusätzliche Informationen) +Config.Debug = false + +-- Wie schnell der Alkohol im Blut abgebaut wird (pro Minute) +Config.BACDecayRate = 0.01 + +-- Ab welchem BAC-Wert die Anzeige rot wird (gesetzliches Limit) +Config.LegalLimit = 0.08 + +-- Alkoholgehalt verschiedener Getränke (BAC-Erhöhung pro Drink) +Config.AlcoholItems = { + ['beer'] = 0.02, + ['whiskey'] = 0.04, + ['vodka'] = 0.04, + ['wine'] = 0.03, + ['raki'] = 0.05, + ['cerveza_barracho'] = 0.02, + ['kayas_rotwein'] = 0.03, + ['kadis_rostbite'] = 0.04, + ['ifs_olden_ectar'] = 0.03, + ['ggdrasils_ssence'] = 0.03, + ['baldurslightelixir'] = 0.03, + ['eimdalls_lear_ight'] = 0.03, + ['lokis_trickster_punch'] = 0.04, + ['odins_wisdom_brew'] = 0.03, + ['skadis_hunt'] = 0.03, + ['njords_tide'] = 0.03, + ['sifs_golden-ale'] = 0.02, + ['yggdrasils_root'] = 0.03, + ['baldurs_light'] = 0.03, + ['heimdalls_watch'] = 0.03, + ['freyas_kiss'] = 0.03, + ['thors_hammer'] = 0.04, + ['odins_mead'] = 0.03, +} diff --git a/resources/[inventory]/nordi_alctester/fxmanifest.lua b/resources/[inventory]/nordi_alctester/fxmanifest.lua new file mode 100644 index 000000000..e0f438c9f --- /dev/null +++ b/resources/[inventory]/nordi_alctester/fxmanifest.lua @@ -0,0 +1,33 @@ +fx_version 'cerulean' +game 'gta5' + +author 'JKSensation & Angepasst' +description 'QBCore Alkoholtester System' +version '1.0.0' + +shared_scripts { + '@qb-core/shared/locale.lua', + 'config.lua' +} + +client_scripts { + 'client/client.lua', + 'client/client_functions.lua' +} + +server_scripts { + '@oxmysql/lib/MySQL.lua', + 'server/server.lua' +} + +ui_page 'web/index.html' + +files { + 'web/index.html', + 'web/index.js', + 'web/styles.css', + 'web/portablebreathalyzer.png', + 'web/digital-7.ttf' +} + +lua54 'yes' diff --git a/resources/[inventory]/nordi_alctester/server/server.lua b/resources/[inventory]/nordi_alctester/server/server.lua new file mode 100644 index 000000000..50ccf0d47 --- /dev/null +++ b/resources/[inventory]/nordi_alctester/server/server.lua @@ -0,0 +1,205 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +-- BAC-Abbaurate (wie viel BAC pro Minute abnimmt) +local BAC_DECAY_RATE = Config.BACDecayRate + +-- Player BAC Cache um übermäßige Datenbankaufrufe zu vermeiden +local playerBAC = {} + +-- Funktion zum Aktualisieren des BAC-Levels eines Spielers +local function UpdatePlayerBAC(citizenid, addAmount) + -- Initialisieren, falls nicht vorhanden + if not playerBAC[citizenid] then + playerBAC[citizenid] = { + bac = 0.0, + lastUpdated = os.time() + } + end + + -- Zeitbasierten Abbau berechnen + local currentTime = os.time() + local minutesPassed = (currentTime - playerBAC[citizenid].lastUpdated) / 60 + + -- Abbau anwenden, aber nicht unter 0 + local decayAmount = BAC_DECAY_RATE * minutesPassed + local newBAC = math.max(0.0, playerBAC[citizenid].bac - decayAmount) + + -- Neuen Alkoholwert hinzufügen + newBAC = newBAC + addAmount + + -- Bei realistischem Maximum begrenzen (0.5% wäre tödlich) + newBAC = math.min(0.5, newBAC) + + -- Cache aktualisieren + playerBAC[citizenid] = { + bac = newBAC, + lastUpdated = currentTime + } + + -- Datenbank aktualisieren + MySQL.Async.execute('INSERT INTO player_bac (citizenid, bac_level) VALUES (?, ?) ON DUPLICATE KEY UPDATE bac_level = ?, last_updated = CURRENT_TIMESTAMP', + {citizenid, newBAC, newBAC}) +end + +-- Funktion zum Abrufen des aktuellen BAC-Levels eines Spielers mit Abbauberechnung +local function GetCurrentBAC(citizenid) + -- Wenn wir Cache-Daten haben + if playerBAC[citizenid] then + local currentTime = os.time() + local minutesPassed = (currentTime - playerBAC[citizenid].lastUpdated) / 60 + + -- Abbau anwenden, aber nicht unter 0 + local decayAmount = BAC_DECAY_RATE * minutesPassed + local currentBAC = math.max(0.0, playerBAC[citizenid].bac - decayAmount) + + -- Cache mit abgebautem Wert aktualisieren + playerBAC[citizenid] = { + bac = currentBAC, + lastUpdated = currentTime + } + + -- Datenbank mit neuem abgebautem Wert aktualisieren + MySQL.Async.execute('UPDATE player_bac SET bac_level = ?, last_updated = CURRENT_TIMESTAMP WHERE citizenid = ?', + {currentBAC, citizenid}) + + return currentBAC + else + -- Wenn nicht im Cache, aus Datenbank abrufen + local result = MySQL.Sync.fetchAll('SELECT bac_level, last_updated FROM player_bac WHERE citizenid = ?', {citizenid}) + + if result and result[1] then + local storedBAC = result[1].bac_level + local lastUpdated = result[1].last_updated + + -- MySQL-Zeitstempel in Unix-Zeitstempel umwandeln + local pattern = "(%d+)-(%d+)-(%d+) (%d+):(%d+):(%d+)" + local year, month, day, hour, min, sec = lastUpdated:match(pattern) + local lastUpdateTime = os.time({year=year, month=month, day=day, hour=hour, min=min, sec=sec}) + + local currentTime = os.time() + local minutesPassed = (currentTime - lastUpdateTime) / 60 + + -- Abbau anwenden, aber nicht unter 0 + local decayAmount = BAC_DECAY_RATE * minutesPassed + local currentBAC = math.max(0.0, storedBAC - decayAmount) + + -- Cache aktualisieren + playerBAC[citizenid] = { + bac = currentBAC, + lastUpdated = currentTime + } + + -- Datenbank mit neuem abgebautem Wert aktualisieren + MySQL.Async.execute('UPDATE player_bac SET bac_level = ?, last_updated = CURRENT_TIMESTAMP WHERE citizenid = ?', + {currentBAC, citizenid}) + + return currentBAC + else + -- Kein Eintrag gefunden, mit 0 initialisieren + playerBAC[citizenid] = { + bac = 0.0, + lastUpdated = os.time() + } + + return 0.0 + end + end +end + +-- Anbindung an das Konsumsystem +-- Dieses Event sollte ausgelöst werden, wenn ein Spieler ein alkoholisches Item konsumiert +RegisterNetEvent('qb-alcoholtest:server:alcoholConsumed') +AddEventHandler('qb-alcoholtest:server:alcoholConsumed', function(itemName, intensity) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + + if not Player then return end + + local citizenid = Player.PlayerData.citizenid + local alcoholContent = 0.0 + + -- Prüfen, ob das konsumierte Item alkoholisch ist + if Config.AlcoholItems[itemName] then + alcoholContent = Config.AlcoholItems[itemName] + + -- Basierend auf Intensität anpassen, falls angegeben + if intensity then + alcoholContent = alcoholContent * intensity + end + + -- BAC des Spielers aktualisieren + UpdatePlayerBAC(citizenid, alcoholContent) + + -- Spieler benachrichtigen + if Config.Debug then + TriggerClientEvent('QBCore:Notify', src, 'Du spürst die Wirkung des Alkohols.', 'primary') + end + end +end) + +-- Verwendbares Alkoholtester-Item erstellen +QBCore.Functions.CreateUseableItem('breathalyzer', function(source, item) + local src = source + TriggerClientEvent('qb-alcoholtest:client:useBreathalyzer', src) +end) + +-- Alkoholtester-Events +RegisterServerEvent('qb-alcoholtest:server:doBacTest') +AddEventHandler('qb-alcoholtest:server:doBacTest', function(target) + TriggerClientEvent('qb-alcoholtest:client:requestBac', target, source, target) +end) + +RegisterServerEvent('qb-alcoholtest:server:refusedBac') +AddEventHandler('qb-alcoholtest:server:refusedBac', function(leo, target) + TriggerClientEvent('qb-alcoholtest:client:bacRefused', leo, target) +end) + +RegisterServerEvent('qb-alcoholtest:server:acceptedBac') +AddEventHandler('qb-alcoholtest:server:acceptedBac', function(leo, target) + TriggerClientEvent('qb-alcoholtest:client:acceptedBac', leo, target) +end) + +-- Neues Event, um den tatsächlichen BAC-Wert aus dem System zu erhalten +RegisterServerEvent('qb-alcoholtest:server:getActualBAC') +AddEventHandler('qb-alcoholtest:server:getActualBAC', function(leo, target) + local targetPlayer = QBCore.Functions.GetPlayer(target) + + if not targetPlayer then return end + + local citizenid = targetPlayer.PlayerData.citizenid + local currentBAC = GetCurrentBAC(citizenid) + + -- BAC auf 2 Dezimalstellen formatieren + local formattedBAC = string.format("%.2f", currentBAC) + + -- Farbe basierend auf gesetzlichem Limit bestimmen + local color = '--color-black' + if currentBAC > Config.LegalLimit then + color = '--color-red' + end + + -- BAC-Ergebnis an den Beamten senden + TriggerClientEvent('qb-alcoholtest:client:displayBac', leo, formattedBAC, color) +end) + +-- Öffentliche Funktion für andere Ressourcen +exports('GetPlayerBAC', function(citizenid) + return GetCurrentBAC(citizenid) +end) + +-- Öffentliche Funktion zum Aktualisieren des BAC-Werts +exports('UpdatePlayerBAC', function(citizenid, amount) + UpdatePlayerBAC(citizenid, amount) +end) + +-- Sicherstellen, dass die Datenbanktabelle existiert, wenn die Ressource startet +MySQL.ready(function() + MySQL.Async.execute([[ + CREATE TABLE IF NOT EXISTS `player_bac` ( + `citizenid` varchar(50) NOT NULL, + `bac_level` float NOT NULL DEFAULT 0.0, + `last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`citizenid`) + ); + ]]) +end) diff --git a/resources/[inventory]/nordi_alctester/web/digital-7.ttf b/resources/[inventory]/nordi_alctester/web/digital-7.ttf new file mode 100644 index 000000000..5dbe6f908 Binary files /dev/null and b/resources/[inventory]/nordi_alctester/web/digital-7.ttf differ diff --git a/resources/[inventory]/nordi_alctester/web/index.html b/resources/[inventory]/nordi_alctester/web/index.html new file mode 100644 index 000000000..272593552 --- /dev/null +++ b/resources/[inventory]/nordi_alctester/web/index.html @@ -0,0 +1,25 @@ + + + + + + + + + + +
+
+
+
+

0.00

+
+
+
+ + +
+
+ + + \ No newline at end of file diff --git a/resources/[inventory]/nordi_alctester/web/index.js b/resources/[inventory]/nordi_alctester/web/index.js new file mode 100644 index 000000000..a04239fda --- /dev/null +++ b/resources/[inventory]/nordi_alctester/web/index.js @@ -0,0 +1,43 @@ +// [ Breathalyzer Script 0.1 Created By JKSensation ] // +// [ DO NOT RELEASE/LEAK/SHARE CODE WITHOUT PERMISSION FROM JKSENSATION ] // + +$(function () { + function display(bool) { + if (bool) { + $("#container").show(); + } else { + $("#container").hide(); + } + } + + display(false) + + window.addEventListener('message', function(event) { + var item = event.data; + if (item.type === "ui") { + if (item.status == true) { + display(true) + } else { + display(false) + } + }else if(item.type === 'data'){ + $('#bacLevel').text(item.bac) + $('#bacLevel').css("color", `var(${item.textColor})`) + } + }) + + document.onkeyup = function (data) { + if (data.which == 27) { + $.post('http://breathalyzer/exit', JSON.stringify({})); + return + } + }; + $("#power").click(function () { + $.post('http://breathalyzer/exit', JSON.stringify({})); + return + }) + $("#start").click(function () { + $.post('http://breathalyzer/startBac', JSON.stringify({})); + return + }) +}) \ No newline at end of file diff --git a/resources/[inventory]/nordi_alctester/web/portablebreathalyzer.png b/resources/[inventory]/nordi_alctester/web/portablebreathalyzer.png new file mode 100644 index 000000000..2f8537d24 Binary files /dev/null and b/resources/[inventory]/nordi_alctester/web/portablebreathalyzer.png differ diff --git a/resources/[inventory]/nordi_alctester/web/styles.css b/resources/[inventory]/nordi_alctester/web/styles.css new file mode 100644 index 000000000..0e649cb9f --- /dev/null +++ b/resources/[inventory]/nordi_alctester/web/styles.css @@ -0,0 +1,99 @@ +/* [ Breathalyzer Script 0.1 Created By JKSensation ] */ +/* [ DO NOT RELEASE/LEAK/SHARE CODE WITHOUT PERMISSION FROM JKSENSATION ] */ + +body { + background: none !important; +} + +:root{ + --color-red: #ca2f2f; +} + +@font-face { + font-family: digital; + src: url(digital-7.ttf); +} + +#container { + position: fixed; + top: 50%; + left: 75%; + transform: translate(-50%, -50%); + display: flex; + justify-content: center; + align-items: center; + height: 600px; + width: 350px; + background-size: cover; + flex-direction: column; +} + +.buttonContainer{ + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + margin-top: 45px; + /* background-color: #00804d; */ +} + +button{ + padding: 7px 0px; + width: 100px; + margin-bottom: 5px; + font-weight: 800; + font-size: 14px; + border: 2px solid #333; + border-radius: 5px; + transition: 0.2s; +} + +.green{ + background: #54BF54; +} + +.green:hover{ + background: #469e46; +} + +.green:active{ + background: #367a36; +} + +.red{ + background: #ca2f2f; +} + +.red:hover{ + background: #bb2b2b; +} + +.red:active{ + background: #a72626; +} + +.screen{ + position: absolute; + top: 75px; + display: flex; + flex-direction: column; +} + +.middle{ + font-family: digital; + font-size: 65px; + font-weight: 100; + height: 50px; + margin: 0px; +} + +.image{ + background-image: url('./portablebreathalyzer.png'); + margin-left: 40px; + width: 500px; + height: 600px; + position: absolute; + background-size: contain; + background-repeat: no-repeat; + z-index: -100; +} \ No newline at end of file diff --git a/resources/[inventory]/pickle_consumables/modules/items/client.lua b/resources/[inventory]/pickle_consumables/modules/items/client.lua index 66feba2a2..373234e4f 100644 --- a/resources/[inventory]/pickle_consumables/modules/items/client.lua +++ b/resources/[inventory]/pickle_consumables/modules/items/client.lua @@ -114,6 +114,8 @@ function ConsumeItem(name) if ProcessingEffect and not Config.Effects[effectName].canOverlap then return end ProcessingEffect = true Config.Effects[effectName].process(cfg.effect) + -- Event auslösen + TriggerEvent("pickle_consumables:effectStarted", effectName, cfg.effect, name) ProcessingEffect = false end) end @@ -289,3 +291,11 @@ AddEventHandler('onResourceStart', function(resourceName) if (GetCurrentResourceName() ~= resourceName) then return end ValidateItemConfigs() end) + +-- Event-Listener für Alkoholkonsum +AddEventHandler("pickle_consumables:effectStarted", function(effectName, effectData, itemName) + if effectName == "drunk" then + local intensity = effectData.intensity or 1.0 + TriggerServerEvent('qb-alcoholtest:server:alcoholConsumed', itemName, intensity) + end +end)