first commit
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
local pendingCallbacks = {}
|
||||
local callbackCounter = 0
|
||||
local resourceName = GetCurrentResourceName()
|
||||
local responseEvent = ('devx_core:client:callback:%s'):format(resourceName)
|
||||
|
||||
function DevXTriggerCallback(name, payload, callback)
|
||||
assert(type(name) == 'string' and name:match('^[%w_%-]+:[%w_%-]+$'),
|
||||
'Callbackname ist ungültig.')
|
||||
assert(type(callback) == 'function', 'Clientcallback muss eine Funktion sein.')
|
||||
|
||||
callbackCounter = callbackCounter + 1
|
||||
local requestId = ('%s:%s:%s'):format(resourceName, GetGameTimer(), callbackCounter)
|
||||
|
||||
pendingCallbacks[requestId] = callback
|
||||
|
||||
TriggerServerEvent(
|
||||
('devx_core:server:callback:%s:%s'):format(resourceName, name),
|
||||
requestId,
|
||||
payload or {}
|
||||
)
|
||||
|
||||
SetTimeout(22000, function()
|
||||
local pending = pendingCallbacks[requestId]
|
||||
if not pending then return end
|
||||
|
||||
pendingCallbacks[requestId] = nil
|
||||
pending({
|
||||
ok = false,
|
||||
error = ('Keine Antwort auf %s. Prüfe die txAdmin- und F8-Konsole.'):format(name)
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
RegisterNetEvent(responseEvent)
|
||||
AddEventHandler(responseEvent, function(requestId, result)
|
||||
local callback = pendingCallbacks[requestId]
|
||||
if not callback then return end
|
||||
|
||||
pendingCallbacks[requestId] = nil
|
||||
callback(result or { ok = false, error = 'Leere Serverantwort.' })
|
||||
end)
|
||||
@@ -0,0 +1,63 @@
|
||||
local PlayerData = nil
|
||||
|
||||
local function notify(message)
|
||||
BeginTextCommandThefeedPost('STRING')
|
||||
AddTextComponentSubstringPlayerName(message)
|
||||
EndTextCommandThefeedPostTicker(false, false)
|
||||
end
|
||||
|
||||
exports('GetPlayerData', function()
|
||||
return PlayerData
|
||||
end)
|
||||
|
||||
exports('IsPlayerLoaded', function()
|
||||
return PlayerData ~= nil
|
||||
end)
|
||||
|
||||
exports('Notify', notify)
|
||||
|
||||
RegisterNetEvent('devx_core:client:playerData', function(data)
|
||||
PlayerData = data
|
||||
LocalPlayer.state:set('devxCharacterId', data.characterId, true)
|
||||
TriggerEvent('devx_core:client:playerLoaded', data)
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(DevXConfig.PositionSaveIntervalMs)
|
||||
if PlayerData then
|
||||
local ped = PlayerPedId()
|
||||
if DoesEntityExist(ped) and not IsEntityDead(ped) then
|
||||
local coords = GetEntityCoords(ped)
|
||||
TriggerServerEvent('devx_core:server:updatePosition', {
|
||||
x = coords.x,
|
||||
y = coords.y,
|
||||
z = coords.z,
|
||||
heading = GetEntityHeading(ped)
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterCommand('coords', function()
|
||||
local ped = PlayerPedId()
|
||||
local coords = GetEntityCoords(ped)
|
||||
local text = ('vector4(%.2f, %.2f, %.2f, %.2f)'):format(
|
||||
coords.x, coords.y, coords.z, GetEntityHeading(ped)
|
||||
)
|
||||
print(text)
|
||||
notify(text)
|
||||
end, false)
|
||||
|
||||
RegisterCommand('unstuck', function()
|
||||
if not PlayerData then return end
|
||||
local spawn = DevXConfig.StreetSpawn
|
||||
SetEntityCoords(PlayerPedId(), spawn.x, spawn.y, spawn.z, false, false, false, false)
|
||||
SetEntityHeading(PlayerPedId(), spawn.heading)
|
||||
notify('Du wurdest zum Straßenstart teleportiert.')
|
||||
end, false)
|
||||
|
||||
RegisterNetEvent('devx_core:client:returnedToWorld', function()
|
||||
notify('Du wurdest in die öffentliche Welt zurückgesetzt.')
|
||||
end)
|
||||
@@ -0,0 +1,23 @@
|
||||
fx_version 'cerulean'
|
||||
game 'gta5'
|
||||
|
||||
author 'Colin-Joel / DevX Studios'
|
||||
description 'DevX RP core sessions, callbacks and player state'
|
||||
version '0.4.3'
|
||||
|
||||
lua54 'yes'
|
||||
|
||||
shared_script 'shared/config.lua'
|
||||
|
||||
-- This helper is loaded by dependent resources using
|
||||
-- @devx_core/client/callbacks.lua. It must be part of the client packfile.
|
||||
files {
|
||||
'client/callbacks.lua'
|
||||
}
|
||||
|
||||
client_script 'client/main.lua'
|
||||
|
||||
server_scripts {
|
||||
'@oxmysql/lib/MySQL.lua',
|
||||
'server/main.lua'
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
local registeredCallbacks = {}
|
||||
local pendingExecutions = {}
|
||||
local resourceName = GetCurrentResourceName()
|
||||
|
||||
print(('[%s] DevX RPC v0.4.1 aktiv.'):format(resourceName))
|
||||
|
||||
local function callbackEventName(name)
|
||||
return ('devx_core:server:callback:%s:%s'):format(resourceName, name)
|
||||
end
|
||||
|
||||
local function responseEventName()
|
||||
return ('devx_core:client:callback:%s'):format(resourceName)
|
||||
end
|
||||
|
||||
local function sendResult(requestSource, requestId, result)
|
||||
TriggerClientEvent(responseEventName(), requestSource, requestId, result)
|
||||
end
|
||||
|
||||
function DevXRegisterCallback(name, handler)
|
||||
assert(type(name) == 'string' and name:match('^[%w_%-]+:[%w_%-]+$'),
|
||||
'Callbackname ist ungültig.')
|
||||
assert(type(handler) == 'function', 'Callbackhandler muss eine Funktion sein.')
|
||||
assert(not registeredCallbacks[name], ('Callback %s wurde doppelt registriert.'):format(name))
|
||||
|
||||
registeredCallbacks[name] = true
|
||||
local eventName = callbackEventName(name)
|
||||
|
||||
RegisterNetEvent(eventName)
|
||||
AddEventHandler(eventName, function(requestId, payload)
|
||||
local requestSource = source
|
||||
|
||||
if type(requestId) ~= 'string' or #requestId > 160 or type(payload) ~= 'table' then
|
||||
sendResult(requestSource, tostring(requestId or ''), {
|
||||
ok = false,
|
||||
error = 'Ungültige Serveranfrage.'
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local executionKey = ('%s:%s'):format(requestSource, requestId)
|
||||
if pendingExecutions[executionKey] then
|
||||
sendResult(requestSource, requestId, {
|
||||
ok = false,
|
||||
error = 'Diese Serveranfrage wird bereits verarbeitet.'
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
pendingExecutions[executionKey] = true
|
||||
local completed = false
|
||||
|
||||
local function finish(result)
|
||||
if completed then return end
|
||||
completed = true
|
||||
pendingExecutions[executionKey] = nil
|
||||
|
||||
if result == nil then result = { ok = true } end
|
||||
sendResult(requestSource, requestId, result)
|
||||
end
|
||||
|
||||
print(('[%s] RPC empfangen: %s von Spieler %s.'):format(resourceName, name, requestSource))
|
||||
|
||||
SetTimeout(20000, function()
|
||||
if completed then return end
|
||||
print(('[%s] RPC %s für Spieler %s hat nach 20 Sekunden nicht geantwortet.'):format(
|
||||
resourceName, name, requestSource
|
||||
))
|
||||
finish({
|
||||
ok = false,
|
||||
error = ('Serververarbeitung für %s abgelaufen. Prüfe die txAdmin-Konsole.'):format(name)
|
||||
})
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
local result = handler(requestSource, payload)
|
||||
finish(result)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,381 @@
|
||||
local Players = {}
|
||||
local positionRateLimit = {}
|
||||
|
||||
local function getLicense(source)
|
||||
for _, identifier in ipairs(GetPlayerIdentifiers(source)) do
|
||||
if identifier:sub(1, 8) == 'license:' then
|
||||
return identifier
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function sanitizeInteger(value, maximum)
|
||||
local number = tonumber(value)
|
||||
if not number then return nil end
|
||||
number = math.floor(number)
|
||||
if number < 0 then return nil end
|
||||
if maximum and number > maximum then return nil end
|
||||
return number
|
||||
end
|
||||
|
||||
local function normalizeDateOfBirth(value)
|
||||
if type(value) == 'number' then
|
||||
local seconds = value > 100000000000 and math.floor(value / 1000) or math.floor(value)
|
||||
return os.date('!%Y-%m-%d', seconds)
|
||||
end
|
||||
|
||||
local text = tostring(value or '')
|
||||
local numeric = tonumber(text)
|
||||
if numeric then
|
||||
local seconds = numeric > 100000000000 and math.floor(numeric / 1000) or math.floor(numeric)
|
||||
return os.date('!%Y-%m-%d', seconds)
|
||||
end
|
||||
|
||||
local date = text:match('^(%d%d%d%d%-%d%d%-%d%d)')
|
||||
return date or text
|
||||
end
|
||||
|
||||
local function phoneNumberFor(characterId)
|
||||
return ('%s-%04d'):format(DevXConfig.PhonePrefix or '555', tonumber(characterId))
|
||||
end
|
||||
|
||||
local function ensurePhoneNumber(character)
|
||||
if character.phone_number and character.phone_number ~= '' then
|
||||
return character.phone_number
|
||||
end
|
||||
|
||||
local phoneNumber = phoneNumberFor(character.id)
|
||||
MySQL.update.await(
|
||||
'UPDATE devx_characters SET phone_number = ? WHERE id = ? AND (phone_number IS NULL OR phone_number = "")',
|
||||
{ phoneNumber, character.id }
|
||||
)
|
||||
return phoneNumber
|
||||
end
|
||||
|
||||
local function emitPlayerData(source)
|
||||
local player = Players[source]
|
||||
if not player then return end
|
||||
TriggerClientEvent('devx_core:client:playerData', source, player)
|
||||
end
|
||||
|
||||
local function loadCharacter(source, characterId)
|
||||
local license = getLicense(source)
|
||||
characterId = sanitizeInteger(characterId)
|
||||
if not license or not characterId then
|
||||
return nil, 'Ungültige Sitzung.'
|
||||
end
|
||||
|
||||
local character = MySQL.single.await([[
|
||||
SELECT id, slot, first_name, last_name, date_of_birth, sex, model,
|
||||
phone_number, cash, position, metadata
|
||||
FROM devx_characters
|
||||
WHERE id = ? AND owner_license = ? AND deleted_at IS NULL
|
||||
LIMIT 1
|
||||
]], { characterId, license })
|
||||
|
||||
if not character then
|
||||
return nil, 'Charakter wurde nicht gefunden.'
|
||||
end
|
||||
|
||||
local account = MySQL.single.await([[
|
||||
SELECT id, account_number, account_name, account_type, balance
|
||||
FROM devx_bank_accounts
|
||||
WHERE character_id = ? AND account_type = 'personal'
|
||||
LIMIT 1
|
||||
]], { character.id })
|
||||
|
||||
local metadata = {}
|
||||
if character.metadata and character.metadata ~= '' then
|
||||
local ok, decoded = pcall(json.decode, character.metadata)
|
||||
if ok and type(decoded) == 'table' then metadata = decoded end
|
||||
end
|
||||
|
||||
local position = nil
|
||||
if character.position and character.position ~= '' then
|
||||
local ok, decoded = pcall(json.decode, character.position)
|
||||
if ok and type(decoded) == 'table' then position = decoded end
|
||||
end
|
||||
|
||||
local phoneNumber = ensurePhoneNumber(character)
|
||||
|
||||
Players[source] = {
|
||||
source = source,
|
||||
license = license,
|
||||
characterId = tonumber(character.id),
|
||||
slot = tonumber(character.slot),
|
||||
firstName = character.first_name,
|
||||
lastName = character.last_name,
|
||||
fullName = ('%s %s'):format(character.first_name, character.last_name),
|
||||
dateOfBirth = normalizeDateOfBirth(character.date_of_birth),
|
||||
sex = character.sex,
|
||||
model = character.model,
|
||||
phoneNumber = phoneNumber,
|
||||
cash = tonumber(character.cash) or 0,
|
||||
bank = account and tonumber(account.balance) or 0,
|
||||
accountId = account and tonumber(account.id) or nil,
|
||||
accountNumber = account and account.account_number or nil,
|
||||
position = position,
|
||||
metadata = metadata,
|
||||
rpXp = tonumber(metadata.rpXp) or 0,
|
||||
introCompleted = metadata.introCompleted == true
|
||||
}
|
||||
|
||||
-- Jeder frisch ausgewählte Charakter startet sicher in der öffentlichen Welt.
|
||||
-- So bleiben keine alten Apartment-/Missions-Buckets nach einem Reconnect hängen.
|
||||
SetPlayerRoutingBucket(source, 0)
|
||||
Player(source).state:set('devxCharacterId', Players[source].characterId, true)
|
||||
Player(source).state:set('devxCharacterName', Players[source].fullName, true)
|
||||
Player(source).state:set('devxPhoneNumber', Players[source].phoneNumber, true)
|
||||
|
||||
emitPlayerData(source)
|
||||
return Players[source]
|
||||
end
|
||||
|
||||
local function savePosition(source)
|
||||
local player = Players[source]
|
||||
if not player or not player.position then return end
|
||||
|
||||
MySQL.update.await(
|
||||
'UPDATE devx_characters SET position = ? WHERE id = ?',
|
||||
{ json.encode(player.position), player.characterId }
|
||||
)
|
||||
end
|
||||
|
||||
local function persistMetadata(source)
|
||||
local player = Players[source]
|
||||
if not player then return false end
|
||||
|
||||
local changed = MySQL.update.await(
|
||||
'UPDATE devx_characters SET metadata = ? WHERE id = ?',
|
||||
{ json.encode(player.metadata or {}), player.characterId }
|
||||
)
|
||||
return changed ~= nil
|
||||
end
|
||||
|
||||
exports('GetLicense', getLicense)
|
||||
exports('GetPlayer', function(source)
|
||||
return Players[tonumber(source)]
|
||||
end)
|
||||
|
||||
exports('GetActivePlayers', function()
|
||||
return Players
|
||||
end)
|
||||
|
||||
exports('ActivateCharacter', function(source, characterId)
|
||||
return loadCharacter(tonumber(source), characterId)
|
||||
end)
|
||||
|
||||
exports('SetMetadataValue', function(source, key, value)
|
||||
source = tonumber(source)
|
||||
local player = Players[source]
|
||||
|
||||
if not player or type(key) ~= 'string' or #key < 1 or #key > 64 then
|
||||
return false
|
||||
end
|
||||
|
||||
player.metadata = player.metadata or {}
|
||||
player.metadata[key] = value
|
||||
if not persistMetadata(source) then return false end
|
||||
|
||||
if key == 'introCompleted' then player.introCompleted = value == true end
|
||||
if key == 'rpXp' then player.rpXp = tonumber(value) or 0 end
|
||||
|
||||
emitPlayerData(source)
|
||||
return true
|
||||
end)
|
||||
|
||||
exports('AddMetadataNumber', function(source, key, amount)
|
||||
source = tonumber(source)
|
||||
amount = tonumber(amount)
|
||||
local player = Players[source]
|
||||
if not player or not amount or type(key) ~= 'string' then return false end
|
||||
|
||||
player.metadata = player.metadata or {}
|
||||
local newValue = math.floor((tonumber(player.metadata[key]) or 0) + amount)
|
||||
player.metadata[key] = newValue
|
||||
if not persistMetadata(source) then return false end
|
||||
|
||||
if key == 'rpXp' then player.rpXp = newValue end
|
||||
emitPlayerData(source)
|
||||
return true, newValue
|
||||
end)
|
||||
|
||||
exports('SetBankCache', function(source, balance)
|
||||
local player = Players[tonumber(source)]
|
||||
balance = sanitizeInteger(balance)
|
||||
if not player or balance == nil then return false end
|
||||
player.bank = balance
|
||||
emitPlayerData(tonumber(source))
|
||||
return true
|
||||
end)
|
||||
|
||||
exports('SetCash', function(source, amount, reason)
|
||||
source = tonumber(source)
|
||||
amount = sanitizeInteger(amount, DevXConfig.MaxMoneyOperation)
|
||||
local player = Players[source]
|
||||
if not player or amount == nil then return false end
|
||||
|
||||
local changed = MySQL.update.await(
|
||||
'UPDATE devx_characters SET cash = ? WHERE id = ?',
|
||||
{ amount, player.characterId }
|
||||
)
|
||||
if not changed or changed < 1 then return false end
|
||||
|
||||
player.cash = amount
|
||||
emitPlayerData(source)
|
||||
TriggerEvent('devx_core:server:moneyAudit', source, 'cash_set', amount, reason or 'Unbekannt')
|
||||
return true
|
||||
end)
|
||||
|
||||
exports('AddCash', function(source, amount, reason)
|
||||
source = tonumber(source)
|
||||
amount = sanitizeInteger(amount, DevXConfig.MaxMoneyOperation)
|
||||
local player = Players[source]
|
||||
if not player or not amount or amount == 0 then return false end
|
||||
|
||||
local newCash = player.cash + amount
|
||||
local changed = MySQL.update.await(
|
||||
'UPDATE devx_characters SET cash = ? WHERE id = ?',
|
||||
{ newCash, player.characterId }
|
||||
)
|
||||
|
||||
if not changed or changed < 1 then return false end
|
||||
player.cash = newCash
|
||||
emitPlayerData(source)
|
||||
TriggerEvent('devx_core:server:moneyAudit', source, 'cash_credit', amount, reason or 'Unbekannt')
|
||||
return true, newCash
|
||||
end)
|
||||
|
||||
exports('RemoveCash', function(source, amount, reason)
|
||||
source = tonumber(source)
|
||||
amount = sanitizeInteger(amount, DevXConfig.MaxMoneyOperation)
|
||||
local player = Players[source]
|
||||
if not player or not amount or amount == 0 or player.cash < amount then return false end
|
||||
|
||||
local newCash = player.cash - amount
|
||||
local changed = MySQL.update.await(
|
||||
'UPDATE devx_characters SET cash = ? WHERE id = ? AND cash >= ?',
|
||||
{ newCash, player.characterId, amount }
|
||||
)
|
||||
|
||||
if not changed or changed < 1 then return false end
|
||||
player.cash = newCash
|
||||
emitPlayerData(source)
|
||||
TriggerEvent('devx_core:server:moneyAudit', source, 'cash_debit', amount, reason or 'Unbekannt')
|
||||
return true, newCash
|
||||
end)
|
||||
|
||||
RegisterNetEvent('devx_core:server:updatePosition', function(position)
|
||||
local source = source
|
||||
local player = Players[source]
|
||||
if not player or type(position) ~= 'table' then return end
|
||||
|
||||
local now = os.time()
|
||||
if positionRateLimit[source] and now - positionRateLimit[source] < 10 then return end
|
||||
positionRateLimit[source] = now
|
||||
|
||||
local x, y, z, heading = tonumber(position.x), tonumber(position.y),
|
||||
tonumber(position.z), tonumber(position.heading)
|
||||
if not x or not y or not z or not heading then return end
|
||||
if math.abs(x) > 10000 or math.abs(y) > 10000 or math.abs(z) > 3000 then return end
|
||||
|
||||
player.position = { x = x, y = y, z = z, heading = heading }
|
||||
savePosition(source)
|
||||
end)
|
||||
|
||||
AddEventHandler('playerDropped', function()
|
||||
local source = source
|
||||
savePosition(source)
|
||||
Players[source] = nil
|
||||
positionRateLimit[source] = nil
|
||||
end)
|
||||
|
||||
AddEventHandler('onResourceStop', function(resourceName)
|
||||
if resourceName ~= GetCurrentResourceName() then return end
|
||||
for source in pairs(Players) do savePosition(source) end
|
||||
end)
|
||||
|
||||
MySQL.ready(function()
|
||||
MySQL.query.await([[
|
||||
ALTER TABLE devx_characters
|
||||
ADD COLUMN IF NOT EXISTS phone_number VARCHAR(16) NULL AFTER model
|
||||
]])
|
||||
|
||||
MySQL.query.await([[
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_devx_character_phone
|
||||
ON devx_characters (phone_number)
|
||||
]])
|
||||
|
||||
MySQL.query.await([[
|
||||
CREATE TABLE IF NOT EXISTS devx_bans (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
license VARCHAR(80) NOT NULL,
|
||||
player_name VARCHAR(96) NULL,
|
||||
reason VARCHAR(255) NOT NULL,
|
||||
banned_by VARCHAR(96) NOT NULL,
|
||||
expires_at DATETIME NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
revoked_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_devx_bans_license (license),
|
||||
KEY idx_devx_bans_active (license, revoked_at, expires_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
]])
|
||||
|
||||
print('[devx_core] Datenbankschema v0.4.1 geprüft.')
|
||||
end)
|
||||
|
||||
AddEventHandler('playerConnecting', function(_, _, deferrals)
|
||||
local source = source
|
||||
deferrals.defer()
|
||||
Wait(0)
|
||||
|
||||
local license = getLicense(source)
|
||||
if not license then
|
||||
deferrals.done('FiveM konnte keine license:-Kennung ermitteln.')
|
||||
return
|
||||
end
|
||||
|
||||
local ok, ban = pcall(function()
|
||||
return MySQL.single.await([[
|
||||
SELECT reason, expires_at
|
||||
FROM devx_bans
|
||||
WHERE license = ?
|
||||
AND revoked_at IS NULL
|
||||
AND (expires_at IS NULL OR expires_at > NOW())
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
]], { license })
|
||||
end)
|
||||
|
||||
if ok and ban then
|
||||
local expiry = ban.expires_at and ('\nAblauf: %s'):format(tostring(ban.expires_at)) or '\nDauer: permanent'
|
||||
deferrals.done(('Du bist von DevX-City gesperrt.\nGrund: %s%s'):format(ban.reason, expiry))
|
||||
return
|
||||
end
|
||||
|
||||
deferrals.done()
|
||||
end)
|
||||
|
||||
RegisterNetEvent('devx_core:server:returnToWorld', function()
|
||||
local source = source
|
||||
if not Players[source] then return end
|
||||
SetPlayerRoutingBucket(source, 0)
|
||||
end)
|
||||
|
||||
RegisterCommand('world', function(source)
|
||||
if source == 0 then return end
|
||||
if not Players[source] then return end
|
||||
SetPlayerRoutingBucket(source, 0)
|
||||
TriggerClientEvent('devx_core:client:returnedToWorld', source)
|
||||
end, false)
|
||||
|
||||
RegisterCommand('setcash', function(source, args)
|
||||
if source ~= 0 and not IsPlayerAceAllowed(source, 'devx.admin') then return end
|
||||
|
||||
local target = tonumber(args[1])
|
||||
local amount = sanitizeInteger(args[2], DevXConfig.MaxMoneyOperation)
|
||||
if not target or amount == nil or not Players[target] then return end
|
||||
exports.devx_core:SetCash(target, amount, ('Admin %s'):format(source))
|
||||
end, false)
|
||||
@@ -0,0 +1,28 @@
|
||||
DevXConfig = {
|
||||
MaxCharacters = 3,
|
||||
StartCash = 150,
|
||||
StartBank = 500,
|
||||
|
||||
StreetSpawn = {
|
||||
x = -1037.72,
|
||||
y = -2737.88,
|
||||
z = 20.17,
|
||||
heading = 329.0
|
||||
},
|
||||
|
||||
PhonePrefix = '555',
|
||||
PositionSaveIntervalMs = 30000,
|
||||
MaxMoneyOperation = 100000000,
|
||||
|
||||
AdminWeapons = {
|
||||
WEAPON_PISTOL = 'Pistole',
|
||||
WEAPON_COMBATPISTOL = 'Kampfpistole',
|
||||
WEAPON_SMG = 'MP',
|
||||
WEAPON_CARBINERIFLE = 'Karabiner',
|
||||
WEAPON_PUMPSHOTGUN = 'Pumpgun',
|
||||
WEAPON_STUNGUN = 'Taser',
|
||||
WEAPON_FLASHLIGHT = 'Taschenlampe',
|
||||
WEAPON_NIGHTSTICK = 'Schlagstock',
|
||||
WEAPON_KNIFE = 'Messer'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user