-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.lua
More file actions
355 lines (328 loc) · 13.8 KB
/
Copy pathserver.lua
File metadata and controls
355 lines (328 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
local transparentPlayers = {}
local cfg = Config
-- Framework detection & wrapper
local Framework = { name = nil, core = nil }
local function DetectFramework()
if cfg.Framework and cfg.Framework.AutoDetect then
local preferred = (cfg.Framework.Preferred or 'esx'):lower()
local order = { preferred, preferred == 'esx' and 'qb' or 'esx' }
for _, fw in ipairs(order) do
if fw == 'esx' then
local ok, obj = pcall(function() return exports['es_extended']:getSharedObject() end)
if ok and obj then Framework.name = 'esx'; Framework.core = obj; return end
-- Legacy event fallback
local legacy
TriggerEvent('esx:getSharedObject', function(esx) legacy = esx end)
if legacy then Framework.name = 'esx'; Framework.core = legacy; return end
elseif fw == 'qb' then
if GetResourceState('qb-core') == 'started' then
local ok, obj = pcall(function() return exports['qb-core']:GetCoreObject() end)
if ok and obj then Framework.name = 'qb'; Framework.core = obj; return end
end
end
end
else
local preferred = (cfg.Framework.Preferred or 'esx'):lower()
if preferred == 'esx' then
local ok, obj = pcall(function() return exports['es_extended']:getSharedObject() end)
if ok and obj then Framework.name = 'esx'; Framework.core = obj else
local legacy
TriggerEvent('esx:getSharedObject', function(esx) legacy = esx end)
if legacy then Framework.name = 'esx'; Framework.core = legacy end
end
elseif preferred == 'qb' then
if GetResourceState('qb-core') == 'started' then
local ok, obj = pcall(function() return exports['qb-core']:GetCoreObject() end)
if ok and obj then Framework.name = 'qb'; Framework.core = obj end
end
end
end
end
DetectFramework()
-- Auto notification event switch for QBCore
if Framework.name == 'qb' and cfg.Notification.Enabled and cfg.Notification.Event == 'notify' then
-- Many QBCore scripts use client side event 'QBCore:Notify'
cfg.Notification.Event = 'QBCore:Notify'
end
local function GetPlayerObject(src)
if Framework.name == 'esx' then
return Framework.core.GetPlayerFromId and Framework.core.GetPlayerFromId(src) or Framework.core.GetPlayerFromId(src)
elseif Framework.name == 'qb' then
if Framework.core.Functions and Framework.core.Functions.GetPlayer then
return Framework.core.Functions.GetPlayer(src)
end
end
end
local function GetPlayerGroup(src)
if Framework.name == 'esx' then
local xPlayer = GetPlayerObject(src)
return xPlayer and xPlayer.getGroup and xPlayer.getGroup() or 'user'
elseif Framework.name == 'qb' then
local p = GetPlayerObject(src)
if p and p.PlayerData and p.PlayerData.job and p.PlayerData.job.name then
return p.PlayerData.job.name
end
return 'citizen'
end
return 'user'
end
-- Routing bucket support detection
local routingBucketSupported = (SetPlayerRoutingBucket ~= nil) and (GetPlayerRoutingBucket ~= nil)
local function TeleportBucket(src, bucket)
if not routingBucketSupported then
return false, 'unsupported'
end
local ok, err = pcall(function()
SetPlayerRoutingBucket(src, bucket)
end)
if not ok then
return false, err or 'failed'
end
return true
end
-- DB Helper
local DB = {}
if cfg.Database and cfg.Database.Enabled then
local adapter = cfg.Database.Adapter
if adapter == 'oxmysql' then
DB.insert = function(query, params, cb)
exports.oxmysql:insert(query, params, cb)
end
DB.execute = function(query, params, cb)
exports.oxmysql:execute(query, params, cb)
end
elseif adapter == 'mysql-async' then
DB.insert = function(query, params, cb)
MySQL.Async.insert(query, params, cb)
end
DB.execute = function(query, params, cb)
MySQL.Async.execute(query, params, cb)
end
else
print('[AdminMode] Unbekannter DB Adapter: '..tostring(adapter))
cfg.Database.Enabled = false
end
end
local function GetIdentifier(src)
for _, id in ipairs(GetPlayerIdentifiers(src)) do
if id:sub(1,5) == 'steam' or id:sub(1,6) == 'license' then
return id
end
end
return GetPlayerIdentifier(src, 0)
end
local function SendWebhook(payload)
if not (cfg.Discord and cfg.Discord.Enabled and cfg.Discord.Url and cfg.Discord.Url ~= '') then return end
PerformHttpRequest(cfg.Discord.Url, function() end, 'POST', json.encode(payload), {
['Content-Type'] = 'application/json'
})
end
local function LogAction(src, action, details)
local identifier = src and GetIdentifier(src) or 'server'
local name = (src and GetPlayerName(src)) or 'Server'
-- DB Logging
if (cfg.Database and cfg.Database.Enabled) then
local tableName = cfg.Database.Table
local query = ('INSERT INTO `%s` (identifier, name, action, details) VALUES (?, ?, ?, ?)'):format(tableName)
DB.insert(query, { identifier, name or 'Unknown', action, details }, function(id)
if cfg.Debug.PrintAdminToggle then
print(('[AdminMode][DB] Logged %s (row=%s)'):format(action, tostring(id)))
end
end)
end
-- Discord Webhook Mirror
if cfg.Discord and cfg.Discord.Enabled then
local formatFn = cfg.Discord.Format
local content = formatFn and formatFn({
action = action,
name = name,
identifier = identifier,
details = details
}) or (action .. ' - ' .. (details or ''))
local payload = {
username = cfg.Discord.Username or 'AdminMode Logger',
avatar_url = cfg.Discord.AvatarUrl or nil,
embeds = {
{
description = content,
color = 16734296,
footer = { text = 'AdminMode' },
timestamp = os.date('!%Y-%m-%dT%H:%M:%S.000Z')
}
}
}
SendWebhook(payload)
end
end
-- Auto create table (if enabled)
CreateThread(function()
if cfg.Database and cfg.Database.Enabled and cfg.Database.AutoCreate then
local tableName = cfg.Database.Table
local schema = cfg.Database.Schema
if schema and DB.execute then
local createSql = ('CREATE TABLE IF NOT EXISTS `%s` (%s)'):format(tableName, schema)
DB.execute(createSql, {}, function()
print('[AdminMode] DB Tabelle geprueft: '..tableName)
end)
end
end
end)
-- Admin toggle command
RegisterCommand(cfg.AdminMode.Command, function(source)
local src = source
-- Optional permission check: only allow listed groups if provided in config
if cfg.AdminMode.AllowedGroups and #cfg.AdminMode.AllowedGroups > 0 then
local group = GetPlayerGroup(src)
local ok = false
for _, g in ipairs(cfg.AdminMode.AllowedGroups) do if g == group then ok = true break end end
if not ok then
if cfg.Notification.Enabled then
TriggerClientEvent(cfg.Notification.Event, src, 3, 'AdminMode', 'No permission for /'..cfg.AdminMode.Command)
end
return
end
end
if transparentPlayers[src] then
transparentPlayers[src] = nil
TriggerClientEvent(cfg.Events.SetTransparency, -1, src, cfg.AdminMode.NormalAlpha)
TriggerClientEvent(cfg.Events.SetGodMode, src, false)
if cfg.Debug.PrintAdminToggle then print(('[AdminMode] %s'):format(L('admin_disabled'))) end
if cfg.LogFlags.AdminToggle then LogAction(src, 'admin_off', L('admin_disabled')) end
else
transparentPlayers[src] = true
-- Broadcast to everyone else including the admin (if you want admin to see self normal, change target list logic)
TriggerClientEvent(cfg.Events.SetTransparency, -1, src, cfg.AdminMode.TransparentAlpha)
TriggerClientEvent(cfg.Events.SetGodMode, src, true)
if cfg.Debug.PrintAdminToggle then print(('[AdminMode] %s'):format(L('admin_enabled'))) end
if cfg.LogFlags.AdminToggle then LogAction(src, 'admin_on', L('admin_enabled')) end
end
end, true)
-- New: allow late joiners to request current transparency states
RegisterNetEvent('adminmode:requestSync')
AddEventHandler('adminmode:requestSync', function()
local src = source
for playerId, _ in pairs(transparentPlayers) do
TriggerClientEvent(cfg.Events.SetTransparency, src, playerId, cfg.AdminMode.TransparentAlpha)
end
end)
RegisterNetEvent(cfg.Events.CheckTransparency)
AddEventHandler(cfg.Events.CheckTransparency, function(playerId)
local src = source
if transparentPlayers[playerId] then
TriggerClientEvent(cfg.Events.SetTransparency, src, playerId, cfg.AdminMode.TransparentAlpha)
else
TriggerClientEvent(cfg.Events.SetTransparency, src, playerId, cfg.AdminMode.NormalAlpha)
end
end)
AddEventHandler('playerDropped', function()
local src = source
if transparentPlayers[src] then
transparentPlayers[src] = nil
TriggerClientEvent(cfg.Events.SetTransparency, -1, src, cfg.AdminMode.NormalAlpha)
end
end)
RegisterNetEvent(cfg.Events.GetStaticId)
AddEventHandler(cfg.Events.GetStaticId, function(targetPlayerId)
if not cfg.StaticId.Enabled then return end
local src = source
if tonumber(targetPlayerId) then
local staticId
local success, result = pcall(function()
return exports[cfg.StaticId.ExportResource][cfg.StaticId.ExportFunction](targetPlayerId)
end)
if success then
staticId = result
if cfg.Debug.PrintStaticIdFetch then
print(('[StaticID] %s -> %s'):format(targetPlayerId, tostring(staticId)))
end
else
if cfg.Debug.PrintStaticIdFetch then
print('[StaticID] Fehler beim Abruf: '..tostring(result))
end
end
TriggerClientEvent(cfg.Events.ReceiveStaticId, src, targetPlayerId, staticId)
end
end)
-- Dimension Command
RegisterCommand(cfg.Dimension.Command, function(source, args)
local targetId = tonumber(args[1])
local dimension = tonumber(args[2])
local xPlayer = GetPlayerObject(source)
if not xPlayer then return end
local group = GetPlayerGroup(source)
local allowed = false
for _, g in ipairs(cfg.Dimension.AllowedGroups) do
if g == group then allowed = true break end
end
if not allowed then
if cfg.Notification.Enabled then TriggerClientEvent(cfg.Notification.Event, source, 1, L('dimension_header'), L('dimension_no_permission')) end
return
end
if not targetId or not dimension then
if cfg.Notification.Enabled then TriggerClientEvent(cfg.Notification.Event, source, 1, L('dimension_header'), L('dimension_usage', cfg.Dimension.Command)) end
return
end
if targetId == -1 then
if cfg.Notification.Enabled then TriggerClientEvent(cfg.Notification.Event, source, 1, L('dimension_header'), L('dimension_self_error')) end
return
end
local xTarget = GetPlayerObject(targetId)
if not xTarget then
if cfg.Notification.Enabled then TriggerClientEvent(cfg.Notification.Event, source, 1, L('dimension_header'), L('player_not_found')) end
return
end
local ok, err = TeleportBucket(targetId, dimension)
if not ok then
if cfg.Notification.Enabled then
if err == 'unsupported' then
TriggerClientEvent(cfg.Notification.Event, source, 3, L('dimension_header'), L('routingbucket_not_supported'))
else
TriggerClientEvent(cfg.Notification.Event, source, 3, L('dimension_header'), L('routingbucket_failed', tostring(err)))
end
end
return
end
local targetSrc = xTarget.source or targetId
if cfg.Notification.Enabled then
TriggerClientEvent(cfg.Notification.Event, source, 1, L('dimension_header'), L('dimension_success_admin', targetSrc))
TriggerClientEvent(cfg.Notification.Event, targetSrc, 1, L('dimension_header'), L('dimension_success_target', GetPlayerRoutingBucket(targetSrc)))
end
if cfg.LogFlags.Dimension then LogAction(source, 'set_dimension', ('Player %s -> Bucket %s'):format(targetSrc, tostring(dimension))) end
end)
-- Godmode specific logs (separate from toggle if granular)
RegisterNetEvent('adminmode:godmodeChange')
AddEventHandler('adminmode:godmodeChange', function(enabled)
local src = source
if cfg.LogFlags.Godmode then
LogAction(src, enabled and 'godmode_on' or 'godmode_off', 'Godmode via client event')
end
end)
-- Teleport logging (client sends coordinates)
RegisterNetEvent('adminmode:teleportLog')
AddEventHandler('adminmode:teleportLog', function(x,y,z)
local src = source
if cfg.LogFlags.Teleport then
LogAction(src, 'teleport', string.format('Teleport to: %.2f %.2f %.2f', x, y, z))
end
end)
-- Player join/leave logging
AddEventHandler('playerConnecting', function(name, setKick, def)
if cfg.LogFlags.PlayerJoin then
local src = source
LogAction(src, 'player_join', 'connect')
end
end)
-- Consolidated playerDropped handler (above already clears transparency) + logging
AddEventHandler('playerDropped', function(reason)
local src = source
if cfg.LogFlags.PlayerLeave then
LogAction(src, 'player_leave', reason or 'disconnect')
end
end)
-- Exports for external resources
exports('LogAction', function(src, action, details)
LogAction(src, action, details)
end)
exports('GetAdminState', function(playerId)
return transparentPlayers[playerId] == true
end)