diff --git a/__resource.lua b/__resource.lua new file mode 100644 index 00000000..5660501e --- /dev/null +++ b/__resource.lua @@ -0,0 +1,14 @@ +resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937' + +description 'ESX Property' + +server_scripts { + '@mysql-async/lib/MySQL.lua', + 'config.lua', + 'server/main.lua' +} + +client_scripts { + 'config.lua', + 'client/main.lua' +} diff --git a/client/main.lua b/client/main.lua new file mode 100644 index 00000000..1be6f35f --- /dev/null +++ b/client/main.lua @@ -0,0 +1,1024 @@ +local Keys = { + ["ESC"] = 322, ["F1"] = 288, ["F2"] = 289, ["F3"] = 170, ["F5"] = 166, ["F6"] = 167, ["F7"] = 168, ["F8"] = 169, ["F9"] = 56, ["F10"] = 57, + ["~"] = 243, ["1"] = 157, ["2"] = 158, ["3"] = 160, ["4"] = 164, ["5"] = 165, ["6"] = 159, ["7"] = 161, ["8"] = 162, ["9"] = 163, ["-"] = 84, ["="] = 83, ["BACKSPACE"] = 177, + ["TAB"] = 37, ["Q"] = 44, ["W"] = 32, ["E"] = 38, ["R"] = 45, ["T"] = 245, ["Y"] = 246, ["U"] = 303, ["P"] = 199, ["["] = 39, ["]"] = 40, ["ENTER"] = 18, + ["CAPS"] = 137, ["A"] = 34, ["S"] = 8, ["D"] = 9, ["F"] = 23, ["G"] = 47, ["H"] = 74, ["K"] = 311, ["L"] = 182, + ["LEFTSHIFT"] = 21, ["Z"] = 20, ["X"] = 73, ["C"] = 26, ["V"] = 0, ["B"] = 29, ["N"] = 249, ["M"] = 244, [","] = 82, ["."] = 81, + ["LEFTCTRL"] = 36, ["LEFTALT"] = 19, ["SPACE"] = 22, ["RIGHTCTRL"] = 70, + ["HOME"] = 213, ["PAGEUP"] = 10, ["PAGEDOWN"] = 11, ["DELETE"] = 178, + ["LEFT"] = 174, ["RIGHT"] = 175, ["TOP"] = 27, ["DOWN"] = 173, + ["NENTER"] = 201, ["N4"] = 108, ["N5"] = 60, ["N6"] = 107, ["N+"] = 96, ["N-"] = 97, ["N7"] = 117, ["N8"] = 61, ["N9"] = 118 +} + +ESX = nil +local GUI = {} +GUI.Time = 0 +local OwnedProperties = {} +local Blips = {} +local CurrentProperty = nil +local LastProperty = nil +local LastPart = nil +local HasAlreadyEnteredMarker = false +local CurrentAction = nil +local CurrentActionMsg = '' +local CurrentActionData = {} + +function DrawSub(text, time) + ClearPrints() + SetTextEntry_2('STRING') + AddTextComponentString(text) + DrawSubtitleTimed(time, 1) +end + +function CreateBlips() + + for i=1, #Config.Properties, 1 do + + local property = Config.Properties[i] + + if property.entering ~= nil then + + Blips[property.name] = AddBlipForCoord(property.entering.x, property.entering.y, property.entering.z) + + SetBlipSprite (Blips[property.name], 369) + SetBlipDisplay(Blips[property.name], 4) + SetBlipScale (Blips[property.name], 1.0) + SetBlipAsShortRange(Blips[property.name], true) + + BeginTextCommandSetBlipName("STRING") + AddTextComponentString("Propriété libre") + EndTextCommandSetBlipName(Blips[property.name]) + + end + end + +end + +function GetProperties() + return Config.Properties +end + +function GetProperty(name) + + for i=1, #Config.Properties, 1 do + if Config.Properties[i].name == name then + return Config.Properties[i] + end + end + +end + +function GetGateway(property) + + for i=1, #Config.Properties, 1 do + + local property2 = Config.Properties[i] + + if property2.isGateway and property2.name == property.gateway then + return property2 + end + + end + +end + +function GetGatewayProperties(property) + + local properties = {} + + for i=1, #Config.Properties, 1 do + if Config.Properties[i].gateway == property.name then + table.insert(properties, Config.Properties[i]) + end + end + + return properties + +end + +function EnterProperty(name) + + local property = GetProperty(name) + local playerPed = GetPlayerPed(-1) + CurrentProperty = property + + for i=1, #Config.Properties, 1 do + if Config.Properties[i].name ~= name then + Config.Properties[i].disabled = true + end + end + + TriggerServerEvent('esx_property:saveLastProperty', name) + + TriggerEvent('instance:get', function(instance) + if instance.host == nil then + TriggerEvent('instance:create') + end + end) + + Citizen.CreateThread(function() + + DoScreenFadeOut(800) + + while not IsScreenFadedOut() do + Citizen.Wait(0) + end + + for i=1, #property.ipls, 1 do + + RequestIpl(property.ipls[i]) + + while not IsIplActive(property.ipls[i]) do + Citizen.Wait(0) + end + + end + + SetEntityCoords(playerPed, property.inside.x, property.inside.y, property.inside.z) + + DoScreenFadeIn(800) + + DrawSub(property.label, 5000) + end) + +end + +function ExitProperty(name) + + local property = GetProperty(name) + local playerPed = GetPlayerPed(-1) + local outside = nil + CurrentProperty = nil + + if property.isSingle then + outside = property.outside + else + outside = GetGateway(property).outside + end + + TriggerServerEvent('esx_property:deleteLastProperty') + + Citizen.CreateThread(function() + + DoScreenFadeOut(800) + + while not IsScreenFadedOut() do + Citizen.Wait(0) + end + + SetEntityCoords(playerPed, outside.x, outside.y, outside.z) + + TriggerEvent('instance:get', function(instance) + if instance.host ~= nil then + TriggerEvent('instance:leave') + end + end) + + for i=1, #property.ipls, 1 do + RemoveIpl(property.ipls[i]) + end + + for i=1, #Config.Properties, 1 do + Config.Properties[i].disabled = false + end + + DoScreenFadeIn(800) + + end) + +end + +function SetPropertyOwned(name, owned) + + local property = GetProperty(name) + local entering = nil + local enteringName = nil + + if property.isSingle then + entering = property.entering + enteringName = property.name + else + local gateway = GetGateway(property) + entering = gateway.entering + enteringName = gateway.name + end + + if owned then + + OwnedProperties[name] = true + + RemoveBlip(Blips[enteringName]) + + Blips[enteringName] = AddBlipForCoord(entering.x, entering.y, entering.z) + + SetBlipSprite(Blips[enteringName], 357) + SetBlipAsShortRange(Blips[enteringName], true) + + BeginTextCommandSetBlipName("STRING") + AddTextComponentString("Propriété") + EndTextCommandSetBlipName(Blips[enteringName]) + + else + + OwnedProperties[name] = nil + + local found = false + + for k,v in pairs(OwnedProperties) do + + local _property = GetProperty(k) + local _gateway = GetGateway(_property) + + if _gateway ~= nil then + + if _gateway.name == enteringName then + found = true + break + end + end + + end + + if not found then + + RemoveBlip(Blips[enteringName]) + + Blips[enteringName] = AddBlipForCoord(entering.x, entering.y, entering.z) + + SetBlipSprite(Blips[enteringName], 369) + SetBlipAsShortRange(Blips[enteringName], true) + + BeginTextCommandSetBlipName("STRING") + AddTextComponentString("Propriété libre") + EndTextCommandSetBlipName(Blips[enteringName]) + + end + + end + +end + +function PropertyIsOwned(property) + return OwnedProperties[property.name] == true +end + +function OpenPropertyMenu(property) + + local elements = {} + + if PropertyIsOwned(property) then + + table.insert(elements, {label = 'Entrer', value = 'enter'}) + + if not Config.EnablePlayerManagement then + table.insert(elements, {label = 'Rendre', value = 'leave'}) + end + + else + + if not Config.EnablePlayerManagement then + table.insert(elements, {label = 'Acheter', value = 'buy'}) + table.insert(elements, {label = 'Louer', value = 'rent'}) + end + + table.insert(elements, {label = 'Visiter', value = 'visit'}) + + end + + ESX.UI.Menu.Open( + 'default', GetCurrentResourceName(), 'property', + { + title = property.label, + align = 'top-left', + elements = elements, + }, + function(data2, menu) + + menu.close() + + if data2.current.value == 'enter' then + EnterProperty(property.name) + end + + if data2.current.value == 'leave' then + TriggerServerEvent('esx_property:removeOwnedProperty', property.name) + end + + if data2.current.value == 'buy' then + TriggerServerEvent('esx_property:buyProperty', property.name) + end + + if data2.current.value == 'rent' then + TriggerServerEvent('esx_property:rentProperty', property.name) + end + + if data2.current.value == 'visit' then + EnterProperty(property.name) + end + + end, + function(data, menu) + + menu.close() + + CurrentAction = 'property_menu' + CurrentActionMsg = 'Appuez sur ~INPUT_CONTEXT~ pour accéder au menu' + CurrentActionData = {property = property} + end + ) + +end + +function OpenGatewayMenu(property) + + if Config.EnablePlayerManagement then + OpenGatewayOwnedPropertiesMenu(gatewayProperties) + else + + ESX.UI.Menu.Open( + 'default', GetCurrentResourceName(), 'gateway', + { + title = property.name, + align = 'top-left', + elements = { + {label = 'Propriétés acquises', value = 'owned_properties'}, + {label = 'Propriétés disponibles', value = 'available_properties'}, + } + }, + function(data, menu) + + if data.current.value == 'owned_properties' then + OpenGatewayOwnedPropertiesMenu(property) + end + + if data.current.value == 'available_properties' then + OpenGatewayAvailablePropertiesMenu(property) + end + + end, + function(data, menu) + + menu.close() + + CurrentAction = 'gateway_menu' + CurrentActionMsg = 'Appuez sur ~INPUT_CONTEXT~ pour accéder au menu' + CurrentActionData = {property = property} + + end + ) + + end + +end + +function OpenGatewayOwnedPropertiesMenu(property) + + local gatewayProperties = GetGatewayProperties(property) + local elements = {} + + for i=1, #gatewayProperties, 1 do + + if PropertyIsOwned(gatewayProperties[i]) then + table.insert(elements, { + label = gatewayProperties[i].label, + value = gatewayProperties[i].name + }) + end + + end + + ESX.UI.Menu.Open( + 'default', GetCurrentResourceName(), 'gateway_owned_properties', + { + title = property.name .. ' - Propriétés Acquises', + align = 'top-left', + elements = elements, + }, + function(data, menu) + + menu.close() + + local elements = { + {label = 'Entrer', value = 'enter'} + } + + if not Config.EnablePlayerManagement then + table.insert(elements, {label = 'Rendre', value = 'leave'}) + end + + ESX.UI.Menu.Open( + 'default', GetCurrentResourceName(), 'gateway_owned_properties_actions', + { + title = data.current.label, + align = 'top-left', + elements = elements, + }, + function(data2, menu) + + menu.close() + + if data2.current.value == 'enter' then + EnterProperty(data.current.value) + end + + if data2.current.value == 'leave' then + TriggerServerEvent('esx_property:removeOwnedProperty', data.current.value) + end + + end, + function(data, menu) + menu.close() + end + ) + + end, + function(data, menu) + menu.close() + end + ) + +end + +function OpenGatewayAvailablePropertiesMenu(property) + + local gatewayProperties = GetGatewayProperties(property) + local elements = {} + + for i=1, #gatewayProperties, 1 do + + if not PropertyIsOwned(gatewayProperties[i]) then + table.insert(elements, { + label = gatewayProperties[i].label .. ' $' .. gatewayProperties[i].price, + value = gatewayProperties[i].name, + price = gatewayProperties[i].price + }) + end + + end + + ESX.UI.Menu.Open( + 'default', GetCurrentResourceName(), 'gateway_available_properties', + { + title = property.name.. ' - Propriétés Disponbibles', + align = 'top-left', + elements = elements, + }, + function(data, menu) + + menu.close() + + ESX.UI.Menu.Open( + 'default', GetCurrentResourceName(), 'gateway_available_properties_actions', + { + title = property.name, + align = 'top-left', + elements = { + {label = 'Acheter', value = 'buy'}, + {label = 'Louer', value = 'rent'}, + {label = 'Visiter', value = 'visit'}, + }, + }, + function(data2, menu) + menu.close() + + if data2.current.value == 'buy' then + TriggerServerEvent('esx_property:buyProperty', data.current.value) + end + + if data2.current.value == 'rent' then + TriggerServerEvent('esx_property:rentProperty', data.current.value) + end + + if data2.current.value == 'visit' then + EnterProperty(data.current.value) + end + + end, + function(data, menu) + menu.close() + end + ) + + end, + function(data, menu) + menu.close() + end + ) + +end + +function OpenRoomMenu(property) + + local entering = nil + + if property.isSingle then + entering = property.entering + else + entering = GetGateway(property).entering + end + + ESX.UI.Menu.CloseAll() + ESX.UI.Menu.Open( + 'default', GetCurrentResourceName(), 'room', + { + title = property.label, + align = 'top-left', + elements = { + {label = 'Inviter un joueur', value = 'invite_player'}, + {label = 'Dressing', value = 'player_dressing'}, + {label = 'Retirer objet', value = 'room_inventory'}, + {label = 'Déposer objet', value = 'player_inventory'}, + }, + }, + function(data, menu) + + if data.current.value == 'invite_player' then + + local playersInArea = ESX.Game.GetPlayersInArea(entering, 10.0) + local elements = {} + + for i=1, #playersInArea, 1 do + if playersInArea[i] ~= PlayerId() then + table.insert(elements, {label = GetPlayerName(playersInArea[i]), value = playersInArea[i]}) + end + end + + ESX.UI.Menu.Open( + 'default', GetCurrentResourceName(), 'room_invite', + { + title = property.label .. ' - Invite', + align = 'top-left', + elements = elements, + }, + function(data, menu) + TriggerEvent('instance:invite', GetPlayerServerId(data.current.value), property.inside) + end, + function(data, menu) + menu.close() + end + ) + + end + + if data.current.value == 'player_dressing' then + + ESX.TriggerServerCallback('esx_property:getPlayerDressing', function(dressing) + + local elements = {} + + for i=1, #dressing, 1 do + table.insert(elements, {label = dressing[i].label, value = dressing[i].skin}) + end + + ESX.UI.Menu.Open( + 'default', GetCurrentResourceName(), 'player_dressing', + { + title = property.label .. ' - Dressing', + align = 'top-left', + elements = elements, + }, + function(data, menu) + + TriggerEvent('skinchanger:getSkin', function(skin) + + TriggerEvent('skinchanger:loadClothes', skin, data.current.value) + + TriggerEvent('skinchanger:getSkin', function(skin) + TriggerServerEvent('esx_skin:save', skin) + end) + + end) + + end, + function(data, menu) + menu.close() + end + ) + + end) + + end + + if data.current.value == 'room_inventory' then + OpenRoomInventoryMenu(property) + end + + if data.current.value == 'player_inventory' then + OpenPlayerInventoryMenu(property) + end + + end, + function(data, menu) + + menu.close() + + CurrentAction = 'room_menu' + CurrentActionMsg = 'Appuez sur ~INPUT_CONTEXT~ pour accéder au menu' + CurrentActionData = {property = property} + end + ) + +end + +function OpenRoomInventoryMenu(property) + + ESX.TriggerServerCallback('esx_property:getPropertyInventory', function(inventory) + + local elements = {} + + table.insert(elements, {label = 'Argent sale $' .. inventory.blackMoney, type = 'item_account', value = 'black_money'}) + + for i=1, #inventory.items, 1 do + + local item = inventory.items[i] + + if item.count > 0 then + table.insert(elements, {label = item.label .. ' x' .. item.count, type = 'item_standard', value = item.name}) + end + + end + + for i=1, #inventory.weapons, 1 do + local weapon = inventory.weapons[i] + table.insert(elements, {label = ESX.GetWeaponLabel(weapon.name) .. ' [' .. weapon.ammo .. ']', type = 'item_weapon', value = weapon.name, ammo = weapon.ammo}) + end + + ESX.UI.Menu.Open( + 'default', GetCurrentResourceName(), 'room_inventory', + { + title = property.label .. ' - Inventaire', + align = 'top-left', + elements = elements, + }, + function(data, menu) + + if data.current.type == 'item_weapon' then + + menu.close() + + TriggerServerEvent('esx_property:getItem', data.current.type, data.current.value, data.current.ammo) + + ESX.SetTimeout(300, function() + OpenRoomInventoryMenu(property) + end) + + else + + ESX.UI.Menu.Open( + 'dialog', GetCurrentResourceName(), 'get_item_count', + { + title = 'Quantité ?', + }, + function(data2, menu) + + local quantity = tonumber(data2.value) + + if quantity == nil then + ESX.ShowNotification('Montant invalide') + else + + menu.close() + + TriggerServerEvent('esx_property:getItem', data.current.type, data.current.value, quantity) + + ESX.SetTimeout(300, function() + OpenRoomInventoryMenu(property) + end) + + end + + end, + function(data2,menu) + menu.close() + end + ) + + end + + end, + function(data, menu) + menu.close() + end + ) + + end) + +end + +function OpenPlayerInventoryMenu(property) + + ESX.TriggerServerCallback('esx_property:getPlayerInventory', function(inventory) + + local elements = {} + + table.insert(elements, {label = 'Argent sale $' .. inventory.blackMoney, type = 'item_account', value = 'black_money'}) + + for i=1, #inventory.items, 1 do + + local item = inventory.items[i] + + if item.count > 0 then + table.insert(elements, {label = item.label .. ' x' .. item.count, type = 'item_standard', value = item.name}) + end + + end + + local playerPed = GetPlayerPed(-1) + local weaponList = ESX.GetWeaponList() + + for i=1, #weaponList, 1 do + + local weaponHash = GetHashKey(weaponList[i].name) + + if HasPedGotWeapon(playerPed, weaponHash, false) and weaponList[i].name ~= 'WEAPON_UNARMED' then + local ammo = GetAmmoInPedWeapon(playerPed, weaponHash) + table.insert(elements, {label = weaponList[i].label .. ' [' .. ammo .. ']', type = 'item_weapon', value = weaponList[i].name, ammo = ammo}) + end + + end + + ESX.UI.Menu.Open( + 'default', GetCurrentResourceName(), 'player_inventory', + { + title = property.label .. ' - Inventaire', + align = 'top-left', + elements = elements, + }, + function(data, menu) + + if data.current.type == 'item_weapon' then + + menu.close() + + TriggerServerEvent('esx_property:putItem', data.current.type, data.current.value, data.current.ammo) + + ESX.SetTimeout(300, function() + OpenPlayerInventoryMenu(property) + end) + + else + + ESX.UI.Menu.Open( + 'dialog', GetCurrentResourceName(), 'put_item_count', + { + title = 'Quantité ?', + }, + function(data2, menu) + + menu.close() + + TriggerServerEvent('esx_property:putItem', data.current.type, data.current.value, tonumber(data2.value)) + + ESX.SetTimeout(300, function() + OpenPlayerInventoryMenu(property) + end) + + end, + function(data2,menu) + menu.close() + end + ) + + end + + end, + function(data, menu) + menu.close() + end + ) + + end) + +end + +local FirstSpawn = true + +AddEventHandler('playerSpawned', function() + + if FirstSpawn then + + ESX.TriggerServerCallback('esx_property:getLastProperty', function(propertyName) + if propertyName ~= nil then + EnterProperty(propertyName) + end + end) + + FirstSpawn = false + end + +end) + +AddEventHandler('esx_property:getProperties', function(cb) + cb(GetProperties()) +end) + +AddEventHandler('esx_property:getProperty', function(name, cb) + cb(GetProperty(name)) +end) + +AddEventHandler('esx_property:getGateway', function(property, cb) + cb(GetGateway(property)) +end) + +RegisterNetEvent('esx_property:setPropertyOwned') +AddEventHandler('esx_property:setPropertyOwned', function(name, owned) + SetPropertyOwned(name, owned) +end) + +RegisterNetEvent('esx:playerLoaded') +AddEventHandler('esx:playerLoaded', function(xPlayer) + + ESX.TriggerServerCallback('esx_property:getOwnedProperties', function(ownedProperties) + for i=1, #ownedProperties, 1 do + SetPropertyOwned(ownedProperties[i], true) + end + end) + +end) + +AddEventHandler('esx_property:hasEnteredMarker', function(name, part) + + local property = GetProperty(name) + + if part == 'entering' then + + if property.isSingle then + CurrentAction = 'property_menu' + CurrentActionMsg = 'Appuez sur ~INPUT_CONTEXT~ pour accéder au menu' + CurrentActionData = {property = property} + else + CurrentAction = 'gateway_menu' + CurrentActionMsg = 'Appuez sur ~INPUT_CONTEXT~ pour accéder au menu' + CurrentActionData = {property = property} + end + + end + + if part == 'exit' then + ExitProperty(name) + end + + if part == 'roomMenu' then + CurrentAction = 'room_menu' + CurrentActionMsg = 'Appuez sur ~INPUT_CONTEXT~ pour accéder au menu' + CurrentActionData = {property = property} + end + +end) + +AddEventHandler('esx_property:hasExitedMarker', function(name, part) + ESX.UI.Menu.CloseAll() + CurrentAction = nil +end) + +-- Init +Citizen.CreateThread(function() + + while ESX == nil do + TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end) + Citizen.Wait(0) + end + + ESX.TriggerServerCallback('esx_property:getProperties', function(properties) + Config.Properties = properties + CreateBlips() + end) + +end) + +-- Display markers +Citizen.CreateThread(function() + while true do + + Wait(0) + + local coords = GetEntityCoords(GetPlayerPed(-1)) + + for i=1, #Config.Properties, 1 do + + local property = Config.Properties[i] + + if(property.entering ~= nil and not property.disabled and GetDistanceBetweenCoords(coords, property.entering.x, property.entering.y, property.entering.z, true) < Config.DrawDistance) then + DrawMarker(Config.MarkerType, property.entering.x, property.entering.y, property.entering.z, 0.0, 0.0, 0.0, 0, 0.0, 0.0, Config.MarkerSize.x, Config.MarkerSize.y, Config.MarkerSize.z, Config.MarkerColor.r, Config.MarkerColor.g, Config.MarkerColor.b, 100, false, true, 2, false, false, false, false) + end + + if(property.exit ~= nil and not property.disabled and GetDistanceBetweenCoords(coords, property.exit.x, property.exit.y, property.exit.z, true) < Config.DrawDistance) then + DrawMarker(Config.MarkerType, property.exit.x, property.exit.y, property.exit.z, 0.0, 0.0, 0.0, 0, 0.0, 0.0, Config.MarkerSize.x, Config.MarkerSize.y, Config.MarkerSize.z, Config.MarkerColor.r, Config.MarkerColor.g, Config.MarkerColor.b, 100, false, true, 2, false, false, false, false) + end + + if(property.roomMenu ~= nil and PropertyIsOwned(property) and not property.disabled and GetDistanceBetweenCoords(coords, property.roomMenu.x, property.roomMenu.y, property.roomMenu.z, true) < Config.DrawDistance) then + DrawMarker(Config.MarkerType, property.roomMenu.x, property.roomMenu.y, property.roomMenu.z, 0.0, 0.0, 0.0, 0, 0.0, 0.0, Config.MarkerSize.x, Config.MarkerSize.y, Config.MarkerSize.z, Config.RoomMenuMarkerColor.r, Config.RoomMenuMarkerColor.g, Config.RoomMenuMarkerColor.b, 100, false, true, 2, false, false, false, false) + end + + end + + end +end) + +-- Enter / Exit marker events +Citizen.CreateThread(function() + while true do + + Wait(0) + + local coords = GetEntityCoords(GetPlayerPed(-1)) + local isInMarker = false + local currentProperty = nil + local currentPart = nil + + for i=1, #Config.Properties, 1 do + + local property = Config.Properties[i] + + if(property.entering ~= nil and not property.disabled and GetDistanceBetweenCoords(coords, property.entering.x, property.entering.y, property.entering.z, true) < Config.MarkerSize.x) then + isInMarker = true + currentProperty = property.name + currentPart = 'entering' + end + + if(property.exit ~= nil and not property.disabled and GetDistanceBetweenCoords(coords, property.exit.x, property.exit.y, property.exit.z, true) < Config.MarkerSize.x) then + isInMarker = true + currentProperty = property.name + currentPart = 'exit' + end + + if(property.inside ~= nil and not property.disabled and GetDistanceBetweenCoords(coords, property.inside.x, property.inside.y, property.inside.z, true) < Config.MarkerSize.x) then + isInMarker = true + currentProperty = property.name + currentPart = 'inside' + end + + if(property.outside ~= nil and not property.disabled and GetDistanceBetweenCoords(coords, property.outside.x, property.outside.y, property.outside.z, true) < Config.MarkerSize.x) then + isInMarker = true + currentProperty = property.name + currentPart = 'outside' + end + + if(property.roomMenu ~= nil and PropertyIsOwned(property) and not property.disabled and GetDistanceBetweenCoords(coords, property.roomMenu.x, property.roomMenu.y, property.roomMenu.z, true) < Config.MarkerSize.x) then + isInMarker = true + currentProperty = property.name + currentPart = 'roomMenu' + end + + end + + if isInMarker and not HasAlreadyEnteredMarker or (isInMarker and (LastProperty ~= currentProperty or LastPart ~= currentPart) ) then + + HasAlreadyEnteredMarker = true + LastProperty = currentProperty + LastPart = currentPart + + TriggerEvent('esx_property:hasEnteredMarker', currentProperty, currentPart) + end + + if not isInMarker and HasAlreadyEnteredMarker then + + HasAlreadyEnteredMarker = false + + TriggerEvent('esx_property:hasExitedMarker', LastProperty, LastPart) + end + + end +end) + +-- Key controls +Citizen.CreateThread(function() + while true do + + Citizen.Wait(0) + + if CurrentAction ~= nil then + + SetTextComponentFormat('STRING') + AddTextComponentString(CurrentActionMsg) + DisplayHelpTextFromStringLabel(0, 0, 1, -1) + + if IsControlPressed(0, Keys['E']) and (GetGameTimer() - GUI.Time) > 300 then + + if CurrentAction == 'property_menu' then + OpenPropertyMenu(CurrentActionData.property) + end + + if CurrentAction == 'gateway_menu' then + + if Config.EnablePlayerManagement then + OpenGatewayOwnedPropertiesMenu(CurrentActionData.property) + else + OpenGatewayMenu(CurrentActionData.property) + end + + end + + if CurrentAction == 'room_menu' then + OpenRoomMenu(CurrentActionData.property) + end + + CurrentAction = nil + GUI.Time = GetGameTimer() + + end + + end + + end +end) \ No newline at end of file diff --git a/config.lua b/config.lua new file mode 100644 index 00000000..25c717f8 --- /dev/null +++ b/config.lua @@ -0,0 +1,9 @@ +Config = {} +Config.DrawDistance = 100 +Config.MarkerSize = {x = 1.5, y = 1.5, z = 1.0} +Config.MarkerColor = {r = 102, g = 102, b = 204} +Config.RoomMenuMarkerColor = {r = 102, g = 204, b = 102} +Config.MarkerType = 1 +Config.Zones = {} +Config.Properties = {} +Config.EnablePlayerManagement = false -- If set to true you need esx_realestateagentjob diff --git a/esx_property.sql b/esx_property.sql new file mode 100644 index 00000000..82419c5b --- /dev/null +++ b/esx_property.sql @@ -0,0 +1,76 @@ +CREATE TABLE `owned_properties` ( + + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `price` double NOT NULL, + `rented` int(11) NOT NULL, + `owner` varchar(60) NOT NULL, + + PRIMARY KEY (`id`) +); + +CREATE TABLE `properties` ( + + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `label` varchar(255) DEFAULT NULL, + `entering` varchar(255) DEFAULT NULL, + `exit` varchar(255) DEFAULT NULL, + `inside` varchar(255) DEFAULT NULL, + `outside` varchar(255) DEFAULT NULL, + `ipls` varchar(255) DEFAULT '[]', + `gateway` varchar(255) DEFAULT NULL, + `is_single` int(11) DEFAULT NULL, + `is_room` int(11) DEFAULT NULL, + `is_gateway` int(11) DEFAULT NULL, + `room_menu` varchar(255) DEFAULT NULL, + `price` int(11) NOT NULL, + + PRIMARY KEY (`id`) +); + +INSERT INTO `properties` VALUES + (1,'WhispymoundDrive','2677 Whispymound Drive','{\"y\":564.89,\"z\":182.959,\"x\":119.384}','{\"y\":558.667,\"z\":183.297,\"x\":116.392}','{\"y\":557.032,\"z\":183.301,\"x\":118.037}','{\"y\":567.798,\"z\":182.131,\"x\":119.249}','[]',NULL,1,1,0,'{\"x\":118.748,\"y\":566.573,\"z\":175.697}',1500000), + (2,'NorthConkerAvenue2045','Milla House','{\"y\":834.47540283203,\"z\":234.92024230957,\"x\":-86.692153930664}','{\"y\":422.84088134766,\"z\":144.90005493164,\"x\":372.70159912109}','{\"y\":420.07595825195,\"z\":145.90487670898,\"x\":373.16149902344}','{\"y\":835.64111328125,\"z\":234.7230072021,\"x\":-90.789779663086}','[]',NULL,1,1,0,'{\"x\":377.349,\"y\":429.422,\"z\":137.3}',1500000), + (3,'RichardMajesticApt2','Richard Majestic, Apt 2','{\"y\":-379.165,\"z\":37.961,\"x\":-936.363}','{\"y\":-365.476,\"z\":113.274,\"x\":-913.097}','{\"y\":-367.637,\"z\":113.274,\"x\":-918.022}','{\"y\":-382.023,\"z\":37.961,\"x\":-943.626}','[]',NULL,1,1,0,'{\"x\":-927.554,\"y\":-377.744,\"z\":112.674}',1700000), + (4,'NorthConkerAvenue2044','2044 North Conker Avenue','{\"y\":440.8,\"z\":146.702,\"x\":346.964}','{\"y\":437.456,\"z\":148.394,\"x\":341.683}','{\"y\":435.626,\"z\":148.394,\"x\":339.595}','{\"y\":444.374,\"z\":145.037,\"x\":-212.15144348145}','[]',NULL,1,1,0,'{\"x\":337.726,\"y\":436.985,\"z\":140.77}',1500000), + (5,'WildOatsDrive','3655 Wild Oats Drive','{\"y\":502.696,\"z\":136.421,\"x\":-176.003}','{\"y\":497.817,\"z\":136.653,\"x\":-174.349}','{\"y\":495.069,\"z\":136.666,\"x\":-173.331}','{\"y\":506.412,\"z\":135.0664,\"x\":-177.927}','[]',NULL,1,1,0,'{\"x\":-174.725,\"y\":493.095,\"z\":129.043}',1500000), + (6,'HillcrestAvenue2862','2862 Hillcrest Avenue','{\"y\":596.58,\"z\":142.641,\"x\":-686.554}','{\"y\":591.988,\"z\":144.392,\"x\":-681.728}','{\"y\":590.608,\"z\":144.392,\"x\":-680.124}','{\"y\":599.019,\"z\":142.059,\"x\":-689.492}','[]',NULL,1,1,0,'{\"x\":-680.46,\"y\":588.6,\"z\":136.769}',1500000), + (7,'LowEndApartment','Appartement de base','{\"y\":-1078.735,\"z\":28.4031,\"x\":292.528}','{\"y\":-1007.152,\"z\":-102.002,\"x\":265.845}','{\"y\":-1002.802,\"z\":-100.008,\"x\":265.307}','{\"y\":-1078.669,\"z\":28.401,\"x\":296.738}','[]',NULL,1,1,0,'{\"x\":265.916,\"y\":-999.38,\"z\":-100.008}',562500), + (8,'MadWayneThunder','2113 Mad Wayne Thunder','{\"y\":454.955,\"z\":96.462,\"x\":-1294.433}','{\"y\":448.07,\"z\":96.894,\"x\":-1290.834}','{\"y\":446.322,\"z\":96.899,\"x\":-1289.642}','{\"y\":455.453,\"z\":96.517,\"x\":-1298.851}','[]',NULL,1,1,0,'{\"x\":-1287.306,\"y\":455.901,\"z\":89.294}',1500000), + (9,'HillcrestAvenue2874','2874 Hillcrest Avenue','{\"y\":696.314,\"z\":147.784,\"x\":-853.282}','{\"y\":690.875,\"z\":151.86,\"x\":-859.961}','{\"y\":688.361,\"z\":151.857,\"x\":-859.395}','{\"y\":701.628,\"z\":147.773,\"x\":-855.007}','[]',NULL,1,1,0,'{\"x\":-858.543,\"y\":697.514,\"z\":144.253}',1500000), + (10,'HillcrestAvenue2868','2868 Hillcrest Avenue','{\"y\":620.494,\"z\":141.588,\"x\":-752.82}','{\"y\":618.62,\"z\":143.153,\"x\":-759.317}','{\"y\":617.629,\"z\":143.153,\"x\":-760.789}','{\"y\":621.281,\"z\":141.254,\"x\":-750.919}','[]',NULL,1,1,0,'{\"x\":-762.504,\"y\":618.992,\"z\":135.53}',1500000), + (11,'TinselTowersApt12','Tinsel Towers, Apt 42','{\"y\":37.025,\"z\":42.58,\"x\":-618.299}','{\"y\":58.898,\"z\":97.2,\"x\":-603.301}','{\"y\":58.941,\"z\":97.2,\"x\":-608.741}','{\"y\":30.603,\"z\":42.524,\"x\":-620.017}','[]',NULL,1,1,0,'{\"x\":-622.173,\"y\":54.585,\"z\":96.599}',1700000), + (12,'MiltonDrive','Milton Drive','{\"x\":-775.17,\"y\":312.01,\"z\":84.658}',NULL,NULL,'{\"x\":-775.346,\"y\":306.776,\"z\":84.7}','[]',NULL,0,0,1,NULL,0), + (13,'Modern1Apartment','Appartement Moderne 1',NULL,'{\"x\":-784.194,\"y\":323.636,\"z\":210.997}','{\"x\":-779.751,\"y\":323.385,\"z\":210.997}',NULL,'[\"apa_v_mp_h_01_a\"]','MiltonDrive',0,1,0,'{\"x\":-766.661,\"y\":327.672,\"z\":210.396}',1300000), + (14,'Modern2Apartment','Appartement Moderne 2',NULL,'{\"x\":-786.8663,\"y\":315.764,\"z\":186.913}','{\"x\":-781.808,\"y\":315.866,\"z\":186.913}',NULL,'[\"apa_v_mp_h_01_c\"]','MiltonDrive',0,1,0,'{\"x\":-795.735,\"y\":326.757,\"z\":186.313}',1300000), + (15,'Modern3Apartment','Appartement Moderne 3',NULL,'{\"x\":-774.012,\"y\":342.042,\"z\":195.686}','{\"x\":-779.057,\"y\":342.063,\"z\":195.686}',NULL,'[\"apa_v_mp_h_01_b\"]','MiltonDrive',0,1,0,'{\"x\":-765.386,\"y\":330.782,\"z\":195.08}',1300000), + (16,'Mody1Apartment','Appartement Mode 1',NULL,'{\"x\":-784.194,\"y\":323.636,\"z\":210.997}','{\"x\":-779.751,\"y\":323.385,\"z\":210.997}',NULL,'[\"apa_v_mp_h_02_a\"]','MiltonDrive',0,1,0,'{\"x\":-766.615,\"y\":327.878,\"z\":210.396}',1300000), + (17,'Mody2Apartment','Appartement Mode 2',NULL,'{\"x\":-786.8663,\"y\":315.764,\"z\":186.913}','{\"x\":-781.808,\"y\":315.866,\"z\":186.913}',NULL,'[\"apa_v_mp_h_02_c\"]','MiltonDrive',0,1,0,'{\"x\":-795.297,\"y\":327.092,\"z\":186.313}',1300000), + (18,'Mody3Apartment','Appartement Mode 3',NULL,'{\"x\":-774.012,\"y\":342.042,\"z\":195.686}','{\"x\":-779.057,\"y\":342.063,\"z\":195.686}',NULL,'[\"apa_v_mp_h_02_b\"]','MiltonDrive',0,1,0,'{\"x\":-765.303,\"y\":330.932,\"z\":195.085}',1300000), + (19,'Vibrant1Apartment','Appartement Vibrant 1',NULL,'{\"x\":-784.194,\"y\":323.636,\"z\":210.997}','{\"x\":-779.751,\"y\":323.385,\"z\":210.997}',NULL,'[\"apa_v_mp_h_03_a\"]','MiltonDrive',0,1,0,'{\"x\":-765.885,\"y\":327.641,\"z\":211.396}',1300000), + (20,'Vibrant2Apartment','Appartement Vibrant 2',NULL,'{\"x\":-786.8663,\"y\":315.764,\"z\":186.913}','{\"x\":-781.808,\"y\":315.866,\"z\":186.913}',NULL,'[\"apa_v_mp_h_03_c\"]','MiltonDrive',0,1,0,'{\"x\":-795.607,\"y\":327.344,\"z\":186.313}',1300000), + (21,'Vibrant3Apartment','Appartement Vibrant 3',NULL,'{\"x\":-774.012,\"y\":342.042,\"z\":195.686}','{\"x\":-779.057,\"y\":342.063,\"z\":195.686}',NULL,'[\"apa_v_mp_h_03_b\"]','MiltonDrive',0,1,0,'{\"x\":-765.525,\"y\":330.851,\"z\":195.085}',1300000), + (22,'Sharp1Apartment','Appartement Pèrcant 1',NULL,'{\"x\":-784.194,\"y\":323.636,\"z\":210.997}','{\"x\":-779.751,\"y\":323.385,\"z\":210.997}',NULL,'[\"apa_v_mp_h_04_a\"]','MiltonDrive',0,1,0,'{\"x\":-766.527,\"y\":327.89,\"z\":210.396}',1300000), + (23,'Sharp2Apartment','Appartement Pèrcant 2',NULL,'{\"x\":-786.8663,\"y\":315.764,\"z\":186.913}','{\"x\":-781.808,\"y\":315.866,\"z\":186.913}',NULL,'[\"apa_v_mp_h_04_c\"]','MiltonDrive',0,1,0,'{\"x\":-795.642,\"y\":326.497,\"z\":186.313}',1300000), + (24,'Sharp3Apartment','Appartement Pèrcant 3',NULL,'{\"x\":-774.012,\"y\":342.042,\"z\":195.686}','{\"x\":-779.057,\"y\":342.063,\"z\":195.686}',NULL,'[\"apa_v_mp_h_04_b\"]','MiltonDrive',0,1,0,'{\"x\":-765.503,\"y\":331.318,\"z\":195.085}',1300000), + (25,'Monochrome1Apartment','Appartement Monochrome 1',NULL,'{\"x\":-784.194,\"y\":323.636,\"z\":210.997}','{\"x\":-779.751,\"y\":323.385,\"z\":210.997}',NULL,'[\"apa_v_mp_h_05_a\"]','MiltonDrive',0,1,0,'{\"x\":-766.289,\"y\":328.086,\"z\":210.396}',1300000), + (26,'Monochrome2Apartment','Appartement Monochrome 2',NULL,'{\"x\":-786.8663,\"y\":315.764,\"z\":186.913}','{\"x\":-781.808,\"y\":315.866,\"z\":186.913}',NULL,'[\"apa_v_mp_h_05_c\"]','MiltonDrive',0,1,0,'{\"x\":-795.692,\"y\":326.762,\"z\":186.313}',1300000), + (27,'Monochrome3Apartment','Appartement Monochrome 3',NULL,'{\"x\":-774.012,\"y\":342.042,\"z\":195.686}','{\"x\":-779.057,\"y\":342.063,\"z\":195.686}',NULL,'[\"apa_v_mp_h_05_b\"]','MiltonDrive',0,1,0,'{\"x\":-765.094,\"y\":330.976,\"z\":195.085}',1300000), + (28,'Seductive1Apartment','Appartement Séduisant 1',NULL,'{\"x\":-784.194,\"y\":323.636,\"z\":210.997}','{\"x\":-779.751,\"y\":323.385,\"z\":210.997}',NULL,'[\"apa_v_mp_h_06_a\"]','MiltonDrive',0,1,0,'{\"x\":-766.263,\"y\":328.104,\"z\":210.396}',1300000), + (29,'Seductive2Apartment','Appartement Séduisant 2',NULL,'{\"x\":-786.8663,\"y\":315.764,\"z\":186.913}','{\"x\":-781.808,\"y\":315.866,\"z\":186.913}',NULL,'[\"apa_v_mp_h_06_c\"]','MiltonDrive',0,1,0,'{\"x\":-795.655,\"y\":326.611,\"z\":186.313}',1300000), + (30,'Seductive3Apartment','Appartement Séduisant 3',NULL,'{\"x\":-774.012,\"y\":342.042,\"z\":195.686}','{\"x\":-779.057,\"y\":342.063,\"z\":195.686}',NULL,'[\"apa_v_mp_h_06_b\"]','MiltonDrive',0,1,0,'{\"x\":-765.3,\"y\":331.414,\"z\":195.085}',1300000), + (31,'Regal1Apartment','Appartement Régal 1',NULL,'{\"x\":-784.194,\"y\":323.636,\"z\":210.997}','{\"x\":-779.751,\"y\":323.385,\"z\":210.997}',NULL,'[\"apa_v_mp_h_07_a\"]','MiltonDrive',0,1,0,'{\"x\":-765.956,\"y\":328.257,\"z\":210.396}',1300000), + (32,'Regal2Apartment','Appartement Régal 2',NULL,'{\"x\":-786.8663,\"y\":315.764,\"z\":186.913}','{\"x\":-781.808,\"y\":315.866,\"z\":186.913}',NULL,'[\"apa_v_mp_h_07_c\"]','MiltonDrive',0,1,0,'{\"x\":-795.545,\"y\":326.659,\"z\":186.313}',1300000), + (33,'Regal3Apartment','Appartement Régal 3',NULL,'{\"x\":-774.012,\"y\":342.042,\"z\":195.686}','{\"x\":-779.057,\"y\":342.063,\"z\":195.686}',NULL,'[\"apa_v_mp_h_07_b\"]','MiltonDrive',0,1,0,'{\"x\":-765.087,\"y\":331.429,\"z\":195.123}',1300000), + (34,'Aqua1Apartment','Appartement Aqua 1',NULL,'{\"x\":-784.194,\"y\":323.636,\"z\":210.997}','{\"x\":-779.751,\"y\":323.385,\"z\":210.997}',NULL,'[\"apa_v_mp_h_08_a\"]','MiltonDrive',0,1,0,'{\"x\":-766.187,\"y\":328.47,\"z\":210.396}',1300000), + (35,'Aqua2Apartment','Appartement Aqua 2',NULL,'{\"x\":-786.8663,\"y\":315.764,\"z\":186.913}','{\"x\":-781.808,\"y\":315.866,\"z\":186.913}',NULL,'[\"apa_v_mp_h_08_c\"]','MiltonDrive',0,1,0,'{\"x\":-795.658,\"y\":326.563,\"z\":186.313}',1300000), + (36,'Aqua3Apartment','Appartement Aqua 3',NULL,'{\"x\":-774.012,\"y\":342.042,\"z\":195.686}','{\"x\":-779.057,\"y\":342.063,\"z\":195.686}',NULL,'[\"apa_v_mp_h_08_b\"]','MiltonDrive',0,1,0,'{\"x\":-765.287,\"y\":331.084,\"z\":195.086}',1300000), + (37,'IntegrityWay','4 Integrity Way','{\"x\":-47.804,\"y\":-585.867,\"z\":36.956}',NULL,NULL,'{\"x\":-54.178,\"y\":-583.762,\"z\":35.798}','[]',NULL,0,0,1,NULL,0), + (38,'IntegrityWay28','4 Integrity Way - Apt 28',NULL,'{\"x\":-31.409,\"y\":-594.927,\"z\":79.03}','{\"x\":-26.098,\"y\":-596.909,\"z\":79.03}',NULL,'[]','IntegrityWay',0,1,0,'{\"x\":-11.923,\"y\":-597.083,\"z\":78.43}',1700000), + (39,'IntegrityWay30','4 Integrity Way - Apt 30',NULL,'{\"x\":-17.702,\"y\":-588.524,\"z\":89.114}','{\"x\":-16.21,\"y\":-582.569,\"z\":89.114}',NULL,'[]','IntegrityWay',0,1,0,'{\"x\":-26.327,\"y\":-588.384,\"z\":89.123}',1700000), + (40,'DellPerroHeights','Dell Perro Heights','{\"x\":-1447.06,\"y\":-538.28,\"z\":33.74}',NULL,NULL,'{\"x\":-1440.022,\"y\":-548.696,\"z\":33.74}','[]',NULL,0,0,1,NULL,0), + (41,'DellPerroHeightst4','Dell Perro Heights - Apt 28',NULL,'{\"x\":-1452.125,\"y\":-540.591,\"z\":73.044}','{\"x\":-1455.435,\"y\":-535.79,\"z\":73.044}',NULL,'[]','DellPerroHeights',0,1,0,'{\"x\":-1467.058,\"y\":-527.571,\"z\":72.443}',1700000), + (42,'DellPerroHeightst7','Dell Perro Heights - Apt 30',NULL,'{\"x\":-1451.562,\"y\":-523.535,\"z\":55.928}','{\"x\":-1456.02,\"y\":-519.209,\"z\":55.929}',NULL,'[]','DellPerroHeights',0,1,0,'{\"x\":-1457.026,\"y\":-530.219,\"z\":55.937}',1700000) +; + diff --git a/server/main.lua b/server/main.lua new file mode 100644 index 00000000..cff86548 --- /dev/null +++ b/server/main.lua @@ -0,0 +1,560 @@ +ESX = nil + +TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end) + +function GetProperty(name) + + for i=1, #Config.Properties, 1 do + if Config.Properties[i].name == name then + return Config.Properties[i] + end + end + +end + +function SetPropertyOwned(name, price, rented, owner) + + MySQL.Async.execute( + 'INSERT INTO owned_properties (name, price, rented, owner) VALUES (@name, @price, @rented, @owner)', + { + ['@name'] = name, + ['@price'] = price, + ['@rented'] = (rented and 1 or 0), + ['@owner'] = owner + }, + function(rowsChanged) + + local xPlayers = ESX.GetPlayers() + + for k,v in pairs(xPlayers) do + if v.identifier == owner then + + TriggerClientEvent('esx_property:setPropertyOwned', v.source, name, true) + + if rented then + TriggerClientEvent('esx:showNotification', v.source, 'Vous avez loué une propriété pour $' .. price) + else + TriggerClientEvent('esx:showNotification', v.source, 'Vous avez acheté une propriété pour $' .. price) + end + + break + end + end + + end + ) + +end + +function RemoveOwnedProperty(name, owner) + + MySQL.Async.execute( + 'DELETE FROM owned_properties WHERE name = @name AND owner = @owner', + { + ['@name'] = name, + ['@owner'] = owner + }, + function(rowsChanged) + + local xPlayers = ESX.GetPlayers() + + for k,v in pairs(xPlayers) do + if v.identifier == owner then + TriggerClientEvent('esx_property:setPropertyOwned', v.source, name, false) + TriggerClientEvent('esx:showNotification', v.source, 'Vous avez rendu une propriété') + break + end + end + + end + ) + +end + +AddEventHandler('onMySQLReady', function () + + MySQL.Async.fetchAll('SELECT * FROM properties', {}, function(properties) + + for i=1, #properties, 1 do + + local entering = nil + local exit = nil + local inside = nil + local outside = nil + local isSingle = nil + local isRoom = nil + local isGateway = nil + local roomMenu = nil + + if properties[i].entering ~= nil then + entering = json.decode(properties[i].entering) + end + + if properties[i].exit ~= nil then + exit = json.decode(properties[i].exit) + end + + if properties[i].inside ~= nil then + inside = json.decode(properties[i].inside) + end + + if properties[i].outside ~= nil then + outside = json.decode(properties[i].outside) + end + + if properties[i].is_single == 0 then + isSingle = false + else + isSingle = true + end + + if properties[i].is_room == 0 then + isRoom = false + else + isRoom = true + end + + if properties[i].is_gateway == 0 then + isGateway = false + else + isGateway = true + end + + if properties[i].room_menu ~= nil then + roomMenu = json.decode(properties[i].room_menu) + end + + table.insert(Config.Properties, { + name = properties[i].name, + label = properties[i].label, + entering = entering, + exit = exit, + inside = inside, + outside = outside, + ipls = json.decode(properties[i].ipls), + gateway = properties[i].gateway, + isSingle = isSingle, + isRoom = isRoom, + isGateway = isGateway, + roomMenu = roomMenu, + price = properties[i].price + }) + + end + + end) + +end) + +AddEventHandler('esx_ownedproperty:getOwnedProperties', function(cb) + + MySQL.Async.fetchAll( + 'SELECT * FROM owned_properties', + {}, + function(result) + + local properties = {} + + for i=1, #result, 1 do + + table.insert(properties, { + id = result[i].id, + name = result[i].name, + price = result[i].price, + rented = (rented == 1 and true or false), + owner = result[i].owner, + }) + end + + cb(properties) + + end + ) + +end) + +AddEventHandler('esx_property:setPropertyOwned', function(name, price, rented, owner) + SetPropertyOwned(name, price, rented, owner) +end) + +AddEventHandler('esx_property:removeOwnedProperty', function(name, owner) + RemoveOwnedProperty(name, owner) +end) + +RegisterServerEvent('esx_property:rentProperty') +AddEventHandler('esx_property:rentProperty', function(propertyName) + + local xPlayer = ESX.GetPlayerFromId(source) + local property = GetProperty(propertyName) + + SetPropertyOwned(propertyName, property.price / 200, true, xPlayer.identifier) + +end) + +RegisterServerEvent('esx_property:buyProperty') +AddEventHandler('esx_property:buyProperty', function(propertyName) + + local xPlayer = ESX.GetPlayerFromId(source) + local property = GetProperty(propertyName) + + if property.price <= xPlayer.get('money') then + + xPlayer.removeMoney(property.price) + SetPropertyOwned(propertyName, property.price, false, xPlayer.identifier) + + else + TriggerClientEvent('esx:showNotification', source, 'Vous n\'avez pas assez d\'argent') + end + +end) + +RegisterServerEvent('esx_property:removeOwnedProperty') +AddEventHandler('esx_property:removeOwnedProperty', function(propertyName) + + local xPlayer = ESX.GetPlayerFromId(source) + + RemoveOwnedProperty(propertyName, xPlayer.identifier) + +end) + +AddEventHandler('esx_property:removeOwnedPropertyIdentifier', function(propertyName, identifier) + RemoveOwnedProperty(propertyName, identifier) +end) + +RegisterServerEvent('esx_property:saveLastProperty') +AddEventHandler('esx_property:saveLastProperty', function(property) + + local xPlayer = ESX.GetPlayerFromId(source) + + MySQL.Async.execute( + 'UPDATE users SET last_property = @last_property WHERE identifier = @identifier', + { + ['@last_property'] = property, + ['@identifier'] = xPlayer.identifier + } + ) + +end) + +RegisterServerEvent('esx_property:deleteLastProperty') +AddEventHandler('esx_property:deleteLastProperty', function() + local xPlayer = ESX.GetPlayerFromId(source) + + MySQL.Async.execute( + 'UPDATE users SET last_property = NULL WHERE identifier = @identifier', + { + ['@identifier'] = xPlayer.identifier + } + ) +end) + +RegisterServerEvent('esx_property:getItem') +AddEventHandler('esx_property:getItem', function(type, item, count) + + local xPlayer = ESX.GetPlayerFromId(source) + + if type == 'item_standard' then + + TriggerEvent('esx_addoninventory:getInventory', 'property', xPlayer.identifier, function(inventory) + + local roomItemCount = inventory.getItem(item).count + + if roomItemCount >= count then + inventory.removeItem(item, count) + xPlayer.addInventoryItem(item, count) + else + TriggerClientEvent('esx:showNotification', source, 'Quantité invalide') + end + + end) + + end + + if type == 'item_account' then + + TriggerEvent('esx_addonaccount:getAccount', 'property_' .. item, xPlayer.identifier, function(account) + + local roomAccountMoney = account.money + + if roomAccountMoney >= count then + account.removeMoney(count) + xPlayer.addAccountMoney(item, count) + else + TriggerClientEvent('esx:showNotification', source, 'Montant invalide') + end + + end) + + end + + if type == 'item_weapon' then + + TriggerEvent('esx_datastore:getDataStore', 'property', xPlayer.identifier, function(store) + + local storeWeapons = store.get('weapons') + + if storeWeapons == nil then + storeWeapons = {} + end + + local weaponName = nil + local ammo = nil + + for i=1, #storeWeapons, 1 do + if storeWeapons[i].name == item then + + weaponName = storeWeapons[i].name + ammo = storeWeapons[i].ammo + + table.remove(storeWeapons, i) + + break + end + end + + store.set('weapons', storeWeapons) + + xPlayer.addWeapon(weaponName, ammo) + + end) + + end + +end) + +RegisterServerEvent('esx_property:putItem') +AddEventHandler('esx_property:putItem', function(type, item, count) + + local xPlayer = ESX.GetPlayerFromId(source) + + if type == 'item_standard' then + + local playerItemCount = xPlayer.getInventoryItem(item).count + + if playerItemCount >= count then + + xPlayer.removeInventoryItem(item, count) + + TriggerEvent('esx_addoninventory:getInventory', 'property', xPlayer.identifier, function(inventory) + inventory.addItem(item, count) + end) + + else + TriggerClientEvent('esx:showNotification', source, 'Quantité invalide') + end + + end + + if type == 'item_account' then + + local playerAccountMoney = xPlayer.getAccount(item).money + + if playerAccountMoney >= count then + + xPlayer.removeAccountMoney(item, count) + + TriggerEvent('esx_addonaccount:getAccount', 'property_' .. item, xPlayer.identifier, function(account) + account.addMoney(count) + end) + + else + TriggerClientEvent('esx:showNotification', source, 'Montant invalide') + end + + end + + if type == 'item_weapon' then + + TriggerEvent('esx_datastore:getDataStore', 'property', xPlayer.identifier, function(store) + + local storeWeapons = store.get('weapons') + + if storeWeapons == nil then + storeWeapons = {} + end + + table.insert(storeWeapons, { + name = item, + ammo = count + }) + + store.set('weapons', storeWeapons) + + xPlayer.removeWeapon(item) + + end) + + end + +end) + +ESX.RegisterServerCallback('esx_property:getProperties', function(source, cb) + cb(Config.Properties) +end) + +ESX.RegisterServerCallback('esx_property:getOwnedProperties', function(source, cb) + + local xPlayer = ESX.GetPlayerFromId(source) + + MySQL.Async.fetchAll( + 'SELECT * FROM owned_properties WHERE owner = @owner', + { + ['@owner'] = xPlayer.identifier + }, + function(ownedProperties) + + local properties = {} + + for i=1, #ownedProperties, 1 do + table.insert(properties, ownedProperties[i].name) + end + + cb(properties) + end + ) + +end) + +ESX.RegisterServerCallback('esx_property:getLastProperty', function(source, cb) + + local xPlayer = ESX.GetPlayerFromId(source) + + MySQL.Async.fetchAll( + 'SELECT * FROM users WHERE identifier = @identifier', + { + ['@identifier'] = xPlayer.identifier + }, + function(users) + cb(users[1].last_property) + end + ) + +end) + +ESX.RegisterServerCallback('esx_property:getPropertyInventory', function(source, cb) + + local xPlayer = ESX.GetPlayerFromId(source) + local blackMoney = 0 + local items = {} + local weapons = {} + + TriggerEvent('esx_addonaccount:getAccount', 'property_black_money', xPlayer.identifier, function(account) + blackMoney = account.money + end) + + TriggerEvent('esx_addoninventory:getInventory', 'property', xPlayer.identifier, function(inventory) + items = inventory.items + end) + + TriggerEvent('esx_datastore:getDataStore', 'property', xPlayer.identifier, function(store) + + local storeWeapons = store.get('weapons') + + if storeWeapons ~= nil then + weapons = storeWeapons + end + + end) + + cb({ + blackMoney = blackMoney, + items = items, + weapons = weapons + }) + +end) + +ESX.RegisterServerCallback('esx_property:getPlayerInventory', function(source, cb) + + local xPlayer = ESX.GetPlayerFromId(source) + local blackMoney = xPlayer.getAccount('black_money').money + local items = xPlayer.inventory + + cb({ + blackMoney = blackMoney, + items = items + }) + +end) + +ESX.RegisterServerCallback('esx_property:getPlayerDressing', function(source, cb) + + local xPlayer = ESX.GetPlayerFromId(source) + local dressing = {} + + TriggerEvent('esx_datastore:getDataStore', 'property', xPlayer.identifier, function(store) + + local storeDressing = store.get('dressing') + + if storeDressing ~= nil then + dressing = storeDressing + end + + end) + + cb(dressing) + +end) + +function PayRent(d, h, m) + + MySQL.Async.fetchAll( + 'SELECT * FROM owned_properties WHERE rented = 1', + {}, + function(result) + + local xPlayers = ESX.GetPlayers() + + for i=1, #result, 1 do + + local foundPlayer = false + local xPlayer = nil + + for k,v in pairs(xPlayers) do + if v.identifier == result[i].owner then + foundPlayer = true + xPlayer = v + end + end + + if foundPlayer then + + xPlayer.removeMoney(result[i].price) + TriggerClientEvent('esx:showNotification', xPlayer.source, 'Vous avez ~g~payé~s~ votre loyer : ~g~$' .. result[i].price) + + else + + MySQL.Async.fetchAll( + 'SELECT * FROM users WHERE identifier = @identifier', + { + ['@identifier'] = result[i].owner + }, + function(result2) + + local money = result2[1].money + money = money - result[i].price + + MySQL.Async.execute( + 'UPDATE users SET money = @money WHERE identifier = @identifier', + { + ['@money'] = money, + ['@identifier'] = result[i].owner + } + ) + end + ) + + end + + TriggerEvent('esx_addonaccount:getSharedAccount', 'society_realestateagent', function(account) + account.addMoney(result[i].price) + end) + + end + + end + ) + +end + +TriggerEvent('cron:runAt', 22, 0, PayRent) \ No newline at end of file