first commit

This commit is contained in:
Colin-Joel Scupin
2026-08-05 01:29:39 +02:00
commit 86d4bcdc37
85 changed files with 7706 additions and 0 deletions
@@ -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' }
}
}
+11
View File
@@ -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}