diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/README.md b/README.md new file mode 100644 index 0000000..1135cdf --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# just_apartments + diff --git a/SQL.sql b/SQL.sql new file mode 100644 index 0000000..4a44815 --- /dev/null +++ b/SQL.sql @@ -0,0 +1,44 @@ +-- Dumping structure for table es_extended.apartments +CREATE TABLE IF NOT EXISTS `apartments` ( + `Name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `Price` int(11) DEFAULT 5000, + `rentLength` int(11) DEFAULT 30 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Dumping data for table es_extended.apartments: ~6 rows (approximately) +/*!40000 ALTER TABLE `apartments` DISABLE KEYS */; +INSERT INTO `apartments` (`Name`, `Price`, `rentLength`) VALUES + ('4IntegrityWay', 5000, 30), + ('DelPerroHeights', 5000, 30), + ('EclipseTowers', 7500, 30), + ('RichardsMajestic', 5000, 30), + ('TinselTowers', 5000, 30), + ('WeazelPlazaApartments', 5000, 30); +/*!40000 ALTER TABLE `apartments` ENABLE KEYS */; + +-- Dumping structure for table es_extended.apartment_keys +CREATE TABLE IF NOT EXISTS `apartment_keys` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `appt_id` int(11) NOT NULL DEFAULT 0, + `appt_name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `player` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0', + `player_name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `appt_owner` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Dumping structure for table es_extended.owned_apartments +CREATE TABLE IF NOT EXISTS `owned_apartments` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `owner` varchar(46) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `apartment` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `renew` tinyint(4) NOT NULL DEFAULT 1, + `expired` tinyint(4) NOT NULL DEFAULT 0, + `lastPayment` int(11) DEFAULT NULL, + `renewDate` int(11) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +ALTER TABLE `users` + ADD `last_property` varchar(50) DEFAULT NULL, +; diff --git a/client.lua b/client.lua new file mode 100644 index 0000000..88dc008 --- /dev/null +++ b/client.lua @@ -0,0 +1,718 @@ +local atDoor +local atExit +local state = nil +local Instanced = false +local InWardrobe = false +local AtStash = false +local allMyOutfits = {} +local currentApartment = nil +local currentApartmentLabel = nil +local currentApartmentID = nil + +Citizen.CreateThread(function() + while ESX == nil do + TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end) + Citizen.Wait(0) + end + + while ESX.GetPlayerData().job == nil do + Citizen.Wait(10) + end + + ESX.PlayerData = ESX.GetPlayerData() +end) + +local function Blips(coords, type, label, job, blipOptions) + if job then return end + if blip == false then return end + local blip = AddBlipForCoord(coords) + SetBlipSprite(blip, blipOptions?.sprite or 357) + SetBlipScale(blip, blipOptions?.scale or 0.8) + SetBlipColour(blip, blipOptions?.colour ~= nil and blipOptions.colour or type == 'car' and Config.BlipColors.Car or type == 'boat' and Config.BlipColors.Boat or Config.BlipColors.Aircraft) + SetBlipAsShortRange(blip, true) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString("Apartment") + EndTextCommandSetBlipName(blip) +end + +for k, v in pairs(Config.Apartments) do + exports["bt-polyzone"]:AddBoxZone(v.zone.name.."Entrance", vector3(v.entrance.x, v.entrance.y, v.entrance.z), 3, 3, { + name = v.zone.name.."Entrance", + heading = v.entrance.h, + debugPoly = false, + minZ = (v.entrance.z - 1), + maxZ = (v.entrance.z + 1.5), + } + ) + if v.wardrobe ~= nil then + exports["bt-polyzone"]:AddBoxZone(v.zone.name.."Wardrobe", vector3(v.wardrobe.x, v.wardrobe.y, v.wardrobe.z), 3, 3, { + name = v.zone.name.."Wardrobe", + heading = v.wardrobe.h, + debugPoly = false, + minZ = (v.wardrobe.z - 1), + maxZ = (v.wardrobe.z + 1.5), + } + ) + end + if v.stash ~= nil then + exports["bt-polyzone"]:AddBoxZone(v.zone.name.."Stash", vector3(v.stash.x, v.stash.y, v.stash.z), 3, 3, { + name = v.zone.name.."Stash", + heading = v.stash.h, + debugPoly = false, + minZ = (v.stash.z - 1), + maxZ = (v.stash.z + 1.5), + } + ) + end + exports["bt-polyzone"]:AddBoxZone(v.zone.name.."Exit", vector3(v.exit.x, v.exit.y, v.exit.z), 3, 3, { + name = v.zone.name.."Exit", + heading = v.exit.h, + debugPoly = false, + minZ = (v.exit.z - 1), + maxZ = (v.exit.z + 1.5), + } + ) + + if v.blip ~= false then + Blips(vector3(v.entrance.x, v.entrance.y, v.entrance.z), v.type, v.label, v.job, v.blip) + end +end + +RegisterNetEvent('bt-polyzone:enter') +AddEventHandler('bt-polyzone:enter', function(name) + for k, v in pairs(Config.Apartments) do + if v.zone.name.."Entrance" == name then + currentApartment = v.zone.name + currentApartmentLabel = v.label + atDoor = true + if currentApartment == 'AltaStreetAppts' then + lib.showTextUI("[E] Apartments", {icon = "fa-solid fa-building"}) + TriggerEvent('just_apartments:enterExitApartment', v.exit, "Entering") + else + TriggerServerEvent('just_apartments:getOwnedApartments', v.zone.name, v.exit) + end + break + end + end + if Config.BrpFivemAppearance then + for k, v in pairs(Config.Apartments) do + if v.zone.name.."Wardrobe" == name then + if currentApartment == "AltaStreetAppts" then + TriggerEvent('just_apartments:enteredWardrobe') + else + TriggerServerEvent('just_apartments:checkApptOwnership', v.zone.name, "Wardrobe", currentApartmentID) + end + break + end + end + end + if Config.UseOxInventory then + for k, v in pairs(Config.Apartments) do + if v.zone.name.."Stash" == name then + AtStash = true + if currentApartment == "AltaStreetAppts" then + TriggerEvent('just_apartments:useStash', 0, "AltaStreetAppts") + else + TriggerServerEvent('just_apartments:checkApptOwnership', v.zone.name, "Stash", currentApartmentID) + end + break + end + end + end + for k, v in pairs(Config.Apartments) do + if v.zone.name.."Exit" == name then + currentApartment = v.zone.name + currentApartmentLabel = v.label + atExit = true + if v.seperateExitPoint == true then + if currentApartment == 'AltaStreetAppts' then + lib.showTextUI("[E] Exit Apartment", {icon = "fa-solid fa-door-open"}) + TriggerEvent('just_apartments:enterExitApartment', v.exitPoint, "Exiting") + else + TriggerServerEvent('just_apartments:getPlayersAtDoor', v.exitPoint, v.zone.name, currentApartmentID, v.exit) + end + else + TriggerServerEvent('just_apartments:getPlayersAtDoor', v.entrance, v.zone.name, currentApartmentID, v.exit) + end + break + end + end +end) + +RegisterNetEvent('bt-polyzone:exit') +AddEventHandler('bt-polyzone:exit', function(name) + for k, v in pairs(Config.Apartments) do + if v.zone.name.."Entrance" == name then + atDoor = false + lib.hideTextUI() + break + end + end + if Config.BrpFivemAppearance then + for k, v in pairs(Config.Apartments) do + if v.zone.name.."Wardrobe" == name then + InWardrobe = false + lib.hideTextUI() + break + end + end + end + if Config.UseOxInventory then + for k, v in pairs(Config.Apartments) do + if v.zone.name.."Stash" == name then + AtStash = false + lib.hideTextUI() + break + end + end + end + for k, v in pairs(Config.Apartments) do + if v.zone.name.."Exit" == name then + atExit = false + lib.hideTextUI() + break + end + end +end) + +RegisterNetEvent('just_apartments:enteredWardrobe') +AddEventHandler('just_apartments:enteredWardrobe', function () + InWardrobe = true + lib.showTextUI("[E] Wardrobe", {icon = "fa-solid fa-shirt"}) + TriggerEvent('just_apartments:useWardrobe') +end) + +RegisterNetEvent('just_apartments:useWardrobe') +AddEventHandler('just_apartments:useWardrobe', function () + while InWardrobe do + Citizen.Wait(0) + if IsControlPressed(1, 38) then + Citizen.Wait(500) + TriggerEvent('fivem-appearance:useWardrobe') + end + end +end) + +RegisterNetEvent('just_apartments:entranceMenu') +AddEventHandler('just_apartments:entranceMenu', function (data) + Citizen.CreateThread(function () + lib.showTextUI("[E] Apartments", {icon = "fa-solid fa-building"}) + while atDoor or atExit do + if (IsControlJustReleased(0, 54) or IsControlJustReleased(0, 175)) then + TriggerServerEvent('just_apartments:getAppartmentsWithKeys', data, currentApartment) + end + Citizen.Wait(0) + end + end) +end) + +RegisterNetEvent('just_apartments:keyEntryMenu') +AddEventHandler('just_apartments:keyEntryMenu', function (apartments, data) + local data = data + local options = {} + TriggerEvent('just_apartments:leaseMenu', data) + if not data.id then + options = { + { + title = "Purchase "..currentApartmentLabel.." Apartment", + description = "$"..data.price, + event = 'just_apartments:purchaseApartment', + args = { + currentApartment = currentApartment, + coords = data.coords, + id = data.id + } + }, + { + title = "Preview Appartment", + description = "OOOOOO This is nice", + event = 'just_apartments:enterExitApartment', + args = { + coords = data.coords, + enteringExiting = "Viewing" + } + }, + { + title = "Ring Apartment", + description = "Let Me In Now", + arrow = true, + serverEvent = 'just_apartments:getBuildingApartments', + args = { + currentApartment = currentApartment, + coords = data.coords, + id = data.id, + ring = true + } + }, + } + else + options = { + { + title = "Appt: "..data.id, + description = "Enter", + event = 'just_apartments:enterExitApartment', + args = { + coords = data.coords, + enteringExiting = "Entering", + id = data.id + } + }, + { + title = "Change Lease", + description = "Should I Stay Or Should I Go Now", + menu = 'just_apartments:leaseMenu', + }, + { + title = "Ring Apartment", + description = "Let Me In Now", + arrow = true, + serverEvent = 'just_apartments:getBuildingApartments', + args = { + currentApartment = currentApartment, + coords = data.coords, + id = data.id, + ring = true + } + }, + } + end + + if apartments ~= nil then + for i=1, #apartments, 1 do + if apartments[i].id ~= nil then + table.insert(options, { + title = "Shared Apartments", + description = "It's Not Mine But It's Still Nice", + event = 'just_apartments:keyEntry', + args = { + apartments = apartments, + data = data + }, + }) + end + end + end + Citizen.Wait(100) + lib.registerContext({ + id = 'just_apartments:keyEntryMenu', + title = currentApartmentLabel, + options = options + }) + Citizen.Wait(100) + lib.showContext('just_apartments:keyEntryMenu') +end) + +RegisterNetEvent('just_apartments:keyEntry') +AddEventHandler('just_apartments:keyEntry', function (args) + local apartments = args.apartments + local data = args.data + + local options = {} + + for i=1, #apartments, 1 do + if apartments[i].owner ~= identifier then + table.insert(options, { + title = "Appt: "..apartments[i].appt_id, + event = "Enter", + event = 'just_apartments:enterExitApartment', + args = { + coords = data.coords, + enteringExiting = "Entering", + id = apartments[i].appt_id + } + }) + end + end + Citizen.Wait(100) + lib.registerContext({ + id = 'just_apartments:keyEntry', + title = currentApartmentLabel.." Appts", + menu = "just_apartments:keyEntryMenu", + options = options + }) + Citizen.Wait(100) + lib.showContext('just_apartments:keyEntry') +end) + +---------- +-- Ring -- +---------- + + +RegisterNetEvent('just_apartments:ringMenu') +AddEventHandler('just_apartments:ringMenu', function (apartments, data, identifier) + local data = data + local options = {} + + for i=1, #apartments, 1 do + if apartments[i].owner ~= identifier then + table.insert(options, { + title = "Ring Appt: "..apartments[i].id, + event = 'just_apartments:ringAppartment', + args = { + coords = data.coords, + enteringExiting = "Entering", + id = apartments[i].id, + apartment = apartments[i].apartment + } + }) + end + end + Citizen.Wait(100) + lib.registerContext({ + id = 'just_apartments:ringMenu', + title = currentApartmentLabel.." Appts", + menu = "just_apartments:keyEntryMenu", + options = options + }) + Citizen.Wait(100) + lib.showContext('just_apartments:ringMenu') +end) + +RegisterNetEvent('just_apartments:ringAppartment') +AddEventHandler('just_apartments:ringAppartment', function (data) + TriggerServerEvent('just_apartments:alertOwner', data) +end) + +----------- +-- Lease -- +----------- + +RegisterNetEvent('just_apartments:leaseMenu') +AddEventHandler('just_apartments:leaseMenu', function (data) + local data = data + + Citizen.Wait(100) + if data.renew == 1 then + lib.registerContext({ + id = 'just_apartments:leaseMenu', + title = "Change Lease", + menu = "just_apartments:keyEntryMenu", + options = {{ + title = "Cancel Lease", + description = "Renews: "..string.sub(data.renewDate,5,6).."/"..string.sub(data.renewDate,7,8).."/"..string.sub(data.renewDate,1,4).." For $"..data.price, + event = 'just_apartments:changeLease', + args = { + renew = 0, + id = data.id + } + }} + }) + else + lib.registerContext({ + id = 'just_apartments:leaseMenu', + title = "Change Lease", + menu = "just_apartments:keyEntryMenu", + options = {{ + title = "Resume Lease $"..data.price, + description = "Lease Ends: "..string.sub(data.renewDate,5,6).."/"..string.sub(data.renewDate,7,8).."/"..string.sub(data.renewDate,1,4), + event = 'just_apartments:changeLease', + args = { + renew = 1, + id = data.id + } + }} + }) + end +end) + +RegisterNetEvent('just_apartments:changeLease') +AddEventHandler('just_apartments:changeLease', function (data) + TriggerServerEvent('just_apartments:changeLease', data) +end) + +RegisterNetEvent('just_apartments:purchaseApartment') +AddEventHandler('just_apartments:purchaseApartment', function (currentApartment) + TriggerServerEvent('just_apartments:purchaseApartment', currentApartment.currentApartment) + atDoor = false + Citizen.Wait(500) + atDoor = true + TriggerServerEvent('just_apartments:getOwnedApartments', currentApartment.currentApartment, currentApartment.exit) +end) + +RegisterNetEvent('just_apartments:exitMenu') +AddEventHandler('just_apartments:exitMenu', function (coords, playersAtDoor, exitDoor, keyholders) + local playersAtDoor = playersAtDoor + Citizen.CreateThread(function () + local coords = coords + lib.showTextUI("[E] Door", {icon = "fa-solid fa-door-open"}) + while atDoor or atExit do + if (IsControlJustReleased(0, 54) or IsControlJustReleased(0, 175)) then + local options = { + { + title = "Exit Apartment", + description = "Go Out Into The World", + event = 'just_apartments:enterExitApartment', + args = { + coords = coords, + enteringExiting = "Exiting" + } + }, + } + + if keyholders ~= nil then + table.insert(options, { + title = "Manage Keyholders", + description = "Who's Got Keys", + arrow = true, + event = 'just_apartments:keyholderMenu', + args = { + keyholders = keyholders + } + }) + end + if playersAtDoor ~= nil then + for i=1, #playersAtDoor.people, 1 do + table.insert(options, { + title = "Let In: "..playersAtDoor.people[i], + description = "It's Always Nice To Have Company", + arrow = true, + event = 'just_apartments:letPlayerIn', + args = { + coords = exitDoor, + enteringExiting = "Entering", + playersAtDoor = playersAtDoor, + player = playersAtDoor.people[i], + id = currentApartmentID + } + }) + end + end + + Citizen.Wait(100) + lib.registerContext({ + id = 'just_apartments:exitMenu', + title = "Elevator", + options = options + }) + Citizen.Wait(100) + lib.showContext('just_apartments:exitMenu') + end + Citizen.Wait(0) + end + end) +end) + +RegisterNetEvent('just_apartments:keyholderMenu') +AddEventHandler('just_apartments:keyholderMenu', function (data) + local playersAtDoor = playersAtDoor + local keyholders = data.keyholders + local options = {} + + if keyholders ~= nil then + for i=1, #keyholders, 1 do + table.insert(options, { + title = "Name: "..keyholders[i].player_name, + description = "Remove Key", + event = 'just_apartments:removeApartmentKeys', + args = { + id = keyholders[i].id + } + }) + end + end + + Citizen.Wait(100) + lib.registerContext({ + id = 'just_apartments:keyholderMenu', + title = "Keyholders", + menu = "just_apartments:exitMenu", + options = options + }) + Citizen.Wait(100) + lib.showContext('just_apartments:keyholderMenu') +end) + +RegisterNetEvent('just_apartments:removeApartmentKeys') +AddEventHandler('just_apartments:removeApartmentKeys', function (data) + local id = data.id + TriggerServerEvent('just_apartments:removeApartmentKeys', id) +end) + +RegisterNetEvent('just_apartments:letPlayerIn') +AddEventHandler('just_apartments:letPlayerIn', function (data) + TriggerServerEvent('just_apartments:bringPlayerIn', data) +end) + +RegisterNetEvent('just_apartments:enterExitApartment') +AddEventHandler('just_apartments:enterExitApartment', function (coords, enteringExiting) + -- exports.xng_parsingtable:ParsingTable_cl(coords) + if coords.id ~= nil then + currentApartmentID = coords.id + end + Citizen.CreateThread(function () + local coords = coords + local player = PlayerPedId() + while atDoor or atExit do + if currentApartment == 'AltaStreetAppts' then + if (IsControlJustReleased(0, 54) or IsControlJustReleased(0, 175)) then + TriggerEvent("mythic_progbar:client:progress", { + name = "just_apartments_use", + duration = 5000, + label = enteringExiting.." Apartment", + useWhileDead = false, + canCancel = true, + controlDisables = { + disableMovement = true, + disableCarMovement = true, + disableMouse = false, + disableCombat = true, + }, + -- animation = { + -- animDict = "missheistdockssetup1clipboard@idle_a", + -- anim = "idle_a", + -- flags = 561, + -- }, + }, function(status) + if not status then + PlaySoundFrontend(-1, "CLOSED", "MP_PROPERTIES_ELEVATOR_DOORS", 1); + Citizen.Wait(500) + PlaySoundFrontend(-1, "Hack_Success", "DLC_HEIST_BIOLAB_PREP_HACKING_SOUNDS", 0) + Citizen.Wait(500) + PlaySoundFrontend(-1, "OPENED", "MP_PROPERTIES_ELEVATOR_DOORS", 1); + + if enteringExiting == "Entering" then + TriggerServerEvent('instance:set') + TriggerServerEvent('just_apartments:updateLastApartment', 'AltaStreetAppts') + else + TriggerServerEvent('instance:set', 0) + currentApartment = nil + currentApartmentLabel = nil + currentApartmentID = nil + TriggerServerEvent('just_apartments:updateLastApartment', nil) + end + atDoor = false + atExit = false + SetEntityCoords(player, coords.x, coords.y, coords.z) + SetEntityHeading(player, coords.h) + end + end) + end + else + atDoor = false + atExit = false + TriggerEvent("mythic_progbar:client:progress", { + name = "just_apartments_use", + duration = 5000, + label = coords.enteringExiting.." Apartment", + useWhileDead = false, + canCancel = true, + controlDisables = { + disableMovement = true, + disableCarMovement = true, + disableMouse = false, + disableCombat = true, + }, + -- animation = { + -- animDict = "missheistdockssetup1clipboard@idle_a", + -- anim = "idle_a", + -- flags = 561, + -- }, + }, function(status) + if not status then + PlaySoundFrontend(-1, "CLOSED", "MP_PROPERTIES_ELEVATOR_DOORS", 1); + Citizen.Wait(500) + PlaySoundFrontend(-1, "Hack_Success", "DLC_HEIST_BIOLAB_PREP_HACKING_SOUNDS", 0) + Citizen.Wait(500) + PlaySoundFrontend(-1, "OPENED", "MP_PROPERTIES_ELEVATOR_DOORS", 1); + + if coords.enteringExiting == "Entering" then + TriggerServerEvent('instance:setNamed', currentApartment..coords.id) + TriggerServerEvent('just_apartments:updateLastApartment', currentApartment.." "..coords.id) + currentApartmentID = coords.id + elseif coords.enteringExiting == "Exiting" then + if state == "Viewing" then + TriggerServerEvent('instance:set', 0) + TriggerServerEvent('just_apartments:updateLastApartment', nil) + else + TriggerServerEvent('instance:setNamed', 0) + TriggerServerEvent('just_apartments:updateLastApartment', nil) + end + state = nil + currentApartment = nil + currentApartmentLabel = nil + currentApartmentID = nil + elseif coords.enteringExiting == "Viewing" then + state = "Viewing" + TriggerServerEvent('instance:set') + end + atDoor = false + atExit = false + SetEntityCoords(player, coords.coords.x, coords.coords.y, coords.coords.z) + SetEntityHeading(player, coords.coords.h) + end + end) + end + Citizen.Wait(0) + end + end) +end) + +---------- +-- Keys -- +---------- + +RegisterCommand('giveApptKey', function() + TriggerEvent('just_apartments:givePlayerKeys') +end, false) + +RegisterNetEvent('just_apartments:givePlayerKeys') +AddEventHandler('just_apartments:givePlayerKeys', function () + local playerPed = PlayerPedId() + local closestPlayer, closestDistance = ESX.Game.GetClosestPlayer() + if closestPlayer ~= -1 and closestDistance <= 4.0 then + local data = { target = GetPlayerServerId(closestPlayer), appt_id = currentApartmentID, appt_name = currentApartment } + -- exports.xng_parsingtable:ParsingTable_cl(data) + TriggerServerEvent('just_apartments:giveKeys', data) + end +end) + +RegisterNetEvent('just_apartments:removePlayerKeys') +AddEventHandler('just_apartments:removePlayerKeys', function () + TriggerServerEvent('just_apartments:removeKeys', data) +end) + +----------- +-- Stash -- +----------- + +RegisterNetEvent('just_apartments:enteredStash') +AddEventHandler('just_apartments:enteredStash', function (ApptID, apartment) + local ApptID = ApptID + local apartment = apartment + AtStash = true + TriggerEvent('just_apartments:useStash', ApptID, apartment) +end) + +RegisterNetEvent('just_apartments:useStash') +AddEventHandler('just_apartments:useStash', function (ApptID, apartment) + local ApptID = ApptID + local apartment = apartment + lib.showTextUI("[E] Stash", {icon = "fa-solid fa-box-open"}) + while AtStash do + Citizen.Wait(0) + if IsControlPressed(1, 38) then + Citizen.Wait(100) + exports.ox_inventory:openInventory('stash', {id = (apartment..ApptID.."Stash"), owner = ApptID}) + end + end +end) + +----------- +-- Relog -- +----------- + +AddEventHandler('onClientResourceStart', function(resource) + TriggerServerEvent('just_apartments:getLastApartment') +end) + +RegisterNetEvent('just_apartments:spawnInProperty') +AddEventHandler('just_apartments:spawnInProperty', function (property, ApptID) + currentApartment = property + if currentApartment == "AltaStreetAppts" then + TriggerServerEvent('instance:set') + else + TriggerServerEvent('instance:setNamed', property..ApptID) + currentApartmentID = ApptID + end +end) \ No newline at end of file diff --git a/config.lua b/config.lua new file mode 100644 index 0000000..bca6fc0 --- /dev/null +++ b/config.lua @@ -0,0 +1,128 @@ +Config = {} + +Config.UseOxInventory = true + +Config.BrpFivemAppearance = true + +Config.Apartments = { + { + label = "Alta Street Apartments", + type = "StarterApartment", + seperateExitPoint = true, + entrance = {x = -270.78894042969, y = -958.21881103516, z = 31.227436065674, h = 118.76303863525}, + exit = {x = -268.41479492188, y = -963.39013671875, z = 21.912317276001, h = 356.97747802734}, + exitPoint = {x = -266.36004638672, y = -955.48510742188, z = 31.227432250977, h = 118.27416992188}, + stash = {x = -268.89364624023, y = -958.93017578125, z = 21.917707443237, h = 60.0}, + wardrobe = {x = -267.76, y = -956.6, z = 21.92, l = 2.4, w = 2.8, h = 0, minZ = 20.9, maxZ = 23.22}, + zone = {name = 'AltaStreetAppts', x = -266.8, y = -959.7, z = 21.9, l = 8.7, w = 5.4, h = 0, minZ = 19, maxZ = 23}, + blip = { + scale = 0.7, + sprite = 475, + colour = 32 + }, + }, + + ---------------- + -- Apartments -- + ---------------- + { + label = "4 Integrity Way", + type = "Apartment", + seperateExitPoint = true, + entrance = {x = -43.677406311035, y = -584.69696044922, z = 38.161270141602, h = 254.36935424805}, + exit = {x = -31.511259078979, y = -594.93328857422, z = 80.030891418457, h = 250.01995849609}, + exitPoint = {x = -44.424266815186, y = -587.35302734375, z = 38.160839080811, h = 67.393547058105}, + stash = {x = -11.746654510498, y = -597.97283935547, z = 79.430206298828, h = 153.85530090332}, + wardrobe = {x = -38.2, y = -589.3, z = 78.8, l = 4.8, w = 6.4, h = 340, minZ = 77, maxZ = 80}, + zone = {name = '4IntegrityWay', x = 1145.3, y = -778.4, z = 7.6, l = 30.0, w = 20.0, h = 90, minZ = 55, maxZ = 60}, + blip = { + scale = 0.7, + sprite = 475, + colour = 32 + }, + }, + + { + label = "Weazel Plaza Apartments", + type = "Apartment", + seperateExitPoint = true, + entrance = {x = -908.98883056641, y = -446.12484741211, z = 39.605274200439, h = 114.50199127197}, + exit = {x = -890.73431396484, y = -436.75045776367, z = 121.60707092285, h = 27.384859085083}, + exitPoint = {x = -906.2998046875, y = -451.72274780273, z = 39.605285644531, h = 120.24710083008}, + stash = {x = -898.38647460938, y = -440.6930847168, z = 121.61553955078, h = 113.11880493164}, + wardrobe = {x = -909.6, y = -445.4, z = 115.4, l = 4.6, w = 5.2, h = 27, minZ = 114, maxZ = 117}, + zone = {name = 'WeazelPlazaApartments', x = 1145.3, y = -778.4, z = 7.6, l = 30.0, w = 20.0, h = 90, minZ = 55, maxZ = 60}, + blip = { + scale = 0.7, + sprite = 475, + colour = 32 + }, + }, + + { + label = "Richards Majestic", + type = "Apartment", + seperateExitPoint = true, + entrance = {x = -935.89715576172, y = -378.78204345703, z = 38.961292266846, h = 114.38324737549}, + exit = {x = -907.18048095703, y = -372.34390258789, z = 109.44027709961, h = 29.907409667969}, + exitPoint = {x = -932.91668701172, y = -383.57510375977, z = 38.96129989624, h = 119.42089080811}, + stash = {x = -914.99621582031, y = -376.62802124023, z = 109.4489440918, h = 116.96913146973}, + wardrobe = {x = -926.1, y = -381.5, z = 103.2, l = 4.2, w = 5.0, h = 27, minZ = 101, maxZ = 104.2}, + zone = {name = 'RichardsMajestic', x = 1145.3, y = -778.4, z = 57.6, l = 30.0, w = 20.0, h = 90, minZ = 55, maxZ = 60}, + blip = { + scale = 0.7, + sprite = 475, + colour = 32 + }, + }, + + { + label = "Del Perro Heights", + type = "Apartment", + seperateExitPoint = false, + entrance = {x = -1447.6293945313, y = -537.32611083984, z = 34.740154266357, h = 215.23828125}, + exit = {x = -1452.2774658203, y = -540.5205078125, z = 74.044303894043, h = 40.185394287109}, + stash = {x = -1466.7415771484, y = -526.98858642578, z = 73.443618774414, h = 306.0309753418}, + wardrobe = {x = -1450.2, y = -549.3, z = 72.8, l = 4.0, w = 4.4, h = 35, minZ = 70, maxZ = 74}, + zone = {name = 'DelPerroHeights', x = 1145.3, y = -778.4, z = 7.6, l = 30.0, w = 20.0, h = 90, minZ = 55, maxZ = 60}, + blip = { + scale = 0.7, + sprite = 475, + colour = 32 + }, + }, + + { + label = "Tinsel Towers", + type = "Apartment", + seperateExitPoint = true, + entrance = {x = -621.08837890625, y = 46.244003295898, z = 43.591468811035, h = 186.38636779785}, + exit = {x = -602.87280273438, y = 58.898902893066, z = 98.200187683105, h = 90.942649841309}, + exitPoint = {x = -614.61468505859, y = 46.540714263916, z = 43.591468811035, h = 184.96089172363}, + stash = {x = -622.54583740234, y = 54.491561889648, z = 97.599517822266, h = 0.27685731649399}, + wardrobe = {x = -594.7, y = 55.6, z = 97.0, l = 4.4, w = 4.2, h = 0, minZ = 95, maxZ = 99}, + zone = {name = 'TinselTowers', x = 1145.3, y = -778.4, z = 7.6, l = 30.0, w = 20.0, h = 90, minZ = 55, maxZ = 60}, + blip = { + scale = 0.7, + sprite = 475, + colour = 32 + }, + }, + + { + label = "Eclipse Towers", + type = "Apartment", + seperateExitPoint = true, + entrance = {x = -776.97711181641, y = 319.74661254883, z = 85.662673950195, h = 177.4141998291}, + exit = {x = -773.93316650391, y = 341.95892333984, z = 196.68617248535, h = 91.444755554199}, + exitPoint = {x = -770.62426757813, y = 319.72906494141, z = 85.662673950195, h = 177.16806030273}, + stash = {x = -764.73388671875, y = 330.80294799805, z = 196.08601379395, h = 180.05033874512}, + wardrobe = {x = -763.2, y = 329.0, z = 199.5, l = 5.2, w = 4.6, h = 0, minZ = 198, maxZ = 201}, + zone = {name = 'EclipseTowers', x = 1145.3, y = -778.4, z = 7.6, l = 30.0, w = 20.0, h = 90, minZ = 55, maxZ = 60}, + blip = { + scale = 0.7, + sprite = 475, + colour = 32 + }, + }, +} \ No newline at end of file diff --git a/fxmanifest.lua b/fxmanifest.lua new file mode 100644 index 0000000..1d29b9c --- /dev/null +++ b/fxmanifest.lua @@ -0,0 +1,25 @@ +fx_version 'cerulean' + +game 'gta5' + +this_is_a_map 'yes' + +lua54 'yes' + +author 'ZiggJoJo' + +version '1.0' + +shared_scripts { + '@ox_lib/init.lua', + 'config.lua' +} + +client_scripts { + 'client.lua' +} + +server_scripts { + '@mysql-async/lib/MySQL.lua', + 'server.lua' +} diff --git a/server.lua b/server.lua new file mode 100644 index 0000000..376d078 --- /dev/null +++ b/server.lua @@ -0,0 +1,406 @@ +ESX = nil + +TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end) + +local instances = {} + +RegisterServerEvent("instance:set") +AddEventHandler("instance:set", function(set) + print('[INSTANCES] Instances looked like this: ', json.encode(instances)) + local src = source + -- TriggerClientEvent('DoTheBigRefreshYmaps', src) + local instanceSource = 0 + if set then + if set == 0 then + for k,v in pairs(instances) do + for k2,v2 in pairs(v) do + if v2 == src then + table.remove(v, k2) + if #v == 0 then + instances[k] = nil + end + end + end + end + end + instanceSource = set + else + instanceSource = math.random(1, 1000) + while instances[instanceSource] and #instances[instanceSource] >= 1 do + instanceSource = math.random(1, 1000) + Citizen.Wait(1) + end + end + print(instanceSource) + if instanceSource ~= 0 then + if not instances[instanceSource] then + instances[instanceSource] = {} + end + table.insert(instances[instanceSource], src) + end + SetPlayerRoutingBucket( + src --[[ string ]], + instanceSource + ) + print('[INSTANCES] Instances now looks like this: ', json.encode(instances)) +end) + +Namedinstances = {} + + +RegisterServerEvent("instance:setNamed") +AddEventHandler("instance:setNamed", function(setName) + print('[INSTANCES] Named Instances looked like this: ', json.encode(Namedinstances)) + local src = source + local instanceSource = nil + + if setName == 0 then + for k,v in pairs(Namedinstances) do + for k2,v2 in pairs(v.people) do + if v2 == src then + table.remove(v.people, k2) + end + end + if #v.people == 0 then + Namedinstances[k] = nil + end + end + instanceSource = setName + else + for k,v in pairs(Namedinstances) do + if v.name == setName then + instanceSource = k + end + end + if instanceSource == nil then + instanceSource = math.random(1, 1000) + + while Namedinstances[instanceSource] and #Namedinstances[instanceSource] >= 1 do + instanceSource = math.random(1, 1000) + Citizen.Wait(1) + end + end + end + if instanceSource ~= 0 then + if not Namedinstances[instanceSource] then + Namedinstances[instanceSource] = { + name = setName, + people = {} + } + end + table.insert(Namedinstances[instanceSource].people, src) + end + SetPlayerRoutingBucket( + src --[[ string ]], + instanceSource + ) + print('[INSTANCES] Named Instances now look like this: ', json.encode(Namedinstances)) +end) + +local playersAtDoor = {} + +RegisterServerEvent("just_apartments:purchaseApartment") +AddEventHandler("just_apartments:purchaseApartment", function(apartment) + local xPlayer = ESX.GetPlayerFromId(source) + local Price = MySQL.scalar.await('SELECT Price FROM apartments WHERE Name = @Name', {['@Name'] = apartment}) + local rentLength = MySQL.scalar.await('SELECT rentLength FROM apartments WHERE Name = @Name', {['@Name'] = apartment}) + + if Price <= xPlayer.getAccount('bank').money then + local t = os.time() + local date = os.date("%Y%m%d",t) + local d = rentLength + local renewDate = t + d * 24 * 60 * 60 + local oldLease = MySQL.scalar.await('SELECT id FROM owned_apartments WHERE apartment = @apartment AND owner = @owner', {['@apartment'] = apartment, ['@owner'] = xPlayer.identifier}) + if oldLease ~= nil then + local lastPayment = MySQL.update.await('UPDATE owned_apartments SET lastPayment = @lastPayment WHERE id = @id', {['@id'] = oldLease, ['@lastPayment'] = tonumber(date)}) + local renewDateChange = MySQL.update.await('UPDATE owned_apartments SET renewDate = @renewDate WHERE id = @id', {['@id'] = oldLease, ['@renewDate'] = os.date("%Y%m%d",renewDate)}) + local lastPayment = MySQL.update.await('UPDATE owned_apartments SET renew = @renew WHERE id = @id', {['@id'] = oldLease, ['@renew'] = tonumber(1)}) + local renewDateChange = MySQL.update.await('UPDATE owned_apartments SET expired = @expired WHERE id = @id', {['@id'] = oldLease, ['@expired'] = tonumber(0)}) + xPlayer.removeAccountMoney('bank', Price) + local account_id = exports.ghmattimysql:scalarSync('SELECT account_id FROM bank_accounts WHERE owner = @identifier AND name = @name', { ['@identifier'] = xPlayer.identifier, ['@name'] = "Personal Checking" }) + TriggerEvent('orp-banking:logTransaction', xPlayer.identifier, "Apartment Lease"..apartment, account_id, 0, Price) + else + MySQL.insert('INSERT INTO `owned_apartments` (`owner`, `lastPayment`, `renewDate`, `apartment`) VALUES (@owner, @lastPayment, @renewDate, @apartment)', { + ['@apartment'] = apartment, + ['@lastPayment'] = os.date("%Y%m%d",t), + ['@renewDate'] = os.date("%Y%m%d",renewDate), + ['@owner'] = xPlayer.identifier + }) + local id = MySQL.scalar.await('SELECT id FROM owned_apartments WHERE apartment = @apartment AND owner = @owner', {['@apartment'] = apartment, ['@owner'] = xPlayer.identifier}) + if Config.UseOxInventory then + exports.ox_inventory:RegisterStash((apartment..id.."Stash"), (apartment.." Stash - "..id), 50, 100000, id) + end + xPlayer.removeAccountMoney('bank', Price) + local account_id = exports.ghmattimysql:scalarSync('SELECT account_id FROM bank_accounts WHERE owner = @identifier AND name = @name', { ['@identifier'] = xPlayer.identifier, ['@name'] = "Personal Checking" }) + TriggerEvent('orp-banking:logTransaction', xPlayer.identifier, "Apartment Lease"..apartment, account_id, 0, Price) + end + else + -- print("not enough money") + end +end) + +RegisterServerEvent("just_apartments:changeLease") +AddEventHandler("just_apartments:changeLease", function(data) + local lastPayment = MySQL.update.await('UPDATE owned_apartments SET renew = @renew WHERE id = @id', {['@id'] = data.id, ['@renew'] = tonumber(data.renew)}) +end) + +RegisterServerEvent("just_apartments:getOwnedApartments") +AddEventHandler("just_apartments:getOwnedApartments", function(apartment, coords) + local _source = source + local xPlayer = ESX.GetPlayerFromId(_source) + local id = MySQL.scalar.await('SELECT id FROM owned_apartments WHERE owner = @owner AND apartment = @apartment', { ['@owner'] = xPlayer.identifier, ['@apartment'] = apartment }) + local expired = MySQL.scalar.await('SELECT expired FROM owned_apartments WHERE id = @id', {['@id'] = id}) + local renewDate = MySQL.scalar.await('SELECT renewDate FROM owned_apartments WHERE id = @id', {['@id'] = id}) + local price = MySQL.scalar.await('SELECT Price FROM apartments WHERE name = @name', { ['@name'] = apartment }) + if id ~= nil and expired == 0 then + local renew = MySQL.scalar.await('SELECT renew FROM owned_apartments WHERE id = @id', {['@id'] = id}) + local data = {coords = coords, id = id, price = price, renewDate = renewDate, renew = renew} + TriggerClientEvent('just_apartments:entranceMenu', _source, data) + else + local id = false + local data = {coords = coords, id = id, price = price, renewDate = renewDate} + TriggerClientEvent('just_apartments:entranceMenu', _source, data) + end +end) + +RegisterServerEvent("just_apartments:checkApptOwnership") +AddEventHandler("just_apartments:checkApptOwnership", function(apartment, type, appt_id) + local xPlayer = ESX.GetPlayerFromId(source) + local _source = source + + local id = MySQL.scalar.await('SELECT id FROM owned_apartments WHERE owner = @owner AND apartment = @apartment', {['@owner'] = xPlayer.identifier,['@apartment'] = apartment}) + if id ~= nil then + if type == "Wardrobe" then + TriggerClientEvent('just_apartments:enteredWardrobe', _source) + elseif type == "Stash" then + TriggerClientEvent('just_apartments:enteredStash', _source, id, apartment) + end + else + local id2 = MySQL.scalar.await('SELECT id FROM apartment_keys WHERE appt_id = @appt_id AND player = @player', {['@appt_id'] = appt_id,['@player'] = xPlayer.identifier}) + if id2 ~= nil then + -- exports.xng_parsingtable:ParsingTable_sv(id2) + if type == "Wardrobe" then + TriggerClientEvent('just_apartments:enteredWardrobe', _source) + elseif type == "Stash" then + TriggerClientEvent('just_apartments:enteredStash', _source, appt_id, apartment) + end + end + end +end) + +RegisterServerEvent("just_apartments:alertOwner") +AddEventHandler("just_apartments:alertOwner", function(data) + local _source = source + if not playersAtDoor[data.apartment..data.id] then + playersAtDoor[data.apartment..data.id] = { + name = data.apartment..data.id, + people = {} + } + end + table.insert(playersAtDoor[data.apartment..data.id].people, _source) + -- exports.xng_parsingtable:ParsingTable_sv(playersAtDoor) + for k,v in pairs(Namedinstances) do + -- exports.xng_parsingtable:ParsingTable_sv(Namedinstances) + if v.name == (data.apartment..data.id) then + for k2,v2 in pairs(Namedinstances[k].people) do + TriggerClientEvent("swt_notifications:Icon",v2 , 'Someones at the door',"top-right",5000,"blue-10","white",true,"mdi-doorbell") + end + end + end +end) + +RegisterServerEvent("just_apartments:getBuildingApartments") +AddEventHandler("just_apartments:getBuildingApartments", function(data) + local _source = source + local xPlayer = ESX.GetPlayerFromId(_source) + MySQL.query('SELECT * FROM owned_apartments WHERE apartment = @apartment',{ + ['@apartment'] = data.currentApartment + }, function(apartments) + TriggerClientEvent('just_apartments:ringMenu', _source, apartments, data, xPlayer.identifier) + end) +end) + +RegisterServerEvent("just_apartments:getPlayersAtDoor") +AddEventHandler("just_apartments:getPlayersAtDoor", function(coords, name, id, exit) + local _source = source + local xPlayer = ESX.GetPlayerFromId(_source) + if id == nil then + TriggerClientEvent('just_apartments:exitMenu', _source, coords) + else + local id2 = MySQL.scalar.await('SELECT id FROM apartment_keys WHERE appt_id = @appt_id AND player = @player', {['@appt_id'] = id,['@player'] = xPlayer.identifier}) + + if id2 ~= nil then + TriggerClientEvent('just_apartments:exitMenu', _source, coords, playersAtDoor[name..id], exit) + else + MySQL.query('SELECT * FROM apartment_keys WHERE appt_id = @appt_id',{ + ['@appt_id'] = id + }, function(keyholders) + TriggerClientEvent('just_apartments:exitMenu', _source, coords, playersAtDoor[name..id], exit, keyholders) + end) + end + end +end) + +RegisterServerEvent("just_apartments:bringPlayerIn") +AddEventHandler("just_apartments:bringPlayerIn", function(data) + local _source = source + -- exports.xng_parsingtable:ParsingTable_sv(data) + for k,v in pairs(playersAtDoor) do + for k2,v2 in pairs(v.people) do + if v2 == data.player then + table.remove(v.people, k2) + end + end + if #v.people == 0 then + playersAtDoor[k] = nil + end + end + -- exports.xng_parsingtable:ParsingTable_sv(data) + TriggerClientEvent('just_apartments:enterExitApartment', data.player, data) +end) + +AddEventHandler('onServerResourceStart', function(resourceName) + if resourceName == 'just_apartments' or resourceName == GetCurrentResourceName() then + MySQL.query('SELECT * FROM owned_apartments ', function(apartments) + for i=1, #apartments, 1 do + local todaysDate = tonumber(os.date("%Y%m%d",os.time())) + if todaysDate > apartments[i].renewDate then + if apartments[i].renew then + local Price = MySQL.scalar.await('SELECT Price FROM apartments WHERE Name = @Name', {['@Name'] = apartments[i].apartment}) + local rentLength = MySQL.scalar.await('SELECT rentLength FROM apartments WHERE Name = @Name', {['@Name'] = apartments[i].apartment}) + local xPlayer = ESX.GetPlayerFromIdentifier(apartments[i].owner) + if Price <= xPlayer.getAccount('bank').money then + local t = os.time() + local date = os.date("%Y%m%d",t) + local d = rentLength + local renewDate = t + d * 24 * 60 * 60 + local lastPayment = MySQL.update.await('UPDATE owned_apartments SET lastPayment = @lastPayment WHERE id = @id', {['@id'] = apartments[i].id, ['@lastPayment'] = tonumber(date)}) + local renewDateChange = MySQL.update.await('UPDATE owned_apartments SET renewDate = @renewDate WHERE id = @id', {['@id'] = apartments[i].id, ['@renewDate'] = os.date("%Y%m%d",renewDate)}) + if lastPayment ~= nil and renewDateChange ~= nil then + xPlayer.removeAccountMoney('bank', Price) + local account_id = exports.ghmattimysql:scalarSync('SELECT account_id FROM bank_accounts WHERE owner = @identifier AND name = @name', { ['@identifier'] = apartments[i].owner, ['@name'] = "Personal Checking" }) + TriggerEvent('orp-banking:logTransaction', apartments[i].owner, "Apartment Lease Renewal"..apartments[i].apartment, account_id, 0, Price) + end + else + MySQL.update('UPDATE owned_apartments SET expired = @expired WHERE id = @id', { + ['@id'] = apartments[i].id, + ['@expired'] = 1 + }, function(id) + end) + end + else + MySQL.update('UPDATE owned_apartments SET expired = @expired WHERE id = @id', { + ['@id'] = apartments[i].id, + ['@expired'] = 1 + }, function(id) + end) + end + end + end + end) + end +end) + +---------- +-- Keys -- +---------- + +RegisterServerEvent("just_apartments:giveKeys") +AddEventHandler("just_apartments:giveKeys", function(data) + local _source = source + local xPlayer = ESX.GetPlayerFromId(source) + local xTarget = ESX.GetPlayerFromId(data.target) + local hasKeys = MySQL.scalar.await('SELECT id FROM apartment_keys WHERE appt_id = @appt_id AND player = @player', {['@appt_id'] = data.appt_id, ['@player'] = xTarget.identifier}) + + -- exports.xng_parsingtable:ParsingTable_sv(xTarget) + if hasKeys == nil then + MySQL.insert('INSERT INTO `apartment_keys` (`appt_id`, `appt_name`, `player`, `player_name`, `appt_owner`) VALUES (@appt_id, @appt_name, @player, @player_name, @appt_owner)', { + ['@appt_id'] = data.appt_id, + ['@appt_name'] = data.appt_name, + ['@player'] = xTarget.identifier, + ['@player_name'] = xTarget.name, + ['@appt_owner'] = xPlayer.identifier + }) + TriggerClientEvent("swt_notifications:Icon", _source , 'Key given',"top-right",5000,"blue-10","white",true,"mdi-key-variant") + -- TriggerClientEvent("swt_notifications:Icon", data.target , 'Key recieved',"top-right",5000,"blue-10","white",true,"mdi-key-variant") + else + TriggerClientEvent("swt_notifications:Icon", _source , 'Person already has keys',"top-right",5000,"red","white",true,"mdi-alert-circle-outline") + end +end) + +RegisterServerEvent("just_apartments:getAppartmentsWithKeys") +AddEventHandler("just_apartments:getAppartmentsWithKeys", function(data, currentApartment) + local _source = source + local xPlayer = ESX.GetPlayerFromId(_source) + MySQL.query('SELECT * FROM apartment_keys WHERE appt_name = @appt_name AND player = @player',{ + ['@appt_name'] = currentApartment, + ['@player'] = xPlayer.identifier + }, function(apartments) + -- exports.xng_parsingtable:ParsingTable_sv(apartments) + TriggerClientEvent('just_apartments:keyEntryMenu', _source, apartments, data) + end) +end) + +RegisterServerEvent("just_apartments:removeApartmentKeys") +AddEventHandler("just_apartments:removeApartmentKeys", function(data) + local id = data.id + MySQL.update('DELETE FROM apartment_keys WHERE id = @id', {['@id'] = id}, function(affectedRows) + if affectedRows then + print(affectedRows) + end + end) +end) + +----------- +-- Stash -- +----------- + +AddEventHandler('onServerResourceStart', function(resourceName) + if resourceName == 'ox_inventory' or resourceName == GetCurrentResourceName() and Config.UseOxInventory then + exports.ox_inventory:RegisterStash("AltaStreetAppts0Stash", "Alta Street Apartments", 25, 100000, true) + MySQL.query('SELECT * FROM owned_apartments ', function(apartments) + for i=1, #apartments, 1 do + exports.ox_inventory:RegisterStash((apartments[i].apartment..apartments[i].id.."Stash"), (apartments[i].apartment.." Stash - "..apartments[i].id), 50, 100000, apartments[i].id) + end + end) + end +end) + +------------------------- +-- Save Last Apartment -- +------------------------- + +function Split(s, delimiter) + if s ~= nil then + result = {}; + for match in (s..delimiter):gmatch("(.-)"..delimiter) do + table.insert(result, match); + end + return result; + end +end + +RegisterServerEvent("just_apartments:updateLastApartment") +AddEventHandler("just_apartments:updateLastApartment", function(last_property) + local _source = source + local xPlayer = ESX.GetPlayerFromId(_source) + MySQL.update('UPDATE users SET last_property = @last_property WHERE identifier = @identifier', { + ['@identifier'] = xPlayer.identifier, + ['@last_property'] = last_property + }, function(id) + end) + +end) + +RegisterServerEvent("just_apartments:getLastApartment") +AddEventHandler("just_apartments:getLastApartment", function() + local _source = source + local xPlayer = ESX.GetPlayerFromId(_source) + if xPlayer ~= nil then + local last_property = MySQL.scalar.await('SELECT last_property FROM users WHERE identifier = @identifier', {['@identifier'] = xPlayer.identifier}) + local apartment_id = Split(last_property, " ") + if apartment_id ~= nil then + TriggerClientEvent('just_apartments:spawnInProperty', _source, apartment_id[1], apartment_id[2]) + end + end +end) \ No newline at end of file diff --git a/stream/Starter.ymap b/stream/Starter.ymap new file mode 100644 index 0000000..d34c4aa Binary files /dev/null and b/stream/Starter.ymap differ diff --git a/stream/_manifest.ymf b/stream/_manifest.ymf new file mode 100644 index 0000000..df930ff Binary files /dev/null and b/stream/_manifest.ymf differ