HeroHunt App: Configuration
Every option in config/config.lua, from the phone app listing and discovery map to spawns, rarities, Candy and tasks, plus the hero and task catalogs.
Tip
After editing, restart only the resource with ensure hype-herohunt-app; a full server restart is not needed.
Active spawns are saved in the database and come back after the restart.
The script has three config files:
| File | Content |
|---|---|
config/config.lua | Every option on this page |
config/heroes.lua | The hero catalog, see Heroes |
config/tasks.lua | Daily, weekly and general tasks, see Tasks catalog |
Each file returns one table; the sections below follow the order of config.lua. Durations use the unit in their
name (...Seconds, ...Ms) and distances are in meters. Invalid hero and task entries are skipped on start and
printed to the server console.
General
Debug = false,
Framework = "auto",
Ownership = "character",Prop
Type
With "character", a collection belongs to the citizenid on QBox and QBCore or the identifier on ESX. With
"account", it belongs to the player's Rockstar license.
Pick Ownership before going live
Collections are stored under the owner key. Switching between "character" and "account" later does not move
existing collections; players start with an empty one.
App
How the app appears in lb-phone and its App Store.
App = {
identifier = "hype-herohunt",
name = "HeroHunt",
description = "Explore the city, discover heroes and complete your collection.",
developer = "Hype Studios",
defaultApp = false,
price = 0,
size = 52400,
},Prop
Type
The app icon is web/build/icon.svg. The app follows the light or dark theme set in the phone settings.
UI
Prop
Type
Discovery
The discovery area is the circle around the player on the map. Heroes inside it can be caught.
Discovery = {
Radius = 100.0,
PreviewRadius = 150.0,
SyncIntervalMs = 3000,
PositionIntervalMs = 500,
PositionMinMove = 1.0,
Area = {
fill = "#7c5cff",
fillOpacity = 0.12,
border = "#7c5cff",
borderOpacity = 1.0,
borderWidth = 2,
},
},Prop
Type
Spawns and positions are only streamed while the app is open on screen. Closing the phone stops the updates.
Spawn
Every SpawnIntervalSeconds, the server looks at each player in RoutingBucket. If fewer than TargetPerPlayer
heroes are within SpawnRadius, it picks new points between MinPlayerDistance and SpawnRadius from the player (at
most 4 per player per round), skips points that break the distance, density or blacklist rules and sends the rest to
that player's game for a ground check. Valid points become spawns that every player sees.
Spawn = {
Enabled = true,
MaxActiveHeroes = 300,
SpawnIntervalSeconds = 30,
SpawnsPerTick = 12,
DespawnAfterSeconds = 1800,
TargetPerPlayer = 6,
SpawnRadius = 180.0,
MinPlayerDistance = 20.0,
MinSpawnDistance = 15.0,
AreaRadius = 150.0,
MaxPerArea = 10,
-- ...
},Prop
Type
The ground check rejects points without ground or navmesh, in water, inside interiors, within 3 meters of a vehicle and points where the safe position is far from the ground height.
Blacklist
Circles where heroes never spawn. Each entry has a label (only for you), an x, y center and a radius.
Blacklist = {
{ label = "Fort Zancudo", x = -2200.0, y = 3100.0, radius = 900.0 },
{ label = "Bolingbroke", x = 1690.0, y = 2600.0, radius = 220.0 },
{ label = "Mission Row PD", x = 440.0, y = -982.0, radius = 45.0 },
{ label = "Pillbox Hospital", x = 307.0, y = -595.0, radius = 45.0 },
},Regions
Named circles used by the region filter of tasks. Every spawn stores the first region (in list order) whose circle
contains it, and the label appears in task titles such as Catch heroes in Downtown (3).
Regions = {
{ id = "vinewood", label = "Vinewood", x = 300.0, y = 200.0, radius = 650.0 },
{ id = "downtown", label = "Downtown", x = 150.0, y = -850.0, radius = 700.0 },
{ id = "vespucci", label = "Vespucci", x = -1300.0, y = -1200.0, radius = 650.0 },
{ id = "sandy", label = "Sandy Shores", x = 1850.0, y = 3700.0, radius = 600.0 },
{ id = "paleto", label = "Paleto Bay", x = -250.0, y = 6300.0, radius = 550.0 },
},Prop
Type
Capture
Prop
Type
Rarities
The key of each entry is the rarity id used in heroes.lua and in task filters.
Rarities = {
common = { order = 1, color = "#94a3b8", spawnWeight = 60, catchChance = 1.0, captureCandy = 1 },
uncommon = { order = 2, color = "#22c55e", spawnWeight = 25, catchChance = 1.0, captureCandy = 2 },
rare = { order = 3, color = "#3b82f6", spawnWeight = 10, catchChance = 1.0, captureCandy = 3 },
epic = { order = 4, color = "#a855f7", spawnWeight = 4, catchChance = 1.0, captureCandy = 5 },
legendary = { order = 5, color = "#f59e0b", spawnWeight = 1, catchChance = 1.0, captureCandy = 10 },
},Prop
Type
The spawn weight applies per hero, so a rarity with more heroes spawns more often overall. With the default catalog (20 common, 14 uncommon, 9 rare, 5 epic and 2 legendary heroes), the odds of each spawn are:
| Rarity | Heroes | Weight | Odds per spawn |
|---|---|---|---|
| Common | 20 | 60 | ~72.2% |
| Uncommon | 14 | 25 | ~21.1% |
| Rare | 9 | 10 | ~5.4% |
| Epic | 5 | 4 | ~1.2% |
| Legendary | 2 | 1 | ~0.1% |
To add a rarity, add an entry here and a label for it under rarity in every locale file.
Candy
Prop
Type
Converting gives the hero's candyValue from heroes.lua for each copy. Catching gives the rarity's captureCandy.
Tasks
Prop
Type
Security
Prop
Type
The catch cooldown is Capture.CooldownMs. Conversions and task rewards also carry a unique request id, so the same
request is never paid twice.
History
Prop
Type
Heroes
The hero catalog is config/heroes.lua. It ships with 50 heroes. Add an entry to add a hero:
return {
{ id = "hero_001", name = "Volty", rarity = "common", candyValue = 1, image = "heroes/hero_001.png", enabled = true },
-- ...
{ id = "hero_051", name = "Nova", rarity = "epic", candyValue = 8, image = "heroes/hero_051.png",
silhouette = "heroes/hero_051_s.png", catchChance = 0.6 },
}Prop
Type
- The number on each card is the hero's position in the file. Add new heroes at the end to keep the numbers.
- The 50 included heroes have their portraits and silhouettes in
web/build/heroes/. For a new hero, put both images there (or use https URLs) and setsilhouetteyourself. If an image cannot be loaded, the app shows a neutral robot icon instead. - To retire a hero, set
enabled = falseinstead of deleting the line. Players who own copies still see it, but it no longer counts toward the collection total. - An entry with an invalid or duplicate
idor an unknownrarityis skipped, for example: Config: heroes[51].rarity = mythic is invalid, skipped.
Tasks catalog
Tasks are defined in config/tasks.lua. Progress is only tracked from real server events: catches and conversions.
return {
-- Daily pool
{ id = "d_capture_5", period = "daily", type = "capture", target = 5, reward = { type = "candy", amount = 8 } },
{ id = "d_uncommon_2", period = "daily", type = "capture", target = 2, filter = { minRarity = "uncommon" }, reward = { type = "candy", amount = 10 } },
{ id = "d_downtown_3", period = "daily", type = "capture", target = 3, filter = { region = "downtown" }, reward = { type = "candy", amount = 10 } },
-- Weekly pool
{ id = "w_epic_1", period = "weekly", type = "capture", target = 1, filter = { minRarity = "epic" }, reward = { type = "candy", amount = 45 } },
-- General progress
{ id = "g_first_capture", period = "general", type = "capture", target = 1, title = "tasks.custom.first_capture", reward = { type = "candy", amount = 5 } },
-- ...
}The default file has 9 daily, 7 weekly and 10 general tasks.
Prop
Type
Periods
| Period | Behaviour |
|---|---|
daily | Resets every day at Tasks.ResetHour. Tasks.DailyCount tasks are picked from the daily pool |
weekly | Resets every week on Tasks.WeekStartsOn. Tasks.WeeklyCount tasks are picked from the weekly pool |
general | Permanent milestones, always active and completed once |
The pick is based on the date, so every player gets the same daily and weekly tasks and they change at each reset. Editing a pool and restarting the resource can change the current pick.
Types
| Type | Progress | Filters |
|---|---|---|
capture | Each catch | minRarity, rarity, region, hero |
discover | Each hero caught for the first time during the period | region |
discovered_total | Total number of different heroes discovered, not period based | - |
capture_rarities | Number of different rarities caught during the period | - |
convert | Each copy converted to Candy | - |
earn_candy | Candy earned from catches and conversions (task rewards do not count) | - |
Filters
Prop
Type
Custom titles
Without title, the title is generated from the type and filter, for example Catch 2 Uncommon or rarer heroes. To use
your own text, add a key under tasks.custom in every locale file and point title (or description) to it. The
task target is passed in as %s.
"tasks": {
"custom": {
"first_capture": "Catch your first hero",
"complete_collection": "Complete the collection",
"night_hunter": "Catch %s heroes at night"
}
}{ id = "g_night_hunter", period = "general", type = "capture", target = 20, title = "tasks.custom.night_hunter", reward = { type = "candy", amount = 25 } },Tasks are written to the hero_tasks table on every start. Tasks removed from the file are disabled there, and their
progress stays in the database.
Locales
The language comes from ox_lib, so it is set in server.cfg rather than in the config:
setr ox:locale "en"en and tr are included. If the code has no matching file, English is used. To add a language, copy
locales/en.json, translate it, save it with the language code as its name (e.g. de.json) and set ox:locale to
that code. The same file holds the app texts, rarity names, task titles, the task notification and the error
messages. Hero names come from heroes.lua and region names from Regions.