diff --git a/resources/[Developer]/[Nordi]/kyaktrailer/trailersmall.yft b/resources/[Developer]/[Nordi]/kyaktrailer/trailersmall.yft new file mode 100644 index 000000000..0a9563fa9 Binary files /dev/null and b/resources/[Developer]/[Nordi]/kyaktrailer/trailersmall.yft differ diff --git a/resources/[Developer]/[Nordi]/kyaktrailer/trailersmall.ytd b/resources/[Developer]/[Nordi]/kyaktrailer/trailersmall.ytd new file mode 100644 index 000000000..49cd1207f Binary files /dev/null and b/resources/[Developer]/[Nordi]/kyaktrailer/trailersmall.ytd differ diff --git a/resources/[Developer]/[Nordi]/kyaktrailer/trailersmall_hi.yft b/resources/[Developer]/[Nordi]/kyaktrailer/trailersmall_hi.yft new file mode 100644 index 000000000..0a9563fa9 Binary files /dev/null and b/resources/[Developer]/[Nordi]/kyaktrailer/trailersmall_hi.yft differ diff --git a/resources/[Developer]/[Nordi]/snipe-sitting-master.zip b/resources/[Developer]/[Nordi]/snipe-sitting-master.zip new file mode 100644 index 000000000..9ada85807 Binary files /dev/null and b/resources/[Developer]/[Nordi]/snipe-sitting-master.zip differ diff --git a/resources/[inventory]/cs_shops/config/config.lua b/resources/[inventory]/cs_shops/config/config.lua index 8799a0136..759d9b541 100644 --- a/resources/[inventory]/cs_shops/config/config.lua +++ b/resources/[inventory]/cs_shops/config/config.lua @@ -51,42 +51,12 @@ CodeStudio.Products = { itemPrice = 2, itemInfo = "Refreshing water", }, - ['ecola_zero_dose'] = { - itemName = "E-Cola Zero Dose", + ['pack_ecola'] = { + itemName = "Packet E-Cola Dosen", itemStock = 50, - itemPrice = 2, - itemInfo = "Fizzy cola with a twist", - }, - ['ecola_dose'] = { - itemName = "E-Cola Dose", - itemStock = 50, - itemPrice = 2, - itemInfo = "Fizzy cola with a twist", - }, - ['sprunk_zero_dose'] = { - itemName = "Sprunk Zero Dose", - itemStock = 50, - itemPrice = 2, - itemInfo = "Fizzy cola with a twist", - }, - ['sprunk_dose'] = { - itemName = "Sprunk Dose", - itemStock = 50, - itemPrice = 2, - itemInfo = "Fizzy cola with a twist", - }, - ['orange_o_tang_zero_dose'] = { - itemName = "Orange O Tang Zero Dose", - itemStock = 50, - itemPrice = 2, - itemInfo = "Fizzy cola with a twist", - }, - ['orange_o_tang_dose'] = { - itemName = "Orange O Tang Dose", - itemStock = 50, - itemPrice = 2, - itemInfo = "Fizzy cola with a twist", - }, + itemPrice = 20, + itemInfo = "", + }, ['ecola_zero_flasche'] = { itemName = "E-Cola Zero Flasche", itemStock = 50, @@ -123,12 +93,6 @@ CodeStudio.Products = { itemPrice = 4, itemInfo = "Fizzy cola with a twist", }, - ['junk_energy'] = { - itemName = "Junk Energy", - itemStock = 50, - itemPrice = 2, - itemInfo = "Dose mit Engery", - }, ['munky_juice'] = { itemName = "Munky Juice", itemStock = 50, diff --git a/resources/[inventory]/inventory_images/images/mimis_instant_nudeln.png b/resources/[inventory]/inventory_images/images/mimis_instant_nudeln.png new file mode 100644 index 000000000..14a1f73b8 Binary files /dev/null and b/resources/[inventory]/inventory_images/images/mimis_instant_nudeln.png differ diff --git a/resources/[inventory]/muhaddil-machines/.gitattributes b/resources/[inventory]/muhaddil-machines/.gitattributes new file mode 100644 index 000000000..dfe077042 --- /dev/null +++ b/resources/[inventory]/muhaddil-machines/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/resources/[inventory]/muhaddil-machines/LICENSE b/resources/[inventory]/muhaddil-machines/LICENSE new file mode 100644 index 000000000..7d57900f2 --- /dev/null +++ b/resources/[inventory]/muhaddil-machines/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Muhaddil + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/resources/[inventory]/muhaddil-machines/NUI/NUI.html b/resources/[inventory]/muhaddil-machines/NUI/NUI.html new file mode 100644 index 000000000..be4c2f2c7 --- /dev/null +++ b/resources/[inventory]/muhaddil-machines/NUI/NUI.html @@ -0,0 +1,53 @@ + + + + + Vending Machine + + +
+

Elige un producto

+
+ +
+ + + + diff --git a/resources/[inventory]/muhaddil-machines/README.md b/resources/[inventory]/muhaddil-machines/README.md new file mode 100644 index 000000000..1b8abbff6 --- /dev/null +++ b/resources/[inventory]/muhaddil-machines/README.md @@ -0,0 +1,61 @@ +# muhaddil_machines (FiveM) + +A FiveM script that adds several machines to improve user experience. + +## Features + +- Vending machines for drinks and snacks +- Water coolers with a timeout feature +- Food stands with various items +- News sellers with newspapers +- Custom animations for interactions +- Configurable framework support (ESX and QBCore) +- Debug mode for development +- Auto version checker + +## Installation + +1. Clone or download the repository. +2. Add the resource to your `resources` folder. +3. Add `start muhaddil_machines` to your `server.cfg`. + +## Configuration + +You can configure the script by editing the [config.lua](config.lua) file. Here are some of the options available: + +- `DebugMode`: Enable or disable debug mode. +- `Framework`: Choose between 'esx' or 'qb'. +- `UseOXNotifications`: Use OX notifications or frameworks. +- `ThirstRemoval`: Amount of thirst removed by water coolers. +- `WaterCoolerTimeout`: Timeout duration for water coolers. +- `VisibleProp`: Show or hide props during animations. +- `ShowWaitNotification`: Show notification when water cooler is on timeout. +- `MaxDrinksBeforeKill`: Maximum drinks before player death. +- `CountDrinksPlace`: Count drinks before or after drinking. + +## Usage + +### Vending Machines + +Interact with vending machines to buy drinks and snacks. The available items and their prices are configured in the [config.lua](config.lua) file. + +### Water Coolers + +Interact with water coolers to drink water. The script includes a timeout feature to prevent excessive use. + +### Food Stands + +Interact with food stands to buy various food items. The available items and their prices are configured in the [config.lua](config.lua) file. + +### News Sellers + +Interact with news sellers to buy newspapers. The available items and their prices are configured in the [config.lua](config.lua) file. + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + +

+ + +

diff --git a/resources/[inventory]/muhaddil-machines/client.lua b/resources/[inventory]/muhaddil-machines/client.lua new file mode 100644 index 000000000..3df4694ba --- /dev/null +++ b/resources/[inventory]/muhaddil-machines/client.lua @@ -0,0 +1,472 @@ +local buying = false -- Variable to prevent multiple purchases +local LastWaterCoolerUse = 0 -- Variable to prevent multiple uses of the water cooler +local TimeoutDuration = Config.WaterCoolerTimeout * 1000 -- Timeout duration for water coolers in milliseconds +local DrinkCount = 0 -- Variable to count the number of drinks + +if Config.Framework == "esx" then + ESX = exports['es_extended']:getSharedObject() +elseif Config.Framework == "qb" then + QBCore = exports['qb-core']:GetCoreObject() +elseif Config.Framework == "ox" then + Ox = require '@ox_core.lib.init' +else + ESX = exports['es_extended']:getSharedObject() +end + +function DebugPrint(...) -- Debug print function + if Config.DebugMode then + print(...) + end +end + +function Notify(msgtitle, msg, time, type2) -- Notification function + if Config.UseOXNotifications then + lib.notify({ + title = msgtitle, + description = msg, + showDuration = true, + type = type2, + style = { + backgroundColor = 'rgba(0, 0, 0, 0.75)', + color = 'rgba(255, 255, 255, 1)', + ['.description'] = { + color = '#909296', + backgroundColor = 'transparent' + } + } + }) + else + if Config.Framework == 'qb' then + QBCore.Functions.Notify(msg, type2, time) + elseif Config.Framework == 'esx' then + TriggerEvent('esx:showNotification', msg, type2, time) + elseif Config.Framework == 'ox' then + lib.notify({ + title = msgtitle, + description = msg, + showDuration = true, + type = type2, + style = { + backgroundColor = 'rgba(0, 0, 0, 0.75)', + color = 'rgba(255, 255, 255, 1)', + ['.description'] = { + color = '#909296', + backgroundColor = 'transparent' + } + } + }) + end + end +end + +RegisterNetEvent("muhaddil-machines:Notify") +AddEventHandler("muhaddil-machines:Notify", function(msgtitle, msg, time, type) + Notify(msgtitle, msg, time, type) +end) + +local function loadAnimDict(animDict) + RequestAnimDict(animDict) + while not HasAnimDictLoaded(animDict) do + Citizen.Wait(0) + end +end + +local function playAnimation(ped, animDict, animName) + loadAnimDict(animDict) + TaskPlayAnim(ped, animDict, animName, 8.0, 5.0, -1, 1, 1, false, false, false) + Citizen.Wait(4500) + ClearPedTasks(ped) + RemoveAnimDict(animDict) +end + +local function vendingAnimation(entity) + local ped = PlayerPedId() + local position = GetOffsetFromEntityInWorldCoords(entity, 0.0, -0.97, 0.05) + local heading = GetEntityHeading(entity) + local prop_name = 'ng_proc_sodacan_01a' + buying = true + + TaskTurnPedToFaceEntity(ped, entity, -1) + if not IsEntityAtCoord(ped, position.x, position.y, position.z, 0.1, 0.0, 0.1, false, true, 0) then + TaskGoStraightToCoord(ped, position.x, position.y, position.z, 1.0, 20000, heading, 0.1) + Citizen.Wait(1000) + end + TaskTurnPedToFaceEntity(ped, entity, -1) + Citizen.Wait(1000) + + Citizen.CreateThread(function() + local playerPed = PlayerPedId() + local x, y, z = table.unpack(GetEntityCoords(playerPed)) + + if Config.VisibleProp then + local prop = CreateObject(GetHashKey(prop_name), x, y, z + 0.2, true, true, true) + local boneIndex = GetPedBoneIndex(playerPed, 18905) + end + + RequestAmbientAudioBank("VENDING_MACHINE", false) + HintAmbientAudioBank("VENDING_MACHINE", 0) + if Config.VisibleProp then + AttachEntityToEntity(prop, playerPed, boneIndex, 0.12, 0.008, 0.03, 240.0, -60.0, 0.0, true, true, false, + true, 1, true) + end + playAnimation(playerPed, Config.Animations.sodamachines[1], Config.Animations.sodamachines[2]) + Citizen.Wait(1000) + + ClearPedSecondaryTask(playerPed) + if DoesEntityExist(prop) then + DeleteObject(prop) + end + ReleaseAmbientAudioBank() + + buying = false + end) +end + + +local function showQuantityDialog(item, vendingMachineName, entity) + local input = lib.inputDialog("Selecciona la cantidad", { + { type = 'number', label = 'Cantidad', min = 1, max = Config.InputMaxValue, default = 1 } + }) + + if not input then return end + local cantidad = input[1] + + if cantidad and cantidad > 0 then + local ped = PlayerPedId() + if buying then return end + buying = true + + vendingAnimation(entity) + + Citizen.Wait(4500) + + TriggerServerEvent('muhaddil-machines:buy', 'machine', vendingMachineName, item.name, cantidad) + else + Notify('Error', 'Cantidad no válida', 5000, "error") + end + + buying = false +end + +local function replacePrice(inputString, price) + return inputString:gsub("%%price%%", tostring(price)) +end + +local function showVendingMenu(vendingMachineName, entity, items) + local options = {} + + for _, item in pairs(items) do + table.insert(options, { + title = replacePrice(item.label, item.price), + icon = item.icon or 'fa-solid fa-bottle-water', + onSelect = function() + if buying then return end + showQuantityDialog(item, vendingMachineName, entity) + end + }) + end + + lib.registerContext({ + id = 'vending_menu_' .. vendingMachineName, + title = 'Máquina expendedora', + canClose = true, + options = options + }) + + lib.showContext('vending_menu_' .. vendingMachineName) +end + +local function interactWithWaterCooler(entity) + local currentTime = GetGameTimer() + + if Config.ShowWaitNotification then + if currentTime - LastWaterCoolerUse < TimeoutDuration then + DebugPrint("Debes esperar antes de usar nuevamente la fuente de agua.") + Notify('¡Echa el freno madaleno!', 'Relaja, espera un poco antes de volver a usar la máquina', 5000, "error") + return + end + end + + if IsAnimated then return end + IsAnimated = true + LastWaterCoolerUse = currentTime + + local prop_name = 'prop_cs_paper_cup' + local ped = PlayerPedId() + local position = GetOffsetFromEntityInWorldCoords(entity, 0.0, -0.97, 0.05) + local heading = GetEntityHeading(entity) + + if not IsEntityAtCoord(ped, position.x, position.y, position.z, 0.1, 0.1, 0.1, false, true, 0) then + TaskGoStraightToCoord(ped, position.x, position.y, position.z, 1.0, 20000, heading, 0.1) + Wait(1000) + end + TaskTurnPedToFaceEntity(ped, entity, -1) + + lib.progressBar({ + duration = 2000, + label = 'Llenado Vaso', + }) + + Citizen.CreateThread(function() + local playerPed = PlayerPedId() + local x, y, z = table.unpack(GetEntityCoords(playerPed)) + local prop = CreateObject(GetHashKey(prop_name), x, y, z + 0.2, true, true, true) + local boneIndex = GetPedBoneIndex(playerPed, 18905) + AttachEntityToEntity(prop, playerPed, boneIndex, 0.12, 0.008, 0.03, 240.0, -60.0, 0.0, true, true, false, true, 1, + true) + + local animDict = 'mp_player_intdrink' + local animName = 'loop_bottle' + + playAnimation(playerPed, animDict, animName) + + if Config.CountDrinksPlace == 'before' then + DrinkCount = DrinkCount + 1 + end + + if Config.KillPlayerOnExcess then + local warningThreshold = math.ceil(Config.MaxDrinksBeforeKill / 2) + print(warningThreshold) + + if DrinkCount >= Config.MaxDrinksBeforeKill then + SetEntityHealth(playerPed, 0) + Notify('Demasiada agua', 'Has bebido demasiada agua y has muerto.', 5000, "error") + DrinkCount = 0 + elseif DrinkCount >= warningThreshold then + Notify('Agua fresca', 'Sigues bebiendo... ten cuidado.', 3000, "info") + else + Notify('Agua fresca', 'Has tomado un vaso de agua.', 3000, "info") + end + else + Notify('Agua fresca', 'Has tomado un vaso de agua.', 3000, "info") + end + + IsAnimated = false + ClearPedSecondaryTask(playerPed) + DeleteObject(prop) + RemoveAnimDict(animDict) + + TriggerServerEvent('muhaddil-machines:RemoveThirst') + + if Config.CountDrinksPlace == 'after' then + DrinkCount = DrinkCount + 1 + end + end) +end + +local function WaterCoolerTarget() + for waterCoolerName, data in pairs(Config.WaterCoolers) do + local options = { + { + label = "Beber Agua", + icon = 'fa-solid fa-glass-water', + onSelect = function(d) + local entity = d.entity + interactWithWaterCooler(entity) + end + } + } + + exports.ox_target:addModel(joaat(data.model), options) + end +end + +local function standAnimation(entity) + local ped = PlayerPedId() + local position = GetOffsetFromEntityInWorldCoords(entity, 0.0, -0.97, 0.05) + local heading = GetEntityHeading(entity) + buying = true + + TaskTurnPedToFaceEntity(ped, entity, -1) + if not IsEntityAtCoord(ped, position.x, position.y, position.z, 0.1, 0.0, 0.1, false, true, 0) then + TaskGoStraightToCoord(ped, position.x, position.y, position.z, 1.0, 20000, heading, 0.1) + Citizen.Wait(1000) + end + TaskTurnPedToFaceEntity(ped, entity, -1) + Citizen.Wait(1000) + + playAnimation(ped, Config.Animations.stand[1], Config.Animations.stand[2]) + Citizen.Wait(1000) + + ClearPedSecondaryTask(ped) + + buying = false +end + +local function showQuantityDialogStands(item, standName, entity) + local input = lib.inputDialog("Selecciona la cantidad", { + { type = 'number', label = 'Cantidad', min = 1, max = Config.InputMaxValue, default = 1 } + }) + + if not input then return end + local cantidad = input[1] + + if cantidad and cantidad > 0 then + local ped = PlayerPedId() + if buying then return end + buying = true + + standAnimation(entity) + + TriggerServerEvent('muhaddil-machines:buy', 'stand', standName, item.name, cantidad) + else + Notify('Error', 'Cantidad no válida', 5000, "error") + end + + buying = false +end + +local function standMenu(standName, entity, items) + local options = {} + + for _, item in pairs(items) do + table.insert(options, { + title = replacePrice(item.label, item.price), + icon = item.icon or 'fa-solid fa-bottle-water', + onSelect = function() + if buying then return end + showQuantityDialogStands(item, standName, entity) + end + }) + end + + lib.registerContext({ + id = 'stand_menu_' .. standName, + title = 'Puesto de Comida', + canClose = true, + options = options + }) + + lib.showContext('stand_menu_' .. standName) +end + +local function newsAnimation(entity) + local ped = PlayerPedId() + local position = GetOffsetFromEntityInWorldCoords(entity, 0.0, -0.97, 0.05) + local heading = GetEntityHeading(entity) + buying = true + + TaskTurnPedToFaceEntity(ped, entity, -1) + if not IsEntityAtCoord(ped, position.x, position.y, position.z, 0.0, 0.0, 0.0, false, true, 0) then + TaskGoStraightToCoord(ped, position.x, position.y, position.z, 1.0, 20000, heading, 0.1) + Citizen.Wait(1000) + end + TaskTurnPedToFaceEntity(ped, entity, -1) + Citizen.Wait(1000) + + loadAnimDict(Config.Animations.newsSellers[1]) + TaskPlayAnim(ped, Config.Animations.newsSellers[1], Config.Animations.newsSellers[2], 8.0, 5.0, -1, 1, 1, false, + false, false) + Citizen.Wait(2500) + ClearPedTasks(ped) + RemoveAnimDict(Config.Animations.newsSellers[1]) + + ClearPedSecondaryTask(ped) + + buying = false +end + + +local function showQuantityDialogNews(item, newsName, entity) + local input = lib.inputDialog("Selecciona la cantidad", { + { type = 'number', label = 'Cantidad', min = 1, max = Config.InputMaxValue, default = 1 } + }) + + if not input then return end + local cantidad = input[1] + + if cantidad and cantidad > 0 then + local ped = PlayerPedId() + if buying then return end + buying = true + + newsAnimation(entity) + + TriggerServerEvent('muhaddil-machines:buy', 'news', newsName, item.name, cantidad) + else + Notify('Error', 'Cantidad no válida', 5000, "error") + end + + buying = false +end + +local function newsMenu(newsName, entity, items) + local options = {} + + for _, item in pairs(items) do + table.insert(options, { + title = replacePrice(item.label, item.price), + icon = item.icon or 'fa-solid fa-bottle-water', + onSelect = function() + if buying then return end + showQuantityDialogNews(item, newsName, entity) + end + }) + end + + lib.registerContext({ + id = 'news_menu_' .. newsName, + title = 'Venta de Noticias', + canClose = true, + options = options + }) + + lib.showContext('news_menu_' .. newsName) +end + +local function setupTargeting() + for vendingMachineName, data in pairs(Config.machines) do + local options = { + { + label = "Abrir Máquina Expendedora", + icon = 'fa-solid fa-basket-shopping', + onSelect = function(d) + if buying then return end + local entity = d.entity + showVendingMenu(vendingMachineName, entity, data.items) + end + } + } + + exports.ox_target:addModel(joaat(data.model), options) + end + + for standName, data in pairs(Config.Stands) do + local options = { + { + label = "Abrir Puesto de Comida", + icon = 'fa-solid fa-utensils', + onSelect = function(d) + if buying then return end + local entity = d.entity + standMenu(standName, entity, data.items) + end + } + } + + exports.ox_target:addModel(joaat(data.model), options) + end + + for newsName, data in pairs(Config.NewsSellers) do + local options = { + { + label = "Abrir Venta de Noticias", + icon = 'fa-solid fa-newspaper', + onSelect = function(d) + if buying then return end + local entity = d.entity + newsMenu(newsName, entity, data.items) + end + } + } + + exports.ox_target:addModel(joaat(data.model), options) + end +end + +CreateThread(function() + Wait(100) + setupTargeting() + WaterCoolerTarget() + -- standsTarget() +end) diff --git a/resources/[inventory]/muhaddil-machines/config.lua b/resources/[inventory]/muhaddil-machines/config.lua new file mode 100644 index 000000000..873e7d8a4 --- /dev/null +++ b/resources/[inventory]/muhaddil-machines/config.lua @@ -0,0 +1,314 @@ +Config = Config or {} + +Config.DebugMode = true -- Enable debug mode +Config.Framework = 'qb' -- 'esx', 'qb' or 'ox' +Config.UseOXNotifications = true -- Use OX Notifications or framework notifications +Config.Inventory = ''-- 'qs', 'ox' or leave blank +Config.NewQBInventory = false -- If you're using the new QB Inventory + +Config.ThirstRemoval = 150000 -- Amount of thirst removed by water coolers +Config.WaterCoolerTimeout = 30 -- Timeout duration for water coolers in seconds +Config.VisibleProp = false -- Show the prop when buying a drink +Config.InputMaxValue = 10 -- Maximum value for the input +Config.KillPlayerOnExcess = true -- Enable one of the two (WaterCooler) +Config.ShowWaitNotification = false -- Enable one of the two (WaterCooler) +Config.MaxDrinksBeforeKill = 3 -- (WaterCooler) +Config.CountDrinksPlace = 'before' -- 'before' or 'after', it varies in the result of the Config.MaxDrinksBeforeKill (WaterCooler) + +Config.Animations = { -- Animations for the vending machines + stand = { -- Stand animations + "special_ped@baygor@monologue_2@monologue_2h", + "you_can_ignore_me_7" + }, + sodamachines = { -- Soda machine animations + "mini@sprunk@first_person", + "plyr_buy_drink_pt1" + }, + newsSellers = { -- News seller animations + "anim@amb@nightclub@mini@drinking@drinking_shots@ped_c@normal", + "pickup" + }, +} + +Config.machines = { -- Vending machines + { + model = 'prop_vend_soda_02', + items = { + { + name = "ecola_dose", + label = 'E-Cola Dose ($%price%)', + icon = 'fa-solid fa-can-food', + price = 4 + }, + { + name = "sprunk_dose", + label = 'Sprunk Dose ($%price%)', + icon = 'fa-solid fa-can-food', + price = 4 + }, + { + name = "orange_o_tang_dose", + label = 'Orange O Tang Dose ($%price%)', + icon = 'fa-solid fa-can-food', + price = 4 + }, + { + name = "ecola_zero_dose", + label = 'E-Cola Zero Dose ($%price%)', + icon = 'fa-solid fa-can-food',', + price = 4 + }, + { + name = "orange_o_tang_zero_dose", + label = 'Orange O Tang Zero Dose ($%price%)', + icon = 'fa-solid fa-can-food',', + price = 4 + }, + { + name = "sprunk_zero_dose", + label = 'Sprunk Zero Dose ($%price%)', + icon = 'fa-solid fa-can-food',', + price = 4 + }, + }, + }, + { + model = 'prop_vend_soda_01', + items = { + { + name = "ecola_dose", + label = 'E-Cola Dose ($%price%)', + icon = 'fa-solid fa-can-food', + price = 4 + }, + { + name = "sprunk_dose", + label = 'Sprunk Dose ($%price%)', + icon = 'fa-solid fa-can-food', + price = 4 + }, + { + name = "orange_o_tang_dose", + label = 'Orange O Tang Dose ($%price%)', + icon = 'fa-solid fa-can-food', + price = 4 + }, + { + name = "ecola_zero_dose", + label = 'E-Cola Zero Dose ($%price%)', + icon = 'fa-solid fa-can-food',', + price = 4 + }, + { + name = "orange_o_tang_zero_dose", + label = 'Orange O Tang Zero Dose ($%price%)', + icon = 'fa-solid fa-can-food',', + price = 4 + }, + { + name = "sprunk_zero_dose", + label = 'Sprunk Zero Dose ($%price%)', + icon = 'fa-solid fa-can-food',', + price = 4 + }, + }, + }, + { + model = 'ch_chint10_vending_smallroom_01', + items = { + { + name = "ecola_dose", + label = 'E-Cola Dose ($%price%)', + icon = 'fa-solid fa-can-food', + price = 4 + }, + { + name = "sprunk_dose", + label = 'Sprunk Dose ($%price%)', + icon = 'fa-solid fa-can-food', + price = 4 + }, + { + name = "orange_o_tang_dose", + label = 'Orange O Tang Dose ($%price%)', + icon = 'fa-solid fa-can-food', + price = 4 + }, + { + name = "ecola_zero_dose", + label = 'E-Cola Zero Dose ($%price%)', + icon = 'fa-solid fa-can-food',', + price = 4 + }, + { + name = "orange_o_tang_zero_dose", + label = 'Orange O Tang Zero Dose ($%price%)', + icon = 'fa-solid fa-can-food',', + price = 4 + }, + { + name = "sprunk_zero_dose", + label = 'Sprunk Zero Dose ($%price%)', + icon = 'fa-solid fa-can-food',', + price = 4 + }, + }, + }, + { + model = 'm23_2_prop_m32_vend_drink_01a', + items = { + { + name = "junk_energy", + label = 'Junk Energy ($%price%)', + icon = 'fa-solid fa-can-food', + price = 4 + }, + }, + }, + { + model = 'sf_prop_sf_vend_drink_01a', + items = { + { + name = "junk_energy", + label = 'Junk Energy($%price%)', + icon = 'fa-solid fa-can-food', + price = 4 + }, + }, + }, + { + model = 'prop_vend_water_01', + items = { + { + name = "water", + label = 'Flasche Wasser ($%price%)', + price = 2 + }, + }, + }, + { + model = 'prop_vend_snak_01', + items = { + { + name = "mimis_instant_nudeln", + label = 'Mimis Instant Nudeln ($%price%)', + icon = 'fa-solid fa-pot-food', + price = 6 + }, + { + name = "twerks_candy", + label = 'Ego Chaser Schockriegel ($%price%)', + icon = 'fa-solid fa-candy-bar', + price = 2 + }, + { + name = "snikkel_candy", + label = 'EarthQuakes ($%price%)', + icon = 'fa-solid fa-candy-bar', + price = 2 + }, + }, + }, + { + model = 'prop_vend_snak_01_tu', + items = { + { + name = "mimis_instant_nudeln", + label = 'Mimis Instant Nudeln ($%price%)', + icon = 'fa-solid fa-pot-food', + price = 6 + }, + { + name = "twerks_candy", + label = 'Ego Chaser Schockriegel ($%price%)', + icon = 'fa-solid fa-candy-bar', + price = 2 + }, + { + name = "snikkel_candy", + label = 'EarthQuakes ($%price%)', + icon = 'fa-solid fa-candy-bar', + price = 2 + }, + }, + }, + + { + model = 'prop_vend_coffe_01', + items = { + { + name = "coffe", + label = 'Kaffee Togo ($%price%)', + icon = 'fa fa-mug-hot', + price = 4 + }, + { + name = "kakao", + label = 'Kakao Togo ($%price%)', + icon = 'fa fa-mug-hot', + price = 4 + }, + }, + }, + { + model = 'prop_vend_fags_01', + items = { + { + name = "redwoodpack", + label = 'Packung Redwood Zigaretten ($%price%)', + icon = 'fa-solid fa-smoking', + price = 10 + }, + }, + }, + + + + +}, + + + + + +Config.WaterCoolers = { -- Water coolers + {model = 'prop_watercooler_dark',}, + {model = 'prop_watercooler',}, +} + + +Config.NewsSellers = { -- News sellers + { + model = 'prop_news_disp_06a', + items = { + { + name = "newspaper", + label = 'Periódico ($%price%)', + icon = 'fa fa-newspaper', + price = 30 + }, + }, + }, + { + model = 'prop_news_disp_01a', + items = { + { + name = "newspaper", + label = 'Periódico ($%price%)', + icon = 'fa fa-newspaper', + price = 30 + }, + }, + }, + { + model = 'prop_news_disp_03a', + items = { + { + name = "newspaper", + label = 'Periódico ($%price%)', + icon = 'fa fa-newspaper', + price = 30 + }, + }, + }, +} diff --git a/resources/[inventory]/muhaddil-machines/fxmanifest.lua b/resources/[inventory]/muhaddil-machines/fxmanifest.lua new file mode 100644 index 000000000..c88c2bc08 --- /dev/null +++ b/resources/[inventory]/muhaddil-machines/fxmanifest.lua @@ -0,0 +1,16 @@ +fx_version 'cerulean' +game 'gta5' +lua54 'yes' + +author 'Muhaddil' +description 'FiveM script that adds vending machines' +version 'v1.0.1' + +shared_scripts { + 'config.lua', + '@ox_lib/init.lua', +} + +client_script 'client.lua' + +server_script 'server/*' \ No newline at end of file diff --git a/resources/[inventory]/muhaddil-machines/server/autoChecker.lua b/resources/[inventory]/muhaddil-machines/server/autoChecker.lua new file mode 100644 index 000000000..386da32f0 --- /dev/null +++ b/resources/[inventory]/muhaddil-machines/server/autoChecker.lua @@ -0,0 +1,120 @@ +local currentVersion = GetResourceMetadata(GetCurrentResourceName(), 'version') +local resourceName = 'Muhaddil/muhaddil-machines' +local githubApiUrl = 'https://api.github.com/repos/' .. resourceName .. '/releases/latest' + +-- Función para calcular la diferencia en días +local function daysAgo(dateStr) + local year, month, day = dateStr:match("(%d+)-(%d+)-(%d+)") + local releaseTime = os.time({ year = year, month = month, day = day }) + local currentTime = os.time() + local difference = os.difftime(currentTime, releaseTime) / (60 * 60 * 24) -- Diferencia en días + return math.floor(difference) +end + +-- Función para convertir la fecha a "hace X días" +local function formatDate(releaseDate) + local days = daysAgo(releaseDate) + if days < 1 then + return "Today" + elseif days == 1 then + return "Yesterday" + else + return days .. " days ago" + end +end + +-- Función para acortar la URL +local function shortenTexts(text) + local maxLength = 35 + if #text > maxLength then + local shortened = text:sub(1, maxLength - 3) .. '...' + return shortened + else + return text + end +end + +local function printWithColor(message, colorCode) + if type(message) ~= "string" then + message = tostring(message) + end + print('\27[' .. colorCode .. 'm' .. message .. '\27[0m') +end + +local function printCentered(text, length, colorCode) + local padding = math.max(length - #text - 2, 0) + local leftPadding = math.floor(padding / 2) + local rightPadding = padding - leftPadding + printWithColor('│' .. string.rep(' ', leftPadding) .. text .. string.rep(' ', rightPadding) .. '│', colorCode) +end + +local function printWrapped(text, length, colorCode) + if type(text) ~= "string" then + text = tostring(text) + end + + local maxLength = length - 2 + local pos = 1 + + while pos <= #text do + local endPos = pos + maxLength - 1 + if endPos > #text then + endPos = #text + else + local spaceIndex = text:sub(pos, endPos):match('.*%s') or maxLength + endPos = pos + spaceIndex - 1 + end + + local line = text:sub(pos, endPos) + if endPos < #text then + line = line .. '...' + end + + printWithColor('│' .. line .. string.rep(' ', length - #line) .. '│', colorCode) + + pos = endPos + 1 + end +end + +if Config.AutoVersionChecker then + PerformHttpRequest(githubApiUrl, function(statusCode, response, headers) + if statusCode == 200 then + local data = json.decode(response) + + if data and data.tag_name then + local latestVersion = data.tag_name + local releaseDate = data.published_at or "Unknown" + local formattedDate = formatDate(releaseDate) + local notes = data.body or "No notes available" + local downloadUrl = data.html_url or "No download link available" + local shortenedUrl = shortenTexts(downloadUrl) + local shortenedNotes = shortenTexts(notes) + + + local boxWidth = 52 + + if latestVersion ~= currentVersion then + print('╭────────────────────────────────────────────────────╮') + printWrapped('[muhaddil-machines] - New Version Available', boxWidth, '34') -- Blue + printWrapped('Current version: ' .. currentVersion, boxWidth, '32') -- Green + printWrapped('Latest version: ' .. latestVersion, boxWidth, '33') -- Yellow + printWrapped('Released: ' .. formattedDate, boxWidth, '33') -- Yellow + printWrapped('Notes: ' .. shortenedNotes, boxWidth, '33') -- Yellow + printWrapped('Download: ' .. shortenedUrl, boxWidth, '32') -- Green + print('╰────────────────────────────────────────────────────╯') + else + print('╭────────────────────────────────────────────────────╮') + printWrapped('[muhaddil-machines] - Up-to-date', boxWidth, '32') -- Green + printWrapped('Current version: ' .. currentVersion, boxWidth, '32') -- Green + print('╰────────────────────────────────────────────────────╯') + end + else + printWithColor('[muhaddil-machines] - Error: The JSON structure is not as expected.', '31') -- Red + printWithColor('GitHub API Response: ' .. response, '31') -- Red + end + else + printWithColor( + '[muhaddil-machines] - Failed to check for latest version. Status code: ' .. statusCode, '31') -- Red + end + end, 'GET') +end diff --git a/resources/[inventory]/muhaddil-machines/server/server.lua b/resources/[inventory]/muhaddil-machines/server/server.lua new file mode 100644 index 000000000..d10af7b62 --- /dev/null +++ b/resources/[inventory]/muhaddil-machines/server/server.lua @@ -0,0 +1,164 @@ +if Config.Framework == "esx" then + ESX = exports['es_extended']:getSharedObject() +elseif Config.Framework == "qb" then + QBCore = exports['qb-core']:GetCoreObject() +elseif Config.Framework == "ox" then + Ox = require '@ox_core.lib.init' +else + ESX = exports['es_extended']:getSharedObject() +end + +local function getPlayerObject(src) -- Get the player object + if Config.Framework == 'qb' then + return QBCore.Functions.GetPlayer(src) + elseif Config.Framework == 'esx' then + return ESX.GetPlayerFromId(src) + elseif Config.Framework == 'ox' then + return Ox.GetPlayer(src) + end +end + +function DebugPrint(...) + if Config.DebugMode then + print(...) + end +end + +local function TakeMoney(playerObject, method, amount) -- Take money from the player + amount = tonumber(amount) + + if Config.Framework == 'qb' then + return playerObject.Functions.RemoveMoney(method, amount) + elseif Config.Framework == 'esx' then + if method == 'cash' then + if playerObject.getMoney() >= amount then + playerObject.removeMoney(amount) + return true + end + elseif method == 'bank' then + if playerObject.getAccount('bank').money >= amount then + playerObject.removeAccountMoney('bank', amount) + return true + end + end + elseif Config.Framework == 'ox' then + if exports.ox_inventory:GetItemCount(source, 'money') >= amount then + exports.ox_inventory:RemoveItem(source, 'money', amount) + return true + end + end + + return false +end + +local function giveItem(src, playerObject, item, amount) -- Give the item to the player + if Config.Framework == 'qb' then + if Config.Inventory == 'qs' then + exports['qs-inventory']:AddItem(src, item.name, amount) + elseif Config.Inventory == 'ox' then + exports.ox_inventory:AddItem(src, item.name, amount) + else + if Config.NewQBInventory then + exports['qb-inventory']:AddItem(source, item.name, amount, false, false, 'Machines') + else + playerObject.Functions.AddItem(item.name, amount) + end + end + elseif Config.Framework == 'esx' then + if Config.Inventory == 'qs' then + exports['qs-inventory']:AddItem(src, item.name, amount) + elseif Config.Inventory == 'ox' then + exports.ox_inventory:AddItem(src, item.name, amount) + else + playerObject.addInventoryItem(item.name, amount) + end + elseif Config.Framework == 'ox' then + exports.ox_inventory:AddItem(source, item.name, amount) + end +end + +local function handlePurchase(src, player, item, machineName, totalPrice, cantidad) -- Handle the purchase + local success = false + + if TakeMoney(player, 'cash', totalPrice) then + success = true + elseif TakeMoney(player, 'bank', totalPrice) then + success = true + end + + if success then + giveItem(src, player, item, cantidad) + else + TriggerClientEvent('muhaddil-machines:Notify', src, '', 'No tienes suficiente dinero.', 'error') + end +end + +local function findItemInSource(sourceData, itemName) -- Find the item in the source data + for _, item in ipairs(sourceData.items) do + if item.name == itemName then + return item + end + end + return nil +end + +RegisterNetEvent('muhaddil-machines:buy', + function(sourceType, sourceName, itemName, cantidad) -- Event for buying the item + local src = source + local player = getPlayerObject(src) + + local sourceData + if sourceType == 'machine' then + sourceData = Config.machines[sourceName] + elseif sourceType == 'stand' then + sourceData = Config.Stands[sourceName] + elseif sourceType == 'news' then + sourceData = Config.NewsSellers[sourceName] + else + TriggerClientEvent('muhaddil-machines:Notify', src, '', 'El tipo de origen no es válido.', 'error') + return + end + + local item = findItemInSource(sourceData, itemName) + if item then + local totalPrice = item.price * cantidad + handlePurchase(src, player, item, sourceName, totalPrice, cantidad) + else + TriggerClientEvent('muhaddil-machines:Notify', src, '', 'El artículo no está disponible', 'error') + end + end) + +RegisterServerEvent('muhaddil-machines:RemoveThirst') -- Event for the watercoolers to remove thirst +AddEventHandler('muhaddil-machines:RemoveThirst', function() + local src = source + + if Config.Framework == 'qb' then + local player = QBCore.Functions.GetPlayer(src) + if player then + local currentThirst = player.PlayerData.metadata['thirst'] or 0 + if currentThirst < 100 then + local newThirst = math.min(currentThirst + Config.ThirstRemoval, 100) + player.Functions.SetMetaData('thirst', newThirst) + + TriggerClientEvent('hud:client:UpdateNeeds', src, player.PlayerData.metadata.hunger or 50, newThirst) + else + print("[Info] El jugador " .. src .. " ya tiene la sed máxima (100).") + end + else + print("[Error] No se pudo obtener el jugador para src: " .. tostring(src)) + end + elseif Config.Framework == 'esx' then + TriggerClientEvent('esx_status:add', src, 'thirst', Config.ThirstRemoval) + elseif Config.Framework == 'ox' then + local player = Ox.GetPlayer(src) + local beforeStatus = player.getStatus('thirst') + player.removeStatus('thirst', Config.ThirstRemoval) + local afterStatus = player.getStatus('thirst') + DebugPrint("[Info] El jugador " .. + src .. " tenía " .. beforeStatus .. " de sed y ahora tiene " .. afterStatus .. ".") + local statuses = player.getStatuses() + DebugPrint("[Info] Los estados del jugador " .. src .. " son: " .. json.encode(statuses)) + else + print("[Error] Configuración de framework no válida.") + end +end) diff --git a/resources/[inventory]/pickle_consumables/config.lua b/resources/[inventory]/pickle_consumables/config.lua index b615d7430..c15f01536 100644 --- a/resources/[inventory]/pickle_consumables/config.lua +++ b/resources/[inventory]/pickle_consumables/config.lua @@ -197,7 +197,7 @@ Config.Items = { stamina = 0, } }, - ["tims_instant_nudeln"] = { + ["mimis_instant_nudeln"] = { uses = 1, prop = { model = `v_res_fa_potnoodle`, boneId = 60309, offset = vec3(0.08, -0.04, 0.07), rotation = vec3(-30.0, 10.0, 0.0) }, idle = { dict = "anim@scripted@island@special_peds@pavel@hs4_pavel_ig5_caviar_p1", anim = "base_idle", time = 2000, params = { nil, nil, nil, 49 } }, diff --git a/resources/[inventory]/tgiann-inventory/configs/configVendingMachine.lua b/resources/[inventory]/tgiann-inventory/configs/configVendingMachine.lua index 9e56ac360..0f5a66667 100644 --- a/resources/[inventory]/tgiann-inventory/configs/configVendingMachine.lua +++ b/resources/[inventory]/tgiann-inventory/configs/configVendingMachine.lua @@ -1,5 +1,5 @@ config.vendingMachine = { - active = true, + active = false, machines = { { objects = { diff --git a/resources/[inventory]/tgiann-inventory/items/items.lua b/resources/[inventory]/tgiann-inventory/items/items.lua index 647ceda90..ea76faef2 100644 --- a/resources/[inventory]/tgiann-inventory/items/items.lua +++ b/resources/[inventory]/tgiann-inventory/items/items.lua @@ -1590,7 +1590,7 @@ itemsData = { }, redwoodpack = { useable = true, - weight = 200, + weight = 100, type = 'item', unique = false, description = '', @@ -8252,16 +8252,16 @@ itemsData = { label = 'Funkgerät', name = 'radio', }, - tims_instant_nudeln = { + mimis_instant_nudeln = { useable = true, weight = 100, type = 'item', unique = false, description = 'der Kulinarische Blitzbesuch in Fernost', - image = 'tims_instant_nudeln.png', + image = 'mimis_instant_nudeln.png', shouldClose = true, - label = 'Tim\'s Instant Nudeln', - name = 'tims_instant_nudeln', + label = 'Mimis Instant Nudeln', + name = 'mimis_instant_nudeln', }, attachment_bench = { useable = true, @@ -10197,7 +10197,7 @@ itemsData = { type = 'item', description = '', weight = 1000, - label = 'Packung E-Cola', + label = 'Packet E-Cola Dosen', unique = true, useable = true, image = 'pack_ecola.png',