installation

This guide will walk you through installing hsJobQuest V2 on your FiveM server.

Prerequisites

Before installing, ensure you have the following:

Required Dependencies

Resource
Download Link
Notes

ox_lib

https://github.com/overextended/ox_lib

UI framework

oxmysql

https://github.com/overextended/oxmysql

Database driver

Framework Support

hsJobQuest V2 supports the following frameworks:

  • QBCore

  • QBOX

  • ESX

1

Step 1: Download & Extract

Download the latest version of hsJobQuest V2 and extract the folder to your resources directory:

resources/
  [jobs]/
    hsJobQuestV2/
      client/
      server/
      shared/
      data/
      fxmanifest.lua
      install.sql

You can place the resource in any folder, but [jobs] is recommended for organization.

2

Step 2: Database Setup

Import the SQL File

  1. Open your database management tool (HeidiSQL, phpMyAdmin, etc.)

  2. Select your FiveM database

  3. Import the install.sql file located in the resource folder

-- Create main tables
CREATE TABLE IF NOT EXISTS `hsjobquest_players` (
    `citizenid` VARCHAR(50) PRIMARY KEY,
    `name` VARCHAR(100) DEFAULT 'Unknown',
    `xp` INT DEFAULT 0,
    `rank` INT DEFAULT 1,
    `money` INT DEFAULT 0,
    `prestige` INT DEFAULT 0,
    `career_path` INT DEFAULT 0,
    `skill_points` INT DEFAULT 0,
    `total_deliveries` INT DEFAULT 0,
    `perfect_deliveries` INT DEFAULT 0,
    `total_earnings` BIGINT DEFAULT 0,
    `total_distance` FLOAT DEFAULT 0,
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `last_played` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `hsjobquest_skills` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `citizenid` VARCHAR(50) NOT NULL,
    `category` VARCHAR(50) NOT NULL,
    `skill_name` VARCHAR(50) NOT NULL,
    `level` INT DEFAULT 0,
    UNIQUE KEY `unique_skill` (`citizenid`, `category`, `skill_name`),
    FOREIGN KEY (`citizenid`) REFERENCES `hsjobquest_players`(`citizenid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `hsjobquest_factions` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `citizenid` VARCHAR(50) NOT NULL,
    `faction_id` VARCHAR(50) NOT NULL,
    `reputation` INT DEFAULT 0,
    `missions_completed` INT DEFAULT 0,
    UNIQUE KEY `unique_faction` (`citizenid`, `faction_id`),
    FOREIGN KEY (`citizenid`) REFERENCES `hsjobquest_players`(`citizenid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `hsjobquest_achievements` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `citizenid` VARCHAR(50) NOT NULL,
    `achievement_id` VARCHAR(100) NOT NULL,
    `unlocked_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `claimed` TINYINT(1) DEFAULT 0,
    UNIQUE KEY `unique_achievement` (`citizenid`, `achievement_id`),
    FOREIGN KEY (`citizenid`) REFERENCES `hsjobquest_players`(`citizenid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `hsjobquest_statistics` (
    `citizenid` VARCHAR(50) PRIMARY KEY,
    `fast_deliveries` INT DEFAULT 0,
    `session_deliveries` INT DEFAULT 0,
    `survived_robberies` INT DEFAULT 0,
    `bad_weather_deliveries` INT DEFAULT 0,
    `night_deliveries` INT DEFAULT 0,
    `coop_deliveries` INT DEFAULT 0,
    `bonus_objectives_completed` INT DEFAULT 0,
    `perfect_streak_current` INT DEFAULT 0,
    `perfect_streak_best` INT DEFAULT 0,
    `unique_job_types_completed` TEXT,
    `stealth_perfect` INT DEFAULT 0,
    FOREIGN KEY (`citizenid`) REFERENCES `hsjobquest_players`(`citizenid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `hsjobquest_heat` (
    `citizenid` VARCHAR(50) PRIMARY KEY,
    `heat_level` INT DEFAULT 0,
    `last_decay` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (`citizenid`) REFERENCES `hsjobquest_players`(`citizenid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `hsjobquest_mission_history` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `citizenid` VARCHAR(50) NOT NULL,
    `mission_type` VARCHAR(50) NOT NULL,
    `job_name` VARCHAR(100) NOT NULL,
    `faction_id` VARCHAR(50),
    `xp_earned` INT DEFAULT 0,
    `money_earned` INT DEFAULT 0,
    `completion_time` INT DEFAULT 0,
    `damage_taken` FLOAT DEFAULT 0,
    `bonus_completed` TINYINT(1) DEFAULT 0,
    `was_coop` TINYINT(1) DEFAULT 0,
    `completed_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (`citizenid`) REFERENCES `hsjobquest_players`(`citizenid`) ON DELETE CASCADE,
    INDEX `idx_citizen_missions` (`citizenid`, `completed_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `hsjobquest_coop_groups` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `leader_citizenid` VARCHAR(50) NOT NULL,
    `mission_id` VARCHAR(100),
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `expires_at` TIMESTAMP NULL,
    FOREIGN KEY (`leader_citizenid`) REFERENCES `hsjobquest_players`(`citizenid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `hsjobquest_coop_members` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `group_id` INT NOT NULL,
    `citizenid` VARCHAR(50) NOT NULL,
    `joined_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY `unique_member_group` (`group_id`, `citizenid`),
    FOREIGN KEY (`group_id`) REFERENCES `hsjobquest_coop_groups`(`id`) ON DELETE CASCADE,
    FOREIGN KEY (`citizenid`) REFERENCES `hsjobquest_players`(`citizenid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `hsjobquest_economy` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `job_type` VARCHAR(50) UNIQUE NOT NULL,
    `current_multiplier` FLOAT DEFAULT 1.0,
    `demand_level` INT DEFAULT 50,
    `last_updated` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO `hsjobquest_economy` (`job_type`, `current_multiplier`, `demand_level`) VALUES
('food_delivery', 1.0, 50),
('parcel_delivery', 1.0, 50),
('medical_supply_delivery', 1.0, 50),
('luxury_goods_delivery', 1.0, 50),
('construction_material_delivery', 1.0, 50),
('mail_delivery', 1.0, 50),
('alcohol_delivery', 1.0, 50),
('pharmaceutical_delivery', 1.0, 50),
('grocery_delivery', 1.0, 50),
('auto_parts_delivery', 1.0, 50)
ON DUPLICATE KEY UPDATE `job_type` = `job_type`;

CREATE TABLE IF NOT EXISTS `hsjobquest_transactions` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `citizenid` VARCHAR(50) NOT NULL,
    `type` ENUM('deposit', 'withdrawal') NOT NULL,
    `amount` INT NOT NULL,
    `description` VARCHAR(255),
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (`citizenid`) REFERENCES `hsjobquest_players`(`citizenid`) ON DELETE CASCADE,
    INDEX `idx_citizen_trans` (`citizenid`, `created_at` DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ===== Index creation with checks (safe) =====
-- We'll create indexes only if they don't exist already.
DELIMITER $$
CREATE PROCEDURE create_index_if_missing()
BEGIN
  DECLARE idxcount INT DEFAULT 0;

  -- idx_player_xp
  SELECT COUNT(*) INTO idxcount
   FROM information_schema.statistics
   WHERE table_schema = DATABASE() AND table_name = 'hsjobquest_players' AND index_name = 'idx_player_xp';
  IF idxcount = 0 THEN
    ALTER TABLE `hsjobquest_players` ADD INDEX `idx_player_xp` (`xp`);
  END IF;

  -- idx_player_prestige
  SELECT COUNT(*) INTO idxcount
   FROM information_schema.statistics
   WHERE table_schema = DATABASE() AND table_name = 'hsjobquest_players' AND index_name = 'idx_player_prestige';
  IF idxcount = 0 THEN
    ALTER TABLE `hsjobquest_players` ADD INDEX `idx_player_prestige` (`prestige`, `xp`);
  END IF;

  -- idx_faction_rep
  SELECT COUNT(*) INTO idxcount
   FROM information_schema.statistics
   WHERE table_schema = DATABASE() AND table_name = 'hsjobquest_factions' AND index_name = 'idx_faction_rep';
  IF idxcount = 0 THEN
    ALTER TABLE `hsjobquest_factions` ADD INDEX `idx_faction_rep` (`faction_id`, `reputation`);
  END IF;

  -- idx_achievement_unlock
  SELECT COUNT(*) INTO idxcount
   FROM information_schema.statistics
   WHERE table_schema = DATABASE() AND table_name = 'hsjobquest_achievements' AND index_name = 'idx_achievement_unlock';
  IF idxcount = 0 THEN
    ALTER TABLE `hsjobquest_achievements` ADD INDEX `idx_achievement_unlock` (`unlocked_at`);
  END IF;
END$$
DELIMITER ;

CALL create_index_if_missing();
DROP PROCEDURE IF EXISTS create_index_if_missing;

-- ===== Safe migration from V1 to V2: only run if legacy table exists =====
DELIMITER $$
CREATE PROCEDURE migrate_playerData_if_exists()
BEGIN
  DECLARE tbl_count INT DEFAULT 0;
  SELECT COUNT(*) INTO tbl_count
    FROM information_schema.tables
   WHERE table_schema = DATABASE()
     AND table_name = 'playerData';

  IF tbl_count > 0 THEN
    INSERT INTO `hsjobquest_players` (`citizenid`, `name`, `xp`, `rank`, `money`)
    SELECT `citizenid`, `name`, `xp`, `rank`, `money`
    FROM `playerData`
    ON DUPLICATE KEY UPDATE `name` = VALUES(`name`);
  END IF;
END$$
DELIMITER ;

CALL migrate_playerData_if_exists();
DROP PROCEDURE IF EXISTS migrate_playerData_if_exists;

DROP TRIGGER IF EXISTS `init_player_stats`;
DROP TRIGGER IF EXISTS `update_session_deliveries`;
DELIMITER $$
CREATE TRIGGER `init_player_stats`
AFTER INSERT ON `hsjobquest_players`
FOR EACH ROW
BEGIN
    INSERT INTO `hsjobquest_statistics` (`citizenid`) VALUES (NEW.citizenid);
END$$

CREATE TRIGGER `update_session_deliveries`
AFTER INSERT ON `hsjobquest_mission_history`
FOR EACH ROW
BEGIN
    UPDATE `hsjobquest_statistics`
    SET `session_deliveries` = `session_deliveries` + 1
    WHERE `citizenid` = NEW.citizenid;

    UPDATE `hsjobquest_players`
    SET `total_deliveries` = `total_deliveries` + 1,
        `total_earnings` = `total_earnings` + NEW.money_earned
    WHERE `citizenid` = NEW.citizenid;
END$$
DELIMITER ;

-- Final checks/status
SELECT 'hsJobQuest V2 Database Installation Complete!' AS Status;
SELECT COUNT(*) AS 'Total Players' FROM hsjobquest_players;
3

Step 3: Configure server.cfg

Add the following to your server.cfg:

# Dependencies (must be before hsJobQuestV2)
ensure ox_lib
ensure oxmysql

# Your framework
ensure qb-core  # or es_extended for ESX

# hsJobQuest V2
ensure hsJobQuestV2
4

Step 4: Basic Configuration

Open shared/config.lua and configure your framework:

-- Framework Selection
Config.Framework = 'QBCORE'  -- Options: 'QBCORE', 'QBOX', 'ESX'

-- Target System
Config.TargetSystem = 'qb-target'  -- Options: 'qb-target', 'ox_target'

-- Inventory System
Config.InventorySystem = 'qb-inventory'  -- Options: 'qb-inventory', 'ox_inventory', etc.

-- Notification System
Config.NotifySystem = 'ox_lib'  -- Options: 'ox_lib', 'qb-core', 'esx'
5

Step 5: Verify Installation

  1. Start your server

  2. Join and go to the job hub location:

    • Default: vec4(154.17, -3075.09, 5.9, 86.59) (near the docks)

  3. Interact with the postal worker NPC

  4. You should see the job selection menu

Check Console

If everything is working, you'll see:

[hsJobQuest] Initializing hsJobQuest V2...
[hsJobQuest] Database initialized
[hsJobQuest] Economy system initialized
[hsJobQuest] Mission generator initialized
[hsJobQuest] hsJobQuest V2 initialized successfully!

Optional: Enable Debug Mode

For troubleshooting, enable debug mode in shared/config.lua:

Config.EnableDebug = true

This will print detailed information to your server console.

Next Steps

Quick Start Guide

Learn how to use the system

Configuration

Customize all settings

Last updated