Playtime levels, hourly gifts and level gates for FiveM, with a sunset-themed panel and webhook logs.
nCore Development · intelnpc.com · Resource: ncore_touchgrass · Version: see fxmanifest.lua
What it is#
Touch Grass rewards the players who stay. It counts how long each player has really been on the server, using the server's own online timer, and stops counting while they're AFK. Played hours become levels with named ranks, every hour pays out a gift, and other resources can lock things behind a level.
It runs on ESX, QBCore, QBox or standalone. It needs oxmysql and a server artifact with the GetPlayerTimeOnline native. Everything else is optional.
Features#
Playtime#
- Time comes from the server's own online timer. The client only reports whether the player is AFK.
- Each check only adds the new time, so a resource restart never counts time twice.
- The last minutes are saved when a player leaves and when the resource stops. Players are also saved on a timer (
ServerConfig.save_seconds, 300 by default). - Playtime is stored per license by default (
ServerConfig.identifier), with other identifiers as backups. It belongs to the account, so on a multi-character server all characters share one level, and gifts go to the character that is loaded when the hour is reached. - A player with no usable identifier is not tracked, and the console warns.
- If the server build has no online timer, tracking does not start and the console explains why.
AFK detection#
- The client checks movement, turning, camera and voice chat every 5 seconds.
- After the set idle time (
Config.afk.minutes, 10 by default) the player stops earning time and gets a message. Time resumes as soon as they're active again. - Time earned before AFK was detected still counts.
- The movement limits are in
Config.afk.
Levels and ranks#
- Default ladder: level 1 at 8 hours, then one level every 8 hours, up to level 8 at 64 hours.
- Add or remove rows and change the hours in
Config.levels. The max level follows the number of rows. - A row that doesn't need more hours than the one before it is refused in the console, and the ladder stops there.
- Stored levels are corrected when a player joins.
- Nine rank names ship in the locale files: Newcomer, Visitor, Local, Regular, Known Face, Resident, Veteran, Legend and Icon. Rename them in
locales/*.lua. A level without a name shows as "Level N".
Gifts#
- A gift after every played hour, plus bigger gifts on milestone hours. It is paid the moment the hour is reached, there is nothing to claim.
- Defaults: $250 cash and a water every hour. Milestones at hour 1 (2 water, 2 burgers), hour 8 ($5,000 bank and a phone), hour 24 ($15,000 bank and 3 lockpicks), hour 40 ($25,000 bank) and hour 64 ($50,000 bank). Rename the items to match your server.
- With
ServerConfig.gifts.stackon, a milestone hour pays both gifts. With it off, the milestone gift replaces the hourly one. ServerConfig.gifts.enabled = falseturns all gifts off.- A gift entry takes
money = { account = 'cash', amount = 250 }(cashorbank, plusblack_moneyon ESX andcryptoon QBCore and QBox),items = { { name = 'water', count = 1 } }with optionalmetadata,event = 'name'andcommand = '...'. - One gift can combine money, items, a server event and a console command. The event is triggered on the server with the player id, the hour and the identifier. The command gets
{id},{hour}and{identifier}, never the player name, so nobody can inject commands through their name. Use events and commands to give vehicles, VIP or XP from any other script. - Item names are checked first, so a typo gives a clear console error instead of endless retries.
- A gift that can't be given yet, for example because the inventory is full or the character isn't loaded, is saved as waiting and tried again. This survives a reconnect and a server restart. The player is told it's waiting and told again when it arrives.
- Gifts that can never work, like a misspelled item, are dropped, and the console names the problem.
- When a player jumps many hours at once, only the last few hourly gifts are paid (24 by default,
ServerConfig.gifts.max_catch_up), and the console says so. Only those hours fire the hour event. - Setting hours or level never pays the skipped hours, and removing time never takes gifts back.
- Gift text is built from the locale files. Add
gift_label_everyorgift_label_<hour>to write your own text for an hour.
Level gates#
- Three gates ship in
Config.gates:buy_vehicleat level 1,buy_weaponat level 3 andown_businessat level 5. Rename them or add your own. - The gates are names and levels. Your other resources ask with one line, on the server or on the client, and decide what to do with the answer. Nothing is enforced by this resource itself.
- On the server you can ask silently (
CanDo) or let the script tell the player why they were refused (RequireAction,RequireLevel). The client check is silent. - Gates show in the player's panel with lock and check icons.
- An action without a gate is always allowed.
- Other resources can add, replace or remove gates while the server runs, with a label in one or more languages. Gates added this way live in memory and are gone when
ncore_touchgrassrestarts. - Refusal messages name the level, for example "You need level 3 (Regular) for this."
Screens#
- The level-up banner is a full-width synthwave sunset banner across the top, with palms, twinkling stars and GTA's rank-up sound. It shows the new level, the rank and the total time played, with a countdown bar. Banners queue so they never overlap, and only level-ups are announced.
Config.ui.banner_secondssets how long it stays. - The gift banner is a smaller banner with a sound when a gift arrives, next to the normal message.
Config.ui.gift_banner = falsekeeps only the message. - The levels panel opens with
/levelsor PAGE UP. It shows the current level and rank, time played, a progress bar with a liveNext level incountdown, the next hourly gift and the next milestone gift, the level ladder and the gates. It closes with ESC, the X or the Close button, and fits small screens. Opened while the player is AFK, it shows an AFK badge and the countdown stands still. - The panel doesn't open over the pause menu or another nCore panel. Opened from chat, it waits a moment for chat to close first.
- The level-up and gift sounds can be any GTA menu sound. If a sound doesn't work, the console warns once and the banner plays silently.
- Fonts ship inside the resource, so nothing loads from the internet.
- Panel data requests are limited to one per second per player.
Admin commands#
/levelsadmincoversinfo,add,sethours,setlevel,reset,gift,levelup,event,gateanddiscord. The table below has the details.info,add,sethours,setlevelandresetalso work on offline players by identifier.- Access is the ACE
ncore_touchgrass.admin. The server console can always use it. A player without access is told the exact line to add. - Replies go to chat with your own prefix and color (
ServerConfig.admin_chat). - Preview tools: show the level-up banner, fire the level and hour events in preview mode, check a gate for a player, post a sample message to the webhook. Previews change nothing.
Webhook logging#
- Level-ups, gifts (what arrived and what failed) and every admin change are posted as embeds. Admin changes name the staff member or the console.
- Entries about a player show their name, server id and identifier.
ServerConfig.discord.mention(on by default) also mentions the player's account when the server has that identifier for them. - Each log type can be turned on or off. The webhook name, avatar and colors are yours to set.
- Keep the webhook out of the config with the
ncore_touchgrass:webhookconvar inserver.cfg. - Messages are queued about one per second and retried up to 3 times when the webhook is rate limited. The webhook must be https.
- Other resources can post their own embed through the same queue with
SendDiscord.
Frameworks, inventories and other resources#
- ESX, QBCore and QBox are detected automatically. Without one, it runs standalone. Detection runs again when a framework or inventory starts or stops.
- Standalone: levels, gates, the panel and the banners work. Money gifts need ncore_inventory, and item gifts need an inventory resource.
- ncore_inventory is preferred and also works on standalone servers. ox_inventory and qb-inventory are detected too, and without any of them items go through the framework's own item functions.
- Messages go through ncore_hud when it runs, otherwise through your framework, ox_lib or the standard GTA feed.
- Other resources can grant bonus minutes that count toward levels and gifts, remove minutes, or set playtime directly. ncore_npc_pro uses this to reward quests.
- Other resources can show the sunset banner with their own text, full or compact and with or without sound.
- Each player's level and AFK state, and the list of gates, are shared as state bags, so any script can read them without an export.
- Keep the folder name
ncore_touchgrass, because other nCore resources look it up by name.
Data#
- The database table is created on start.
sql/install.sqlis optional. - On first start, if the new table is empty, it can import names and minutes once from another playtime table. Levels are recalculated and no past gifts are paid. The source table needs the columns
identifier_kind,account_id,display_name,total_minutes,first_seenandlast_seen. SetServerConfig.migrate_fromtofalseto skip it. - Gifts and the webhook live in
config_server.lua, which only the server loads. Players never download it.
Languages and console#
- English and Hungarian. All texts are in the locale files. Missing texts fall back to English, and a new language is one copied file.
Config.locale = 'auto'reads your server's locale setting (the convarslr:locale,locale,ox:locale,esx:localeandtxAdmin-locale, in that order), and English when none of them has a language that ships.- The console prints a colored startup summary with the detected framework and inventory, every level change, and clear errors.
Config.debugadds detail. tools/fetch_refs.shdownloads GTA's sound name list so you can find valid sounds. The resource doesn't load it.
Commands and keys#
| Command or key | Who | What it does |
|---|---|---|
/levels |
Everyone | Opens the playtime panel (name set by Config.ui.command) |
| PAGE UP | Everyone | Opens the panel (rebindable in the game's key settings, Config.ui.key, '' for no key) |
| ESC | Everyone | Closes the panel |
/levelsadmin |
Admin / console | Lists the subcommands |
/levelsadmin info <id\|identifier> |
Admin / console | Level, played time, online status, gift hour and waiting gifts |
/levelsadmin add <id\|identifier> <minutes> [reason] |
Admin / console | Adds minutes, negative removes. Logged to the webhook |
/levelsadmin sethours <id\|identifier> <hours> |
Admin / console | Sets played hours. Skipped hours pay no gifts. Logged to the webhook |
/levelsadmin setlevel <id\|identifier> <0-max> |
Admin / console | Jumps to the start of a level. Skipped hours pay no gifts. Logged to the webhook |
/levelsadmin reset <id\|identifier> |
Admin / console | Clears time, bonus, gift hour and waiting gifts. Logged to the webhook |
/levelsadmin gift <id> <hour> |
Admin / console | Gives that hour's gift now, for real (online only). Logged to the webhook |
/levelsadmin levelup <id> [level] |
Admin / console | Previews the level-up banner. Nothing changes |
/levelsadmin event <id> levelup [level] |
Admin / console | Fires the level event in preview mode and shows the banner |
/levelsadmin event <id> hour [hour] |
Admin / console | Fires the hour event in preview mode |
/levelsadmin gate <id> <action> |
Admin / console | Checks whether the player passes a gate. An unknown action lists the known gates |
/levelsadmin discord |
Admin / console | Posts a sample message to the webhook |
For developers#
Server exports: GetLevel, GetPlaytime, GetPlayedHours, GetProgress, GetMaxLevel, GetLevelTitle, HasLevel, RequireLevel, CanDo, RequireAction, RegisterGate, RemoveGate, GetGates, GrantBonusMinutes, RemoveMinutes, SetPlaytime, ShowBanner, SendDiscord, IsAfk.
Client exports: GetLevel, HasLevel, CanDo, GetGates, IsAfk, OpenPanel, ClosePanel.
A few calls as examples:
-- server
local allowed, needed = exports.ncore_touchgrass:CanDo(source, 'buy_weapon') -- silent
if not exports.ncore_touchgrass:RequireAction(source, 'buy_weapon') then return end -- tells the player
exports.ncore_touchgrass:RegisterGate('rent_hangar', 4, { en = 'Rent a hangar' })
exports.ncore_touchgrass:GrantBonusMinutes(source, 30, 'quest')
exports.ncore_touchgrass:ShowBanner(source, { kicker = 'Race', title = 'First place', subtitle = 'Sandy Shores', variant = 'gift' })
exports.ncore_touchgrass:SendDiscord({ title = 'Race', description = 'Finished first', player = source })
-- client
local allowed, needed = exports.ncore_touchgrass:CanDo('buy_weapon')CanDo returns true and nil for an action without a gate. ShowBanner with variant = 'gift' shows the smaller banner, and sound = false plays it silent. SetPlaytime(src, seconds, give_gifts) pays the skipped hours only when give_gifts is true.
| Event | Side | When |
|---|---|---|
ncore_touchgrass:levelChanged(src, new, old, preview) |
Server and client | A level goes up or down |
ncore_touchgrass:hourReached(src, hour, preview) |
Server and client | A played hour is reached |
ncore_touchgrass:giftDelivered(src, hour, delivered, failed, preview) |
Server | A gift was handled |
preview is true only when fired by the admin preview or gift commands. Clients can't fire the server events. State bags: Player(src).state.lrLevel, Player(src).state.lrAfk, GlobalState.lrLevelGates.
Configuration#
config.lua: language, debug, level ladder, level gates, AFK rules, panel command and key, banner time, gift banner and sounds.config_server.lua: framework, identifier, tick and save timing, import table, gifts, admin command and chat style, webhook and logs.locales/en.lua,locales/hu.lua: all texts, including rank names, gate names, gift text, messages, admin replies and log text.
Settings you'll change first:
Config.levelsfor the hours each level needs.Config.gatesfor which action needs which level.ServerConfig.gifts.every_hourandServerConfig.gifts.hoursfor the gifts.ServerConfig.gifts.stackto pay milestone and hourly gifts together or not.Config.afk.minutesfor the idle time before AFK.ServerConfig.discord.webhook, or thencore_touchgrass:webhookconvar.ServerConfig.admin_acefor the admin permission.
Other settings:
Config.afk.enabled = falseturns AFK detection off, so all online time counts.ServerConfig.frameworkis'auto'. Set'esx','qb'or'qbox'to pick one when more than one is running.ServerConfig.tick_seconds(30, at least 10) is how often time is added and waiting gifts are tried again.ServerConfig.identifieris the identifier kind playtime is saved under. If a player has none of that kind,license,license2,fivem,discordandsteamare tried in that order.ServerConfig.admin_commandandConfig.ui.commandrename/levelsadminand/levels.
Troubleshooting#
| What you see | What to do |
|---|---|
The console says there is no GetPlayerTimeOnline native |
Update your FXServer artifact. Nothing is tracked until it has the native. |
| Admin commands reply "not ready" | The console names the cause, usually the missing native above or a database error while the table was prepared. |
| A gift item never arrives | The console names the item. An item your inventory doesn't know is dropped, so rename the default items (water, burger, phone, lockpick) in config_server.lua. |
| Money gifts fail on a standalone server | Without a framework only cash can be given, and only through ncore_inventory. Use an event or command gift for anything else. |
| "You are missing the ace" | Add add_ace group.admin ncore_touchgrass.admin allow to server.cfg. The reply prints the same line. |
| Nothing reaches the webhook | Run /levelsadmin discord and watch the console. The URL must start with https://, and the console prints the HTTP answer when a post fails. |
| The panel doesn't open | It stays closed over the pause menu and while another nCore panel has focus. From chat it waits up to 1.5 seconds for chat to close. |
| A crash lost some minutes | The timed save runs every ServerConfig.save_seconds (300 by default), so a crash can lose the time since the last one. A normal resource stop saves everything. |
Requirements#
- oxmysql.
- An FXServer build that has the
GetPlayerTimeOnlineserver native.
Optional, detected automatically:
- ESX, QBCore or QBox. Without one it runs standalone.
- ncore_inventory, ox_inventory or qb-inventory. Without one, items go through the framework's own item functions.
- ncore_hud for messages, with ox_lib as a fallback.
- ncore_npc_pro for quest bonus time.
- Any chat resource that uses the standard chat events, such as ncore_chat.
- A webhook URL for logging.
Setup steps are in INSTALLATION.md.
Support#
Support is on intelnpc.com and I reply within 72 hours. It covers installing, configuring and bugs in the script as shipped. Payments and refunds are handled by Tebex under their terms.
The escrow edition is protected with Cfx.re Asset Escrow. config.lua, config_server.lua, server/bridge.lua (the framework and inventory calls) and the locale files stay editable. The full source edition has every file open.
nCore Touch Grass is made by nCore Development. It is not approved, sponsored or endorsed by Rockstar Games.
