· 4 years ago · Jun 14, 2021, 04:10 PM
1local debug = debug
2local coroutine_close = coroutine.close or (function(c) end) -- 5.3 compatibility
3
4-- setup msgpack compat
5msgpack.set_string('string_compat')
6msgpack.set_integer('unsigned')
7msgpack.set_array('without_hole')
8msgpack.setoption('empty_table_as_array', true)
9
10-- setup json compat
11json.version = json._VERSION -- Version compatibility
12json.setoption("empty_table_as_array", true)
13json.setoption('with_hole', true)
14
15-- temp
16local function FormatStackTrace()
17 return Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString())
18end
19
20local function ProfilerEnterScope(scopeName)
21 return Citizen.InvokeNative(`PROFILER_ENTER_SCOPE` & 0xFFFFFFFF, scopeName)
22end
23
24local function ProfilerExitScope()
25 return Citizen.InvokeNative(`PROFILER_EXIT_SCOPE` & 0xFFFFFFFF)
26end
27
28local newThreads = {}
29local threads = setmetatable({}, {
30 -- This circumvents undefined behaviour in "next" (and therefore "pairs")
31 __newindex = newThreads,
32 -- This is needed for CreateThreadNow to work correctly
33 __index = newThreads
34})
35
36local boundaryIdx = 1
37local runningThread
38
39local function dummyUseBoundary(idx)
40 return nil
41end
42
43local function getBoundaryFunc(bfn, bid)
44 return function(fn, ...)
45 local boundary = bid or (boundaryIdx + 1)
46 boundaryIdx = boundaryIdx + 1
47
48 bfn(boundary, coroutine.running())
49
50 local wrap = function(...)
51 dummyUseBoundary(boundary)
52
53 local v = table.pack(fn(...))
54 return table.unpack(v)
55 end
56
57 local v = table.pack(wrap(...))
58
59 bfn(boundary, nil)
60
61 return table.unpack(v)
62 end
63end
64
65local runWithBoundaryStart = getBoundaryFunc(Citizen.SubmitBoundaryStart)
66local runWithBoundaryEnd = getBoundaryFunc(Citizen.SubmitBoundaryEnd)
67
68--[[
69
70 Thread handling
71
72]]
73local function resumeThread(coro) -- Internal utility
74 if coroutine.status(coro) == "dead" then
75 threads[coro] = nil
76 coroutine_close(coro)
77 return false
78 end
79
80 runningThread = coro
81
82 local thread = threads[coro]
83
84 if thread then
85 if thread.name then
86 ProfilerEnterScope(thread.name)
87 else
88 ProfilerEnterScope('thread')
89 end
90
91 Citizen.SubmitBoundaryStart(thread.boundary, coro)
92 end
93
94 local ok, wakeTimeOrErr = coroutine.resume(coro)
95
96 if ok then
97 thread = threads[coro]
98 if thread then
99 thread.wakeTime = wakeTimeOrErr or 0
100 end
101 else
102 --Citizen.Trace("Error resuming coroutine: " .. debug.traceback(coro, wakeTimeOrErr) .. "\n")
103 local fst = FormatStackTrace()
104
105 if fst then
106 Citizen.Trace("^1SCRIPT ERROR: " .. wakeTimeOrErr .. "^7\n")
107 Citizen.Trace(fst)
108 end
109 end
110
111 runningThread = nil
112
113 ProfilerExitScope()
114
115 -- Return not finished
116 return coroutine.status(coro) ~= "dead"
117end
118
119function Citizen.CreateThread(threadFunction)
120 local bid = boundaryIdx + 1
121 boundaryIdx = boundaryIdx + 1
122
123 local tfn = function()
124 return runWithBoundaryStart(threadFunction, bid)
125 end
126
127 local di = debug.getinfo(threadFunction, 'S')
128
129 threads[coroutine.create(tfn)] = {
130 wakeTime = 0,
131 boundary = bid,
132 name = ('thread %s[%d..%d]'):format(di.short_src, di.linedefined, di.lastlinedefined)
133 }
134end
135
136function Citizen.Wait(msec)
137 coroutine.yield(GetGameTimer() + msec)
138end
139
140-- legacy alias (and to prevent people from calling the game's function)
141Wait = Citizen.Wait
142CreateThread = Citizen.CreateThread
143
144function Citizen.CreateThreadNow(threadFunction, name)
145 local bid = boundaryIdx + 1
146 boundaryIdx = boundaryIdx + 1
147
148 local di = debug.getinfo(threadFunction, 'S')
149 name = name or ('thread_now %s[%d..%d]'):format(di.short_src, di.linedefined, di.lastlinedefined)
150
151 local tfn = function()
152 return runWithBoundaryStart(threadFunction, bid)
153 end
154
155 local coro = coroutine.create(tfn)
156 threads[coro] = {
157 wakeTime = 0,
158 boundary = bid,
159 name = name
160 }
161 return resumeThread(coro)
162end
163
164function Citizen.Await(promise)
165 local coro = coroutine.running()
166 if not coro then
167 error("Current execution context is not in the scheduler, you should use CreateThread / SetTimeout or Event system (AddEventHandler) to be able to Await")
168 end
169
170 -- Indicates if the promise has already been resolved or rejected
171 -- This is a hack since the API does not expose its state
172 local isDone = false
173 local result, err
174 promise = promise:next(function(...)
175 isDone = true
176 result = {...}
177 end,function(error)
178 isDone = true
179 err = error
180 end)
181
182 if not isDone then
183 local threadData = threads[coro]
184 threads[coro] = nil
185
186 local function reattach()
187 threads[coro] = threadData
188 resumeThread(coro)
189 end
190
191 promise:next(reattach, reattach)
192 Citizen.Wait(0)
193 end
194
195 if err then
196 error(err)
197 end
198
199 return table.unpack(result)
200end
201
202function Citizen.SetTimeout(msec, callback)
203 local bid = boundaryIdx + 1
204 boundaryIdx = boundaryIdx + 1
205
206 local tfn = function()
207 return runWithBoundaryStart(callback, bid)
208 end
209
210 local coro = coroutine.create(tfn)
211 threads[coro] = {
212 wakeTime = GetGameTimer() + msec,
213 boundary = bid
214 }
215end
216
217SetTimeout = Citizen.SetTimeout
218
219Citizen.SetTickRoutine(function()
220 local curTime = GetGameTimer()
221
222 for coro, thread in pairs(newThreads) do
223 rawset(threads, coro, thread)
224 newThreads[coro] = nil
225 end
226
227 for coro, thread in pairs(threads) do
228 if curTime >= thread.wakeTime then
229 resumeThread(coro)
230 end
231 end
232end)
233
234--[[
235
236 Event handling
237
238]]
239
240local alwaysSafeEvents = {
241 ["playerDropped"] = true,
242 ["playerConnecting"] = true
243}
244
245local eventHandlers = {}
246local deserializingNetEvent = false
247
248Citizen.SetEventRoutine(function(eventName, eventPayload, eventSource)
249 -- set the event source
250 local lastSource = _G.source
251 _G.source = eventSource
252
253 -- try finding an event handler for the event
254 local eventHandlerEntry = eventHandlers[eventName]
255
256 -- deserialize the event structure (so that we end up adding references to delete later on)
257 local data = msgpack.unpack(eventPayload)
258
259 if eventHandlerEntry and eventHandlerEntry.handlers then
260 -- if this is a net event and we don't allow this event to be triggered from the network, return
261 if eventSource:sub(1, 3) == 'net' then
262 if not eventHandlerEntry.safeForNet and not alwaysSafeEvents[eventName] then
263 Citizen.Trace('event ' .. eventName .. " was not safe for net\n")
264
265 return
266 end
267
268 deserializingNetEvent = { source = eventSource }
269 _G.source = tonumber(eventSource:sub(5))
270 end
271
272 -- return an empty table if the data is nil
273 if not data then
274 data = {}
275 end
276
277 -- reset serialization
278 deserializingNetEvent = nil
279
280 -- if this is a table...
281 if type(data) == 'table' then
282 -- loop through all the event handlers
283 for k, handler in pairs(eventHandlerEntry.handlers) do
284 local di = debug.getinfo(handler)
285
286 Citizen.CreateThreadNow(function()
287 handler(table.unpack(data))
288 end, ('event %s [%s[%d..%d]]'):format(eventName, di.short_src, di.linedefined, di.lastlinedefined))
289 end
290 end
291 end
292
293 _G.source = lastSource
294end)
295
296local stackTraceBoundaryIdx
297
298Citizen.SetStackTraceRoutine(function(bs, ts, be, te)
299 if not ts then
300 ts = runningThread
301 end
302
303 local t
304 local n = 1
305
306 local frames = {}
307 local skip = false
308
309 if bs then
310 skip = true
311 end
312
313 repeat
314 if ts then
315 t = debug.getinfo(ts, n, 'nlfS')
316 else
317 t = debug.getinfo(n, 'nlfS')
318 end
319
320 if t then
321 if t.name == 'wrap' and t.source == '@citizen:/scripting/lua/scheduler.lua' then
322 if not stackTraceBoundaryIdx then
323 local b, v
324 local u = 1
325
326 repeat
327 b, v = debug.getupvalue(t.func, u)
328
329 if b == 'boundary' then
330 break
331 end
332
333 u = u + 1
334 until not b
335
336 stackTraceBoundaryIdx = u
337 end
338
339 local _, boundary = debug.getupvalue(t.func, stackTraceBoundaryIdx)
340
341 if boundary == bs then
342 skip = false
343 end
344
345 if boundary == be then
346 break
347 end
348 end
349
350 if not skip then
351 if t.source and t.source:sub(1, 1) ~= '=' and t.source:sub(1, 10) ~= '@citizen:/' then
352 table.insert(frames, {
353 file = t.source:sub(2),
354 line = t.currentline,
355 name = t.name or '[global chunk]'
356 })
357 end
358 end
359
360 n = n + 1
361 end
362 until not t
363
364 return msgpack.pack(frames)
365end)
366
367local eventKey = 10
368
369function AddEventHandler(eventName, eventRoutine)
370 local tableEntry = eventHandlers[eventName]
371
372 if not tableEntry then
373 tableEntry = { }
374
375 eventHandlers[eventName] = tableEntry
376 end
377
378 if not tableEntry.handlers then
379 tableEntry.handlers = { }
380 end
381
382 eventKey = eventKey + 1
383 tableEntry.handlers[eventKey] = eventRoutine
384
385 RegisterResourceAsEventHandler(eventName)
386
387 return {
388 key = eventKey,
389 name = eventName
390 }
391end
392
393function RemoveEventHandler(eventData)
394 if not eventData.key and not eventData.name then
395 error('Invalid event data passed to RemoveEventHandler()')
396 end
397
398 -- remove the entry
399 eventHandlers[eventData.name].handlers[eventData.key] = nil
400end
401
402function RegisterNetEvent(eventName)
403 local tableEntry = eventHandlers[eventName]
404
405 if not tableEntry then
406 tableEntry = { }
407
408 eventHandlers[eventName] = tableEntry
409 end
410
411 tableEntry.safeForNet = true
412end
413
414function TriggerEvent(eventName, ...)
415 local payload = msgpack.pack({...})
416
417 return runWithBoundaryEnd(function()
418 return TriggerEventInternal(eventName, payload, payload:len())
419 end)
420end
421
422if IsDuplicityVersion() then
423 function TriggerClientEvent(eventName, playerId, ...)
424 local payload = msgpack.pack({...})
425
426 return TriggerClientEventInternal(eventName, playerId, payload, payload:len())
427 end
428
429 function TriggerLatentClientEvent(eventName, playerId, bps, ...)
430 local payload = msgpack.pack({...})
431
432 return TriggerLatentClientEventInternal(eventName, playerId, payload, payload:len(), tonumber(bps))
433 end
434
435 RegisterServerEvent = RegisterNetEvent
436 RconPrint = Citizen.Trace
437 GetPlayerEP = GetPlayerEndpoint
438 RconLog = function() end
439
440 function GetPlayerIdentifiers(player)
441 local numIds = GetNumPlayerIdentifiers(player)
442 local t = {}
443
444 for i = 0, numIds - 1 do
445 table.insert(t, GetPlayerIdentifier(player, i))
446 end
447
448 return t
449 end
450
451 function GetPlayers()
452 local num = GetNumPlayerIndices()
453 local t = {}
454
455 for i = 0, num - 1 do
456 table.insert(t, GetPlayerFromIndex(i))
457 end
458
459 return t
460 end
461
462 local httpDispatch = {}
463 AddEventHandler('__cfx_internal:httpResponse', function(token, status, body, headers)
464 if httpDispatch[token] then
465 local userCallback = httpDispatch[token]
466 httpDispatch[token] = nil
467 userCallback(status, body, headers)
468 end
469 end)
470
471 function PerformHttpRequest(url, cb, method, data, headers)
472 local t = {
473 url = url,
474 method = method or 'GET',
475 data = data or '',
476 headers = headers or {}
477 }
478
479 local d = json.encode(t)
480 local id = PerformHttpRequestInternal(d, d:len())
481
482 httpDispatch[id] = cb
483 end
484else
485 function TriggerServerEvent(eventName, ...)
486 local payload = msgpack.pack({...})
487
488 return TriggerServerEventInternal(eventName, payload, payload:len())
489 end
490
491 function TriggerLatentServerEvent(eventName, bps, ...)
492 local payload = msgpack.pack({...})
493
494 return TriggerLatentServerEventInternal(eventName, payload, payload:len(), tonumber(bps))
495 end
496end
497
498local funcRefs = {}
499local funcRefIdx = 0
500
501local function MakeFunctionReference(func)
502 local thisIdx = funcRefIdx
503
504 funcRefs[thisIdx] = {
505 func = func,
506 refs = 0
507 }
508
509 funcRefIdx = funcRefIdx + 1
510
511 local refStr = Citizen.CanonicalizeRef(thisIdx)
512 return refStr
513end
514
515function Citizen.GetFunctionReference(func)
516 if type(func) == 'function' then
517 return MakeFunctionReference(func)
518 elseif type(func) == 'table' and rawget(func, '__cfx_functionReference') then
519 return MakeFunctionReference(function(...)
520 return func(...)
521 end)
522 end
523
524 return nil
525end
526
527local function doStackFormat(err)
528 local fst = FormatStackTrace()
529
530 -- already recovering from an error
531 if not fst then
532 return nil
533 end
534
535 return '^1SCRIPT ERROR: ' .. err .. "^7\n" .. fst
536end
537
538Citizen.SetCallRefRoutine(function(refId, argsSerialized)
539 local refPtr = funcRefs[refId]
540
541 if not refPtr then
542 Citizen.Trace('Invalid ref call attempt: ' .. refId .. "\n")
543
544 return msgpack.pack({})
545 end
546
547 local ref = refPtr.func
548
549 local err
550 local retvals
551 local cb = {}
552
553 local di = debug.getinfo(ref)
554
555 local waited = Citizen.CreateThreadNow(function()
556 local status, result, error = xpcall(function()
557 retvals = { ref(table.unpack(msgpack.unpack(argsSerialized))) }
558 end, doStackFormat)
559
560 if not status then
561 err = result or ''
562 end
563
564 if cb.cb then
565 cb.cb(retvals or false, err)
566 end
567 end, ('ref call [%s[%d..%d]]'):format(di.short_src, di.linedefined, di.lastlinedefined))
568
569 if not waited then
570 if err then
571 --error(err)
572 if err ~= '' then
573 Citizen.Trace(err)
574 end
575
576 return msgpack.pack(nil)
577 end
578
579 return msgpack.pack(retvals)
580 else
581 return msgpack.pack({{
582 __cfx_async_retval = function(rvcb)
583 cb.cb = rvcb
584 end
585 }})
586 end
587end)
588
589Citizen.SetDuplicateRefRoutine(function(refId)
590 local ref = funcRefs[refId]
591
592 if ref then
593 --print(('%s %s ref %d - new refcount %d (from %s)'):format(GetCurrentResourceName(), 'duplicating', refId, ref.refs + 1, GetInvokingResource() or 'nil'))
594
595 ref.refs = ref.refs + 1
596
597 return refId
598 end
599
600 return -1
601end)
602
603Citizen.SetDeleteRefRoutine(function(refId)
604 local ref = funcRefs[refId]
605
606 if ref then
607 --print(('%s %s ref %d - new refcount %d (from %s)'):format(GetCurrentResourceName(), 'deleting', refId, ref.refs - 1, GetInvokingResource() or 'nil'))
608
609 ref.refs = ref.refs - 1
610
611 if ref.refs <= 0 then
612 funcRefs[refId] = nil
613 end
614 end
615end)
616
617-- RPC REQUEST HANDLER
618local InvokeRpcEvent
619
620if GetCurrentResourceName() == 'sessionmanager' then
621 local rpcEvName = ('__cfx_rpcReq')
622
623 RegisterNetEvent(rpcEvName)
624
625 AddEventHandler(rpcEvName, function(retEvent, retId, refId, args)
626 local source = source
627
628 local eventTriggerFn = TriggerServerEvent
629
630 if IsDuplicityVersion() then
631 eventTriggerFn = function(name, ...)
632 TriggerClientEvent(name, source, ...)
633 end
634 end
635
636 local returnEvent = function(args, err)
637 eventTriggerFn(retEvent, retId, args, err)
638 end
639
640 local function makeArgRefs(o)
641 if type(o) == 'table' then
642 for k, v in pairs(o) do
643 if type(v) == 'table' and rawget(v, '__cfx_functionReference') then
644 o[k] = function(...)
645 return InvokeRpcEvent(source, rawget(v, '__cfx_functionReference'), {...})
646 end
647 end
648
649 makeArgRefs(v)
650 end
651 end
652 end
653
654 makeArgRefs(args)
655
656 runWithBoundaryEnd(function()
657 local payload = Citizen.InvokeFunctionReference(refId, msgpack.pack(args))
658
659 if #payload == 0 then
660 returnEvent(false, 'err')
661 return
662 end
663
664 local rvs = msgpack.unpack(payload)
665
666 if type(rvs[1]) == 'table' and rvs[1].__cfx_async_retval then
667 rvs[1].__cfx_async_retval(returnEvent)
668 else
669 returnEvent(rvs)
670 end
671 end)
672 end)
673end
674
675local rpcId = 0
676local rpcPromises = {}
677local playerPromises = {}
678
679-- RPC REPLY HANDLER
680local repName = ('__cfx_rpcRep:%s'):format(GetCurrentResourceName())
681
682RegisterNetEvent(repName)
683
684AddEventHandler(repName, function(retId, args, err)
685 local promise = rpcPromises[retId]
686 rpcPromises[retId] = nil
687
688 -- remove any player promise for us
689 for k, v in pairs(playerPromises) do
690 v[retId] = nil
691 end
692
693 if promise then
694 if args then
695 promise:resolve(args[1])
696 elseif err then
697 promise:reject(err)
698 end
699 end
700end)
701
702if IsDuplicityVersion() then
703 AddEventHandler('playerDropped', function(reason)
704 local source = source
705
706 if playerPromises[source] then
707 for k, v in pairs(playerPromises[source]) do
708 local p = rpcPromises[k]
709
710 if p then
711 p:reject('Player dropped: ' .. reason)
712 end
713 end
714 end
715
716 playerPromises[source] = nil
717 end)
718end
719
720local EXT_FUNCREF = 10
721local EXT_LOCALFUNCREF = 11
722
723msgpack.extend_clear(EXT_FUNCREF, EXT_LOCALFUNCREF)
724
725-- RPC INVOCATION
726InvokeRpcEvent = function(source, ref, args)
727 if not coroutine.running() then
728 error('RPC delegates can only be invoked from a thread.')
729 end
730
731 local src = source
732
733 local eventTriggerFn = TriggerServerEvent
734
735 if IsDuplicityVersion() then
736 eventTriggerFn = function(name, ...)
737 TriggerClientEvent(name, src, ...)
738 end
739 end
740
741 local p = promise.new()
742 local asyncId = rpcId
743 rpcId = rpcId + 1
744
745 local refId = ('%d:%d'):format(GetInstanceId(), asyncId)
746
747 eventTriggerFn('__cfx_rpcReq', repName, refId, ref, args)
748
749 -- add rpc promise
750 rpcPromises[refId] = p
751
752 -- add a player promise
753 if not playerPromises[src] then
754 playerPromises[src] = {}
755 end
756
757 playerPromises[src][refId] = true
758
759 return Citizen.Await(p)
760end
761
762local funcref_mt = nil
763
764funcref_mt = msgpack.extend({
765 __gc = function(t)
766 DeleteFunctionReference(rawget(t, '__cfx_functionReference'))
767 end,
768
769 __index = function(t, k)
770 error('Cannot index a funcref')
771 end,
772
773 __newindex = function(t, k, v)
774 error('Cannot set indexes on a funcref')
775 end,
776
777 __call = function(t, ...)
778 local netSource = rawget(t, '__cfx_functionSource')
779 local ref = rawget(t, '__cfx_functionReference')
780
781 if not netSource then
782 local args = msgpack.pack({...})
783
784 -- as Lua doesn't allow directly getting lengths from a data buffer, and _s will zero-terminate, we have a wrapper in the game itself
785 local rv = runWithBoundaryEnd(function()
786 return Citizen.InvokeFunctionReference(ref, args)
787 end)
788 local rvs = msgpack.unpack(rv)
789
790 -- handle async retvals from refs
791 if rvs and type(rvs[1]) == 'table' and rawget(rvs[1], '__cfx_async_retval') and coroutine.running() then
792 local p = promise.new()
793
794 rvs[1].__cfx_async_retval(function(r, e)
795 if r then
796 p:resolve(r)
797 elseif e then
798 p:reject(e)
799 end
800 end)
801
802 return table.unpack(Citizen.Await(p))
803 end
804
805 return table.unpack(rvs)
806 else
807 return InvokeRpcEvent(tonumber(netSource.source:sub(5)), ref, {...})
808 end
809 end,
810
811 __ext = EXT_FUNCREF,
812
813 __pack = function(self, tag)
814 local refstr = Citizen.GetFunctionReference(self)
815 if refstr then
816 return refstr
817 else
818 error(("Unknown funcref type: %d %s"):format(tag, type(self)))
819 end
820 end,
821
822 __unpack = function(data, tag)
823 local ref = data
824
825 -- add a reference
826 DuplicateFunctionReference(ref)
827
828 local tbl = {
829 __cfx_functionReference = ref,
830 __cfx_functionSource = deserializingNetEvent
831 }
832
833 if tag == EXT_LOCALFUNCREF then
834 tbl.__cfx_functionSource = nil
835 end
836
837 tbl = setmetatable(tbl, funcref_mt)
838
839 return tbl
840 end,
841})
842
843--[[ Also initialize unpackers for local function references --]]
844msgpack.extend({
845 __ext = EXT_LOCALFUNCREF,
846 __pack = funcref_mt.__pack,
847 __unpack = funcref_mt.__unpack,
848})
849
850msgpack.settype("function", EXT_FUNCREF)
851
852-- exports compatibility
853local function getExportEventName(resource, name)
854 return string.format('__cfx_export_%s_%s', resource, name)
855end
856
857-- callback cache to avoid extra call to serialization / deserialization process at each time getting an export
858local exportsCallbackCache = {}
859
860local exportKey = (IsDuplicityVersion() and 'server_export' or 'export')
861
862AddEventHandler(('on%sResourceStart'):format(IsDuplicityVersion() and 'Server' or 'Client'), function(resource)
863 if resource == GetCurrentResourceName() then
864 local numMetaData = GetNumResourceMetadata(resource, exportKey) or 0
865
866 for i = 0, numMetaData-1 do
867 local exportName = GetResourceMetadata(resource, exportKey, i)
868
869 AddEventHandler(getExportEventName(resource, exportName), function(setCB)
870 -- get the entry from *our* global table and invoke the set callback
871 if _G[exportName] then
872 setCB(_G[exportName])
873 end
874 end)
875 end
876 end
877end)
878
879-- Remove cache when resource stop to avoid calling unexisting exports
880AddEventHandler(('on%sResourceStop'):format(IsDuplicityVersion() and 'Server' or 'Client'), function(resource)
881 exportsCallbackCache[resource] = {}
882end)
883
884-- invocation bit
885exports = {}
886
887setmetatable(exports, {
888 __index = function(t, k)
889 local resource = k
890
891 return setmetatable({}, {
892 __index = function(t, k)
893 if not exportsCallbackCache[resource] then
894 exportsCallbackCache[resource] = {}
895 end
896
897 if not exportsCallbackCache[resource][k] then
898 TriggerEvent(getExportEventName(resource, k), function(exportData)
899 exportsCallbackCache[resource][k] = exportData
900 end)
901
902 if not exportsCallbackCache[resource][k] then
903 error('No such export ' .. k .. ' in resource ' .. resource)
904 end
905 end
906
907 return function(self, ...)
908 local status, result = pcall(exportsCallbackCache[resource][k], ...)
909
910 if not status then
911 error('An error happened while calling export ' .. k .. ' of resource ' .. resource .. ' (' .. result .. '), see above for details')
912 end
913
914 return result
915 end
916 end,
917
918 __newindex = function(t, k, v)
919 error('cannot set values on an export resource')
920 end
921 })
922 end,
923
924 __newindex = function(t, k, v)
925 error('cannot set values on exports')
926 end,
927
928 __call = function(t, exportName, func)
929 AddEventHandler(getExportEventName(GetCurrentResourceName(), exportName), function(setCB)
930 setCB(func)
931 end)
932 end
933})
934
935-- NUI callbacks
936if not IsDuplicityVersion() then
937 function RegisterNUICallback(type, callback)
938 RegisterNuiCallbackType(type)
939
940 AddEventHandler('__cfx_nui:' .. type, function(body, resultCallback)
941 local status, err = pcall(function()
942 callback(body, resultCallback)
943 end)
944
945 if err then
946 Citizen.Trace("error during NUI callback " .. type .. ": " .. err .. "\n")
947 end
948 end)
949 end
950
951 local _sendNuiMessage = SendNuiMessage
952
953 function SendNUIMessage(message)
954 _sendNuiMessage(json.encode(message))
955 end
956end
957
958-- entity helpers
959local EXT_ENTITY = 41
960local EXT_PLAYER = 42
961
962msgpack.extend_clear(EXT_ENTITY, EXT_PLAYER)
963
964local function NewStateBag(es)
965 local sv = IsDuplicityVersion()
966
967 return setmetatable({}, {
968 __index = function(_, s)
969 if s == 'set' then
970 return function(_, s, v, r)
971 local payload = msgpack.pack(v)
972 SetStateBagValue(es, s, payload, payload:len(), r)
973 end
974 end
975
976 return GetStateBagValue(es, s)
977 end,
978
979 __newindex = function(_, s, v)
980 local payload = msgpack.pack(v)
981 SetStateBagValue(es, s, payload, payload:len(), sv)
982 end
983 })
984end
985
986GlobalState = NewStateBag('global')
987
988local entityTM = {
989 __index = function(t, s)
990 if s == 'state' then
991 local es = ('entity:%d'):format(NetworkGetNetworkIdFromEntity(t.__data))
992
993 if IsDuplicityVersion() then
994 EnsureEntityStateBag(t.__data)
995 end
996
997 return NewStateBag(es)
998 end
999
1000 return nil
1001 end,
1002
1003 __newindex = function()
1004 error('Not allowed at this time.')
1005 end,
1006
1007 __ext = EXT_ENTITY,
1008
1009 __pack = function(self, t)
1010 return tostring(NetworkGetNetworkIdFromEntity(self.__data))
1011 end,
1012
1013 __unpack = function(data, t)
1014 local ref = NetworkGetEntityFromNetworkId(tonumber(data))
1015
1016 return setmetatable({
1017 __data = ref
1018 }, entityTM)
1019 end
1020}
1021
1022msgpack.extend(entityTM)
1023
1024local playerTM = {
1025 __index = function(t, s)
1026 if s == 'state' then
1027 local pid = t.__data
1028
1029 if pid == -1 then
1030 pid = GetPlayerServerId(PlayerId())
1031 end
1032
1033 local es = ('player:%d'):format(pid)
1034
1035 return NewStateBag(es)
1036 end
1037
1038 return nil
1039 end,
1040
1041 __newindex = function()
1042 error('Not allowed at this time.')
1043 end,
1044
1045 __ext = EXT_PLAYER,
1046
1047 __pack = function(self, t)
1048 return tostring(self.__data)
1049 end,
1050
1051 __unpack = function(data, t)
1052 local ref = tonumber(data)
1053
1054 return setmetatable({
1055 __data = ref
1056 }, playerTM)
1057 end
1058}
1059
1060msgpack.extend(playerTM)
1061
1062function Entity(ent)
1063 if type(ent) == 'number' then
1064 return setmetatable({
1065 __data = ent
1066 }, entityTM)
1067 end
1068
1069 return ent
1070end
1071
1072function Player(ent)
1073 if type(ent) == 'number' or type(ent) == 'string' then
1074 return setmetatable({
1075 __data = tonumber(ent)
1076 }, playerTM)
1077 end
1078
1079 return ent
1080end
1081
1082if not IsDuplicityVersion() then
1083 LocalPlayer = Player(-1)
1084end