Files
devx-rp/resources/[devx]/devx_crime/server/main.lua
T
2026-08-05 01:29:39 +02:00

166 lines
7.1 KiB
Lua

local activeRobberies = {}
local prepSessions = {}
local globalCooldownUntil = 0
local function nearLocation(source, coords, maximum)
local ped = GetPlayerPed(source)
if not ped or ped <= 0 then return false end
local playerCoords = GetEntityCoords(ped)
return #(playerCoords - vector3(coords.x, coords.y, coords.z)) <= maximum
end
local function findPrep(key)
for _, entry in ipairs(DevXCrimeConfig.PrepLocations) do
if entry.key == key then return entry end
end
return nil
end
local function getProgress(characterId)
local row = MySQL.single.await([[
SELECT drill_done AS drillDone, card_done AS cardDone, vehicle_done AS vehicleDone
FROM devx_heist_progress WHERE character_id = ? LIMIT 1
]], { characterId })
if not row then
MySQL.insert.await('INSERT IGNORE INTO devx_heist_progress (character_id) VALUES (?)', { characterId })
row = { drillDone = 0, cardDone = 0, vehicleDone = 0 }
end
return {
drill = tonumber(row.drillDone) == 1,
card = tonumber(row.cardDone) == 1,
vehicle = tonumber(row.vehicleDone) == 1
}
end
local function leavePrep(source)
prepSessions[source] = nil
SetPlayerRoutingBucket(source, 0)
end
MySQL.ready(function()
MySQL.query.await([[
CREATE TABLE IF NOT EXISTS devx_heist_progress (
character_id BIGINT UNSIGNED NOT NULL,
drill_done TINYINT(1) NOT NULL DEFAULT 0,
card_done TINYINT(1) NOT NULL DEFAULT 0,
vehicle_done TINYINT(1) NOT NULL DEFAULT 0,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (character_id),
CONSTRAINT fk_devx_heist_character FOREIGN KEY (character_id)
REFERENCES devx_characters (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
]])
end)
DevXRegisterCallback('crime:status', function(source)
local player = exports.devx_core:GetPlayer(source)
if not player then return { ok = false, error = 'Kein Charakter aktiv.' } end
return {
ok = true,
progress = getProgress(player.characterId),
cooldownSeconds = math.max(0, globalCooldownUntil - os.time()),
active = activeRobberies[source] ~= nil
}
end)
DevXRegisterCallback('crime:enterPrep', function(source, payload)
local player = exports.devx_core:GetPlayer(source)
local prep = findPrep(tostring(payload.key or ''))
if not player or not prep then return { ok = false, error = 'Ungültige Vorbereitungsmission.' } end
if not nearLocation(source, prep.exterior, 6.0) then return { ok = false, error = 'Du bist nicht am Lagereingang.' } end
local bucket = DevXCrimeConfig.MissionBucketBase + tonumber(source)
prepSessions[source] = { key = prep.key, exterior = prep.exterior, bucket = bucket }
SetRoutingBucketPopulationEnabled(bucket, false)
SetRoutingBucketEntityLockdownMode(bucket, 'relaxed')
SetPlayerRoutingBucket(source, bucket)
return { ok = true, interior = DevXCrimeConfig.PrepInterior, prep = prep }
end)
DevXRegisterCallback('crime:leavePrep', function(source)
local session = prepSessions[source]
if not session then return { ok = false, error = 'Keine Vorbereitungsmission aktiv.' } end
local exterior = session.exterior
leavePrep(source)
return { ok = true, exterior = exterior }
end)
DevXRegisterCallback('crime:completePrep', function(source, payload)
local player = exports.devx_core:GetPlayer(source)
local key = tostring(payload.key or '')
local prep = findPrep(key)
local session = prepSessions[source]
if not player or not prep or not session or session.key ~= key then
return { ok = false, error = 'Keine passende Vorbereitungsmission aktiv.' }
end
if not nearLocation(source, prep.objective, 5.0) then return { ok = false, error = 'Du bist nicht am Missionsziel.' } end
local column = ({ drill = 'drill_done', card = 'card_done', vehicle = 'vehicle_done' })[key]
MySQL.query.await('INSERT IGNORE INTO devx_heist_progress (character_id) VALUES (?)', { player.characterId })
MySQL.update.await(('UPDATE devx_heist_progress SET %s = 1 WHERE character_id = ?'):format(column), { player.characterId })
local exterior = session.exterior
leavePrep(source)
return { ok = true, progress = getProgress(player.characterId), exterior = exterior }
end)
DevXRegisterCallback('crime:startRobbery', function(source)
local player = exports.devx_core:GetPlayer(source)
if not player then return { ok = false, error = 'Kein Charakter aktiv.' } end
if not nearLocation(source, DevXCrimeConfig.Bank, 6.0) then return { ok = false, error = 'Du bist nicht im Tresorbereich.' } end
if activeRobberies[source] then return { ok = false, error = 'Der Raub läuft bereits.' } end
if os.time() < globalCooldownUntil then
return { ok = false, error = ('Die Bank ist noch %s Minuten gesperrt.'):format(math.ceil((globalCooldownUntil - os.time()) / 60)) }
end
local progress = getProgress(player.characterId)
if not progress.drill or not progress.card or not progress.vehicle then
return { ok = false, error = 'Schließe zuerst alle drei Vorbereitungsmissionen ab.' }
end
local duration = DevXCrimeConfig.RobberyDurationSeconds
activeRobberies[source] = { characterId = player.characterId, completeAt = os.time() + duration }
globalCooldownUntil = os.time() + DevXCrimeConfig.GlobalCooldownSeconds
return { ok = true, duration = duration }
end)
DevXRegisterCallback('crime:finishRobbery', function(source)
local robbery = activeRobberies[source]
local player = exports.devx_core:GetPlayer(source)
if not robbery or not player or robbery.characterId ~= player.characterId then
return { ok = false, error = 'Kein aktiver Bankraub.' }
end
if os.time() < robbery.completeAt then return { ok = false, error = 'Der Tresor ist noch nicht geöffnet.' } end
if not nearLocation(source, DevXCrimeConfig.Bank, 12.0) then
activeRobberies[source] = nil
return { ok = false, error = 'Du hast den Bankraubbereich verlassen.' }
end
local reward = math.random(DevXCrimeConfig.MinimumReward, DevXCrimeConfig.MaximumReward)
exports.devx_core:AddCash(source, reward, 'Fleeca-Bankraub')
exports.devx_core:AddMetadataNumber(source, 'rpXp', 75)
MySQL.update.await([[
UPDATE devx_heist_progress
SET drill_done = 0, card_done = 0, vehicle_done = 0
WHERE character_id = ?
]], { player.characterId })
activeRobberies[source] = nil
return { ok = true, reward = reward, xp = 75 }
end)
AddEventHandler('playerDropped', function()
activeRobberies[source] = nil
leavePrep(source)
end)
AddEventHandler('onResourceStop', function(resourceName)
if resourceName ~= GetCurrentResourceName() then return end
for _, playerId in ipairs(GetPlayers()) do SetPlayerRoutingBucket(tonumber(playerId), 0) end
end)
RegisterCommand('resetheist', function(source)
if source ~= 0 and not IsPlayerAceAllowed(source, 'devx.admin') then return end
globalCooldownUntil = 0
activeRobberies = {}
end, false)