├── README.md
├── [examples]
├── not-enough-palm
│ ├── __resource.lua
│ └── nep.xml
├── stunt-afterburner
│ ├── __resource.lua
│ └── map.xml
├── stunt-canyon-crossing
│ ├── __resource.lua
│ └── map.xml
├── stunt-chiliad
│ ├── __resource.lua
│ └── map.xml
├── stunt-city-air
│ ├── __resource.lua
│ └── map.xml
├── stunt-double-loop
│ ├── __resource.lua
│ └── map.xml
├── stunt-downtown-loop
│ ├── __resource.lua
│ └── map.xml
├── stunt-duel
│ ├── __resource.lua
│ └── map.xml
├── stunt-east-coast
│ ├── __resource.lua
│ └── map.xml
├── stunt-forest
│ ├── __resource.lua
│ └── map.xml
├── stunt-h200
│ ├── __resource.lua
│ └── map.xml
├── stunt-high-flier
│ ├── __resource.lua
│ └── map.xml
├── stunt-maze-bank-ascent
│ ├── __resource.lua
│ └── map.xml
├── stunt-nightlife
│ ├── __resource.lua
│ └── map.xml
├── stunt-over-and-under
│ ├── __resource.lua
│ └── map.xml
├── stunt-over-the-bridge
│ ├── __resource.lua
│ └── map.xml
├── stunt-plummet
│ ├── __resource.lua
│ └── map.xml
├── stunt-racing-alley
│ ├── __resource.lua
│ └── map.xml
├── stunt-rally
│ ├── __resource.lua
│ └── map.xml
├── stunt-splits
│ ├── __resource.lua
│ └── map.xml
├── stunt-the-wave
│ ├── __resource.lua
│ └── map.xml
├── stunt-threading-the-needle
│ ├── __resource.lua
│ └── map.xml
├── stunt-trench-1
│ ├── __resource.lua
│ └── map.xml
├── stunt-trench-2
│ ├── __resource.lua
│ └── map.xml
├── stunt-turbine
│ ├── __resource.lua
│ └── map.xml
├── stunt-vespucci
│ ├── __resource.lua
│ └── map.xml
├── stunt-vinewood-downhill
│ ├── __resource.lua
│ └── map.xml
└── stunt-zebra
│ ├── __resource.lua
│ └── map.xml
├── object-loader
├── __resource.lua
├── object_loader.lua
└── xml.lua
└── object-teleports
├── __resource.lua
├── client.lua
└── server.lua
/README.md:
--------------------------------------------------------------------------------
1 | # ObjectLoader
2 |
3 | This is a resource for [FiveReborn](http://www.fivereborn.com/) that loads object files from [Map
4 | Editor](https://www.gta5-mods.com/scripts/map-editor) (and some "Spooner" format) on clients.
5 |
6 | It also comes with a teleport handler (use **/mtp** in the chat to view teleports) and has some API calls.
7 |
8 | Some example maps are included in the **[examples]** folder.
9 |
10 | To use, start the `object-loader` resource, an object resource (like `stunt-chiliad`) and optionally the `object-teleports`
11 | resource.
12 |
13 | ## Screenshot
14 |
15 | 
16 |
--------------------------------------------------------------------------------
/[examples]/not-enough-palm/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'nep.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-afterburner/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-canyon-crossing/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-chiliad/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-city-air/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-double-loop/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-downtown-loop/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-duel/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-east-coast/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-forest/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-h200/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-h200/map.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/[examples]/stunt-high-flier/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-maze-bank-ascent/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-nightlife/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-nightlife/map.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/[examples]/stunt-over-and-under/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-over-the-bridge/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-plummet/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-racing-alley/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-rally/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-splits/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-the-wave/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-threading-the-needle/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-trench-1/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-trench-2/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-turbine/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-vespucci/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-vespucci/map.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/[examples]/stunt-vinewood-downhill/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/[examples]/stunt-zebra/__resource.lua:
--------------------------------------------------------------------------------
1 | local function object_entry(data)
2 | dependency 'object-loader'
3 |
4 | files(data)
5 | object_file(data)
6 | end
7 |
8 | object_entry 'map.xml'
--------------------------------------------------------------------------------
/object-loader/__resource.lua:
--------------------------------------------------------------------------------
1 | client_script {
2 | 'object_loader.lua',
3 | 'xml.lua',
4 | }
5 |
6 | export 'getSpawns'
--------------------------------------------------------------------------------
/object-loader/object_loader.lua:
--------------------------------------------------------------------------------
1 | local objectSets = {}
2 | local spawns = {}
3 | local currentParseName, currentParseResource
4 |
5 | function DeleteObject(object)
6 | return Citizen.InvokeNative(0x539E0AE3E6634B9F, Citizen.PointerValueIntInitialized(object))
7 | end
8 |
9 | local function getSetLoader(sets)
10 | return function()
11 | -- request all the models
12 | for _,obj in ipairs(sets) do
13 | RequestModel(obj.hash)
14 | end
15 |
16 | -- make sure all the models are loaded
17 | while true do
18 | local loaded = true
19 |
20 | Citizen.Wait(0)
21 |
22 | for _,obj in ipairs(sets) do
23 | if not HasModelLoaded(obj.hash) then
24 | loaded = false
25 | break
26 | end
27 | end
28 |
29 | if loaded then
30 | break
31 | end
32 | end
33 | end
34 | end
35 |
36 | local function clearObjectSet(set)
37 | for _, obj in ipairs(set) do
38 | if obj.object then
39 | DeleteObject(obj.object)
40 | end
41 |
42 | SetModelAsNoLongerNeeded(obj.hash)
43 | end
44 | end
45 |
46 | -- object streamer
47 | local function isNearObject(p1, obj)
48 | local diff = obj.pos - p1
49 | local dist = (diff.x * diff.x) + (diff.y * diff.y)
50 |
51 | return (dist < (400 * 400))
52 | end
53 |
54 | Citizen.CreateThread(function()
55 | while true do
56 | Citizen.Wait(100)
57 |
58 | -- spawn objects
59 | local pos = GetEntityCoords(GetPlayerPed(-1))
60 |
61 | for k, sets in pairs(objectSets) do
62 | for i, obj in ipairs(sets) do
63 | local shouldHave = isNearObject(pos, obj)
64 |
65 | if shouldHave and not obj.object then
66 | local o = CreateObjectNoOffset(obj.hash, obj.pos, false --[[ create netobj? ]], false, false)
67 |
68 | if o then
69 | SetEntityRotation(o, obj.rot, 2, true)
70 | FreezeEntityPosition(o, true)
71 |
72 | obj.object = o
73 | end
74 | elseif not shouldHave and obj.object then
75 | DeleteObject(obj.object)
76 | obj.object = nil
77 | end
78 |
79 | if (i % 75) == 0 then
80 | Citizen.Wait(15)
81 | end
82 | end
83 | end
84 | end
85 | end)
86 |
87 | local function registerObjectSpawn(name, pos, heading)
88 | local t = {
89 | name = name,
90 | filename = currentParseName,
91 | owner = currentParseResource,
92 | spawnPos = { pos.x, pos.y, pos.z },
93 | heading = heading
94 | }
95 |
96 | table.insert(spawns, t)
97 |
98 | TriggerEvent('objectLoader:onSpawnLoaded', t)
99 | end
100 |
101 | function getSpawns()
102 | return spawns
103 | end
104 |
105 | local function createObject(data)
106 | -- a no-op
107 | return data
108 | end
109 |
110 | local function parseIniObjectSet(data)
111 | local i = parseIni(data)
112 | local a = {}
113 |
114 | for k, v in pairs(i) do
115 | if v.Model then
116 | table.insert(a, createObject({
117 | pos = vector3(tonumber(v.x), tonumber(v.y), tonumber(v.z) + tonumber(v.h)),
118 | rot = quat(tonumber(v.qx), tonumber(v.qy), tonumber(v.qz), tonumber(v.qw)),
119 | hash = tonumber(v.Model)
120 | }))
121 | end
122 | end
123 |
124 | registerObjectSpawn(currentParseName, vector3(
125 | tonumber(i.Player.x),
126 | tonumber(i.Player.y),
127 | tonumber(i.Player.z)),
128 | 0.0)
129 |
130 | return a
131 | end
132 |
133 | local function parseMapEditorXml(xml)
134 | local a = {}
135 |
136 | for _,obj in ipairs(xml.Objects[1].MapObject) do
137 | if obj.Type[1] == 'Prop' then
138 | table.insert(a, createObject({
139 | pos = vector3(tonumber(obj.Position[1].X[1]), tonumber(obj.Position[1].Y[1]), tonumber(obj.Position[1].Z[1])),
140 | rot = vector3(tonumber(obj.Rotation[1].X[1]), tonumber(obj.Rotation[1].Y[1]), tonumber(obj.Rotation[1].Z[1])),
141 | hash = tonumber(obj.Hash[1])
142 | }))
143 | end
144 | end
145 |
146 | if xml.Metadata then
147 | registerObjectSpawn(xml.Metadata[1].Name[1], vector3(
148 | tonumber(xml.Metadata[1].TeleportPoint[1].X[1]),
149 | tonumber(xml.Metadata[1].TeleportPoint[1].Y[1]),
150 | tonumber(xml.Metadata[1].TeleportPoint[1].Z[1])),
151 | tonumber(xml.Metadata[1].TeleportPoint[1].Heading[1]))
152 | end
153 |
154 | return a
155 | end
156 |
157 | local function parseSpoonerXml(xml)
158 | local a = {}
159 |
160 | for _,obj in ipairs(xml.Placement) do
161 | if obj.Type[1] == '3' then
162 | table.insert(a, createObject({
163 | pos = vector3(tonumber(obj.PositionRotation[1].X[1]), tonumber(obj.PositionRotation[1].Y[1]), tonumber(obj.PositionRotation[1].Z[1])),
164 | rot = vector3(tonumber(obj.PositionRotation[1].Pitch[1]), tonumber(obj.PositionRotation[1].Roll[1]), tonumber(obj.PositionRotation[1].Yaw[1])),
165 | hash = tonumber(obj.ModelHash[1])
166 | }))
167 | end
168 | end
169 |
170 | if xml.ReferenceCoords then
171 | registerObjectSpawn(currentParseName, vector3(
172 | tonumber(xml.ReferenceCoords[1].X[1]),
173 | tonumber(xml.ReferenceCoords[1].Y[1]),
174 | tonumber(xml.ReferenceCoords[1].Z[1])),
175 | 0.0)
176 | end
177 |
178 | return a
179 | end
180 |
181 | local function processXml(el)
182 | local v = {}
183 | local text
184 |
185 | for _,kid in ipairs(el.kids) do
186 | if kid.type == 'text' then
187 | text = kid.value
188 | elseif kid.type == 'element' then
189 | if not v[kid.name] then
190 | v[kid.name] = {}
191 | end
192 |
193 | table.insert(v[kid.name], processXml(kid))
194 | end
195 | end
196 |
197 | v._ = el.attr
198 |
199 | if #el.attr == 0 and #el.el == 0 then
200 | v = text
201 | end
202 |
203 | return v
204 | end
205 |
206 | local function parseObjectSet(data)
207 | local xml = SLAXML:dom(data)
208 |
209 | if xml and xml.root then
210 | Citizen.Trace("parsed as xml\n")
211 |
212 | if xml.root.name == 'Map' then
213 | return parseMapEditorXml(processXml(xml.root))
214 | elseif xml.root.name == 'SpoonerPlacements' then
215 | return parseSpoonerXml(processXml(xml.root))
216 | end
217 | else
218 | -- ini maps don't work due to quaternions being weird
219 | --return parseIniObjectSet(data)
220 | return {}
221 | end
222 | end
223 |
224 | AddEventHandler('onClientResourceStart', function(name)
225 | local metaEntries = GetNumResourceMetadata(name, 'object_file')
226 |
227 | if not metaEntries then
228 | return
229 | end
230 |
231 | currentParseResource = name
232 |
233 | local sets = {}
234 |
235 | for i = 0, metaEntries - 1 do
236 | local fileName = GetResourceMetadata(name, 'object_file', i)
237 | local data = LoadResourceFile(name, fileName)
238 |
239 | currentParseName = fileName
240 |
241 | if data then
242 | table.merge(sets, parseObjectSet(data))
243 | end
244 | end
245 |
246 | objectSets[name] = sets
247 |
248 | Citizen.CreateThread(getSetLoader(sets))
249 | end)
250 |
251 | AddEventHandler('onClientResourceStop', function(name)
252 | if objectSets[name] then
253 | clearObjectSet(objectSets[name])
254 | end
255 | end)
256 |
257 | -- mapmanager support
258 | local mapObjectSets = {}
259 | local mapObjectSet = 1
260 |
261 | AddEventHandler('getMapDirectives', function(add, resource)
262 | local function addMap(state, data)
263 | local set = parseObjectSet(data)
264 |
265 | Citizen.CreateThread(getSetLoader(set))
266 |
267 | mapObjectSets[mapObjectSet] = set
268 | state.set = mapObjectSet
269 |
270 | mapObjectSet = mapObjectSet + 1
271 | end
272 |
273 | local function undoMap(state, arg)
274 | clearObjectSet(mapObjectSets[state.set])
275 | mapObjectSets[state.set] = nil
276 | end
277 |
278 | add('object_data', addMap, undoMap)
279 |
280 | if not resource then
281 | return
282 | end
283 |
284 | -- if no owning resource was specified, don't add the object_file directive
285 | add('object_file', function(state, name)
286 | local data = LoadResourceFile(resource, name)
287 |
288 | addMap(state, data)
289 | end, undoMap)
290 | end)
291 |
292 | function table.merge(t1, t2)
293 | for k,v in ipairs(t2) do
294 | table.insert(t1, v)
295 | end
296 | end
297 |
298 | -- ini parser
299 | --[[
300 | Copyright (c) 2012 Carreras Nicolas
301 |
302 | Permission is hereby granted, free of charge, to any person obtaining a copy
303 | of this software and associated documentation files (the "Software"), to deal
304 | in the Software without restriction, including without limitation the rights
305 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
306 | copies of the Software, and to permit persons to whom the Software is
307 | furnished to do so, subject to the following conditions:
308 |
309 | The above copyright notice and this permission notice shall be included in all
310 | copies or substantial portions of the Software.
311 |
312 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
313 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
314 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
315 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
316 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
317 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
318 | SOFTWARE.
319 | --]]
320 | --- Lua INI Parser.
321 | -- It has never been that simple to use INI files with Lua.
322 | --@author Dynodzzo
323 |
324 | --- Returns a table containing all the data from the INI file.
325 | --@param fileName The name of the INI file to parse. [string]
326 | --@return The table containing all data from the INI file. [table]
327 | function parseIni(fileData)
328 | local function lines(str)
329 | local t = {}
330 | local function helper(line) table.insert(t, line) return "" end
331 | helper((str:gsub("(.-)\r?\n", helper)))
332 | return t
333 | end
334 |
335 | local data = {};
336 | local section;
337 | for _, line in ipairs(lines(fileData)) do
338 | local tempSection = line:match('^%[([^%[%]]+)%]$');
339 | if(tempSection)then
340 | section = tonumber(tempSection) and tonumber(tempSection) or tempSection;
341 | data[section] = data[section] or {};
342 | end
343 | local param, value = line:match('^([%w|_]+)%s-=%s-(.+)$');
344 | if(param and value ~= nil)then
345 | if(tonumber(value))then
346 | value = tonumber(value);
347 | elseif(value == 'true')then
348 | value = true;
349 | elseif(value == 'false')then
350 | value = false;
351 | end
352 | if(tonumber(param))then
353 | param = tonumber(param);
354 | end
355 | data[section][param] = value;
356 | end
357 | end
358 | return data;
359 | end
360 |
--------------------------------------------------------------------------------
/object-loader/xml.lua:
--------------------------------------------------------------------------------
1 | -- Optional parser that creates a flat DOM from parsing
2 | --[=====================================================================[
3 | v0.7 Copyright © 2013-2014 Gavin Kistner ; MIT Licensed
4 | See http://github.com/Phrogz/SLAXML for details.
5 | --]=====================================================================]
6 | SLAXML = {
7 | VERSION = "0.7",
8 | _call = {
9 | pi = function(target,content)
10 | print(string.format("%s %s?>",target,content))
11 | end,
12 | comment = function(content)
13 | print(string.format("",content))
14 | end,
15 | startElement = function(name,nsURI,nsPrefix)
16 | io.write("<")
17 | if nsPrefix then io.write(nsPrefix,":") end
18 | io.write(name)
19 | if nsURI then io.write(" (ns='",nsURI,"')") end
20 | print(">")
21 | end,
22 | attribute = function(name,value,nsURI,nsPrefix)
23 | io.write(' ')
24 | if nsPrefix then io.write(nsPrefix,":") end
25 | io.write(name,'=',string.format('%q',value))
26 | if nsURI then io.write(" (ns='",nsURI,"')") end
27 | io.write("\n")
28 | end,
29 | text = function(text)
30 | print(string.format(" text: %q",text))
31 | end,
32 | closeElement = function(name,nsURI,nsPrefix)
33 | print(string.format("%s>",name))
34 | end,
35 | }
36 | }
37 |
38 | function SLAXML:parser(callbacks)
39 | return { _call=callbacks or self._call, parse=SLAXML.parse }
40 | end
41 |
42 | function SLAXML:parse(xml,options)
43 | if not options then options = { stripWhitespace=false } end
44 |
45 | -- Cache references for maximum speed
46 | local find, sub, gsub, char, push, pop, concat = string.find, string.sub, string.gsub, string.char, table.insert, table.remove, table.concat
47 | local first, last, match1, match2, match3, pos2, nsURI
48 | local unpack = unpack or table.unpack
49 | local pos = 1
50 | local state = "text"
51 | local textStart = 1
52 | local currentElement={}
53 | local currentAttributes={}
54 | local currentAttributeCt -- manually track length since the table is re-used
55 | local nsStack = {}
56 | local anyElement = false
57 |
58 | local utf8markers = { {0x7FF,192}, {0xFFFF,224}, {0x1FFFFF,240} }
59 | local function utf8(decimal) -- convert unicode code point to utf-8 encoded character string
60 | if decimal<128 then return char(decimal) end
61 | local charbytes = {}
62 | for bytes,vals in ipairs(utf8markers) do
63 | if decimal<=vals[1] then
64 | for b=bytes+1,2,-1 do
65 | local mod = decimal%64
66 | decimal = (decimal-mod)/64
67 | charbytes[b] = char(128+mod)
68 | end
69 | charbytes[1] = char(vals[2]+decimal)
70 | return concat(charbytes)
71 | end
72 | end
73 | end
74 | local entityMap = { ["lt"]="<", ["gt"]=">", ["amp"]="&", ["quot"]='"', ["apos"]="'" }
75 | local entitySwap = function(orig,n,s) return entityMap[s] or n=="#" and utf8(tonumber('0'..s)) or orig end
76 | local function unescape(str) return gsub( str, '(&(#?)([%d%a]+);)', entitySwap ) end
77 |
78 | local function finishText()
79 | if first>textStart and self._call.text then
80 | local text = sub(xml,textStart,first-1)
81 | if options.stripWhitespace then
82 | text = gsub(text,'^%s+','')
83 | text = gsub(text,'%s+$','')
84 | if #text==0 then text=nil end
85 | end
86 | if text then self._call.text(unescape(text)) end
87 | end
88 | end
89 |
90 | local function findPI()
91 | first, last, match1, match2 = find( xml, '^<%?([:%a_][:%w_.-]*) ?(.-)%?>', pos )
92 | if first then
93 | finishText()
94 | if self._call.pi then self._call.pi(match1,match2) end
95 | pos = last+1
96 | textStart = pos
97 | return true
98 | end
99 | end
100 |
101 | local function findComment()
102 | first, last, match1 = find( xml, '^', pos )
103 | if first then
104 | finishText()
105 | if self._call.comment then self._call.comment(match1) end
106 | pos = last+1
107 | textStart = pos
108 | return true
109 | end
110 | end
111 |
112 | local function nsForPrefix(prefix)
113 | if prefix=='xml' then return 'http://www.w3.org/XML/1998/namespace' end -- http://www.w3.org/TR/xml-names/#ns-decl
114 | for i=#nsStack,1,-1 do if nsStack[i][prefix] then return nsStack[i][prefix] end end
115 | error(("Cannot find namespace for prefix %s"):format(prefix))
116 | end
117 |
118 | local function startElement()
119 | anyElement = true
120 | first, last, match1 = find( xml, '^<([%a_][%w_.-]*)', pos )
121 | if first then
122 | currentElement[2] = nil -- reset the nsURI, since this table is re-used
123 | currentElement[3] = nil -- reset the nsPrefix, since this table is re-used
124 | finishText()
125 | pos = last+1
126 | first,last,match2 = find(xml, '^:([%a_][%w_.-]*)', pos )
127 | if first then
128 | currentElement[1] = match2
129 | currentElement[3] = match1 -- Save the prefix for later resolution
130 | match1 = match2
131 | pos = last+1
132 | else
133 | currentElement[1] = match1
134 | for i=#nsStack,1,-1 do if nsStack[i]['!'] then currentElement[2] = nsStack[i]['!']; break end end
135 | end
136 | currentAttributeCt = 0
137 | push(nsStack,{})
138 | return true
139 | end
140 | end
141 |
142 | local function findAttribute()
143 | first, last, match1 = find( xml, '^%s+([:%a_][:%w_.-]*)%s*=%s*', pos )
144 | if first then
145 | pos2 = last+1
146 | first, last, match2 = find( xml, '^"([^<"]*)"', pos2 ) -- FIXME: disallow non-entity ampersands
147 | if first then
148 | pos = last+1
149 | match2 = unescape(match2)
150 | else
151 | first, last, match2 = find( xml, "^'([^<']*)'", pos2 ) -- FIXME: disallow non-entity ampersands
152 | if first then
153 | pos = last+1
154 | match2 = unescape(match2)
155 | end
156 | end
157 | end
158 | if match1 and match2 then
159 | local currentAttribute = {match1,match2}
160 | local prefix,name = string.match(match1,'^([^:]+):([^:]+)$')
161 | if prefix then
162 | if prefix=='xmlns' then
163 | nsStack[#nsStack][name] = match2
164 | else
165 | currentAttribute[1] = name
166 | currentAttribute[4] = prefix
167 | end
168 | else
169 | if match1=='xmlns' then
170 | nsStack[#nsStack]['!'] = match2
171 | currentElement[2] = match2
172 | end
173 | end
174 | currentAttributeCt = currentAttributeCt + 1
175 | currentAttributes[currentAttributeCt] = currentAttribute
176 | return true
177 | end
178 | end
179 |
180 | local function findCDATA()
181 | first, last, match1 = find( xml, '^', pos )
182 | if first then
183 | finishText()
184 | if self._call.text then self._call.text(match1) end
185 | pos = last+1
186 | textStart = pos
187 | return true
188 | end
189 | end
190 |
191 | local function closeElement()
192 | first, last, match1 = find( xml, '^%s*(/?)>', pos )
193 | if first then
194 | state = "text"
195 | pos = last+1
196 | textStart = pos
197 |
198 | -- Resolve namespace prefixes AFTER all new/redefined prefixes have been parsed
199 | if currentElement[3] then currentElement[2] = nsForPrefix(currentElement[3]) end
200 | if self._call.startElement then self._call.startElement(unpack(currentElement)) end
201 | if self._call.attribute then
202 | for i=1,currentAttributeCt do
203 | if currentAttributes[i][4] then currentAttributes[i][3] = nsForPrefix(currentAttributes[i][4]) end
204 | self._call.attribute(unpack(currentAttributes[i]))
205 | end
206 | end
207 |
208 | if match1=="/" then
209 | pop(nsStack)
210 | if self._call.closeElement then self._call.closeElement(unpack(currentElement)) end
211 | end
212 | return true
213 | end
214 | end
215 |
216 | local function findElementClose()
217 | first, last, match1, match2 = find( xml, '^([%a_][%w_.-]*)%s*>', pos )
218 | if first then
219 | nsURI = nil
220 | for i=#nsStack,1,-1 do if nsStack[i]['!'] then nsURI = nsStack[i]['!']; break end end
221 | else
222 | first, last, match2, match1 = find( xml, '^([%a_][%w_.-]*):([%a_][%w_.-]*)%s*>', pos )
223 | if first then nsURI = nsForPrefix(match2) end
224 | end
225 | if first then
226 | finishText()
227 | if self._call.closeElement then self._call.closeElement(match1,nsURI) end
228 | pos = last+1
229 | textStart = pos
230 | pop(nsStack)
231 | return true
232 | end
233 | end
234 |
235 | while pos<#xml do
236 | if state=="text" then
237 | if not (findPI() or findComment() or findCDATA() or findElementClose()) then
238 | if startElement() then
239 | state = "attributes"
240 | else
241 | first, last = find( xml, '^[^<]+', pos )
242 | pos = (first and last or pos) + 1
243 | end
244 | end
245 | elseif state=="attributes" then
246 | if not findAttribute() then
247 | if not closeElement() then
248 | error("Was in an element and couldn't find attributes or the close.")
249 | end
250 | end
251 | end
252 | end
253 |
254 | if not anyElement then error("Parsing did not discover any elements") end
255 | if #nsStack > 0 then error("Parsing ended with unclosed elements") end
256 | end
257 |
258 | function SLAXML:dom(xml,opts)
259 | if not opts then opts={} end
260 | local rich = not opts.simple
261 | local push, pop = table.insert, table.remove
262 | local stack = {}
263 | local doc = { type="document", name="#doc", kids={} }
264 | local current = doc
265 | local builder = SLAXML:parser{
266 | startElement = function(name,nsURI)
267 | local el = { type="element", name=name, kids={}, el=rich and {} or nil, attr={}, nsURI=nsURI, parent=rich and current or nil }
268 | if current==doc then
269 | if doc.root then error(("Encountered element '%s' when the document already has a root '%s' element"):format(name,doc.root.name)) end
270 | doc.root = el
271 | end
272 | push(current.kids,el)
273 | if current.el then push(current.el,el) end
274 | current = el
275 | push(stack,el)
276 | end,
277 | attribute = function(name,value,nsURI)
278 | if not current or current.type~="element" then error(("Encountered an attribute %s=%s but I wasn't inside an element"):format(name,value)) end
279 | local attr = {type='attribute',name=name,nsURI=nsURI,value=value,parent=rich and current or nil}
280 | if rich then current.attr[name] = value end
281 | push(current.attr,attr)
282 | end,
283 | closeElement = function(name)
284 | if current.name~=name or current.type~="element" then error(("Received a close element notification for '%s' but was inside a '%s' %s"):format(name,current.name,current.type)) end
285 | pop(stack)
286 | current = stack[#stack]
287 | end,
288 | text = function(value)
289 | if current.type~='document' then
290 | if current.type~="element" then error(("Received a text notification '%s' but was inside a %s"):format(value,current.type)) end
291 | push(current.kids,{type='text',name='#text',value=value,parent=rich and current or nil})
292 | end
293 | end,
294 | comment = function(value)
295 | push(current.kids,{type='comment',name='#comment',value=value,parent=rich and current or nil})
296 | end,
297 | pi = function(name,value)
298 | push(current.kids,{type='pi',name=name,value=value,parent=rich and current or nil})
299 | end
300 | }
301 | builder:parse(xml,opts)
302 | return doc
303 | end
304 | return SLAXML
--------------------------------------------------------------------------------
/object-teleports/__resource.lua:
--------------------------------------------------------------------------------
1 | client_script 'client.lua'
2 | server_script 'server.lua'
3 |
4 | dependency 'object-loader'
--------------------------------------------------------------------------------
/object-teleports/client.lua:
--------------------------------------------------------------------------------
1 | local spawns = {}
2 |
3 | local function chatMessage(msg)
4 | TriggerEvent('chatMessage', '', {0, 0, 0}, msg)
5 | end
6 |
7 | local function chatMessageYou(msg)
8 | TriggerEvent('chatMessage', 'You', {0, 250, 120}, msg)
9 | end
10 |
11 | local cancelFlag = false
12 |
13 | RegisterNetEvent('objectTeleports:handleTeleportCommand')
14 |
15 | AddEventHandler('objectTeleports:handleTeleportCommand', function(command)
16 | if #spawns == 0 then
17 | spawns = exports['object-loader']:getSpawns()
18 | end
19 |
20 | chatMessageYou('/mtp ' .. command)
21 |
22 | if command:len() == 0 then
23 | cancelFlag = false
24 |
25 | Citizen.CreateThread(function()
26 | if #spawns == 0 then
27 | chatMessage('^1No Object Loader Teleports are ^6loaded :(')
28 | return
29 | end
30 |
31 | chatMessage('^1Object Loader Teleports')
32 |
33 | for i, spawn in ipairs(spawns) do
34 | Citizen.Wait(750)
35 |
36 | if cancelFlag then
37 | cancelFlag = false
38 | break
39 | end
40 |
41 | chatMessage('^3/mtp ' .. i .. ': ^2' .. spawn.name)
42 | end
43 | end)
44 | else
45 | local idx = tonumber(command)
46 |
47 | if spawns[idx] then
48 | SetEntityCoords(GetPlayerPed(-1), spawns[idx].spawnPos[1], spawns[idx].spawnPos[2], spawns[idx].spawnPos[3])
49 | SetEntityHeading(GetPlayerPed(-1), spawns[idx].heading)
50 |
51 | chatMessage('Teleported to ^3' .. spawns[idx].name)
52 |
53 | cancelFlag = true
54 | else
55 | chatMessage('Invalid teleport: ' .. tostring(idx))
56 | end
57 | end
58 | end)
59 |
60 | AddEventHandler('objectLoader:onSpawnLoaded', function(data)
61 | if #spawns == 0 then
62 | spawns = exports['object-loader']:getSpawns()
63 | end
64 |
65 | table.insert(spawns, data)
66 | end)
--------------------------------------------------------------------------------
/object-teleports/server.lua:
--------------------------------------------------------------------------------
1 | AddEventHandler('chatMessage', function(source, name, message)
2 | if message:sub(1, 4) == '/mtp' then
3 | TriggerClientEvent('objectTeleports:handleTeleportCommand', source, message:sub(5))
4 | CancelEvent()
5 | end
6 | end)
--------------------------------------------------------------------------------