CityRP Documentation Repository

MESKA

Last updated

MESKA is CityRP's Metrics, Skills and Achievements framework. It stores player activity as metrics, derives skill experience and levels from those metrics, and evaluates achievements against persistent skill statistics.

Startup lifecycle

core/libraries/meska/init.lua loads MESKA in this order:

  1. Shared constants and helpers.
  2. Database and cache providers.
  3. Configuration and timers.
  4. Daily bonus, custom bonus, metrics, skills and achievements.
  5. MESKA's Garry's Mod hooks.

After CityRP reports DBConnected, MESKA:

  1. loads achievement definitions;
  2. loads daily bonuses;
  3. synchronizes configured metrics;
  4. emits MESKA::ACHIEVEMENTS::READY; and
  5. emits MESKA::INTEGRATIONS::SETUP for add-on skills.

New skills must register on MESKA::INTEGRATIONS::SETUP. Registering earlier can race the database connection, while registering later can omit the skill from a player's first metrics provisioning pass.

Skill configuration

meska.skills.setup(name, config) adds four configuration sections:

  • metrics: named counters and products;
  • defaults: minimum and maximum level;
  • perks: level-gated benefits; and
  • guides: Skills-page guide entries.

It also registers the supplied achievements and synchronizes the metric configuration to the database.

Every metric includes:

{
	lvl = 1,
	exp = 1,
	highlight = false,
	requirements = {},
	excludeFromProductEligibility = true
}

exp is the amount of skill XP granted per tracked unit. Metrics used only as achievement counters should use exp = 0.

Persistence

MESKA uses these configured tables:

Table Purpose
meska_metrics Per-skill metric definitions, defaults, perks and guides
meska_metrics_user Player metrics and skill stats stored as JSON
meska_achievements Achievement definitions
meska_achievements_requirements Achievement requirements
meska_achievements_rewards Achievement rewards
meska_achievements_user Awarded achievements
meska_dailybonus Global skill bonuses
meska_custombonus Player-specific skill bonuses

The database schema also uses meska_skills.name as the parent registry for skill names, and meska_achievements_type.name for achievement types. meska_metrics.skill, meska_achievements.skill and meska_achievements.type carry foreign keys to those registries. MESKA creates a missing registry row itself, through meska.skills.ensureRegistered and meska.achievements.ensureType, before it writes metric configuration or an achievement, so a fresh database needs no hand-inserted rows. Neither name column has a unique index, so those helpers check for the row in the query; an INSERT IGNORE against either table adds a duplicate row.

While a skill's registration is in flight, further ensureRegistered calls for it issue no query and queue behind it. An insert that MariaDB aborts as a deadlock victim is retried once and every queued callback then runs once; if the retry also fails, MESKA logs an error naming the skill, runs no callback and tries afresh on the next call.

Player data in meska_metrics_user has two JSON documents:

  • metrics.<skill>.<metric> stores metric counters; and
  • stats.<skill>.exp and stats.<skill>.lvl store experience and level.

meska.metrics.initFor inserts missing JSON paths for a player. meska.skills.loadFor reads those paths into ply.meska.skills and mirrors the result to the client skills network variable.

Tracking and levels

Call:

meska.metrics.track(ply, "skill_name", "metric_name", "+1")

MESKA multiplies the tracked amount by the metric's exp, applies active daily and custom bonuses, updates the player's metric and stats JSON, then calculates:

level = floor((experience ^ (1 / 3)) / 1.4)

The configured minimum and maximum are applied when skills load. A level increase emits MESKA::SKILLS::LEVELUP.

Achievements

Achievement requirements are evaluated by MESKA::Achievements::MeetsRequirements. The important requirement types are:

  • stats: compare skills[skill .. "_lvl"] or another skill statistic; and
  • metrics: compare skills[skill][metric].

MESKA scans connected players every five minutes. Systems may also call meska.achievements.award(ply) after a metric update when immediate feedback is important.

Requirement evaluation must treat a missing skill table as unmet. It must not index through a missing skill, because players connected during a hot reload can temporarily have an older MESKA cache.

Reset and debugging

MESKA does not currently register a chat or console command that resets player achievements, skill XP or metrics. The Car Boosting !boosting debug refresh|status|complete|fail actions exercise active Boosting contracts; they do not revoke MESKA awards or clear persistent MESKA data.

Awarded achievements are rows in meska_achievements_user, while skill metrics and stats are JSON fields in meska_metrics_user. A development reset therefore needs a deliberately scoped database operation for one SteamID and the affected skill or achievement, followed by a reconnect or explicit cache reload. This should not be exposed as an unrestricted live-server command.

Base achievements

The general and fishing achievements live in libraries/meska/achievements/server/base.lua and are written to the database on every boot, before plugin skills register. Code is the source of truth: an edit there reaches Live on its next boot, and a hand edit in the database does not survive one. Each entry carries the id it holds on Live, so awarded rows keep pointing at the same achievement on every database. Seasonal achievements added by hand are untouched, since the sync only writes the aliases it knows.

Boosting Skill

Car Boosting registers boosting as its own skill. The Skills web page formats that registry entry as Boosting Skill and groups Boosting achievements under it.

The integration first ensures this parent row exists through meska.skills.ensureRegistered("boosting", ...). Only after that callback does it register metrics and achievements. It then re-provisions any connected player whose cache predates the new skill.

This order prevents:

meska_metrics_meska_skills_name_fk

from rejecting the meska_metrics insert. MESKA's metric-requirement hook also checks the intermediate skill table, preventing the later nil access previously reported at hooks.lua:194.

Adding another skill

  1. Pick a lowercase stable skill identifier.
  2. Register on MESKA::INTEGRATIONS::SETUP; meska.skills.setup creates the meska_skills registry row.
  3. Give counter-only metrics exp = 0.
  4. Use unique achievement aliases.
  5. Re-provision connected players if hot reload is supported.
  6. Confirm the skill, guides and achievements appear on the Skills page.
  7. Reconnect and confirm metrics, XP, levels and awards persist.
  8. Test with a player cache that does not yet contain the new skill.