diff --git a/README.md b/README.md index 2a2878fd..185d67a3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,16 @@ # fxserver-esx_phone FXServer esx_phone + +[INSTALLATION] + +1) CD in your resources folder + +2) +``` +git clone https://github.com/FXServer-ESX/fxserver-esx_phone +``` +3) Add this in your server.cfg : + +``` +start esx_phone +``` \ No newline at end of file diff --git a/__resource.lua b/__resource.lua new file mode 100644 index 00000000..c7e09045 --- /dev/null +++ b/__resource.lua @@ -0,0 +1,35 @@ +resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937' + +description 'ESX Phone' + +server_scripts { + '@mysql-async/lib/MySQL.lua', + 'server/main.lua' +} + +client_script 'client/main.lua' + +ui_page 'html/ui.html' + +files { + 'html/ui.html', + 'html/bankgothic.ttf', + 'html/pdown.ttf', + 'html/css/app.css', + 'html/scripts/mustache.min.js', + 'html/scripts/app.js', + 'html/img/cursor.png', + 'html/img/keys/enter.png', + 'html/img/keys/return.png', + 'html/img/phone.png', + 'html/img/icons/signal.png', + 'html/img/icons/rep.png', + 'html/img/icons/msg.png', + 'html/img/icons/add.png', + 'html/img/icons/back.png', + 'html/img/icons/new-msg.png', + 'html/img/icons/reply.png', + 'html/img/icons/write.png', + 'html/img/icons/edit.png', + 'html/img/icons/location.png' +} \ No newline at end of file diff --git a/client/main.lua b/client/main.lua new file mode 100644 index 00000000..73dc6121 --- /dev/null +++ b/client/main.lua @@ -0,0 +1,265 @@ +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 +GUI.PhoneIsShowed = false +GUI.MessagesIsShowed = false +GUI.AddContactIsShowed = false +local PhoneData = {phoneNumber = 0, contacts = {}} +local RegisteredMessageCallbacks = {} +local ContactJustAdded = false +local CurrentAction = nil +local CurrentActionMsg = '' +local CurrentActionData = {} +local CurrentDispatchRequestId = -1 + +Citizen.CreateThread(function() + + while ESX == nil do + TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end) + Citizen.Wait(0) + end + + ESX.UI.Menu.RegisterType('phone', OpenPhone, ClosePhone) + +end) + +function OpenPhone() + + TriggerServerEvent('esx_phone:reload', PhoneData.phoneNumber) + + SendNUIMessage({ + showPhone = true, + phoneData = PhoneData + }) + + GUI.PhoneIsShowed = true + + ESX.SetTimeout(200, function() + SetNuiFocus(true, true) + end) + +end + +function ClosePhone() + + SendNUIMessage({ + showPhone = false + }) + + SetNuiFocus(false) + + GUI.PhoneIsShowed = false + +end + +RegisterNetEvent('esx_phone:loaded') +AddEventHandler('esx_phone:loaded', function(phoneNumber, contacts) + + PhoneData.phoneNumber = phoneNumber + PhoneData.contacts = {} + + for i=1, #contacts, 1 do + table.insert(PhoneData.contacts, contacts[i]) + end + + SendNUIMessage({ + reloadPhone = true, + phoneData = PhoneData + }) + +end) + +RegisterNetEvent('esx_phone:addContact') +AddEventHandler('esx_phone:addContact', function(name, phoneNumber, isOnline) + + table.insert(PhoneData.contacts, { + name = name, + number = phoneNumber, + online = isOnline + }) + -- CALL HERE RELOADCONTACT + SendNUIMessage({ + contactAdded = true, + phoneData = PhoneData + }) +end) + +RegisterNetEvent('esx_phone:addSpecialContact') +AddEventHandler('esx_phone:addSpecialContact', function(name, phoneNumber, base64Icon) + + SendNUIMessage({ + addSpecialContact = true, + name = name, + number = phoneNumber, + base64Icon = base64Icon + }) + +end) + +RegisterNUICallback('add_contact', function(data, cb) + + local phoneNumber = tonumber(data.phoneNumber) + local contactName = tostring(data.contactName) + + if phoneNumber then + TriggerServerEvent('esx_phone:addPlayerContact', phoneNumber, contactName) + end + +end) + + +RegisterNetEvent('esx_phone:onMessage') +AddEventHandler('esx_phone:onMessage', function(phoneNumber, message, position, anon, job, dispatchRequestId) + + ESX.ShowNotification('~b~Nouveau message') + + SendNUIMessage({ + newMessage = true, + phoneNumber = phoneNumber, + message = message, + position = position, + anonyme = anon, + job = job + }) + + if dispatchRequestId then + + CurrentAction = 'dispatch' + CurrentActionMsg = job .. ' - Appuez sur ~INPUT_CONTEXT~ pour prendre l\'appel' + CurrentDispatchRequestId = dispatchRequestId + + CurrentActionData = { + phoneNumber = phoneNumber, + message = message, + position = position, + actions = actions, + anonyme = anon, + job = job + } + + ESX.SetTimeout(15000, function() + CurrentAction = nil + end) + + end + +end) + +RegisterNetEvent('esx_phone:stopDispatch') +AddEventHandler('esx_phone:stopDispatch', function(dispatchRequestId, playerName) + + if CurrentDispatchRequestId == dispatchRequestId and CurrentAction == 'dispatch' then + CurrentAction = nil + ESX.ShowNotification(playerName .. ' a pris l\'appel') + end + +end) + +RegisterNUICallback('setGPS', function(data) + SetNewWaypoint(data.x, data.y) + ESX.ShowNotification('Position entrée dans le GPS') +end) + +RegisterNUICallback('send', function(data) + + local phoneNumber = data.number + + if tonumber(phoneNumber) ~= nil then + phoneNumber = tonumber(phoneNumber) + end + + TriggerServerEvent('esx_phone:send', phoneNumber, data.message, data.anonyme) + + SendNUIMessage({ + showMessageEditor = false + }) + + ESX.ShowNotification('Message envoyé') + +end) + +RegisterNUICallback('escape', function() + ESX.UI.Menu.Close('phone', GetCurrentResourceName(), 'main') +end) + +Citizen.CreateThread(function() + while true do + + Wait(0) + + if GUI.PhoneIsShowed then -- codes here: https://pastebin.com/guYd0ht4 + DisableControlAction(0, 1, true) -- LookLeftRight + DisableControlAction(0, 2, true) -- LookUpDown + DisableControlAction(0, 25, true) -- Input Aim + DisableControlAction(0, 106, true) -- Vehicle Mouse Control Override + + DisableControlAction(0, 24, true) -- Input Attack + DisableControlAction(0, 140, true) -- Melee Attack Alternate + DisableControlAction(0, 141, true) -- Melee Attack Alternate + DisableControlAction(0, 142, true) -- Melee Attack Alternate + DisableControlAction(0, 257, true) -- Input Attack 2 + DisableControlAction(0, 263, true) -- Input Melee Attack + DisableControlAction(0, 264, true) -- Input Melee Attack 2 + + DisableControlAction(0, 12, true) -- Weapon Wheel Up Down + DisableControlAction(0, 14, true) -- Weapon Wheel Next + DisableControlAction(0, 15, true) -- Weapon Wheel Prev + DisableControlAction(0, 16, true) -- Select Next Weapon + DisableControlAction(0, 17, true) -- Select Prev Weapon + else + + if IsControlPressed(0, Keys['F1']) and (GetGameTimer() - GUI.Time) > 150 then + + if not ESX.UI.Menu.IsOpen('phone', GetCurrentResourceName(), 'main') then + ESX.UI.Menu.CloseAll() + ESX.UI.Menu.Open('phone', GetCurrentResourceName(), 'main') + end + + GUI.Time = GetGameTimer() + + end + + 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 == 'dispatch' then + TriggerServerEvent('esx_phone:stopDispatch', CurrentDispatchRequestId) + SetNewWaypoint(CurrentActionData.position.x, CurrentActionData.position.y) + end + + CurrentAction = nil + GUI.Time = GetGameTimer() + + end + + end + + end +end) \ No newline at end of file diff --git a/esx_phone.sql b/esx_phone.sql new file mode 100644 index 00000000..19d3b255 --- /dev/null +++ b/esx_phone.sql @@ -0,0 +1,11 @@ +CREATE TABLE `user_contacts` ( + `id` int(11) NOT NULL, + `identifier` varchar(255) NOT NULL, + `name` varchar(255) NOT NULL, + `number` int(11) NOT NULL +); + +ALTER TABLE `user_contacts` ADD PRIMARY KEY (`id`); + +ALTER TABLE `users` +ADD COLUMN `phone_number` INT NULL AFTER `position`; diff --git a/html/bankgothic.ttf b/html/bankgothic.ttf new file mode 100644 index 00000000..f3d8049b Binary files /dev/null and b/html/bankgothic.ttf differ diff --git a/html/css/app.css b/html/css/app.css new file mode 100644 index 00000000..d29c0273 --- /dev/null +++ b/html/css/app.css @@ -0,0 +1,392 @@ +html, body { + width: 100%; + height: 100%; + overflow: hidden; +} + +#cursor { + position: absolute; + z-index: 999999; + display: none; +} + +#console { + position: absolute; + left: 0; + bottom: 0; + width: 320px; + height: 250px; + background-color: rgba(0, 0, 0, 0.8); + color: white; + padding: 5px; + overflow-y: auto; + overflow-x: hidden; +} + +#phone { + background-image: url(../img/phone.png); + background-repeat: no-repeat; + font-family: 'Catamaran', sans-serif; + font-weight: 400; + font-size: 12px; + width: 298px; + height: 480px; + color: #212121; + position: absolute; + right: 300px; + bottom: 0; + overflow: hidden; + cursor: default; + display: none; +} + +#phone > .container { + position: absolute; + top: 59px; + left: 46px; + width: 207px; + height: 365px; + background-repeat: no-repeat; + overflow: hidden; +} + +#phone .head { + position: relative; + text-transform: uppercase; + text-align: center; + height: 20px; + line-height: 20px; + background-color: black; + z-index: 5; +} + +#phone-icon { + display: block; + position: absolute; + height: 12px; + line-height: 20px; + top: 4px; + right: 5px; +} + +#phone-number { + display: block; + position: absolute; + height: 20px; + line-height: 20px; + top: 0; + left: 5px; + color: white; +} + +#phone .menu { + max-height: 320px; + overflow-y: scroll; + z-index: 1; +} + +#phone .menu .home { + list-style: none outside none; + margin: 0; + padding: 13px; + color: white; +} + +#phone .menu .phone-icon { + position: relative; + display: block; + float: left; + text-align: center; + width: 60px; + padding-top: 50px; + padding-bottom: 5px; + background-size: 32px 32px; + background-color: transparent; + background-repeat: no-repeat; + background-position: center 10px; + cursor: pointer; +} + +#phone .menu .phone-icon::after { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(255, 255, 255, 0.01); +} + +#phone-icon-rep { + background-image: url("../img/icons/rep.png"); +} + +#phone-icon-msg { + background-image: url("../img/icons/msg.png"); +} + +#phone .menu::-webkit-scrollbar { + display: none; +} + +#phone .menu .phone-icon.selected, #phone .menu .phone-icon:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.screen { + position: absolute; + top: 0; + left: 207px; + width: 207px; + height: 365px; + background-color: #dde1e8; + z-index: 1; + transition: left 0.25s ease; + overflow: hidden; +} + +.screen.active { + left: 0; +} + +.screen > .container { + position: absolute; + top: 50px; + left: 0; + width: 224px; + height: 315px; + overflow-y: scroll; +} + +.head-screen { + position: absolute; + height: 30px; + line-height: 30px; + text-transform: uppercase; + background-color: white; + text-align: center; + color: #212121; + background-color: white; + font-weight: 600; + border-bottom: 1px solid rgba(0, 0, 0, 0.3); + font-size: 13px; + top: 20px; + left: 0; + right: 0; + z-index: 5; +} + +.btn-head { + display: block; + color: #212121; + width: 30px; + height: 30px; + font-size: 20px; + cursor: pointer; +} + +.btn-head:hover { + background-color: #f1f1f1; +} + +.btn-head-back { + float: left; + background: transparent url("../img/icons/back.png") no-repeat 50%; +} + +.btn-head-new { + float: right; + background: transparent url("../img/icons/add.png") no-repeat 50%; +} + +.message, .contact { + position: relative; + padding: 3px 35px 3px 25px; + background-color: white; + border-bottom: 1px solid rgba(0, 0, 0, 0.3); + color: #212121; + min-height: 30px; +} + +.message.no-item, .contact.no-item { + padding: 3px 5px; +} + +.contact { + opacity: 0.5; +} + +.contact.online, .contact.no-item { + opacity: 1; +} + +.contact::after { + position: absolute; + display: block; + content: ''; + width: 8px; + height: 8px; + background-color: #dcdcdc; + left: 10px; + top: 8px; + border-radius: 50%; +} + +.contact.online::after { + background-color: #6ecc49; +} + +.contact.no-item::after { + display: none; +} + +.no-item { + text-align: center; +} + +.message .sender-info, .message .body, .contact .sender-info, { + margin-right: 30px; +} + +.message .phone-number { + line-height: 10px; + color: #999; +} + +.message .body { + margin-top: 5px; + line-height: 16px; +} + +.sender { + font-weight: 600; +} + +.actions { + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 24px; +} + +.actions .new-msg { + display: block; + width: 24px; + height: 24px; + background: transparent url("../img/icons/write.png") no-repeat 50%; + cursor: pointer; +} + +.actions .reply-btn { + background: transparent url("../img/icons/reply.png") no-repeat 50%; +} + +.actions .gps { + display: none; + width: 24px; + height: 24px; + background: transparent url(../img/icons/location.png) no-repeat 50%; + cursor: pointer; +} + +.actions .ok-btn { + display: none; + width: 24px; + height: 24px; + background: transparent url(../img/icons/ok.png) no-repeat 50%; + cursor: pointer; +} + +.actions .new-msg.anonyme, .contact .actions .new-msg { + display: none; +} + +.contact.online .actions .new-msg, .actions .gps.active, .actions .ok-btn.showOK { + display: block; +} + +.actions .new-msg:hover, .actions .gps:hover, .actions .ok-btn:hover { + background-color: #f1f1f1; +} + +#writer textarea { + width: 207px; + min-width: 207px; + max-width: 207px; + height: 255px; + min-height: 255px; + max-height: 255px; + background-color: #f1f1f1; + border: none; + box-shadow: 0 2px 3px 1px rgba(0, 0, 0, 0.1) inset; + padding: 5px; + outline: none; +} + +#writer label { + display: block; + width: 100%; + height: 30px; + line-height: 34px; + background-color: #f1f1f1; + border: none; + box-shadow: 0 2px 3px 1px rgba(0, 0, 0, 0.1) inset; + font-size: 14px; + font-weight: 600; + color: #6b6b6b; +} + +#writer label input { + top: 2px; + position: relative; + left: 5px; + margin-right: 10px; +} + +#writer button, #contact button { + display: block; + float: left; + width: 103px; + height: 30px; + line-height: 30px; + background-color: #dcdcdc; + border-color: rgba(0, 0, 0, 0.5); + border: none; + color: white; + font-weight: 600; + cursor: pointer; + outline: none; +} + +#writer button.marged, #contact button.marged { + margin-left: 1px; +} + +#writer #writer_cancel, #contact #contact_cancel { + background-color: #d9534f; +} + +#writer #writer_send:hover, #contact #contact_send:hover { + background-color: #74c374; +} + +#writer #writer_cancel:hover, #contact #contact_cancel:hover { + background-color: #dc6b68; +} + +#writer #writer_send, #contact #contact_send { + background-color: #5cb85c; +} + +#contact input { + display: block; + width: 100%; + height: 30px; + line-height: 30px; + padding: 2px 5px; + background-color: #f1f1f1; + border: none; + border-bottom: 1px solid rgba(0, 0, 0, 0.3); + outline: none; + box-shadow: 0 2px 3px 1px rgba(0, 0, 0, 0.1) inset; +} \ No newline at end of file diff --git a/html/img/cursor.png b/html/img/cursor.png new file mode 100644 index 00000000..39536f95 Binary files /dev/null and b/html/img/cursor.png differ diff --git a/html/img/icons/add.png b/html/img/icons/add.png new file mode 100644 index 00000000..ad5a1310 Binary files /dev/null and b/html/img/icons/add.png differ diff --git a/html/img/icons/back.png b/html/img/icons/back.png new file mode 100644 index 00000000..f7fa8c38 Binary files /dev/null and b/html/img/icons/back.png differ diff --git a/html/img/icons/edit.png b/html/img/icons/edit.png new file mode 100644 index 00000000..9f0c9db5 Binary files /dev/null and b/html/img/icons/edit.png differ diff --git a/html/img/icons/location.png b/html/img/icons/location.png new file mode 100644 index 00000000..54e65f47 Binary files /dev/null and b/html/img/icons/location.png differ diff --git a/html/img/icons/msg.png b/html/img/icons/msg.png new file mode 100644 index 00000000..aec485a3 Binary files /dev/null and b/html/img/icons/msg.png differ diff --git a/html/img/icons/new-msg.png b/html/img/icons/new-msg.png new file mode 100644 index 00000000..a5970481 Binary files /dev/null and b/html/img/icons/new-msg.png differ diff --git a/html/img/icons/ok.png b/html/img/icons/ok.png new file mode 100644 index 00000000..c08a8a7a Binary files /dev/null and b/html/img/icons/ok.png differ diff --git a/html/img/icons/rep.png b/html/img/icons/rep.png new file mode 100644 index 00000000..efb0082e Binary files /dev/null and b/html/img/icons/rep.png differ diff --git a/html/img/icons/reply.png b/html/img/icons/reply.png new file mode 100644 index 00000000..0b359ecc Binary files /dev/null and b/html/img/icons/reply.png differ diff --git a/html/img/icons/signal.png b/html/img/icons/signal.png new file mode 100644 index 00000000..143301f5 Binary files /dev/null and b/html/img/icons/signal.png differ diff --git a/html/img/icons/write.png b/html/img/icons/write.png new file mode 100644 index 00000000..28f32beb Binary files /dev/null and b/html/img/icons/write.png differ diff --git a/html/img/keys/enter.png b/html/img/keys/enter.png new file mode 100644 index 00000000..3eb9a1f6 Binary files /dev/null and b/html/img/keys/enter.png differ diff --git a/html/img/keys/return.png b/html/img/keys/return.png new file mode 100644 index 00000000..a3ca125a Binary files /dev/null and b/html/img/keys/return.png differ diff --git a/html/img/phone.png b/html/img/phone.png new file mode 100644 index 00000000..47f6f82f Binary files /dev/null and b/html/img/phone.png differ diff --git a/html/img/pointer.png b/html/img/pointer.png new file mode 100644 index 00000000..9e1fcd47 Binary files /dev/null and b/html/img/pointer.png differ diff --git a/html/pdown.ttf b/html/pdown.ttf new file mode 100644 index 00000000..69c76cf3 Binary files /dev/null and b/html/pdown.ttf differ diff --git a/html/scripts/app.js b/html/scripts/app.js new file mode 100644 index 00000000..160d91d1 --- /dev/null +++ b/html/scripts/app.js @@ -0,0 +1,434 @@ +(function(){ + + let ContactTpl = + '
' + + '
' + + '
' + + '{{sender}}
#{{phoneNumber}}' + + '
' + + '
' + + '
' + + '
' + ; + + let MessageTpl = + '
' + + '
' + + '
' + + '{{sender}}
#{{phoneNumber}}' + + '
' + + '
' + + '
{{message}}
' + + '
' + + '
' + ; + + let SpecialContactTpl = '
  • {{name}}
  • '; + + let contacts = []; + let specialContacts = []; + let menu = []; + let currentItem = 0; + let currentVal = null; + let isMessageEditorOpen = false; + let isMessagesOpen = false; + let isPhoneShowed = false; + + let showMain = function() { + $('.screen').removeClass('active'); + } + + let showRepertoire = function() { + $('#repertoire').addClass('active'); + } + + let hideRepertoire = function() { + $('#repertoire').removeClass('active'); + } + + let showMessages = function(){ + $('#messages').addClass('active'); + isMessagesOpen = true; + } + + let hideMessages = function(){ + $('#messages').removeClass('active'); + isMessagesOpen = false; + } + + let showAddContact = function() { + $('#contact').addClass('active'); + } + + let hideAddContact = function() { + $('#contact').removeClass('active'); + $('#contact_name').val(''); + $('#contact_number').val(''); + } + + let showNewMessage = function(cnum, cname) { + $('#writer').addClass('active'); + $('#writer_number').val(cnum); + $('#writer .header-title').html(cname); + } + + let hideNewMessage = function() { + $('#writer').removeClass('active'); + $('#writer_number').val(''); + $('#writer_message').val(''); + $('#writer .header-title').html(''); + } + + let showGPS = function(xPos, yPos) { + $.post('http://esx_phone/setGPS', JSON.stringify({ + x: parseFloat(xPos), + y: parseFloat(yPos) + } + )); + } + + let renderContacts = function(){ + + let contactHTML = ''; + + if(contacts.length > 0) { + + for(let i=0; i 0) { + + for(let i=0; i { + return a.name.localeCompare(b.name); + }); + + renderSpecialContacts(); + + } + + let renderSpecialContacts = function(){ + + $('.phone-icon').unbind('click'); + + $('#phone .menu .home').html( + '
  • Repertoire
  • ' + + '
  • Messages
  • ' + ); + + for(let i=0; i":">",'"':""","'":"'","/":"/","`":"`","=":"="};function escapeHtml(string){return String(string).replace(/[&<>"'`=\/]/g,function fromEntityMap(s){return entityMap[s]})}var whiteRe=/\s*/;var spaceRe=/\s+/;var equalsRe=/\s*=/;var curlyRe=/\s*\}/;var tagRe=/#|\^|\/|>|\{|&|=|!/;function parseTemplate(template,tags){if(!template)return[];var sections=[];var tokens=[];var spaces=[];var hasTag=false;var nonSpace=false;function stripSpace(){if(hasTag&&!nonSpace){while(spaces.length)delete tokens[spaces.pop()]}else{spaces=[]}hasTag=false;nonSpace=false}var openingTagRe,closingTagRe,closingCurlyRe;function compileTags(tagsToCompile){if(typeof tagsToCompile==="string")tagsToCompile=tagsToCompile.split(spaceRe,2);if(!isArray(tagsToCompile)||tagsToCompile.length!==2)throw new Error("Invalid tags: "+tagsToCompile);openingTagRe=new RegExp(escapeRegExp(tagsToCompile[0])+"\\s*");closingTagRe=new RegExp("\\s*"+escapeRegExp(tagsToCompile[1]));closingCurlyRe=new RegExp("\\s*"+escapeRegExp("}"+tagsToCompile[1]))}compileTags(tags||mustache.tags);var scanner=new Scanner(template);var start,type,value,chr,token,openSection;while(!scanner.eos()){start=scanner.pos;value=scanner.scanUntil(openingTagRe);if(value){for(var i=0,valueLength=value.length;i0?sections[sections.length-1][4]:nestedTokens;break;default:collector.push(token)}}return nestedTokens}function Scanner(string){this.string=string;this.tail=string;this.pos=0}Scanner.prototype.eos=function eos(){return this.tail===""};Scanner.prototype.scan=function scan(re){var match=this.tail.match(re);if(!match||match.index!==0)return"";var string=match[0];this.tail=this.tail.substring(string.length);this.pos+=string.length;return string};Scanner.prototype.scanUntil=function scanUntil(re){var index=this.tail.search(re),match;switch(index){case-1:match=this.tail;this.tail="";break;case 0:match="";break;default:match=this.tail.substring(0,index);this.tail=this.tail.substring(index)}this.pos+=match.length;return match};function Context(view,parentContext){this.view=view;this.cache={".":this.view};this.parent=parentContext}Context.prototype.push=function push(view){return new Context(view,this)};Context.prototype.lookup=function lookup(name){var cache=this.cache;var value;if(cache.hasOwnProperty(name)){value=cache[name]}else{var context=this,names,index,lookupHit=false;while(context){if(name.indexOf(".")>0){value=context.view;names=name.split(".");index=0;while(value!=null&&index")value=this.renderPartial(token,context,partials,originalTemplate);else if(symbol==="&")value=this.unescapedValue(token,context);else if(symbol==="name")value=this.escapedValue(token,context);else if(symbol==="text")value=this.rawValue(token);if(value!==undefined)buffer+=value}return buffer};Writer.prototype.renderSection=function renderSection(token,context,partials,originalTemplate){var self=this;var buffer="";var value=context.lookup(token[1]);function subRender(template){return self.render(template,context,partials)}if(!value)return;if(isArray(value)){for(var j=0,valueLength=value.length;j + + + + + + + +
    + +
    + +
    + + #123456 +
    + + + +
    + +
    Répertoire
    +
    +
    +
    +

    Aucun contact

    +
    +
    +
    +
    + +
    +
    Messages
    +
    +
    +
    +

    Aucun message

    +
    +
    +
    +
    + +
    +
    +
    +
    + + + + + +
    +
    +
    + +
    +
    Ajouter un contact
    +
    +
    + + + + +
    +
    +
    + +
    +
    + + + + + + + + \ No newline at end of file diff --git a/server/main.lua b/server/main.lua new file mode 100644 index 00000000..14d1afdb --- /dev/null +++ b/server/main.lua @@ -0,0 +1,309 @@ +ESX = nil + +TriggerEvent('esx:getSharedObject', function(obj) + ESX = obj +end) + +local RegisteredCallbacks = {} +local DisptachRequestId = 0 + +function GenerateUniquePhoneNumber() + + local foundNumber = false + local phoneNumber = nil + + while not foundNumber do + + phoneNumber = math.random(10000, 99999) + + local result = MySQL.Sync.fetchAll( + 'SELECT COUNT(*) as count FROM users WHERE phone_number = @phoneNumber', + { + ['@phoneNumber'] = phoneNumber + } + ) + + local count = tonumber(result[1].count) + + if count == 0 then + foundNumber = true + end + + end + + return phoneNumber +end + +function GetDistpatchRequestId() + + local requestId = DisptachRequestId + + if DisptachRequestId < 65535 then + DisptachRequestId = DisptachRequestId + 1 + else + DisptachRequestId = 0 + end + + return requestId + +end + +AddEventHandler('esx_phone:getDistpatchRequestId', function(cb) + cb(GetDistpatchRequestId()) +end) + +AddEventHandler('onResourceStart', function(ressource) + if ressource == 'esx_phone' then + TriggerEvent('esx_phone:ready') + end +end) + +AddEventHandler('esx:playerLoaded', function(source) + + local xPlayer = ESX.GetPlayerFromId(source) + + MySQL.Async.fetchAll( + 'SELECT * FROM users WHERE identifier = @identifier', + { + ['@identifier'] = xPlayer.identifier + }, + function(result) + + local phoneNumber = result[1].phone_number + + if phoneNumber == nil then + + phoneNumber = GenerateUniquePhoneNumber() + + MySQL.Async.execute( + 'UPDATE users SET phone_number = @phone_number WHERE identifier = @identifier', + { + ['@identifier'] = xPlayer.identifier, + ['@phone_number'] = phoneNumber + } + ) + end + + xPlayer.set('phoneNumber', phoneNumber) + + local contacts = {} + + MySQL.Async.fetchAll( + 'SELECT * FROM user_contacts WHERE identifier = @identifier ORDER BY name ASC', + { + ['@identifier'] = xPlayer.identifier + }, + function(result2) + + for i=1, #result2, 1 do + + table.insert(contacts, { + name = result2[i].name, + number = result2[i].number, + online = false + }) + + local xPlayers = ESX.GetPlayers() + + for k, v in pairs(xPlayers) do + if v.get('phoneNumber') == contacts[i].number then + contacts[i].online = true + end + end + + end + + xPlayer.set('contacts', contacts) + + TriggerClientEvent('esx_phone:loaded', source, phoneNumber, contacts) + + end + ) + + end + ) + +end) + + +RegisterServerEvent('esx_phone:reload') +AddEventHandler('esx_phone:reload', function(phoneNumber) + + local xPlayer = ESX.GetPlayerFromId(source) + local contacts = {} + + MySQL.Async.fetchAll( + 'SELECT * FROM user_contacts WHERE identifier = @identifier ORDER BY name ASC', + { + ['@identifier'] = xPlayer.identifier + }, + function(result2) + + for i=1, #result2, 1 do + + table.insert(contacts, { + name = result2[i].name, + number = result2[i].number, + online = false + }) + + local xPlayers = ESX.GetPlayers() + + for k, v in pairs(xPlayers) do + if v.get('phoneNumber') == contacts[i].number then + contacts[i].online = true + end + end + + end + + xPlayer['contacts'] = contacts + + TriggerClientEvent('esx_phone:loaded', source, phoneNumber, contacts) + + end + ) + +end) + +RegisterServerEvent('esx_phone:registerCallback') +AddEventHandler('esx_phone:registerCallback', function(cb) + table.insert(RegisteredCallbacks, cb) +end) + +RegisterServerEvent('esx_phone:send') +AddEventHandler('esx_phone:send', function(phoneNumber, message, anon) + for i=1, #RegisteredCallbacks, 1 do + RegisteredCallbacks[i](source, phoneNumber, message, anon) + end +end) + +RegisterServerEvent('esx_phone:addPlayerContact') +AddEventHandler('esx_phone:addPlayerContact', function(phoneNumber, contactName) + + local xPlayers = ESX.GetPlayers() + local foundNumber = false + local foundPlayer = nil + + MySQL.Async.fetchAll( + 'SELECT phone_number FROM users WHERE phone_number = @number', + { + ['@number'] = phoneNumber + }, + function(result) + + if result[1] ~= nil then + foundNumber = true + end + + if foundNumber then + + local xPlayer = ESX.GetPlayerFromId(source) + + if phoneNumber == xPlayer.get('phoneNumber') then + TriggerClientEvent('esx:showNotification', _source, 'Vous ne pouvez pas vous ajouter vous-même') + else + + local hasAlreadyAdded = false + local contacts = xPlayer.get('contacts') + + for i=1, #contacts, 1 do + if contacts[i].number == phoneNumber then + hasAlreadyAdded = true + end + end + + if hasAlreadyAdded then + TriggerClientEvent('esx:showNotification', _source, 'Ce numéro est déja dans votre liste de contacts') + else + + table.insert(contacts, { + name = contactName, + number = phoneNumber, + }) + + xPlayer.set('contacts', contacts) + + MySQL.Async.execute( + 'INSERT INTO user_contacts (identifier, name, number) VALUES (@identifier, @name, @number)', + { + ['@identifier'] = xPlayer.identifier, + ['@name'] = contactName, + ['@number'] = phoneNumber + }, + function(rowsChanged) + + TriggerClientEvent('esx:showNotification', _source, 'Contact ajouté') + + local xPlayers = ESX.GetPlayers() + local isOnline = false + + for k,v in pairs(xPlayers) do + if v.get('phoneNumber') == phoneNumber then + isOnline = true + break + end + end + + TriggerClientEvent('esx_phone:addContact', source, contactName, phoneNumber, isOnline) + end + ) + + end + end + + else + TriggerClientEvent('esx:showNotification', source, 'Ce numéro n\'est pas attribué...') + end + + end + ) + +end) + +AddEventHandler('esx_phone:ready', function() + TriggerEvent('esx_phone:registerCallback', function(source, phoneNumber, message, anon) + + local xPlayer = ESX.GetPlayerFromId(source) + local xPlayers = ESX.GetPlayers() + local job = 'player' + + print('MESSAGE => ' .. xPlayer.name .. '@' .. phoneNumber .. ' : ' .. message) + + for k, v in pairs(xPlayers) do + + if phoneNumber == "police" then + + if v.job.name == 'cop' then + job = "ALERTE POLICE" + TriggerClientEvent('esx_phone:onMessage', v.source, xPlayer.get('phoneNumber'), message, xPlayer.get('coords'), anon, job, GetDistpatchRequestId()) + end + + elseif phoneNumber == "ambulance" then + + if v.job.name == 'ambulance' then + job = "ALERTE AMBULANCE" + TriggerClientEvent('esx_phone:onMessage', v.source, xPlayer.get('phoneNumber'), message, xPlayer.get('coords'), anon, job, GetDistpatchRequestId()) + + end + + elseif phoneNumber == "depanneur" then + + if v.job.name == 'depanneur' then + job = "APPEL DÉPANNEUR" + TriggerClientEvent('esx_phone:onMessage', v.source, xPlayer.get('phoneNumber'), message, xPlayer.get('coords'), anon, job, GetDistpatchRequestId()) + end + + elseif v.get('phoneNumber') == phoneNumber then + TriggerClientEvent('esx_phone:onMessage', v.source, xPlayer.get('phoneNumber'), message, xPlayer.get('coords'), anon, job, false) + end + + end + + end) +end) + +RegisterServerEvent('esx_phone:stopDispatch') +AddEventHandler('esx_phone:stopDispatch', function(dispatchRequestId) + TriggerClientEvent('esx_phone:stopDispatch', -1, dispatchRequestId, GetPlayerName(source)) +end) \ No newline at end of file