QBESX
Agency-LifeinvaderV3 Konfiguration
Modernes Werbungssystem für FiveM mit Liquid Glass UI, gestaffelter Preisgestaltung, Anzeigenkategorien.
v3.0.0Bezahlt4 Seiten

01Konfiguration
Schnellinstallation · config.lua · Agency-LifeinvaderV3
--[[ ═══════════════════════════════════════════
AGENCY LIFEINVADER V3 - CONFIGURATION
by Agency Scripts
═══════════════════════════════════════════ ]]
Config = {}
-- Framework auto-detection (ESX, QBCore, or standalone)
Config.Framework = 'auto'
-- Enable Database Integration (MySQL/MariaDB via oxmysql)
-- If false: Ads are not saved through restarts, recent feed is empty, settings are reset, and
-- admin panel cannot fetch or edit previous ads. Good for temporary servers.
Config.UseDatabase = true
-- Debug mode (set to true for detailed console/chat messages)
Config.Debug = false
-- Localization
-- Available: en, de, fr, es, it, pt, pl, nl, tr, ru, cs, sv, da, no, ro
Config.Locale = 'en'
Config.Localization = {
fallbackLocale = 'en', -- Missing translation keys fall back to English automatically
useLocaleNotifications = true
}
-- Use Agency Notify system
Config.UseAgencyNotify = true
-- ═══ UI BRANDING ══════════════════════════════
-- Customize the name and logo shown in all UI panels.
-- Change "BrandName" to match your server name (e.g. "WEAZEL NEWS", "LS ADVERTISER", etc.)
-- "BrandSubtitle" is the small text shown below the brand name in the main menu header.
-- "LogoFile" is the path to your logo image.
-- Option 1: Just the filename if it's in the web/ folder (e.g. "logo.png")
-- Option 2: Full nui:// path (e.g. "nui://Agency-LifeinvaderV3/web/logo.png")
-- Option 3: "" (empty string) to use the default SVG icon instead of an image
-- The logo should be a white/light-colored PNG with transparent background for best results.
-- IMPORTANT: Make sure your logo file is listed in the "files" block of fxmanifest.lua!
Config.UI = {
BrandName = 'LIFEINVADER', -- Main title shown in the menu header (uppercase recommended)
BrandSubtitle = 'Ad Agency v3', -- Subtitle shown below the brand name
LogoFile = 'nui://Agency-LifeinvaderV3/web/logo.png', -- Logo path ("" = use default icon)
}
-- ═══ COMMANDS ═══════════════════════════════
-- All commands are configurable. Prefix "al" = Agency Lifeinvader
Config.Commands = {
main = 'alad', -- Open ad creation menu
recent = 'alfeed', -- Open recent ads feed
test = 'altest', -- Show a test notification
admin = 'aladmin' -- Open admin panel
}
-- ═══ KEY BINDINGS ═══════════════════════════
-- Key to open the main menu (F6 = 167)
Config.OpenKey = 167
-- Key to open recent ads menu (F5 = 166)
Config.RecentKey = 166
-- ═══ ADVERTISEMENT SETTINGS ═════════════════
Config.Price = 5000 -- Base price per ad
Config.MaxReps = 5 -- Maximum repetitions
Config.MinInterval = 30 -- Minimum interval in seconds
Config.MaxInterval = 300 -- Maximum interval in seconds
Config.MaxLength = 300 -- Maximum message length
Config.DisplayDuration = 8000 -- Default how long notifications show (ms)
Config.MaxDisplayDuration = 60 -- Max display duration players can choose (seconds)
Config.CostPerSecond = 500 -- Additional cost per second of display duration
Config.TickerInterval = 15 -- How often the server checks for ads (seconds)
Config.InitialDelay = 0 -- Default delay before first send
Config.NotificationColor = '#3b82f6' -- Global accent color for all notifications
-- ═══ AD CATEGORIES ═════════════════════════
Config.Categories = {
{ id = 'jobs', label = 'Jobs', emoji = '💼' },
{ id = 'vehicles', label = 'Vehicles', emoji = '🚗' },
{ id = 'realestate', label = 'Real Estate', emoji = '🏠' },
{ id = 'services', label = 'Services', emoji = '🔧' },
{ id = 'events', label = 'Events', emoji = '🎉' },
{ id = 'other', label = 'Other', emoji = '📌' }
}
-- Category-specific behavior (language-neutral, reused by UI + server logic)
Config.CategoryRules = {
jobs = {
priceMultiplier = 0.9, -- Price multiplier (0.9 = 10% cheaper)
priorityBonus = 2, -- Queue priority boost
requireApproval = false, -- Require admin approval for this category
maxRepetitions = 4, -- Max repetitions for this category
minInterval = 30 -- Min interval in seconds for this category
},
vehicles = {
priceMultiplier = 1.15,
priorityBonus = 1,
requireApproval = false,
maxRepetitions = 5,
minInterval = 45
},
realestate = {
priceMultiplier = 1.25,
priorityBonus = 1,
requireApproval = true,
maxRepetitions = 4,
minInterval = 60
},
services = {
priceMultiplier = 1.0,
priorityBonus = 2,
requireApproval = false,
maxRepetitions = 5,
minInterval = 30
},
events = {
priceMultiplier = 1.1,
priorityBonus = 3,
requireApproval = false,
maxRepetitions = 5,
minInterval = 30
},
other = {
priceMultiplier = 1.0,
priorityBonus = 0,
requireApproval = false,
maxRepetitions = 3,
minInterval = 45
}
}
-- ═══ AGENCY EMPLOYEE PANEL ═════════════════
-- Configure a job/company whose employees can access the Agency panel.
-- Employees can manage ads and earn commission from each ad placed through them.
Config.Agency = {
enabled = true,
command = 'alagency', -- Command to open the agency panel
-- Job/company requirements (framework-dependent)
job = 'lifeinvader', -- Job name (ESX/QBCore job)
minGrade = 0, -- Minimum grade/rank required
-- Commission settings
commissionPercent = 10, -- Percentage of ad revenue paid to the employee
commissionType = 'cash', -- 'cash' or 'bank'
-- Agency account (shared company money pot)
useCompanyAccount = true, -- If true, ad revenue goes to a shared account
companyAccountName = 'lifeinvader_agency', -- Account identifier for company funds
}
-- ═══ ANONYMOUS ADS ═════════════════════════
Config.AllowAnonymous = true -- Allow players to post anonymously
-- ═══ BOOST SYSTEM ══════════════════════════
Config.BoostMultiplier = 2 -- Price multiplier for boosted ads (2x)
Config.BoostEnabled = true -- Enable the boost feature
-- ═══ ADMIN SETTINGS ═════════════════════════
Config.Admin = {
-- Who can use the admin panel?
-- 'ace' = uses ace permissions (group.admin)
-- 'identifier' = uses a list of identifiers
-- 'framework' = uses framework admin detection
permissionType = 'ace',
-- If permissionType = 'identifier', list allowed identifiers
allowedIdentifiers = {
-- 'license:xxxx',
-- 'steam:xxxx',
},
-- Require admin approval before ads go live?
requireApproval = false,
-- Maximum ads shown in admin panel
maxAdsInPanel = 50
}
-- ═══ V3 FEATURE FLAGS ════════════════════════
Config.Features = {
agencyAI = true, -- Enable heuristic AI moderation
playerLimits = true, -- Enable cooldown/limit system
adminLogs = true, -- Show activity logs in admin panel
adminBulkActions = true, -- Allow bulk approve/reject/delete in admin panel
adminActivityFeed = true, -- Show recent activity feed in admin panel
adminLogDetails = true, -- Show detailed log entries
adminSearch = true, -- Enable search bar in admin panel
adminStats = true, -- Show statistics in admin panel
scheduleOverview = true, -- Show schedule overview in history tab
queueInsights = true, -- Show queue insights in history tab
pendingEditing = true, -- Allow editing pending (under review) ads
categoryRules = true, -- Enable category rules system
scheduling = true, -- Enable ad scheduling
draftTitleEditing = true, -- Allow naming drafts
drafts = true -- Enable draft system
}
-- ═══ PLAYER LIMITS / FLOW CONTROL ════════════
Config.PlayerLimits = {
cooldownSeconds = 45, -- Time between ad submissions
maxPendingAds = 3, -- Max waiting-for-review ads per player
maxActiveAds = 4, -- Max currently running approved ads per player
adminBypass = true -- Allow admins to bypass these limits
}
-- ═══ SCHEDULING ══════════════════════════════
Config.Scheduling = {
enabled = true,
maxMinutesAhead = 1440 -- How far ahead players can schedule (1440 = 24 hours)
}
-- ═══ AGENCY AI (HEURISTIC, NO EXTERNAL AI) ═══
-- Built-in heuristic system that flags suspicious ads for manual review.
-- This does NOT use any external AI API — it runs locally on the server.
Config.AgencyAI = {
enabled = true,
autoNormalizeWhitespace = true, -- Auto-clean excessive whitespace
forceApprovalOnFlag = true, -- Force admin approval when AI flags an ad
scoreThreshold = 4, -- Penalty score threshold to trigger a flag
duplicateWindowMinutes = 30, -- Time window to check for duplicate ads
duplicateThreshold = 1, -- How many duplicates before flagging
externalLinkPatterns = { -- Patterns that count as external links
'http://',
'https://',
'www.',
'discord.gg',
'bit.ly'
},
suspiciousTerms = { -- Terms that trigger a penalty
'@everyone',
'free money',
'hack',
'exploit',
'cheap cash',
'real money'
},
categoryPriority = { -- Queue priority bonus per category
events = 3,
jobs = 2,
services = 2,
vehicles = 1,
realestate = 1,
other = 0
},
boostedBonus = 8, -- Extra priority score for boosted ads
penalties = { -- Penalty scores per violation type
duplicate_message = 3,
aggressive_frequency = 2,
excessive_caps = 1,
excessive_symbols = 1,
external_link = 2,
contact_heavy = 1,
suspicious_term = 2,
long_copy = 1
}
}
-- ═══ AI TEXT GENERATION ═════════════════════
-- Generate professional ad copy using AI. Players can click "Generate" in the ad form.
-- API key can also be set via server.cfg: set lifeinvader_ai_key "your-key"
Config.AIGeneration = {
enabled = true,
-- Provider: 'groq', 'openai', 'perplexity', 'claude'
provider = 'groq',
-- API keys per provider (leave empty to use server convar "lifeinvader_ai_key")
apiKeys = {
groq = '',
openai = '',
perplexity = '',
claude = ''
},
-- Recommended models per provider
models = {
groq = '',
openai = 'gpt-4o-mini',
perplexity = '',
claude = ''
},
-- API endpoints per provider
endpoints = {
groq = 'https://api.groq.com/openai/v1/chat/completions',
openai = 'https://api.openai.com/v1/chat/completions',
perplexity = 'https://api.perplexity.ai/chat/completions',
claude = 'https://api.anthropic.com/v1/messages'
},
-- Generation settings
maxTokens = 200,
temperature = 0.8,
-- System prompt for generating ad text
systemPrompt = 'You are an advertising copywriter for Lifeinvader (a social media platform in GTA/FiveM). Write short, catchy advertisement texts in the language the user writes in. Keep it under 250 characters. Be creative, professional and persuasive. Include a catchy title line followed by the ad body. Do NOT use hashtags or emojis. Respond ONLY with the ad text, nothing else.'
}
-- ═══ PLAYER SETTINGS (DEFAULTS) ═════════════
-- Default values for player-configurable settings.
-- Players can change these in their settings menu (gear icon).
Config.PlayerSettingsDefaults = {
notificationPosition = {x = 0, y = 50}, -- Default position (left side, vertically centered)
notificationEnabled = true, -- Can players disable ad notifications?
notificationScale = 1.0, -- Default UI scale (0.8 to 1.4)
uiTransparency = 0.55, -- Menu background opacity (0.1 to 1.0)
soundEnabled = true -- Play sounds on new ads?
}
-- ═══ SOUNDS ═════════════════════════════════
-- Native GTA sounds (soundName, soundSet)
Config.Sounds = {
openMenu = { name = "SELECT", set = "HUD_FRONTEND_DEFAULT_SOUNDSET" },
closeMenu = { name = "CANCEL", set = "HUD_FRONTEND_DEFAULT_SOUNDSET" },
submitAd = { name = "PURCHASE", set = "HUD_LIQUOR_STORE_SOUNDSET" },
newAd = { name = "TEXT_ARRIVE_TONE", set = "HUD_AWARDS" }
}
-- ═══ NOTIFICATION SETTINGS ══════════════════
Config.AdNotification = {
enabled = true,
position = {x = 0, y = 50}, -- Default position (left side, vertically centered)
duration = 6000, -- Display duration in milliseconds
maxWidth = 380, -- Max notification width in pixels
showAuthor = true -- Show who posted the ad
}
-- ═══ MONEY SETTINGS ═════════════════════════
Config.Money = {
type = "cash", -- "cash" or "bank"
removeOnSubmit = true, -- Set to false if you handle money elsewhere
agencyPay = true -- Allow payment via Agency-Pay (requires agency-phone)
}
-- ═══ NOTIFICATION MESSAGES ══════════════════
-- These are the server-side notification texts sent to players.
Config.Notification = {
useAgencyNotify = true,
successMessage = "Advertisement submitted successfully!",
errorMessage = "Not enough money to submit advertisement!",
pendingMessage = "Your ad has been submitted and is awaiting admin approval.",
adminPendingMessage = "A new advertisement is waiting for admin review.",
approvedMessage = "Your ad has been approved and will be broadcast soon!",
rejectedMessage = "Your ad has been rejected by an admin.",
aiPendingMessage = "Agency AI flagged your ad for review before it can go live.",
cooldownMessage = "Please wait %s seconds before submitting another advertisement.",
pendingLimitMessage = "You already have %s advertisements waiting for review.",
activeLimitMessage = "You already have %s active advertisements running."
}
02SQL Datei
Importiere diese SQL-Datei in deine Datenbank bevor du die Ressource startest:
-- ═══════════════════════════════════════════
-- AGENCY LIFEINVADER V3 - DATABASE SETUP
-- by Agency Scripts
-- ═══════════════════════════════════════════
CREATE TABLE IF NOT EXISTS `lifeinvader_ads` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`identifier` VARCHAR(100) NOT NULL,
`author_name` VARCHAR(100) DEFAULT 'Anonymous',
`message` TEXT NOT NULL,
`category` VARCHAR(50) DEFAULT 'other',
`anonymous` TINYINT DEFAULT 0,
`boosted` TINYINT DEFAULT 0,
`repetitions_total` INT DEFAULT 1,
`repetitions_left` INT DEFAULT 1,
`interval_seconds` INT DEFAULT 60,
`display_duration` INT DEFAULT 8,
`status` ENUM('pending','active','completed','rejected') DEFAULT 'active',
`approved` TINYINT DEFAULT 1,
`approved_by` VARCHAR(100) DEFAULT NULL,
`rejected_reason` TEXT DEFAULT NULL,
`next_send_time` DATETIME DEFAULT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_identifier` (`identifier`),
INDEX `idx_status` (`status`),
INDEX `idx_approved` (`approved`),
INDEX `idx_category` (`category`),
INDEX `idx_boosted` (`boosted`),
INDEX `idx_next_send` (`next_send_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `lifeinvader_player_settings` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`identifier` VARCHAR(100) NOT NULL UNIQUE,
`notification_position` VARCHAR(20) DEFAULT '0,50',
`notification_enabled` TINYINT DEFAULT 1,
`notification_scale` FLOAT DEFAULT 1.0,
`ui_transparency` FLOAT DEFAULT 0.55,
`accent_color` VARCHAR(20) DEFAULT '#3b82f6',
`sound_enabled` TINYINT DEFAULT 1,
`admin_panel_scale` FLOAT DEFAULT 1.0,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_identifier` (`identifier`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `lifeinvader_ad_drafts` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`identifier` VARCHAR(100) NOT NULL,
`title` VARCHAR(120) DEFAULT NULL,
`message` TEXT NOT NULL,
`category` VARCHAR(50) DEFAULT 'other',
`anonymous` TINYINT DEFAULT 0,
`boosted` TINYINT DEFAULT 0,
`repetitions_total` INT DEFAULT 1,
`interval_seconds` INT DEFAULT 60,
`display_duration` INT DEFAULT 8,
`delay` INT DEFAULT 0,
`scheduled_for` DATETIME DEFAULT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_identifier` (`identifier`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `lifeinvader_admin_logs` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`admin_identifier` VARCHAR(100) DEFAULT NULL,
`admin_name` VARCHAR(100) DEFAULT NULL,
`action` VARCHAR(64) NOT NULL,
`ad_id` INT DEFAULT NULL,
`metadata` LONGTEXT DEFAULT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_action` (`action`),
INDEX `idx_ad_id` (`ad_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ═══ AUTO UPGRADE SYSTEM ═══
-- Safely adds missing columns without throwing errors on existing databases
DROP PROCEDURE IF EXISTS `UpgradeLifeinvaderColumns`;
DELIMITER //
CREATE PROCEDURE `UpgradeLifeinvaderColumns`()
BEGIN
DECLARE _dbName VARCHAR(255) DEFAULT DATABASE();
DECLARE cnt INT;
-- Add category column
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'category';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `category` VARCHAR(50) DEFAULT 'other' AFTER `message`;
END IF;
-- Add anonymous column
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'anonymous';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `anonymous` TINYINT DEFAULT 0 AFTER `category`;
END IF;
-- Add boosted column
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'boosted';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `boosted` TINYINT DEFAULT 0 AFTER `anonymous`;
END IF;
-- Add ui_transparency column to player settings
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_player_settings' AND COLUMN_NAME = 'ui_transparency';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_player_settings` ADD COLUMN `ui_transparency` FLOAT DEFAULT 0.55 AFTER `notification_scale`;
END IF;
-- Add accent_color column to player settings
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_player_settings' AND COLUMN_NAME = 'accent_color';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_player_settings` ADD COLUMN `accent_color` VARCHAR(20) DEFAULT '#3b82f6' AFTER `ui_transparency`;
END IF;
-- Add admin_panel_scale column to player settings
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_player_settings' AND COLUMN_NAME = 'admin_panel_scale';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_player_settings` ADD COLUMN `admin_panel_scale` FLOAT DEFAULT 1.0 AFTER `sound_enabled`;
END IF;
-- Add display_duration column to drafts
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ad_drafts' AND COLUMN_NAME = 'display_duration';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ad_drafts` ADD COLUMN `display_duration` INT DEFAULT 8 AFTER `interval_seconds`;
END IF;
-- Add delay column to drafts
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ad_drafts' AND COLUMN_NAME = 'delay';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ad_drafts` ADD COLUMN `delay` INT DEFAULT 0 AFTER `display_duration`;
END IF;
-- Add scheduling / review / AI fields
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'scheduled_for';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `scheduled_for` DATETIME DEFAULT NULL AFTER `next_send_time`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'review_requested_at';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `review_requested_at` DATETIME DEFAULT NULL AFTER `approved`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'reviewed_at';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `reviewed_at` DATETIME DEFAULT NULL AFTER `review_requested_at`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'review_decision';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `review_decision` VARCHAR(32) DEFAULT NULL AFTER `reviewed_at`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'queue_priority';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `queue_priority` INT DEFAULT 0 AFTER `review_decision`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'ai_score';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `ai_score` INT DEFAULT 0 AFTER `queue_priority`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'ai_flags';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `ai_flags` LONGTEXT DEFAULT NULL AFTER `ai_score`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'admin_notes';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `admin_notes` LONGTEXT DEFAULT NULL AFTER `ai_flags`;
END IF;
-- Add display_duration column
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'display_duration';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `display_duration` INT DEFAULT 8 AFTER `interval_seconds`;
END IF;
-- Indexes cannot be safely checked inline as easily in older MySQL,
-- but usually they don't break queries if missing. We only add them on fresh install via CREATE TABLE above.
END //
DELIMITER ;
CALL UpgradeLifeinvaderColumns();
DROP PROCEDURE IF EXISTS `UpgradeLifeinvaderColumns`;