first commit
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
local wheelBusy = false
|
||||
local casinoPeds = {}
|
||||
local uiOpen = false
|
||||
|
||||
local function notify(message) exports.devx_core:Notify(message) end
|
||||
local function help(text)
|
||||
BeginTextCommandDisplayHelp('STRING'); AddTextComponentSubstringPlayerName(text); EndTextCommandDisplayHelp(0, false, true, -1)
|
||||
end
|
||||
local function loadModel(name)
|
||||
local hash = GetHashKey(name); RequestModel(hash)
|
||||
local deadline = GetGameTimer() + 6000
|
||||
while not HasModelLoaded(hash) and GetGameTimer() < deadline do Wait(0) end
|
||||
return HasModelLoaded(hash) and hash or nil
|
||||
end
|
||||
local function ensureCasinoInterior()
|
||||
for _, ipl in ipairs(DevXCasinoConfig.Ipls or {}) do RequestIpl(ipl) end
|
||||
local p = DevXCasinoConfig.InteriorEntry
|
||||
SetFocusPosAndVel(p.x, p.y, p.z, 0.0, 0.0, 0.0)
|
||||
RequestCollisionAtCoord(p.x, p.y, p.z)
|
||||
NewLoadSceneStartSphere(p.x, p.y, p.z, 150.0, 0)
|
||||
local deadline = GetGameTimer() + 9000
|
||||
while not IsNewLoadSceneLoaded() and GetGameTimer() < deadline do Wait(0) end
|
||||
NewLoadSceneStop(); ClearFocus()
|
||||
end
|
||||
local function teleport(position)
|
||||
DoScreenFadeOut(250); while not IsScreenFadedOut() do Wait(0) end
|
||||
local ped = PlayerPedId(); FreezeEntityPosition(ped, true)
|
||||
RequestCollisionAtCoord(position.x, position.y, position.z)
|
||||
SetEntityCoordsNoOffset(ped, position.x, position.y, position.z, false, false, false)
|
||||
SetEntityHeading(ped, position.w or position.heading or 0.0)
|
||||
local deadline = GetGameTimer() + 5000
|
||||
while not HasCollisionLoadedAroundEntity(ped) and GetGameTimer() < deadline do RequestCollisionAtCoord(position.x, position.y, position.z); Wait(0) end
|
||||
FreezeEntityPosition(ped, false); Wait(150); DoScreenFadeIn(350)
|
||||
end
|
||||
local function spawnCasinoPeds()
|
||||
for _, ped in ipairs(casinoPeds) do if DoesEntityExist(ped) then DeleteEntity(ped) end end
|
||||
casinoPeds = {}
|
||||
for _, definition in ipairs(DevXCasinoConfig.Npcs or {}) do
|
||||
local model = loadModel(definition.model)
|
||||
if model then
|
||||
local c = definition.coords
|
||||
local ped = CreatePed(4, model, c.x, c.y, c.z, c.w, false, false)
|
||||
SetEntityInvincible(ped, true); SetBlockingOfNonTemporaryEvents(ped, true); FreezeEntityPosition(ped, true)
|
||||
TaskStartScenarioInPlace(ped, definition.scenario or 'WORLD_HUMAN_STAND_MOBILE', 0, true)
|
||||
casinoPeds[#casinoPeds + 1] = ped; SetModelAsNoLongerNeeded(model)
|
||||
end
|
||||
end
|
||||
end
|
||||
local function getWheelObject()
|
||||
for _, modelName in ipairs(DevXCasinoConfig.WheelModels or {}) do
|
||||
local object = GetClosestObjectOfType(DevXCasinoConfig.Wheel.x, DevXCasinoConfig.Wheel.y, DevXCasinoConfig.Wheel.z, 3.0, GetHashKey(modelName), false, false, false)
|
||||
if object and object ~= 0 and DoesEntityExist(object) then return object end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
local function animateWheel(result)
|
||||
local wheel = getWheelObject()
|
||||
if not wheel then notify('Das originale Casino-Glücksrad ist noch nicht geladen. Betritt das Casino erneut.'); return false end
|
||||
local duration, start = 6200, GetGameTimer()
|
||||
local base = GetEntityRotation(wheel, 2).y
|
||||
local segment = 360.0 / tonumber(result.totalSegments or 8)
|
||||
local target = 1440.0 + (360.0 - ((tonumber(result.segment or 0) * segment) + segment * .5))
|
||||
FreezeEntityPosition(wheel, true)
|
||||
while GetGameTimer() - start < duration do
|
||||
local progress = (GetGameTimer() - start) / duration
|
||||
local eased = 1.0 - ((1.0 - progress) ^ 4)
|
||||
SetEntityRotation(wheel, 0.0, (base + target * eased) % 360.0, 0.0, 2, true)
|
||||
help('Das Glücksrad dreht sich …'); Wait(0)
|
||||
end
|
||||
notify(('Glücksrad: %s gewonnen!'):format(result.reward and result.reward.label or 'Gewinn'))
|
||||
return true
|
||||
end
|
||||
local function spinWheel()
|
||||
if #(GetEntityCoords(PlayerPedId()) - DevXCasinoConfig.Wheel) > 3.0 then
|
||||
notify('Du musst direkt am Glücksrad stehen.')
|
||||
return
|
||||
end
|
||||
if wheelBusy then return end
|
||||
wheelBusy = true
|
||||
DevXTriggerCallback('casino:spin', {}, function(result)
|
||||
if not result.ok then notify(result.error or 'Das Glücksrad konnte nicht gedreht werden.'); wheelBusy = false; return end
|
||||
CreateThread(function() animateWheel(result); wheelBusy = false end)
|
||||
end)
|
||||
end
|
||||
local function openUi(mode)
|
||||
uiOpen = true; SetNuiFocus(true, true)
|
||||
DevXTriggerCallback('casino:status', {}, function(status)
|
||||
SendNUIMessage({ action = 'open', mode = mode, status = status })
|
||||
end)
|
||||
end
|
||||
local function closeUi()
|
||||
uiOpen = false; SetNuiFocus(false, false); SendNUIMessage({ action = 'close' })
|
||||
end
|
||||
|
||||
RegisterNetEvent('devx_casino:client:notify', notify)
|
||||
RegisterCommand('dailywheel', spinWheel, false)
|
||||
RegisterNUICallback('close', function(_, cb) closeUi(); cb({ ok = true }) end)
|
||||
RegisterNUICallback('refresh', function(_, cb) DevXTriggerCallback('casino:status', {}, cb) end)
|
||||
RegisterNUICallback('exchange', function(data, cb) DevXTriggerCallback('casino:exchange', data, cb) end)
|
||||
RegisterNUICallback('slots', function(data, cb) DevXTriggerCallback('casino:slots', data, cb) end)
|
||||
RegisterNUICallback('roulette', function(data, cb) DevXTriggerCallback('casino:roulette', data, cb) end)
|
||||
RegisterNUICallback('blackjackStart', function(data, cb) DevXTriggerCallback('casino:blackjackStart', data, cb) end)
|
||||
RegisterNUICallback('blackjackAction', function(data, cb) DevXTriggerCallback('casino:blackjackAction', data, cb) end)
|
||||
|
||||
CreateThread(function()
|
||||
ensureCasinoInterior(); Wait(1000); spawnCasinoPeds()
|
||||
local blip = AddBlipForCoord(DevXCasinoConfig.ExteriorEntry.x, DevXCasinoConfig.ExteriorEntry.y, DevXCasinoConfig.ExteriorEntry.z)
|
||||
SetBlipSprite(blip, 679); SetBlipScale(blip, .8); SetBlipColour(blip, 27); SetBlipAsShortRange(blip, true)
|
||||
BeginTextCommandSetBlipName('STRING'); AddTextComponentString('Diamond Casino'); EndTextCommandSetBlipName(blip)
|
||||
end)
|
||||
|
||||
local points = {
|
||||
{ key = 'cashier', position = DevXCasinoConfig.Cashier, label = '~INPUT_CONTEXT~ Casino-Kasse und Chips', mode = 'cashier' },
|
||||
{ key = 'slots', position = DevXCasinoConfig.Slots, label = '~INPUT_CONTEXT~ Spielautomaten', mode = 'slots' },
|
||||
{ key = 'roulette', position = DevXCasinoConfig.Roulette, label = '~INPUT_CONTEXT~ Roulette', mode = 'roulette' },
|
||||
{ key = 'blackjack', position = DevXCasinoConfig.Blackjack, label = '~INPUT_CONTEXT~ Blackjack', mode = 'blackjack' }
|
||||
}
|
||||
|
||||
CreateThread(function()
|
||||
while true do
|
||||
local sleep = 700
|
||||
if exports.devx_core:IsPlayerLoaded() and not uiOpen then
|
||||
local coords = GetEntityCoords(PlayerPedId())
|
||||
local exterior = vector3(DevXCasinoConfig.ExteriorEntry.x, DevXCasinoConfig.ExteriorEntry.y, DevXCasinoConfig.ExteriorEntry.z)
|
||||
local interiorExit = vector3(DevXCasinoConfig.InteriorExit.x, DevXCasinoConfig.InteriorExit.y, DevXCasinoConfig.InteriorExit.z)
|
||||
if #(coords - exterior) < 12.0 then
|
||||
sleep = 0; DrawMarker(1, exterior.x, exterior.y, exterior.z - 1.0,0,0,0,0,0,0,1.1,1.1,.3,190,110,255,130,false,false,2,false,nil,nil,false)
|
||||
if #(coords - exterior) < 1.5 then help('~INPUT_CONTEXT~ Casino betreten'); if IsControlJustReleased(0,38) then ensureCasinoInterior(); teleport(DevXCasinoConfig.InteriorEntry); spawnCasinoPeds() end end
|
||||
elseif #(coords - interiorExit) < 8.0 then
|
||||
sleep = 0; DrawMarker(1, interiorExit.x, interiorExit.y, interiorExit.z - 1.0,0,0,0,0,0,0,1.0,1.0,.25,190,110,255,130,false,false,2,false,nil,nil,false)
|
||||
if #(coords - interiorExit) < 1.5 then help('~INPUT_CONTEXT~ Casino verlassen'); if IsControlJustReleased(0,38) then teleport(DevXCasinoConfig.ExteriorEntry) end end
|
||||
else
|
||||
local wheelDistance = #(coords - DevXCasinoConfig.Wheel)
|
||||
if wheelDistance < 10.0 then
|
||||
sleep = 0; DrawMarker(2, DevXCasinoConfig.Wheel.x,DevXCasinoConfig.Wheel.y,DevXCasinoConfig.Wheel.z+.15,0,0,0,0,180,0,.25,.25,.25,255,190,70,150,false,false,2,false,nil,nil,false)
|
||||
if wheelDistance < 1.8 then help(wheelBusy and 'Das Glücksrad dreht sich …' or '~INPUT_CONTEXT~ täglichen Glücksrad-Dreh starten'); if not wheelBusy and IsControlJustReleased(0,38) then spinWheel() end end
|
||||
end
|
||||
for _, point in ipairs(points) do
|
||||
local distance = #(coords - point.position)
|
||||
if distance < 8.0 then
|
||||
sleep = 0; DrawMarker(2,point.position.x,point.position.y,point.position.z+.15,0,0,0,0,180,0,.22,.22,.22,210,160,70,145,false,false,2,false,nil,nil,false)
|
||||
if distance < 1.6 then help(point.label); if IsControlJustReleased(0,38) then openUi(point.mode) end end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Wait(sleep)
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler('onResourceStop', function(resource)
|
||||
if resource ~= GetCurrentResourceName() then return end
|
||||
closeUi()
|
||||
for _, ped in ipairs(casinoPeds) do if DoesEntityExist(ped) then DeleteEntity(ped) end end
|
||||
end)
|
||||
@@ -0,0 +1,13 @@
|
||||
fx_version 'cerulean'
|
||||
game 'gta5'
|
||||
|
||||
author 'Colin-Joel / DevX Studios'
|
||||
description 'DevX RP Diamond Casino with NPC ambience, lucky wheel, chips, slots, roulette and blackjack'
|
||||
version '0.4.3'
|
||||
lua54 'yes'
|
||||
shared_script 'shared/config.lua'
|
||||
ui_page 'web/index.html'
|
||||
files { 'web/index.html', 'web/style.css', 'web/app.js' }
|
||||
client_scripts { '@devx_core/client/callbacks.lua', 'client/main.lua' }
|
||||
server_scripts { '@oxmysql/lib/MySQL.lua', '@devx_core/server/callbacks.lua', 'server/main.lua' }
|
||||
dependencies { 'devx_core', 'devx_banking', 'devx_ipl' }
|
||||
@@ -0,0 +1,231 @@
|
||||
local spinLocks = {}
|
||||
local gameLocks = {}
|
||||
local blackjackSessions = {}
|
||||
|
||||
local rewards = {
|
||||
{ type = 'cash', amount = 500, label = '500 $ Bargeld' },
|
||||
{ type = 'xp', amount = 25, label = '25 RP-XP' },
|
||||
{ type = 'bank', amount = 1000, label = '1.000 $ Bankguthaben' },
|
||||
{ type = 'cash', amount = 750, label = '750 $ Bargeld' },
|
||||
{ type = 'xp', amount = 50, label = '50 RP-XP' },
|
||||
{ type = 'bank', amount = 1500, label = '1.500 $ Bankguthaben' },
|
||||
{ type = 'cash', amount = 1250, label = '1.250 $ Bargeld' },
|
||||
{ type = 'xp', amount = 100, label = '100 RP-XP' }
|
||||
}
|
||||
|
||||
MySQL.ready(function()
|
||||
MySQL.query.await([[
|
||||
CREATE TABLE IF NOT EXISTS devx_casino_daily (
|
||||
character_id BIGINT UNSIGNED NOT NULL,
|
||||
last_spin DATETIME NULL,
|
||||
total_spins INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (character_id),
|
||||
CONSTRAINT fk_devx_casino_character FOREIGN KEY (character_id)
|
||||
REFERENCES devx_characters (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
]])
|
||||
MySQL.query.await([[
|
||||
CREATE TABLE IF NOT EXISTS devx_casino_wallets (
|
||||
character_id BIGINT UNSIGNED NOT NULL,
|
||||
chips BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (character_id),
|
||||
CONSTRAINT fk_devx_casino_wallet_character FOREIGN KEY (character_id)
|
||||
REFERENCES devx_characters (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
]])
|
||||
end)
|
||||
|
||||
local function amount(value, min, max)
|
||||
local n = math.floor(tonumber(value) or 0)
|
||||
if n < (min or 1) or n > (max or 100000) then return nil end
|
||||
return n
|
||||
end
|
||||
local function wallet(characterId)
|
||||
MySQL.query.await('INSERT IGNORE INTO devx_casino_wallets (character_id, chips) VALUES (?, 0)', { characterId })
|
||||
return tonumber(MySQL.scalar.await('SELECT chips FROM devx_casino_wallets WHERE character_id = ?', { characterId })) or 0
|
||||
end
|
||||
local function addChips(characterId, chips)
|
||||
MySQL.query.await([[
|
||||
INSERT INTO devx_casino_wallets (character_id, chips) VALUES (?, ?)
|
||||
ON DUPLICATE KEY UPDATE chips = chips + VALUES(chips)
|
||||
]], { characterId, chips })
|
||||
return wallet(characterId)
|
||||
end
|
||||
local function removeChips(characterId, chips)
|
||||
local changed = MySQL.update.await([[
|
||||
UPDATE devx_casino_wallets SET chips = chips - ? WHERE character_id = ? AND chips >= ?
|
||||
]], { chips, characterId, chips })
|
||||
return changed and changed > 0, wallet(characterId)
|
||||
end
|
||||
local function spinStatus(characterId)
|
||||
local row = MySQL.single.await([[
|
||||
SELECT UNIX_TIMESTAMP(last_spin) AS lastSpin, total_spins AS totalSpins
|
||||
FROM devx_casino_daily WHERE character_id = ? LIMIT 1
|
||||
]], { characterId })
|
||||
local lastSpin = row and tonumber(row.lastSpin) or 0
|
||||
local nextSpin = lastSpin + DevXCasinoConfig.CooldownSeconds
|
||||
return { available = os.time() >= nextSpin, remainingSeconds = math.max(0, nextSpin - os.time()), totalSpins = row and tonumber(row.totalSpins) or 0 }
|
||||
end
|
||||
local function card()
|
||||
local rank = math.random(1, 13)
|
||||
local labels = { 'A','2','3','4','5','6','7','8','9','10','J','Q','K' }
|
||||
return { rank = rank, label = labels[rank] }
|
||||
end
|
||||
local function handValue(cards)
|
||||
local total, aces = 0, 0
|
||||
for _, item in ipairs(cards) do
|
||||
if item.rank == 1 then total = total + 11; aces = aces + 1
|
||||
elseif item.rank >= 10 then total = total + 10
|
||||
else total = total + item.rank end
|
||||
end
|
||||
while total > 21 and aces > 0 do total = total - 10; aces = aces - 1 end
|
||||
return total
|
||||
end
|
||||
local function blackjackPayload(session, reveal, result)
|
||||
local dealer = {}
|
||||
for index, item in ipairs(session.dealer) do
|
||||
dealer[index] = (reveal or index == 1) and item or { label = '?' }
|
||||
end
|
||||
return {
|
||||
ok = true, active = result == nil, result = result,
|
||||
playerCards = session.player, dealerCards = dealer,
|
||||
playerValue = handValue(session.player), dealerValue = reveal and handValue(session.dealer) or nil,
|
||||
chips = wallet(session.characterId)
|
||||
}
|
||||
end
|
||||
local function settleBlackjack(source, session, outcome)
|
||||
local result = outcome
|
||||
if outcome == 'win' then addChips(session.characterId, session.bet * 2)
|
||||
elseif outcome == 'blackjack' then addChips(session.characterId, math.floor(session.bet * 2.5))
|
||||
elseif outcome == 'push' then addChips(session.characterId, session.bet) end
|
||||
blackjackSessions[source] = nil
|
||||
return blackjackPayload(session, true, result)
|
||||
end
|
||||
|
||||
DevXRegisterCallback('casino:status', function(source)
|
||||
local player = exports.devx_core:GetPlayer(source)
|
||||
if not player then return { ok = false, error = 'Kein Charakter aktiv.' } end
|
||||
local status = spinStatus(player.characterId)
|
||||
status.ok = true; status.chips = wallet(player.characterId)
|
||||
return status
|
||||
end)
|
||||
|
||||
DevXRegisterCallback('casino:exchange', function(source, payload)
|
||||
local player = exports.devx_core:GetPlayer(source)
|
||||
local value = amount(payload.amount, 100, 100000)
|
||||
local direction = payload.direction
|
||||
if not player or not value or (direction ~= 'buy' and direction ~= 'sell') then return { ok = false, error = 'Ungültige Wechselanfrage.' } end
|
||||
if gameLocks[source] then return { ok = false, error = 'Eine Casino-Buchung läuft bereits.' } end
|
||||
gameLocks[source] = true
|
||||
local response
|
||||
if direction == 'buy' then
|
||||
local paid, balance = exports.devx_banking:ChargeCharacter(player.characterId, value, 'Casino-Chips', 'Diamond Casino')
|
||||
if paid then exports.devx_core:SetBankCache(source, balance); response = { ok = true, chips = addChips(player.characterId, value) }
|
||||
else response = { ok = false, error = balance } end
|
||||
else
|
||||
local removed = removeChips(player.characterId, value)
|
||||
if removed then
|
||||
local credited, balance = exports.devx_banking:CreditCharacter(player.characterId, value, 'Casino-Chips eingelöst', 'Diamond Casino')
|
||||
if credited then exports.devx_core:SetBankCache(source, balance); response = { ok = true, chips = wallet(player.characterId) }
|
||||
else addChips(player.characterId, value); response = { ok = false, error = balance } end
|
||||
else response = { ok = false, error = 'Du besitzt nicht genügend Chips.' } end
|
||||
end
|
||||
gameLocks[source] = nil
|
||||
return response
|
||||
end)
|
||||
|
||||
DevXRegisterCallback('casino:spin', function(source)
|
||||
local player = exports.devx_core:GetPlayer(source)
|
||||
if not player then return { ok = false, error = 'Kein Charakter aktiv.' } end
|
||||
if spinLocks[source] then return { ok = false, error = 'Dein Glücksrad-Dreh läuft bereits.' } end
|
||||
spinLocks[source] = true
|
||||
local status = spinStatus(player.characterId)
|
||||
if not status.available then spinLocks[source] = nil; return { ok = false, error = ('Nächster Dreh in %s Stunden.'):format(math.ceil(status.remainingSeconds / 3600)) } end
|
||||
|
||||
local index = math.random(1, #rewards); local reward = rewards[index]; local success = false
|
||||
if reward.type == 'cash' then success = exports.devx_core:AddCash(source, reward.amount, 'Casino-Glücksrad') == true
|
||||
elseif reward.type == 'bank' then
|
||||
local credited, newBalance = exports.devx_banking:CreditCharacter(player.characterId, reward.amount, 'Casino-Glücksrad', 'Diamond Casino')
|
||||
success = credited == true; if success then exports.devx_core:SetBankCache(source, newBalance) end
|
||||
elseif reward.type == 'xp' then success = exports.devx_core:AddMetadataNumber(source, 'rpXp', reward.amount) == true end
|
||||
if not success then spinLocks[source] = nil; return { ok = false, error = 'Gewinn konnte nicht gutgeschrieben werden.' } end
|
||||
|
||||
MySQL.query.await([[
|
||||
INSERT INTO devx_casino_daily (character_id, last_spin, total_spins) VALUES (?, NOW(), 1)
|
||||
ON DUPLICATE KEY UPDATE last_spin = NOW(), total_spins = total_spins + 1
|
||||
]], { player.characterId })
|
||||
spinLocks[source] = nil
|
||||
return { ok = true, reward = reward, segment = index - 1, totalSegments = #rewards, nextSpinSeconds = DevXCasinoConfig.CooldownSeconds }
|
||||
end)
|
||||
|
||||
DevXRegisterCallback('casino:slots', function(source, payload)
|
||||
local player = exports.devx_core:GetPlayer(source); local bet = amount(payload.bet, 100, 5000)
|
||||
if not player or not bet then return { ok = false, error = 'Ungültiger Einsatz.' } end
|
||||
local removed = removeChips(player.characterId, bet)
|
||||
if not removed then return { ok = false, error = 'Nicht genügend Chips.' } end
|
||||
local symbols = { '7', 'BAR', 'KIRSCHE', 'DIAMANT', 'GLOCKE', 'ZITRONE' }
|
||||
local roll = { symbols[math.random(#symbols)], symbols[math.random(#symbols)], symbols[math.random(#symbols)] }
|
||||
local payout = 0
|
||||
if roll[1] == roll[2] and roll[2] == roll[3] then payout = bet * (roll[1] == '7' and 12 or 7)
|
||||
elseif roll[1] == roll[2] or roll[2] == roll[3] or roll[1] == roll[3] then payout = bet * 2 end
|
||||
if payout > 0 then addChips(player.characterId, payout) end
|
||||
return { ok = true, symbols = roll, payout = payout, chips = wallet(player.characterId) }
|
||||
end)
|
||||
|
||||
local redNumbers = { [1]=true,[3]=true,[5]=true,[7]=true,[9]=true,[12]=true,[14]=true,[16]=true,[18]=true,[19]=true,[21]=true,[23]=true,[25]=true,[27]=true,[30]=true,[32]=true,[34]=true,[36]=true }
|
||||
DevXRegisterCallback('casino:roulette', function(source, payload)
|
||||
local player = exports.devx_core:GetPlayer(source); local bet = amount(payload.bet, 100, 10000); local choice = payload.choice
|
||||
if not player or not bet or (choice ~= 'red' and choice ~= 'black' and choice ~= 'green') then return { ok = false, error = 'Ungültiger Einsatz.' } end
|
||||
local removed = removeChips(player.characterId, bet); if not removed then return { ok = false, error = 'Nicht genügend Chips.' } end
|
||||
local number = math.random(0, 36); local color = number == 0 and 'green' or (redNumbers[number] and 'red' or 'black')
|
||||
local payout = color == choice and bet * (color == 'green' and 14 or 2) or 0
|
||||
if payout > 0 then addChips(player.characterId, payout) end
|
||||
return { ok = true, number = number, color = color, payout = payout, chips = wallet(player.characterId) }
|
||||
end)
|
||||
|
||||
DevXRegisterCallback('casino:blackjackStart', function(source, payload)
|
||||
local player = exports.devx_core:GetPlayer(source); local bet = amount(payload.bet, 100, 10000)
|
||||
if not player or not bet then return { ok = false, error = 'Ungültiger Einsatz.' } end
|
||||
if blackjackSessions[source] then return { ok = false, error = 'Du spielst bereits eine Runde.' } end
|
||||
local removed = removeChips(player.characterId, bet); if not removed then return { ok = false, error = 'Nicht genügend Chips.' } end
|
||||
local session = { characterId = player.characterId, bet = bet, player = { card(), card() }, dealer = { card(), card() } }
|
||||
blackjackSessions[source] = session
|
||||
local pv, dv = handValue(session.player), handValue(session.dealer)
|
||||
if pv == 21 and dv == 21 then return settleBlackjack(source, session, 'push') end
|
||||
if pv == 21 then return settleBlackjack(source, session, 'blackjack') end
|
||||
if dv == 21 then return settleBlackjack(source, session, 'lose') end
|
||||
return blackjackPayload(session, false, nil)
|
||||
end)
|
||||
|
||||
DevXRegisterCallback('casino:blackjackAction', function(source, payload)
|
||||
local session = blackjackSessions[source]
|
||||
if not session then return { ok = false, error = 'Keine aktive Blackjack-Runde.' } end
|
||||
if payload.action == 'hit' then
|
||||
session.player[#session.player + 1] = card()
|
||||
local value = handValue(session.player)
|
||||
if value > 21 then return settleBlackjack(source, session, 'bust') end
|
||||
if value == 21 then payload.action = 'stand' else return blackjackPayload(session, false, nil) end
|
||||
end
|
||||
if payload.action == 'stand' then
|
||||
while handValue(session.dealer) < 17 do session.dealer[#session.dealer + 1] = card() end
|
||||
local pv, dv = handValue(session.player), handValue(session.dealer)
|
||||
if dv > 21 or pv > dv then return settleBlackjack(source, session, 'win')
|
||||
elseif pv == dv then return settleBlackjack(source, session, 'push')
|
||||
else return settleBlackjack(source, session, 'lose') end
|
||||
end
|
||||
return { ok = false, error = 'Ungültige Aktion.' }
|
||||
end)
|
||||
|
||||
RegisterCommand('resetwheel', function(source, args)
|
||||
if source ~= 0 and not IsPlayerAceAllowed(source, 'devx.admin') then return end
|
||||
local target = tonumber(args[1]) or source
|
||||
local player = exports.devx_core:GetPlayer(target)
|
||||
if not player then return end
|
||||
MySQL.query.await('DELETE FROM devx_casino_daily WHERE character_id = ?', { player.characterId })
|
||||
TriggerClientEvent('devx_casino:client:notify', target, 'Dein täglicher Glücksrad-Dreh wurde zurückgesetzt.')
|
||||
end, false)
|
||||
|
||||
AddEventHandler('playerDropped', function()
|
||||
spinLocks[source] = nil; gameLocks[source] = nil; blackjackSessions[source] = nil
|
||||
end)
|
||||
@@ -0,0 +1,25 @@
|
||||
DevXCasinoConfig = {
|
||||
ExteriorEntry = vector4(935.72, 46.86, 81.10, 146.0),
|
||||
InteriorEntry = vector4(1089.70, 206.12, -49.00, 350.0),
|
||||
InteriorExit = vector4(1085.83, 214.41, -49.20, 130.0),
|
||||
Wheel = vector3(1111.12, 229.84, -49.64),
|
||||
WheelModels = { 'vw_prop_vw_luckywheel_02a', 'vw_prop_vw_luckywheel_01a' },
|
||||
Cashier = vector3(1117.48, 219.63, -49.44),
|
||||
Slots = vector3(1114.65, 235.85, -49.64),
|
||||
Roulette = vector3(1143.82, 264.58, -51.84),
|
||||
Blackjack = vector3(1148.76, 269.20, -51.84),
|
||||
CooldownSeconds = 86400,
|
||||
Ipls = {
|
||||
'hei_dlc_windows_casino', 'hei_dlc_casino_aircon', 'vw_dlc_casino_door',
|
||||
'hei_dlc_casino_door', 'vw_casino_main', 'vw_casino_garage', 'vw_casino_carpark'
|
||||
},
|
||||
Npcs = {
|
||||
{ model = 's_f_y_casino_01', coords = vector4(1117.52, 220.48, -49.44, 90.0), scenario = 'WORLD_HUMAN_CLIPBOARD' },
|
||||
{ model = 's_m_y_casino_01', coords = vector4(1144.10, 264.25, -51.84, 180.0), scenario = 'WORLD_HUMAN_STAND_IMPATIENT' },
|
||||
{ model = 's_m_y_casino_01', coords = vector4(1148.95, 268.82, -51.84, 180.0), scenario = 'WORLD_HUMAN_STAND_IMPATIENT' },
|
||||
{ model = 'a_f_y_business_04', coords = vector4(1116.15, 233.75, -49.64, 110.0), scenario = 'WORLD_HUMAN_STAND_MOBILE' },
|
||||
{ model = 'a_m_y_business_03', coords = vector4(1107.65, 229.25, -49.64, 280.0), scenario = 'WORLD_HUMAN_DRINKING' },
|
||||
{ model = 'a_f_y_bevhills_02', coords = vector4(1126.85, 241.30, -49.64, 205.0), scenario = 'WORLD_HUMAN_PARTYING' },
|
||||
{ model = 'a_m_m_bevhills_02', coords = vector4(1136.25, 250.90, -51.04, 50.0), scenario = 'WORLD_HUMAN_STAND_MOBILE' }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
const resource=typeof GetParentResourceName==='function'?GetParentResourceName():'devx_casino';const app=document.querySelector('#app'),chips=document.querySelector('#chips'),message=document.querySelector('#message');const views=[...document.querySelectorAll('.view')];let blackjackActive=false;
|
||||
async function post(name,data={}){try{const r=await fetch(`https://${resource}/${name}`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});return await r.json()}catch{return{ok:false,error:'Keine Antwort vom Casino.'}}}
|
||||
function money(v){return new Intl.NumberFormat('de-DE').format(Number(v)||0)}function setMessage(t){message.textContent=t||''}function setChips(v){chips.textContent=money(v)}function show(id){views.forEach(v=>v.classList.toggle('hidden',v.id!==id));document.querySelector('#title').textContent={cashier:'Casino-Kasse',slots:'Spielautomaten',roulette:'Roulette',blackjack:'Blackjack'}[id]||'Casino'}
|
||||
async function refresh(){const r=await post('refresh');if(r.ok)setChips(r.chips);else setMessage(r.error)}
|
||||
document.querySelector('#close').onclick=()=>post('close');document.addEventListener('keyup',e=>{if(e.key==='Escape')post('close')});document.querySelectorAll('nav button').forEach(b=>b.onclick=()=>show(b.dataset.view));
|
||||
async function exchange(direction){const amount=document.querySelector('#exchangeAmount').value;const r=await post('exchange',{direction,amount});setMessage(r.ok?'Buchung abgeschlossen.':r.error);if(r.ok)setChips(r.chips)}document.querySelector('#buyChips').onclick=()=>exchange('buy');document.querySelector('#sellChips').onclick=()=>exchange('sell');
|
||||
document.querySelector('#slotPlay').onclick=async()=>{setMessage('Walzen drehen …');const r=await post('slots',{bet:document.querySelector('#slotBet').value});if(!r.ok){setMessage(r.error);return}document.querySelectorAll('#reels b').forEach((el,i)=>el.textContent=r.symbols[i]);setChips(r.chips);setMessage(r.payout>0?`Gewinn: ${money(r.payout)} Chips`:'Leider kein Gewinn.')};
|
||||
document.querySelectorAll('[data-color]').forEach(b=>b.onclick=async()=>{const r=await post('roulette',{choice:b.dataset.color,bet:document.querySelector('#rouletteBet').value});if(!r.ok){setMessage(r.error);return}const ball=document.querySelector('#rouletteResult');ball.textContent=r.number;ball.style.background=r.color==='red'?'#9d2b37':r.color==='black'?'#17191e':'#26804a';setChips(r.chips);setMessage(r.payout>0?`Gewinn: ${money(r.payout)} Chips`:`${r.number} · ${r.color}`)});
|
||||
function cards(list){return (list||[]).map(c=>c.label).join(' ')}function renderBlackjack(r){document.querySelector('#dealerCards').textContent=cards(r.dealerCards);document.querySelector('#playerCards').textContent=cards(r.playerCards);document.querySelector('#playerValue').textContent=r.playerValue?`${r.playerValue} Punkte`:'';setChips(r.chips);blackjackActive=Boolean(r.active);document.querySelector('#blackjackActions').classList.toggle('hidden',!blackjackActive);document.querySelector('#blackjackStart').disabled=blackjackActive;if(r.result)setMessage({win:'Gewonnen!',blackjack:'Blackjack!',push:'Unentschieden.',lose:'Dealer gewinnt.',bust:'Überkauft.'}[r.result]||r.result)}
|
||||
document.querySelector('#blackjackStart').onclick=async()=>{const r=await post('blackjackStart',{bet:document.querySelector('#blackjackBet').value});if(!r.ok){setMessage(r.error);return}setMessage('');renderBlackjack(r)};document.querySelectorAll('[data-action]').forEach(b=>b.onclick=async()=>{const r=await post('blackjackAction',{action:b.dataset.action});if(!r.ok){setMessage(r.error);return}renderBlackjack(r)});
|
||||
window.addEventListener('message',e=>{const d=e.data||{};if(d.action==='open'){app.classList.remove('hidden');setChips(d.status?.chips||0);show(d.mode==='lobby'?'cashier':d.mode||'cashier');setMessage('')}if(d.action==='close')app.classList.add('hidden')});
|
||||
@@ -0,0 +1,9 @@
|
||||
<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="stylesheet" href="style.css"></head><body>
|
||||
<main id="app" class="hidden"><section class="card"><button id="close">×</button><small>THE DIAMOND CASINO & RESORT</small><h1 id="title">Casino</h1><div class="wallet"><span>CHIPS</span><strong id="chips">0</strong></div>
|
||||
<nav><button data-view="cashier">Kasse</button><button data-view="slots">Slots</button><button data-view="roulette">Roulette</button><button data-view="blackjack">Blackjack</button></nav>
|
||||
<p id="message"></p>
|
||||
<section id="cashier" class="view"><h2>Casino-Kasse</h2><input id="exchangeAmount" type="number" min="100" step="100" value="1000"><div class="row"><button id="buyChips">Chips kaufen</button><button id="sellChips">Chips einlösen</button></div></section>
|
||||
<section id="slots" class="view hidden"><h2>Spielautomat</h2><div id="reels" class="reels"><b>?</b><b>?</b><b>?</b></div><input id="slotBet" type="number" min="100" max="5000" step="100" value="500"><button id="slotPlay">Drehen</button></section>
|
||||
<section id="roulette" class="view hidden"><h2>Roulette</h2><div id="rouletteResult" class="roulette-result">–</div><input id="rouletteBet" type="number" min="100" max="10000" step="100" value="500"><div class="row three"><button data-color="red">Rot</button><button data-color="black">Schwarz</button><button data-color="green">Grün</button></div></section>
|
||||
<section id="blackjack" class="view hidden"><h2>Blackjack</h2><div class="hands"><div><span>Dealer</span><strong id="dealerCards">–</strong></div><div><span>Du</span><strong id="playerCards">–</strong><small id="playerValue"></small></div></div><input id="blackjackBet" type="number" min="100" max="10000" step="100" value="500"><button id="blackjackStart">Runde starten</button><div id="blackjackActions" class="row hidden"><button data-action="hit">Karte</button><button data-action="stand">Halten</button></div></section>
|
||||
</section></main><script src="app.js"></script></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
:root{font-family:Inter,system-ui,sans-serif;color:#fff}*{box-sizing:border-box}body{margin:0;background:transparent;overflow:hidden}.hidden{display:none!important}button,input{font:inherit}#app{position:fixed;inset:0;display:grid;place-items:center;background:rgba(3,4,8,.52);backdrop-filter:blur(4px)}.card{position:relative;width:min(680px,calc(100vw - 70px));padding:28px;border:1px solid rgba(255,255,255,.14);border-radius:22px;background:linear-gradient(160deg,#171421,#0c0d13);box-shadow:0 35px 100px rgba(0,0,0,.65)}#close{position:absolute;right:18px;top:16px;border:0;background:transparent;color:white;font-size:28px;cursor:pointer}.card>small{color:#d8b66e;font-weight:900;letter-spacing:.18em}.card h1{margin:5px 0 16px}.wallet{position:absolute;right:58px;top:22px;text-align:right}.wallet span{display:block;font-size:9px;color:rgba(255,255,255,.55);letter-spacing:.13em}.wallet strong{font-size:22px;color:#f0cc80}nav{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:15px 0}button{border:1px solid rgba(255,255,255,.11);border-radius:10px;padding:11px;color:white;background:rgba(255,255,255,.06);cursor:pointer;font-weight:800}button:hover{background:rgba(213,176,101,.17);border-color:#d6b267}button:disabled{opacity:.45;cursor:not-allowed}.view{min-height:290px;padding:18px;border-radius:15px;background:rgba(255,255,255,.035)}.view h2{margin:0 0 18px}input{width:100%;padding:12px;margin-bottom:10px;border:1px solid rgba(255,255,255,.12);border-radius:10px;color:white;background:rgba(0,0,0,.25);outline:0}.row{display:grid;grid-template-columns:1fr 1fr;gap:10px}.row.three{grid-template-columns:repeat(3,1fr)}#message{min-height:22px;color:#f0cc80}.reels{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:18px 0}.reels b{height:90px;display:grid;place-items:center;border-radius:14px;background:#f7f1e6;color:#191720;font-size:22px}.roulette-result{height:90px;margin:18px 0;display:grid;place-items:center;border-radius:50%;width:90px;background:#333;font-size:28px;font-weight:900}.hands{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:15px}.hands>div{padding:15px;border-radius:12px;background:rgba(255,255,255,.06)}.hands span,.hands small{display:block;color:rgba(255,255,255,.55);font-size:10px}.hands strong{display:block;margin:8px 0;font-size:20px}
|
||||
Reference in New Issue
Block a user