local actionCooldowns = {} MySQL.ready(function() MySQL.query.await([[ CREATE TABLE IF NOT EXISTS devx_club_relations ( character_id BIGINT UNSIGNED NOT NULL, relation_key VARCHAR(32) NOT NULL, satisfaction TINYINT UNSIGNED NOT NULL DEFAULT 0, contact_unlocked TINYINT(1) NOT NULL DEFAULT 0, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (character_id, relation_key), CONSTRAINT fk_devx_club_character FOREIGN KEY (character_id) REFERENCES devx_characters (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ]]) end) local function relation(characterId) local row = MySQL.single.await([[ SELECT satisfaction, contact_unlocked AS contactUnlocked FROM devx_club_relations WHERE character_id = ? AND relation_key = ? LIMIT 1 ]], { characterId, DevXStripclub.Nikki.key }) return { satisfaction = row and tonumber(row.satisfaction) or 0, contactUnlocked = row and tonumber(row.contactUnlocked) == 1 or false } end local function saveRelation(characterId, satisfaction, unlocked) MySQL.query.await([[ INSERT INTO devx_club_relations (character_id, relation_key, satisfaction, contact_unlocked) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE satisfaction = VALUES(satisfaction), contact_unlocked = VALUES(contact_unlocked) ]], { characterId, DevXStripclub.Nikki.key, satisfaction, unlocked and 1 or 0 }) end DevXRegisterCallback('stripclub:state', function(source) local player = exports.devx_core:GetPlayer(source) if not player then return { ok = false, error = 'Kein Charakter aktiv.' } end local current = relation(player.characterId) current.ok = true current.name = DevXStripclub.Nikki.name current.threshold = DevXStripclub.Nikki.contactThreshold return current end) DevXRegisterCallback('stripclub:interact', function(source, payload) local player = exports.devx_core:GetPlayer(source) local action = payload and payload.action if not player or (action ~= 'talk' and action ~= 'lounge') then return { ok = false, error = 'Ungültige Anfrage.' } end local now = os.time() local cooldown = actionCooldowns[source] or 0 if cooldown > now then return { ok = false, error = ('Warte noch %s Sekunden.'):format(cooldown - now) } end local gain = DevXStripclub.TalkGain if action == 'lounge' then local paid, result = exports.devx_banking:ChargeCharacter( player.characterId, DevXStripclub.LoungePrice, 'VIP-Lounge', 'Vanilla Unicorn' ) if not paid then return { ok = false, error = result } end exports.devx_core:SetBankCache(source, result) gain = DevXStripclub.LoungeGain actionCooldowns[source] = now + 45 else actionCooldowns[source] = now + 15 end local current = relation(player.characterId) current.satisfaction = math.min(DevXStripclub.Nikki.contactThreshold, current.satisfaction + gain) local newlyUnlocked = not current.contactUnlocked and current.satisfaction >= DevXStripclub.Nikki.contactThreshold if newlyUnlocked then current.contactUnlocked = true end saveRelation(player.characterId, current.satisfaction, current.contactUnlocked) if current.contactUnlocked then exports.devx_phone:AddSystemContact( player.characterId, DevXStripclub.Nikki.name, DevXStripclub.Nikki.phone, 'npc_companion', { relationKey = DevXStripclub.Nikki.key } ) TriggerClientEvent('devx_phone:client:refresh', source) end return { ok = true, satisfaction = current.satisfaction, threshold = DevXStripclub.Nikki.contactThreshold, contactUnlocked = current.contactUnlocked, newlyUnlocked = newlyUnlocked, action = action } end) local function inviteCompanion(source) local player = exports.devx_core:GetPlayer(source) if not player then return { ok = false, error = 'Kein Charakter aktiv.' } end local current = relation(player.characterId) if not current.contactUnlocked then return { ok = false, error = 'Dieser Kontakt ist noch nicht freigeschaltet.' } end local location = exports.devx_housing:GetPlayerHousingLocation(source) if not location or location.type ~= 'unit' then return { ok = false, error = 'Du musst dich in deinem Apartment befinden.' } end if tonumber(location.ownerCharacterId) ~= tonumber(player.characterId) then return { ok = false, error = 'Du kannst Nikki nur in dein eigenes Apartment einladen.' } end local spawn = location.interior and location.interior.companionSpawn if not spawn then return { ok = false, error = 'Für dieses Apartment ist kein Treffpunkt eingerichtet.' } end TriggerClientEvent('devx_stripclub:client:spawnCompanion', source, { name = DevXStripclub.Nikki.name, model = DevXStripclub.Nikki.model, spawn = spawn }) return { ok = true, message = 'Nikki ist auf dem Weg zu deinem Apartment.' } end exports('InviteCompanion', inviteCompanion) DevXRegisterCallback('stripclub:inviteCompanion', function(source) return inviteCompanion(source) end) RegisterCommand('resetnikki', 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_club_relations WHERE character_id = ? AND relation_key = ?', { player.characterId, DevXStripclub.Nikki.key }) MySQL.query.await("DELETE FROM devx_phone_contacts WHERE character_id = ? AND contact_type = 'npc_companion'", { player.characterId }) TriggerClientEvent('devx_phone:client:refresh', target) TriggerClientEvent('devx_housing:client:notify', target, 'Nikki-Beziehung wurde zurückgesetzt.') end, false) AddEventHandler('playerDropped', function() actionCooldowns[source] = nil end)