Gameplay content

Stats

Defining the numbers people have, how they drift, how difficulty scales them, and where they show up.

Every number a person has (mood, energy, fitness, money, rapport with the player, how much pet food is in the cupboard) is a stat, defined by a .ulstat file anywhere in your mod.

lua-- Stats/ynl_drunk.ulstat
return {
	STAT_ID = "ynl_drunk",
	STAT_NAME = "Drunk",
	STAT_DESCRIPTION = "How much you have had. Wears off over the day.",
	PLAYER_ONLY = false,
	NPC_ONLY = false,
	HIGHER_BETTER = "false",
	STAT_MIN = 0,
	STAT_MAX = 100,
	DEFAULT_VALUE = 0,
	DAILY_CHANGE = -40,
}

Fields

Field Meaning
STAT_ID The id scripts and actions use. Name the file after it (ynl_drunk.ulstat): some screens find stats by file name.
STAT_NAME, STAT_DESCRIPTION Shown to the player.
STAT_MIN, STAT_MAX Every change is clamped into this range (no maximum when STAT_MAX is not above STAT_MIN).
DEFAULT_VALUE The value people start with.
DAILY_CHANGE Added every game day (negative to decay). 1/365 on age is how people age.
HOURLY_CHANGE Optional: added for every game hour the player lives through (actions, scenes, sleep), for needs that climb through the day. hunger uses 4. The player only.
HOURLY_EFFECTS Optional: what the stat does to others while it is past a threshold, per hour. { { ABOVE = 80, STAT = "energy", AMOUNT = -2 } }; BELOW instead of ABOVE for a stat that hurts when low. The player only. See Needs.
HIGHER_BETTER "true", "false", or "no_difficulty_adjustment". See below.
PLAYER_ONLY, NPC_ONLY Whether the character creator shows the stat for the player / for NPCs.
CATEGORY Optional: the section of the character creator the stat is listed under (skills, body, mind, social, career, money, attraction, sexuality, possessions, progress). Missing = other. A mod's story state (a counter, a stage) belongs in progress, which starts folded. See Creator sections.
HIDDEN Optional, true (or EDITABLE = false): never shown in the character creator, so a stat your mod gates content on cannot be set at creation.
warning

A STAT_MAX that is not above STAT_MIN (legacy files write 0 and 0) means no maximum: the stat is only kept at or above STAT_MIN. That is how sentence and the Stock stats count up.

note

DAILY_CHANGE is applied to the player and every known person, whether they are in the world that day or not. PLAYER_ONLY and NPC_ONLY only affect what the character creator shows: the stat still exists on everyone.

Needs

A need is a short-term stat that drifts by the hour and costs something when you neglect it. The base game's is hunger (ul_Base/Stats/Shortterm/hunger.ulstat):

luaHOURLY_CHANGE = 4,
HOURLY_EFFECTS = {
	{ ABOVE = 60, STAT = "mood", AMOUNT = -0.5 },
	{ ABOVE = 80, STAT = "mood", AMOUNT = -1 },
	{ ABOVE = 80, STAT = "energy", AMOUNT = -2 },
	{ ABOVE = 95, STAT = "energy", AMOUNT = -3 },
},

Actions bring it back down with an ordinary effect ({ STAT = "hunger", AMOUNT = -45, TRIGGER = "END" } on a meal). Every stat in Stats/Shortterm/ gets a bar on the map screen, with the icon Map/Icons/Stats/<id>.png, so a mod that adds thirst or hygiene the same way needs no other change.

Difficulty

HIGHER_BETTER tells the game which direction is good for the player:

  • "true": more is better (fitness, mood). On harder difficulty, gains are smaller and losses larger.
  • "false": less is better (drunkenness, hunger). The same, mirrored.
  • "no_difficulty_adjustment": never scaled. Use it for counters, stock and anything that is not a "good" or "bad" number.

The scaling applies to action effects and to Actor:modifyStat in scenes (unless the scene calls Game:PreciseModify()). setStat is never scaled.

The map's player panel also uses it to flag a stat as critical: below 25 % of its range when higher is better, above 75 % when lower is better.

Where stats show up

Where Which stats
Needs bars on the map's player panel Every stat in a Stats/Shortterm/ folder
Chips under the needs bars The ids listed in Map/player_panel.json (see below)
Character creator Stats not excluded by PLAYER_ONLY / NPC_ONLY / HIDDEN, grouped by CATEGORY
Anywhere else Wherever your scenes and UI read them

The base game organises its stats in ul_Base/Stats/: Shortterm (needs: mood, energy, arousal, intoxication), Longterm (skills and traits: fitness, intelligence, interpersonal, karma…), Relationship (rapportwithplayer, attractiontoplayer), SexPreferences (likes_*), Stock (what the player owns: stock_coffee, car_value…), plus money and age.

Creator sections

Stats/categories.json (in ul_Base) names the sections, orders them, and says which page each one is on:

json{ "categories": {
    "skills":     { "label": "Skills",     "order": 0 },
    "attraction": { "label": "Attraction", "order": 6, "page": "preferences" },
    "progress":   { "label": "Story Progress", "order": 9, "collapsed": true }
} }

page: "preferences" lists the section on Life & Stats > Preferences instead of Skills; collapsed starts it folded. Ship your own Stats/categories.json to add a section or relabel one; a later mod wins per id. A CATEGORY no file names still gets a section, titled after its id.

The player panel chips

Map/player_panel.json in any mod adds or removes stat chips:

json{
  "stats": ["ynl_drunk"],
  "remove": ["karma"]
}

Each chip shows Map/Icons/Stats/<id>.png and the value. Ship the icon in your mod.

Using stats

  • In scenes: Player:getStat, Actor:modifyStat, Actor:setStat (People).
  • In actions: EFFECTS and CONDITIONS (Actions).
  • In routines: AI:stat (NPC routines).

A stat nobody defined can still be set from a script; it simply has no bounds, no default and no drift. Define a .ulstat for anything that matters.

Modifiers

A bonus that must come off exactly as it went on (a perk, something worn, a course of treatment) is a modifier, not a change to the stat:

luaPlayer:addStatModifier("mymod_glasses", { intelligence = 2, attractiveness = 1 })
Player:addStatModifier("mymod_cast", "fitness", -10)   -- one stat
Player:getStat("intelligence")      -- with every modifier and worn garment
Player:getBaseStat("intelligence")  -- without
Player:removeStatModifier("mymod_glasses")

A modifier sits on top of the base value under an id your mod picks (the same id again replaces it), is saved with the person, and is never written into the base, so a clamp at 100 cannot eat part of it. getStatModifiers() lists them all, perks included (perk:<id>, see Perks); a garment's Stats: work the same way while it is worn (Clothing).

setStat takes the value with modifiers, the way getStat returns it: Player:setStat(s, Player:getStat(s) + 1) moves the base by one and leaves the bonuses where they are.

A bonus that should wear off on its own is a buff: Player:addBuff({ energy = 8 }, 240) raises the stats for that many game minutes, then takes it back, so a coffee's lift ends in its dip. Each call stacks, and a buff is saved with the player. It is the scene version of an action's BUFF effect, for when the amount depends on a choice inside the scene.

Changing a base stat

Ship a .ulstat with the same STAT_ID in a mod loaded after the one that defines it. The whole definition is replaced, so copy every field. Example: ul_Pets redefines stock_petfood with a real maximum.