166 lines
7.1 KiB
Lua
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)
|